
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.
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) │
└──────────────────────────────────────────────────────┘
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).
Las soluciones de sandboxing existentes implican concesiones:
| Z-Jail | Firecracker | gVisor | bwrap | nsjail | |
|---|---|---|---|---|---|
| Dependencias externas | cero | libc, seccomp | Go runtime | libc | libc, protobuf |
| Tamaño del binario | ~73 KiB | 20+ MiB | 40+ MiB | ~70 KiB | ~1 MiB |
| Aislamiento de VM | no | sí (microVM) | no (sandbox) | no | no |
| Lista blanca seccomp | sí | no | sí | opcional | sí |
| Hash de contenido | sí | no | no | no | no |
| Auditoría JSON | sí | no | sí | no | parcial |
| Complejidad de compilación | un solo make | compleja | compleja | trivial | moderada |
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.
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]
Cada capa está ordenada de modo que una capa posterior no pueda ser deshecha por una anterior:
capset a partir de este puntosequenceDiagram
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
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.
Se crean cinco espacios de nombres mediante clone():
| Espacio de nombres | Flag | Propósito |
|---|---|---|
| Mount | CLONE_NEWNS | Árbol de sistema de archivos aislado |
| PID | CLONE_NEWPID | Espacio de ID de proceso (el hijo es el pid 1) |
| Net | CLONE_NEWNET | Sin interfaces de red |
| IPC | CLONE_NEWIPC | Sin memoria compartida / semáforos |
| UTS | CLONE_NEWUTS | Nombre de host separado |
Requiere CAP_SYS_ADMIN en el espacio de nombres inicial.
Reemplaza la raíz del espacio de nombres de montaje con el directorio --root:
MS_BIND|MS_REC)pivot_root(new_root, put_old) — intercambia el árbol de montajeschdir("/") — se mueve a la nueva raízumount2("/.pivot_old", MNT_DETACH) — desmonta la raíz antiguarmdir("/.pivot_old") — limpiaEsto 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).
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.
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.
Lista blanca de 15 syscalls: cualquier cosa que no esté en la lista recibe SECCOMP_RET_KILL: