
Z-Jail v1.1.0
Un sandbox Linux léger et multicouche combinant les namespaces, pivot_root, seccomp-bpf, la suppression de capacités (capability dropping) et un moteur de verdict basé sur les preuves (Truthimatics Public Version) pour une exécution de code sécurisée et vérifiable.
Z-Jail
Sandbox multicouche pour l'exécution de code natif sur Linux.
Sept couches de défense indépendantes — aucune dépendance externe, binaire PIE d'environ 73 Kio.
┌──────────────────────────────────────────────────────┐
│ Z-Jail │
├──────────────────────────────────────────────────────┤
│ Truthimatics PV (moteur de verdict factuel) │
│ Namespaces (mount, pid, net, ipc, uts) │
│ pivot_root (chroot amélioré) │
│ Capabilities (tout abandonner, verrouiller securebits) │
│ NO_NEW_PRIVS (pas d'élévation de privilèges) │
│ seccomp-BPF (liste blanche : 15 appels système uniquement) │
│ Audit (journalisation JSON + hachage BLAKE2b) │
└──────────────────────────────────────────────────────┘
Table des matières
- Démarrage rapide
- Pourquoi Z-Jail
- Architecture
- Couches
- Utilisation
- Construction et installation
- Tests
- Performances
- Modèle de menace
- Documentation
- Feuille de route
- Licence
Démarrage rapide
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
Le répertoire --root doit contenir un système de fichiers minimal avec le binaire cible et ses dépendances (pour les binaires statiques, seul le binaire suffit).
Pourquoi Z-Jail
Les solutions de sandboxing existantes font des compromis :
| Z-Jail | Firecracker | gVisor | bwrap | nsjail | |
|---|---|---|---|---|---|
| Dépendances externes | zéro | libc, seccomp | Runtime Go | libc | libc, protobuf |
| Taille du binaire | ~73 Kio | 20+ Mio | 40+ Mio | ~70 Kio | ~1 Mio |
| Isolation VM | non | oui (microVM) | non (sandbox) | non | non |
| Liste blanche seccomp | oui | non | oui | optionnel | oui |
| Hachage du contenu | oui | non | non | non | non |
| Audit JSON | oui | non | oui | non | partiel |
| Complexité de construction | un make | complexe | complexe | trivial | modérée |
Z-Jail comble le vide entre bwrap (minimal, pas de seccomp par défaut) et nsjail (riche en fonctionnalités, dépendances lourdes). Il est conçu pour les pipelines CI, les challenges de jail CTF et l'évaluation légère de code où vous avez besoin d'une défense en profondeur sans intégrer un environnement d'exécution de conteneurs.
Architecture
Flux de données
flowchart LR
CLI[Arguments CLI] --> P[parse_args]
P --> C{clone espaces de noms}
C -->|enfant| CR[child_run]
C -->|parent| W[waitpid]
CR --> RL[setrlimit]
RL --> FD[fermer fd >= 3]
FD --> DUMP[PR_SET_DUMPABLE=0]
DUMP --> PV[pivot_root]
PV --> NNP[PR_SET_NO_NEW_PRIVS]
NNP --> CAP[abandonner capacités]
CAP --> SC[seccomp-BPF]
SC --> SIG[signaler au parent]
SIG --> EX[execve cible]
W --> A[audit JSON]
A --> EXIT[sortie]
Ordre des couches
Chaque couche est ordonnée de sorte qu'une couche ultérieure ne puisse pas être annulée par une précédente :
- setrlimit — limiter CPU, espace d'adressage, nombre de fichiers, processus avant toute autre chose
- nettoyage des fd — fermer tous les descripteurs hérités sauf le tube de rapport
- PR_SET_DUMPABLE=0 — désactiver les vidages mémoire, verrouiller /proc/self/mem
- pivot_root — détacher du système de fichiers hôte ; ancienne racine démontée de manière paresseuse
- PR_SET_NO_NEW_PRIVS — pas de setuid, pas d'escalade
capsetaprès ce point - drop_caps — mettre à zéro toutes les capacités, verrouiller securebits
- seccomp-BPF — restreindre les appels système à la liste blanche uniquement
- signaler au parent — informer le parent que le sandbox est prêt
- execve — remplacer le processus par le binaire cible
sequenceDiagram
participant P as Parent
participant C as Enfant
P->>C: clone (NEWNS|NEWPID|NEWNET|NEWIPC|NEWUTS)
Note over C: setrlimit(CPU, AS, NOFILE, NPROC)
Note over C: close(tous fd > 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(tout à zéro) + securebits
Note over C: seccomp(SECCOMP_MODE_FILTER, liste blanche)
C->>P: write(pipe, ready=1)
Note over C: execve(cible)
P->>P: waitpid
P->>P: écrire audit JSON
Couches
1. Version publique de Truthimatics
Moteur de verdict fondé sur des preuves. Collecte des observations pondérées sur le binaire exécuté et détermine un verdict final (DETERMINISTIC, REJECT ou UNCERTAIN). Chaque observation porte un poids ; toute observation unique dont le poids > 50 % du total décide du verdict.
2. Espaces de noms
Cinq espaces de noms sont créés via clone() :
| Espace de noms | Drapeau | But |
|---|---|---|
| Montage | CLONE_NEWNS | Arbre de fichiers isolé |
| PID | CLONE_NEWPID | Espace d'ID de processus (l'enfant est pid 1) |
| Réseau | CLONE_NEWNET | Aucune interface réseau |
| IPC | CLONE_NEWIPC | Pas de mémoire partagée / sémaphores |
| UTS | CLONE_NEWUTS | Nom d'hôte séparé |
Nécessite CAP_SYS_ADMIN dans l'espace de noms initial.
3. pivot_root
Remplace la racine de l'espace de noms de montage par le répertoire --root :
- Montage lié du répertoire racine sur lui-même (
MS_BIND|MS_REC) pivot_root(new_root, put_old)— échange l'arbre de montagechdir("/")— se déplacer dans la nouvelle racineumount2("/.pivot_old", MNT_DETACH)— détacher l'ancienne racinermdir("/.pivot_old")— nettoyer
C'est strictement plus fort que chroot(2) — le processus sandboxé ne peut pas s'échapper vers la racine hôte, même avec CLONE_NEWNS depuis l'intérieur du sandbox (qui est déjà bloqué par seccomp).
4. Capacités
Toutes les capacités sont abandonnées via :
capset(hdr, data) // data = {0, 0, 0}
prctl(SECBIT_KEEP_CAPS_LOCKED | SECBIT_NO_SETUID_FIXUP | ...)
Le processus abandonne setuid/setgid avant capset afin que le changement d'UID prenne effet alors que CAP_SETUID est encore détenu. Après capset, toutes les capacités ont disparu et les securebits sont verrouillés — aucune réactivation possible.
5. NO_NEW_PRIVS
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
Empêche le processus ou ses enfants d'obtenir de nouveaux privilèges via les binaires setuid, les capacités de fichiers ou les transitions LSM. Irréversible.
6. seccomp-BPF (whitelist-v1)
Liste blanche de 15 appels système — tout ce qui n'est pas dans la liste reçoit SECCOMP_RET_KILL :
| Appel système | Numéro | Notes |
|---|---|---|
read | 0 | stdin |
write | 1 | stdout/stderr + tube de rapport |
openat | 257 | accès fichiers (pas open) |
close | 3 | — |
lseek | 8 | — |
brk | 12 | gestion du tas |
mmap | 9 | arguments restreints : flags & 4 == 0 (pas de MAP_SHARED), flags == 0x22 (MAP_PRIVATE|MAP_ANONYMOUS) |
munmap | 11 | — |
execve | 59 | exec unique au démarrage |
exit_group | 231 | sortie propre du processus |
rt_sigaction | 13 | gestionnaires de signaux |
rt_sigprocmask | 14 | masquage de signaux |
getrandom | 318 | source de nombres aléatoires |
clock_gettime | 228 | chronométrage |
fstat | 5 | métadonnées de fichier |
Le filtre BPF est généré dynamiquement : pour chaque entrée de la liste blanche, une chaîne de sauts est émise qui soit autorise (si l'appel système correspond), soit tombe sur KILL. L'architecture est vérifiée en premier (AUDIT_ARCH_X86_64).
Le filtre est vérifié indépendamment par un test autonome (tests/seccomp_filter_test.c, 8/8 réussis) qui fork+exécute des cas de test contre un vrai prctl(PR_SET_SECCOMP) sans nécessiter root.
7. Audit
Chaque exécution produit un enregistrement d'audit 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..."
}
Écrit dans build/audits/<nom-binaire>.audit.json. Le content_fingerprint est un hachage BLAKE2b-256 du binaire cible, calculé par le parent après la fin de l'enfant.
Utilisation
z_jail --root=<rép> [--seccomp-enforce] [--self-hash=<hex>]
[--quiet] [--verbose] -- <programme> [args...]
| Drapeau | Description |
|---|---|
--root=<rép> | Répertoire racine du sandbox (obligatoire) |
--seccomp-enforce | Activer la liste blanche d'appels système seccomp-BPF |
--self-hash=<hex> | Vérifier que le binaire correspond au hachage BLAKE2b-256 attendu |
--quiet | Supprimer la sortie d'audit |
--verbose | Activer les journaux de débogage |
--version | Afficher l'ID de construction (Z-Jail/v1+dev) |
--help | Afficher l'utilisation et quitter |
Exemples
# Exécuter un binaire statique avec toutes les protections
sudo z_jail --root=./roots --seccomp-enforce -- bin/hello_static
# Exécuter avec vérification d'intégrité du binaire
sudo z_jail --root=./roots --seccomp-enforce \
--self-hash=$(sha256sum z_jail | cut -c1-64) -- bin/program
# Mode silencieux (pas de JSON d'audit)
sudo z_jail --root=./roots --quiet -- bin/program
Codes de retour
| Code | Signification |
|---|---|
| 0 | L'enfant s'est terminé normalement (verdict : DETERMINISTIC) |
| 1 | L'enfant a été tué par un signal (verdict : REJECT) |
| 2 | Self-hash : chaîne hexadécimale invalide ou fichier illisible |
| 3 | Self-hash : non-concordance (le binaire a été modifié) |
| 101 | Erreur de configuration de l'enfant (rlimit, etc.) |
| 102 | Échec de l'installation du filtre seccomp de l'enfant |
| 103 | Échec de execve de l'enfant (binaire introuvable, pas de permission d'exécution) |
| 104 | Échec de pivot_root de l'enfant |
| 105 | Échec de l'abandon des capacités de l'enfant |
| 125 | Échec de la création de l'espace de noms (exécuter en root ? support du noyau ?) |
Construction et installation
Prérequis
- Noyau Linux ≥ 5.4 (espaces de noms, seccomp-BPF, pivot_root)
- GCC ≥ 11 (testé sur 11.4, 13.2, 15.2)
- Aucune bibliothèque externe — juste la chaîne d'outils C standard
Commandes
make # construire z_jail (~130 Kio binaire PIE)
make install # installer dans /usr/local/bin + page de manuel
make clean # supprimer les artefacts de construction
make dist # créer une archive de distribution
make check # test de fumée (--version + --help)
Le binaire est construit comme un exécutable indépendant de la position avec -fstack-protector-strong, -D_FORTIFY_SOURCE=2, RELRO complet et -z now.
Options de compilation
make CC=clang CFLAGS="-O3 -march=native" # compilateur/drapeaux personnalisés
Tests
Test rapide (sans root)
# Logique du filtre seccomp (8 tests)
tests/build/seccomp_filter_test
# Test de réponse connue BLAKE2b
tests/build/blake2b_known
Ces tests ne nécessitent pas root et s'exécutent en moins de 100 ms.
Suite de tests complète
make -C tests setup # construire les charges utiles + racines de test
sudo bash tests/run_tests.sh # 17 scénarios
Nécessite root pour la création d'espaces de noms. La suite de tests couvre :
| # | Scénario | Type | Ce qu'il teste |
|---|---|---|---|
| 0 | blake2b_regress | réponse connue | Exactitude de l'implémentation BLAKE2b |
| 1 | seccomp_filter | BPF autonome | 8 sous-tests de la logique du filtre BPF |
| 2 | hello_static | ok | Exécution de binaire statique de base |
| 3 | hello_dynamic | ok | Binaire dynamique avec ld-linux + libc |
| 4 | execve_replacement | ok | execve dans le sandbox (bloqué par seccomp) |
| 5 | fd_inherited_read | ok | stdin/stdout hérités correctement |
| 6 | mmap_bad_flags | tué | mmap avec MAP_SHARED bloqué |
| 7 | mmap_good_allowed | ok | mmap avec MAP_PRIVATE|ANONYMOUS autorisé |
| 8 | mmap_prot_exec | tué | mmap avec PROT_EXEC bloqué |
| 9 | mmap_self_modify | tué | Code auto-modifiant bloqué |
| 10 | ptrace | tué | ptrace bloqué |
| 11 | socket | tué | Création de socket bloquée |
| 12 | chroot_escape | tué | Appel système chroot bloqué |
| 13 | double_chroot | tué | Double chroot bloqué |
| 14 | mount_replay | tué | Appel système mount bloqué |
| 15 | cpu_exhaust | tué | RLIMIT_NPROC bloque la bombe de fork |
| 16 | signal_parent | tué | Signal au parent bloqué |
| 17 | self_hash | ok | Vérification d'intégrité du binaire |
Performances
Mesurées sur WSL2 (noyau 6.18.x-microsoft-standard-WSL2, Kali Linux),
50 échantillons par outil, charge de travail uniforme (un binaire statique autonome dont
le corps est exit_group(0)), chronométré avec un harnais getrusage. Voir
docs/BENCHMARKS.md pour la méthodologie.
| Métrique | Valeur |
|---|---|
| Taille du binaire | ~73 Kio non strippé (~28 Kio strippé) |
| Latence moyenne du sandbox | 5,85 ± 1,45 ms (IC 95 % [5,45, 6,25]) |
| RSS de pointe | 1,62 Mio |
| Lignes de code (noyau) | ~900 |
Tête-à-tête (même hôte, même méthodologie)
| Outil | Latence moyenne ± écart-type | RSS de pointe | seccomp par défaut |
|---|---|---|---|
| Z-Jail | 5,85 ± 1,45 ms | 1,62 Mio | oui |
| bwrap | 3,56 ± 0,40 ms | 2,19 Mio | non |
| nsjail | 8,98 ± 1,68 ms | 7,91 Mio | oui |
Dans les conditions testées, Z-Jail a le plus petit ensemble résident des
trois sandbox au niveau processus et une latence entre bwrap et nsjail.
Bubblewrap est le plus rapide mais n'effectue aucun filtrage seccomp par défaut, donc
il effectue moins de travail d'initialisation ; Z-Jail installe une liste blanche seccomp, abandonne
les capacités et effectue pivot_root à chaque exécution. gVisor (runsc)
provoque un segfault sur ce noyau WSL2 et n'a pas pu être mesuré ; Firecracker
isole via une microVM (démarrage à froid de VM, une métrique différente) et est exclu
du tableau fork-to-exec. Ce sont des chiffres sur un seul hôte — traitez-les comme relatifs.
Remarque : les chiffres documentés plus anciens (~8 ms, ~4 Mio, ~130 Kio) ne correspondent pas à une construction
makeactuelle (~73 Kio, ~5,9 ms) et semblent avoir été inexacts ; les chiffres ci-dessus ont été remesurés sur ce codebase. Truthimatics fait toujours partie du code et n'a pas été supprimée (les anciens rapportsaxiom_jailutilisaient simplement un nom de binaire et un harnais différents). Un bogue de propagation de montage découvert lors du benchmarking a été corrigé danssrc/sandbox.c(MS_REC|MS_PRIVATEavant le montage lié) ; le remontage récursif contribue en partie à la latence mesurée.
Modèle de menace
Dans le périmètre
- Exécution de code natif arbitraire par une charge utile non fiable
- Évasion via
chroot,mount,ptrace,socket,process_vm_writev - Bombes de fork, épuisement CPU (
RLIMIT_CPU), épuisement mémoire (RLIMIT_AS) - Fuites de descripteurs de fichier à travers
execve - Escalade via
setuid/ éditeur de liens dynamique /LD_PRELOAD - Suppression du filtre seccomp ou réactivation des capacités
Hors périmètre
- Faille zero-day du noyau en dehors de la surface d'appels système autorisée
- Canaux auxiliaires matériels (Spectre, Meltdown)
- Évasion de VM colocalisée via des montages partagés
/proc,/sys - Sortie réseau au-delà de ce que
CLONE_NEWNET+ socket bloqué fournit - Affamement de ressources des sandbox frères (nécessite le support cgroup)
Hypothèses
- Le noyau hôte est Linux ≥ 5.4 non modifié
clone(CLONE_NEWNS|CLONE_NEWPID|...)réussit (nécessiteCAP_SYS_ADMIN)- Le binaire cible est lié statiquement (ou les bibliothèques dynamiques sont disponibles dans
--root) --self-hash=<hex>est configuré dans les déploiements de production
Documentation
| Fichier | Description |
|---|---|
README.md | Ce fichier |
docs/ARCHITECTURE.md | Aperçu de l'architecture |
docs/SANDBOX.md | Internes du sandbox couche par couche |
docs/SECCOMP.md | Conception de la liste blanche seccomp-BPF |
docs/AUDIT_SCHEMA.md | Référence du schéma JSON d'audit |
docs/THREAT_MODEL.md | Hypothèses de sécurité et périmètre |
docs/BLAKE2B.md | Détails de l'implémentation BLAKE2b |
docs/BENCHMARKS.md | Benchmark de performances |
docs/BUILD.md | Instructions de construction |
docs/adr/ | Enregistrements de décisions d'architecture (4 docs) |
man/z_jail.1 | Page de manuel |
SECURITY.md | Politique de sécurité et signalement |
CONTRIBUTING.md | Comment contribuer |
CHANGELOG.md | Historique des versions |
ROADMAP.md | Plans futurs |
TODO.md | Lacunes connues et travaux prévus |
Feuille de route
v1 (actuelle)
- Sandbox de défense en profondeur à 7 couches
- Empreinte numérique BLAKE2b-256 du contenu
- Sortie JSON d'audit
- 17 scénarios de test
- Page de manuel, complétions (bash, zsh, fish)
v2 (prévue)
- Fichier de politique seccomp externe (JSON ou source BPF)
- Drapeaux d'espace de noms personnalisés par instance de sandbox
- Liste blanche d'appels système configurable via CLI
- Hooks de profilage de performance pour intégration CI
- Signature des versions (minisign/signify)
Statut
Licence
MIT — voir LICENSE pour le texte complet.
Z-Jail a été construit sur WSL2 (Kali Linux, GCC 15.2.0), ciblant Linux 5.4+. Maintenu par Division-36. Signalez les problèmes sur le suivi de tickets.