¿Cómo prevenir scanf causando un desbordamiento de buffer en C?
overflow (6)
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 preferiría poder procesar cualquier cosa que el usuario ingrese. ¿O no se puede hacer de forma segura usando scanf y debería usar fgets?
En su libro The Practice of Programming (que bien 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 ''buffer''. Si necesita más espacio, debe asignar memoria o utilizar 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 de asignación) para las 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 todas las especificaciones de conversión, toda la memoria asignada para las conversiones %ms
-like se libera antes de que la función regrese.
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ás agregar un getchar()
para consumir esto. También verifique si llegó al final de la transmisión:
if (!feof(stdin)) { ...
y eso es todo.
Limitar la duración de la entrada es definitivamente más fácil. Puede aceptar una entrada arbitrariamente larga mediante el uso de un bucle, leyendo en un bit a la vez, reasignando espacio para la cadena según sea necesario ...
Pero eso es mucho trabajo, por lo que la mayoría de los programadores de C simplemente cortan la entrada en una longitud arbitraria. Supongo que ya lo sabe, pero usar fgets () no le permitirá aceptar cantidades arbitrarias de texto, de todos modos necesitará establecer un límite.
No es mucho trabajo hacer una función que esté asignando la memoria necesaria para su cadena. Esa es una pequeña función c que escribí hace un tiempo, siempre la utilizo para leer en cuerdas.
Devolverá la cadena de lectura o si se produce un error de memoria NULL. Pero tenga en cuenta que debe liberar () su cadena y verificar 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;
}
Si está utilizando gcc, puede usar la extensión GNU a
especificador para que scanf () asigne memoria para que usted mantenga la entrada:
int main()
{
char *str = NULL;
scanf ("%as", &str);
if (str) {
printf("/"%s/"/n", str);
free(str);
}
return 0;
}
Editar: como señaló Jonathan, debe consultar las páginas man de scanf
ya que el especificador puede ser diferente ( %m
) y es posible que deba habilitar ciertas definiciones al compilar.
scanf(3)
directamente 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 en el que, si no se encuentran suficientes objetos, más líneas resuelvan el problema, pero ese es el modo predeterminado para scanf. (Si un usuario no sabe ingresar un número en la primera línea, una segunda y tercera líneas probablemente no ayuden).
Al menos si se da cuenta de fgets(3)
usted sabe cuántas líneas de entrada necesitará su programa, y no tendrá desbordamientos de búfer ...