什么是控制流完整性 (CFI),为什么它很重要?
Android 驱动的设备存储我们最敏感的个人数据。因此,安全性是 Arm 的重中之重,这延伸到系统的各个层面,包括 Chromium 等用户空间应用程序。除了我们的照片、密码和银行详细信息之外,我们还希望 Chromium 能够使用沙盒等行业标准技术安全地从 Internet 上下载和执行不受信任的内容。
虽然 Chromium 因其强大的安全性而受到认可,但所有软件都建立在抽象和隐含承诺的基础上,即执行符合开发人员的意图——正确的子例程在预期的上下文中以正确的顺序完全执行。跳转或返回到任意位置会让攻击者链接以前无害的代码片段,从而导致对内核或 Android 系统服务的更严重的攻击。让这些攻击更加困难是 Arm 开发 PAC 和 BTI 背后的架构、硬件和软件的原因。
PAC 和 BTI 如何工作?
当攻击者实现了对存储在当前堆栈帧上的返回地址的覆盖时,就会发生面向返回的编程 (ROP) 攻击。指针身份验证有助于防止这种攻击并通过在将链接寄存器保存到堆栈之前使用PACIASP指令使用唯一的每个进程密钥对链接寄存器进行加密保护来保护后向边缘(参见图 1)。当函数返回时,PAC在返回之前使用匹配的AUTIASP指令验证恢复的链接寄存器。Arm 将 PAC 的这种特定用途称为保护返回地址“PAC-ret”。
虽然 PAC 是一个很好的改进,但它只保护反向边缘 - 这就是 BTI 的用武之地。例如,考虑这样的函数:
这可能会使用 -mbranch-protection=pac-ret 编译器标志编译成这样的东西:
如果攻击者可以实现小的越界写入(例如,通过破坏 C++ vtable 并通过 use-after-free 分支到它),他们可以在此函数中的任意位置着陆。这意味着他们可以通过登陆 PACIASP 来签署一个错误的链接寄存器,这也可以方便地跳过范围检查——如果攻击者在 x0 中有正确的寄存器值,他们可以通过直接跳转到函数返回来继续攻击。BTI 通过要求从寄存器中获取的所有间接分支都落在 BTI 或 PACIASP着陆板指令上,从而减轻了这种攻击,这表明该位置旨在作为有效的函数开始或其他分支目标。使用 BTI(通过 -mbranch-protection=standard 编译器标志启用),函数现在看起来像这样:
使用 BTI,如果攻击者从 x16 或 x17 寄存器分支到那里,则攻击者只能到达 PACIASP 并签署任意链接寄存器。如果他们不控制这些寄存器,他们只能通过 BLR(带有链接寄存器的分支)登陆函数的起始 BTI C,这表明这是一个有效的函数入口点(他们不能跳过范围检查任何一个)。由于新的 BTI J 指令,除了通过 BR(没有链接寄存器的分支)之外,也无法访问 switch 语句的任何其他部分,这也使得控制反向沿变得更加困难。相关指令完全向后兼容,并在基于 Armv9 的 CPU 上作为无操作执行。
将这些技术带到 Chromium 需要什么?
对于像 Chrome 这样静态链接到单个二进制文件的应用程序,我们需要确保每个可能的间接调用目标都使用 BTI 着陆板进行注释。通过特殊的 ELF 注释声明支持 - 如果 BTI 和非 BTI 对象文件链接在一起,则默认情况下禁用 BTI。为了在 Chromium 中启用 BTI(并提供最强大的 PAC 保护),我们需要在任何地方启用它。
在 Chromium 中启用 PAC 和 BTI 始于在 Android 开源项目 (AOSP) 中启用它,因为来自 AOSP 的构建工件被用作 Android 原生开发工具包 (NDK) 的一部分。Android 和浏览器支持团队与我们的开发解决方案小组和 CE-OSS 中的其他团队合作,为 AOSP 的组成组件(如其构建系统、工具链、BoringSSL 和 Linux 内核的 v5.10)添加 PAC 和 BTI 支持,并广泛在 2020 年发布之前验证了 AOSP 12。
2020 年,我们还开始与 Google 合作,在 Chromium 中追踪任何预编译的二进制 blob,并使用 BTI重建它们。
我们需要准备 Chromium 以使用 NDK 的 r23 版本,这涉及到大量的手术以删除对预构建的非 BTI libgcc 4.9 库的任何剩余依赖项。我们还需要从它们的 GCC 等价物[迁移到 LLVM 的工具。
在采用 NDK r23 之前,需要调查和纠正 LLVM 的 compiler-rt 和 libunwind 库中的各种问题和回归。我们还需要通过在 Chromium 中提供正确的内存映射原语来集成 V8 的 BTI 支持,并且我们手动将 BTI 着陆板添加到libdav1d、libffmpeg等组件, 和breakpad。
2022 年 1 月,我们收到了首批支持 PAC 和 BTI 硬件的 Android 12 设备,这加快了这项工作。2022 年 3 月,我们开始在此硬件上运行 Chromium 的所有测试套件。在 M101 中,我们为 Chromium 中的所有 C++ 代码启用了指针身份验证,随后在 M102 中支持了 V8 生成的代码。在 M105 之前,我们一直将 BTI 作为选择加入标志,现在它是默认的。Arm实施的链接器检查以维持 M106 中的 BTI 支持,并随着 PAC/BTI 硬件的普及继续对其进行定期测试。
下一步是什么?
由于我们在 AOSP 和 NDK 上的工作,大多数使用本机代码的 Android 应用程序和库都可以通过使用-mbranch-protection=standard编译器标志进行编译来采用增强的 CFI ,我们强烈建议大多数项目使用此方法。
然而,安全是一个过程,而不是结果。尽管我们在保护 Chromium 方面取得了良好的开端,但我们将继续努力保护最终用户,确保 Rust 等具有前瞻性的技术能够很好地支持 PAC 和 BTI,提高性能,并为 Linux 上的 Chromium 提供 PAC 和 BTI 支持系统。我们还在研究新方法,以在生成可执行代码的地方(如 Chromium 的 V8 中)增强 Arm 架构的安全性,改进 Arm 的应用程序二进制接口以更好地利用指针身份验证,并继续关注其他类别的内存安全等安全问题。
原作者:理查德·汤森