Como evitar scanf causando um estouro de buffer em C?

Eu uso este código:

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

Qual seria a melhor maneira de impedir o possível estouro de buffer, de modo que possa ser passado strings de tamanhos randoms?

Eu sei que posso limitar a string de input chamando por exemplo:

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

Mas prefiro poder processar o que o usuário insere. Ou isso não pode ser feito com segurança usando o scanf e eu deveria usar fgets?

    Em seu livro The Practice of Programming (que vale a pena ser lido), Kernighan e Pike discutem esse problema, e o resolvem usando snprintf() para criar a string com o tamanho correto do buffer para passar para a família de funções scanf() . . Com efeito:

     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); } 

    Observe que isso ainda limita a input para o tamanho fornecido como ‘buffer’. Se você precisar de mais espaço, terá que fazer a alocação de memory ou usar uma function de biblioteca não padrão que faça a alocação de memory para você.


    Observe que a versão POSIX 2008 (2013) da família de funções scanf() suporta um modificador de formato m (um caractere de alocação de atribuição) para inputs de string ( %s , %c , %[ ). Em vez de pegar um argumento char * , é necessário um argumento char ** e aloca o espaço necessário para o valor que ele lê:

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

    Se a function sscanf() não atender a todas as especificações de conversão, toda a memory alocada para conversões semelhantes a %ms será liberada antes que a function retorne.

    Se você estiver usando o gcc, você pode usar a extensão GNU como a especificador para ter scanf () alocar memory para você manter a input:

     int main() { char *str = NULL; scanf ("%as", &str); if (str) { printf("\"%s\"\n", str); free(str); } return 0; } 

    Edit: Como Jonathan apontou, você deve consultar as páginas man scanf como o especificador pode ser diferente ( %m ) e você pode precisar habilitar certas definições ao compilar.

    Na maioria das vezes, uma combinação de fgets e sscanf faz o trabalho. A outra coisa seria escrever seu próprio analisador, se a input estiver bem formatada. Observe também que seu segundo exemplo precisa de um pouco de modificação para ser usado com segurança:

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

    O acima descarta o stream de input até, mas não incluindo o caractere de nova linha ( \n ). Você precisará adicionar um getchar() para consumir isso. Além disso, verifique se você chegou ao final do stream:

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

    e é sobre isso.

    O uso direto do scanf(3) e suas variantes apresenta vários problemas. Normalmente, os usuários e casos de uso não interativos são definidos em termos de linhas de input. É raro ver um caso em que, se objects suficientes não forem encontrados, mais linhas resolverão o problema, mas esse é o modo padrão para o scanf. (Se um usuário não souber digitar um número na primeira linha, uma segunda e terceira linha provavelmente não ajudarão.)

    Pelo menos se você fgets(3) você sabe quantas linhas de input seu programa irá precisar, e você não terá nenhum buffer overflow …

    Limitar o comprimento da input é definitivamente mais fácil. Você poderia aceitar uma input arbitrariamente longa usando um loop, lendo um pouco de cada vez, realocando espaço para a string conforme necessário …

    Mas isso é muito trabalho, então a maioria dos programadores C apenas cortam a input em um comprimento arbitrário. Suponho que você já saiba disso, mas usar o fgets () não permitirá que você aceite quantidades arbitrárias de texto – você ainda precisará definir um limite.

    Não é muito trabalho fazer uma function que aloque a memory necessária para sua string. Essa é uma pequena function c que escrevi há algum tempo, sempre uso para ler em strings.

    Ele retornará a seqüência de caracteres de leitura ou se ocorrer um erro de memory NULL. Mas esteja ciente de que você tem que liberar () sua string e sempre verificar seu valor de retorno.

     #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; }