
네임스페이스, pivot_root, seccomp-bpf, capability 드롭 및 증거 기반 평결 엔진(Truthimatics Public Version)을 결합하여 안전하고 감사 가능한 코드 실행을 제공하는 가볍고 다층적인 Linux 샌드박스입니다.
Linux에서 네이티브 코드 실행을 위한 다층 샌드박스.
7개의 독립적인 방어 계층 — 외부 의존성 없음, ~73KiB PIE 바이너리.
┌──────────────────────────────────────────────────────┐
│ 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
--root 디렉터리에는 대상 바이너리와 해당 종속성이 포함된 최소한의 파일시스템이 있어야 합니다(정적 바이너리의 경우 바이너리 하나면 충분합니다).
기존 샌드박싱 솔루션은 트레이드오프가 있습니다:
| Z-Jail | Firecracker | gVisor | bwrap | nsjail | |
|---|---|---|---|---|---|
| 외부 의존성 | 없음 | libc, seccomp | Go 런타임 | libc | libc, protobuf |
| 바이너리 크기 | ~73 KiB | 20+ MiB | 40+ MiB | ~70 KiB | ~1 MiB |
| VM 격리 | 아니요 | 예 (microVM) | 아니요 (샌드박스) | 아니요 | 아니요 |
| seccomp 화이트리스트 | 지원 | 아니요 | 지원 | 선택 | 지원 |
| 콘텐츠 해싱 | 지원 | 아니요 | 아니요 | 아니요 | 아니요 |
| 감사 JSON | 지원 | 아니요 | 지원 | 아니요 | 부분 |
| 빌드 복잡도 | make 한 번 | 복잡 | 복잡 | 간단 | 보통 |
Z-Jail은 bwrap(최소 기능, 기본적으로 seccomp 없음)과 nsjail(기능이 풍부하지만 의존성이 과중) 사이의 틈새를 채웁니다. 컨테이너 런타임을 끌어들이지 않으면서 심층 방어가 필요한 CI 파이프라인, CTF jail 챌린지, 경량 코드 평가 환경을 위해 설계되었습니다.
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]
각 계층은 이후 계층이 이전 계층에 의해 취소될 수 없도록 순서가 정해져 있습니다:
capset 권한 상승 불가sequenceDiagram
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
증거 기반 판정 엔진. 실행된 바이너리에 대한 가중치가 부여된 관찰(observation)을 수집하여 최종 판정(DETERMINISTIC, REJECT, 또는 UNCERTAIN)을 결정합니다. 각 관찰에는 가중치가 부여되며, 총합의 50%를 초과하는 가중치를 가진 단일 관찰이 판정을 결정합니다.
clone()을 통해 5개의 네임스페이스가 생성됩니다:
| 네임스페이스 | 플래그 | 용도 |
|---|---|---|
| Mount | CLONE_NEWNS | 격리된 파일시스템 트리 |
| PID | CLONE_NEWPID | 프로세스 ID 공간(자식이 pid 1) |
| Net | CLONE_NEWNET | 네트워크 인터페이스 없음 |
| IPC | CLONE_NEWIPC | 공유 메모리/세마포어 없음 |
| UTS | CLONE_NEWUTS | 분리된 호스트 이름 |
초기 네임스페이스에서 CAP_SYS_ADMIN이 필요합니다.
마운트 네임스페이스 루트를 --root 디렉터리로 교체합니다:
MS_BIND|MS_REC)pivot_root(new_root, put_old) — 마운트 트리를 교체합니다chdir("/") — 새 루트로 이동합니다umount2("/.pivot_old", MNT_DETACH) — 이전 루트를 분리합니다rmdir("/.pivot_old") — 정리합니다이는 chroot(2)보다 엄격하게 강력합니다 — 샌드박스 내부에서 CLONE_NEWNS를 사용하더라도(이미 seccomp에 의해 차단됨) 샌드박스 프로세스가 호스트 루트로 탈출할 방법이 없습니다.
모든 capabilities는 다음을 통해 제거됩니다:
capset(hdr, data) // data = {0, 0, 0}
prctl(SECBIT_KEEP_CAPS_LOCKED | SECBIT_NO_SETUID_FIXUP | ...)
프로세스는 capset 이전에 setuid/setgid를 제거하여 CAP_SETUID를 아직 보유한 상태에서 uid 변경이 적용되도록 합니다. capset 이후에는 모든 caps가 사라지고 securebits가 잠겨 재활성화가 불가능합니다.
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
프로세스 또는 그 자식이 setuid 바이너리, 파일 capabilities, LSM 전환을 통해 새 권한을 획득하는 것을 방지합니다. 되돌릴 수 없습니다.
15개 syscall 허용 목록 — 목록에 없는 모든 것은 SECCOMP_RET_KILL 처리됩니다:
| syscall | 번호 | 설명 |
|---|---|---|
read | 0 | stdin |
write | 1 | stdout/stderr + 보고 파이프 |
openat | 257 | 파일 접근(open 아님) |
close | 3 | — |
lseek | 8 | — |
brk | 12 | 힙 관리 |
mmap | 9 | 인자 제한: flags & 4 == 0 (MAP_SHARED 금지), flags == 0x22 (MAP_PRIVATE|MAP_ANONYMOUS) |
munmap | 11 | — |
execve | 59 | 시작 시 단일 exec |
exit_group | 231 | 프로세스 정상 종료 |
rt_sigaction | 13 | 시그널 핸들러 |
rt_sigprocmask | 14 | 시그널 마스킹 |
getrandom | 318 | 난수 소스 |
clock_gettime | 228 | 타이밍 |
fstat | 5 | 파일 메타데이터 |
BPF 필터는 동적으로 생성됩니다: 각 화이트리스트 항목에 대해 syscall이 일치하면 허용하거나 KILL로 폴스루하는 점프 체인이 생성됩니다. 아키텍처가 먼저 확인됩니다(AUDIT_ARCH_X86_64).
필터는 독립 실행 테스트(tests/seccomp_filter_test.c, 8/8 통과)로 별도 검증됩니다. 이 테스트는 root 권한 없이 실제 prctl(PR_SET_SECCOMP)에 대해 테스트 케이스를 fork+execve합니다.
모든 실행은 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..."
}
build/audits/<binary-name>.audit.json에 기록됩니다. content_fingerprint는 자식 프로세스가 종료된 후 부모 프로세스가 계산하는 대상 바이너리의 BLAKE2b-256 해시입니다.
z_jail --root=<dir> [--seccomp-enforce] [--self-hash=<hex>]
[--quiet] [--verbose] -- <program> [args...]
| 플래그 | 설명 |
|---|---|
--root=<dir> | 샌드박스 루트 디렉터리(필수) |
--seccomp-enforce | seccomp-BPF syscall 화이트리스트 활성화 |
--self-hash=<hex> | 바이너리가 예상 BLAKE2b-256 해시와 일치하는지 확인 |
--quiet | 감사 출력 억제 |
--verbose | 디버그 로깅 활성화 |
--version | 빌드 ID 표시(Z-Jail/v1+dev) |
--help | 사용법 표시 후 종료 |