2009-10-25 18 views
61

utilizo este código:¿Cómo prevenir scanf causando un desbordamiento de buffer en C?

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

¿Cuál sería la mejor manera de prevenir un posible desbordamiento del búfer de modo que se puede transmitir 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 independientemente de las entradas del usuario. ¿O no se puede hacer de forma segura usando scanf y debería usar fgets?

Respuesta

4

Directamente utilizando 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 usted fgets(3) sabe cuántas líneas de entrada necesitará su programa, y ​​usted no tendrá ningún desbordamiento de búfer ...

1

Limitar la longitud de la entrada es definitivamente más fácil. Puede aceptar una entrada arbitrariamente larga usando 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 C solo cortan fuera de 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.

+0

Entonces, ¿alguien sabe cómo hacer eso con scanf entonces? – goe

+2

Usar fgets en un bucle puede permitirle aceptar cantidades arbitrarias de texto, simplemente mantenga 'realloc()' ing en su búfer. – bdonlan

9

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

Los descartes encima de la corriente de entrada hasta, pero no incluyendo el (\n) carácter de nueva línea. Deberá agregar un getchar() para consumir esto. También verifique si llegó al final de la corriente:

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

y eso es todo.

+1

¿Podría poner el código 'feof' en un contexto más grande? Pregunto porque esa función a menudo se usa mal. –

26

Si se está empleando gcc, se puede utilizar la GNU-extensión a especificador tener scanf() asignar 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 Jonathan señaló, se debe consultar las páginas del manual scanf ya que el especificador puede ser diferente (%m) y es posible que deba habilitar ciertas definiciones al compilar.

+7

Eso es más un problema de usar glibc (la Biblioteca GNU C) que de usar el Compilador GNU C. –

+1

Y tenga en cuenta que el estándar POSIX 2008 proporciona el modificador 'm' para hacer el mismo trabajo. Ver ['scanf()'] (http://pubs.opengroup.org/onlinepubs/9699919799/functions/scanf.html). Deberá verificar si los sistemas que usa son compatibles con este modificador. –

+3

GNU (como se encuentra en Ubuntu 13.10, en cualquier caso) es compatible con '% ms'. La notación '% a' es un sinónimo de'% f' (en la salida, solicita datos de coma flotante hexadecimales). La página man de GNU para 'scanf()' dice: _ No está disponible si el programa está compilado con 'gcc -std = c99' o gcc -D_ISOC99_SOURCE (a menos que' _GNU_SOURCE' también esté especificado), en cuyo caso el 'a 'se interpreta como un especificador para números de coma flotante (ver arriba) ._ –

49

En su libro The Practice of Programming (que es bien vale la pena leer), Kernighan y Pike discutir este problema, y ​​lo solucionan mediante el uso de snprintf() para crear la cadena con el tamaño del búfer correcta para pasar a la scanf() familia de funciones.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); 
} 

Nota, esto todavía limita la entrada al tamaño provisto 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) del scanf() familia de funciones admite un modificador de formato m (un carácter de asignación asignación) para las entradas de cadena (%s, %c, %[). En lugar de tomar un argumento char *, se necesita un argumento char **, y se asigna el espacio necesario para el valor se 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, a continuación, toda la memoria se asigna para %ms, las conversiones similares se liberan antes de que la función regrese.

+0

¿No es buflen-1? – Sam

+0

@Sam: Sí, debería ser 'buflen-1' - Gracias. Luego debe preocuparse por el flujo insuficiente sin firmar (envolviendo a un número bastante grande), de ahí la prueba 'if'. Me sentiría muy tentado de reemplazarlo con un 'assert()', o respaldarlo con 'assert()' antes del 'if' que se dispara durante el desarrollo si alguien es lo suficientemente descuidado para pasar 0 como el tamaño. No he revisado cuidadosamente la documentación de lo que '% 0s' significa' sscanf() '- la prueba podría ser mejor que' if (buflen <2) '. –

+0

Así 'snprintf' escribe algunos datos en un buffer de cadena, y' sscanf' lee de esa cadena creada. ¿Dónde exactamente esto reemplaza 'scanf' en que lee de stdin? – krb686

1

No es mucho trabajo realizar una función que asigne 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 NULO. 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; 
} 
+0

'sizeof (char)' es por definición '1'. No lo necesitas aquí. – RastaJedi

Cuestiones relacionadas