Как предотвратить сканирование, вызывающее переполнение буфера в C? - PullRequest
71 голосов
/ 25 октября 2009

Я использую этот код:

while ( scanf("%s", buf) == 1 ){

Каков наилучший способ предотвратить возможное переполнение буфера, чтобы в него могли передаваться строки произвольной длины?

Я знаю, что могу ограничить строку ввода, вызвав, например:

while ( scanf("%20s", buf) == 1 ){

Но я бы предпочел иметь возможность обрабатывать любые пользовательские данные. Или это нельзя сделать безопасно с помощью scanf, а я должен использовать fgets?

Ответы [ 6 ]

56 голосов
/ 25 октября 2009

В своей книге Практика программирования (которую стоит прочитать) Керниган и Пайк обсуждают эту проблему и решают ее, используя snprintf(), чтобы создать строку с правильным размером буфера для переход к семейству функций scanf(). В действительности:

int scanner(const char *data, char *buffer, size_t buflen)
{
    char format[32];
    if (buflen == 0)
        return 0;
    snprintf(format, sizeof(format), "%%%ds", (int)(buflen-1));
    return sscanf(data, format, buffer);
}

Обратите внимание, это по-прежнему ограничивает входные данные размером, указанным как «буфер». Если вам нужно больше места, то вам нужно выделить память или использовать нестандартную библиотечную функцию, которая делает выделение памяти за вас.


Обратите внимание, что версия семейства функций scanf() в POSIX 2008 (2013) поддерживает модификатор формата m (символ назначения-назначения) для строковых входов (%s, * 1016) *, %[). Вместо аргумента char * он принимает аргумент char ** и выделяет необходимое пространство для значения, которое он читает:

char *buffer = 0;
if (sscanf(data, "%ms", &buffer) == 1)
{
    printf("String is: <<%s>>\n", buffer);
    free(buffer);
}

Если функция sscanf() не удовлетворяет всем спецификациям преобразования, то вся память, выделенная для %ms -образных преобразований, освобождается до ее возврата.

29 голосов
/ 25 октября 2009

Если вы используете gcc, вы можете использовать спецификатор GNU-extension a, чтобы scanf () выделил память для хранения ввода:

int main()
{
  char *str = NULL;

  scanf ("%as", &str);
  if (str) {
      printf("\"%s\"\n", str);
      free(str);
  }
  return 0;
}

Редактировать: Как указал Джонатан, вам следует обратиться к справочным страницам scanf, поскольку спецификатор может отличаться (%m), и вам может потребоваться включить определенные определения при компиляции.

9 голосов
/ 25 октября 2009

Большую часть времени комбинация fgets и sscanf делает работу. Другое дело было бы написать свой собственный парсер, если входные данные хорошо отформатированы. Также обратите внимание, что ваш второй пример нуждается в некоторой модификации для безопасного использования:

#define LENGTH          42
#define str(x)          # x
#define xstr(x)         str(x)

/* ... */ 
int nc = scanf("%"xstr(LENGTH)"[^\n]%*[^\n]", array); 

Приведенное выше отбрасывает входной поток до, но не включая символ перевода строки (\n). Вам нужно будет добавить getchar(), чтобы использовать это. Также убедитесь, что вы достигли конца потока:

if (!feof(stdin)) { ...

и это все.

4 голосов
/ 25 октября 2009

Непосредственное использование scanf(3) и его вариантов создает ряд проблем. Как правило, пользователи и неинтерактивные сценарии использования определяются в терминах строк ввода. Редко можно увидеть случай, когда, если не найдено достаточного количества объектов, больше строк решат проблему, но это режим по умолчанию для scanf. (Если пользователь не знал, как ввести число в первой строке, вторая и третья строка, вероятно, не помогут.)

По крайней мере, если вы fgets(3) знаете, сколько строк ввода понадобится вашей программе, и у вас не будет переполнения буфера ...

1 голос
/ 05 апреля 2013

Не так много работы для создания функции, которая выделяет необходимую память для вашей строки. Это небольшая c-функция, которую я написал некоторое время назад, я всегда использую ее для чтения в строках.

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

#define BUFFER 32

char *readString()
{
    char *str = malloc(sizeof(char) * BUFFER), *err;
    int pos;
    for(pos = 0; str != NULL && (str[pos] = getchar()) != '\n'; pos++)
    {
        if(pos % BUFFER == BUFFER - 1)
        {
            if((err = realloc(str, sizeof(char) * (BUFFER + pos + 1))) == NULL)
                free(str);
            str = err;
        }
    }
    if(str != NULL)
        str[pos] = '\0';
    return str;
}
1 голос
/ 25 октября 2009

Ограничение длины ввода определенно проще. Вы можете принять произвольно длинный ввод, используя цикл, считывая бит за раз, перераспределяя место для строки по мере необходимости ...

Но это большая работа, поэтому большинство программистов на Си просто отрезают входные данные произвольной длины. Я предполагаю, что вы уже знаете это, но использование fgets () не позволит вам принимать произвольные объемы текста - вам все равно нужно будет установить ограничение.

...