ARTICLE · 1091995
AI 时代软件沙箱入门:从原理到实践的安全指南
软件沙箱(Sandboxing)是限制程序权限的核心安全技术。本文深入解析沙箱的本质——自主权限丢弃,以及如何在 Linux 和 FreeBSD 上实现真正安全的进程隔离与沙箱化。
什么是软件沙箱?
深入软件沙箱领域,就像驶入一片几乎未被探索的海域。实现良好沙箱所需的各种技术碎片散落各处,先驱者们尚未将这些知识整合成一份统一的"世界地图"来引导后来者。
我们先从一个非正式但有用的定义开始。以下是 Julien Tinnes 和 Chris Evans 在 2009 年马来西亚 Hack In The Box 大会上给出的定义:
沙箱 = 限制进程权限的能力:
以编程方式实现 无需机器上的管理员权限 自行权限放弃

三个关键特征详解
1. 编程式自行权限放弃
操作系统向用户和软件开发者提供不同的接口。系统管理员传统上依赖文件系统权限来隔离服务。但如果允许第三方程序随意更改这些权限,就会使管理员控制执行的策略失效。
第三方程序抽象出自己的权限规则,而 UNIX 文件系统权限通常不适合这些规则所需的安全策略。你会用 UNIX 权限模式来定义谁能看到你的 Twitter 动态吗?文件系统的逻辑,需要每个人来申请查看权限,你不可能用这种方式。
对于通过编程式自动处理这些权限,传统 UNIX 接口力不从心。真正重视这个问题的操作系统会提供超越传统 UNIX 的扩展接口,比如 FreeBSD 的 Capsicum 和 Linux 的 Seccomp。
2. 无需 Root 权限
当权限管理的沙箱接口不可用时,程序员们曾通过用超级用户可用的机制来创建沙箱。最典型的技术是使用 suid 辅助二进制文件来配置 chroot 权限栅栏。
这些方法的问题在于它们并非对所有程序可用。允许任何程序安装 suid 二进制文件会破坏所有安全措施。权限应该只减不增(最小权限原则)。
另一个相关的问题是,不要设计会指数级增加内核攻击面的 API。Docker 的流行使 Linux 命名空间成为廉价隔离服务的机制。但在嵌套的用户命名空间内,进程以超级用户身份运行,原本仅对超级用户可用的内核代码路径现在对每个用户都开放了。
正如 Andy Lutomirski 所说:
"我认为能够使用 CLONE_NEWUSER 获取对任何网络命名空间的 CAP_NET_ADMIN 从而访问网络配置 API 是一个巨大的风险。例如,非特权用户可以编程 iptables。如果里面没有权限提升漏洞,我把帽子吃了。"
Linux 命名空间是软件沙箱的糟糕接口。较新的沙箱接口如 Landlock 经过精心设计,不会像用户命名空间那样指数级增加内核攻击面。
3. 自主权限丢弃
沙箱也可以定义为:一个受限的、受控的执行环境,防止潜在恶意软件访问除授权资源外的任何系统资源。
自主权限丢弃不能替代系统管理策略,而是与之互补,应同时存在。
实践沙箱化:进程是关键
在当今每个主流操作系统中,权限边界都在进程级别。凭证与每个进程关联,内核据此决定进程是否可以通过环境权威获取新资源。
一旦我们将程序分割为独立进程,就可以进入下一步:
为每个隔间(进程)分配不同的权限 处理隔间之间的通信
FreeBSD Capsicum 的研究者们早就有了正确的思维模型:
"隔间化应用开发本质上是分布式应用开发,软件组件运行在不同进程中,通过消息传递进行通信。"
Actor 模型与基于能力的安全性
Actor 模型是分布式系统开发最著名的模式之一。如果我们将其总结为具体的设计选择:
有一个函数可以创建 actor,返回新 actor 的地址 actor 的地址可用于发送消息 actor 的地址也可以作为消息的一部分 有一个函数可以接收消息 actor 之间不共享内存
在 Emilua 中,这个设计转化为 3 个函数:
spawn_vm(module) -> actoractor.send(msg)inbox.receive() -> msg
如果我们决定使用 actor 模型进行沙箱化,那么每个进程就是一个 actor。UNIX 域套接字可用于 actor 消息传递。

