
Jailer는 Linux를 위한 강제 접근 제어(MAC)를 제공하는 eBPF 기반 프로세스 격리 시스템입니다. BPF task_storage 맵을 사용하여 프로세스를 추적하고 파일 액세스, 네트워크 작업 및 프로세스 실행에 대한 역할 기반 정책을 적용합니다.
경고: 이 프로젝트는 활발히 개발 중이며 프로덕션 환경에서 사용할 준비가 되지 않았습니다. API, 정책 형식 및 동작은 예고 없이 변경될 수 있습니다. 테스트 및 실험 목적으로만 사용하세요.
참고: 이는 독립적인 구현이며 Meta의 솔루션과 동일한 프로젝트가 아닙니다. BpfJailer는 Liam Wisehart, Justin Nga, Carl El Khoury, Mansee Chadha (Meta)의 원래 아이디어와 설계에서 영감을 받아 기능적으로 유사하지만, 이는 독립적으로 개발된 별도의 코드베이스입니다. 이 작업에 영감을 준 비전과 기본 개념에 감사드립니다.
Jailer는 eBPF 기반 프로세스 격리 시스템으로, Linux에 대한 필수 접근 제어(MAC)를 제공합니다. BPF task_storage 맵을 사용하여 프로세스를 추적하고 파일 접근, 네트워크 작업, 프로세스 실행에 대한 역할 기반 정책을 적용합니다.

