David Chisnall认为,软件隔离中最困难的挑战并不是将两个工作负载完全分开,而是隔离那些需要相互通信和共享数据的组件。在InfoQ发布的一场演讲中,Chisnall解释了CHERI架构如何将安全能力的概念与扩展指针结合起来,为指针提供硬件保护,同时在同一地址空间内支持内存安全和精细化隔离。
Chisnall是SCI Semiconductor的联合创始人兼系统工程总监,同时也是剑桥大学的访问研究员。这场演讲基于CHERI的研究工作及其软硬件实现,包括CheriBSD、ARM Morello扩展和面向微控制器的CHERIoT;此外还涉及名为RVY的RISC-V扩展,他表示该扩展的标准化工作已接近完成。
指针成为受硬件保护的实体
CHERI并不是传统意义上的独立指令集,而是一组可以与不同架构进行适配的思想,类似于SIMD概念如何通过多个扩展得到实现。按照这一理念,指针不再只是表示内存地址的数字,而是成为一种能力,其中携带地址、对象边界和一组权限,并附带一个硬件标签,用于标识该值是否为有效指针。
这些能力具有单向属性:可以收紧边界或移除权限,但不能从指针本身扩大边界或恢复权限。此外,在将指针用于加载、存储或跳转操作之前,硬件会验证该指针是否有效且带有标签。如果程序试图使用一个被当作数据处理的值,或试图篡改该值以生成地址,操作就会失败,而不会自动将其转换为可用指针。
根据演讲,这种设计可以直接防御内存利用攻击中常见的若干阶段,例如用攻击者可控的值覆盖指针。它还支持诸如memcpy之类的通用复制操作,无需预先知道指针的位置,因为指针标签会随值一同转移,或者在覆盖能力空间的一部分时被清除。
可收紧的边界和权限
CHERI能力包含读取和写入权限,以及用于处理数据中指针的独立权限。Chisnall解释说,这种精细控制可以让程序读取复杂结构中的数据,却不能在其中存储新的指针。这样一来,就能提供覆盖完整数据树的只读视图,而不是仅对单个对象进行表面保护。
sealing机制还可以创建安全的不透明指针(opaque pointer),类似句柄或文件描述符,但未必需要由内核维护转换表。能力使用类型代码进行封闭;在封闭状态下,不能修改它、对其解引用,也不能将其用于跳转、加载或存储。当它返回给所有者时,只有类型代码匹配,解封操作才会成功。
内存的空间和时间安全
在发生越界错误时,CHERI可以将C语言中的未定义行为转化为确定性的硬件停止。Chisnall展示了一个包含14个元素的数组被访问第14个元素的例子:在传统系统中,这种访问可能覆盖返回地址;而在CHERI系统中,操作会被拦截,因为指针受到数组边界的限制。
这一方案并不局限于空间安全。在CHERIoT中,指针标签与影子内存配合使用,影子内存为每个分配单元保存一个比特,因此可以使与已释放内存相关的指针失效。当能力被加载到寄存器时,加载过滤器可以检查内存块的状态,并在内存已释放时清除标签。硬件机制还会定期扫描内存,移除指向已不再存活对象的指针标签,从而在失效周期完成后重新使用内存。
从内存安全走向精细化隔离
正如Chisnall所述,CHERI项目最初的目标是构建超越传统进程隔离边界的精细化隔离。将每个组件隔离到独立进程中,会产生与页表、RPC机制、数据序列化和复制相关的成本。对于数十个隔离域,这可能是可以接受的;但当浏览器需要为图像和音频解码、JavaScript JIT运行等组件运行数千甚至数万个沙箱时,这种方式就不太合适了。
CHERI模型允许将从执行寄存器中现有能力出发能够访问的内存集合视为一个保护域。同一地址空间内的两个域可以共享部分数据,同时保持其他部分隔离,因为共享始于特定能力,并且可以扩展到复杂的数据结构及其可访问的一切内容。
对于更大型的系统,演讲介绍了通过coexecve进行渐进式隔离的路径。该调用会在不创建新地址空间的情况下创建新进程,然后固定一组根能力,确定新进程能够访问的内容。而CHERIoT则在实时操作系统中对权限进行严格分离,并采用一个负责在隔离舱之间进行上下文切换和调用的切换组件;根据演讲,该组件规模约为350条指令。
Chisnall提出的结论是,内存保护和隔离不应依赖于完全重写代码规则,也不应依赖于将每个组件都拆分到独立进程中。在保持C模型与通常情况相近的同时,使指针和访问能力变得可理解并受到硬件控制,可能为逐步减少内存错误、构建更易审计的共享机制提供路径。大型系统与微控制器之间的实现细节仍然不同,而且这场演讲反映的是演讲者本人在CHERI方面的观点和直接经验,并不构成能够自动保证消除所有安全问题的通用标准。