夜雨聆风学习资料网

ARTICLE · 1091995

AI 时代软件沙箱入门:从原理到实践的安全指南

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) -> actor
  • actor.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 研究发表时,以下对比表格令人印象深刻:

操作系统
模型
代码行数
描述
Windows
ACLs
22,350
Windows ACL 和 SID
Linux
chroot
605
setuid root 辅助沙箱
Linux
seccomp
11,301
seccomp 和用户空间系统调用包装
FreeBSD
Capsicum
100
使用 cap_enter 的沙箱

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/),而不是通过路径。

安全模型的关键问题是:评估你的安全模型是否真的有意义。有时,由可信个人编写的可审计代码,比由不可信的人编写的"更好"代码更重要。

总结

软件沙箱的核心原则:

  1. 进程是权限边界
     —— 将程序分割为独立进程
  2. 使用 Actor 模型通信
     —— 通过 UNIX 域套接字传递消息和文件描述符
  3. 文件描述符即能力
     —— 但要小心 ioctl
  4. 优先使用 Capsicum
    (FreeBSD)或 Seccomp + Landlock(Linux)
  5. 对现有代码使用 LD_PRELOAD 拦截
     —— 实现无感沙箱化

沙箱化曾经非常昂贵,但现在不应该只有 Firefox 和 Chrome 这样的巨头才能用得起。尤其在 AI 时代每个处理不可信数据的程序都应该采用沙箱技术。


原文链接:Software sandboxing: The basics

觉得这篇文章有价值?点个赞和在看,让更多开发者看到!

欢迎在评论区分享你的想法。

欢迎转发给身边的朋友。

#软件沙箱 #Sandboxing #Linux安全 #FreeBSD #Capsicum #Seccomp #系统安全 #进程隔离 #网络安全 #程序员必读

相关学习资料