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

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

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

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

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

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

Ответы

Ответ 1

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

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

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

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

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

Ответ 2

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

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

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

Стандарт C11 ISO/IEC 9899: 2011 исключает gets() в качестве стандартной функции, которая называется A 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 в комментарии, а 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'.

Ответ 3

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

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

gets(array1);

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

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

Ответ 4

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

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

Ответ 5

fgets.

Для чтения из stdin:

char string[512];

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

Ответ 6

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

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

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

Ответ 7

Недавно я прочитал сообщение USENET в comp.lang.c, которое gets() удаляется из стандарта. WooHoo

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

Ответ 8

В C11 (ISO/IEC 9899: 201x), gets() был удален. (Он устарел в ИСО/МЭК 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.

Ответ 9

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

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

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

Ответ 10

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

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

Ответ 11

Функция C становится опасной и была очень дорогостоящей ошибкой. Тони Хоар выделяет это для конкретного упоминания в своем разговоре "Null References: The Billion Dollar Mistake":

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

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

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