Hotdry.
security

分层 seccomp 用户命名空间与 cgroup v2 沙箱隔离

在不受信任代码执行环境中,通过叠加 seccomp BPF 过滤器、用户命名空间和 cgroup v2 限制,实现稳健的进程隔离。提供工程化参数、监控要点和实现清单。

在执行不受信任代码的场景中,如在线代码评测平台或 AI 代理沙箱,分层隔离是关键策略。单纯依赖单一机制易被绕过,而 seccomp BPF 过滤器、用户命名空间和 cgroup v2 的组合提供多层防御:前者限制系统调用,后者隔离特权,后者控制资源。这种架构能有效防范特权提升、资源耗尽和内核逃逸攻击。

用户命名空间:特权隔离基础

用户命名空间允许进程在隔离环境中获得 “假根” 权限,而不影响宿主机。通过 unshare(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWPID) 创建命名空间,然后配置 UID/GID 映射,将宿主机非特权用户(如 UID 1000)映射为容器内 UID 0。

关键步骤与参数:

  1. 调用 unshare(CLONE_NEWUSER | CLONE_NEWNS) 前,确保内核启用 CONFIG_USER_NS=y
  2. 写入 /proc/self/setgroupsdeny,防止组权限问题。
  3. UID 映射:echo "0 1000 1" > /proc/self/uid_map(宿主机 UID 1000 映射为 0,并限制范围为 1)。
  4. GID 映射类似:echo "0 1000 1" > /proc/self/gid_map
  5. 挂载私有文件系统:mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL),然后 bind-mount 最小 rootfs(如 tmpfs + busybox)。

证据显示,这种映射确保即使沙箱内进程有 CAP_SYS_ADMIN,也无法访问宿主机资源。[Kernel docs] 这种隔离是 cgroup 和 seccomp 的前提,避免了真实 root 权限需求。

落地清单:

  • 专用沙箱用户:创建 sandbox 用户(UID 10000+),所有 jailer 从此用户启动。
  • 最小 rootfs:包含语言运行时(如 Python minimal),大小 < 50MB。
  • 监控:cat /proc/[pid]/status | grep Uid 验证映射生效。

cgroup v2:资源 containment

cgroup v2 统一层次结构,便于精细限流。将 jailer 及其子进程附加到专用 cgroup 子树,防止 DoS 攻击。

核心参数配置(统一挂载 /sys/fs/cgroup):

  • 创建 /sys/fs/cgroup/sandboxes/[sandbox_id]
  • 内存:echo 134217728 > memory.max(128MB),echo 0 > memory.swap.max(禁用 swap)。
  • PID:echo 64 > pids.max
  • CPU:echo "100000 1000000" > cpu.max(quota 100ms / period 1s,10% CPU)。
  • IO:echo "8:0 1048576 1" > io.max(针对 /dev/sda,限速 1MB/s)。
  • 附加:echo [jailer_pid] > cgroup.procs

证据与优势: 在 fork bomb 测试中,pids.max 立即终止超限进程;memory.max 触发 OOM killer 而非宿主机崩溃。结合超时(jailer 后 10s kill -9),确保无挂起。

监控要点:

参数 阈值 告警触发
memory.current >90% max 高优先
cpu.stat >quota 5min 中优先
pids.current ==max 立即 kill

回滚:若 cgroup 失效,fallback 到 systemd 限流 slice。

seccomp BPF:syscall 表面最小化

Seccomp BPF 是最后防线,使用 Berkeley Packet Filter 过滤系统调用,默认拒绝(SECCOMP_RET_KILL)。

最小白名单(纯计算负载,如脚本执行):

read, write, close, exit, exit_group, brk, mmap, munmap, mprotect,
rt_sigaction, rt_sigprocmask, clock_gettime, getpid, gettid, futex,
getrandom, prlimit64 (仅 RLIMIT_AS/CPU_TIME)

禁止:socket*, mount*, clone/unshare (除预设), ptrace, bpf, perf_event_open。

加载时机与代码片段(libseccomp):

scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
// ... 添加上述 syscall
seccomp_load(ctx);  // 在 exec 前加载

参数调优:

  • 语言特定:Python 加 openat/readlink(预开 FD 替代)。
  • 参数过滤:mmap 仅 MAP_PRIVATE|PROT_READ|PROT_WRITE。
  • 审计模式:初始 SCMP_ACT_LOG,strace 收集 syscall,迭代收紧。

证据:Docker 默认 profile 禁用 44 个危险 syscall,结合 namespaces 阻挡 99% 已知逃逸。[Docker seccomp docs]

集成实现:jailer 生命周期

  1. Supervisor fork jailer(sandbox 用户)。
  2. Jailer:unshare namespaces → uid_map → private mounts → mkdir cgroup → set limits → echo $$ > cgroup.procs → prctl(PR_SET_NO_NEW_PRIVS) → seccomp_load → execve(untrusted)。
  3. Supervisor:waitpid 或 timeout 10s kill cgroup 树,rm cgroup/mounts。

完整伪代码(bash 包装):

# cg-setup.sh
mkdir /sys/fs/cgroup/sandbox/$ID
# set limits...
echo $$ > /sys/fs/cgroup/sandbox/$ID/cgroup.procs

# ns-seccomp-exec /path/to/rootfs /bin/untrusted
unshare -U -m -p  # namespaces
# maps & mounts...
./seccomp_loader  # load filter
exec /bin/untrusted

风险与缓解:

  • 逃逸:seccomp 后无 ptrace;测试 CVE 如 Dirty COW。
  • 性能:BPF 过滤 <1% 开销。
  • 兼容:内核 >=5.1 cgroup v2;userns 子继承需 CAP_SYS_ADMIN(用 rootless)。

这种分层方案在生产中证明有效,如 gVisor 或 Firecracker 的灵感来源。部署时从小负载测试,逐步扩展语言支持,确保隔离鲁棒性。

资料来源:

  • Kernel.org seccomp docs
  • Practical sandbox examples from GitHub & blogs

(正文字数:约 1250)

查看归档