nodos - asignacion indexada
¿Malloc() asigna un bloque contiguo de memoria? (14)
Tengo un código escrito por un programador muy antiguo :-). Es algo parecido a esto
typedef struct ts_request
{
ts_request_buffer_header_def header;
char package[1];
} ts_request_def;
ts_request_def* request_buffer =
malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));
el programador básicamente está trabajando en un concepto de desbordamiento de búfer. Sé que el código parece dudoso. entonces mis preguntas son:
¿Malloc siempre asigna bloques contiguos de memoria? porque en este código si los bloques no son contiguos, el código fallará a lo grande
Al hacer free (request_buffer), liberará todos los bytes asignados por malloc ie sizeof (ts_request_def) + (2 * 1024 * 1024), o solo los bytes del tamaño de la estructura sizeof (ts_request_def)
¿Ve algún problema evidente con este enfoque, necesito discutir esto con mi jefe y me gustaría señalar las lagunas con este enfoque?
1) Sí lo hace, o malloc fallará si no hay un bloque contiguo lo suficientemente grande disponible. (Una falla con malloc devolverá un puntero NULL)
2) Sí lo hará. La asignación de memoria interna hará un seguimiento de la cantidad de memoria asignada con ese valor de puntero y liberará todo.
3) Es un poco un hack de lenguaje, y un poco dudoso sobre su uso. Todavía está sujeto a desbordamientos de búfer, solo puede llevar a los atacantes un poco más para encontrar una carga útil que lo cause. El costo de la "protección" también es bastante elevado (¿realmente necesitas más de 2 MB por buffer de solicitud?). También es muy feo, aunque es posible que tu jefe no aprecie ese argumento :)
3 - Es un truco de C muy común para asignar una matriz dinámica al final de una estructura. La alternativa sería poner un puntero en la estructura y luego asignar la matriz por separado y no olvidar liberarla también. Que el tamaño se fija en 2mb parece un poco inusual.
El exploit (pregunta 3) depende realmente de la interfaz hacia esta estructura tuya. En contexto, esta asignación podría tener sentido, y sin más información es imposible decir si es segura o no.
Pero si te refieres a problemas para asignar memoria más grande que la estructura, este no es un mal diseño de C (ni siquiera diría que es ESA vieja escuela ...;))
Solo una nota final aquí: el hecho de tener un char [1] es que el NULL de terminación siempre estará en la estructura declarada, lo que significa que puede haber 2 * 1024 * 1024 caracteres en el búfer, y usted no tiene que dar cuenta para el NULL por un "+1". Puede parecer una pequeña hazaña, pero solo quería señalar.
En cuanto al # 3, sin más código es difícil de responder. No veo nada malo en eso, a menos que suceda mucho. Quiero decir, no quieres asignar 2mb de fragmentos de memoria todo el tiempo. Tampoco quiere hacerlo innecesariamente, por ejemplo, si solo usa 2k.
El hecho de que no te guste por alguna razón no es suficiente para objetarlo o justificar que se lo vuelva a escribir por completo. Me gustaría ver el uso de cerca, tratar de entender lo que el programador original estaba pensando, mirar de cerca los desbordamientos del búfer (como señaló workmad3) en el código que utiliza esta memoria.
Hay muchos errores comunes que puede encontrar. Por ejemplo, ¿el código comprueba para asegurarse de que malloc () tuvo éxito?
En respuesta a su tercera pregunta.
free
siempre libera toda la memoria asignada en una sola toma.
int* i = (int*) malloc(1024*2);
free(i+1024); // gives error because the pointer ''i'' is offset
free(i); // releases all the 2KB memory
Este es un truco C estándar, y no es más peligroso que cualquier otro buffer.
Si intenta mostrarle a su jefe que es más inteligente que el "muy antiguo programador de la escuela", este código no es un caso para usted. La vieja escuela no necesariamente es mala. Parece que el chico de la "vieja escuela" sabe lo suficiente sobre el manejo de la memoria;)
Este truco común de C también se explica en esta pregunta de (¿alguien puede explicar esta definición de la estructura direccional en Solaris?) .
He visto y usado este patrón con frecuencia.
Su ventaja es simplificar la administración de memoria y así evitar el riesgo de pérdidas de memoria. Todo lo que se necesita es liberar el bloque malloc. Con un buffer secundario, necesitarás dos gratis. Sin embargo, uno debe definir y utilizar una función de destructor para encapsular esta operación, de modo que siempre pueda cambiar su comportamiento, como cambiar a un búfer secundario o agregar operaciones adicionales que se realizarán al eliminar la estructura.
El acceso a los elementos de la matriz también es ligeramente más eficiente, pero eso es cada vez menos significativo con las computadoras modernas.
El código también funcionará correctamente si la alineación de memoria cambia en la estructura con diferentes compiladores, ya que es bastante frecuente.
El único problema potencial que veo es si el compilador permuta el orden de almacenamiento de las variables miembro porque este truco requiere que el campo del paquete permanezca último en el almacenamiento. No sé si el estándar C prohíbe la permutación.
Tenga en cuenta también que el tamaño del búfer asignado probablemente será mayor que el requerido, al menos en un byte con los bytes de relleno adicionales, si los hay.
La respuesta a la pregunta 1 y 2 es Sí
Acerca de la fealdad (es decir, pregunta 3) ¿Qué intenta hacer el programador con esa memoria asignada?
Lo que hay que tener en cuenta aquí es que malloc
no ve el cálculo hecho en este
malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));
Es lo mismo que
int sz = sizeof(ts_request_def) + (2 * 1024 * 1024);
malloc(sz);
Podría pensar que está asignando 2 fragmentos de memoria, y en su mente son "la estructura", "algunos almacenamientos intermedios". Pero malloc no ve eso en absoluto.
Me gustaría añadir que no es común, pero también podría llamarlo una práctica estándar porque la API de Windows está llena de dicho uso.
Compruebe la estructura de encabezado de BITMAP muy común, por ejemplo.
http://msdn.microsoft.com/en-us/library/aa921550.aspx
El último quad RBG es una matriz de 1 tamaño, que depende exactamente de esta técnica.
No creo que las respuestas existentes lleguen a la esencia de este tema. Usted dice que el programador de la vieja escuela está haciendo algo como esto;
typedef struct ts_request
{
ts_request_buffer_header_def header;
char package[1];
} ts_request_def;
ts_request_buffer_def* request_buffer =
malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));
Creo que es poco probable que esté haciendo exactamente eso, porque si eso es lo que quería hacer, podría hacerlo con un código equivalente simplificado que no necesita ningún truco;
typedef struct ts_request
{
ts_request_buffer_header_def header;
char package[2*1024*1024 + 1];
} ts_request_def;
ts_request_buffer_def* request_buffer =
malloc(sizeof(ts_request_def));
Apuesto a que lo que realmente está haciendo es algo como esto;
typedef struct ts_request
{
ts_request_buffer_header_def header;
char package[1]; // effectively package[x]
} ts_request_def;
ts_request_buffer_def* request_buffer =
malloc( sizeof(ts_request_def) + x );
Lo que quiere lograr es la asignación de una solicitud con un tamaño de paquete variable x. Por supuesto, es ilegal declarar el tamaño de la matriz con una variable, por lo que está solucionando esto con un truco. Parece que él sabe lo que me está haciendo, el truco está bien hacia el final respetable y práctico de la escala de trucos de C.
Para responder a tus puntos numerados.
- Sí.
- Todos los bytes. Malloc / free no conoce ni se preocupa por el tipo de objeto, solo el tamaño.
- Es un comportamiento estrictamente indefinido, pero es un truco común respaldado por muchas implementaciones. Vea a continuación otras alternativas.
El último estándar C, ISO / IEC 9899: 1999 (informalmente C99), permite miembros de matriz flexibles .
Un ejemplo de esto sería:
int main(void)
{
struct { size_t x; char a[]; } *p;
p = malloc(sizeof *p + 100);
if (p)
{
/* You can now access up to p->a[99] safely */
}
}
Esta función ahora estandarizada le permitió evitar el uso de la extensión de implementación común, pero no estándar, que describe en su pregunta. Estrictamente hablando, usar un miembro de matriz no flexible y acceder más allá de sus límites es un comportamiento indefinido, pero muchas implementaciones lo documentan y fomentan.
Además, gcc permite matrices de longitud cero como una extensión. Las matrices de longitud cero son ilegales en la norma C, pero gcc introdujo esta característica antes de que C99 nos diera miembros de matriz flexibles.
En una respuesta a un comentario, explicaré por qué el siguiente fragmento es un comportamiento técnicamente indefinido. Los números de sección que cito se refieren a C99 (ISO / IEC 9899: 1999)
struct {
char arr[1];
} *x;
x = malloc(sizeof *x + 1024);
x->arr[23] = 42;
En primer lugar, 6.5.2.1 # 2 muestra que [i] es idéntica a (* ((a) + (i))), por lo que x-> arr [23] es equivalente a (* ((x-> arr) + ( 23))). Ahora, 6.5.6 # 8 (al agregar un puntero y un entero) dice:
"Si tanto el operando del puntero como el resultado apuntan a elementos del mismo objeto de la matriz, o uno más allá del último elemento del objeto de la matriz, la evaluación no producirá un desbordamiento, de lo contrario, el comportamiento no está definido ".
Por esta razón, como x-> arr [23] no está dentro de la matriz, el comportamiento no está definido. Aún puede pensar que está bien porque el malloc () implica que el conjunto ahora se ha extendido, pero este no es el caso estrictamente. El Anexo informativo J.2 (que enumera ejemplos de comportamiento indefinido) proporciona una aclaración adicional con un ejemplo:
Un subíndice de matriz está fuera de rango, incluso si un objeto es aparentemente accesible con el subíndice dado (como en la expresión lvalue a [1] [7] dada la declaración int a [4] [5]) (6.5.6).
Sí. malloc solo devuelve un único puntero: ¿cómo podría decirle a un solicitante que había asignado varios bloques no contiguos para satisfacer una solicitud?