第203题:Sandbox 隔离哪些资源?
1. 核心回答
对于执行不可信代码的 Sandbox,我会至少隔离六类资源:
- 文件系统
- 进程
- 网络
- 身份与凭据
- CPU、内存、进程数和执行时间
- 系统调用与内核攻击面
实际工程中还会进一步限制设备、IPC、磁盘写入和输出大小。
整体原则可以概括为:
默认拒绝 → 最小暴露 → 有限资源 → 有限生命周期 → 可审计销毁
例如一个用于 Agent 执行 Python 代码的沙箱,理想状态应该是:
只看到当前任务文件
不能访问宿主文件
默认不能访问公网
没有生产凭据
不能观察其他任务进程
只能调用必要系统调用
CPU / 内存 / 时间都有上限
任务结束后实例销毁
2. 文件系统隔离
文件系统隔离解决的问题是:
沙箱中的代码能够读写哪些文件。
Linux 可以通过 Mount Namespace 为不同执行环境提供不同的挂载视图。
一个不可信代码沙箱通常不会直接暴露:
/etc/home/root- SSH Key
- 云服务配置文件
- Docker Socket
- 其他用户数据
- 其他任务 Workspace
更合理的设计是:
只读基础镜像
当前任务需要的只读输入
独立临时可写目录
例如:
/app -> read-only /input -> read-only /work -> writable tmpfs /output -> controlled writable directory任务结束后删除/work。
真正需要返回给用户的文件,通过受控的/output导出。
这样可以避免程序:
- 修改宿主文件;
- 读取其他用户数据;
- 在系统目录中持久化;
- 污染后续任务。
2.1 Host Bind Mount 要特别谨慎
如果直接:
-v /:/host或者把敏感宿主目录以可写形式挂载进去,那么文件系统隔离基本失效。
因此需要遵循:
No Host Mount by Default
确有需要时:
最小目录 + read-only
3. 进程隔离
第二类是 Process Isolation。
Linux 的 PID Namespace 可以让沙箱拥有独立的 PID 空间。
沙箱内部可能看到:
PID 1 PID 2 PID 3但无法正常观察宿主上的:
database ssh other-agent other-user-job这样可以防止程序直接:
- 枚举宿主进程;
- 给宿主进程发送 Signal;
- 干扰其他任务;
- 读取其他进程状态。
同时还应该限制能够创建多少进程。
例如:
pids.max = 128否则攻击者可以执行 Fork Bomb:
process ├── process │ ├── process │ └── process └── process ├── process └── process持续创建进程直至宿主资源耗尽。
因此:
PID Namespace 负责可见性隔离,PIDs Limit 负责资源耗尽控制。
4. 网络隔离
第三类是 Network Isolation。
Network Namespace 可以为 Sandbox 创建独立:
- 网络设备;
- IP Stack;
- Routing Table;
- Port;
- Socket Namespace。
对于执行用户提交代码的环境,我通常采用:
Default Deny Network也就是默认无公网访问。
原因包括:
- 防止下载恶意 Payload;
- 防止建立反向 Shell;
- 防止扫描内网;
- 防止访问 Metadata Service;
- 防止数据外泄;
- 防止攻击内部服务。
如果任务必须联网,例如:
pip install可以改成受控访问:
Sandbox ↓ Egress Proxy ↓ Domain Allowlist ↓ Approved Repository例如只允许:
pypi.org files.pythonhosted.org而不能访问:
任意公网地址 内网数据库 169.254.169.254 其他租户服务因此网络策略可以按照:
无网络
↓
域名 Allowlist
↓
受控 Egress Proxy
逐级开放。
5. 身份和凭据隔离
这一层经常被遗漏。
即使文件、网络和进程都做了隔离,如果 Sandbox 获得了高权限 Credential,风险仍然很高。
需要隔离:
- API Key;
- Database Password;
- Cloud Credential;
- SSH Key;
- OAuth Token;
- Kubernetes ServiceAccount;
- Docker Socket;
- Internal Service Token。
对于普通代码执行任务:
Production Credential = 0是最安全的默认值。
如果确实需要调用外部服务,应提供:
Short-lived + Scoped + Task-specific的临时凭据。
例如任务只需要:
object_storage.read就不要授予:
object_storage.admin6. 用户身份和 Linux Capability
Linux User Namespace 可以把沙箱内部的 UID/GID 映射到宿主上的低权限用户。
例如沙箱中:
uid = 0在宿主机上可以映射为:
uid = 100000+这样即使程序在 Sandbox 内表现为 root,它在宿主上仍然缺少真正的 root 权限。
同时需要删除不必要的 Linux Capabilities。
例如通常不应该授予:
CAP_SYS_ADMIN CAP_SYS_MODULE CAP_SYS_PTRACE CAP_NET_ADMIN CAP_SYS_BOOT安全设计通常遵循:
Drop All Capabilities ↓ Add Only Required Capabilities对于普通 Python、JavaScript 或代码编译任务,往往根本不需要额外 Capability。
7. CPU、内存和进程资源隔离
Sandbox 还要防止 Denial of Service。
例如用户提交:
whileTrue:pass会持续消耗 CPU。
或者:
x=[]whileTrue:x.append("A"*100000000)会持续占用内存。
因此应该通过 cgroups 等资源控制机制限制:
CPU Memory PIDs I/O例如:
CPU = 1 Core Memory = 512 MB PIDs = 128Docker 官方文档明确指出,容器在没有配置资源限制时,默认可以使用宿主调度器允许的大量 CPU 和内存。
因此“运行在容器里”本身不能解决资源耗尽问题。
8. 执行时间也必须限制
除了 CPU 和内存,还应该有 Wall-clock Timeout。
例如:
timeout = 30s如果任务超过:
30 秒执行器直接:
SIGTERM ↓ grace period ↓ SIGKILL这样能够处理:
- 死循环;
- 无限等待;
- 网络阻塞;
- Deadlock;
- 恶意 Sleep;
- 算法复杂度异常。
所以资源预算可以理解为:
Compute Budget = CPU + Memory + PIDs + Disk + Execution Time + Output Size每一项都应该存在明确上限。
9. 系统调用隔离
即使文件、网络等已经隔离,进程仍然通过 System Call 与 Linux Kernel 交互。
因此还需要限制:
syscall surfaceLinux seccomp 可以通过过滤规则限制允许执行的系统调用。
例如可以禁止或限制高风险操作:
mount reboot setns unshare ptrace swaponDocker 默认也使用 seccomp profile,限制一部分系统调用。
需要注意:
seccomp 提供系统调用过滤能力,它只是完整 Sandbox 的一个组件。
Linux Kernel 官方文档也明确指出,seccomp filtering 用于减少暴露给应用程序的 Kernel Surface,完整的安全边界还需要其他硬化机制配合。
10. AppArmor / SELinux 进一步限制行为
System Call 本身粒度仍然有限。
例如应用可能合法调用:
open()但我们希望:
允许 open("/work/a.txt") 禁止 open("/etc/shadow")此时可以使用:
- AppArmor;
- SELinux;
等 Mandatory Access Control 机制。
Docker 默认环境可以使用docker-defaultAppArmor Profile。
AppArmor 可以进一步限制:
- 文件访问;
- 网络行为;
- ptrace;
- 可执行程序;
- Capability。
形成:
Namespaces + cgroups + Capabilities + seccomp + AppArmor / SELinux多层隔离。
11. 设备也应该控制
Sandbox 一般不应该直接获得宿主设备访问权限。
例如:
/dev GPU USB Block Device KVM需要按照任务最小化暴露。
尤其应该避免随意授予:
--privileged因为 Privileged Container 会显著扩大 Capability 和设备访问范围。
普通代码执行任务通常只需要极少数基础设备,例如:
/dev/null /dev/zero /dev/randomGPU 也应作为单独授权资源管理。
12. 为什么单独使用容器还需要继续加固
Container 提供非常有价值的基础隔离,包括:
- Namespace;
- cgroup;
- Capability;
- seccomp;
- LSM。
安全强度仍然取决于配置和宿主 Kernel。
如果出现:
- Kernel Vulnerability;
- Container Runtime Vulnerability;
- Dangerous Capability;
- Host Mount;
- Docker Socket Exposure;
- Privileged Container;
隔离边界可能被突破。
因此对于:
可信内部服务普通容器加固可能已经满足需求。
对于:
用户任意代码 恶意样本 不可信 Agent 工具调用 CTF / Malware 多租户 Code Execution我会进一步考虑更强的隔离层,例如:
- gVisor;
- Firecracker microVM;
- 独立虚拟机;
- 专用执行节点。
gVisor 会在应用和宿主 Kernel 之间增加独立 Application Kernel。
Firecracker 则基于 KVM 创建 microVM,为每个工作负载提供独立 Guest Kernel 边界。
13. Sandbox 的生命周期也要隔离
安全沙箱最好采用:
One Task → One Fresh Sandbox → Execute → Collect Artifact → Destroy不要让多个不可信任务长期共享同一个可写环境。
否则前一个任务可能留下:
- 恶意文件;
- 后门;
- 环境变量;
- 修改后的依赖;
- Cache Poisoning;
- 持久进程。
任务结束后应销毁:
Filesystem Process Network Namespace Temporary Credential Memory State下一任务重新创建干净环境。
14. 怎么验证 Sandbox 真的隔离成功
不能只执行:
print("hello world")然后认为沙箱安全。
我会主动测试逃逸路径。
14.1 文件测试
尝试:
读取 /etc/shadow 读取宿主 home 修改宿主文件 访问其他任务目录预期:
Denied14.2 网络测试
尝试:
访问公网 扫描内网 访问 Metadata Service 建立 Reverse Shell预期:
Denied14.3 进程测试
尝试:
查看 Host PID ptrace 其他进程 Fork Bomb预期:
不可见 / 被拒绝 / 被 PIDs Limit 终止14.4 资源测试
测试:
无限 CPU 无限内存 无限进程 无限输出 无限执行时间验证:
CPU throttle OOM termination PIDs limit Output limit Timeout均能正确触发。
14.5 权限测试
尝试:
获得 root 加载 Kernel Module mount Host Filesystem 访问 Docker Socket 使用 Host Network全部应该受到策略限制。
15. 一个比较完整的安全架构
对于 Agent Code Execution,我会设计成:
Agent ↓ Sandbox Controller ↓ Policy Check ↓ Fresh Isolated Runtime ├── Read-only RootFS ├── Task-only Workspace ├── PID Namespace ├── Network Namespace ├── User Namespace ├── Dropped Capabilities ├── seccomp ├── AppArmor / SELinux ├── CPU / Memory / PIDs Limit ├── Timeout └── No Production Credentials ↓ Execute ↓ Collect Approved Artifacts ↓ Destroy Sandbox如果代码风险等级较高:
Native Container ↓ gVisor / microVM ↓ Dedicated Worker逐步提高隔离强度。
16. 面试时可以压缩成下面这段
Sandbox 至少要隔离六类资源:文件、进程、网络、凭据、计算资源和系统调用。
文件侧通过独立 Mount Namespace、只读 RootFS 和任务级临时目录,避免看到宿主和其他任务的数据;进程侧用 PID Namespace 和 PIDs Limit;网络默认关闭,需要联网时通过 Allowlist 或 Egress Proxy;身份侧使用非特权用户、User Namespace、最小 Capability,并且不向任务暴露生产 Credential。
资源侧通过 cgroups 限制 CPU、内存、进程数和 I/O,同时设置执行 Timeout,防止死循环、Fork Bomb 和资源耗尽。内核交互侧使用 seccomp 限制 System Call,再配合 AppArmor 或 SELinux 缩小文件、网络和 Capability 权限。
容器可以提供这些机制的基础实现。对于任意用户代码、恶意样本或多租户 Agent 执行环境,我还会考虑 gVisor、Firecracker microVM 或独立执行节点增加隔离强度。最后通过文件越权、网络外连、Fork Bomb、资源耗尽、权限提升和 Sandbox Escape 等攻击测试验证边界是否真正生效。
核心原则是:
只给任务完成所必需的文件、网络、权限、资源和生命周期。
17. 来源
- Linux man-pages,namespaces(7):说明 Linux Namespace 用于隔离全局系统资源,包括 Mount、PID、Network、User 等 Namespace。
- Linux man-pages,mount_namespaces(7)、pid_namespaces(7)、network_namespaces(7)、user_namespaces(7):分别说明文件挂载视图、进程 ID、网络栈以及 UID/GID 和 Capability 的隔离机制。
- Linux Kernel Documentation,Control Group v2:说明 cgroup 的资源分配和限制机制。
- Linux Kernel Documentation,Seccomp BPF:说明 seccomp 用于过滤 System Call、缩小暴露给应用程序的 Kernel Surface,并明确其属于构建 Sandbox 的组件。
- Docker Documentation,Docker Engine Security:说明 Docker 使用 Namespace、cgroup、Linux Capabilities 和其他 Kernel Security Features 建立容器隔离。
- Docker Documentation,Resource Constraints:说明容器默认没有 CPU/Memory 资源上限,需要显式配置资源限制。
- Docker Documentation,Seccomp Security Profiles:说明 Docker 默认 seccomp profile 会限制部分系统调用。
- Docker Documentation,AppArmor Security Profiles:说明
docker-defaultAppArmor Profile 及其访问控制作用。 - gVisor Documentation,What is gVisor?:说明 gVisor 通过独立 Application Kernel 减少应用直接接触 Host Kernel 的攻击面。
- AWS / Firecracker Documentation:说明 Firecracker 使用 KVM microVM 为多租户工作负载提供硬件虚拟化边界。