
Jailer é um sistema de confinamento de processos baseado em eBPF que fornece controle de acesso obrigatório (MAC) para Linux. Ele rastreia processos usando mapas BPF task_storage e aplica políticas baseadas em papéis no acesso a arquivos, operações de rede e execução de processos.
Aviso: Este projeto está em desenvolvimento intenso e NÃO está pronto para uso em produção. APIs, formatos de política e comportamento podem mudar sem aviso. Use apenas para testes e experimentação.
Nota: Esta é uma implementação independente e não é o mesmo projeto que a solução da Meta. Embora o BpfJailer seja funcionalmente semelhante e inspirado pela ideia e design originais de Liam Wisehart, Justin Nga, Carl El Khoury, Mansee Chadha na Meta, esta é uma base de código separada desenvolvida independentemente. Estendemos nossa gratidão pela visão e conceitos fundamentais que inspiraram este trabalho.
Jailer é um sistema de confinamento de processos baseado em eBPF que fornece controle de acesso obrigatório (MAC) para Linux. Ele rastreia processos usando mapas BPF task_storage e aplica políticas baseadas em funções para acesso a arquivos, operações de rede e execução de processos.
| Feature | Status | Description |
|---|---|---|
| Process Tracking | ✅ Working | Rastreia processos usando o mapa BPF task_storage |
| Socket Enrollment | ✅ Working | Processos se inscrevem via API de socket Unix |
| Role-based Policies | ✅ Working | Funções restritas e permissivas |
| File Access Control | ✅ Working | Bloquear/permitir operações de abertura de arquivos |
| Jail Inheritance | ✅ Working | Processos filhos herdam o confinamento do pai |
| Network Control | ✅ Working | Bloquear/permitir bind/connect de socket |
| Port/Protocol Filtering | ✅ Working | Regras de permitir/negar TCP/UDP por porta |
| Exec Control | ✅ Working | Bloquear/permitir execução de processos |
| Path Matching | ✅ Working | Percorrimento de dentry com invalidação de cache |
| Signed Binaries | 🚧 Stub | Validação de assinatura binária (não implementado) |
| Alternative Enrollment | ✅ Working | Auto-inscrição por executável, cgroup ou xattr |
| Daemonless Mode | ✅ Working | Binário bootstrap fixa programas na inicialização |
| Audit Events | ✅ Working | Buffer perf para integração com systemd-journald |

