
Jailer est un système de confinement de processus basé sur eBPF qui fournit un contrôle d'accès obligatoire (MAC) pour Linux. Il suit les processus à l'aide de maps BPF task_storage et applique des politiques basées sur les rôles en matière d'accès aux fichiers, d'opérations réseau et d'exécution de processus.
Avertissement : Ce projet est en cours de développement intensif et n'est PAS prêt pour une utilisation en production. Les API, les formats de politique et le comportement peuvent changer sans préavis. Utilisez-le uniquement pour des tests et des expérimentations.
Note : Il s'agit d'une implémentation indépendante et non du même projet que la solution de Meta. Bien que BpfJailer soit fonctionnellement similaire et inspiré de l'idée originale et de la conception de Liam Wisehart, Justin Nga, Carl El Khoury, Mansee Chadha chez Meta, il s'agit d'une base de code distincte développée indépendamment. Nous exprimons notre gratitude pour la vision et les concepts fondamentaux qui ont inspiré ce travail.
Jailer est un système d'isolation de processus basé sur eBPF qui fournit un contrôle d'accès obligatoire (MAC) pour Linux. Il suit les processus à l'aide de cartes BPF task_storage et applique des politiques basées sur les rôles pour l'accès aux fichiers, les opérations réseau et l'exécution de processus.

