c++ - snprintf - entendiendo los peligros de sprintf(…)
sprintf r (8)
OWASP dice:
"Las funciones de la biblioteca C, como strcpy (), strcat (), sprintf () y vsprintf () operan en cadenas terminadas en nulo y no realizan verificación de límites".
sprintf escribe datos formateados en la cadena int sprintf (char * str, const char * format, ...);
Ejemplo:
sprintf(str, "%s", message); // assume declaration and
// initialization of variables
Si entiendo el comentario de OWASP, entonces los peligros de usar sprintf son que
1) si la longitud del mensaje > longitud de str , hay un desbordamiento de búfer
y
2) si el mensaje no termina en nulo con /0
, entonces el mensaje podría copiarse en str más allá de la dirección de memoria del mensaje , provocando un desbordamiento del búfer
Por favor confirme / niegue. Gracias
Ambas afirmaciones son correctas.
Hay un problema adicional no mencionado. No hay comprobación de tipo en los parámetros. Si no concuerda con la cadena de formato y los parámetros, podría producirse un comportamiento indefinido e indeseable. Por ejemplo:
char buf[1024] = {0};
float f = 42.0f;
sprintf(buf, "%s", f); // `f` isn''t a string. the sun may explode here
Esto puede ser particularmente desagradable para depurar.
Todo lo anterior lleva a muchos desarrolladores de C ++ a la conclusión de que nunca debes usar sprintf
y sus hermanos. De hecho, hay instalaciones que puede utilizar para evitar todos los problemas anteriores. Uno, streams, está integrado en el lenguaje:
#include <sstream>
#include <string>
// ...
float f = 42.0f;
stringstream ss;
ss << f;
string s = ss.str();
... y otra opción popular para aquellos que, como yo, aún prefieren usar sprintf
provienen de las bibliotecas de formatos de boost :
#include <string>
#include <boost/format.hpp>
// ...
float f = 42.0f;
string s = (boost::format("%1%") %f).str();
¿Deberías adoptar el mantra "nunca uses sprintf"? Decide por ti mismo. Generalmente hay una mejor herramienta para el trabajo y, dependiendo de lo que estés haciendo, sprintf
podría ser esa.
Casi he dicho un pequeño ejemplo de cómo podría deshacerse de la declaración del tamaño del búfer para el sprintf (¡si es que tenía la intención, por supuesto!) Y no se incluyó snprintf ...
Nota : Este es un ejemplo de APÉNDICE / CONCATENACIÓN, eche un vistazo here
Es muy importante recordar que sprintf () agrega el carácter ASCII 0 como terminador de cadena al final de cada cadena. Por lo tanto, el búfer de destino debe tener al menos n + 1 bytes (para imprimir la palabra "HOLA", se requiere un búfer de 6 bytes, NO 5)
En el siguiente ejemplo, puede que no sea obvio, pero en el búfer de destino de 2 bytes, el segundo byte se sobrescribirá con un carácter ASCII 0. Si solo se asignara 1 byte para el búfer, esto causaría un desbordamiento del búfer.
char buf[3] = {''1'', ''2''};
int n = sprintf(buf, "A");
También tenga en cuenta que el valor de retorno de sprintf () NO incluye el carácter de terminación nula. En el ejemplo anterior, se escribieron 2 bytes, pero la función devuelve ''1''.
En el siguiente ejemplo, el primer byte de la variable de miembro de clase ''i'' se sobrescribiría parcialmente con sprintf () (en un sistema de 32 bits).
struct S
{
char buf[4];
int i;
};
int main()
{
struct S s = { };
s.i = 12345;
int num = sprintf(s.buf, "ABCD");
// The value of s.i is NOT 12345 anymore !
return 0;
}
La función sprintf, cuando se usa con ciertos especificadores de formato, presenta dos tipos de riesgos de seguridad: (1) no debería escribir memoria; (2) la memoria de lectura no debería. Si snprintf se usa con un parámetro de tamaño que coincide con el búfer, no escribirá nada que no deba. Dependiendo de los parámetros, todavía puede leer cosas que no debería. Dependiendo del entorno operativo y qué más está haciendo un programa, el peligro de lecturas incorrectas puede o no ser menos grave que el de las escrituras incorrectas.
Sí, es principalmente una cuestión de desbordamientos de búfer. Sin embargo, esos son asuntos muy serios hoy en día, ya que los desbordamientos de búferes son el principal vector de ataque utilizado por los craqueadores del sistema para burlar el software o la seguridad del sistema. Si expone algo como esto a la entrada del usuario, es muy probable que esté entregando las llaves de su programa (o incluso de su computadora) a los crackers.
Desde la perspectiva de OWASP, simulemos que estamos escribiendo un servidor web y usamos sprintf para analizar la entrada que un navegador nos pasa.
Ahora, supongamos que alguien malintencionado pasa a nuestro navegador web una cadena mucho más grande de lo que cabe en el búfer que elegimos. Sus datos adicionales en cambio sobrescribirán los datos cercanos. Si lo hace lo suficientemente grande, algunos de sus datos se copiarán a través de las instrucciones del servidor web en lugar de sus datos. Ahora él puede hacer que nuestro servidor web ejecute su código .
Su interpretación parece ser correcta. Sin embargo, su caso # 2 no es realmente un desbordamiento de búfer. Es más una violación de acceso a la memoria. Eso es solo terminología, sin embargo, sigue siendo un gran problema.
Sus 2 conclusiones numeradas son correctas, pero incompletas.
Existe un riesgo adicional:
char* format = 0;
char buf[128];
sprintf(buf, format, "hello");
Aquí, el format
no está terminado en NULL. sprintf()
tampoco lo comprueba.
Usted tiene razón en ambos problemas, aunque en realidad ambos son el mismo problema (que es acceder a datos más allá de los límites de una matriz).
Una solución a su primer problema es utilizar snprintf
, que acepta un tamaño de búfer como argumento.
Una solución a su segundo problema es dar un argumento de longitud máxima a s[n]printf
. Por ejemplo:
char buffer[128];
snprintf(buffer, sizeof(buffer), "This is a %.4s/n", "testGARBAGE DATA");
// strcmp(buffer, "This is a test/n") == 0
Si desea almacenar la cadena completa (por ejemplo, en el caso de sizeof(buffer)
es demasiado pequeño), ejecute snprintf
dos veces:
// Behaviour is different in SUSv2; see
// "conforming to" section of man 3 sprintf
int length = snprintf(NULL, 0, "This is a %.4s/n", "testGARBAGE DATA");
++length; // +1 for null terminator
char *buffer = malloc(length);
snprintf(buffer, length, "This is a %.4s/n", "testGARBAGE DATA");
(Probablemente puede encajar esto en una función usando va
.)