Volver a actualizaciones
Nuevo releaseJul 27, 2026

Z-Jail v1.1.0

Un sandbox Linux ligero y multicapa que combina namespaces, pivot_root, seccomp-bpf, supresión de capacidades (capability dropping) y un motor de veredictos basado en evidencia (Truthimatics Public Version) para una ejecución de código segura y auditable.

Compartir
Z-Jail

Z-Jail

Sandbox multicapa para ejecución de código nativo en Linux.
Siete capas de defensa independientes — sin dependencias externas, binario PIE de ~73 KiB.


┌──────────────────────────────────────────────────────┐
│                    Z-Jail                            │
├──────────────────────────────────────────────────────┤
│  Truthimatics PV  (evidence-based verdict engine)    │
│  Namespaces       (mount, pid, net, ipc, uts)        │
│  pivot_root       (chroot on steroids)               │
│  Capabilities     (drop all, lock securebits)        │
│  NO_NEW_PRIVS     (no privilege escalation)          │
│  seccomp-BPF      (whitelist: 15 syscalls only)      │
│  Audit            (JSON logging + BLAKE2b hashing)   │
└──────────────────────────────────────────────────────┘

Tabla de contenidos


Inicio rápido

git clone https://github.com/Division-36/Z-Jail.git
cd Z-Jail
make
sudo ./z_jail --root=/path/to/rootfs --seccomp-enforce -- /bin/ls

El directorio --root debe contener un sistema de archivos mínimo con el binario objetivo y sus dependencias (para binarios estáticos, basta con el binario).


Por qué Z-Jail

Las soluciones de sandboxing existentes implican concesiones:

Z-JailFirecrackergVisorbwrapnsjail
Dependencias externascerolibc, seccompGo runtimelibclibc, protobuf
Tamaño del binario~73 KiB20+ MiB40+ MiB~70 KiB~1 MiB
Aislamiento de VMnosí (microVM)no (sandbox)nono
Lista blanca seccompnoopcional
Hash de contenidonononono
Auditoría JSONnonoparcial
Complejidad de compilaciónun solo makecomplejacomplejatrivialmoderada

Z-Jail ocupa el nicho entre bwrap (mínimo, sin seccomp por defecto) y nsjail (con muchas funciones, dependencias pesadas). Está diseñado para pipelines de CI, retos CTF de jail y evaluación ligera de código donde necesitas defensa en profundidad sin incorporar un runtime de contenedores.


Arquitectura

Flujo de datos

flowchart LR
    CLI[CLI args] --> P[parse_args]
    P --> C{clone namespaces}
    C -->|child| CR[child_run]
    C -->|parent| W[waitpid]
    CR --> RL[setrlimit]
    RL --> FD[close fds >= 3]
    FD --> DUMP[PR_SET_DUMPABLE=0]
    DUMP --> PV[pivot_root]
    PV --> NNP[PR_SET_NO_NEW_PRIVS]
    NNP --> CAP[drop capabilities]
    CAP --> SC[seccomp-BPF]
    SC --> SIG[signal parent]
    SIG --> EX[execve target]
    W --> A[audit JSON]
    A --> EXIT[exit]

Orden de las capas

Cada capa está ordenada de modo que una capa posterior no pueda ser deshecha por una anterior:

  1. setrlimit — limita CPU, espacio de direcciones, número de archivos y procesos antes que cualquier otra cosa
  2. fd scrub — cierra todos los descriptores de archivo heredados excepto el pipe de reporte
  3. PR_SET_DUMPABLE=0 — volcados de núcleo deshabilitados, /proc/self/mem bloqueado
  4. pivot_root — se separa del sistema de archivos del host; el root antiguo se desmonta de forma diferida
  5. PR_SET_NO_NEW_PRIVS — sin setuid, sin escalada de capset a partir de este punto
  6. drop_caps — pone a cero todas las capacidades, bloquea securebits
  7. seccomp-BPF — restringe las syscalls solo a la lista blanca
  8. signal parent — indica al proceso padre que el sandbox está listo
  9. execve — reemplaza el proceso con el binario objetivo
sequenceDiagram
    participant P as Parent
    participant C as Child
    P->>C: clone (NEWNS|NEWPID|NEWNET|NEWIPC|NEWUTS)
    Note over C: setrlimit(CPU, AS, NOFILE, NPROC)
    Note over C: close(all fds > 2)
    Note over C: PR_SET_DUMPABLE=0
    Note over C: pivot_root → chdir("/") → umount -l
    Note over C: PR_SET_NO_NEW_PRIVS
    Note over C: capset(all zero) + securebits
    Note over C: seccomp(SECCOMP_MODE_FILTER, whitelist)
    C->>P: write(pipe, ready=1)
    Note over C: execve(target)
    P->>P: waitpid
    P->>P: write audit JSON

