2009-10-01 8 views
9

El siguiente código provoca un error y mata mi aplicación. Tiene sentido ya que el búfer tiene solo 10 bytes de longitud y el texto tiene 22 bytes de longitud (desbordamiento de búfer).sprintf_s con un búfer demasiado pequeño

char buffer[10];  
int length = sprintf_s(buffer, 10, "1234567890.1234567890."); 

¿Cómo puedo detectar este error para poder informarlo en lugar de bloquear mi aplicación?

Editar:

Después de leer los comentarios a continuación Fui con _snprintf_s. Si devuelve un valor -1, entonces el búfer no se actualizó.

length = _snprintf_s(buffer, 10, 9, "123456789"); 
printf("1) Length=%d\n", length); // Length == 9 

length = _snprintf_s(buffer, 10, 9, "1234567890.1234567890."); 
printf("2) Length=%d\n", length); // Length == -1 

length = _snprintf_s(buffer, 10, 10, "1234567890.1234567890."); 
printf("3) Length=%d\n", length); // Crash, it needs room for the NULL char 
+0

Pasar el tamaño del búfer y el tamaño del búfer menos uno es obtuso y propenso a errores. Debería preferir la variante que describo a continuación: length = _snprintf_s (buffer, _TRUNCATE, "1234567890.1234567890."); Dado que se omite el primer parámetro de tamaño, el compilador usa la sobrecarga de la plantilla que infiere el tamaño. _TRUNCATE es un valor especial que hace lo que dice. No hay números mágicos, y ahora su código es seguro, sostenible y un buen ejemplo. Si te gusta este comentario y _snprintf_s, entonces debes seleccionar mi respuesta, en lugar de la peligrosa respuesta snprintf/_snprintf. –

Respuesta

5

En lugar de sprintf_s, puede usar snprintf (a.k.a _snprintf en windows).

#ifdef WIN32 
#define snprintf _snprintf 
#endif 

char buffer[10];  
int length = snprintf(buffer, 10, "1234567890.1234567890."); 
// unix snprintf returns length output would actually require; 
// windows _snprintf returns actual output length if output fits, else negative 
if (length >= sizeof(buffer) || length<0) 
{ 
    /* error handling */ 
} 
+4

También hay un snprintf_s. – Joe

+2

Nota: como una cuestión de seguridad, si no hay suficiente espacio, el contenido del búfer puede no tener terminación nula. – Managu

+2

@Managu: si MS reclamó conformidad con C99, lo cual no es cierto, esa afirmación sería falsa; el estándar C99 requiere snprintf() para anular terminar la cadena a menos que la longitud de la cadena sea 0. Sección 7.19.6.5: Si n es cero, no se escribe nada ... De lo contrario, los caracteres de salida más allá del n-1st son descartados de lo que se escribe en la matriz, y un carácter nulo se escribe al final de los caracteres realmente escritos en la matriz. Si se produce una copia entre objetos que se superponen, el comportamiento no está definido. –

0

De MSDN:

La otra diferencia principal entre sprintf_s y sprintf es que sprintf_s toma un parámetro de longitud que especifica el tamaño de la memoria intermedia de salida en caracteres. Si el búfer es demasiado pequeño para el texto que se está imprimiendo, el búfer se establece en una cadena vacía y se invoca el controlador de parámetros no válido. A diferencia de snprintf, sprintf_s garantiza que el búfer tendrá terminación nula (a menos que el tamaño del búfer sea cero).

Por lo tanto, idealmente lo que ha escrito debe funcionar correctamente.

+4

El "controlador de parámetros no válido" predeterminado finaliza el proceso. –

+0

cierto, pero es fácil de instalar uno que no lo hace, lo que da como resultado que sprintf_s devuelva -1 si el buffer es demasiado pequeño – stijn

0

Parece que está escribiendo en MSVC de algún tipo?

Creo que los documentos de MSDN para sprintf_s dicen que se muere, por lo que no estoy muy seguro de poder detectarlo mediante programación.

