2011-09-06 14 views
27

Deseo saber cuál de estas dos opciones es el más seguro de usar:¿Cuál de sprintf/snprintf es más seguro?

#define MAXLEN 255 
char buff[MAXLEN + 1] 
  1. sprintf(buff, "%.*s", MAXLEN, name)

  2. snprintf(buff, MAXLEN, "%s", name)

Mi entendimiento es que ambos son iguales . Por favor recomiende.

+0

Cambie # 2 a 'MAXLEN + 1' y serán idénticos en lo que escriben en' buff' en todos los casos (el valor de retorno será diferente si 'strlen (name)> 255'). –

Respuesta

0

Ambos darán el resultado que desee, pero snprintf es más genérico, y protegerá su cadena contra sobrepases sin importar la cadena de formato dada.

Además, debido a snprintf (o sprintf para el caso) se suma una final \0, usted debe hacer el búfer de cadena un byte más grande, char buff[MAXLEN + 1].

+1

Eso no es el caso. Del documento snprintf: "Las funciones snprintf() y vsnprintf() escriben en la mayoría de los bytes de tamaño (incluido el byte nulo de terminación ('\ 0')) en str." – Chriszuma

25

Las dos expresiones que dio son no equivalente: sprintf no toma ningún argumento que especifique la cantidad máxima de bytes para escribir; simplemente toma un búfer de destino, una cadena de formato y un montón de argumentos. Por lo tanto, puede escribir más bytes de los que su búfer tiene espacio y, al hacerlo, escribir código arbitrario. El %.*s no es una solución satisfactoria debido a que:

  1. Cuando el especificador de formato se refiere a la longitud, es en referencia a la equivalente de strlen; esta es una medida del número de caracteres en la cadena, no su longitud en la memoria (es decir, no cuenta el terminador nulo).
  2. Cualquier cambio en la cadena de formato (agregar una nueva línea, por ejemplo) cambiará el comportamiento de la versión sprintf con respecto a los desbordamientos del búfer. Con snprintf, se establece un máximo fijo y claro independientemente de los cambios en la cadena de formato o los tipos de entrada.
+0

En realidad, aproximadamente 1, estaba equivocado, especifica el número máximo de caracteres (pero sin contar el '\ 0'). He editado mi respuesta en consecuencia. –

+0

Gracias - Recordé que había un problema con ese especificador de formato, y asumí que tenía razón :-P – azernik

+1

Bueno, todo esto depende del tipo de seguridad de la que se trata la pregunta del OP. Formalmente, un 'sprintf' utilizado correctamente es tan seguro en este caso específico como 'snprintf'. De lo que estás hablando en esta respuesta es la falta de protección del programador perezoso/incompetente. Si el OP preguntaba sobre este lado de la seguridad, no lo sé. – AnT

2

Su declaración de sprintf es correcta, pero no estoy tan seguro de sí mismo como para usarla con fines de seguridad (p. Ej., Perder un carácter críptico y no tiene pantalla) mientras hay snprintf alrededor que se puede aplicar a cualquier formato ... oh wait snprintf no está en ANSI C. Es (¿solo?) C99. Esa podría ser una razón (débil) para preferir la otra.

Bien. También podría usar strncpy, ¿o no?

p. Ej.

char buffer[MAX_LENGTH+1]; 
    buffer[MAX_LENGTH]=0;    // just be safe in case name is too long 
    strncpy(buffer,MAX_LENGTH,name); // strncpy will never overwrite last byte 
+0

¿Y está seguro de que el '%. * S' está disponible en ANSI C? Intenté encontrar la especificación, pero solo pude encontrar una referencia (no confiable) que no especificaba el '. *'. –

+0

@Eli: Especificar la precisión (como aquí) o el ancho del campo como un asterisco ha estado en ANSI C desde el estándar original (1989/1990). 'snprintf()' se agregó en el estándar C99. –

+0

@Michael - para cadena ('% s') también? Eso es bueno saber, gracias. –

8

Para el ejemplo simple en la pregunta, puede que no haya mucha diferencia en la seguridad entre las dos llamadas. Sin embargo, en el caso general snprintf() es probablemente más seguro. Una vez que tiene una cadena de formato más compleja con múltiples especificaciones de conversión, puede ser difícil (o casi imposible) asegurarse de tener la longitud del búfer contabilizada con precisión en las diferentes conversiones, especialmente dado que las conversiones anteriores no producen necesariamente un número fijo. de los caracteres de salida.

Entonces, me quedaría con snprintf().

Otra pequeña ventaja de snprintf() (aunque no está relacionada con la seguridad) es que te dirá qué tan grande de un buffer necesitas.

Una nota final - se debe especificar el tamaño del búfer actual en la llamada snprintf() - que va a manejar la contabilidad para el terminador nulo para usted:

snprintf(buff, sizeof(buff), "%s", name); 
2

diría snprintf() es mucho más mejor hasta que leí este pasaje:

https://buildsecurityin.us-cert.gov/bsi/articles/knowledge/coding/838-BSI.html

Breve resumen es: snprintf() no portátil de su cambio de comportamiento de un sistema a otro. El problema más grave con snprintf() puede ocurrir cuando se implementa snprintf() simplemente llamando al sprintf(). Puede pensar que lo protege del desbordamiento del búfer y baje la guardia, pero puede que no.

Así que ahora todavía estoy diciendo snprintf() más seguro, pero también siendo precavido cuando lo uso.

1

¡La mejor y más flexible forma sería usar snprintf!

size_t nbytes = snprintf(NULL, 0, "%s", name) + 1; /* +1 for the '\0' */ 
char *str = malloc(nbytes); 
snprintf(str, nbytes, "%s", name); 

En snprintf C99, devuelve el número de bytes escritos en la cadena excluyendo el '\0'. Si hubo menos de la cantidad necesaria de bytes, snprintf devuelve el número de bytes que habría sido necesario para expandir el formato (aún excluyendo el '\0'). Pasando snprintf una cadena de 0 de longitud, puede averiguar con anticipación cuánto tiempo debería haber sido la cadena expandida, y usarla para asignar la memoria necesaria.

1

Hay una diferencia importante entre estos dos: la llamada snprintf escaneará el argumento name hasta el final (que termina en NUL) para determinar el valor de retorno correcto. La llamada sprintf por otro lado leerá AT MOST 255 caracteres de name.

Así que si name es un puntero a un no-NUL terminado búfer con al menos 255 caracteres, el snprintf llamada podría salirse de la final del búfer y provocar un comportamiento indefinido (como estrellarse), mientras que la versión sprintf no lo hará .

Cuestiones relacionadas