Capas

1. Versión pública de Truthimatics

Motor de veredictos basado en evidencia. Recopila observaciones ponderadas sobre el binario ejecutado y determina un veredicto final (DETERMINISTIC, REJECT o UNCERTAIN). Cada observación lleva un peso; cualquier observación individual con un peso >50% del total decide el veredicto.

2. Espacios de nombres

Se crean cinco espacios de nombres mediante clone():

Espacio de nombresFlagPropósito
MountCLONE_NEWNSÁrbol de sistema de archivos aislado
PIDCLONE_NEWPIDEspacio de ID de proceso (el hijo es el pid 1)
NetCLONE_NEWNETSin interfaces de red
IPCCLONE_NEWIPCSin memoria compartida / semáforos
UTSCLONE_NEWUTSNombre de host separado

Requiere CAP_SYS_ADMIN en el espacio de nombres inicial.

3. pivot_root

Reemplaza la raíz del espacio de nombres de montaje con el directorio --root:

  1. Vuelve a montar (bind-mount) el directorio raíz sobre sí mismo (MS_BIND|MS_REC)
  2. pivot_root(new_root, put_old) — intercambia el árbol de montajes
  3. chdir("/") — se mueve a la nueva raíz
  4. umount2("/.pivot_old", MNT_DETACH) — desmonta la raíz antigua
  5. rmdir("/.pivot_old") — limpia

Esto es estrictamente más fuerte que chroot(2) — no hay forma de que el proceso en el sandbox escape de vuelta a la raíz del host, ni siquiera con CLONE_NEWNS desde dentro del sandbox (que ya está bloqueado por seccomp).

4. Capacidades

Todas las capacidades se eliminan mediante:

capset(hdr, data)  // data = {0, 0, 0}
prctl(SECBIT_KEEP_CAPS_LOCKED | SECBIT_NO_SETUID_FIXUP | ...)

El proceso elimina setuid/setgid antes de capset para que el cambio de uid tenga efecto mientras aún se mantiene CAP_SETUID. Después de capset, todas las capacidades desaparecen y los securebits quedan bloqueados; no es posible volver a habilitarlas.

5. NO_NEW_PRIVS

prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);

Impide que el proceso o sus hijos obtengan nuevos privilegios mediante binarios setuid, capacidades de archivo o transiciones LSM. Irreversible.

6. seccomp-BPF (whitelist-v1)

Lista blanca de 15 syscalls: cualquier cosa que no esté en la lista recibe SECCOMP_RET_KILL:

SyscallNúmeroNotas
read0stdin
write1stdout/stderr + pipe de reporte
openat257acceso a archivos (no open)
close3
lseek8
brk12gestión del heap
mmap9con restricción de argumentos: flags & 4 == 0 (sin MAP_SHARED), flags == 0x22 (MAP_PRIVATE|MAP_ANONYMOUS)
munmap11
execve59ejecución única al inicio
exit_group231salida limpia del proceso
rt_sigaction13manejadores de señales
rt_sigprocmask14enmascaramiento de señales
getrandom318fuente de números aleatorios
clock_gettime228temporización
fstat5metadatos de archivo

El filtro BPF se genera dinámicamente: para cada entrada de la lista blanca se emite una cadena de saltos que permite (si la syscall coincide) o cae hasta KILL. La arquitectura se comprueba primero (AUDIT_ARCH_X86_64).

El filtro se verifica de forma independiente mediante una prueba autónoma (tests/seccomp_filter_test.c, 8/8 pasan) que hace fork+execve de casos de prueba contra un prctl(PR_SET_SECCOMP) real sin necesidad de root.

7. Auditoría

Cada ejecución produce un registro de auditoría JSON:

{
  "schema": "z-jail.audit/v1",
  "build_id": "Z-Jail/v1+dev",
  "timestamp": 1749000000,
  "duration_ns": 8500000,
  "executable": "/bin/ls",
  "verdict": "DETERMINISTIC",
  "exit_code": 0,
  "sandbox": {
    "seccomp_filter": "whitelist-v1",
    "seccomp_whitelist_size": 15,
    "seccomp_arg_rules_size": 2,
    "namespaces": ["mount","pid","net","ipc","uts"],
    "pivot_root": "/var/run/z-jail/roots/default",
    "no_new_privs": true,
    "capabilities_dropped": true
  },
  "content_fingerprint": "0e5751c026e543b2e8ab2eb06099daa1..."
}

Se escribe en build/audits/<binary-name>.audit.json. El content_fingerprint es un hash BLAKE2b-256 del binario objetivo, calculado por el padre cuando el hijo termina.


Uso

z_jail --root=<dir> [--seccomp-enforce] [--self-hash=<hex>]
       [--quiet] [--verbose] -- <program> [args...]
