70% 的 CVE 是内存安全缺陷。缓冲区溢出、释放后使用、重复释放。这类漏洞让攻击者可以从一张畸形 JPEG 一路提权到 root 访问。

自 1975 年以来,我们就知道如何在硬件层面阻止其中的大部分。剑桥 CAP 计算机使用了基于权能的寻址。卡内基梅隆大学的 Hydra 也是如此。想法很简单:每次内存访问都携带自己的许可条。硬件强制执行。没有许可条,就没有访问权。

然后,权能硬件在接下来的四十年里一直在输给扁平内存模型。直到现在,随着 ARM 的 Morello 板和 CHERI 指令集的推出,权能架构才正在上演一场可信的回归。

问题不在于权能是否有效。它确实有效。问题在于,为什么业界花了四十年才开始关心它。

权能指针是不可伪造的票据,而不仅仅是地址

普通指针是一个整数。一个地址。它没有长度、没有权限、没有出处。如果你能猜到一个地址,你就可以访问它。这不是特性。这是 C 从 PDP-11 继承的设计缺陷。

相比之下,权能指针是一个由硬件强制执行的元组。它包含虚拟地址、授权区域的基址和长度、读/写/执行权限,以及一个 1 位的有效性标签。标签存储在正常内存之外,放在专用硬件元数据中,这使得权能不可伪造。你无法从整数构造出有效的权能。你只能从已经拥有权限的实体接收它。

在 CHERI 上,这在 64 位机器上看起来像一个 128 位指针。额外的 64 位携带边界和权限。标签位根据实现不同,存放在影子表或备用 ECC 位中。

以下是在 CHERI 系统上缩小权能的实际样子:

#include <cheriintrin.h>
#include <stdio.h>

int main(void) {
    char buffer[64];

    // In the purecap ABI, &buffer[0] is already a capability
    // carrying 64-byte bounds and read/write permissions.
    char *cap = buffer;

    printf("Original length: %zu\n", cheri_length_get(cap));

    // Narrow authority to the first 16 bytes.
    char *narrow = cheri_bounds_set(cap, 16);
    printf("Narrowed length: %zu\n", cheri_length_get(narrow));

    // This access is safe and hardware-authorized.
    narrow[15] = 'x';

    // This would trap with a CHERI bounds violation:
    // narrow[20] = 'y';

    return 0;
}

如果攻击者破坏了权能指针,标签位会被清除。硬件在下一次解引用时引发异常。利用链在第一步就死亡。

这比 ASLR 和栈金丝雀好得多,后者只是减速带。

iAPX 432 为整整一代人投了毒

那么,为什么我们在四十年里一直假装指针只是整数?

英特尔在 1981 年推出了 iAPX 432。它有硬件权能、面向对象的内存段,甚至还有硬件辅助的垃圾回收。但它也比 8086 慢大约五到十倍,极其复杂,而且与所有现有编译器都不兼容。

IBM 的 System/38 运气更好,它对单层存储使用了权能。它奏效了。它很稳定。但它也是一种专有的中档系统,没有通往新兴开放生态系统的路径。当 UNIX 革命到来时,它带来了扁平地址空间、C 指针以及指针可以放入寄存器的假设。这个假设成为了整个行业的 ABI。

到了 1990 年代,权能硬件只是研究上的奇闻。商业要务是向后兼容和原始性能。内存安全是语言层面的问题,如果它真的是个问题的话。C 和 C++ 干脆宣称这是程序员的问题。

在互联网让每次缓冲区溢出都可以被远程利用之前,这还算够用。

CHERI 的混合模式打破了兼容性僵局

现代权能硬件,特别是 CHERI,吸取了 iAPX 432 的教训。它不强制全有或全无的迁移。

CHERI 支持一种混合 ABI,其中遗留的整数指针和权能指针在同一个进程中共存。你可以只为网络解析器或沙箱化库使用权能编译,而应用程序的其余部分使用普通指针。操作系统(CheriBSD 或 CHERI Linux 移植版)管理过渡。

这很重要,因为真正的障碍从来不是硅片面积。而是软件惯性。有数十亿行 C 和 C++ 代码假设 sizeof(void *) == 8。一种要求重写所有这些代码的权能架构在面世时就已死亡。一种可以逐步采用的架构才有机会。

硬件机制很优雅。权能感知的加载和存储使用专用指令,在地址转换的同时并行检查标签和边界。整数指针完全绕过检查。你只在需要安全的地方为安全付费。

真正的成本是 TLB 压力和指针膨胀,而非周期数

权能并非免费。开销分为三类。

第一,指针大小。在纯权能 ABI 中,每个指针都是 128 位。这使指针密集型数据结构的缓存压力翻倍。链表、树和虚函数表都变胖了。

第二,边界粒度。CHERI 以字节粒度强制执行边界,这意味着内存子系统必须在每次解引用时检查边界。检查本身很快,但如果你创建许多小的受保护区域,细粒度的权能会增加 TLB 和缓存压力。

第三,也是最重要的,软件变更。编译器必须生成权能感知的序言。ABI 发生变化。内存分配器必须返回有界权能而不是原始指针。调试器需要理解 128 位寄存器。

在 CHERI 硬件上测得的运行时开销,对大多数工作负载通常在个位数百分比范围内,对指针密集型基准测试则高达 10% 到 15%。这低于许多现代缓解措施(如内存标记或沙箱化)的开销,而且提供了更强的保证。

权衡的不是性能。权衡的是生态系统动荡。

你今天就可以在开发板上运行它

如果你想尝试权能硬件,你不需要时光机或研究经费。

ARM 的 Morello 板在 Neoverse N1 核心上实现了 CHERI 扩展。CheriBSD 开箱即用。如果你没有硬件,QEMU-CHERI 可以模拟完整的权能架构。

工具链是标准 LLVM。你使用 cheri-clang-march=morello+cheri 标志编译。存在 FreeBSD 和 Linux 移植版。LLVM 集成汇编器、链接器和调试器都理解权能。

从一个函数开始。用缩小的权能封装解析器或反序列化例程。针对它运行你的模糊测试器。当越界写入会破坏堆时,你会得到一个干净的硬件异常。

权能硬件不是内存安全的理论修复。它是一种可用的、已交付的技术,业界忽视了四十年,因为激励措施错了。扁平内存建造起来更快、移植更容易,而且在远程利用成为默认威胁模型之前已经够用了。

硅片有效。编译器有效。操作系统有效。剩下的只是决定代码库的哪些部分值得首先保护。