Rust in Android: move fast and fix things
谷歌在线安全博客:Android 中的 Rust:快速推进与修复之道
发布者:Jeff Vander Stoep, Android 发布日期:2025年11月13日
去年,我们撰文阐述了为何专注于新代码漏洞预防的内存安全策略能快速带来持久且复合的收益。今年,我们来看看这种方法如何不仅仅是修复问题,还能帮助我们更快地发展。
2025年的数据持续验证了这一方法,内存安全漏洞首次降至总漏洞的20%以下。
这是2025年的更新数据。这些数据涵盖了Android平台中第一方和第三方(开源)代码在C、C++、Java、Kotlin和Rust上的变更。本文在2025年底前几个月发布,但由于Android行业标准的90天补丁窗口,这些结果非常接近最终状态。我们可以在必要时加速打补丁。
我们采用Rust是出于其安全性,并且与Android的C和C++代码相比,我们看到内存安全漏洞密度降低了1000倍。但最大的惊喜是Rust对软件交付的影响。Rust变更的回滚率降低了4倍,代码评审时间缩短了25%,如今更安全的道路也是更快的道路。
在这篇文章中,我们深入探讨了这种转变背后的数据,并涵盖:
- 我们如何扩大影响范围:我们正努力在整个软件栈中使安全代码成为默认选项。我们有关于第一方应用、Linux内核和固件中Rust采用的最新进展。
- 我们的第一个Rust内存安全漏洞…差一点就发生了:我们将分析一个在unsafe Rust中差点发生的内存安全漏洞:它是如何发生的、如何被缓解的,以及我们正在采取哪些措施防止其再次发生。这也是回答“如果Rust也会有内存安全问题,那何必费心呢?”这个问题的好机会。
更快地构建更好的软件
开发操作系统需要像C、C++和Rust这样的系统编程语言提供的低层控制力和可预测性。虽然Java和Kotlin对于Android平台开发很重要,但它们是系统语言的补充,而非替代品。我们将Rust引入Android,作为C和C++的直接替代方案,提供相似的控制水平但没有它们的许多风险。我们专注于对新代码和活跃开发的代码进行分析,因为我们的数据显示这是一种有效的方法。
当我们观察系统语言(不包括Java和Kotlin)的开发时,出现了两种趋势:Rust使用量的急剧上升,以及新C++使用量缓慢而稳定的下降。
![Rust与C++新增代码行数对比图:Android第一方代码]
- 吞吐量:交付软件变更的速度。
- 稳定性:这些变更的质量。
跨语言比较可能具有挑战性。我们使用了几种技术来确保比较的可靠性。
- 相似规模的变更:Rust和C++具有相似的功能密度,尽管Rust略高。这种差异对C++有利,但比较仍然有效。我们使用Gerrit的变更规模定义。
- 相似的开发者群体:我们只考虑来自Android平台开发者的第一方变更。大多数是谷歌的软件工程师,群体间有相当大的重叠,许多人同时贡献于两者。
- 追踪随时间变化的趋势:随着Rust采用率的增加,指标是稳步变化、加速进展,还是回归到平均水平?
吞吐量 代码评审是开发过程中耗时且高延迟的部分。重构代码是这些代价高昂延迟的主要原因。数据显示,Rust代码需要更少的修订。自2023年以来,这一趋势一直保持一致。类似规模的Rust变更比对应的C++变更少需要大约20%的修订。
![Rust与C++变更评审修订次数对比图]
此外,与C++相比,Rust变更目前在代码评审上花费的时间大约少25%。我们推测,2023年至2024年间Rust优势的显著变化是由于Android团队中Rust专业知识的增加。
![Rust与C++变更在评审中花费的时间对比图]
虽然减少返工和加快代码评审能带来适度的生产力提升,但最显著的改进在于变更的稳定性和质量。
稳定性 稳定和高质量的变更使Rust脱颖而出。DORA使用回滚率来评估变更稳定性。Rust的回滚率非常低,并且持续下降,即使其在Android中的采用率超过了C++。
![Rust与C++变更回滚率对比图]
对于中型和大型变更,Android中Rust变更的回滚率比C++低约4倍。这种低回滚率不仅表明稳定性,还积极提高了整体开发吞吐量。回滚对生产力极具破坏性,会引入组织摩擦,并动员远超出提交错误变更的开发者的资源。回滚需要返工和更多的代码评审,也可能导致构建重新进行、事后分析和阻碍其他团队。由此产生的事后分析通常会引入新的保障措施,这又会增加更多的开发开销。
在2022年的一项自我报告调查中,谷歌的软件工程师报告称,Rust既更容易评审,也更可能是正确的。关于回滚率和评审时间的确凿数据验证了这些印象。
综合来看 从历史上看,安全改进往往伴随着代价。更多的安全意味着更多的流程、更慢的性能或延迟的功能,迫使在安全性和其他产品目标之间做出权衡。向Rust的转变是不同的:我们正在显著提高安全性以及关键的开发效率和产品稳定性指标。
扩大影响范围
随着Rust对构建Android系统服务和库的支持现已成熟,我们正专注于将其安全和生产力优势带到其他地方。
- 内核:Android的6.12 Linux内核是我们第一个启用Rust支持的内核,也是我们第一个投入生产的Rust驱动程序。更令人兴奋的项目正在进行中,例如我们与Arm和Collabora在基于Rust的内核模式GPU驱动程序上的持续合作。
- 固件:高权限、性能限制以及许多安全措施适用性有限的结合,使得固件既是高风险,又难以保护。将固件迁移到Rust可以显著提高安全性。我们已经部署Rust在固件中多年,甚至为更广泛的社区发布了教程、培训和代码。我们特别兴奋的是我们与Arm在Rusted Firmware-A上的合作。
- 第一方应用程序:Rust正在几个安全关键的Google应用程序中从根本上确保内存安全,例如:
- Nearby Presence:通过蓝牙安全私密地发现本地设备的协议是在Rust中实现的,目前正在Google Play服务中运行。
- MLS:安全RCS消息传递协议是在Rust中实现的,将在未来版本中包含在Google Messages应用中。
- Chromium:PNG、JSON和网络字体的解析器已被Rust中的内存安全实现所取代,使得Chromium工程师在处理网络数据时更容易遵循Rule of 2。
这些例子凸显了Rust在降低安全风险方面的作用,但内存安全语言只是全面内存安全战略的一部分。我们继续采用纵深防御方法,最近的一次险情清楚地证明了其价值。
我们的第一个Rust内存安全漏洞…差一点就发生了
我们最近险些发布了基于Rust的第一个内存安全漏洞:CrabbyAVIF中的一个线性缓冲区溢出。这是一次险情。为了确保该补丁获得高优先级并通过发布渠道进行跟踪,我们为其分配了标识符CVE-2025-48530。虽然这个漏洞从未进入公开版本是件好事,但这次险情提供了宝贵的经验教训。以下部分重点介绍我们事后分析中的关键要点。
Scudo加固分配器立大功 一个关键发现是,由于辅助分配周围有防护页,Android的Scudo加固分配器确定性地使此漏洞无法被利用。虽然Scudo是Android的默认分配器,用于Google Pixel和许多其他设备,但我们继续与合作伙伴合作使其成为强制要求。与此同时,对于可以通过Scudo预防的漏洞,我们将发布足够严重性的CVE。
除了防止溢出外,Scudo使用防护页有助于通过将溢出从静默的内存损坏转变为明显的崩溃来识别此问题。然而,我们确实发现了崩溃报告中的一个漏洞:它未能清楚地表明崩溃是由溢出引起的,这减慢了三方响应和响应速度。这个问题已经修复,我们现在在溢出到Scudo防护页时会收到清晰的信号。
Unsafe代码评审和培训 操作系统开发需要unsafe代码,通常是C、C++或unsafe Rust(例如用于FFI和与硬件交互),因此简单地禁止unsafe代码是不可行的。当开发者必须使用unsafe时,他们应该了解如何安全、负责任地使用。
为此,我们正在为我们的全面Rust培训添加一个关于unsafe代码的新深度讲解。这个正在开发中的新模块旨在教开发者如何推理unsafe Rust代码、健全性和未定义行为,以及最佳实践,如安全注释和将unsafe代码封装在安全抽象中。
更好地理解unsafe Rust将导致整个开源软件生态系统和Android内部更高质量和更安全的代码。正如我们将在下一节讨论的,我们的unsafe Rust实际上已经相当安全。考虑到这个门槛能提到多高,实在令人兴奋。
比较漏洞密度 这次险情不可避免地引发了一个问题:“如果Rust也会有内存安全漏洞,那么意义何在?”
意义在于漏洞密度要低得多。低到足以代表安全态势的重大转变。根据我们的险情,我们可以做一个保守估计。Android平台中大约有500万行Rust代码,发现了一个潜在的内存安全漏洞(并在发布前修复),我们估计Rust的漏洞密度为每100万行代码(MLOC)0.2个漏洞。
我们对C和C++的历史数据显示,其密度接近每MLOC 1000个内存安全漏洞。我们的Rust代码目前的密度要低几个数量级:减少了1000倍以上。
内存安全理应受到重点关注,因为这类漏洞具有独特的能力并且(历史上)非常普遍。高漏洞密度会破坏原本稳固的安全设计,因为这些缺陷可以被串联起来绕过防御措施,包括那些专门针对内存安全利用的措施。显著降低漏洞密度不仅减少了错误数量;它还极大地提高了我们整个安全架构的有效性。
关于Rust的主要安全担忧通常集中在约4%写在unsafe{}块中的代码。Rust的这一子集引发了大量猜测、误解,甚至有人认为unsafe Rust可能比C更容易出错。实证证据表明这是非常错误的。
我们的数据表明,即使更保守地假设一行unsafe Rust代码出现错误的可能性与一行C或C++代码相同,也严重高估了unsafe Rust的风险。我们不确定为什么会这样,但可能有几个促成因素:
unsafe{}实际上并没有禁用所有甚至大部分Rust的安全检查(这是一个常见的误解)。- 封装实践使得对安全不变量的局部推理成为可能。
unsafe{}块受到的额外审查。
最后思考
从历史上看,我们不得不接受一个权衡:缓解内存安全缺陷的风险需要大量投资于静态分析、运行时缓解、沙箱和反应性修补。这种方法试图快速推进,然后再收拾残局。这些分层保护措施是必要的,但它们以高性能和开发人员生产力为代价,同时仍然提供不足的保证。
虽然C和C++将继续存在,软件和硬件安全机制对于分层防御仍然至关重要,但向Rust的转变是一种不同的方法,其中更安全的道路也被证明是更高效的。我们可以在修复问题的同时更快地推进,而不是快速推进然后再收拾烂摊子。谁知道呢,随着我们的代码变得越来越安全,也许我们可以在提高安全性的同时,开始更多地收回我们为了安全而交换的性能和生产力。
致谢
感谢以下个人对本文章的贡献:
- Ivan Lozano 负责编写关于CVE-2025-48530的详细事后分析。
- Chris Ferris 负责验证事后分析的发现,并因此改进了Scudo的崩溃处理。
- Dmytro Hrybenko 负责领导开发unsafe Rust培训的工作,并对本文提供了广泛的反馈。
- Alex Rebert 和 Lars Bergstrom 提供了宝贵的建议和对本文的广泛反馈。
- Peter Slatala, Matthew Riley, 和 Marshall Pierce 提供了关于Rust在Google应用中使用的部分信息。
最后,衷心感谢Android Rust团队以及整个Android组织,感谢你们对工程卓越和持续改进的不懈承诺。
注释
- DevOps研究和评估(DORA)项目由谷歌云发布。 ↩