FlagDescripción
--root=<dir>Directorio raíz del sandbox (obligatorio)
--seccomp-enforceHabilita la lista blanca de syscalls seccomp-BPF
--self-hash=<hex>Verifica que el binario coincide con el hash BLAKE2b-256 esperado
--quietSuprime la salida de auditoría
--verboseHabilita el registro de depuración
--versionMuestra el ID de compilación (Z-Jail/v1+dev)
--helpMuestra el uso y sale

Ejemplos

# Run a static binary with all protections
sudo z_jail --root=./roots --seccomp-enforce -- bin/hello_static

# Run with binary integrity verification
sudo z_jail --root=./roots --seccomp-enforce \
  --self-hash=$(sha256sum z_jail | cut -c1-64) -- bin/program

# Quiet mode (no audit JSON)
sudo z_jail --root=./roots --quiet -- bin/program

Códigos de salida

CódigoSignificado
0El hijo salió normalmente (veredicto: DETERMINISTIC)
1El hijo fue eliminado por una señal (veredicto: REJECT)
2Self-hash: cadena hex inválida o archivo ilegible
3Self-hash: discrepancia (el binario ha sido manipulado)
101Error de configuración del hijo (rlimit, etc.)
102Falló la instalación del filtro seccomp del hijo
103Falló execve del hijo (binario no encontrado, sin permiso de ejecución)
104Falló pivot_root del hijo
105Falló la eliminación de capacidades del hijo
125Falló la creación del espacio de nombres (¿ejecutar como root? ¿soporte del kernel?)

Compilación e instalación

Requisitos

  • kernel Linux ≥ 5.4 (espacios de nombres, seccomp-BPF, pivot_root)
  • GCC ≥ 11 (probado en 11.4, 13.2, 15.2)
  • Sin bibliotecas externas: solo el toolchain estándar de C

Comandos

make              # build z_jail (~130 KiB PIE binary)
make install      # install to /usr/local/bin + man page
make clean        # remove build artifacts
make dist         # create release tarball
make check        # smoke test (--version + --help)

El binario se compila como un ejecutable independiente de posición (PIE) con -fstack-protector-strong, -D_FORTIFY_SOURCE=2, RELRO completo y -z now.

Opciones de compilación

make CC=clang CFLAGS="-O3 -march=native"   # custom compiler/flags

Pruebas

Prueba rápida (sin root)

# seccomp filter logic (8 tests)
tests/build/seccomp_filter_test

# BLAKE2b known-answer test
tests/build/blake2b_known

Estas no requieren root y se ejecutan en menos de 100 ms.

Suite de pruebas completa

make -C tests setup          # build payloads + test roots
sudo bash tests/run_tests.sh # 17 scenarios

Requiere root para la creación de espacios de nombres. La suite de pruebas cubre:

#EscenarioTipoQué prueba
0blake2b_regressrespuesta conocidaCorrección de la implementación de BLAKE2b
1seccomp_filterBPF autónomo8 subpruebas de la lógica del filtro BPF
2hello_staticcorrectoEjecución básica de un binario estático
3hello_dynamiccorrectoBinario dinámico con ld-linux + libc
4execve_replacementcorrectoexecve en el sandbox (bloqueado por seccomp)
5fd_inherited_readcorrectostdin/stdout heredados correctamente
6mmap_bad_flagseliminadommap con MAP_SHARED bloqueado
7mmap_good_allowedcorrectommap con MAP_PRIVATE|ANONYMOUS permitido
8mmap_prot_execeliminadommap con PROT_EXEC bloqueado
9mmap_self_modifyeliminadoCódigo automodificable bloqueado
10ptraceeliminadoptrace bloqueado
11socketeliminadocreación de socket bloqueada
12chroot_escapeeliminadosyscall chroot bloqueada
13double_chrooteliminadodoble chroot bloqueado
14mount_replayeliminadosyscall mount bloqueada
15cpu_exhausteliminadoRLIMIT_NPROC bloquea fork bomb
16signal_parenteliminadoSeñal al padre bloqueada
17self_hashcorrectoVerificación de integridad del binario

Rendimiento

Medido en WSL2 (kernel 6.18.x-microsoft-standard-WSL2, Kali Linux), 50 muestras por herramienta, carga de trabajo uniforme (un binario estático independiente cuyo cuerpo es exit_group(0)), cronometrado con un harness getrusage. Consulta docs/BENCHMARKS.md para conocer la metodología.

MétricaValor
Tamaño del binario~73 KiB sin strip (~28 KiB con strip)
Latencia media del sandbox5.85 ± 1.45 ms (95% CI [5.45, 6.25])
RSS máximo1.62 MiB
Líneas de código (núcleo)~900

Comparativa (mismo host, misma metodología)

