Как предотвратить сканирование, вызывающее переполнение буфера в C?

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

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

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

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

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

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

Ответ 1

В своей книге "Практика программирования" (которую стоит прочитать), Керниган и Пайк обсуждают эту проблему, и они решают ее, используя 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, %c, %[). Вместо принятия аргумента char * он принимает аргумент char ** и выделяет необходимое пространство для значения, которое он читает:

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

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

Ответ 2

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

int main()
{
  char *str = NULL;

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

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

Ответ 3

В большинстве случаев комбинация 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

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

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

Ответ 5

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

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

Ответ 6

Не так много работы, чтобы создать функцию, которая выделяет необходимую память для вашей строки. Это небольшая 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;
}