在多租户或不信任代理(untrusted agent)运行时,如 AI 编码代理或用户上传代码执行,Linux 沙箱是高效的第一道防线。通过结合 namespaces 实现进程视图隔离、cgroups v2 施加资源限额、seccomp-BPF 严格过滤系统调用,可以显著降低内核暴露面,同时保持低开销。相较单纯 Docker,这种原生组合更轻量,适用于高 QPS 场景。本文聚焦单一技术栈的实战落地,给出参数阈值、配置清单与集成步骤,避免内核漏洞横向扩散。
Namespaces:进程与资源视图隔离
Namespaces 是沙箱的基础,提供 “私有视野” 而非硬件边界。核心使用 user、pid、mount、net、uts、ipc namespaces 组合,确保每个 job 独立。
- User namespace:将沙箱内 uid 0 映射到宿主机非特权用户(如 uid 1000 + 动态分配),防止提权。创建后立即 drop ambient capabilities,禁止额外 uid 映射范围。
- PID namespace:沙箱内进程树以独立 PID 1 启动,外界不可见。结合 reaper 进程回收僵尸。
- Mount namespace:私有文件系统,使用 pivot_root 切换到预置最小 rootfs(包含 /bin/lib 等),remount 为 nosuid,nodev,noexec;/tmp、/run 用 tmpfs(大小限 1-10MB)。
- Network namespace:禁用网络或 veth pair+iptables/eBPF 限出站;不信任代码默认无 net。
- UTS/IPC:独立 hostname 与 IPC 空间,防信号 / 共享内存泄漏。
实施参数:
- clone/unshare flags: CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWUTS | CLONE_NEWIPC
- User map: /proc/PID/uid_map 写 "0 100000 65536"(沙箱 0-> 宿主 100000,范围 65k)
- Mount: pivot_root (new_root, old_root),后 umount -l old_root
如 Shayon Mukherjee 所述,namespaces 仅为 “visibility walls”,内核 bug 仍可绕过,故需搭配其他层。
Cgroups v2:精细资源限额与 DoS 防护
Cgroups v2 统一 hierarchy,便于多租户分层:顶级 /tenants/tenant1/jobs/job-uuid。
针对 CPU / 内存饥饿、fork bomb,配置如下阈值(per-job,调整依负载):
| 资源 | 文件路径 | 示例值 | 说明 |
|---|---|---|---|
| Memory | memory.max | 256M | 硬限,超阈 OOM-kill cgroup |
| Memory | memory.high | 192M | 软限,压力前 throttle |
| CPU | cpu.max | "50000 100000" | 50ms/100ms 周期限 50% |
| CPU | cpu.weight | 100 | 租户相对权重 |
| PIDs | pids.max | 128 | 防 fork bomb |
| IO | io.max | "8:0 r 10M w 5M" | 设备读写 Bps 限 |
| Net | net_cls.classid | 0x10001 | TC 分类整形(可选) |
操作清单:
- mkdir /sys/fs/cgroup/sandbox/jobs/job-uuid
- echo 256M > memory.max; echo 192M > memory.high
- echo "50000 100000" > cpu.max
- echo $$ > cgroup.procs(attach 进程)
- 监控:cgroup.events (oom, max)触发清理
多租户下,预创 /tenants/tenantX,动态子 cgroup。结合 rlimits(如 RLIMIT_NPROC=100)双保险。
Seccomp-BPF:系统调用 Allowlist 防火墙
Seccomp 是纳秒级 syscall 过滤器,从 SCMP_ACT_KILL 默认拒,逐加必要调用。针对 Python/Node 等,测试 workload 记录 strace,精简至 50-100 syscall。
最小核心 allowlist(C-like 二进制):
- read, write, close, mmap, munmap, brk, exit, exit_group, rt_sigaction, rt_sigreturn, clock_gettime, futex
- openat/at_fstat(限 dirfd & O_RDONLY)
高级:arg 过滤,如 openat flags & O_WRONLY==0; deny clone3 (CLONE_NEW*)=nested ns。
libseccomp 示例(C):
#include <seccomp.h>
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
// ... 30+ calls
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(ptrace), 0); // 示例deny
seccomp_load(ctx);
execl("/usr/bin/python3", "python3", script, NULL);
加载时机:namespaces+priv drop 后、exec 前。per-runtime profile(python.json 等),audit 日志 /sys/kernel/debug/tracing/seccomp。
Deny 高危:ptrace, kexec_load, init_module, bpf, io_uring_setup, mount(post-setup)。
集成实施:Job 生命周期清单
小主管进程(Go/C/Rust)处理:
- 接收 job(code, lang, timeout=30s)
- 动态 uid/cgroup/uuid 分配
- cgroup 创建 & 限额写
- fork child
- Child: unshare namespaces → uid_map/setgroups → pivot_root/tmpfs → cap drop/PR_SET_NO_NEW_PRIVS → seccomp_load → exec
- Parent: wait cgroup 事件 /timeout,kill -9,rm cgroup/mounts
伪代码(bash 简化,prod 用 native):
#!/bin/bash
CG=/sys/fs/cgroup/sandbox/job$$
mkdir $CG; echo 256M > $CG/memory.max; ...
unshare -U -p -m -n -u -i --map-root-user -- bash -c "
mount --bind /minimal/root /; pivot_root / /proc/1/fd/0; # setup fs
echo 0 > /proc/self/sessionid; # no new privs
# seccomp via bpftool or ld.so preload
exec /bin/python3 $1"
echo $! > $CG/cgroup.procs
wait; rmdir $CG
监控要点:
- cgroupfs 通知:inotify on cgroup.events
- Seccomp: tracepoint seccomp:ret
- Prometheus: cgroup stats (memory.current, cpu.stat)
- 回滚:job fail>5%? 紧缩 memory.high 20%,或 fallback gVisor
权衡、风险与增强
开销:namespaces<1ms,cgroups/seccomp0 开销。1000QPS 可,vs Docker10x 慢。
风险:
- Allowed syscall vuln(如 CVE in read),限 seccomp bypass 罕见。
- Config 漏:writable host mount 或宽松 uid map。
增强:gVisor(user-kernel,syscall~60),内仍用以上;或 Firecracker microVM(VM 边界,冷启动 <250ms snapshot)。“namespaces 仅 visibility walls,非安全边界”。
生产如 nsjail/GitHub Awesome-sandbox 验证此栈。
资料来源: [1] https://www.shayon.dev/post/2026/52/lets-discuss-sandbox-isolation/ “namespaces are visibility walls” [2] https://github.com/google/nsjail
(正文字数:约 1250)