HerramientaLatencia media ± DERSS máximoseccomp por defecto
Z-Jail5.85 ± 1.45 ms1.62 MiB
bwrap3.56 ± 0.40 ms2.19 MiBno
nsjail8.98 ± 1.68 ms7.91 MiB

En las condiciones probadas, Z-Jail tiene el conjunto residente más pequeño de los tres sandboxes a nivel de proceso y una latencia entre bwrap y nsjail. Bubblewrap es el más rápido, pero no realiza filtrado seccomp por defecto, por lo que hace menos trabajo de preparación; Z-Jail instala una lista blanca seccomp, elimina capacidades y hace pivot_root en cada ejecución. gVisor (runsc) falla con segfault en este kernel WSL2 y no pudo medirse; Firecracker aísla mediante una microVM (arranque en frío de la VM, una métrica diferente) y queda excluido de la tabla fork-to-exec. Estas cifras son de un solo host: trátalas como relativas.

Nota: las cifras documentadas anteriormente (~8 ms, ~4 MiB, ~130 KiB) no coinciden con una compilación actual de make (~73 KiB, ~5.9 ms) y parecen haber sido inexactas; los números anteriores se volvieron a medir en este código base. Truthimatics sigue siendo parte del código y no se eliminó (los informes antiguos de axiom_jail simplemente usaban un nombre de binario y un harness distintos). Un error de propagación de montajes encontrado durante las pruebas comparativas se corrigió en src/sandbox.c (MS_REC|MS_PRIVATE antes del bind mount); el remontaje recursivo contribuye a parte de la latencia medida.


Modelo de amenazas

Dentro del alcance

  • Ejecución arbitraria de código nativo por una carga útil no confiable
  • Escape mediante chroot, mount, ptrace, socket, process_vm_writev
  • Fork bombs, agotamiento de CPU (RLIMIT_CPU), agotamiento de memoria (RLIMIT_AS)
  • Fugas de descriptores de archivo a través de execve
  • Escalada mediante setuid / enlazador dinámico / LD_PRELOAD
  • Eliminación del filtro seccomp o re-habilitación de capacidades

Fuera del alcance

  • Días cero del kernel fuera de la superficie de syscalls permitida
  • Canales laterales de hardware (Spectre, Meltdown)
  • Escape de una VM ubicada en el mismo host mediante montajes compartidos de /proc, /sys
  • Salida de red más allá de lo que proporcionan CLONE_NEWNET + socket bloqueado
  • Inanición de recursos de sandboxes hermanos (necesita soporte de cgroups)

Suposiciones

  • El kernel del host es Linux ≥ 5.4 sin modificar
  • clone(CLONE_NEWNS|CLONE_NEWPID|...) tiene éxito (requiere CAP_SYS_ADMIN)
  • El binario objetivo está enlazado estáticamente (o las bibliotecas dinámicas están disponibles en --root)
  • --self-hash=<hex> está configurado en despliegues de producción

Documentación

ArchivoDescripción
README.mdEste archivo
docs/ARCHITECTURE.mdDescripción general de la arquitectura
docs/SANDBOX.mdDetalles internos del sandbox capa por capa
docs/SECCOMP.mdDiseño de la lista blanca seccomp-BPF
docs/AUDIT_SCHEMA.mdReferencia del esquema JSON de auditoría
docs/THREAT_MODEL.mdSuposiciones y alcance de seguridad
docs/BLAKE2B.mdDetalles de implementación de BLAKE2b
docs/BENCHMARKS.mdPruebas de rendimiento
docs/BUILD.mdInstrucciones de compilación
docs/adr/Registros de decisiones de arquitectura (4 documentos)
man/z_jail.1Página del manual
SECURITY.mdPolítica de seguridad y cómo reportar
CONTRIBUTING.mdCómo contribuir
CHANGELOG.mdHistorial de versiones
ROADMAP.mdPlanes futuros
TODO.mdBrechas conocidas y trabajo planificado

Hoja de ruta

v1 (actual)

  • Sandbox de defensa en profundidad de 7 capas
  • Huella digital de contenido BLAKE2b-256
  • Salida de auditoría JSON
  • 17 escenarios de prueba
  • Página del manual, completions (bash, zsh, fish)

v2 (planificado)

  • Archivo de política seccomp externo (JSON o fuente BPF)
  • Flags de espacio de nombres personalizados por instancia de sandbox
  • Lista blanca de syscalls configurable mediante CLI
  • Hooks de perfilado de rendimiento para integración con CI
  • Firma de lanzamientos (minisign/signify)

Estado

build coverage


Licencia

MIT — consulta LICENSE para ver el texto completo.


Z-Jail se construyó en WSL2 (Kali Linux, GCC 15.2.0), orientado a Linux 5.4+. Mantenido por Division-36. Reporta problemas en el rastreador de incidencias.

Categorías