LWN:在GCC和glibc里支持 CHERI 的 capability!

CHERIin GCC and glibc
By
26, 2022
DeepL
CHERI 架构来自于一个研究计划 , 希望能对普通的 CPU 架构进行扩展,来阻止多种类型的内存相关 bug(和漏洞) 。在 2022 年的 GNU Tools会议上,Alex和Nagy 介绍了将 GCC 和 GNU C (glibc)引入该架构的工作 。CHERI 从根本上改变了内存访问方式,要想能正确地支持这个架构,可不是一个小任务 。
CHERI
首先介绍了 CHERI,这是一个已经运行了十年左右的研究项目,来自于剑桥大学 。它引入了 "" 的概念,这个术语在这里的含义跟平常不太一样 。说,是访问一系列内存的一个token,不能被伪造() 。可以传送给别人,它们可以从其他派生()出来 , 但这样派生之后获得的拥有的不能增加,只能减少 。
在 CHERI 架构的概述中可以看到更多描述找到 。简而言之,一个可以被认为是一个特殊类型的指针 , 占据了 129 bits 。其中最低位的 64 bit 是传统的虚拟地址,而更高的 64 位(通常被称为 " (出处)")描述了相关的访问权限 。其中包括了用来指示允许的操作(读、写、执行)的 , 以及该适用的内存区域 。这 128 位加在一起,就可以像一个指针一样在允许的内存范围内进行它所允许的访问 。
第 129 位存储在其他地方,由 CPU 管理;只有在这个 bit 置 1 的时候,这个才是有效的 。一些 CPU 指令可以从一个旧的衍生出一个新的,这些指令将保持这个 bit 的设置;对这个的直接修改,会导致这个生效 bit 被清零 。其他不被允许的操作,如试图使用一个在允许的内存范围之外写入,也会使该失效 。如果硬件设计完好无误,那么每一个都允许以某些方式访问内存范围,仅此而已 。
在系统启动时 ,  为内核提供了一个对所有内存都允许访问的。在系统的生命周期中使用的每一个其他最终都来自于这个 "root " 。设计良好的系统里,每个可能经过几个层次的衍生之后,就被缩小到一小块内存区域了 。
【LWN:在GCC和glibc里支持 CHERI 的 capability!】 GCC
CHERI 是定义成附加在现有架构上的一个组件的; 项目就是在向 ARMv8-A 架构添加 CHERI 功能 。现在已有原型实现了这种组合 。基于 LLVM 的成熟也有了 , 但 ARM 也希望有 GCC 的版本 , 因此GCC 项目正在努力在 GCC 中增加支持;该项目还在移植 、GDB 和 glibc 。
已经实现了两种model,称为 "pure-cap "和 "";前者将施加在所有指针上,而后者只对系统中的一部分指针使用。模式可以允许把那些支持的代码跟不支持的代码混合在一起运行;Linux 内核已经利用模式获得了对架构的支持 。
在尝试这个移植时 , 面临了很多挑战 。第一步是重新映射type(这是描述一个用来容纳指针值的整数) 。当然,这里要解决的核心问题就是普通的 long 形不够用来放置 ;如果试图使用从 long 中提取的指针,都会在运行时被捕获 。因此,在按这种方式使用此类型的代码在 CHERI 系统中就无法运行了 。对指针进行比较,也比较棘手;两个指向同一位置的可能有不同的访问权限,因此两者逐 bit 比较的话并不完全相等,但在指针比较的时候应该判定它们是相等的 。
说 , 尽管有这些陷阱,但大多数代码在 pure-cap 模式下编译就可以正常工作 。不过,那些非常底层的软件代码则可能需要大量的修改,尤其是那些把玩指针的代码可能会有问题 。例如,在 GCC 中有一个排序功能 , 它会利用指针完成一些技巧,这就会导致编译器报错 。
要教会 GCC 能了解 ,就是一个很大的挑战,需要对整个代码进行许多修改 。它们打破了 GCC 内部的两个基本假设: 指针和整数是完全可以直接互相替换的,以及假设地址和本质上是同一种性质的 。编译器必须确保所有指针都有正确的出处,以及确保指针比较要给出正确的结果 , 等等 。
GCC 的工作最开始是增加了一个 -mfake- 选项,这使得编译器在其内部表示中可以一直使用方式,但仍像以前一样利用来生成标准代码 。这使得的概念与架构的具体细节可以隔离开了 。开发人员首先努力使所有的地方都能使用这个 flag,然后才着手生成能在真实硬件上使用的代码 。
剩下的一个问题是,中的地址范围的边界是用压缩浮点表示法来存储的;否则,这些信息就没法塞在那些 bits 里了 。但是这就导致,无法把所有的地址、base 和 limit 的组合都被表示出来 。说,这个问题主要对内存分配器有影响 , 必须要能对更大范围的内存进行操作 。
glibc
Nagy 接着谈到了为了把 glibc 移植到架构上所做的工作 。这项工作目前是在一个单独的分支中进行的,没有计划将其提交到上游,需要等到可以创建一个更干净的 patch set 再说了 。他说,需要对 glibc 进行大量修改;CHERI C 是一种完全不同的语言 。比如说需要把所有操作指针的代码都修改掉 。
更细节的问题是,必须对 ()等基本函数进行特殊处理 。除非特别小心,否则存储在要复制的内存中的任何在复制目标那里都会完全无效 。()必须返回类型(这是一个 ABI 变动)才能成功返回一个指针 , 其他许多系统调用也需要类似的改变 。
然后是(衍生)的问题 。当内核启动一个进程时 , 该进程获得了一个能够访问整个用户空间的一个。当然,有可能可以继续使用这个来完成所有一切工作,这是第一步,但它并没有真正利用上机制 。所以下一步是在不需要访问所有内存的特定情况下来缩小。
还有一个更棘手的问题,就是动态链接器,它对 64 位地址进行了大量操作 。不过 , 它是与 ELF 二进制文件配合工作的,所以它的大部分工作是以 "base 加 " 的形式进行计算的 。如果 base 值是一个正确的,那么一切都可以正常工作 。随着最初的问题被克服 , 下一阶段是开始将允许写入的与允许执行的区分开;这将需要在中更加谨慎地处理,他说 。
然后是 () 的问题 。理想情况下,这个函数将返回一个可以访问到被分配对象的指针(不可以访问到其他任何东西) 。在第一阶段 , 并没有实现缩小访问范围的工作;工作重心是希望让基本功能可以正常工作起来 。第二阶段则要把返回的指针的范围缩小到只有相关的对象的范围 。但这给 free() 带来了挑战 , 它必须能够访问存储在这个对象本身之外的 ;这要给 free() 单独准备一个特殊的来使用 。他说,要使所有这些工作顺利进行的话 , 还有其他各种挑战;他将为 CHERI 实际上设计了专用的 ()接口 。
他说,目前 test可以运行,但只有在 GCC 中不使用 stack bound 的情况下才能运行 。此外还有一些缺失的功能,包括 、支持可执行的 stack、对的支持,以及对存储在共享内存或通过文件描述符发送的指针的支持 。他总结说,后面这些情况可能永远都无法支持起来 。
[感谢 LWN 订户支持编者参加这次活动] 。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议 。