Software sandboxing: The basics (2025)
Software sandboxing: The basics (2025)
软件沙箱:基础知识(2025)
Diving into the territory of software sandboxing is diving into mostly uncharted territory. The necessary pieces to implement good sandboxing in your software are scattered all-around and the pioneers haven’t yet gathered enough knowledge into an unified mappa mundi that can guide new sailors through some well understood safe routes. In this blog post I’ll offer my own share of experiences that I have acquired while working on sandboxing support for Emilua. Writing style will suffer a little because I’ll err on the side of repeating myself too much to avoid any misunderstandings. Do keep in mind that some Lua code samples here require the unreleased Emilua 0.11 (just grab a recent commit from the repo’s development branch).
探索软件沙箱领域就像进入一片未知的海域。在软件中实现良好沙箱所需的必要组件散落在各处,先驱们尚未将足够的知识汇集成一张统一的“世界地图”,以引导新航海者走过那些公认的安全路线。在这篇博文中,我将分享我在为 Emilua 开发沙箱支持时积累的经验。写作风格可能会稍显啰嗦,因为我宁愿多重复几次,以避免任何误解。请记住,这里的一些 Lua 代码示例需要尚未发布的 Emilua 0.11 版本(只需从仓库的开发分支获取最新的提交即可)。
First, let’s get some informal (but useful) definition for sandboxing just to make sure we’re on the same page. Here’s the definition that was used by Julien Tinnes and Chris Evans at Hack In The Box Malaysia 2009: The ability to restrict a process’ privileges: Programmatically; Without administrative authority on the machine; Discretionary privilege dropping. That’s a very good definition to keep the ball rolling. Let’s quickly iterate over each point individually to make them crystal clear. However keep in mind that the opinions I possess today are a little different from the opinions J. Tinnes and C. Evans had during the 2009 talk (especially around “is it okay to use superuser APIs?”), so my explanations will differ a little and guide you towards what I consider better practices for 2025.
首先,让我们对沙箱给出一个非正式(但有用)的定义,以确保我们达成共识。以下是 Julien Tinnes 和 Chris Evans 在 2009 年马来西亚 Hack In The Box 大会上使用的定义:限制进程权限的能力:以编程方式;无需机器上的管理员权限;自主放弃权限。这是一个非常好的定义,可以作为我们讨论的起点。让我们快速逐点梳理,使其清晰明了。但请记住,我今天的观点与 J. Tinnes 和 C. Evans 在 2009 年演讲时的观点有所不同(特别是在“是否可以使用超级用户 API”这一问题上),因此我的解释会略有差异,并将引导你了解我认为在 2025 年更好的实践。
Programmatic privilege dropping
以编程方式放弃权限
OSes present different interfaces to users and software developers. System administrators traditionally rely on filesystem permissions to isolate services (UNIX daemons). If we allowed third-party programs to freely change such permissions then it’d nullify the policies the sysadmin was trying to enforce to begin with. Furthermore third-party programs abstract their own virtual worlds and most of the time UNIX filesystem permissions aren’t a good fit to model the security policies such other virtual worlds require. Do you use UNIX permission modes to define who can see your Twitter feed or message you on Identi.ca? Filesystem permissions aren’t the only knobs sysadmins possess to restrict access rights, but the reasoning developed here also apply to these other knobs.
操作系统为用户和软件开发人员提供了不同的接口。系统管理员传统上依赖文件系统权限来隔离服务(UNIX 守护进程)。如果我们允许第三方程序随意更改这些权限,那么系统管理员试图强制执行的策略将失效。此外,第三方程序抽象出了它们自己的虚拟世界,而 UNIX 文件系统权限通常并不适合为这些虚拟世界所需的安全策略建模。你会使用 UNIX 权限模式来定义谁可以看到你的 Twitter 动态或在 Identi.ca 上给你发消息吗?文件系统权限并不是系统管理员限制访问权限的唯一手段,但这里阐述的逻辑同样适用于其他手段。
Nonetheless a process inevitably runs on top of an OS and there are kernel-exposed resources the process interacts with (e.g. files). It’s this interface that matters to the software developer. Web browsers such as Firefox run DRM plugins and it’s desirable to run such third-party plugins without allowing them to have full access to every file that Firefox has access to (usually every file in the user’s HOME directory). Traditional tools such as setuidgid can’t help here and their usefulness is limited as interfaces sysadmins turn to. setuidgid and similar tools aren’t interfaces intended for the software developer to use.
尽管如此,进程不可避免地运行在操作系统之上,并且存在进程与之交互的内核暴露资源(例如文件)。正是这个接口对软件开发人员至关重要。像 Firefox 这样的网络浏览器运行 DRM 插件,我们希望在运行这些第三方插件时,不让它们拥有 Firefox 所能访问的每个文件的完全访问权限(通常是用户 HOME 目录下的所有文件)。传统的工具(如 setuidgid)在这里无能为力,作为系统管理员使用的接口,它们的用途也有限。setuidgid 和类似的工具并不是为软件开发人员设计的接口。
For programmatic privilege dropping, traditional UNIX interfaces are a poor match, and OSes where this gap actually matters will provide extended interfaces that go beyond traditional UNIX (e.g. FreeBSD’s Capsicum and Linux’s Seccomp).
对于以编程方式放弃权限而言,传统的 UNIX 接口并不匹配。在那些真正重视这一差距的操作系统中,会提供超越传统 UNIX 的扩展接口(例如 FreeBSD 的 Capsicum 和 Linux 的 Seccomp)。
Dropping privileges without root
无需 root 权限即可放弃权限
When good interfaces for sandboxing weren’t available, programmers found their way to create sandboxes anyway by abusing mechanisms available only to the superuser. The most emblematic technique in this class is a helper suid binary that’ll configure a chroot jail. The obvious problem with these approaches is that they aren’t available to all programs. Allowing any program to install suid binaries defeat any security measures. Suid binaries equal to temporally raising privileges to full administrative authority over the system. Privileges should only ever decrease, never increase (principle of least privilege).
当没有好的沙箱接口可用时,程序员们通过滥用仅超级用户可用的机制来创建沙箱。这类技术中最具代表性的是配置 chroot 监狱的辅助 suid 二进制文件。这些方法显而易见的问题是它们并非对所有程序都可用。允许任何程序安装 suid 二进制文件会破坏任何安全措施。Suid 二进制文件等同于将权限临时提升至系统的完全管理权限。权限应该只减不增(最小权限原则)。
Another related concern here is to not design APIs that backfire by exponentially increasing the kernel attack surface. The Docker boom popularized Linux namespaces as a mechanism to cheaply isolate services. However within a nested user namespace, the process runs as superuser (within that namespace), and code paths within the kernel that would normally only be available to the superuser are now available to every user. We have over a decade of kernel code that was never written with this premise in mind. This decision caused security problems in the past, and it’s bound to happen again.
另一个相关的问题是,不要设计出通过指数级增加内核攻击面而适得其反的 API。Docker 的兴起普及了 Linux 命名空间(namespaces),将其作为一种低成本隔离服务的机制。然而,在嵌套的用户命名空间内,进程以超级用户身份运行(在该命名空间内),而内核中通常仅对超级用户开放的代码路径现在对每个用户都可用。我们有十多年的内核代码从未考虑到这一前提。这一决定在过去引发了安全问题,未来也必然会再次发生。
It’s fine to allow user namespaces as long as you restrict this interface to trusted containerization tools (e.g. Docker). However Linux namespaces is a terrible interface for software sandboxing. Newer sandboxing interfaces in Linux such as Landlock were carefully designed to not exponentially increase the kernel attack surface as to avoid the disasters we’ve seen with Linux’s user namespaces. Moreover new ways to restrict namespaces within Linux are still being developed and long-term it’s a bad bet to rely on them as a general sandboxing mechanism. The first few years of software sandboxing research I’ve put into Emilua were solely focused on Linux namespaces. After a lot of frustration the focus shifted towards different solutions. Nowadays Emilua still offers support for Linux namespaces, but the intended use-case now is the creation of containerization tools. For proper sandboxing within Emilua, you’ll use mechanisms other than Linux namespaces.
只要将此接口限制在受信任的容器化工具(如 Docker)中,允许使用用户命名空间是可以的。然而,Linux 命名空间对于软件沙箱来说是一个糟糕的接口。Linux 中较新的沙箱接口(如 Landlock)经过精心设计,不会指数级增加内核攻击面,从而避免了我们在 Linux 用户命名空间中看到的灾难。此外,Linux 中限制命名空间的新方法仍在开发中,从长远来看,将其作为通用沙箱机制并非明智之举。我在 Emilua 上投入的前几年软件沙箱研究完全集中在 Linux 命名空间上。在经历了许多挫折后,重点转向了其他解决方案。如今,Emilua 仍然提供对 Linux 命名空间的支持,但其预期的使用场景现在是创建容器化工具。要在 Emilua 中实现适当的沙箱,你应该使用 Linux 命名空间以外的机制。
Discretionary privilege dropping
自主放弃权限
Actually sandboxes might also… 实际上,沙箱也可能……