BPF_MAP_TYPE_TASK_STORAGE)# Check current kernel config
zcat /proc/config.gz 2>/dev/null || cat /boot/config-$(uname -r)
Opções necessárias:
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_LSM=y
CONFIG_DEBUG_INFO_BTF=y
O BPF LSM deve ser ativado nos parâmetros de inicialização do kernel:
# Check if BPF LSM is active
cat /sys/kernel/security/lsm
# Should include "bpf" in the list
# If not, add to kernel boot parameters:
# Edit /etc/default/grub and add to GRUB_CMDLINE_LINUX:
# lsm=lockdown,capability,landlock,yama,apparmor,bpf
# Then update grub and reboot:
sudo update-grub
sudo reboot
Para sistemas Ubuntu/Debian, você também pode usar:
# Create a script to enable 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 enabled. Please reboot."
EOF
chmod +x /tmp/enable_bpf_lsm.sh
sudo /tmp/enable_bpf_lsm.sh
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Install build dependencies (Ubuntu/Debian)
sudo apt-get install -y clang llvm libelf-dev linux-headers-$(uname -r)
# Install BPF target for Rust
rustup target add bpfel-unknown-none
cd bpfjail
# Build BPF programs
cd bpfjailer-bpf && cargo build --release && cd ..
# Build daemon and client
cargo build --release
# Run as root (loads config/policy.json if present)
sudo RUST_LOG=info ./target/release/bpfjailer-daemon
Expected output:
[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
# Run vulnerability tests WITHOUT jailing (shows attacks succeed)
sudo python3 tests/vulnerable_apps/run_tests.py
# Run vulnerability tests WITH restricted role (shows attacks blocked)
sudo python3 tests/vulnerable_apps/run_tests.py --role 1
# Run specific test
sudo python3 tests/vulnerable_apps/run_tests.py --role 1 --test path
# List available tests and roles
sudo python3 tests/vulnerable_apps/run_tests.py --list
Exemplo de saída com função restrita:
============================================================
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)
| Test | Vulnerability | Mitigated By |
|---|---|---|
path_traversal | Leitura arbitrária de arquivo via ../ | restricted, isolated |
command_injection | Execução de comandos shell | restricted, webserver, isolated |
reverse_shell | Conexões de saída para atacante | restricted, isolated |
ssrf | Acesso a serviços internos/metadados da nuvem | restricted, isolated |
arbitrary_write | Escrita em caminhos sensíveis | restricted |
crypto_miner | Download + execução + conexão ao pool | restricted, webserver |
privilege_escalation | Ler shadow, escrever sudoers | restricted |
#!/usr/bin/env python3
import socket
import json
import os
# Connect to daemon
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect("/run/bpfjailer/enrollment.sock")
# Enroll with restricted role (ID 1)
request = {"Enroll": {"pod_id": 1, "role_id": 1}}
sock.send((json.dumps(request) + "\n").encode())
response = sock.recv(4096).decode()
print(f"Enrollment: {response}")
sock.close()
# Try to read a file (should be blocked)
try:
open("/etc/passwd").read()
print("File access: ALLOWED")
except PermissionError:
print("File access: BLOCKED")
O BpfJailer carrega funções de um arquivo de política JSON. O daemon procura o arquivo de política nesta ordem:
$BPFJAILER_POLICY environment variable/etc/bpfjailer/policy.jsonconfig/policy.json (relativo ao diretório de trabalho){
"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": []
}
| Flag | Description |
|---|---|
allow_file_access | Permitir operações de abertura de arquivos |
allow_network | Permitir bind/connect de socket |
allow_exec | Permitir execução de processos |
allow_setuid | Permitir operações setuid |
allow_ptrace | Permitir operações ptrace |
| Role ID | Name | Acesso a Arquivos | Rede | Execução |
|---|---|---|---|---|
| 1 | restricted | Bloqueado | Bloqueado | Bloqueado |
| 2 | permissive | Permitido | Permitido | Permitido |
| 3 | webserver | Permitido | Portas 80, 443, 8080 | Bloqueado |
| 4 | database | Permitido | Portas 5432, 6379 | Bloqueado |
| 5 | isolated | Permitido | Bloqueado | Bloqueado |
| 6 | web_with_db | Permitido | Portas 80, 443, 5432, 3306, 6379 | Bloqueado |
| 7 | worker | Permitido | Portas 443, 5432, 6379, 5672 | Permitido |
O BpfJailer suporta controle de rede granular com regras TCP/UDP por porta.
network_rules map:
Key: { role_id, port, protocol, direction }
Value: allowed (1) or denied (0)
// In daemon code:
use bpfjailer_daemon::process_tracker::{PROTO_TCP, DIR_CONNECT};
// Allow TCP connect to ports 80 and 443 only
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)?;
| Constant | Value | Description |
|---|---|---|
PROTO_TCP | 6 | Protocolo TCP |
PROTO_UDP | 17 | Protocolo UDP |
DIR_BIND | 0 | bind() de socket |
DIR_CONNECT | 1 | connect() de socket |
Faixas de porta podem ser especificadas no policy.json usando port_start e port_end:
{
"network_rules": [
{"protocol": "tcp", "port": 443, "allow": true},
{"protocol": "tcp", "port_start": 8000, "port_end": 8100, "allow": true}
]
}
| Field | Description |
|---|---|
port | Porta única (ex.: 80) |
port_start + port_end | Faixa de portas (ex.: 8000-8100) |
Nota: Faixas grandes (>1000 portas) consomem muitas entradas do mapa BPF. Um aviso é registrado para faixas que excedem 1000 portas.
┌─────────────────────────────────────────────────────────────┐
│ User Space │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ Client │───▶│ bpfjailer-daemon │ │
│ │ (enroll) │ │ │ │
│ └─────────────┘ │ - PolicyManager │ │
│ │ - ProcessTracker│ │
│ │ - EnrollmentSvr │ │
│ └────────┬─────────┘ │
│ │ writes to │
│ ▼ │
├─────────────────────────────────────────────────────────────┤
│ BPF Maps │
│ ┌──────────────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │pending_enrollments│ │ role_flags │ │ task_storage │ │
│ │ (PID → info) │ │ (role→flags)│ │(task→info) │ │
│ └──────────────────┘ └─────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ BPF LSM Hooks │
│ ┌────────────┐ ┌───────────┐ ┌─────────────┐ ┌──────────┐ │
│ │ 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] e role_flags[role_id]task_storagetask_storage + role_flags para aplicaçãotask_allocO BpfJailer suporta dois modos de instalação:
Usa um daemon em execução para inscrição e gerenciamento de políticas:
# Install systemd service
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/
# Enable and start
sudo systemctl daemon-reload
sudo systemctl enable bpfjailer-daemon
sudo systemctl start bpfjailer-daemon
Características:
Carrega programas BPF no início da inicialização e sai. Os programas permanecem ativos até a reinicialização:
# Install bootstrap service
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/
# Enable (will run at next boot)
sudo systemctl daemon-reload
sudo systemctl enable bpfjailer-bootstrap
# Or run manually now
sudo bpfjailer-bootstrap
Características:
/sys/fs/bpf/bpfjailer/Verificar programas fixados:
ls -la /sys/fs/bpf/bpfjailer/
ls -la /sys/fs/bpf/bpfjailer/maps/
ls -la /sys/fs/bpf/bpfjailer/progs/
Ver eventos de auditoria:
# Events are emitted to perf buffer, picked up by journald
journalctl -f | grep bpfjailer
| Aspecto | Modo Daemon | Modo Sem Daemon |
|---|---|---|
| Superfície de Ataque | Daemon em execução | Nenhum processo em execução |
| Inscrição | Socket Unix + alternativas | Apenas alternativas |
| Atualizações de Política | Recarga a quente | Reinicialização necessária |
| Registro de Auditoria | Daemon lê ringbuf | journald via buffer perf |
| Remoção de Programa | Parar daemon | Apenas reinicialização |
| Ordem de Inicialização | Após network.target | Antes de basic.target |
# Check kernel version (needs 5.11+)
uname -r
# Check BPF LSM is active
cat /sys/kernel/security/lsm | grep bpf
# Check BTF is available
ls -la /sys/kernel/btf/vmlinux
# Check if BPF programs can be loaded
sudo bpftool prog list
# Check capabilities
sudo capsh --print | grep cap_bpf
# Ensure daemon is running as root
ps aux | grep bpfjailer
# Check socket permissions
ls -la /run/bpfjailer/enrollment.sock
Além da inscrição por socket Unix, o BpfJailer suporta inscrição automática baseada em:
Inscrever automaticamente todos os processos que executam um binário específico:
{
"exec_enrollments": [
{
"executable_path": "/usr/bin/nginx",
"pod_id": 1000,
"role": "webserver"
}
]
}
Quando qualquer processo executa /usr/bin/nginx, ele é automaticamente inscrito com a função webserver. Isso é correspondido pelo inode do executável, então links simbólicos e links físicos são tratados corretamente.
Inscrever automaticamente todos os processos em um cgroup específico:
{
"cgroup_enrollments": [
{
"cgroup_path": "/sys/fs/cgroup/bpfjailer/sandbox",
"pod_id": 2000,
"role": "sandbox"
}
]
}
Crie o cgroup e mova processos para ele:
# Create cgroup
sudo mkdir -p /sys/fs/cgroup/bpfjailer/sandbox
# Move process to cgroup
echo $$ | sudo tee /sys/fs/cgroup/bpfjailer/sandbox/cgroup.procs
# Process is now auto-enrolled with sandbox role
Defina atributos estendidos em executáveis para informações de inscrição:
# Set enrollment xattrs
sudo setfattr -n user.bpfjailer.pod_id -v $(printf '\x01\x00\x00\x00\x00\x00\x00\x00') /path/to/binary
sudo setfattr -n user.bpfjailer.role_id -v $(printf '\x03\x00\x00\x00') /path/to/binary
# Check xattrs
getfattr -d /path/to/binary
No momento da execução (hook LSM bprm_check_security):
exec_enrollmentcgroup_enrollmenttask_storage com pod_id e role_idA inscrição persiste através de fork/exec via hook task_alloc
As regras de política (rede, caminho, exec) são aplicadas com base no role_id
GPL-2.0 (necessário para programas BPF)