¿Cómo prevenir scanf causando un desbordamiento de búfer en C?

Yo uso este código:

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

¿Cuál sería la mejor manera de evitar un posible desbordamiento del búfer para que se puedan pasar cadenas de longitudes aleatorias?

Sé que puedo limitar la cadena de entrada llamando por ejemplo:

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

Pero prefiero ser capaz de procesar lo que el usuario ingresa. ¿O no se puede hacer esto de forma segura usando scanf y debería usar fgets?

En su libro The Practice of Programming (que vale la pena leer), Kernighan y Pike discuten este problema, y ​​lo resuelven usando snprintf() para crear la cadena con el tamaño de búfer correcto para pasar a la familia de funciones scanf() . En efecto:

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

Tenga en cuenta que esto todavía limita la entrada al tamaño proporcionado como ‘búfer’. Si necesita más espacio, entonces tiene que hacer una asignación de memoria, o usar una función de biblioteca no estándar que haga la asignación de memoria por usted.


Tenga en cuenta que la versión POSIX 2008 (2013) de la familia de funciones scanf() admite un modificador de formato m (un carácter de asignación-asignación) para entradas de cadena ( %s , %c , %[ ). En lugar de tomar un argumento char * , toma un argumento char ** y asigna el espacio necesario para el valor que lee:

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

Si la función sscanf() no cumple con todas las especificaciones de conversión, entonces toda la memoria que asignó para %ms conversiones similares se liberan antes de que la función regrese.

Si está utilizando gcc, puede usar la extensión GNU, a especificador para que scanf () asigne memoria para que mantenga la entrada:

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

Edición: como Jonathan señaló, debería consultar las páginas de manual de scanf ya que el especificador podría ser diferente ( %m ) y tal vez deba habilitar ciertas definiciones al comstackr.

La mayoría de las veces una combinación de fgets y sscanf hace el trabajo. La otra cosa sería escribir su propio analizador, si la entrada está bien formateada. También tenga en cuenta que su segundo ejemplo necesita un poco de modificación para ser utilizado con seguridad:

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

Lo anterior descarta el flujo de entrada hasta pero sin incluir el carácter de nueva línea ( \n ). Necesitará agregar un getchar() para consumir esto. También verifique si llegó al final de la transmisión:

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

y eso es todo.

El uso directo de scanf(3) y sus variantes plantea una serie de problemas. Normalmente, los usuarios y los casos de uso no interactivos se definen en términos de líneas de entrada. Es raro ver un caso donde, si no se encuentran suficientes objetos, más líneas resolverán el problema, pero ese es el modo predeterminado para scanf. (Si un usuario no sabía cómo ingresar un número en la primera línea, es probable que una segunda y una tercera líneas no ayuden).

Al menos si fgets(3) sabes cuántas líneas de entrada necesitará tu progtwig y no tendrás ningún desbordamiento de búfer …

Limitar la longitud de la entrada es definitivamente más fácil. Podría aceptar una entrada arbitrariamente larga utilizando un bucle, leyendo poco a poco, reasignando espacio para la cadena según sea necesario …

Pero eso es mucho trabajo, por lo que la mayoría de los progtwigdores de C simplemente cortan la entrada en una longitud arbitraria. Supongo que ya lo sabe, pero el uso de fgets () no le permitirá aceptar cantidades arbitrarias de texto, aún tendrá que establecer un límite.

No es tanto trabajo hacer una función que asigne la memoria necesaria para tu cadena. Esa es una pequeña función c que escribí hace algún tiempo, siempre la uso para leer en cadenas.

Devolverá la cadena de lectura o si se produce un error de memoria NULL. Pero tenga en cuenta que debe liberar () su cadena y comprobar siempre su 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; }