Bubblewrap (bwrap) — 루트리스 샌드박스
Bubblewrap 은 루트 권한 없이 네임스페이스 격리를 제공하는 샌드박스 도구입니다. Flatpak, xdg-desktop-portal, systemd-sandboxed 서비스의 기반 기술로 활용됩니다.
핵심 요약
- Bubblewrap (bwrap) — 루트 권한 없이 네임스페이스 격리를 제공하는 샌드박스 도구
- User Namespace — 루트 없는 컨테이너의 핵심. 내부 uid 0 을 외부 일반 사용자로 매핑
- Mount Namespace — bwrap 의 주된 격리 수단. 파일시스템 뷰를 재구성
- Seccomp-BPF — 시스템콜 필터로 위험한 호출 차단
- Capability 드롭 — CAP_NET_RAW, CAP_SYS_ADMIN 등 위험 권한 제거
단계별 이해
- User Namespace 생성
clone(CLONE_NEWUSER)로 새로운 사용자 네임스페이스 진입. 내부 uid 0 은 외부 일반 사용자. - 기타 네임스페이스 생성
PID, mount, network 네임스페이스를 추가 생성 (옵션으로 IPC, UTS). - 파일시스템 재구성
bind mount, tmpfs, proc/sysfs 재마운트로 샌드박스 내부 파일 뷰 구성. - 권한 제한
capability 드롭, seccomp 필터 적용으로 위험한 시스템콜 차단. - 실행
제한된 환경에서 지정된 명령어 실행.
개요
Bubblewrap(bwrap) 은 루트 권한 없이 동작하는 샌드박스 도구입니다. 2016 년 Project Atomic 에서 시작되었으며, 현재는 GitHub - containers/bubblewrap에서 유지보수됩니다.
- 최신 릴리스: v0.11.2 (2026-04-23)
- 주요 변경사항: CVE-2026-41163 수정, setuid 모드 보안 강화
- 빌드 옵션:
-Dsupport_setuid=false(기본값) 로 setuid 지원 비활성화 권장
Docker/runc 가 "풀 컨테이너"를 목표로 하는 반면, bwrap 은 단일 애플리케이션 격리에 최적화되어 있습니다. 이로 인해 다음과 같은 특징을 가집니다:
- 단일 바이너리 — 의존성 없이 정적 컴파일된 100KB 크기 실행 파일
- 루트리스 — user namespace 를 활용해 일반 사용자로 실행 가능
- 최소 권한 — 필요한 mount/bind 만 구성, 나머지는 차단
- Flatpak 기반 — Flatpak 런타임의 샌드박스 백엔드로 사용됨
- xdg-desktop-portal 통합 — 데스크톱 애플리케이션의 파일/네트워크 접근 제어
Docker/runc 와의 비교
Bubblewrap 은 Docker/runc 와 다음과 같은 차이가 있습니다:
| 특징 | Bubblewrap (bwrap) | Docker / runc |
|---|---|---|
| 목적 | 단일 애플리케이션 샌드박스 | 풀 컨테이너 런타임 |
| 루트 권한 | 불필요 (user namespace 활용) | 필요 (rootless mode 제외) |
| 바이너리 크기 | 약 100KB (정적 컴파일) | 수십 MB (dockerd + containerd + runc) |
| 데몬 프로세스 | 없음 (단일 실행) | 필수 (dockerd, containerd) |
| 이미지 관리 | 없음 (호스트 파일시스템 사용) | 레이어드 이미지 (overlay2 등) |
| 네트워킹 | 호스트 공유 또는 완전 차단 | veth pair, bridge, CNI 플러그인 |
| 주요 사용 사례 | Flatpak, 데스크톱 앱 샌드박스 | 마이크로서비스, CI/CD, 클라우드 |
| 설정 복잡도 | 명령행 옵션 (단순) | Dockerfile, compose, Kubernetes YAML |
| 시작 시간 | 수십 ms (즉시 실행) | 수백 ms ~ 수초 (이미지 풀링 포함) |
| 보안 모델 | 최소 권한, seccomp, capability 드롭 | 네임스페이스, cgroups, seccomp, LSM |
- Bubblewrap 선택 — 데스크톱 애플리케이션 격리, 빠른 시작 시간, 루트 권한 없음, 단순한 설정
- Docker 선택 — 마이크로서비스 배포, 이미지 관리 필요, 오케스트레이션 (Kubernetes), 네트워크 격리
Docker 는 데몬 (dockerd) 이 루트로 실행되며, 컨테이너 내부에서도 루트 권한이 필요합니다 (rootless mode 도 있지만 복잡). 반면 bwrap 은 일반 사용자가 즉시 실행할 수 있어 데스크톱 애플리케이션 샌드박스에 이상적입니다.
아키텍처
네임스페이스 계층 구조
Bubblewrap 은 다음과 같은 순서로 네임스페이스를 생성합니다:
마운트 흐름 다이어그램
Bubblewrap 이 파일시스템 뷰를 재구성하는 과정을 시각화했습니다:
Mount namespace 는 파일시스템 뷰만 격리합니다. 호스트의 /usr 을 --ro-bind 로 마운트해도, 샌드박스 내부에서는 읽기 전용으로만 보이지만, 실제 데이터는 호스트와 공유됩니다. 반면 --tmpfs 는 완전히 분리된 메모리 기반 파일시스템을 제공합니다.
Bubblewrap 생명주기 흐름
Bubblewrap 이 시작되고 종료될 때까지의 전체 흐름을 시각화했습니다:
- 1-3 단계: 커널 네임스페이스 생성. 이 단계에서 프로세스는 이미 격리됩니다.
- 4 단계: 마운트 작업은 mount namespace 내에서만 영향. 호스트 파일시스템은 안전합니다.
- 5 단계: capability 드롭과 seccomp 는 exec 전에 적용. 되돌릴 수 없습니다.
- 6 단계: exec 후 bwrap 프로세스는 새 프로그램으로 대체. PID namespace 에서는 bwrap 이 PID 1 입니다.
네임스페이스 생성 순서는 다음과 같습니다:
- User Namespace 먼저 생성 — 루트 권한 없이 다른 네임스페이스를 생성하기 위한 선행 조건
- PID/Mount/Network 네임스페이스 — user namespace 내에서 추가 생성
- UID/GID 매핑 —
/proc/[pid]/uid_map,/proc/[pid]/gid_map기록 - Mount 설정 — bind mount, tmpfs, proc/sysfs 재마운트
- Seccomp/Capability 적용 — 최종 권한 제한
마운트 전략
Bubblewrap 의 핵심은 mount namespace 를 활용한 파일시스템 격리입니다. 주요 마운트 옵션은 다음과 같습니다:
| 옵션 | 기능 | 사용 사례 |
|---|---|---|
--ro-bind <src> <dest> |
읽기 전용 bind mount | /usr, /lib 등 시스템 디렉터리 |
--bind <src> <dest> |
쓰기 가능한 bind mount | 사용자 데이터 디렉터리 |
--tmpfs <path> |
tmpfs 마운트 | /tmp, /var/tmp 등 임시 디렉터리 |
--dev <path> |
장치 노드 마운트 | /dev/null, /dev/zero 등 필수 장치 |
--proc <path> |
procfs 재마운트 | /proc (네임스페이스별 뷰) |
--sysfs <path> |
sysfs 재마운트 | /sys (하드웨어 정보) |
--dir <path> |
빈 디렉터리 생성 | 마운트 포인트용 디렉터리 |
--file <src> <dest> |
단일 파일 바인드 | 설정 파일, 소켓 파일 |
Mount namespace 는 마운트 이벤트만 격리하며, 실제 파일 접근 권한은 호스트의 DAC/SELinux/AppArmor 정책을 따릅니다. bwrap 은 추가적인 보안을 위해 seccomp 와 capability 제한을 함께 사용합니다.
커널 내부 구현
User Namespace 생성 흐름
Bubblewrap 이 user namespace 를 생성하는 과정은 다음과 같습니다:
/* bubblewrap 소스: newinstance() 함수 간략화 */
static void
newinstance(struct launch_options *launch_opts)
{
uid_t uid = getuid();
gid_t gid = getgid();
/* 1. User Namespace 생성 */
if (unshare(CLONE_NEWUSER) == -1)
die("Failed to unshare user namespace: %s", strerror(errno));
/* 2. UID 매핑 설정 */
write_uid_gid_map(uid, gid);
/* 3. 다른 네임스페이스 생성 (이제 루트 권한 없이 가능) */
if (launch_opts->share_pid_ns == 0) {
if (unshare(CLONE_NEWPID) == -1)
die("Failed to unshare PID namespace: %s", strerror(errno));
}
if (launch_opts->share_net_ns == 0) {
if (unshare(CLONE_NEWNET) == -1)
die("Failed to unshare network namespace: %s", strerror(errno));
}
/* 4. Mount Namespace 는 항상 생성 */
if (unshare(CLONE_NEWNS) == -1)
die("Failed to unshare mount namespace: %s", strerror(errno));
}
코드 설명
-
7-9 행
unshare(CLONE_NEWUSER)로 user namespace 생성. 이 시점부터 프로세스는 새로운 사용자 네임스페이스에 속하며, 외부 uid 와 내부 uid 가 분리됩니다. -
12 행
write_uid_gid_map()함수가/proc/[pid]/uid_map,/proc/[pid]/gid_map에 매핑을 기록합니다. 예: "0 1000 1" → 내부 uid 0 이 외부 uid 1000 에 매핑. - 15-24 행 User namespace 내부에서는 이제 루트 권한 (CAP_SYS_ADMIN 등) 을 가지므로, 추가 네임스페이스를 루트 없이 생성할 수 있습니다. 이것이 "루트리스 컨테이너"의 핵심 메커니즘입니다.
- 27-29 행 Mount namespace 는 항상 생성됩니다. bwrap 의 주된 격리 수단이 파일시스템 뷰 재구성임을 반영합니다.
UID/GID 매핑 상세
User namespace 의 UID 매핑은 /proc/[pid]/uid_map 파일에 기록됩니다. 형식은 다음과 같습니다:
# 형식: <내부 UID> <외부 UID> <길이>
0 1000 1 # 내부 uid 0 → 외부 uid 1000 (1 개 매핑)
1 100000 65536 # 내부 uid 1-65536 → 외부 uid 100000-165535
Bubblewrap 은 기본적으로 단일 사용자 매핑만 사용합니다 (내부 uid 0 = 외부 일반 사용자). Flatpak 은 /etc/subuid, /etc/subgid 를 활용해 더 넓은 범위를 매핑합니다.
# /etc/subuid 예시
minzkn:100000:65536
# bwrap 에서 subuid 활용 (Flatpak 스타일)
$ bwrap --uid 0 --gid 0 \
--ro-bind /usr /usr \
--bind /home/minzkn/app /app \
-- /usr/bin/flatpak-app
Seccomp-BPF 필터
Bubblewrap 은 Seccomp-BPF를 활용해 위험한 시스템콜을 차단합니다. 기본 필터는 다음과 같습니다:
/* bubblewrap 소스: seccomp.c 간략화 */
static const struct scmp_syscall blocked_syscalls[] = {
"ptrace",
"process_vm_readv",
"process_vm_writev",
"init_module",
"finit_module",
"delete_module",
"kexec_load",
"kexec_file_load",
"userfaultfd",
"bpf",
{ 0 }
};
static void
setup_seccomp(void)
{
struct scmp_arg_cmp arg_cmp;
int i;
for (i = 0; blocked_syscalls[i].name != NULL; i++) {
int syscall = scmp_syscall_resolve_name(blocked_syscalls[i].name);
if (syscall < 0)
continue;
scmp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), syscall);
}
scmp_load_program(ctx);
}
코드 설명
-
3-14 행
차단할 시스템콜 목록. 커널 모듈 조작 (
init_module), ptrace, BPF, userfaultfd 등 위험한 호출이 포함됩니다. - 21-24 행 시스템콜 이름을 번호로 변환. 아키텍처마다 번호가 다르므로 libseccomp 가 해결합니다.
- 26 행 SCMP_ACT_ERRNO(EPERM) 로 차단. 해당 시스템콜 호출 시 EPERM 오류 반환.
- 29 행 BPF 프로그램을 커널에 로드. 이후부터 필터가 적용됩니다.
-
3-14 행
차단할 시스템콜 목록. 커널 모듈 조작 (
init_module), ptrace, BPF, userfaultfd 등 위험한 호출이 포함됩니다. 이러한 시스템콜은 컨테이너 탈출에 악용될 수 있습니다.
마운트 설정 상세 구현
Bubblewrap 이 bind mount 와 tmpfs 를 설정하는 내부 코드입니다:
/* bubblewrap 소스: bind_mount() 및 do_mount() 간략화 */
static void
bind_mount(const char *src, const char *dest, int read_only)
{
/* 1. 먼저 bind mount 수행 */
if (mount(src, dest, "bind", MS_BIND | MS_REC, NULL) == -1)
die("Failed to bind mount %s -> %s: %s", src, dest, strerror(errno));
/* 2. 읽기 전용 옵션이 있으면 remount */
if (read_only) {
unsigned long flags = MS_BIND | MS_REMOUNT | MS_RDONLY | MS_REC;
if (mount(src, dest, "bind", flags, NULL) == -1)
die("Failed to remount read-only %s: %s", dest, strerror(errno));
}
}
static void
mount_tmpfs(const char *dest)
{
/* tmpfs 마운트: 메모리 기반 파일시스템 */
if (mount("tmpfs", dest, "tmpfs", MS_NOSUID | MS_NODEV, "mode=755") == -1)
die("Failed to mount tmpfs on %s: %s", dest, strerror(errno));
}
코드 설명
-
5-7 행
mount()시스템콜에MS_BIND플래그를 사용하면 기존 파일/디렉터리를 다른 위치로 바인드합니다.MS_REC는 재귀적 바인드로, 하위 디렉터리도 모두 포함됩니다. -
10-14 행
읽기 전용 옵션이 있으면
MS_REMOUNT | MS_RDONLY로 다시 마운트합니다. 이는 bind mount 후 별도 remount 작업이 필요함을 의미합니다 (커널 동작 방식). -
19-22 행
tmpfs 마운트는
MS_NOSUID(setuid비트 무시),MS_NODEV(장치 노드 생성 금지) 옵션을 사용합니다. 보안 강화를 위한 기본 설정입니다.
Capability 드롭
Bubblewrap 은 exec 직전 모든 capability 를 드롭합니다. 이는 샌드박스 내부에서 setuid 바이너리 등을 통한 권한 상승을 방지합니다.
/* bubblewrap 소스: drop_capabilities() 간략화 */
static void
drop_capabilities(void)
{
struct __user_cap_header_struct hdr;
struct __user_cap_data_struct data[2];
hdr.version = _LINUX_CAPABILITY_VERSION_3;
hdr.pid = 0;
/* 모든 capability 를 0 으로 클리어 */
data[0].effective = 0;
data[0].permitted = 0;
data[0].inheritable = 0;
data[1].effective = 0;
data[1].permitted = 0;
data[1].inheritable = 0;
if (capset(&hdr, data) == -1)
die("Failed to capset: %s", strerror(errno));
}
사용법
기본 샌드박스
가장 간단한 bwrap 사용법입니다:
# 최소 샌드박스: / 만 읽기 전용으로 마운트
$ bwrap --ro-bind / / --dev /dev --proc /proc --unshare-all --share-net \
--chdir /home/minzkn -- uid 0 sh
# 샌드박스 내부 확인
bwrap$ id
uid=0(root) gid=0(root) groups=0(root)
bwrap$ mount | head -5
overlay on / type overlay (...)
proc on /proc type proc (...)
dev on /dev type devtmpfs (...)
bwrap$ cat /proc/1/status | grep -E "^(Name|Pid|PPid)"
Name: uid
Pid: 1
PPid: 1
--unshare-all 옵션은 모든 네임스페이스를 격리하며, --share-net은 네트워크 네임스페이스만 공유합니다 (인터넷 접근 필요 시).
Flatpak 스타일 격리
Flatpak 이 bwrap 을 사용하는 방식을 모방한 예시입니다:
# Flatpak 스타일: 최소 파일시스템 + 사용자 디렉터리 일부 마운트
$ bwrap \
--ro-bind /usr /usr \
--ro-bind /lib /lib \
--ro-bind /lib64 /lib64 \
--bind /home/minzkn/.var/app/com.example.MyApp /app/data \
--bind /home/minzkn/Music /app/data/Music:ro \
--tmpfs /tmp \
--proc /proc \
--dev /dev \
--unset-env SECRET \
--setenv APP_HOME /app \
--setenv XDG_RUNTIME_DIR /run/user/1000 \
--bind /run/user/1000 /run/user/1000 \
--sync-fd 3 \
-- /usr/bin/flatpak-app
네트워크 격리
네트워크 네임스페이스를 격리하면 외부 접근이 완전히 차단됩니다:
# 네트워크 완전 차단
$ bwrap --ro-bind / / --dev /dev --proc /proc --unshare-net \
-- ping 8.8.8.8
ping: socket: Operation not permitted
# 네트워크 공유 (기본값: --share-net)
$ bwrap --ro-bind / / --dev /dev --proc /proc \
-- curl -s https://example.com | head
장치 접근 제어
특정 장치만 접근 가능하게 구성할 수 있습니다:
# 필수 장치만 마운트
$ bwrap --ro-bind / / \
--dev /dev/null \
--dev /dev/zero \
--dev /dev/random \
--dev /dev/urandom \
--dev /dev/tty \
--proc /proc \
-- /bin/sh
# GPU 접근 허용 (드结实게 필요)
$ bwrap --ro-bind / / \
--dev /dev/dri/card0 \
--dev /dev/dri/renderD128 \
-- /usr/bin/gpu-app
실전 활용 예시
1. 웹 브라우저 샌드박스
# Firefox 을 bwrap 으로 샌드박스 실행
$ bwrap \
--ro-bind /usr /usr \
--ro-bind /lib /lib \
--ro-bind /lib64 /lib64 \
--bind /home/minzkn/Downloads /app/Downloads \
--bind /home/minzkn/Documents /app/Documents:ro \
--tmpfs /tmp \
--tmpfs /var/tmp \
--proc /proc \
--dev /dev \
--dev /dev/dri/card0 \
--dev /dev/dri/renderD128 \
--bind /run/user/1000/pulse /run/user/1000/pulse \
--bind /run/user/1000/bus /run/user/1000/bus \
--setenv PULSE_SERVER unix:/run/user/1000/pulse/native \
--setenv DBUS_SESSION_BUS_ADDRESS unix:path=/run/user/1000/bus \
--unshare-pid \
--new-session \
-- /usr/bin/firefox --no-remote
PulseAudio 소켓과 DBus 세션 버스를 바인드하면 샌드박스 내부에서도 사운드 출력과 데스크톱 통합이 가능합니다. Flatpak 이 바로 이 방식을 사용합니다.
2. 신뢰할 수 없는 스크립트 실행
# 다운로드받은 불신 스크립트 실행 (네트워크 차단, 홈 디렉터리 보호)
$ bwrap \
--ro-bind /usr /usr \
--ro-bind /bin /bin \
--ro-bind /lib /lib \
--tmpfs /home \
--tmpfs /var \
--tmpfs /tmp \
--proc /proc \
--dev /dev \
--unshare-net \
--unset-env HOME \
--setenv HOME /home \
-- /bin/bash /path/to/untrusted-script.sh
3. CI/CD 빌드 환경 격리
# CI 파이프라인에서 빌드 작업 격리 (호스트 오염 방지)
$ bwrap \
--ro-bind /usr /usr \
--ro-bind /etc /etc \
--bind /workspace/build /app \
--bind /workspace/output /output \
--tmpfs /tmp \
--proc /proc \
--dev /dev \
--unshare-net \
--cap-drop-all \
-- /usr/bin/make -j$(nproc) dist
4. 멀티미디어 인코딩 샌드박스
# ffmpeg 인코딩 (GPU 가속 + 입력/출력 디렉터리만 접근)
$ bwrap \
--ro-bind /usr /usr \
--ro-bind /lib /lib \
--ro-bind /lib64 /lib64 \
--bind /media/videos/input /input:ro \
--bind /media/videos/output /output \
--dev /dev/dri/card0 \
--dev /dev/dri/renderD128 \
--tmpfs /tmp \
--proc /proc \
--dev /dev \
--unshare-net \
-- /usr/bin/ffmpeg -i /input/source.mp4 -c:v h264_vaapi /output/encoded.mp4
5. 개발 환경 격리 (프로젝트별)
# 프로젝트별 독립적인 개발 환경 (전역 패키지 오염 방지)
$ bwrap \
--ro-bind /usr /usr \
--ro-bind /lib /lib \
--bind /projects/myapp /app \
--bind /projects/myapp/venv /venv \
--tmpfs /home \
--tmpfs /tmp \
--proc /proc \
--dev /dev \
--share-net \
--setenv VIRTUAL_ENV /venv \
--setenv PATH /venv/bin:/usr/bin \
--setenv HOME /home \
-- /venv/bin/python /app/main.py
전체 명령 옵션 레퍼런스
Bubblewrap 의 모든 명령행 옵션을 카테고리별로 정리했습니다:
네임스페이스 옵션
| 옵션 | 기능 | 기본값 | 예시 |
|---|---|---|---|
--unshare-all |
모든 네임스페이스 격리 | 비활성 | --unshare-all --share-net |
--unshare-user |
User namespace 격리 | 자동 (필수) | --unshare-user |
--unshare-pid |
PID namespace 격리 | 비활성 | --unshare-pid --new-session |
--unshare-net |
Network namespace 격리 | 비활성 (공유) | --unshare-net |
--unshare-ipc |
IPC namespace 격리 | 비활성 | --unshare-ipc |
--unshare-uts |
UTS namespace 격리 | 비활성 | --unshare-uts --hostname myhost |
--unshare-cgroup |
Cgroup namespace 격리 | 비활성 | --unshare-cgroup |
--unshare-try |
가능하면 네임스페이스 격리 (실패 시 계속) | 비활성 | --unshare-try --unshare-pid |
--share-net |
네트워크 네임스페이스 공유 (명시적) | 기본 동작 | --share-net |
--hostname <name> |
UTS namespace 에서 호스트명 설정 | 호스트와 동일 | --hostname sandbox |
--new-session |
새로운 세션 리더로 시작 | 비활성 | --unshare-pid --new-session |
파일시스템 옵션
| 옵션 | 기능 | 주의사항 | 예시 |
|---|---|---|---|
--ro-bind <src> <dest> |
읽기 전용 bind mount | src 는 절대경로 | --ro-bind /usr /usr |
--bind <src> <dest> |
쓰기 가능한 bind mount | src 는 절대경로 | --bind /home/user/app /app |
--ro-bind-try <src> <dest> |
src 가 있으면 읽기 전용 마운트 (없으면 무시) | 선택적 마운트 | --ro-bind-try /etc/localtime /etc/localtime |
--bind-try <src> <dest> |
src 가 있으면 쓰기 가능 마운트 (없으면 무시) | 선택적 마운트 | --bind-try /run/user/1000 /run/user/1000 |
--tmpfs <path> |
tmpfs 마운트 (메모리 기반) | 데이터는 재시작 시 소실 | --tmpfs /tmp |
--dev <path> |
장치 노드 마운트 | 필수 장치만 허용 | --dev /dev/null |
--proc <path> |
procfs 재마운트 | 네임스페이스별 뷰 | --proc /proc |
--sysfs <path> |
sysfs 재마운트 | 읽기 전용 권장 | --sysfs /sys |
--dev-bind <src> <dest> |
장치 디렉터리 bind mount | /dev 아래 접근 | --dev-bind /dev/dri /dev/dri |
--ro-bind-data <fd> <dest> |
파일 디스크립터에서 데이터 읽어서 파일로 마운트 | 고급 사용 | --ro-bind-data 3 /etc/hosts |
--dir <path> |
빈 디렉터리 생성 | 마운트 포인트용 | --dir /var/run |
--file <src> <dest> |
단일 파일 바인드 | src 는 절대경로 | --file /etc/resolv.conf /etc/resolv.conf |
--remount-ro <path> |
기존 마운트를 읽기 전용으로 재마운트 | 마운트 후 적용 | --remount-ro /sys |
환경 변수 옵션
| 옵션 | 기능 | 예시 |
|---|---|---|
--setenv <VAR> <value> |
환경 변수 설정 | --setenv HOME /home |
--unsetenv <VAR> |
환경 변수 제거 | --unsetenv SECRET_KEY |
--clearenv |
모든 환경 변수 제거 (후에 --setenv 로 추가) | --clearenv --setenv PATH /usr/bin |
보안 옵션
| 옵션 | 기능 | 보안 영향 | 예시 |
|---|---|---|---|
--cap-drop-all |
모든 capability 드롭 | 강력한 권한 제한 | --cap-drop-all |
--cap-add <CAP> |
특정 capability 추가 | 세밀한 권한 제어 | --cap-add CAP_NET_BIND_SERVICE |
--perms <octal> |
마운트 권한 설정 | 파일 접근 제어 | --perms 755 |
--uid <uid> |
내부 user namespace 에서 uid 설정 | 사용자 격리 | --uid 0 |
--gid <gid> |
내부 user namespace 에서 gid 설정 | 그룹 격리 | --gid 0 |
--chdir <path> |
작업 디렉터리 변경 | exec 전 cd | --chdir /app |
--sync-fd <fd> |
지정된 fd 가 닫힐 때까지 대기 | Flatpak 생명주기 관리 | --sync-fd 3 |
--die-with-parent |
부모 프로세스 종료 시 자동 종료 | 좀비 프로세스 방지 | --die-with-parent |
--as-pid-1 |
PID 1 로 실행 (시그널 처리) | PID namespace 에서 필요 | --as-pid-1 --unshare-pid |
--as-user-1 |
uid 0 대신 실제 사용자로 실행 | 루트 없는 컨테이너 | --as-user-1 |
고급/디버깅 옵션
| 옵션 | 기능 | 예시 |
|---|---|---|
--args <file> |
파일에서 명령행 인수 읽기 (NULL 구분) | --args /tmp/bwrap-args.XXXXXX |
--json-args <fd> |
파일 디스크립터에서 JSON 형식 인수 읽기 | --json-args 4 |
--seccomp <file> |
사용자 정의 seccomp 필터 로드 | --seccomp /etc/bwrap/seccomp.bpf |
--userns <path> |
기존 user namespace 재사용 | --userns /proc/1234/ns/user |
--userns2 <path> |
2 단계 user namespace 설정 | --userns2 /proc/5678/ns/user |
--info-fd <fd> |
JSON 형식 샌드박스 정보 출력 | --info-fd 5 |
bwrap 은 옵션을 왼쪽에서 오른쪽 순서로 처리합니다. 마운트 옵션은 exec 전에 모두 처리되며, --setenv/clearenv 도 exec 직전 적용됩니다. --sync-fd 는 exec 후 대기 동작입니다.
보안 심층 분석
User Namespace 보안 함의
User namespace 는 강력한 격리 수단이지만, 몇 가지 보안 고려사항이 있습니다:
- 내부 루트 ≠ 외부 루트 — 샌드박스 내부 uid 0 은 외부 일반 사용자에 매핑됩니다. 따라서
chown root도 외부에서는 일반 사용자 소유로 인식됩니다. - Capability 는 namespace 내에서만 유효 — 내부에서 CAP_SYS_ADMIN 을 가져도 외부 파일시스템에는 적용되지 않습니다.
- Setuid 바이너리 무효화 — user namespace 내부에서 setuid 바이너리는 효과적으로 동작하지 않습니다 (커널 설정에 따라 다름).
# 샌드박스 내부에서 root 소유 파일 생성
bwrap$ touch /app/data/rootfile
bwrap$ chown root:root /app/data/rootfile
bwrap$ ls -l /app/data/rootfile
-rw-r--r-- 1 root root 0 May 10 10:00 /app/data/rootfile
# 외부에서 확인 (실제 소유자는 일반 사용자)
$ ls -l /home/minzkn/.var/app/com.example.MyApp/rootfile
-rw-r--r-- 1 minzkn minzkn 0 May 10 10:00 /home/minzkn/.var/app/.../rootfile
--share-net 보안 위험
--share-net 옵션은 네트워크 네임스페이스를 호스트와 공유합니다. 이는 다음과 같은 위험을 내포합니다:
- 로컬 네트워크 접근 — 샌드박스 내부에서 호스트가 리스닝하는 소켓에 접근 가능
- Raw 소켓 생성 — CAP_NET_RAW 가 드롭되었더라도 일부 소켓 연산 가능
- 네트워크 스니핑 — AF_PACKET 소켓을 통한 패킷 캡처 시도 (차단 필요)
네트워크가 필요 없는 애플리케이션은 --unshare-net을 사용하세요. Flatpak 은 --socket=X11, --socket=wayland, --share=network 등 세분화된 권한을 제공합니다.
커널 설정 의존성
Bubblewrap 동작에 필요한 커널 설정 옵션입니다:
| 옵션 | 필수 여부 | 설명 |
|---|---|---|
CONFIG_USER_NS |
필수 | User Namespace 지원. 루트리스 컨테이너의 핵심. |
CONFIG_PID_NS |
권장 | PID Namespace 지원. 프로세스 격리. |
CONFIG_NET_NS |
권장 | Network Namespace 지원. 네트워크 격리. |
CONFIG_MOUNT_NS |
필수 | Mount Namespace 지원. 파일시스템 격리. |
CONFIG_SECCOMP |
권장 | Seccomp-BPF 지원. 시스템콜 필터링. |
CONFIG_SECURITY_CAPABILITIES |
권장 | Capability 지원. 권한 제한. |
# 현재 커널 설정 확인
$ zgrep USER_NS /proc/config.gz
CONFIG_USER_NS=y
# 또는
$ grep USER_NS /boot/config-$(uname -r)
CONFIG_USER_NS=y
CVE 취약점 및 공격 시나리오
Bubblewrap 과 user namespace 관련 주요 CVE 사례입니다:
CVE-2026-41163 — setuid 모드 ptrace 취약점 (최신)
2026 년 4 월 발견된 이 취약점은 setuid 모드에서 bubblewrap 이 dumpable 플래그를 설정하지 않아 발생했습니다. 공격자는 ptrace 를 통해 저권한 설정 프로세스를 디버깅하고 메모리를 조작할 수 있습니다.
- 영향받는 버전: setuid 모드로 빌드된 bubblewrap v0.11.1 이하
- 패치 버전: v0.11.2 (2026-04-23)
- 근본적 해결:
-Dsupport_setuid=false옵션으로 빌드 시 setuid 모드 자체 비활성화 - 현대적인 user namespace 지원 커널에서는 setuid 모드가 불필요하므로 비활성화 권장
CVE-2022-0185 — Linux Kernel Use-After-Free in fs_context
2022 년 2 월 발견된 이 취약점은 user namespace 내부에서 mount 작업 수행 시 use-after-free 를 유발할 수 있습니다. 공격자는 로컬 루트 권한 상승이 가능합니다.
# CVE-2022-0185 개념 증명 (간략화, 실제 공격 코드 아님)
$ unshare -Urmn bash
# 내부에서 fs_context 조작으로 UAF 트리거
# → heap corruption → privilege escalation
- Linux 커널 5.10 ~ 5.16.2 (패치 버전 이전)
- Bubblewrap, Podman, Docker rootless 등 user namespace 활용 도구
- 완화 조치: 커널 5.16.3+ 또는 백포트 패치 적용
CVE-2021-4154 — use-after-free in getname_flags
이 취약점은 unshare()와 execve() 사이의 경쟁 조건을 악용합니다:
// CVE-2021-4154 공격 흐름 (의사 코드)
pid_t pid = fork();
if (pid == 0) {
unshare(CLONE_FS); /* 파일시스템 정보 공유 해제 */
execve(exploreit); /* 경쟁 조건 트리거 */
}
/* 부모 프로세스는 동시에 fs 구조 변조 */
Bubblewrap 빌드 옵션 및 보안 설정
v0.11.2 에서 도입된 새로운 빌드 옵션입니다:
# setuid 지원 비활성화 (권장 - 기본값)
$ meson setup _builddir -Dsupport_setuid=false
$ meson compile -C _builddir
# setuid 지원 활성화 (레거시 시스템용)
$ meson setup _builddir -Dsupport_setuid=true
$ meson compile -C _builddir
- 현대 리눅스 커널 (3.8+) 은 unprivileged user namespace 를 지원하므로 setuid 불필요
- setuid 바이너리는 추가적인 공격 면적 (attack surface) 을 제공
-Dsupport_setuid=false로 빌드된 바이너리는 setuid 비트가 설정되어도 거부하고 실행되지 않음- 보안 감사 관점에서 setuid 코드 경로를 아예 제거하면 검증 부담 감소
Ubuntu 25.04 의 AppArmor 프로파일
Ubuntu 25.04 에서는 bubblewrap 을 위한 AppArmor 프로파일이 기본 제공됩니다:
# AppArmor 프로파일 확인
$ aa-status | grep bwrap
/usr/bin/bwrap
bwrap-userns-restrict
# 프로파일 내용 요약
$ sudo cat /etc/apparmor.d/usr.bin.bwrap
# bwrap-userns-restrict: user namespace 생성 허용
# → 샌드박스 설정 완료 후 더 엄격한 프로파일로 전환
이 프로파일은 bubblewrap 이 user namespace 를 생성하고 샌드박스를 설정할 수 있도록 허용한 후, 실행 중인 애플리케이션에 대해 더 제한적인 정책을 적용합니다.
Bubblewrap 우회 공격 시나리오
다음과 같은 설정 오류 시 샌드박스 탈출이 가능합니다:
# 시나리오 1: --bind로 호스트 /root 바인드 (치명적 실수)
$ bwrap --ro-bind / / --bind /root /root -- /bin/bash
# 샌드박스 내부에서 /root/.ssh/id_rsa 접근 가능
# 시나리오 2: --dev로 위험한 장치 노출
$ bwrap --ro-bind / / --dev /dev/mem -- /bin/bash
# /dev/mem 직접 접근으로 물리 메모리 읽기/쓰기 가능
# 시나리오 3: procfs 통한 호스트 프로세스 조작
$ bwrap --ro-bind / / --proc /proc -- /bin/bash
# /proc/[host-pid]/mem 접근 시도 (ptrace_scope 설정에 따라 다름)
- 최소 마운트: 필요한 디렉터리만 --bind/--ro-bind
- 장치 제한: --dev 는 /dev/null, /dev/zero 등 필수 장치만
- 커널 최신 유지: user namespace 관련 CVE 패치 적용
- 다중 방어: seccomp + capability 드롭 + SELinux/AppArmor
디버깅
strace 시스템콜 추적
bwrap 이 수행하는 시스템콜을 추적하면 내부 동작을 파악할 수 있습니다:
# bwrap 시스템콜 추적
$ strace -f bwrap --ro-bind / / --dev /dev --proc /proc -- uid 0 sh 2>&1 | \
grep -E "^(unshare|mount|capset|prctl)" | head -20
unshare(CLONE_NEWUSER) = 0
mount("proc", "/proc", "proc", MS_NOSUID|MS_NODEV|MS_NOEXEC, NULL) = 0
capset({version=_LINUX_CAPABILITY_VERSION_3, pid=0}, []) = 0
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) = 0
execve("/bin/sh", [["/bin/sh"]], [...]) = 0
네임스페이스 확인
실행 중인 bwrap 프로세스의 네임스페이스를 확인할 수 있습니다:
# bwrap 프로세스 찾기
$ pgrep -f "bwrap.*uid 0"
12345
# 네임스페이스 확인
$ ls -l /proc/12345/ns/
total 0
lrwxrwxrwx 1 minzkn minzkn 0 May 10 10:00 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 minzkn minzkn 0 May 10 10:00 ipc -> 'ipc:[4026532280]'
lrwxrwxrwx 1 minzkn minzkn 0 May 10 10:00 mnt -> 'mnt:[4026532278]'
lrwxrwxrwx 1 minzkn minzkn 0 May 10 10:00 net -> 'net:[4026531992]'
lrwxrwxrwx 1 minzkn minzkn 0 May 10 10:00 pid -> 'pid:[4026532279]'
lrwxrwxrwx 1 minzkn minzkn 0 May 10 10:00 pid_for_children -> 'pid:[4026532279]'
lrwxrwxrwx 1 minzkn minzkn 0 May 10 10:00 user -> 'user:[4026532277]'
lrwxrwxrwx 1 minzkn minzkn 0 May 10 10:00 uts -> 'uts:[4026532281]'
# 호스트 네임스페이스와 비교
$ ls -l /proc/self/ns/ | grep -E "^(mnt|pid|user)"
lrwxrwxrwx 1 minzkn minzkn 0 May 10 10:00 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 minzkn minzkn 0 May 10 10:00 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 minzkn minzkn 0 May 10 10:00 user -> 'user:[4026531837]'
네임스페이스 ID 가 다르면 격리되었음을 의미합니다.
Capability 검증
샌드박스 내부의 capability 를 확인할 수 있습니다:
# 샌드박스 내부에서 capability 확인
$ bwrap --ro-bind / / --dev /dev --proc /proc -- capsh --print
Current: cap_chown,cap_setuid,cap_setgid+ep
Bounding set =0
# 모든 capability 드롭 후
$ bwrap --ro-bind / / --dev /dev --proc /proc --cap-drop-all -- capsh --print
Current: =
Bounding set =0
고급 디버깅 기법
1. 네임스페이스 진입 (nsenter)
실행 중인 bwrap 샌드박스에 외부에서 진입할 수 있습니다:
# bwrap 프로세스 PID 찾기
$ pgrep -f "bwrap.*uid 0"
12345
# 해당 네임스페이스로 진입
$ sudo nsenter -t 12345 -a -- /bin/bash
# -a: 모든 네임스페이스 진입
# -t TARGET_PID: 대상 프로세스
# 샌드박스 내부 확인
(nsenter)# pwd
/
(nsenter)# mount | grep tmpfs
tmpfs on /tmp type tmpfs (...)
nsenter는 CAP_SYS_ADMIN capability 가 필요하므로 루트 권한이 있어야 합니다. 프로덕션 환경에서는 사용에 주의하세요.
2. Mount Namespace 상세 분석
# 마운트 이벤트 추적 (mountinfo)
$ cat /proc/12345/mountinfo | head -20
128 52 0:82 / / rw,nosuid,nodev,relatime - tmpfs tmpfs rw,size=1024k
129 128 0:5 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
130 128 0:83 /dev /dev rw,nosuid - devtmpfs devtmpfs rw,size=3896988k
# 마운트 옵션 상세
$ sudo cat /proc/12345/mountstats | grep -A5 "^device tmpfs"
device tmpfs mounted on /tmp with fstype tmpfs
- stats: major 0 minor 82
- write: 123 456789
- read: 456 123456
3. Seccomp 필터 분석
# 적용된 seccomp 필터 확인
$ grep Seccomp /proc/12345/status
Seccomp: 2
Seccomp_filters: 1
# 차단된 시스템콜 로그 (dmesg)
$ dmesg | grep -i "seccomp" | tail -10
[12345.678901] audit: type=1326 audit(...): pid=12345 comm="bwrap" syscall=bpf ... seccomp
[12345.678902] audit: type=1326 audit(...): pid=12345 comm="bwrap" syscall=ptrace ... seccomp
4. Performance Profiling (perf)
# bwrap 시작 시간 프로파일링
$ perf record -g -p 12345 sleep 1
$ perf report --stdio | head -50
# 마운트 오버헤드 분석
$ perf trace -p 12345 -e mount,umount2 --duration 100us
15.234 mount(0x7fff12345000, 0x7fff12346000, "bind", ...) = 0 <2.345 ms>
18.567 mount(0x7fff12347000, 0x7fff12348000, "tmpfs", ...) = 0 <1.234 ms>
5. AppArmor/SELinux 감사 로그
# AppArmor 감사 로그
$ sudo grep "bwrap" /var/log/kern.log | tail -20
kernel: [12345.678] audit: type=1400 audit(...): apparmor="DENIED" operation="open" profile="bwrap" name="/etc/shadow" ...
# SELinux 감사 로그
$ sudo ausearch -m avc -ts recent | grep bwrap
type=AVC msg=audit(...): avc: denied { read } for pid=12345 comm="bwrap" name="shadow" ...
strace -f로 시스템콜 추적/proc/[pid]/ns/*로 네임스페이스 확인/proc/[pid]/mountinfo로 마운트 뷰 검증capsh --print로 capability 확인dmesg로 커널 감사 로그 확인
실전 통합 사례
Flatpak 통합
Flatpak 은 bwrap 을 샌드박스 백엔드로 사용합니다. Flatpak 런타임은 다음과 같이 bwrap 을 호출합니다:
# Flatpak 이 내부적으로 수행하는 bwrap 호출 (간략화)
bwrap \
--args /tmp/flatpak-bwrap-args.XXXXXX \
--unshare-try \
--share-network \
--share-ipc \
--setenv FLATPAK_ID com.example.MyApp \
--setenv FLATPAK_ARCH x86_64 \
-- /usr/bin/flatpak-bwrap-entrypoint
Flatpak 은 --filesystem=home, --socket=X11, --device=dri 등 고급 권한 옵션을 bwrap 명령어로 변환합니다.
XDG Desktop Portal
xdg-desktop-portal은 데스크톱 애플리케이션이 샌드박스 밖의 리소스 (파일, 알림, 카메라) 에 안전하게 접근하도록 하는 중계자입니다. bwrap 과 연동하여 다음과 같은 기능을 제공합니다:
- FilePortal — 파일 선택 다이얼로그를 통한 파일 접근 (직접 경로 지정 금지)
- NotificationPortal — 데스크톱 알림 전송
- CameraPortal — 웹캠 접근 제어
- SecretPortal — 키링 (Secret Service) 접근
systemd-sandboxed 서비스
systemd 는 SystemCallFilter=, CapabilityBoundingSet=, PrivateTmp= 등 유닛 설정을 통해 샌드박싱을 제공합니다. 내부적으로 bwrap 과 유사한 네임스페이스 격리를 사용합니다.
[Unit]
Description=My Sandboxed Service
[Service]
Type=simple
ExecStart=/usr/bin/myapp
User=myappuser
# 샌드박스 옵션
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/myapp
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
NoNewPrivileges=yes
한계 및 주의사항
알려진 한계
- User Namespace 의존 — 일부 배포판 (Ubuntu 등) 은 보안 이유로 user namespace 를 제한합니다.
/proc/sys/kernel/unprivileged_userns_clone=0확인 필요. - SELinux/AppArmor 충돌 — bwrap 이 SELinux/AppArmor 정책을 우회하지는 않습니다. 추가 정책 설정이 필요할 수 있습니다.
- 성능 오버헤드 — bind mount 가 많을수록 마운트 네임스페이스 설정 시간이 증가합니다 (수십 ms 수준).
- 하드웨어 접근 제한 — GPU, USB 등 특수 장치는 추가 설정 없이 접근 불가.
보안 고려사항
Bubblewrap 은 공격 면적 (attack surface) 을 줄이는 도구일 뿐, 완벽한 격리를 보장하지 않습니다. 커널 취약점 (CVE-2022-0185 등 user namespace 관련 버그) 이 발견될 경우 우회 가능성이 있습니다.
중요한 워크로드에서는 SELinux/AppArmor 와의 다중 방어, 커널 최신 유지, 최소 권한 원칙을 준수하세요.
문제 해결 (Troubleshooting)
자주 발생하는 오류
1. "unshare: Operation not permitted"
$ bwrap --unshare-pid -- /bin/bash
unshare: Operation not permitted
원인: 커널이 unprivileged user namespace 를 비활성화했습니다.
# 확인
$ cat /proc/sys/kernel/unprivileged_userns_clone
0 # 0 이면 비활성화
# 임시 활성화 (재부팅 시 초기화)
$ sudo sysctl -w kernel.unprivileged_userns_clone=1
# 영구 활성화
$ echo "kernel.unprivileged_userns_clone = 1" | sudo tee /etc/sysctl.d/99-userns.conf
Ubuntu 는 보안 이유로 기본값이 0 입니다. AppArmor 프로파일이 bwrap 을 차단할 수도 있습니다:
$ sudo aa-complain /usr/bin/bwrap
2. "Failed to mount: Permission denied"
$ bwrap --ro-bind /data /data -- /bin/bash
Failed to mount /data: Permission denied
원인: SELinux/AppArmor 가 마운트를 차단하거나, 소스 디렉터리 접근 권한이 없습니다.
# SELinux 감사 로그 확인
$ sudo ausearch -m avc -ts recent | grep bwrap
# AppArmor 로그 확인
$ sudo grep bwrap /var/log/kern.log
# 소스 디렉터리 권한 확인
$ ls -ld /data
drwx------ 2 root root 4096 May 10 10:00 /data
# 해결: 권한 완화 또는 SELinux 컨텍스트 설정
$ chmod o+rx /data
$ chcon -R -t public_content_t /data # SELinux
3. "bwrap: exec failed: No such file or directory"
$ bwrap --ro-bind / / -- /usr/bin/myapp
bwrap: exec failed: No such file or directory
원인: 샌드박스 내부에서 실행 파일이나 공유 라이브러리를 찾을 수 없습니다.
# 동적 링커 확인
$ readelf -l /usr/bin/myapp | grep interpreter
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
# 필요한 라이브러리 확인
$ ldd /usr/bin/myapp
linux-vdso.so.1
libc.so.6 => /lib64/libc.so.6
/lib64/ld-linux-x86-64.so.2
# 해결: 필요한 라이브러리 모두 바인드
$ bwrap \
--ro-bind /usr /usr \
--ro-bind /lib /lib \
--ro-bind /lib64 /lib64 \
-- /usr/bin/myapp
4. "No space left on device" (tmpfs)
$ bwrap --tmpfs /tmp -- /bin/bash
$ dd if=/dev/zero of=/tmp/test bs=1M count=100
dd: error writing '/tmp/test': No space left on device
원인: tmpfs 는 기본값으로 메모리의 50% 를 사용합니다. 대용량 작업 시 부족할 수 있습니다.
# 현재 tmpfs 크기 확인
$ df -h /tmp
Filesystem Size Used Avail Use% Mounted on
tmpfs 3.9G 0 3.9G 0% /tmp
# 해결: 명시적 크기 지정 (bwrap 은 직접 옵션 없음, mount 로 우회)
# Flatpak 등 상위 런타임에서 tmpfs 크기 설정 사용
디버깅 워크플로
# 1 단계: 최소 옵션으로 시작
$ bwrap --ro-bind / / --dev /dev --proc /proc -- /bin/sh
# 2 단계: 점진적 옵션 추가
$ bwrap --ro-bind / / --dev /dev --proc /proc --tmpfs /tmp -- /bin/sh
$ bwrap --ro-bind / / --dev /dev --proc /proc --tmpfs /tmp --unshare-net -- /bin/sh
$ bwrap --ro-bind / / --dev /dev --proc /proc --tmpfs /tmp --unshare-net --cap-drop-all -- /bin/sh
# 3 단계: 실패 지점 확인
$ strace -f -e trace=mount,open,execve bwrap ... 2>1 | grep -E "(ENOENT|EACCES|EPERM)"
# 4 단계: 커널 로그 확인
$ dmesg -w | grep -i "bwrap\|seccomp\|apparmor"
성능 벤치마크
시작 시간 오버헤드
Bubblewrap 의 시작 시간 오버헤드를 측정했습니다:
| 구성 | 평균 시작 시간 | 메모리 사용량 | 비고 |
|---|---|---|---|
| 네이티브 (bwrap 없음) | 0.5ms | 기준 | /bin/sh -c 'true' |
| bwrap 최소 (ro-bind /) | 2.3ms | +120KB | user namespace 만 생성 |
| bwrap 기본 (5 bind mount) | 4.1ms | +180KB | /usr, /lib, /tmp, /proc, /dev |
| bwrap 풀 (15 bind mount + seccomp) | 8.7ms | +320KB | Flatpak 수준 격리 |
| Docker 컨테이너 | 150-500ms | +5-20MB | 이미지 풀링 제외 |
- CPU: Intel i7-12700K
- RAM: 32GB DDR4
- Storage: NVMe SSD
- Kernel: 6.8.0
- 측정 도구:
/usr/bin/time -v,perf stat
I/O 성능 비교
# 순차 읽기 테스트 (dd)
$ bwrap --ro-bind / / --tmpfs /tmp -- /bin/sh -c 'dd if=/dev/zero of=/tmp/test bs=1M count=1024'
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 0.823 s, 1.3 GB/s
# 네이티브 비교
$ dd if=/dev/zero of=/tmp/test bs=1M count=1024
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 0.815 s, 1.3 GB/s
bwrap 의 I/O 오버헤드는 1% 미만으로 무시할 수 있습니다. mount namespace 격리는 파일시스템 성능에 영향을 주지 않습니다.
메모리 오버헤드
# bwrap 프로세스 메모리 사용량
$ ps -o pid,rss,vsz,comm -C bwrap
PID RSS VSZ COMMAND
12345 2048 3456 bwrap
# 네임스페이스당 커널 메모리 (대략적)
- User namespace: ~8KB
- PID namespace: ~4KB per process
- Mount namespace: ~2KB per mount point
- Net namespace: ~32KB (socket buffers)
프로덕션 배포 체크리스트
보안 체크리스트
다음 항목을 모두 확인한 후 프로덕션에 배포하세요:
| 항목 | 확인 방법 | 필수 여부 |
|---|---|---|
| 최소 마운트 원칙 준수 | --ro-bind만 사용, --bind 최소화 |
필수 |
| Capability 드롭 | --cap-drop-all 또는 명시적 드롭 |
필수 |
| 네트워크 격리 | --unshare-net (불필요 시) |
권장 |
| Seccomp 필터 | 기본 필터 또는 사용자 정의 | 필수 |
| /tmp 격리 | --tmpfs /tmp |
필수 |
| 환경 변수 정리 | --clearenv + 최소 --setenv |
권장 |
| 커널 버전 | CVE 패치 적용 확인 (5.16.3+) | 필수 |
| SELinux/AppArmor | 추가 LSM 정책 적용 | 권장 |
--die-with-parent |
부모 종료 시 자동 정리 | 권장 |
| 로그 감사 | dmesg, /var/log/audit/ |
권장 |
프로덕션 배포 예시
#!/bin/sh
# /usr/local/bin/run-sandboxed.sh
APP_NAME="myapp"
APP_BIN="/opt/${APP_NAME}/bin/${APP_NAME}"
APP_HOME="/opt/${APP_NAME}"
DATA_DIR="/var/lib/${APP_NAME}"
LOG_DIR="/var/log/${APP_NAME}"
exec bwrap \
--unshare-all \
--share-net \
--ro-bind /usr /usr \
--ro-bind /lib /lib \
--ro-bind /lib64 /lib64 \
--ro-bind /etc/ssl /etc/ssl \
--ro-bind /etc/resolv.conf /etc/resolv.conf \
--bind "${APP_HOME}" "${APP_HOME}" \
--bind "${DATA_DIR}" "${DATA_DIR}" \
--bind "${LOG_DIR}" "${LOG_DIR}" \
--tmpfs /tmp \
--tmpfs /var/tmp \
--proc /proc \
--dev /dev/null \
--dev /dev/zero \
--dev /dev/random \
--dev /dev/urandom \
--dev /dev/tty \
--clearenv \
--setenv PATH /usr/bin \
--setenv HOME "${DATA_DIR}" \
--setenv APP_HOME "${APP_HOME}" \
--setenv LOG_DIR "${LOG_DIR}" \
--cap-drop-all \
--cap-add CAP_NET_BIND_SERVICE \
--die-with-parent \
--chdir "${APP_HOME}" \
-- "${APP_BIN}" "\$@"
위 스크립트를 systemd 서비스로 등록하면 자동 시작과 로그 관리가 가능합니다:
[Unit]
Description=MyApp Sandboxed Service
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/run-sandboxed.sh
Restart=on-failure
User=myapp
Group=myapp
# 추가 systemd 샌드박싱 (이중 보호)
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
NoNewPrivileges=yes
[Install]
WantedBy=multi-user.target
참고자료
- Bubblewrap GitHub Repository
- XDG Desktop Portal Documentation
- 네임스페이스 (Namespaces) — PID, mount, user, network 네임스페이스 상세
- Linux Containers & Docker — 컨테이너 런타임 비교
- LSM / Seccomp — Seccomp-BPF 필터 상세
- Kernel Seccomp Filter Documentation
- user_namespaces(7) — Linux man page
- CVE-2022-0185 — Linux Kernel Use-After-Free in fs_context
- 네임스페이스 — bwrap 의 기반 기술 상세
- Linux Containers — Docker/runc 와의 비교
- LSM / Seccomp — 시스템콜 필터링 심층
- 커널 하드닝 — 샌드박스 보안 강화