1. Qué es el espacio de direcciones de un proceso
Cuando el sistema crea un proceso, le entrega un espacio de direcciones virtuales propio: el conjunto de todas las direcciones que ese proceso puede nombrar. Dentro de ese espacio, el cargador del programa y el propio programa colocan distintas cosas en distintas regiones, según su función y sus permisos.
El espacio de direcciones no es una lista de bytes indiferenciados: es una colección de regiones, cada una con una dirección inicial, un tamaño, unos permisos y un origen (un archivo del disco, memoria anónima, la pila...). El núcleo mantiene esa lista y la expone en /proc/PID/maps.
Espacio de direcciones de un proceso es el conjunto de regiones virtuales que el proceso puede usar, cada una con su rango, permisos y origen, administradas por el núcleo.
2. Las regiones típicas
Un programa en C compilado y ejecutado en Linux tiene, como mínimo, estas regiones:
- Código (text). Las instrucciones del programa y de las bibliotecas. Permisos de lectura y ejecución, sin escritura. Es compartible: varios procesos que usan la misma biblioteca comparten los mismos marcos físicos.
- Datos inicializados (data). Variables globales y estáticas con valor inicial. Lectura y escritura.
- BSS. Variables globales y estáticas sin valor inicial (o inicializadas a cero). No ocupa espacio en el archivo ejecutable: el núcleo la pone a cero al cargar. Lectura y escritura.
- Heap. Memoria dinámica obtenida con
malloc,calloc,realloc. Crece hacia arriba (direcciones crecientes). Lectura y escritura. - Regiones mapeadas (mmap). Archivos mapeados, bibliotecas compartidas, memoria anónima. Pueden aparecer en cualquier parte del espacio.
- Pila (stack). Variables locales, parámetros y direcciones de retorno de las funciones. Crece hacia abajo (direcciones decrecientes). Lectura y escritura.
- Regiones del núcleo visibles al usuario.
[vdso],[vvar],[vsyscall]: pequeñas páginas que el núcleo expone para acelerar llamadas al sistema.
3. El orden en un proceso Linux x86-64
En Linux sobre x86-64, el espacio virtual de un proceso de 64 bits se divide aproximadamente así (las direcciones son virtuales):
| Región | Rango típico (con ASLR) | Contenido |
|---|---|---|
| Código + datos | 0x55... (aleatorio) | El binario y sus segmentos |
| Heap | justo después del código | Bloques de malloc |
| Bibliotecas (mmap) | 0x7f... (aleatorio) | libc, libm, etc. |
| Pila | 0x7fff... (aleatorio) | Variables locales, marcos |
| vdso / vvar | cerca de la pila | Trampolín al núcleo |
Los rangos exactos cambian en cada ejecución por ASLR, pero el orden relativo se mantiene: código y heap en la parte baja, bibliotecas y pila en la alta, y un gran hueco libre en medio (espacio no asignado).
4. El formato de /proc/PID/maps
Cada línea de /proc/PID/maps describe una región. El formato es:
inicio-fin permisos desplazamiento mayor:menor inodo ruta
- inicio-fin: rango de direcciones virtuales de la región (hexadecimal).
- permisos:
rlectura,wescritura,xejecución,pprivada (copy-on-write) oscompartida. - desplazamiento: posición dentro del archivo de origen (para mmap de archivos).
- mayor:menor: dispositivo del archivo de origen.
- inodo: número de inodo del archivo.
- ruta: archivo o nombre especial (
[heap],[stack],[vdso], anónimo si está vacío).
5. Experimento: imprimir regiones y leer el mapa
El programa siguiente imprime las direcciones de distintas variables y luego muestra su propio mapa, destacando las regiones de código, datos, heap, pila y mmap:
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
static void funcion(void) { }
int global_init = 99; /* data */
int global_bss; /* BSS */
int main(void)
{
int local = 42; /* pila */
void *heap = malloc(1024); /* heap */
void *mapa = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); /* mmap anónimo */
printf("código : %p\n", (void *)&funcion);
printf("data : %p\n", (void *)&global_init);
printf("BSS : %p\n", (void *)&global_bss);
printf("heap : %p\n", heap);
printf("mmap : %p\n", mapa);
printf("pila : %p\n", (void *)&local);
printf("\n/proc/self/maps:\n");
FILE *f = fopen("/proc/self/maps", "r");
char linea[256];
while (fgets(linea, sizeof linea, f)) fputs(linea, stdout);
fclose(f);
munmap(mapa, 4096);
free(heap);
return 0;
}
$ gcc -std=c17 -Wall -Wextra -o espacio espacio.c
$ ./espacio
código : 0x55c3a1b01149
data : 0x55c3a1b04010
BSS : 0x55c3a1b04014
heap : 0x55c3a1b062a0
mmap : 0x7f8b4c000000
pila : 0x7ffe2c3a1f2c
/proc/self/maps:
55c3a1b01000-55c3a1b02000 r-xp 00000000 08:01 1310734 /home/ana/espacio
55c3a1b02000-55c3a1b03000 r--p 00001000 08:01 1310734 /home/ana/espacio
55c3a1b03000-55c3a1b04000 rw-p 00002000 08:01 1310734 /home/ana/espacio
55c3a1b04000-55c3a1b05000 rw-p 00000000 00:00 0
55c3a1b06000-55c3a1b07000 rw-p 00000000 00:00 0 [heap]
7f8b4be00000-7f8b4be21000 rw-p 00000000 00:00 0
7f8b4be21000-7f8b4c000000 ---p 00000000 00:00 0
7f8b4c000000-7f8b4c001000 rw-p 00000000 00:00 0 (anon)
7f8b4c001000-7f8b4c200000 ---p 00000000 00:00 0
7f8b4c200000-7f8b4c3e7000 r-xp 00000000 08:01 1310735 /lib/x86_64-linux-gnu/libc.so.6
7f8b4c3e7000-7f8b4c3e8000 r--p 001e7000 08:01 1310735 /lib/x86_64-linux-gnu/libc.so.6
...
7ffe2c380000-7ffe2c3a2000 rw-p 00000000 00:00 0 [stack]
7ffe2c3f6000-7ffe2c3f8000 r-xp 00000000 00:00 0 [vdso]
Cada dirección impresa cae dentro de una región del mapa: código en la región ejecutable del binario, data y BSS en la región de datos, heap en [heap], el mmap en una región anónima, y la variable local en [stack]. Las bibliotecas (libc) aparecen como varias regiones mapeadas con distintos permisos.
6. Permisos por región
La MMU comprueba los permisos de cada región en cada acceso. Esto es lo que hace que un programa no pueda escribir en su código ni ejecutar datos:
| Región | Permisos típicos | Por qué |
|---|---|---|
| Código (text) | r-xp | Se lee y se ejecuta, pero no se escribe (protección W^X). |
| Datos (data, BSS) | rw-p | Se leen y se escriben, pero no se ejecutan. |
| Heap | rw-p | Memoria dinámica de lectura/escritura. |
| Pila | rw-p | Variables locales y marcos de activación. |
| Bibliotecas (código) | r-xp | Compartido entre procesos, solo lectura/ejecución. |
| Regiones anónimas guardia | ---p | Páginas sin permisos, para detectar desbordes. |
La política W^X (escribir o ejecutar, pero no ambas) es una defensa contra ataques: impide inyectar código en datos y ejecutarlo. Por eso un programa que intenta ejecutar la pila o el heap recibe SIGSEGV.
7. Regiones mapeadas: archivos y anónimas
Las regiones mapeadas con mmap son la forma más flexible de crear memoria. Pueden ser:
- Anónimas: memoria sin archivo de respaldo, usada para el heap grande, pilas de hilos y memoria compartida. El núcleo la pone a cero.
- De archivo: un archivo del disco aparece como un rango de direcciones. Leer o escribir esas direcciones accede al archivo (tema 41).
- Compartidas: varios procesos mapean la misma región y ven los mismos datos (tema 43).
Las bibliotecas compartidas (.so) se cargan con mmap, por eso aparecen como regiones con archivo de origen en el mapa. Esto permite compartir el código de libc entre todos los procesos que la usan.
8. ASLR: aleatorizar las direcciones
Para dificultar los ataques, Linux aleatoriza la posición de las regiones en cada ejecución: código, heap, pila, mmap y bibliotecas se cargan en direcciones distintas. Es el ASLR (Address Space Layout Randomization).
- Se controla con
/proc/sys/kernel/randomize_va_space: 0 desactivado, 1 parcial, 2 completo (por defecto). - Se puede desactivar para un programa con
setarch -Ro conpersonality(ADDR_NO_RANDOMIZE). - ASLR no cambia el orden relativo de las regiones ni sus permisos, solo las direcciones base.
- No es infalible: existen técnicas de fuga de información que revelan direcciones.
9. Probar con un simulador
Este simulador dibuja el espacio de direcciones de un proceso con sus regiones principales, y permite activar o desactivar ASLR para ver cómo cambian las direcciones base. También muestra el tamaño de cada región.
Simulador · espacio de direcciones de un proceso
| Región | Rango virtual | Permisos |
|---|
10. Mirar el espacio en Linux
$ cat /proc/self/maps
$ pmap -x $$
Address Kbytes RSS Dirty Mode Mapping
000055...-... 4 4 0 r-x-- bash
...
total kB ... ... ...
$ cat /proc/sys/kernel/randomize_va_space
2
$ setarch -R ./espacio # desactiva ASLR para este programa
$ cat /proc/PID/smaps_rollup # resumen de memoria del proceso
/proc/PID/maps: el mapa completo, región por región.pmap -x PID: resumen con RSS y tamaño por región./proc/PID/smaps: detalle por región (RSS, PSS, dirty, etc.)./proc/PID/smaps_rollup: resumen agregado./proc/sys/kernel/randomize_va_space: nivel de ASLR.
11. Resumen
- El espacio de direcciones de un proceso es su conjunto de regiones virtuales: código, datos, BSS, heap, mmap, pila y vdso.
- Cada región tiene rango, permisos y origen; el núcleo las administra y las expone en
/proc/PID/maps. - El código es r-x, los datos y el heap son rw-, y hay páginas guardia sin permisos.
- El heap crece hacia arriba, la pila hacia abajo; las bibliotecas se cargan con mmap.
- ASLR aleatoriza las direcciones base en cada ejecución para dificultar ataques.
- En Linux,
maps,pmap,smapsysmaps_rolluppermiten inspeccionar el espacio.
12. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. ¿Qué regiones tiene un proceso típico en Linux?
Código (text), datos inicializados (data), BSS, heap, regiones mapeadas (mmap, bibliotecas), pila (stack) y regiones del núcleo (vdso, vvar).
2. ¿Qué significa el permiso r-xp en /proc/PID/maps?
Lectura y ejecución, región privada (copy-on-write). Es típico del código: se puede leer y ejecutar, pero no escribir.
3. ¿En qué dirección crece el heap y en cuál la pila?
El heap crece hacia direcciones crecientes (hacia arriba); la pila crece hacia direcciones decrecientes (hacia abajo).
4. ¿Qué es ASLR y para qué sirve?
Address Space Layout Randomization: aleatoriza las direcciones base de las regiones en cada ejecución para dificultar los ataques que dependen de direcciones fijas.
5. ¿Qué diferencia hay entre una región anónima y una mapeada de archivo?
La anónima no tiene archivo de respaldo (el núcleo la pone a cero); la de archivo corresponde a un archivo del disco y acceder a ella lee o escribe el archivo.
6. ¿Por qué el código tiene permiso de ejecución pero no de escritura?
Por la política W^X (escribir o ejecutar, pero no ambas), que impide inyectar código en datos y ejecutarlo, una defensa contra ataques.
7. ¿Qué muestra pmap que no muestra /proc/PID/maps?
Un resumen con el tamaño en KiB y el RSS de cada región, además del total, en un formato más legible.
8. ¿Qué es una página guardia (---p)?
Una región sin permisos de ningún tipo, colocada entre regiones para detectar desbordes de búfer: cualquier acceso provoca SIGSEGV.
Para practicar
- Compila y ejecuta
espacio.cvarias veces. ¿Cambia la dirección del heap entre ejecuciones? ¿Y la pila? Relaciona con ASLR. - Ejecuta
setarch -R ./espacioy compara las direcciones con las de una ejecución normal. - Usa
pmap -xsobre el proceso deespaciomientras está parado (añade ungetchar()antes de terminar) y localiza cada región. - Modifica
espacio.cpara pedir 1 MiB conmallocy observa si aparece una nueva región en el mapa o si se amplía el heap. - Crea un programa que mapee un archivo de texto con
mmapy modifique un byte. Comprueba que el archivo cambia. - Consulta
/proc/self/smaps_rollupy anota Rss, Pss y Dirty. ¿Qué diferencia hay entre Rss y Pss? - Desactiva ASLR con
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space, ejecutaespacioy vuelve a activarlo. (Restaura el valor 2 al terminar.)