Функция gets() в C
Мне нужна помощь снова! Я подумал, что использовать функцию gets()
довольно круто, потому что она похожа на scanf()
, в которой я мог бы получить ввод с пробелами. Но я читал в одном из потоков (информация о файле информации об учениках), что это нехорошо использовать, поскольку, согласно им, это инструмент дьявола для создания переполнения буфера ( которого я не понимаю)
Если я использую функцию gets()
, я мог бы это сделать. Введите свое имя: Keanu Reeves
.
Если я использую scanf()
, я мог бы это сделать. Введите свое имя: Keanu
Итак, я прислушался к их советам и заменил весь мой код gets()
на fgets()
. Проблема в том, что некоторые из моих кодов больше не работают... есть ли какие-либо функции, кроме gets()
и fgets()
, которые могли бы прочитать всю строку и игнорировать пробелы.
Ответы
Ответ 1
это инструмент дьявола для создания переполнения буфера
Поскольку gets
не принимает параметр длины, он не знает, насколько велик ваш буфер ввода. Если вы переходите в 10-символьный буфер, и пользователь вводит 100 символов - ну, вы получите точку.
fgets
является более безопасной альтернативой gets
, поскольку он принимает длину буфера в качестве параметра, поэтому вы можете вызвать его следующим образом:
fgets(str, 10, stdin);
и он будет читать не более 9 символов.
проблема в том, что некоторые из моих кодов больше не работают
Возможно, из-за того, что fgets
также сохраняет символ новой строки (\n
) в вашем буфере - если ваш код не ожидает этого, вы должны удалить его вручную:
int len = strlen(str);
if (len > 0 && str[len-1] == '\n')
str[len-1] = '\0';
Ответ 2
Как отмечали другие ответы, gets()
не проверяет пространство буфера. В дополнение к случайным проблемам переполнения, эта слабость может использоваться злоумышленниками для создания всех видов хаоса.
Один из первых распространенных червей, выпущенный в 1988 году, использовал gets()
, чтобы пропагандировать себя в Интернете. Здесь интересный отрывок из программы Expert C от Peter Van Der Linden, в которой обсуждается, как это работает:
Ранняя ошибка получает() интернет-червь
Проблемы в 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()
, чтобы заменить запущенное изображение на оболочку. Voilà, теперь вы разговариваете с оболочкой на удаленной машине вместо демона finger
, и вы можете выдавать команды для перетаскивания копии вируса на другую машину.
По иронии судьбы, подпрограмма gets()
- это устаревшая функция, обеспечивающая совместимость с самой первой версией портативной библиотеки ввода-вывода, и ее заменили стандартным вводом-выводом более десяти лет назад. В manpage даже настоятельно рекомендуется использовать fgets()
. Процедура fgets()
устанавливает ограничение на количество прочитанных символов, поэтому оно не будет превышать размер буфера. Демон finger
был защищен с заменой двух строк:
gets(line);
по строкам:
if (fgets(line, sizeof(line), stdin) == NULL)
exit(1);
Это проглатывает ограниченный объем ввода и, следовательно, нельзя манипулировать переписыванием важных мест кем-то, кто запускает программу. Однако стандарт ANSI C не удалял gets()
с языка. Таким образом, хотя эта конкретная программа была защищена, основной дефект в стандартной библиотеке C не был удален.
Ответ 3
Вы можете посмотреть на этот вопрос: Безопасная альтернатива gets()
. Существует ряд полезных ответов.
Вы должны уточнить, почему ваш код не работает с fgets()
. Как объясняют ответы в другом вопросе, вы должны иметь дело с новой строкой, которую gets()
не указывается.
Ответ 4
Чтобы прочитать все слова, используя scanf, вы можете сделать это следующим образом:
Пример:
printf("Enter name: ");
scanf("%[^\n]s",name); //[^\n] is the trick
Ответ 5
Вы можете прочитать несколько полей с scanf()
, чтобы вы могли:
scanf("%s %s\n", first_name, last_name);
Тем не менее, я думаю, что было бы лучше прочитать строку, а затем разбить ее самостоятельно, так как они, возможно, не ввели только имя, или first/middle/last.
Каковы проблемы с fgets()
?
Проблема с gets()
заключается в том, что она возвращает столько символов, сколько пользователь вводит - вы, как вызывающий, не контролируете это. Таким образом, вы можете выделить 80 символов, пользователь может ввести 100 символов, а последние 20 будут списаны с конца выделенной памяти, топая, кто знает что.
Ответ 6
Вы можете использовать scanf
для имитации gets
. Это не очень, хотя.
#include <stdio.h>
#define S_HELPER(X) # X
#define STRINGIZE(X) S_HELPER(X)
#define MAX_NAME_LEN 20
int flushinput(void) {
int ch;
while (((ch = getchar()) != EOF) && (ch != '\n')) /* void */;
return ch;
}
int main(void) {
char name[MAX_NAME_LEN + 1] = {0};
while (name[0] != '*') {
printf("Enter a name (* to quit): ");
fflush(stdout);
scanf("%" STRINGIZE(MAX_NAME_LEN) "[^\n]", name); /* safe gets */
if (flushinput() == EOF) break;
printf("Name: [%s]\n", name);
puts("");
}
return 0;
}
Вам гораздо лучше читать с помощью fgets
и анализировать (при необходимости) с помощью sscanf
.
EDIT, объясняя вызов scanf и окружающий код.
"% [" спецификация преобразования scanf
принимает максимальную ширину поля, которая не включает нулевой ограничитель. Таким образом, массив для хранения ввода должен иметь еще 1 символ, чем читать с помощью scanf.
Чтобы сделать это только с одной константой, я использовал макрос STRINGIZE. С помощью этого макроса я могу использовать константу # define'd как размер массива (для определения переменной) в виде строки (для спецификатора).
Вот еще один аспект, который заслуживает упоминания: flushinput
. При использовании gets
все данные записываются в память (даже при переполнении буфера) вплоть до новой строки. Чтобы подражать этому, scanf
читает ограниченное число символов, но не включает в себя новую строку, и, в отличие от gets
, сохраняет новую строку во входном буфере. Чтобы новая строка была удалена и что делает flushinput
.
Остальная часть кода была в основном для настройки тестовой среды.