Функция get () в C - PullRequest
       8

Функция get () в C

28 голосов
/ 03 декабря 2010

Мне снова нужна помощь!Я подумал, что довольно круто использовать функцию gets(), потому что она похожа на scanf(), где я могу получить ввод с пробелами.Но я прочитал в одном из потоков ( обработка студенческого информационного файла ), что его нецелесообразно использовать, потому что, по их мнению, это инструмент дьявола для создания переполнения буфера (что я не понимаю)

Если я использую функцию gets(), я могу это сделать.Введите свое имя: Keanu Reeves.

Если я использую scanf(), я смогу сделать только это.Введите свое имя: Keanu

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

Ответы [ 6 ]

40 голосов
/ 03 декабря 2010

это инструмент дьявола для создания переполнения буфера

Поскольку 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';
14 голосов
/ 03 декабря 2010

Как отмечали другие ответы, 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 не был устранен.

3 голосов
/ 03 декабря 2010

Вы можете посмотреть на этот вопрос: Безопасная альтернатива gets().Есть несколько полезных ответов.

Вы должны быть более точными о том, почему ваш код не работает с fgets().Как объясняют ответы на другой вопрос, вам приходится иметь дело с новой строкой, которую gets() опускает.

2 голосов
/ 30 января 2015

Чтобы прочитать все слова, используя scanf, вы можете сделать это следующим образом

Пример:

printf("Enter name: ");

scanf("%[^\n]s",name);       //[^\n] is the trick
1 голос
/ 03 декабря 2010

Вы можете использовать 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.


РЕДАКТИРОВАТЬ объяснение вызова scanf и окружающего кода.

Спецификация преобразования "% [" scanf принимает максимальную ширину поля, которая не включает нулевой терминатор.Поэтому массив для хранения ввода должен иметь на 1 символ больше, чем считывается с помощью scanf.

Чтобы сделать это только с одной константой, я использовал макрос STRINGIZE.С этим макросом я могу использовать константу # define'd как размер массива (для определения переменной), так и строку (для спецификатора).

Есть еще один аспект, заслуживающий упоминания: flushinput,При использовании gets все данные записываются в память (даже при переполнении буфера) вплоть до новой строки, но не включая ее.Чтобы имитировать это, scanf читает ограниченное количество символов вплоть до, но не включая символ новой строки, и, в отличие от gets, сохраняет новую строку во входном буфере .Так что перевод строки должен быть удален, и именно это flushinput делает.

Остальная часть кода была в основном для настройки среды тестирования.

1 голос
/ 03 декабря 2010

Вы можете прочитать более одного поля с помощью scanf(), поэтому вы можете сделать:

scanf("%s %s\n", first_name,  last_name);

Тем не менее, я думаю, что было бы лучше прочитать строку, а затем разбить ее самостоятельно, поскольку они, возможно, не ввели только имя или имя / отчество / фамилию.

Какие у вас проблемы с fgets()?

Проблема с gets() состоит в том, что он возвращает столько символов, сколько вводит пользователь - вы, как вызывающая сторона, не можете это контролировать. Таким образом, вы можете выделить 80 символов, пользователь может ввести 100 символов, а последние 20 будут записаны в конец выделенной памяти, топая, кто что знает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...