CHERI:为安全可信计算基奠定基础
作者:Brooks Davis
BSD 操作系统自 20 世纪 80 年代就已存在,UNIX 的历史更可一路追溯至 1969 年。然而,尽管性能、存储和内存容量都增长了好几个数量级,我们使用的 CPU 在计算模型上仍与早期 UNIX 运行的 PDP-11 高度相似。我们拥有平坦的虚拟地址空间(现在为 64 位而非 16 位),基于 TLB 的进程内存虚拟化,以及页粒度的权限。
这种模型有许多优点,多年来为我们提供了良好服务,但也有一些严重缺点。多类内存损坏漏洞(包括缓冲区溢出)尽管经过多年努力修复,仍是源源不断的漏洞来源。作为一个社区,我们采用了许多缓解技术,如地址空间布局随机化(ASLR)和分区化(又称特权分离),但它们都有显著代价。ASLR 带来不可忽略的性能开销,提供的保护是统计而非绝对,而某些常见编程模型(如预派生服务器)使其容易遭到高效自动化攻击。分区化通过把程序中风险较高的部分限制在特权受限的环境中,限制了其他缓解机制失效时的影响范围。对软件做分区化时,程序员把程序改造为分布式计算,增加了上下文切换开销和 TLB 压力,同时把单地址空间的程序转化为分布式系统,并带来所有相关的复杂性。这意味着分区化通常只用在与应用编程模型天然契合的简单场景(uniq(1)),或漏洞影响最关键的场合(sshd(1)、Chrome 或 Firefox)。
CHERI(Capability Hardware Enhanced RISC Instructions,能力硬件增强 RISC 指令)是在 DARPA CRASH(Clean-slate design of Resilient, Adaptive, Secure Hosts,弹性、自适应、安全主机的全新设计)计划下开发的硬件/软件协同设计项目,挑战了过去 40 多年来我们对软硬件接口的假设。我们对 MIPS64 ISA 做了扩展,提供稳健、细粒度、硬件强制执行的进程范围内存能力,以与 C 指针兼容的方式强制对象边界和权限。我们进一步扩展这些能力,提供稳健、高效的进程内分区化。我们的扩展以 MIPS 协处理器的方式实现,允许 CHERI 特性的渐进式部署。
为开发并展示这些特性,我们实现了一款 FPGA 软核 CPU,把 FreeBSD 移植到其上,并为 FreeBSD 扩展了 CHERI 能力支持 [Woodruff]。我们为 LLVM 和 Clang 添加了在 C 中使用能力的支持,包括混合模式(带特殊注解的指针变成能力)和纯模式(所有指针都是能力)[Chisnall]。此外,我们还扩展了 FreeBSD 移植版本(CheriBSD),以支持代码的进程内分区化,并用 tcpdump 程序和 zlib 库验证了该方法的可行性 [Watson]。
CHERI 能力
CHERI 能力是对虚拟地址空间区域的不可伪造引用。它们存储在内存中时附带一个标签位,用以验证其有效性(而非任意排列的比特),并在特殊的能力寄存器中操作。能力包含基址、长度、相对基址的偏移、权限和类型。操作能力的指令只能缩小基址和长度所界的范围,或减少权限。试图扩大范围或权限会触发硬件异常。因此,能力提供单调递减的权限。
所有内存访问都通过能力进行。这可以通过新的基于能力的 load/store 指令直接进行,也可以通过默认数据能力(DDC)间接进行。运行不感知能力的程序或混合模式程序时,DDC 被设置为对整个地址空间拥有权限的能力。这使得所有遗留 load/store 指令在混合模式或纯 MIPS 二进制中按预期工作。
由于所有内存访问都通过能力进行,给定线程可访问的地址空间范围,是寄存器堆中能力集合可访问地址空间的传递闭包。也就是说,是当前寄存器堆中的能力可直接访问的内存,加上从这些可访问内存中加载的能力所能访问的内存。因此,分区化可以通过改变寄存器集的内容来实现。这通过 CCall 指令完成,它取一对同类型的代码能力与数据能力,保存当前寄存器内容,并设置一个在代码能力中执行、可访问参数寄存器和数据能力指定寄存器的新寄存器集。退出沙箱时,CReturn 指令从可信栈中恢复寄存器集。
在我们主要的原型中,能力为 256 位,强对齐,标签位存储在 DRAM 中独立且不可访问的部分。
使用能力的主要开销来自指针变成能力后,代码内存占用(尤其是缓存占用)增加。指针密集型基准测试有可测量的开销,但到目前为止,tcpdump 等真实程序没有表现出明显损失。
C 支持
与许多过去的能力系统不同,我们设计 CHERI 能力时明确以用作 C 指针为目标。这对我们的 ISA 和能力的内容都有相当大的影响。最显著的是,我们的能力不仅包含基址和长度,还包含偏移,因为真实 C 程序经常临时访问超出其分配范围的值。
我们最初在 C 中支持能力的工作重点是添加新的 __capability 注解,用于我们希望受限的指针。例如,我们对一个版本的 tcpdump 做了注解,保护其 packet 指针,防止越界访问产生虚假结果。需要注解的代码达数千行,再加上新导入 tcpdump 代码库时产生的数千个合并冲突,使我们确信 CHERI 要可用就需要纯能力模式。我们目前可以在纯能力模式下编译几乎所有 C 代码,并对大部分分区化代码这么做。我们继续在 libc 中必须感知能力的支持代码以及 MIPS64 与纯能力代码之间(通常在分区化代码边缘)的转换中使用混合模式。
将 CHERI 能力作为指针支持还有许多 ABI 上的微妙之处,超出本文范围。
CheriBSD
为确保我们的想法真正可行,我们从一开始就设定目标,要在 CHERI 上运行真实的操作系统和应用栈,并允许软件渐进式采用 CHERI 特性。为此,我们首先让 FreeBSD 在 CHERI 上启动,添加了原型板所需的特性,但不包括能力支持所需的特性 [Davis]。我们另外添加了内核对运行包含能力的程序的支持。这包括进程启动、上下文切换代码、信号处理和调试。下表列出了所需的少量改动。
头文件
19
1,424
49
CHERI 初始化
2
11
4
上下文管理
2
392
10
异常处理
3
574
90
内存复制
2
122
0
虚拟内存
5
398
27
对象能力
2
883
0
系统调用
2
76
0
信号传递
3
327
71
进程监控/调试
3
298
0
内核调试器
2
264
0
为支持混合能力程序,我们对 libc 略作修改,使内存操作函数(memcpy()、memmove()、qsort())感知能力,以允许结构体包含能力。这些改动使我们继续支持未修改的 MIPS64 二进制,事实上,我们的大多数用户态程序之所以感知能力,仅仅是因为无条件使用感知能力的内存操作函数更省事(且可能因每条指令复制更多字节而更快)。我们还提供了字符串和内存操作函数的感知能力变体。例如,下面展示了感知能力(因而内存安全)的 strcpy_c() 实现。
由于 clang 对 MIPS64 的初始支持较弱,我们修改了 FreeBSD 构建系统,允许用我们修改过的 clang 版本构建选定的库,同时用基础 gcc 编译系统其余部分。我们还修改了构建系统,构建所有库的纯能力版本,类似于普通构建系统在 64 位系统上构建 32 位库的方式。这既让我们能更全面地测试编译器支持,也允许我们把未修改的库链接到使用纯能力模式的分区化代码段中。
libcheri
CheriBSD 中的分区化目前在 libcheri 库中实现。它提供加载沙箱类和创建沙箱对象的接口。沙箱对象实质上是进程内的小型地址空间——libcheri 映射一段地址空间区域,把分区化对象加载进去,再安全地调用该对象实现的方法。在我们当前实现中,分区化代码通常是纯能力代码,以利用内存安全保证并简化从沙箱外部传入能力,但其他模型也是可能的。例如,少量包装代码可以让一个未修改的 32 位库在 64 位程序的沙箱中运行。
从概念上讲,libcheri 沙箱是库,但有一点不同:每个库可以创建多个实例,且这些实例可以独立失败并重置。这使得像 tcpdump 包解码或 zlib 解压这样的高风险代码可以被放入沙箱,降低失败的影响,因为沙箱代码没有直接进行系统调用的能力,对主进程的影响也大幅减小。
我们用 libcheri 分区化来保护 tcpdump 主进程(常以 root 运行!)免受包解析和打印代码(手工编写的 C 代码,处理来自网络的不可信且常常损坏的数据)的影响。我们能防御从简单崩溃到基于无限循环的拒绝服务攻击,甚至供应链攻击(即数据包触发恶意解析器中的 bug)等多种攻击模型。这些改动启用后,崩溃或拒绝服务 bug 的影响仅限于丢失该数据包的格式化输出,以及重载沙箱实例时短暂的减速。
除了保护 tcpdump 这类应用免受其内部代码影响,我们还实现了库分区化,为分区化后的 zlib 库提供 API 和 ABI 兼容的接口。这让完全未修改的、动态链接的 gzip、gif2png 等程序无需重新编译即可获得分区化且内存安全的 zlib 带来的好处。这种库分区化策略让分区化的成果能以最少的工作惠及尽可能多的用户。
对 FreeBSD 的贡献
在 CHERI 和 CheriBSD 的工作过程中,我们向 FreeBSD 回馈了若干改动。在最初的启动工作中,我们改进了对 CFI flash 设备的支持,把对 Flat Device Trees 和 FreeBSD 引导加载器的支持移植到了 MIPS,并总体上改进了 MIPS 支持。我们已将这些工作以及我们 Terasic DE4 参考板上特定 Altera 和 Terasic 硬件的驱动合并到上游。
在 CHERI 工作中,我们对 C 标准更严格的解读偶尔会发现 FreeBSD 代码中的正确性问题。我们发现的通用问题已合并,因为它们既会让基于 CHERI 的潜在平台受益,也会在 C 语言支持实现后让 Intel MXP 等其他内存安全系统受益。
我们还支持了 QEMU 用户模式支持的开发,以便在我们的 FPGA 上构建软件包。在 EdgeRouter™ Lite 这样的嵌入式板子上构建软件包很慢,而在 100 MHz、I/O 缓慢的 FPGA 上构建完全不可行,所以我们不得不支持构建新的基础设施。
最后,如果未来出现 CHERI 的硬件实现,CheriBSD 已准备好作为 FreeBSD 中 CHERI 主线支持的参考平台和基础。
未来工作
我们目前正探索一种新的系统调用接口,其中系统调用参数指针是能力。这将允许我们以纯能力模式运行大量未修改的程序。我们希望能在不久的将来构建并安全运行具有完整空间内存安全的 FreeBSD 用户态。我们预计,在如此庞大的代码库上强制边界会暴露出若干细微 bug。
库分区化是改进多样代码库安全性的有力工具。我们已实现了若干分区化原型,但需要移植更广泛的库。这样做将帮助我们确定需要开发哪些工具来简化此过程。我们已通过 SOAAP 项目 [Gudka] 在这一领域做了一些工作,但主要集中在应用分区化上。库分区化会带来相似但相关的问题。在某些情况下,对某些库实现基于进程的库分区化可能既可行又可取。基于缓冲区接口的库(如 zlib)不适合这种方式,但面向流接口的库即便在面向进程的环境中也可能达到合理性能。
由于缓存压力是 CHERI 的主要性能影响之一,我们目前正为 CHERI 探索 128 位压缩能力模型。我们的实验显示,这能显著降低开销,代价是大对象粒度的损失。我们推测,粒度的损失在很大程度上与现有的向上取整分配做法相符,但确认这一推测的工作仍在进行中。
延伸阅读
本文仅触及我们 CHERI 工作的表面。我们的三篇会议论文涵盖了更详细的内容,并展示了我们随着软件开发支持进展而演化的思路。The CHERI capability model: Revisiting RISC in an age of risk [Woodruff] 涵盖了 CHERI 的关键内存安全属性。Beyond the PDP-11: Processor support for a memory-safe C abstract machine [Chisnall] 展示了在 CHERI 之上实现 C 所需的改动。最后,CHERI: A Hybrid Capability-System Architecture for Scalable Software Compartmentalization [Watson-Oakland] 详述了我们的分区化策略。
对 ISA 细节感兴趣的读者可能会对 Capability Hardware Enhanced RISC Instructions: CHERI Instruction-Set Architecture [Watson-ISA] 一文感兴趣。
试用 CHERI
我们已发布 CHERI 的 QEMU 实现,在我们的 GitHub 仓库 https://github.com/CTSRD-CHERI/ 上跟踪 CheriBSD、Clang 和 LLVM 的进展。
我们已将 CHERI CPU 的 FPGA 实现及相关软件快照以开源形式发布在 http://chericpu.org。FPGA 版本发布频率较低,且落后于当前开发状态。如果你需要 FPGA 版本,请联系我们。
Brooks Davis
Brooks Davis 是 SRI International 计算机科学实验室的高级软件工程师,也是剑桥大学计算机实验室的客座研究员。他从 1994 年起使用 FreeBSD,2001 年起成为 FreeBSD 提交者,2006 至 2012 年间是核心团队成员。Brooks 于 1998 年在哈维穆德学院获得计算机科学学士学位。他的计算兴趣包括安全、操作系统、网络、高性能计算,当然还有在各种领域使用 FreeBSD 的方法。不写代码时,他喜欢烹饪、酿酒、园艺、木工、锻铁和徒步。
最后更新于