Как отмечали другие ответы, gets()
не проверяет буферное пространство. В дополнение к случайным проблемам с переполнением, эта уязвимость может использоваться злоумышленниками для создания разного рода хаосов.
Один из первых широко распространенных червей, выпущенный в 1988 году, использовал gets()
для распространения в интернете. Вот интересная выдержка из Expert C Programming Питера Ван Дер Линдена, в которой обсуждается, как это работает:
Ранняя ошибка получает () Интернет-червь
Проблемы в C не ограничиваются только языком. Некоторые подпрограммы в стандартной библиотеке имеют небезопасную семантику. Это было наглядно продемонстрировано в ноябре 1988 года программой-червем, которая пробиралась через тысячи машин в сети Интернет. Когда дым рассеялся и расследования были завершены, было установлено, что один из способов распространения червя - слабость в демоне finger
, который принимает по сети запросы о том, кто в данный момент вошел в систему. Демон finger
, in.fingerd
, используется стандартная процедура ввода / вывода gets()
.
Номинальная задача gets()
- прочитать строку из потока. Звонящий говорит ему, куда поместить входящие символы. Но gets()
не проверяет буферное пространство; на самом деле, он не может проверить буферное пространство. Если вызывающая сторона предоставляет указатель на стек и больше входных данных, чем буферное пространство, gets()
успешно перезапишет стек. Демон finger
содержал код:
main(argc, argv)
char *argv[];
{
char line[512];
...
gets(line);
Здесь line
- это 512-байтовый массив, автоматически выделяемый в стеке. Когда пользователь предоставляет больше входных данных, чем это для демона finger
, подпрограмма gets()
будет продолжать помещать его в стек. Большинство архитектур уязвимы для перезаписи существующей записи в середине стека чем-то большим, что также перезаписывает соседние записи. Стоимость проверки каждого стекового доступа на предмет размера и разрешения была бы непомерно высокой в программном обеспечении. Знающий злоумышленник может изменить адрес возврата в записи активации процедуры в стеке, сохранив правильные двоичные шаблоны в строке аргумента. Это перенаправит поток выполнения не туда, откуда он пришел, а в специальную последовательность команд (также аккуратно помещенную в стек), которая вызывает execv()
для замены рабочего образа оболочкой. Вуаля, теперь вы говорите с оболочкой на удаленной машине вместо демона finger
, и вы можете вводить команды для перетаскивания копии вируса на другую машину.
Как ни странно, подпрограмма gets()
является устаревшей функцией, которая обеспечивала совместимость с самой первой версией переносной библиотеки ввода-вывода и была заменена стандартным вводом-выводом более десяти лет назад. Руководство даже настоятельно рекомендует использовать вместо него fgets()
. Подпрограмма fgets()
устанавливает ограничение на количество прочитанных символов, поэтому оно не будет превышать размер буфера. Демон finger
был защищен с помощью двухстрочного исправления, которое заменило:
gets(line);
по строкам:
if (fgets(line, sizeof(line), stdin) == NULL)
exit(1);
Это поглощает ограниченное количество входных данных и, следовательно, не может быть использовано для перезаписи важных мест кем-то, кто запускает программу. Однако стандарт ANSI C не удалил gets()
из языка. Таким образом, хотя эта конкретная программа была защищена, основной дефект стандартной библиотеки C не был устранен.