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


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

я понял это

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

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

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

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

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

11 176

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 C11:2011 исключен gets() как стандартная функция, которая является хорошей вещью™ (она была официально отмечена как "устаревшая" и "устаревшая" в ISO/IEC 9899:1999/Cor.3: 2007 - техническое исправление 3 для С99, а затем снято в С11). К сожалению, он останется в библиотеках в течение многих лет (что означает "десятилетия") по причинам обратной совместимости. Если бы это было до меня, реализация 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] = '';
        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, чтение строки и не усеченное, а частичное чтение строки, Но данные были усечены.

эта проблема не возникает с 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);

Runtime-constraints

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

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

описание

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

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

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

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

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

, потому что 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() по-прежнему популярны.

The 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 минут.

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