Como sugirió LBushkin, es mucho mejor que utilices clases que administren las cadenas.

16

Es por diseño. El punto completo de sprintf_s y otras funciones de la familia *_s es capturar los errores de desbordamiento del búfer y tratarlos como violaciones de condiciones previas. Esto significa que en realidad no están destinados a ser recuperables. Esto está diseñado para detectar solo errores: no debe llamar al sprintf_s si sabe que la cadena puede ser demasiado grande para un búfer de destino. En ese caso, primero use strlen para verificar y decidir si necesita recortar.

+0

No estoy de acuerdo. Es completamente razonable llamar a sprintf_s con un búfer de destino que es demasiado pequeño, siempre que use el indicador _TRUNCATE para indicar eso. De acuerdo, técnicamente _TRUNCATE requiere el uso de snprintf_s en lugar de sprintf_s, pero mi punto se mantiene en su mayoría. Usar strlen a menudo no es aplicable o no es conveniente, pero usar _TRUNCATE a menudo es trivial y apropiado. –

+0

Creo que usar 'snprintf_s' es la diferencia crucial, y no es realmente un mero tecnicismo. –

+0

Es una diferencia crucial, definitivamente. Pero creo que su respuesta hace que parezca que la familia de funciones no puede truncar, lo que podría ser engañoso. –

0

Consulte la sección 6.6.1 de TR24731 que es la versión ISO C Committee de la funcionalidad implementada por Microsoft. Proporciona funciones set_constraint_handler(), abort_constraint_handler() y ignore_constraint_handler() funciones.

Hay comentarios de Pavel Minaev que sugieren que la implementación de Microsoft no cumple con la propuesta TR24731 (que es un 'Informe técnico de tipo 2'), por lo que es posible que no pueda intervenir, o puede que tenga que hacer algo diferente de lo que indica TR debe hacerse. Para eso, escudriña MSDN.

+1

Desafortunadamente, MSVC no implementa completamente TR24731, en particular, no implementa específicamente las funciones a las que hace referencia (también, sus nombres también terminan con '_s' - es decir' set_constraint_handler_s'). –

+1

Pero según http://msdn.microsoft.com/en-us/library/ksazx244%28VS.80%29.aspx hay una función _set_invalid_parameter_handler() que se puede usar para cambiar el comportamiento predeterminado de abortar el programa . –

5

Esto funciona con VC++ y es aún más seguro que snprintf (y ciertamente más seguro que _snprintf):

bandera
void TestString(const char* pEvil) 
{ 
    char buffer[100]; 
    _snprintf_s(buffer, _TRUNCATE, "Some data: %s\n", pEvil); 
} 

El _TRUNCATE indica que la cadena debe ser truncado. De esta forma, el tamaño del búfer no se transfiere realmente, lo que (¡paradójicamente!) es lo que lo hace tan seguro. El compilador utiliza magia de plantilla para inferir el tamaño del búfer lo que significa que no se puede especificar incorrectamente (un error sorprendentemente común). Esta técnica se puede aplicar para crear otras envolturas de cuerdas seguras, como se describe en la publicación de mi blog aquí: https://randomascii.wordpress.com/2013/04/03/stop-using-strncpy-already/

+0

al mirar la documentación de MSDN de _snprintf_s, parece que olvidó un argumento en la llamada a _snprintf_s. Este argumento debería aparecer entre el buffer y _TRUNCATE y se llama sizeOfBuffer – user1741137

+1

No he olvidado un argumento, ese código se compila y es perfectamente seguro. Necesita volver a leer la documentación. Estoy utilizando una anulación de plantilla para _snprintf_s que le dice al compilador que infiera el tamaño del búfer. He visto cientos de lugares donde los programadores han pasado explícitamente en un tamaño de búfer y han pasado en el tamaño * wrong *. Solo haciendo que el compilador infiera el tamaño del búfer se puede evitar este grave error. Aludí a esta técnica en el artículo al que me he vinculado en mi solución. Muy recomendado. –

Cuestiones relacionadas