BPF_MAP_TYPE_TASK_STORAGE 지원 필요)# 현재 커널 설정 확인
zcat /proc/config.gz 2>/dev/null || cat /boot/config-$(uname -r)
필수 옵션:
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_LSM=y
CONFIG_DEBUG_INFO_BTF=y
BPF LSM은 커널 부트 매개변수에서 활성화되어야 합니다:
# BPF LSM이 활성화되었는지 확인
cat /sys/kernel/security/lsm
# 목록에 "bpf"가 포함되어야 함
# 포함되지 않은 경우, 커널 부트 매개변수에 추가:
# /etc/default/grub 편집 후 GRUB_CMDLINE_LINUX에 다음 추가:
# lsm=lockdown,capability,landlock,yama,apparmor,bpf
# 그런 다음 grub 업데이트 후 재부팅:
sudo update-grub
sudo reboot
Ubuntu/Debian 시스템에서는 다음을 사용할 수도 있습니다:
# 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이 활성화되었습니다. 재부팅하세요."
EOF
chmod +x /tmp/enable_bpf_lsm.sh
sudo /tmp/enable_bpf_lsm.sh
# Rust 설치
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 빌드 종속성 설치 (Ubuntu/Debian)
sudo apt-get install -y clang llvm libelf-dev linux-headers-$(uname -r)
# Rust용 BPF 타겟 설치
rustup target add bpfel-unknown-none
cd bpfjail
# BPF 프로그램 빌드
cd bpfjailer-bpf && cargo build --release && cd ..
# 데몬 및 클라이언트 빌드
cargo build --release
# root로 실행 (config/policy.json이 있으면 로드)
sudo RUST_LOG=info ./target/release/bpfjailer-daemon
예상 출력:
[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
# 감옥 없이 취약점 테스트 실행 (공격 성공 표시)
sudo python3 tests/vulnerable_apps/run_tests.py
# 제한된 역할로 취약점 테스트 실행 (공격 차단 표시)
sudo python3 tests/vulnerable_apps/run_tests.py --role 1
# 특정 테스트 실행
sudo python3 tests/vulnerable_apps/run_tests.py --role 1 --test path
# 사용 가능한 테스트 및 역할 목록
sudo python3 tests/vulnerable_apps/run_tests.py --list
제한된 역할을 사용한 예시 출력:
============================================================
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
# 데몬에 연결
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect("/run/bpfjailer/enrollment.sock")
# 제한된 역할(ID 1)로 등록
request = {"Enroll": {"pod_id": 1, "role_id": 1}}
sock.send((json.dumps(request) + "\n").encode())
response = sock.recv(4096).decode()
print(f"등록: {response}")
sock.close()
# 파일 읽기 시도 (차단되어야 함)
try:
open("/etc/passwd").read()
print("파일 접근: 허용됨")
except PermissionError:
print("파일 접근: 차단됨")
BpfJailer는 JSON 정책 파일에서 역할을 로드합니다. 데몬은 다음 순서로 정책 파일을 검색합니다:
$BPFJAILER_POLICY 환경 변수/etc/bpfjailer/policy.jsonconfig/policy.json (작업 디렉토리 기준){
"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는 포트별 TCP/UDP 규칙으로 세분화된 네트워크 제어를 지원합니다.
network_rules 맵:
키: { role_id, port, protocol, direction }
값: 허용(1) 또는 거부(0)
// 데몬 코드에서:
use bpfjailer_daemon::process_tracker::{PROTO_TCP, DIR_CONNECT};
// 포트 80과 443에 대한 TCP connect만 허용
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)?;
포트 범위는 policy.json에서 port_start와 port_end를 사용하여 지정할 수 있습니다:
{
"network_rules": [
{"protocol": "tcp", "port": 443, "allow": true},
{"protocol": "tcp", "port_start": 8000, "port_end": 8100, "allow": true}
]
}
| 필드 | 설명 |
|---|---|
port | 단일 포트 (예: 80) |
port_start + port_end | 포트 범위 (예: 8000-8100) |
참고: 큰 범위(>1000 포트)는 많은 BPF 맵 항목을 소비합니다. 1000 포트를 초과하는 범위에 대해 경고가 기록됩니다.
┌─────────────────────────────────────────────────────────────┐
│ 사용자 공간 │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ 클라이언트 │───▶│ bpfjailer-daemon │ │
│ │ (등록) │ │ │ │
│ └─────────────┘ │ - PolicyManager │ │
│ │ - ProcessTracker│ │
│ │ - EnrollmentSvr │ │
│ └────────┬─────────┘ │
│ │ 기록 │
│ ▼ │
├─────────────────────────────────────────────────────────────┤
│ BPF 맵 │
│ ┌──────────────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │pending_enrollments│ │ role_flags │ │ task_storage │ │
│ │ (PID → 정보) │ │ (역할→플래그) │ │(작업→정보) │ │
│ └──────────────────┘ └─────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ BPF LSM 훅 │
│ ┌────────────┐ ┌───────────┐ ┌─────────────┐ ┌──────────┐ │
│ │ task_alloc │ │ file_open │ │socket_bind/ │ │bprm_check│ │
│ │(상속 + 초기)│ │(마이그레이션 │ │ connect │ │_security │ │
│ │ │ │ + 검사) │ │ (검사) │ │ (검사) │ │
│ └────────────┘ └───────────┘ └─────────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
/run/bpfjailer/enrollment.sock에 연결{"Enroll": {"pod_id": N, "role_id": M}}pending_enrollments[PID] 및 role_flags[role_id]에 기록task_storage로 마이그레이션task_storage + role_flags를 확인하여 적용task_alloc 훅을 통해 상속BpfJailer는 두 가지 설치 모드를 지원합니다:
실행 중인 데몬을 사용하여 등록 및 정책 관리:
# 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/
# 활성화 및 시작
sudo systemctl daemon-reload
sudo systemctl enable bpfjailer-daemon
sudo systemctl start bpfjailer-daemon
기능:
초기 부팅 시 BPF 프로그램을 로드하고 종료합니다. 프로그램은 재부팅될 때까지 활성 상태를 유지합니다:
# 부트스트랩 서비스 설치
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/
# 활성화 (다음 부팅 시 실행)
sudo systemctl daemon-reload
sudo systemctl enable bpfjailer-bootstrap
# 또는 지금 수동 실행
sudo bpfjailer-bootstrap
기능:
/sys/fs/bpf/bpfjailer/에 고정됨고정된 프로그램 확인:
ls -la /sys/fs/bpf/bpfjailer/
ls -la /sys/fs/bpf/bpfjailer/maps/
ls -la /sys/fs/bpf/bpfjailer/progs/
감사 이벤트 보기:
# 이벤트는 perf 버퍼로 전송되며 journald가 수집
journalctl -f | grep bpfjailer
# 커널 버전 확인 (5.11+ 필요)
uname -r
# BPF LSM 활성화 확인
cat /sys/kernel/security/lsm | grep bpf
# BTF 사용 가능 확인
ls -la /sys/kernel/btf/vmlinux
# BPF 프로그램 로드 가능 확인
sudo bpftool prog list
# 기능 확인
sudo capsh --print | grep cap_bpf
# 데몬이 root로 실행 중인지 확인
ps aux | grep bpfjailer
# 소켓 권한 확인
ls -la /run/bpfjailer/enrollment.sock
Unix 소켓 등록 외에도 BpfJailer는 다음 기준으로 자동 등록을 지원합니다:
특정 바이너리를 실행하는 모든 프로세스를 자동 등록:
{
"exec_enrollments": [
{
"executable_path": "/usr/bin/nginx",
"pod_id": 1000,
"role": "webserver"
}
]
}
프로세스가 /usr/bin/nginx를 실행하면 자동으로 webserver 역할로 등록됩니다. 실행 파일의 inode로 매칭되므로 심볼릭 링크와 하드 링크도 올바르게 처리됩니다.
특정 cgroup의 모든 프로세스를 자동 등록:
{
"cgroup_enrollments": [
{
"cgroup_path": "/sys/fs/cgroup/bpfjailer/sandbox",
"pod_id": 2000,
"role": "sandbox"
}
]
}
cgroup을 생성하고 프로세스를 이동:
# cgroup 생성
sudo mkdir -p /sys/fs/cgroup/bpfjailer/sandbox
# 프로세스를 cgroup으로 이동
echo $$ | sudo tee /sys/fs/cgroup/bpfjailer/sandbox/cgroup.procs
# 이제 프로세스가 sandbox 역할로 자동 등록됨
실행 파일에 확장 속성을 설정하여 등록 정보 제공:
# 등록 xattr 설정
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
# xattr 확인
getfattr -d /path/to/binary
실행 시 (bprm_check_security LSM 훅):
exec_enrollment 맵에 있는지 확인cgroup_enrollment 맵에 있는지 확인task_storage에 pod_id와 role_id를 자동 설정등록은 task_alloc 훅을 통해 fork/exec 시 지속됨
정책 규칙 (네트워크, 경로, 실행)은 role_id에 따라 적용됨
GPL-2.0 (BPF 프로그램에 필요)
| 기능 | 상태 | 설명 |
|---|
| 프로세스 추적 | ✅ 작동 중 | task_storage BPF 맵을 사용하여 프로세스 추적 |
| 소켓 등록 | ✅ 작동 중 | 프로세스가 Unix 소켓 API를 통해 등록 |
| 역할 기반 정책 | ✅ 작동 중 | 제한된 역할과 허용된 역할 |
| 파일 접근 제어 | ✅ 작동 중 | 파일 열기 작업 차단/허용 |
| Jail 상속 | ✅ 작동 중 | 자식 프로세스가 부모의 jail을 상속 |
| 네트워크 제어 | ✅ 작동 중 | 소켓 bind/connect 차단/허용 |
| 포트/프로토콜 필터링 | ✅ 작동 중 | 포트별 TCP/UDP 허용/차단 규칙 |
| 실행 제어 | ✅ 작동 중 | 프로세스 실행 차단/허용 |
| 경로 매칭 | ✅ 작동 중 | 캐시 무효화를 포함한 Dentry 탐색 |
| 서명된 바이너리 | 🚧 스텁 | 바이너리 서명 검증 (구현되지 않음) |
| 대체 등록 | ✅ 작동 중 | 실행 파일, cgroup 또는 xattr로 자동 등록 |
| 데몬리스 모드 | ✅ 작동 중 | 부트스트랩 바이너리가 초기 부팅 시 프로그램 고정 |
| 감사 이벤트 | ✅ 작동 중 | systemd-journald 통합을 위한 Perf 버퍼 |
| 테스트 | 취약점 | 완화되는 역할 |
|---|
path_traversal | ../를 통한 임의 파일 읽기 | restricted, isolated |
command_injection | 셸 명령 실행 | restricted, webserver, isolated |
reverse_shell | 공격자로의 아웃바운드 연결 | restricted, isolated |
ssrf | 내부 서비스/클라우드 메타데이터 접근 | restricted, isolated |
arbitrary_write | 민감한 경로에 쓰기 | restricted |
crypto_miner | 다운로드 + 실행 + 풀에 연결 | restricted, webserver |
privilege_escalation | shadow 읽기, sudoers 쓰기 | restricted |
| 플래그 | 설명 |
|---|
allow_file_access | 파일 열기 작업 허용 |
allow_network | 소켓 bind/connect 허용 |
allow_exec | 프로세스 실행 허용 |
allow_setuid | setuid 작업 허용 |
allow_ptrace | ptrace 작업 허용 |
| 역할 ID | 이름 | 파일 접근 | 네트워크 | 실행 |
|---|
| 1 | restricted | 차단됨 | 차단됨 | 차단됨 |
| 2 | permissive | 허용됨 | 허용됨 | 허용됨 |
| 3 | webserver | 허용됨 | 포트 80, 443, 8080 | 차단됨 |
| 4 | database | 허용됨 | 포트 5432, 6379 | 차단됨 |
| 5 | isolated | 허용됨 | 차단됨 | 차단됨 |
| 6 | web_with_db | 허용됨 | 포트 80, 443, 5432, 3306, 6379 | 차단됨 |
| 7 | worker | 허용됨 | 포트 443, 5432, 6379, 5672 | 허용됨 |
| 상수 | 값 | 설명 |
|---|
PROTO_TCP | 6 | TCP 프로토콜 |
PROTO_UDP | 17 | UDP 프로토콜 |
DIR_BIND | 0 | socket bind() |
DIR_CONNECT | 1 | socket connect() |
| 측면 | 데몬 모드 | 데몬리스 모드 |
|---|
| 공격 표면 | 실행 중인 데몬 | 실행 중인 프로세스 없음 |
| 등록 | Unix 소켓 + 대체 방법 | 대체 방법만 |
| 정책 업데이트 | 핫 리로드 | 재부팅 필요 |
| 감사 로깅 | 데몬이 ringbuf 읽음 | perf 버퍼를 통한 journald |
| 프로그램 제거 | 데몬 중지 | 재부팅만 |
| 부팅 순서 | network.target 이후 | basic.target 이전 |