BPF_MAP_TYPE_TASK_STORAGE)# Vérifier la configuration actuelle du noyau
zcat /proc/config.gz 2>/dev/null || cat /boot/config-$(uname -r)
Options requises :
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_LSM=y
CONFIG_DEBUG_INFO_BTF=y
BPF LSM doit être activé dans les paramètres de démarrage du noyau :
# Vérifier si BPF LSM est actif
cat /sys/kernel/security/lsm
# Doit inclure "bpf" dans la liste
# Si ce n'est pas le cas, ajouter aux paramètres de démarrage :
# Éditer /etc/default/grub et ajouter à GRUB_CMDLINE_LINUX :
# lsm=lockdown,capability,landlock,yama,apparmor,bpf
# Puis mettre à jour grub et redémarrer :
sudo update-grub
sudo reboot
Pour les systèmes Ubuntu/Debian, vous pouvez également utiliser :
# Créer un script pour activer BPF LSM
cat > /tmp/enable_bpf_lsm.sh << 'EOF'
#!/bin/bash
GRUB_FILE="/etc/default/grub"
if grep -q "lsm=" "$GRUB_FILE"; then
sudo sed -i 's/lsm=[^""]*/lsm=lockdown,capability,landlock,yama,apparmor,bpf/' "$GRUB_FILE"
else
sudo sed -i 's/GRUB_CMDLINE_LINUX="\(.*\)"/GRUB_CMDLINE_LINUX="\1 lsm=lockdown,capability,landlock,yama,apparmor,bpf"/' "$GRUB_FILE"
fi
sudo update-grub
echo "BPF LSM activé. Veuillez redémarrer."
EOF
chmod +x /tmp/enable_bpf_lsm.sh
sudo /tmp/enable_bpf_lsm.sh
# Installer Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Installer les dépendances de construction (Ubuntu/Debian)
sudo apt-get install -y clang llvm libelf-dev linux-headers-$(uname -r)
# Installer la cible BPF pour Rust
rustup target add bpfel-unknown-none
cd bpfjail
# Construire les programmes BPF
cd bpfjailer-bpf && cargo build --release && cd ..
# Construire le démon et le client
cargo build --release
# Exécuter en tant que root (charge config/policy.json si présent)
sudo RUST_LOG=info ./target/release/bpfjailer-daemon
Sortie attendue :
[INFO] BpfJailer daemon starting...
[INFO] Loading BpfJailer eBPF programs with libbpf-rs...
[INFO] ✓ pending_enrollments map available for enrollment
[INFO] ✓ network_rules map available for port/protocol filtering
[INFO] ✓ task_storage map created successfully
[INFO] ✓ Program task_alloc attached
[INFO] ✓ Program file_open attached
[INFO] ✓ Program socket_bind attached
[INFO] ✓ Program socket_connect attached
[INFO] ✓ Program bprm_check_security attached
[INFO] Initialized with default roles: restricted (1), permissive (2)
[INFO] Loaded policy from config/policy.json
[INFO] Loaded 5 roles
[INFO] Enrollment server listening on /run/bpfjailer/enrollment.sock
# Exécuter les tests de vulnérabilité SANS emprisonnement (montre les attaques réussies)
sudo python3 tests/vulnerable_apps/run_tests.py
# Exécuter les tests de vulnérabilité AVEC le rôle restreint (montre les attaques bloquées)
sudo python3 tests/vulnerable_apps/run_tests.py --role 1
# Exécuter un test spécifique
sudo python3 tests/vulnerable_apps/run_tests.py --role 1 --test path
# Lister les tests et rôles disponibles
sudo python3 tests/vulnerable_apps/run_tests.py --list
Exemple de sortie avec le rôle restreint :
============================================================
TEST: Path Traversal / Arbitrary File Read
============================================================
Attempting to read /etc/passwd via path traversal...
BLOCKED - Permission denied (BpfJailer blocked file access)
============================================================
TEST: Command Injection
============================================================
Attempting command injection...
BLOCKED - Permission denied (BpfJailer blocked exec)
============================================================
TEST: Reverse Shell / Data Exfiltration
============================================================
Test 1: Reverse shell connection to 127.0.0.1:4444
BLOCKED - Permission denied (BpfJailer blocked connect)
#!/usr/bin/env python3
import socket
import json
import os
# Se connecter au démon
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect("/run/bpfjailer/enrollment.sock")
# S'inscrire avec le rôle restreint (ID 1)
request = {"Enroll": {"pod_id": 1, "role_id": 1}}
sock.send((json.dumps(request) + "\n").encode())
response = sock.recv(4096).decode()
print(f"Inscription : {response}")
sock.close()
# Essayer de lire un fichier (devrait être bloqué)
try:
open("/etc/passwd").read()
print("Accès fichier : AUTORISÉ")
except PermissionError:
print("Accès fichier : BLOQUÉ")
BpfJailer charge les rôles à partir d'un fichier de politique JSON. Le démon recherche le fichier de politique dans cet ordre :
$BPFJAILER_POLICY/etc/bpfjailer/policy.jsonconfig/policy.json (relatif au répertoire de travail){
"roles": {
"restricted": {
"id": 1,
"name": "restricted",
"flags": {
"allow_file_access": false,
"allow_network": false,
"allow_exec": false
},
"network_rules": []
},
"webserver": {
"id": 3,
"name": "webserver",
"flags": {
"allow_file_access": true,
"allow_network": true,
"allow_exec": false
},
"network_rules": [
{"protocol": "tcp", "port": 80, "allow": true},
{"protocol": "tcp", "port": 443, "allow": true}
]
}
},
"pods": []
}
BpfJailer prend en charge un contrôle réseau fin avec des règles TCP/UDP par port.
network_rules map:
Key: { role_id, port, protocol, direction }
Value: allowed (1) or denied (0)
// Dans le code du démon :
use bpfjailer_daemon::process_tracker::{PROTO_TCP, DIR_CONNECT};
// Autoriser la connexion TCP aux ports 80 et 443 uniquement
process_tracker.add_network_rule(RoleId(1), 80, PROTO_TCP, DIR_CONNECT, true)?;
process_tracker.add_network_rule(RoleId(1), 443, PROTO_TCP, DIR_CONNECT, true)?;
Les plages de ports peuvent être spécifiées dans policy.json en utilisant port_start et port_end :
{
"network_rules": [
{"protocol": "tcp", "port": 443, "allow": true},
{"protocol": "tcp", "port_start": 8000, "port_end": 8100, "allow": true}
]
}
| Champ | Description |
|---|---|
port | Port unique (ex. 80) |
port_start + port_end | Plage de ports (ex. 8000-8100) |
Note : Les grandes plages (>1000 ports) consomment beaucoup d'entrées de carte BPF. Un avertissement est journalisé pour les plages dépassant 1000 ports.
┌─────────────────────────────────────────────────────────────┐
│ Espace utilisateur │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ Client │───▶│ bpfjailer-daemon │ │
│ │ (enroll) │ │ │ │
│ └─────────────┘ │ - PolicyManager │ │
│ │ - ProcessTracker│ │
│ │ - EnrollmentSvr │ │
│ └────────┬─────────┘ │
│ │ écrit dans │
│ ▼ │
├─────────────────────────────────────────────────────────────┤
│ Cartes BPF │
│ ┌──────────────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │pending_enrollments│ │ role_flags │ │ task_storage │ │
│ │ (PID → info) │ │ (role→flags)│ │(task→info) │ │
│ └──────────────────┘ └─────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Hooks BPF LSM │
│ ┌────────────┐ ┌───────────┐ ┌─────────────┐ ┌──────────┐ │
│ │ task_alloc │ │ file_open │ │socket_bind/ │ │bprm_check│ │
│ │(inheritance│ │(migration │ │ connect │ │_security │ │
│ │ + init) │ │ + check) │ │ (check) │ │ (check) │ │
│ └────────────┘ └───────────┘ └─────────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
/run/bpfjailer/enrollment.sock{"Enroll": {"pod_id": N, "role_id": M}}pending_enrollments[PID] et role_flags[role_id]task_storagetask_storage + role_flags pour l'applicationtask_allocBpfJailer prend en charge deux modes d'installation :
Utilise un démon en cours d'exécution pour l'inscription et la gestion des politiques :
# Installer le service systemd
sudo cp config/bpfjailer-daemon.service /etc/systemd/system/
sudo cp target/release/bpfjailer-daemon /usr/sbin/
sudo mkdir -p /etc/bpfjailer
sudo cp config/policy.json /etc/bpfjailer/
# Activer et démarrer
sudo systemctl daemon-reload
sudo systemctl enable bpfjailer-daemon
sudo systemctl start bpfjailer-daemon
Fonctionnalités :
Charge les programmes BPF au démarrage précoce puis se termine. Les programmes restent actifs jusqu'au redémarrage :
# Installer le service bootstrap
sudo cp config/bpfjailer-bootstrap.service /etc/systemd/system/
sudo cp target/release/bpfjailer-bootstrap /usr/sbin/
sudo mkdir -p /etc/bpfjailer
sudo cp config/policy.json /etc/bpfjailer/
# Activer (s'exécutera au prochain démarrage)
sudo systemctl daemon-reload
sudo systemctl enable bpfjailer-bootstrap
# Ou exécuter manuellement maintenant
sudo bpfjailer-bootstrap
Fonctionnalités :
/sys/fs/bpf/bpfjailer/Vérifier les programmes épinglés :
ls -la /sys/fs/bpf/bpfjailer/
ls -la /sys/fs/bpf/bpfjailer/maps/
ls -la /sys/fs/bpf/bpfjailer/progs/
Voir les événements d'audit :
# Les événements sont émis vers le tampon perf, récupérés par journald
journalctl -f | grep bpfjailer
# Vérifier la version du noyau (nécessite 5.11+)
uname -r
# Vérifier que BPF LSM est actif
cat /sys/kernel/security/lsm | grep bpf
# Vérifier que BTF est disponible
ls -la /sys/kernel/btf/vmlinux
# Vérifier si les programmes BPF peuvent être chargés
sudo bpftool prog list
# Vérifier les capacités
sudo capsh --print | grep cap_bpf
# Assurez-vous que le démon s'exécute en tant que root
ps aux | grep bpfjailer
# Vérifier les permissions du socket
ls -la /run/bpfjailer/enrollment.sock
Au-delà de l'inscription par socket Unix, BpfJailer prend en charge l'inscription automatique basée sur :
Auto-inscrire tous les processus exécutant un binaire spécifique :
{
"exec_enrollments": [
{
"executable_path": "/usr/bin/nginx",
"pod_id": 1000,
"role": "webserver"
}
]
}
Lorsqu'un processus exécute /usr/bin/nginx, il est automatiquement inscrit avec le rôle webserver. La correspondance est basée sur l'inode de l'exécutable, donc les liens symboliques et les liens durs sont correctement traités.
Auto-inscrire tous les processus dans un cgroup spécifique :
{
"cgroup_enrollments": [
{
"cgroup_path": "/sys/fs/cgroup/bpfjailer/sandbox",
"pod_id": 2000,
"role": "sandbox"
}
]
}
Créer le cgroup et déplacer les processus dedans :
# Créer le cgroup
sudo mkdir -p /sys/fs/cgroup/bpfjailer/sandbox
# Déplacer le processus vers le cgroup
echo $$ | sudo tee /sys/fs/cgroup/bpfjailer/sandbox/cgroup.procs
# Le processus est maintenant auto-inscrit avec le rôle sandbox
Définir des attributs étendus sur les exécutables pour les informations d'inscription :
# Définir les xattrs d'inscription
sudo setfattr -n user.bpfjailer.pod_id -v $(printf '\x01\x00\x00\x00\x00\x00\x00\x00') /chemin/vers/binaire
sudo setfattr -n user.bpfjailer.role_id -v $(printf '\x03\x00\x00\x00') /chemin/vers/binaire
# Vérifier les xattrs
getfattr -d /chemin/vers/binaire
Au moment de l'exécution (hook LSM bprm_check_security) :
exec_enrollmentcgroup_enrollmenttask_storage avec pod_id et role_idL'inscription persiste à travers fork/exec via le hook task_alloc
Les règles de politique (réseau, chemin, exec) sont appliquées en fonction de role_id
GPL-2.0 (requis pour les programmes BPF)
| Fonctionnalité | Statut | Description |
|---|
| Suivi des processus | ✅ Fonctionnel | Suit les processus à l'aide de la carte BPF task_storage |
| Inscription par socket | ✅ Fonctionnel | Les processus s'inscrivent via l'API socket Unix |
| Politiques basées sur les rôles | ✅ Fonctionnel | Rôles restreints et permissifs |
| Contrôle d'accès aux fichiers | ✅ Fonctionnel | Bloquer/autoriser les opérations d'ouverture de fichiers |
| Héritage de la prison | ✅ Fonctionnel | Les processus enfants héritent de la prison du parent |
| Contrôle réseau | ✅ Fonctionnel | Bloquer/autoriser les opérations socket bind/connect |
| Filtrage par port/protocole | ✅ Fonctionnel | Règles d'autorisation/refus par port TCP/UDP |
| Contrôle d'exécution | ✅ Fonctionnel | Bloquer/autoriser l'exécution de processus |
| Correspondance de chemin | ✅ Fonctionnel | Parcours de dentry avec invalidation de cache |
| Binaires signés | 🚧 Ébauche | Validation de signature binaire (non implémentée) |
| Inscription alternative | ✅ Fonctionnel | Auto-inscription par exécutable, cgroup ou xattr |
| Mode sans démon | ✅ Fonctionnel | Binaire bootstrap épinglant des programmes au démarrage précoce |
| Événements d'audit | ✅ Fonctionnel | Tampon perf pour l'intégration systemd-journald |
| Test | Vulnérabilité | Atténué par |
|---|
path_traversal | Lecture arbitraire de fichiers via ../ | restricted, isolated |
command_injection | Exécution de commandes shell | restricted, webserver, isolated |
reverse_shell | Connexions sortantes vers l'attaquant | restricted, isolated |
ssrf | Accès aux services internes/métadonnées cloud | restricted, isolated |
arbitrary_write | Écriture dans des chemins sensibles | restricted |
crypto_miner | Téléchargement + exécution + connexion au pool | restricted, webserver |
privilege_escalation | Lecture shadow, écriture sudoers | restricted |
| Drapeau | Description |
|---|
allow_file_access | Autoriser les opérations d'ouverture de fichiers |
allow_network | Autoriser les opérations socket bind/connect |
allow_exec | Autoriser l'exécution de processus |
allow_setuid | Autoriser les opérations setuid |
allow_ptrace | Autoriser les opérations ptrace |
| ID Rôle | Nom | Accès fichiers | Réseau | Exec |
|---|
| 1 | restricted | Bloqué | Bloqué | Bloqué |
| 2 | permissive | Autorisé | Autorisé | Autorisé |
| 3 | webserver | Autorisé | Ports 80, 443, 8080 | Bloqué |
| 4 | database | Autorisé | Ports 5432, 6379 | Bloqué |
| 5 | isolated | Autorisé | Bloqué | Bloqué |
| 6 | web_with_db | Autorisé | Ports 80, 443, 5432, 3306, 6379 | Bloqué |
| 7 | worker | Autorisé | Ports 443, 5432, 6379, 5672 | Autorisé |
| Constante | Valeur | Description |
|---|
PROTO_TCP | 6 | Protocole TCP |
PROTO_UDP | 17 | Protocole UDP |
DIR_BIND | 0 | socket bind() |
DIR_CONNECT | 1 | socket connect() |
| Aspect | Mode Démon | Mode Sans Démon |
|---|
| Surface d'attaque | Démon en cours d'exécution | Aucun processus en cours |
| Inscription | Socket Unix + alternatives | Alternatives uniquement |
| Mise à jour des politiques | Rechargement à chaud | Redémarrage requis |
| Journalisation d'audit | Le démon lit ringbuf | journald via tampon perf |
| Suppression du programme | Arrêter le démon | Redémarrage uniquement |
| Ordre de démarrage | Après network.target | Avant basic.target |