Regla 0x3014h: Prohibición de doble liberación de memoria (double free) sobre el mismo puntero
Memoria, punteros y tipos (0x30XX)
0x3014h: Prohibición de doble liberación de memoria (double free) sobre el mismo puntero¶
Enunciado normativo¶
NO DEBE invocarse
freedos veces sobre el mismo bloque de memoria. Cada bloque reservado con el asignador DEBE liberarse exactamente una vez, aunque existan varios punteros que lo referencien.
¿Por qué existe esta regla?¶
El problema¶
El asignador de memoria (glibc, jemalloc, mimalloc) mantiene estructuras
auxiliares con la lista de bloques libres y ocupados. free(ptr) devuelve el
bloque a esa lista y puede fusionarlo con vecinos. Invocar free otra vez sobre
la misma dirección hace que el asignador procese dos veces un bloque que ya
está libre: intenta fusionarlo consigo mismo, corrompe la lista y, en la mayoría
de las implementaciones, aborta el proceso con SIGABRT y el mensaje
double free or corruption. El error es frecuente con varios caminos de error,
con punteros compartidos por alias o dentro de un lazo.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Aborto inmediato | glibc detecta la corrupción y llama a abort() (SIGABRT). |
| Corrupción de heap | Si no se detecta, la lista de bloques queda inconsistente. |
| Seguridad | El double free es una vulnerabilidad clásica de explotación. |
Fundamento en el estándar y en la cátedra¶
C11 §7.22.3.3 exige que el argumento de free sea un puntero devuelto por el
asignador o NULL; liberar dos veces el mismo bloque es comportamiento
indefinido. La cátedra adopta la defensa de 0x3002h: Liberá siempre la memoria dinámica y asigná NULL al puntero para mitigar punteros colgantes: anular el puntero
inmediatamente después de liberarlo convierte el segundo free en free(NULL),
que el estándar define como no-op.
Alcance y excepciones¶
Aplica a free y a las funciones que liberan por dentro (destructores de TAD).
No aplica a free(NULL), que es válido y puede repetirse sin consecuencias.
Tampoco exime de liberar: la regla prohíbe la liberación doble, no la liberación
única. Si dos módulos comparten propiedad, el contrato debe decidir cuál libera
(ver 0x3006h: Documentá la propiedad de los recursos al utilizar punteros).
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — Doble free consecutivo¶
char *mensaje = malloc(64);
if (mensaje == NULL)
{
return -1;
}
copiar(mensaje, "hola");
free(mensaje);
free(mensaje);Por qué falla: el segundo free procesa un bloque ya devuelto. glibc aborta de
inmediato con double free or corruption. Anular la variable después del primer
free habría hecho que el segundo sea inofensivo.
❌ Contraejemplo 2 — Doble free por alias¶
nodo_t *nodo = lista_extraer(lista);
nodo_t *copia = nodo;
procesar(copia);
free(nodo);
free(copia);Por qué falla: nodo y copia apuntan al mismo bloque. Liberar con uno deja al
otro colgando, y el segundo free reincide sobre el mismo bloque. La anulación
de un alias no anula el otro: hay que anular todos o liberar una sola vez.
✅ Ejemplo conforme 1 — Liberar y anular¶
char *mensaje = malloc(64);
if (mensaje == NULL)
{
return -1;
}
copiar(mensaje, "hola");
free(mensaje);
mensaje = NULL;Tras la anulación, un free(mensaje) accidental sería free(NULL) y no haría
nada. El patrón es la defensa directa contra el doble free y coincide con
0x3002h: Liberá siempre la memoria dinámica y asigná NULL al puntero para mitigar punteros colgantes.
✅ Ejemplo conforme 2 — Un solo dueño en el camino de error¶
resultado_t *crear_resultado(void)
{
char *nombre = malloc(64);
resultado_t *resultado = malloc(sizeof(*resultado));
if (nombre == NULL || resultado == NULL)
{
free(nombre);
free(resultado);
return NULL;
}
resultado->nombre = nombre;
return resultado;
}Cada bloque se libera una sola vez, aun en el camino de error. free(NULL) es
seguro, de modo que si sólo una de las reservas falló, la otra igual se libera.
La propiedad queda clara y no hay liberación repetida.
⚠️ Casos límite¶
free(NULL)repetido: permitido; no es doble liberación.Estructuras compartidas: si dos partes pueden liberar, definí un dueño único y hacelo explícito con 0x3006h: Documentá la propiedad de los recursos al utilizar punteros.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | Dos free sobre el mismo puntero sin anulación intermedia. |
gcc / clang | gcc -Wall -Wextra -std=c11 -fanalyzer ... | double-'free' en el análisis de caminos. |
valgrind | valgrind ./programa | Invalid free() / delete / delete[] y double free. |
Checklist de autocontrol¶
¿Cada bloque se libera exactamente una vez?
¿Anulé el puntero tras liberarlo?
¿Anulé todos los alias del mismo bloque?
¿Los caminos de error liberan sin repetir?
¿Definí un dueño único cuando el recurso se comparte?
Reglas relacionadas¶
0x3002h: Liberá siempre la memoria dinámica y asigná NULL al puntero para mitigar punteros colgantes — liberar y anular es la defensa principal.
0x300Fh: Liberá la memoria en el orden inverso a su asignación — el orden inverso evita fugas y liberaciones confusas.
0x3015h: Reallocación segura: no sobreescribir el puntero original directamente —
reallocgestiona el bloque viejo; no lo liberes aparte.0x3006h: Documentá la propiedad de los recursos al utilizar punteros — un dueño documentado previene la liberación duplicada.