文件描述符作为能力
UNIX 系统通常只在创建新文件描述符时运行权限检查,而不在使用现有文件描述符时检查。这种行为与能力模型兼容。
这意味着:如果你能证明没有将文件描述符泄露给错误的 actor,就可以安全地使用 actor 模型。
重要例外:ioctl。 对来自不受信任进程的文件描述符执行 ioctl 总是危险的。即使是 isatty() 在 Linux 上也是通过 ioctl 实现的。
FreeBSD 的 Capsicum:沙箱的黄金标准
Capsicum 是 FreeBSD 9.0 开始提供的接口,更好地支持将文件描述符的能力。核心函数 cap_enter 一个调用就能丢弃所有权限:
local new_actor3 = spawn_vm{ module = 'module3', subprocess = { init = 'C.cap_enter()' } }一个函数调用,权限就丢弃了。所有系统访问都必须通过已打开的文件描述符进行。
当 Capsicum 研究发表时,以下对比表格令人印象深刻:
Capsicum 只需要 100 行代码,而 seccomp 需要 11,301 行。
Linux 的 Seccomp:复杂但必要
Seccomp 不是自主权限丢弃的好机制,但它是 OS 加固的好机制。它是一个基于 BPF 程序的简单可编程系统调用过滤机制。
Seccomp 的问题在于:
Linux 允许多架构系统,系统调用编号在不同架构间差异很大 即使你在 x86-64 上阻止了某个系统调用,被入侵的沙箱也可以通过运行 x86 可执行文件来绕过 系统调用参数顺序在不同架构间也会变化
使用 Kafel 简化 Seccomp
Kafel 是用于指定系统调用过滤策略的语言和库。策略被编译成可用于 seccomp-filter 的 BPF 代码。
Kafel 允许你定义策略组,如 BasicIo、Filesystem、NetworkIo、Process 等,使策略管理更加模块化。
沙箱化现有代码
每个 shell 都应该有沙箱。Shell 是定义人在某个虚拟空间可以操作的内容。浏览器是 www 世界的 shell,文件服务器是使用文件的 shell。
哪些软件需要沙箱?
Web 浏览器(Firefox、Chrome) 即时通讯软件(Telegram 等) 媒体解析器
一个实用的方法是"无感沙箱化"(oblivious sandboxing):通过 LD_PRELOAD 注入动态库来拦截环境权威调用。Super Capsicumizer 9000 项目就采用了这种方法。

沙箱化原生插件
当假设代码从一开始就不可信时,我们需要在安全环境中构建插件,然后通过文件描述符加载(fdlopen 或 /proc/self/fd/),而不是通过路径。
安全模型的关键问题是:评估你的安全模型是否真的有意义。有时,由可信个人编写的可审计代码,比由不可信的人编写的"更好"代码更重要。
总结
软件沙箱的核心原则:
- 进程是权限边界
—— 将程序分割为独立进程 - 使用 Actor 模型通信
—— 通过 UNIX 域套接字传递消息和文件描述符 - 文件描述符即能力
—— 但要小心 ioctl - 优先使用 Capsicum
(FreeBSD)或 Seccomp + Landlock(Linux) - 对现有代码使用 LD_PRELOAD 拦截
—— 实现无感沙箱化
沙箱化曾经非常昂贵,但现在不应该只有 Firefox 和 Chrome 这样的巨头才能用得起。尤其在 AI 时代每个处理不可信数据的程序都应该采用沙箱技术。
原文链接:Software sandboxing: The basics
觉得这篇文章有价值?点个赞和在看,让更多开发者看到!
欢迎在评论区分享你的想法。
欢迎转发给身边的朋友。
#软件沙箱 #Sandboxing #Linux安全 #FreeBSD #Capsicum #Seccomp #系统安全 #进程隔离 #网络安全 #程序员必读