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

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

Я понимаю это

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

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

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

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

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

11 ответов


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

вместо gets, вы хотите использовать fgets, в котором имеется подпись

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

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

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


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

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

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

исключенный стандарт ISO/IEC 9899:2011 C11gets() как стандартная функция, которая является хорошей вещью™ (она была официально отмечена как "устаревшая" и "устаревшая" в 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 ()

As все остальные говорили, каноническая альтернатива gets() is 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] = '';
        return buffer;
    }
    return 0;
}

или, лучше:

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

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

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

остаточная проблема заключается в том, как сообщить о трех различных состояниях результата - EOF или error, line read and not truncated, и partial line read but data was truncated.

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


есть еще 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] имеет значение null символ и символы читаются и отбрасываются из stdin пока символ новой строки не будет прочитан, или конец файла или возникает ошибка чтения.

описание

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

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

рекомендуется

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

25) на ' персонаж, например.


, потому что gets не делает никакой проверки при получении байтов из stdin и положить их куда-нибудь. Простой пример:

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

gets(array1);

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

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


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

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

в правильнее будет использовать '; 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.

читать из stdin:

char string[512];

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

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

вот почему одна ссылка выдает:

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


Я читал недавно, в USENET post to comp.lang.c, что gets() удаляется из стандарта. WOOHOO

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


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

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

C11 K. 3.5.4.1 The 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.


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

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

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


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

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


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

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

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

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