¿Malloc() asigna un bloque contiguo de memoria?

9 minutos de lectura

¿Malloc asigna un bloque contiguo de memoria
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:

  1. ¿Malloc siempre asigna bloques contiguos de memoria? porque en este código si los bloques no son contiguos, el código fallará mucho

  2. 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)

  3. ¿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.

  • ¿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


1647581829 265 ¿Malloc asigna un bloque contiguo de memoria
Chris joven

Para responder a sus puntos numerados.

  1. Si.
  2. Todos los bytes. Malloc/free no sabe ni se preocupa por el tipo de objeto, solo por el tamaño.
  3. 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).

  • +1, para las matrices flexibles y de longitud cero. Tal vez también podría agregar que el beneficio de esta práctica es que guarda la memoria para un puntero y la reduce a una sola asignación (costosa).

    – quinmars

    9 de marzo de 2009 a las 8:54

  • No estoy de acuerdo con el comportamiento indefinido. Se garantiza que malloc() devolverá un bloque continuo de memoria para que pueda acceder de manera segura a la memoria más allá de la estructura utilizando aritmética de puntero o índice de matriz; de acuerdo con el estándar, son iguales. Por lo tanto, es un comportamiento definido.

    – qrdl

    13 de marzo de 2009 a las 9:49

  • @qrdl: el estándar prohíbe específicamente el acceso más allá de la matriz. He editado mi publicación para explicar por qué no está definida.

    – Chris joven

    14 de marzo de 2009 a las 5:09

  • @Robert S. Barnes: no está equivocado, pero el diseño físico es completamente irrelevante para el estándar C. Solo importa que aparezca contiguo al programa cuando se accede a él de una manera bien definida. Es igualmente cierto e irrelevante señalar que la memoria puede no ser contigua porque puede abarcar varias piezas de silicio.

    – Chris joven

    15 de octubre de 2009 a las 6:13

  • Para char tipos esto no es UB.

    – R.. GitHub DEJA DE AYUDAR A ICE

    19 de octubre de 2011 a las 15:49

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.

  • Muchas gracias por sus comentarios . básicamente recibimos datos de socket. No sabemos el tamaño exacto que vamos a recibir y lo hemos limitado a 2 MB. los datos que recibimos se copian en esta estructura. Este cambio se hizo porque este era el que tenía el impacto mínimo.

    – usuario66854

    9 de marzo de 2009 a las 7:49

  • @unknown (google), si el tamaño es fijo, también puede cambiar el tamaño de la matriz de 1 a su tamaño fijo. Este truco solo tiene sentido para matrices con longitudes variables.

    – quinmars

    9 de marzo de 2009 a las 11:05

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;)

¿Malloc asigna un bloque contiguo de 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.

1647581830 311 ¿Malloc asigna un bloque contiguo de memoria
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?

1647581830 886 ¿Malloc asigna un bloque contiguo de memoria
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.

  • Además, el estándar no permite arreglos de tamaño 0, aunque algunos compiladores sí.

    – TrayMan

    9 de marzo de 2009 a las 7:42

  • No, no puede; un char * se dirigiría a la memoria en otro lugar por completo, en lugar de contigua a la estructura. Para C99, la declaración adecuada para esto es una matriz de tamaño flexible “paquete char[]”. Pero prácticamente cualquier compilador compatible que también admita la extensión GNU para el tamaño 0.

    – puetzk

    9 de marzo de 2009 a las 14:47

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad