Почему функция get так опасна, что ее нельзя использовать?

Когда я пытаюсь скомпилировать код C, который использует gets() функция с GCC,

Я понял

предупреждение:

(.text + 0x34): предупреждение: функция `gets'опасна и не должна использоваться.

Я помню, что это как-то связано с защитой стека и безопасностью, но я не знаю точно, почему?

Может ли кто-нибудь помочь мне удалить это предупреждение и объяснить, почему существует такое предупреждение об использовании gets()?

Если gets() тогда так опасно, почему мы не можем удалить это?

13 ответов

Решение

Для того, чтобы использовать gets безопасно вы должны точно знать, сколько символов вы будете читать, чтобы вы могли сделать свой буфер достаточно большим. Вы будете знать это только в том случае, если точно знаете, какие данные вы будете читать.

Вместо того, чтобы использовать gets, вы хотите использовать fgets, который имеет подпись

char* fgets(char *string, int length, FILE * stream);

(fgets, если он читает всю строку, оставит '\n' в строке; вам придется иметь дело с этим.)

Он оставался официальной частью языка до стандарта ISO C 1999 года, но был официально удален стандартом 2011 года. Большинство реализаций C все еще поддерживают его, но по крайней мере gcc выдает предупреждение для любого кода, который его использует.

Почему gets() опасно

Первый интернет-червь ( Morris Internet Worm) сбежал около 30 лет назад (1988-11-02) и использовал gets() и переполнение буфера как один из его методов распространения от системы к системе. Основная проблема заключается в том, что функция не знает, насколько большой буфер, поэтому она продолжает чтение, пока не найдет новую строку или не встретит EOF, и может переполнить границы заданного буфера.

Вы должны забыть, что когда-либо слышали, что gets() существовала.

Стандарт C11 ISO/IEC 9899:2011 исключен gets() как стандартная функция, которая называется "Good Thing™" (она была официально помечена как "устаревшая" и "устарела" в ISO/IEC 9899:1999/Cor.3:2007 - Техническое исправление 3 для C99, а затем удалена в C11), К сожалению, из-за обратной совместимости он будет храниться в библиотеках в течение многих лет (что означает "десятилетия"). Если бы это было до меня, реализация gets() станет:

char *gets(char *buffer)
{
    assert(buffer != 0);
    abort();
    return 0;
}

Учитывая, что ваш код в любом случае рухнет, рано или поздно лучше решить проблему раньше, чем позже. Я был бы готов добавить сообщение об ошибке:

fputs("obsolete and dangerous function gets() called\n", stderr);

Современные версии системы компиляции Linux выдают предупреждения, если вы ссылаетесь gets() - а также для некоторых других функций, которые также имеют проблемы с безопасностью (mktemp() …)

Альтернативы gets()

fgets()

Как и все остальные, каноническая альтернатива gets() является fgets() указав stdin как файловый поток.

char buffer[BUFSIZ];

while (fgets(buffer, sizeof(buffer), stdin) != 0)
{
    ...process line of data...
}

Что еще никто не упомянул, так это то, что gets() не включает перевод строки, но fgets() делает. Таким образом, вам может понадобиться обертка вокруг fgets() который удаляет символ новой строки:

char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
    if (fgets(buffer, buflen, fp) != 0)
    {
        size_t len = strlen(buffer);
        if (len > 0 && buffer[len-1] == '\n')
            buffer[len-1] = '\0';
        return buffer;
    }
    return 0;
}

Или лучше:

char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
    if (fgets(buffer, buflen, fp) != 0)
    {
        buffer[strcspn(buffer, "\n")] = '\0';
        return buffer;
    }
    return 0;
}

Кроме того, как caf в комментарии caf и paxdiablo показывает в своем ответе: fgets() у вас могут остаться данные в строке. Мой код оболочки оставляет эти данные для чтения в следующий раз; вы можете легко изменить его, чтобы поглотить остальную часть строки данных, если вы предпочитаете:

        if (len > 0 && buffer[len-1] == '\n')
            buffer[len-1] = '\0';
        else
        {
             int ch;
             while ((ch = getc(fp)) != EOF && ch != '\n')
                 ;
        }

Остаточная проблема заключается в том, как сообщать о трех различных состояниях результата - EOF или ошибке, чтение строки не усечено, а частичное чтение строки, но данные были усечены.

Эта проблема не возникает с gets() потому что он не знает, где заканчивается ваш буфер, и весело топчет за его пределами, нанося ущерб вашей красиво улаженной структуре памяти, часто портя возвращаемый стек ( переполнение стека), если буфер размещен в стеке, или растоптывая его. управляющая информация, если буфер выделяется динамически, или копирование данных поверх других ценных глобальных (или модульных) переменных, если буфер статически распределен. Ничто из этого не является хорошей идеей - они воплощают фразу "неопределенное поведение".


Существует также TR 24731-1 (Технический отчет от Комитета по стандарту C), который предоставляет более безопасные альтернативы различным функциям, включая gets():

§6.5.4.1 gets_s функция

конспект

#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);

Время воспроизведения-ограничение

s не должен быть нулевым указателем. n не должен быть равен нулю или быть больше RSIZE_MAX. Символ новой строки, конец файла или ошибка чтения должны возникать при чтении n-1 персонажи из stdin, 25)

3 Если есть нарушение ограничения времени выполнения, s[0] устанавливается на нулевой символ, а символы читаются и удаляются из stdin пока не будет прочитан символ новой строки, или конец файла или ошибка чтения.

Описание

4 gets_s функция читает не более, чем на число символов, указанное n из потока, на который указывает stdin в массиве, указанном s, Никакие дополнительные символы не читаются после символа новой строки (который отбрасывается) или после конца файла. Выброшенный символ новой строки не учитывается в количестве прочитанных символов. Нулевой символ записывается сразу после последнего прочитанного символа в массив.

5 Если обнаружен конец файла и в массив не было прочитано ни одного символа или если во время операции произошла ошибка чтения, то s[0] устанавливается на нулевой символ, а другие элементы s принимать неопределенные значения.

Рекомендуемая практика

6 fgets Функция позволяет правильно написанным программам безопасно обрабатывать входные строки, слишком длинные для хранения в массиве результатов. В общем, это требует, чтобы абоненты fgets обратите внимание на наличие или отсутствие символа новой строки в массиве результатов. Рассмотреть возможность использования fgets (вместе с любой необходимой обработкой, основанной на символах новой строки) вместо gets_s,

25) gets_s функция, в отличие от gets, делает это нарушением ограничения времени выполнения для строки ввода, чтобы переполнить буфер для его хранения. В отличие от fgets, gets_s поддерживает отношения один-к-одному между строками ввода и успешными вызовами gets_s, Программы, которые используют gets ожидайте таких отношений.

Компиляторы Microsoft Visual Studio реализуют приближение к стандарту TR 24731-1, но есть различия между сигнатурами, реализованными Microsoft, и сигнатурами в TR.

Стандарт C11, ISO/IEC 9899-2011, включает TR24731 в Приложении K в качестве необязательной части библиотеки. К сожалению, он редко реализуется в Unix-подобных системах.


getline() - POSIX

POSIX 2008 также предоставляет безопасную альтернативу gets() называется getline(), Он динамически распределяет место для строки, поэтому вам, в конечном итоге, придется ее освободить. Таким образом, он снимает ограничение на длину строки. Он также возвращает длину данных, которые были прочитаны, или -1 (и не EOF!), что означает, что нулевые байты на входе могут быть надежно обработаны. Существует также вариант "выберите свой собственный одиночный символ разделитель" getdelim(); это может быть полезно, если вы имеете дело с выводом из find -print0 где концы имен файлов помечены как ASCII NUL '\0' характер, например.

Так как gets не выполняет никаких проверок при получении байтов от стандартного ввода и их размещении. Простой пример:

char array1[] = "12345";
char array2[] = "67890";

gets(array1);

Теперь, во-первых, вы можете ввести, сколько символов вы хотите, gets не будет заботиться об этом. Во-вторых, байты превышают размер массива, в который вы их помещаете (в данном случае array1) перезапишет все, что они найдут в памяти, потому что gets напишу их. В предыдущем примере это означает, что если вы введете "abcdefghijklmnopqrts" возможно, непредсказуемо, это перезапишет также array2 или что угодно.

Функция небезопасна, потому что предполагает согласованный ввод. НИКОГДА НЕ ИСПОЛЬЗУЙТЕ ЕГО!

Вы не должны использовать gets так как он не может остановить переполнение буфера. Если пользователь вводит больше данных, чем может поместиться в вашем буфере, вы, скорее всего, в конечном итоге будете повреждены или хуже.

На самом деле, ISO на самом деле сделали шаг gets из стандарта C (начиная с C11, хотя в C99 он устарел), который, учитывая, насколько высоко они оценивают обратную совместимость, должен указывать на то, насколько плохой была эта функция.

Правильнее всего использовать fgets функция с stdin дескриптор файла, так как вы можете ограничить количество символов, читаемых пользователем.

Но это также имеет свои проблемы, такие как:

  • дополнительные символы, введенные пользователем, будут выбраны в следующий раз.
  • нет быстрого уведомления о том, что пользователь ввел слишком много данных.

С этой целью почти каждый кодер C в какой-то момент своей карьеры напишет более полезную оболочку fgets также. Вот мой:

#include <stdio.h>
#include <string.h>

#define OK       0
#define NO_INPUT 1
#define TOO_LONG 2
static int getLine (char *prmpt, char *buff, size_t sz) {
    int ch, extra;

    // Get line with buffer overrun protection.
    if (prmpt != NULL) {
        printf ("%s", prmpt);
        fflush (stdout);
    }
    if (fgets (buff, sz, stdin) == NULL)
        return NO_INPUT;

    // If it was too long, there'll be no newline. In that case, we flush
    // to end of line so that excess doesn't affect the next call.
    if (buff[strlen(buff)-1] != '\n') {
        extra = 0;
        while (((ch = getchar()) != '\n') && (ch != EOF))
            extra = 1;
        return (extra == 1) ? TOO_LONG : OK;
    }

    // Otherwise remove newline and give string back to caller.
    buff[strlen(buff)-1] = '\0';
    return OK;
}

с некоторым тестовым кодом:

// Test program for getLine().

int main (void) {
    int rc;
    char buff[10];

    rc = getLine ("Enter string> ", buff, sizeof(buff));
    if (rc == NO_INPUT) {
        printf ("No input\n");
        return 1;
    }

    if (rc == TOO_LONG) {
        printf ("Input too long\n");
        return 1;
    }

    printf ("OK [%s]\n", buff);

    return 0;
}

Он обеспечивает ту же защиту, что и fgets тем, что он предотвращает переполнение буфера, но также уведомляет вызывающую сторону о том, что произошло, и удаляет лишние символы, чтобы они не влияли на вашу следующую операцию ввода.

Не стесняйтесь использовать его по своему усмотрению, поэтому я выпускаю его под лицензией "делай, что хочешь, черт побери":-)

Fgets

Читать со стандартного ввода:

char string[512];

fgets(string, sizeof(string), stdin); /* no buffer overflows here, you're safe! */

Вы не можете удалить функции API, не нарушая API. Если хотите, многие приложения больше не будут компилироваться или запускаться вообще.

Это причина того, что одна ссылка дает:

Чтение строки, переполняющей массив, на который указывает s, приводит к неопределенному поведению. Рекомендуется использовать fgets().

Я недавно прочитал в посте USENETcomp.lang.c, тот gets() удаляется из стандарта. WooHoo

Вы будете рады узнать, что комитет только что проголосовал (как оказалось, единогласно), чтобы убрать get () из черновика.

В C11(ISO/IEC 9899:201x), gets() был удален. (Это устарело в ISO/IEC 9899:1999/Cor.3:2007(E))

В дополнение к fgets()C11 представляет новую безопасную альтернативу gets_s():

C11 K.3.5.4.1 gets_s функция

#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);

Однако в разделе " Рекомендуемая практика " fgets() все еще предпочтительнее.

fgets Функция позволяет правильно написанным программам безопасно обрабатывать входные строки, слишком длинные для хранения в массиве результатов. В общем, это требует, чтобы абоненты fgets обратите внимание на наличие или отсутствие символа новой строки в массиве результатов. Рассмотреть возможность использования fgets (вместе с любой необходимой обработкой на основе символов новой строки) вместо gets_s,

gets() опасно, потому что пользователь может завершить работу программы, набрав слишком много в приглашении. Он не может определить конец доступной памяти, поэтому, если вы выделите слишком мало памяти для этой цели, это может вызвать сбой сегмента и сбой. Иногда кажется маловероятным, чтобы пользователь набрал 1000 букв в приглашении, предназначенном для имени человека, но как программисты мы должны сделать наши программы пуленепробиваемыми. (это также может представлять угрозу безопасности, если пользователь может вывести из строя системную программу, отправив слишком много данных).

fgets() позволяет указать, сколько символов вынимается из стандартного входного буфера, чтобы они не переполняли переменную.

Функция C получает опасно и была очень дорогой ошибкой. Тони Хоар особо отмечает это в своем выступлении "Нулевые ссылки: ошибка в миллиард долларов":

http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare

Целый час стоит посмотреть, но за просмотр его комментариев от 30 минут с конкретной критикой около 39 минут.

Надеемся, что это разжигает ваш аппетит ко всему выступлению, что привлекает внимание к тому, как нам нужны более формальные доказательства правильности в языках и как виноваты языковые разработчики, а не программист. Похоже, что это была весьма сомнительная причина, по которой разработчики плохих языков возлагали вину на программистов под маской "свободы программиста".

Я хотел бы направить искреннее приглашение всем сопровождающим библиотеки C, которые до сих пор gets в их библиотеках "на всякий случай, если кто-то все еще зависит от этого": пожалуйста, замените вашу реализацию на эквивалентную

char *gets(char *str)
{
    strcpy(str, "Never use gets!");
    return str;
}

Это поможет убедиться, что никто не зависит от этого. Спасибо.

Дополнительная информация:

Из man 3 getsв Linux Ubuntu вы увидите (выделено мной):

       DESCRIPTION
       Never use this function.

И из вики cppreference.com здесь () вы увидите: Notes Never use gets().:

Заметки

Функция не выполняет проверку границ, поэтому эта функция чрезвычайно уязвима для атак переполнения буфера. Его нельзя использовать безопасно (если только программа не работает в среде, которая ограничивает то, что может отображаться на stdin). По этой причине эта функция объявлена ​​устаревшей в третьем исправлении к стандарту C99 и полностью удалена в стандарте C11. и являются рекомендуемой заменой.

Никогда не используйте gets().

Как видите, эта функция устарела и полностью удалена в C11 или более поздних версиях.

Использоватьfgets()или жеhttps://en.cppreference.com/w/c/io/getsgets_s()вместо.

В нескольких словах gets()(может) быть опасным, потому что пользователь может ввести что-то большее, чем то, что имеет достаточно места для хранения переменной. Первый ответ говорит о fgets()и почему это безопаснее.

Другие вопросы по тегам