solucion - desbordamiento de pila
¿Por qué este código es vulnerable a los ataques de desbordamiento de búfer? (5)
int func(char* str)
{
char buffer[100];
unsigned short len = strlen(str);
if(len >= 100)
{
return (-1);
}
strncpy(buffer,str,strlen(str));
return 0;
}
Este código es vulnerable a un ataque de desbordamiento de búfer, y estoy tratando de averiguar por qué.
Estoy pensando que tiene que ver con que
len
sea declarado
short
lugar de
int
, pero no estoy realmente seguro.
¿Algunas ideas?
Aunque esté utilizando
strncpy
, la longitud del corte aún depende del puntero de cadena pasado.
No tiene idea de cuánto dura esa cadena (es decir, la ubicación del terminador nulo en relación con el puntero).
strlen
a
strlen
solo te abre a la vulnerabilidad.
Si quieres estar más seguro, usa
strnlen(str, 100)
.
El código completo corregido sería:
int func(char *str) {
char buffer[100];
unsigned short len = strnlen(str, 100); // sizeof buffer
if (len >= 100) {
return -1;
}
strcpy(buffer, str); // this is safe since null terminator is less than 100th index
return 0;
}
El problema está aquí:
strncpy(buffer,str,strlen(str));
^^^^^^^^^^^
Si la cadena es mayor que la longitud del búfer de destino, strncpy aún la copiará. Está basando el número de caracteres de la cadena como el número a copiar en lugar del tamaño del búfer. La forma correcta de hacer esto es la siguiente:
strncpy(buffer,str, sizeof(buff) - 1);
buffer[sizeof(buff) - 1] = ''/0'';
Lo que esto hace es limitar la cantidad de datos copiados al tamaño real del búfer menos uno para el carácter de terminación nulo. Luego establecemos el último byte en el búfer al carácter nulo como salvaguarda adicional. La razón de esto es porque strncpy copiará hasta n bytes, incluido el nulo de terminación, si strlen (str) <len - 1. Si no, entonces el nulo no se copia y tiene un escenario de bloqueo porque ahora su búfer tiene un no terminado cuerda.
Espero que esto ayude.
EDITAR: Tras un examen adicional y la entrada de otros, sigue una posible codificación para la función:
int func (char *str)
{
char buffer[100];
unsigned short size = sizeof(buffer);
unsigned short len = strlen(str);
if (len > size - 1) return(-1);
memcpy(buffer, str, len + 1);
buffer[size - 1] = ''/0'';
return(0);
}
Como ya conocemos la longitud de la cadena, podemos usar memcpy para copiar la cadena de la ubicación a la que hace referencia str en el búfer. Tenga en cuenta que según la página del manual para strlen (3) (en un sistema FreeBSD 9.3), se establece lo siguiente:
The strlen() function returns the number of characters that precede the terminating NUL character. The strnlen() function returns either the same result as strlen() or maxlen, whichever is smaller.
Lo que interpreto es que la longitud de la cadena no incluye el nulo. Es por eso que copio len + 1 bytes para incluir el nulo, y la prueba verifica para asegurar que la longitud <tamaño del búfer - 2. Menos uno porque el búfer comienza en la posición 0, y menos otro para asegurarse de que haya espacio para el nulo
EDITAR: Resulta que el tamaño de algo comienza con 1 mientras que el acceso comienza con 0, por lo que el -2 anterior era incorrecto porque devolvería un error para cualquier cosa> 98 bytes pero debería ser> 99 bytes.
EDITAR: Aunque la respuesta sobre un corto sin signo es generalmente correcta, ya que la longitud máxima que se puede representar es 65.535 caracteres, en realidad no importa porque si la cadena es más larga que eso, el valor se ajustará. Es como tomar 75,231 (que es 0x000125DF) y enmascarar los 16 bits superiores para obtener 9695 (0x000025DF). El único problema que veo con esto son los primeros 100 caracteres más allá de 65,535, ya que la verificación de longitud permitirá la copia, pero solo copiará hasta los primeros 100 caracteres de la cadena en todos los casos y nulo terminará la cadena . Entonces, incluso con el problema envolvente, el búfer aún no se desbordará.
Esto puede o no en sí mismo representar un riesgo de seguridad dependiendo del contenido de la cadena y para qué lo está utilizando. Si es solo texto directo que es legible para los humanos, entonces generalmente no hay problema. Solo obtienes una cadena truncada. Sin embargo, si es algo así como una URL o incluso una secuencia de comandos SQL, podría tener un problema.
En la mayoría de los compiladores, el valor máximo de un
unsigned short
es 65535.
Cualquier valor anterior que se envuelva, entonces 65536 se convierte en 0 y 65600 se convierte en 65.
Esto significa que las cadenas largas de la longitud correcta (por ejemplo, 65600) pasarán la comprobación y desbordarán el búfer.
Use
size_t
para almacenar el resultado de
strlen()
, no un
unsigned short
, y compare
len
con una expresión que codifique directamente el tamaño del
buffer
.
Así por ejemplo:
char buffer[100];
size_t len = strlen(str);
if (len >= sizeof(buffer) / sizeof(buffer[0])) return -1;
memcpy(buffer, str, len + 1);
La respuesta con el envoltorio es correcta. Pero hay un problema que creo que no se mencionó si (len> = 100)
Bueno, si Len fuera 100, copiaríamos 100 elementos y no tendríamos / 0. Eso claramente significaría que cualquier otra función que dependa de una cadena terminada adecuada iría mucho más allá de la matriz original.
La cadena problemática de C es en mi humilde opinión irresoluble. Es mejor que tenga algunos límites antes de la llamada, pero incluso eso no ayudará. No hay verificación de límites y, por lo tanto, los desbordamientos del búfer siempre pueden y, desafortunadamente, sucederán ...
Más allá de los problemas de seguridad relacionados con la invocación de
strlen
más de una vez, generalmente no se deben usar métodos de cadena en cadenas cuya longitud se conoce con precisión [para la mayoría de las funciones de cadena, solo hay un caso realmente estrecho en el que deberían usarse, en cadenas para las que un se puede garantizar la longitud máxima, pero no se conoce la longitud precisa].
Una vez que se conoce la longitud de la cadena de entrada y se conoce la longitud del búfer de salida, se debe determinar qué tan grande se debe copiar una región y luego usar
memcpy()
para realizar la copia en cuestión.
Aunque es posible que
strcpy
supere a
memcpy()
al copiar una cadena de solo 1-3 bytes más o menos, en muchas plataformas es probable que
memcpy()
sea más del doble de rápido cuando se trata de cadenas más grandes.
Aunque hay algunas situaciones en las que la seguridad vendría a costa del rendimiento, esta es una situación en la que el enfoque seguro
también
es el más rápido.
En algunos casos, puede ser razonable escribir código que no sea seguro contra entradas de comportamiento extraño, si el código que suministra las entradas puede garantizar que se comportarán bien, y si protegerse contra las entradas de comportamiento incorrecto impediría el rendimiento.
Asegurarse de que las longitudes de las cadenas solo se verifiquen una vez mejora
tanto el
rendimiento como la seguridad, aunque se puede hacer una cosa adicional para ayudar a proteger la seguridad incluso al rastrear la longitud de la cadena manualmente: por cada cadena que se espera que tenga un nulo final, escriba explícitamente el nulo final. que esperar que la cadena fuente lo tenga.
Por lo tanto, si uno estuviera escribiendo un
strdup
equivalente:
char *strdupe(char const *src)
{
size_t len = strlen(src);
char *dest = malloc(len+1);
// Calculation can''t wrap if string is in valid-size memory block
if (!dest) return (OUT_OF_MEMORY(),(char*)0);
// OUT_OF_MEMORY is expected to halt; the return guards if it doesn''t
memcpy(dest, src, len);
dest[len]=0;
return dest;
}
Tenga en cuenta que la última instrucción generalmente podría omitirse si la memoria había procesado
len+1
bytes, pero si otra hebra modificara la cadena de origen, el resultado podría ser una cadena de destino sin terminación NUL.