Почему 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 в комментарии, а 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
которого, например, концы имен файлов помечены '\0'
символом ASCII NUL .
gets()
Buffer_overflow_attack