Andrew Walbran,Android Rust 团队 
去年,我们写了一篇文章,介绍了如何 将 Android 中的本机代码从 C++ 迁移到 Rust,从而减少了安全漏洞 。 我们当时提到的大多数组件都是用户空间中的系统服务(在 Linux 下运行),但这些并不是唯一通常用内存不安全语言编写的组件。 Android 系统的许多安全关键组件都在 Linux 内核之外的“裸机”环境中运行,并且这些组件过去都是用 C 编写的。作为我们强化 Android 设备固件的一部分,我们越来越多地 使用 Rust在这些裸机环境中也是如此。 为此,我们 用 Rust 重写了 Android 虚拟化框架的受保护 VM (pVM) 固件, 为 pVM 信任根提供内存安全基础。 之上 U-Boot 该固件执行与引导加载程序类似的功能,最初构建在广泛使用的开源引导加载程序 。 然而,U-Boot 的设计并未考虑到恶劣环境中的安全性,并且 许多安全漏洞 由于内存访问越界、整数下溢和内存损坏,在 U-Boot 中发现了 。 它的 VirtIO 驱动程序尤其存在许多 缺失 或 有问题的 边界检查。 我们修复了在 U-Boot 中发现的具体问题,但通过利用 Rust,我们可以避免将来出现此类内存安全漏洞。 新的 Rust pVM 固件在 Android 14 中发布。
作为这项工作的一部分,我们通过尽可能使用和贡献现有的 crate 来回馈 Rust 社区,并发布一些新的 crate。 例如,对于 pVM 固件中的 VirtIO,我们花了时间修复现有 virtio-drivers crate 中的错误和健全性问题,以及添加新功能,现在正在帮助维护此 crate。 我们发布了用于 进行 PSCI 和其他 Arm SMCCC 调用 以及 管理页表的 包。 这些只是一个开始; 我们计划发布更多 Rust crate 以支持一系列平台上的裸机编程。 这些板条箱也在 Android 之外使用,例如在 Project Oak 和 课程的裸机部分 我们的 综合 Rust 中。
许多工程师都对 Rust 的高效和令人愉悦的工作方式感到非常惊讶,即使在低级别环境中也能提供良好的高级功能。 从事这些项目的工程师来自不同的背景。 我们全面的 Rust 课程帮助经验丰富的和新手程序员快速上手。 有趣的是,Rust 类型系统(包括借用检查器和生命周期)有助于避免在 C 或 C++ 中容易犯的错误,例如泄漏指向超出范围的堆栈分配值的指针。
我们的一位裸机 Rust 课程参与者这样说:
“可以构建引入 Rust 所有优点和安全性的类型, 但仍然编译成极其高效的代码,例如 writes 内存映射 IO 常量。”
完成调查的 97% 的参与者认为该课程值得花时间。
为了灵活性,设备驱动程序通常以面向对象的方式编写,即使是用 C 语言也是如此。Rust 特征(可以视为编译时多态性的一种形式)为此提供了有用的高级抽象。 在许多情况下,这可以在编译时完全解决,无需通过 vtable 或函数指针结构进行动态调度的运行时开销。
存在一些挑战。 Safe Rust 的类型系统的设计有一个隐含的假设:程序需要关心的唯一内存是由程序分配的(无论是在堆栈上、堆上还是静态地),并且仅由程序使用。 裸机程序通常必须处理 MMIO 和共享内存,这打破了这一假设。 这往往需要大量不安全的代码和原始指针,并且封装工具有限。 Rust 社区对于 MMIO 空间引用的健全性存在一些分歧,并且在稳定 Rust 中使用原始指针的工具目前有些有限。 的稳定化 offset_of, slice_ptr_get, slice_ptr_len, offset_of和其他夜间功能将改善这一点,但干净地封装仍然具有挑战性。 通过原始指针访问结构体字段和数组索引而不创建引用的更好语法也会有所帮助。
中断和异常处理程序引入的并发性也可能很尴尬,因为它们通常需要访问共享的可变状态,但不能依赖于能够获取锁。 对关键部分更好的抽象会有所帮助,但有一些实际上无法禁用的异常,例如用于实现写时复制或其他按需页面映射策略的页面错误。
我们遇到的另一个问题是,一些不安全的操作(例如操作页表)无法干净地封装,因为它们对整个程序有安全影响。 通常在 Rust 中,我们能够将不安全操作(在某些情况下可能导致未定义行为的操作,因为它们具有编译器无法检查的契约)封装在安全包装器中,我们确保必要的先决条件,以便不可能发生任何调用者导致未定义的行为。 然而,在程序的一个部分中映射或取消映射页面可能会使程序的其他部分无效,因此我们还没有找到一种方法来为此提供完全通用的安全接口。 应该指出的是,同样的问题也适用于用 C 编写的程序,程序员总是必须推理整个程序的安全性。
一些在裸机用例中采用 Rust 的人对二进制大小提出了担忧。 我们在某些情况下已经看到了这种情况; 例如,我们的 Rust pVM 固件二进制文件约为 460 kB,而早期 C 版本为 220 kB。 然而,这并不是一个公平的比较,因为我们还添加了更多功能,使我们能够从引导链中删除其他组件,因此所有 VM 引导链组件的总体大小具有可比性。 在这种情况下,我们也没有特别优化二进制大小; 速度和正确性更为重要。 在二进制大小至关重要的情况下,使用 大小优化 进行编译,注意依赖性,并避免在发布版本中使用 Rust 的字符串格式化机制,通常可以获得与 C 相当的结果。
架构支持是另一个问题。 Rust 通常 。 在我们最常见的 Arm 和 RISC-V 内核上得到了很好的支持,但与 C 相比,对更深奥的架构(例如,Android 手机中使用的许多高通 SoC 中包含的高通 Hexagon DSP)的支持可能缺乏
总的来说,尽管存在这些挑战和限制,我们仍然发现,在我们迄今为止尝试过的所有裸机用例中,无论是在安全性还是生产力方面,Rust 都比 C(或 C++)有显着的改进。 我们计划在任何可行的地方使用它。
除了 Android 虚拟化框架方面的工作外, Trusty (Pixel 手机等上使用的开源可信执行环境)团队也一直在努力增加对 Rust 编写的可信应用程序的支持。 例如, 参考 KeyMint 可信应用程序实现 现在采用 Rust 语言。 随着我们继续使用 Rust 来提高您信任的设备的安全性,未来的 Android 设备还会有更多功能。