
Z-Jail v1.1.0
네임스페이스, pivot_root, seccomp-bpf, capability 드롭 및 증거 기반 평결 엔진(Truthimatics Public Version)을 결합하여 안전하고 감사 가능한 코드 실행을 제공하는 가볍고 다층적인 Linux 샌드박스입니다.
Z-Jail
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인가
기존 샌드박싱 솔루션은 트레이드오프가 있습니다:
| 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]
계층 순서
각 계층은 이후 계층이 이전 계층에 의해 취소될 수 없도록 순서가 정해져 있습니다:
- setrlimit — 다른 모든 것보다 먼저 CPU, 주소 공간, 파일 수, 프로세스 수를 제한합니다
- fd scrub — 보고 파이프를 제외한 모든 상속된 fd를 닫습니다
- PR_SET_DUMPABLE=0 — 코어 덤프 비활성화, /proc/self/mem 잠금
- pivot_root — 호스트 파일시스템에서 분리합니다. 이전 루트는 지연 언마운트됩니다
- PR_SET_NO_NEW_PRIVS — 이 시점 이후 setuid,
capset권한 상승 불가 - drop_caps — 모든 capabilities를 0으로 설정하고 securebits를 잠급니다
- seccomp-BPF — syscall을 화이트리스트로만 제한합니다
- signal parent — 부모에게 샌드박스가 준비되었음을 알립니다
- execve — 프로세스를 대상 바이너리로 교체합니다
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
계층
1. Truthimatics 공개 버전
증거 기반 판정 엔진. 실행된 바이너리에 대한 가중치가 부여된 관찰(observation)을 수집하여 최종 판정(DETERMINISTIC, REJECT, 또는 UNCERTAIN)을 결정합니다. 각 관찰에는 가중치가 부여되며, 총합의 50%를 초과하는 가중치를 가진 단일 관찰이 판정을 결정합니다.
2. 네임스페이스
clone()을 통해 5개의 네임스페이스가 생성됩니다:
| 네임스페이스 | 플래그 | 용도 |
|---|---|---|
| Mount | CLONE_NEWNS | 격리된 파일시스템 트리 |
| PID | CLONE_NEWPID | 프로세스 ID 공간(자식이 pid 1) |
| Net | CLONE_NEWNET | 네트워크 인터페이스 없음 |
| IPC | CLONE_NEWIPC | 공유 메모리/세마포어 없음 |
| UTS | CLONE_NEWUTS | 분리된 호스트 이름 |
초기 네임스페이스에서 CAP_SYS_ADMIN이 필요합니다.
3. pivot_root
마운트 네임스페이스 루트를 --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에 의해 차단됨) 샌드박스 프로세스가 호스트 루트로 탈출할 방법이 없습니다.
4. Capabilities
모든 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가 잠겨 재활성화가 불가능합니다.
5. NO_NEW_PRIVS
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
프로세스 또는 그 자식이 setuid 바이너리, 파일 capabilities, LSM 전환을 통해 새 권한을 획득하는 것을 방지합니다. 되돌릴 수 없습니다.
6. seccomp-BPF (whitelist-v1)
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합니다.
7. 감사(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..."
}
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 | 사용법 표시 후 종료 |
예제
# Run a static binary with all protections
sudo z_jail --root=./roots --seccomp-enforce -- bin/hello_static
# Run with binary integrity verification
sudo z_jail --root=./roots --seccomp-enforce \
--self-hash=$(sha256sum z_jail | cut -c1-64) -- bin/program
# Quiet mode (no audit JSON)
sudo z_jail --root=./roots --quiet -- bin/program
종료 코드
| 코드 | 의미 |
|---|---|
| 0 | 자식 프로세스 정상 종료(판정: DETERMINISTIC) |
| 1 | 자식 프로세스가 시그널로 종료됨(판정: REJECT) |
| 2 | 자체 해시: 잘못된 hex 문자열 또는 파일을 읽을 수 없음 |
| 3 | 자체 해시: 불일치(바이너리가 변조됨) |
| 101 | 자식 프로세스 설정 오류(rlimit 등) |
| 102 | 자식 프로세스 seccomp 필터 설치 실패 |
| 103 | 자식 프로세스 execve 실패(바이너리를 찾을 수 없거나 실행 권한 없음) |
| 104 | 자식 프로세스 pivot_root 실패 |
| 105 | 자식 프로세스 capability 제거 실패 |
| 125 | 네임스페이스 생성 실패(root로 실행했는지? 커널 지원 여부?) |
빌드 및 설치
요구 사항
- Linux 커널 ≥ 5.4(네임스페이스, seccomp-BPF, pivot_root)
- GCC ≥ 11(11.4, 13.2, 15.2에서 테스트됨)
- 외부 라이브러리 없음 — 표준 C 툴체인만
명령어
make # build z_jail (~130 KiB PIE binary)
make install # install to /usr/local/bin + man page
make clean # remove build artifacts
make dist # create release tarball
make check # smoke test (--version + --help)
바이너리는 -fstack-protector-strong, -D_FORTIFY_SOURCE=2, 전체 RELRO 및 -z now가 적용된 Position Independent Executable로 빌드됩니다.
컴파일 타임 옵션
make CC=clang CFLAGS="-O3 -march=native" # custom compiler/flags
테스트
빠른 테스트(root 불필요)
# seccomp filter logic (8 tests)
tests/build/seccomp_filter_test
# BLAKE2b known-answer test
tests/build/blake2b_known
이 테스트는 root가 필요 없으며 100ms 이내에 실행됩니다.
전체 테스트 스위트
make -C tests setup # build payloads + test roots
sudo bash tests/run_tests.sh # 17 scenarios
네임스페이스 생성에는 root가 필요합니다. 테스트 스위트는 다음을 다룹니다:
| # | 시나리오 | 유형 | 테스트 내용 |
|---|---|---|---|
| 0 | blake2b_regress | known-answer | BLAKE2b 구현 정확성 |
| 1 | seccomp_filter | standalone BPF | BPF 필터 로직의 8개 하위 테스트 |
| 2 | hello_static | ok | 기본 정적 바이너리 실행 |
| 3 | hello_dynamic | ok | ld-linux + libc를 포함한 동적 바이너리 |
| 4 | execve_replacement | ok | 샌드박스 내 execve(seccomp에 의해 차단됨) |
| 5 | fd_inherited_read | ok | stdin/stdout이 올바르게 상속됨 |
| 6 | mmap_bad_flags | killed | MAP_SHARED를 사용한 mmap 차단 |
| 7 | mmap_good_allowed | ok | MAP_PRIVATE|ANONYMOUS를 사용한 mmap 허용 |
| 8 | mmap_prot_exec | killed | PROT_EXEC를 사용한 mmap 차단 |
| 9 | mmap_self_modify | killed | 자기 수정 코드 차단 |
| 10 | ptrace | killed | ptrace 차단 |
| 11 | socket | killed | 소켓 생성 차단 |
| 12 | chroot_escape | killed | chroot syscall 차단 |
| 13 | double_chroot | killed | 이중 chroot 차단 |
| 14 | mount_replay | killed | mount syscall 차단 |
| 15 | cpu_exhaust | killed | RLIMIT_NPROC가 fork bomb 차단 |
| 16 | signal_parent | killed | 부모로의 시그널 차단 |
| 17 | self_hash | ok | 바이너리 무결성 검증 |
성능
WSL2(커널 6.18.x-microsoft-standard-WSL2, Kali Linux)에서 측정되었으며,
도구당 50개 샘플, 균일한 워크로드(본문이 exit_group(0)인 프리스탠딩 정적
바이너리), getrusage 하네스로 시간을 측정했습니다. 방법론은
docs/BENCHMARKS.md를 참조하세요.
| 지표 | 값 |
|---|---|
| 바이너리 크기 | ~73 KiB 스트립되지 않음(~28 KiB 스트립) |
| 평균 샌드박스 지연 시간 | 5.85 ± 1.45 ms (95% CI [5.45, 6.25]) |
| 최대 RSS | 1.62 MiB |
| 핵심 코드 줄 수 | ~900 |
직접 비교(동일 호스트, 동일 방법론)
| 도구 | 평균 지연 시간 ± 표준편차 | 최대 RSS | 기본 seccomp |
|---|---|---|---|
| Z-Jail | 5.85 ± 1.45 ms | 1.62 MiB | 지원 |
| bwrap | 3.56 ± 0.40 ms | 2.19 MiB | 미지원 |
| nsjail | 8.98 ± 1.68 ms | 7.91 MiB | 지원 |
테스트된 조건에서 Z-Jail은 세 가지 프로세스 수준 샌드박스 중 가장 작은 상주 메모리 세트를 가지며 지연 시간은 bwrap과 nsjail 사이입니다.
Bubblewrap이 가장 빠르지만 기본적으로 seccomp 필터링을 수행하지 않으므로 설정 작업이 더 적습니다. Z-Jail은 매 실행마다 seccomp 화이트리스트를 설치하고 capabilities를 제거하며 pivot_root를 수행합니다. gVisor(runsc)는 이 WSL2 커널에서 세그폴트가 발생하여 측정할 수 없었습니다. Firecracker는 microVM(VM 콜드 부팅, 다른 지표)을 통해 격리하므로 fork-to-exec 테이블에서 제외됩니다. 이는 단일 호스트에서 측정된 수치이므로 상대적인 값으로 간주하세요.
참고: 이전에 문서화된 수치(~8 ms, ~4 MiB, ~130 KiB)는 현재
make빌드(~73 KiB, ~5.9 ms)와 일치하지 않으며 부정확했던 것으로 보입니다. 위 수치는 이 코드베이스에서 다시 측정되었습니다. Truthimatics는 여전히 코드의 일부이며 제거되지 않았습니다(이전axiom_jail보고서는 단순히 다른 바이너리 이름과 하네스를 사용했습니다). 벤치마킹 중 발견된 마운트 전파 버그는src/sandbox.c에서 수정되었습니다(바인드 마운트 전에MS_REC|MS_PRIVATE). 재귀적 리마운트가 측정된 지연 시간의 일부를 차지합니다.
위협 모델
범위 내
- 신뢰할 수 없는 페이로드에 의한 임의 네이티브 코드 실행
chroot,mount,ptrace,socket,process_vm_writev를 통한 탈출- Fork bomb, CPU 고갈(
RLIMIT_CPU), 메모리 고갈(RLIMIT_AS) execve를 통한 파일 디스크립터 누수setuid/ 동적 링커 /LD_PRELOAD권한 상승- seccomp 필터 제거 또는 capability 재활성화
범위 외
- 허용된 syscall 표면을 벗어난 커널 제로데이
- 하드웨어 부채널 공격(Spectre, Meltdown)
- 공유
/proc,/sys마운트를 통한 동일 호스트 VM 탈출 CLONE_NEWNET+ 차단된socket이 제공하는 범위를 넘어서는 네트워크 이그레스- 형제 샌드박스의 리소스 기아(스타베이션)(cgroup 지원 필요)
가정
- 호스트 커널은 수정되지 않은 Linux ≥ 5.4
clone(CLONE_NEWNS|CLONE_NEWPID|...)성공(CAP_SYS_ADMIN필요)- 대상 바이너리가 정적으로 링크됨(또는
--root에 동적 라이브러리가 있음) - 프로덕션 배포에서
--self-hash=<hex>가 구성됨
문서
| 파일 | 설명 |
|---|---|
README.md | 이 파일 |
docs/ARCHITECTURE.md | 아키텍처 개요 |
docs/SANDBOX.md | 계층별 샌드박스 내부 구조 |
docs/SECCOMP.md | seccomp-BPF 화이트리스트 설계 |
docs/AUDIT_SCHEMA.md | 감사 JSON 스키마 참조 |
docs/THREAT_MODEL.md | 보안 가정 및 범위 |
docs/BLAKE2B.md | BLAKE2b 구현 세부사항 |
docs/BENCHMARKS.md | 성능 벤치마크 |
docs/BUILD.md | 빌드 지침 |
docs/adr/ | 아키텍처 결정 기록(문서 4개) |
man/z_jail.1 | 매뉴얼 페이지 |
SECURITY.md | 보안 정책 및 신고 |
CONTRIBUTING.md | 기여 방법 |
CHANGELOG.md | 릴리스 이력 |
ROADMAP.md | 향후 계획 |
TODO.md | 알려진 격차 및 계획된 작업 |
로드맵
v1(현재)
- 7계층 심층 방어 샌드박스
- BLAKE2b-256 콘텐츠 핑거프린팅
- 감사 JSON 출력
- 17개 테스트 시나리오
- 매뉴얼 페이지, 셸 완성(bash, zsh, fish)
v2(계획)
- 외부 seccomp 정책 파일(JSON 또는 BPF 소스)
- 샌드박스 인스턴스별 사용자 정의 네임스페이스 플래그
- CLI를 통한 구성 가능한 syscall 화이트리스트
- CI 통합을 위한 성능 프로파일링 훅
- 릴리스 서명(minisign/signify)
상태
라이선스
MIT — 전체 텍스트는 LICENSE를 참조하세요.
Z-Jail은 Linux 5.4+를 대상으로 WSL2(Kali Linux, GCC 15.2.0)에서 빌드되었습니다. 관리자: Division-36. 문제는 이슈 트래커에 보고해 주세요.