
usuario66854
Tengo un fragmento de código escrito por un programador de la vieja escuela :-). 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á mucho
-
Haciendo free(request_buffer)
liberará todos los bytes asignados por malloc, es decir 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 cualquier laguna con este enfoque.

Chris joven
Para responder a sus puntos numerados.
- Si.
- Todos los bytes. Malloc/free no sabe ni se preocupa por el tipo de objeto, solo por el tamaño.
- Estrictamente hablando, es un comportamiento indefinido, pero un truco común admitido 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 característica 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.
Es más, CCG permite matrices de longitud cero como una extensión. Los arreglos de longitud cero son ilegales en C estándar, pero gcc introdujo esta función antes de que C99 nos diera miembros de arreglo flexibles.
En respuesta a un comentario, explicaré por qué el fragmento a continuación 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 un[i] es idéntico a (*((a)+(i))), entonces x->arr[23] es equivalente a (*((x->arr)+(23))). Ahora, 6.5.6#8 (sobre la adición de un puntero y un número entero) dice:
“Si tanto el operando puntero como el resultado apuntan a elementos del mismo objeto de matriz, o uno más allá del último elemento del objeto de matriz, la evaluación no producirá un desbordamiento; de lo contrario, el comportamiento es indefinido.”
Por esta razón, porque x->arr[23] no está dentro de la matriz, el comportamiento no está definido. Todavía podría pensar que está bien porque malloc() implica que la matriz ahora se ha extendido, pero este no es estrictamente el caso. El Anexo informativo J.2 (que enumera ejemplos de comportamiento indefinido) proporciona más aclaraciones con un ejemplo:
Un subíndice de matriz está fuera de rango, incluso si aparentemente se puede acceder a un objeto con el subíndice dado (como en la expresión lvalue a[1][7] dada la declaración en un[4][5]) (6.5.6).
3 – Ese es un truco de C bastante común para asignar una matriz dinámica al final de una estructura. La alternativa sería colocar un puntero en la estructura y luego asignar la matriz por separado, sin olvidar liberarla también. Sin embargo, que el tamaño se fije en 2 MB parece un poco inusual.
Este es un truco estándar de C y no es más peligroso que cualquier otro búfer.
Si está tratando de mostrarle a su jefe que es más inteligente que un “programador de la vieja escuela”, este código no es un caso para usted. La vieja escuela no es necesariamente mala. Parece que el chico de la “vieja escuela” sabe lo suficiente sobre la gestión de la memoria;)

trabajomad3
1) Sí, 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 realizará un seguimiento de la cantidad de memoria asignada con ese valor de puntero y la liberará por completo.
3) Es un truco de lenguaje y un poco dudoso sobre su uso. Todavía está sujeto a desbordamientos de búfer también, solo que los atacantes pueden tardar un poco más en encontrar una carga útil que lo cause. El costo de la ‘protección’ también es bastante alto (¿realmente necesita> 2 MB por búfer de solicitud?). También es muy feo, aunque puede que a tu jefe no le agrade ese argumento 🙂
No creo que las respuestas existentes lleguen a la esencia de este problema. Dices 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 trucos;
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 lo está solucionando con un truco. Parece como si él supiera lo que me está haciendo, el truco está bien hacia el final respetable y práctico de la escala de engaño C.

soy_jorf
En cuanto al n. ° 3, sin más código es difícil de responder. No veo nada malo en ello, a menos que suceda mucho. Quiero decir, no desea asignar fragmentos de memoria de 2 MB todo el tiempo. Tampoco desea 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 lo reescribas por completo. Miraría el uso de cerca, trataría de entender lo que estaba pensando el programador original, buscaría desbordamientos de búfer (como señaló workmad3) en el código que usa esta memoria.
Hay muchos errores comunes que puedes encontrar. Por ejemplo, ¿verifica el código para asegurarse de que malloc() se haya realizado correctamente?

dominicano
El exploit (pregunta 3) realmente depende 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 con la asignación de 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 punto de tener un char[1] es que el NULL final siempre estará en la estructura declarada, lo que significa que puede haber 2 * 1024 * 1024 caracteres en el búfer, y no es necesario contabilizar el NULL con un “+1”. Puede parecer una hazaña pequeña, pero solo quería señalar.
¿No es el mismo patrón que este stackoverflow.com/questions/2060974/dynamic-array-in-struct-c
– Romain Hippeau
26 de abril de 2010 a las 18:37
“los bloques” — Esta pregunta asume que malloc (y free) pueden distinguir los sumandos de su argumento y producir dos “bloques” porque hay un
+
en el cálculo, lo que obviamente es absurdo.–Jim Balter
28 de marzo de 2014 a las 22:41