Android Binder换Rust:1.1万行C代码退场

Rust 化能减少 Binder 的部分内存安全风险,但不会让 Binder 变成“零漏洞”组件,迁移的成败取决于 unsafe 边界、ABI 兼容和 OEM 升级能力。这里的 unsafe 是 Rust 中由开发者承担底层正确性责任的代码区域;ABI(应用程序二进制接口)则规定不同组件之间如何调用,以及如何解释参数和内存布局。

热点文章将这次变化概括为“删掉 1.1 万行 C 代码,Rust 正式接手 Android Binder”。对普通应用开发者而言,只要公开 API、Parcel 格式和事务语义保持稳定,通常不需要重写应用;需要谨慎评估的是平台实现、内部原生接口,以及用户态、内核与 OEM 分支之间的版本组合。

1.1 万行代码位于哪一层

Android Binder 的实现分为三层:

  • Java 框架层负责向应用暴露 Binder API、对象引用和事务机制,开发者日常调用的是这一层。
  • 用户态 libbinder 包含代理、存根及引用管理逻辑,并通过 ioctl 等接口与内核驱动协作。ioctl 是用户态控制字符设备或驱动的一类系统调用接口。
  • 内核 Binder 驱动负责进程节点、对象引用、死亡通知、事务路由、缓冲区复制和权限检查,是安全边界最敏感的一层。

热点文章提供的是事件级信息,并没有在本文所依据的材料中给出足够完整的提交级统计。因此,“1.1 万行”至少存在三种可能口径:旧实现的毛删除量、计入新增 Rust 代码后的净减少量,或者迁入 Rust 的代码规模。

此外,还必须核对统计对象究竟是 C、C++、生成代码还是构建辅助代码。标题中的“C”不能自动扩大为“整个 Binder 技术栈”;同样,删除旧代码也不等于项目净减少了同等数量的维护代码。

Android 的 Rust 平台策略与风险

一次普通的跨进程调用,大致是客户端取得服务代理,把方法调用编码成事务,再由服务端处理并返回结果。Binder 资料通常也按服务注册、启动和跨进程调用来梳理这条链路。Binder 位于多个系统服务之间,引用计数、生命周期或事务处理一旦出错,影响可能从一个服务扩散到其他服务。

Android 从 Android 12 开始把 Rust 作为平台语言。到 Android 13,新增原生代码中约 21% 使用 Rust,AOSP 当时已有约 150 万行 Rust 代码。同期 Android 内存安全漏洞的年度数量从 223 个降至 85 个,占比从 76% 降至 35%,但相关报道也明确指出,这种同期关系不能直接认定为因果关系。

因此,Android 的总体策略是让新增底层代码优先采用内存安全语言,而不是简单推倒全部 C/C++ 实现。Binder 的 Rust 化属于其中一次关键基础设施迁移,其价值不能只用“删了多少行”衡量。

1.1 万行代码的迁移路径:接口、所有权、FFI 与测试

可行的路径不是一次性改写,而是先冻结外部契约,再逐步迁移内部实现,并持续证明新旧行为等价。以下是根据依赖关系还原的实施路径,不是对官方逐个提交的复述。

第一步:确定接口清单

需要先盘点公开原生符号、ioctl 命令号、参数结构布局、错误码、对象引用及生命周期约定,再把内容分成两类:必须保持不变的外部契约,以及允许重写的内部实现。

如果接口没有先冻结,后续即使 Rust 代码通过编译,也可能出现旧调用方能够编译、运行时却因命令号或结构布局变化而失败的问题。

第二步:建立所有权模型

迁移的核心不是把裸指针机械地包进 Rust,而是明确谁拥有对象、谁只能借用、引用可以存活多久。原有引用计数、全局状态和共享可变数据,都需要转化为显式的所有权与生命周期关系。

实施时通常先迁移依赖较少、边界清晰的叶子模块,再处理引用表、节点关系和锁保护等核心共享状态。共享状态无法用单一所有权表达时,可以保留最小同步原语,但不宜把旧式全局状态原样搬进 Rust。

第三步:收窄 FFI 与 unsafe 边界

FFI(Foreign Function Interface,跨语言接口)用于连接 C/C++ 与 Rust。桥接层应尽量窄,只传递调用方实际需要的句柄和数据,不暴露大块内部对象图。

参数结构、协议定义和包装代码如果由工具生成,应使用同一份定义源,并在构建阶段检查布局,避免 C 与 Rust 两侧悄然分叉。原始指针、内核内存操作和侵入式链表等无法由安全 Rust 完整表达的行为,则应集中在数量有限、可单独审查的 unsafe 区域。

第四步:验证新旧实现等价

新旧实现需要针对相同输入比较事务结果、错误返回、对象引用、并发顺序和缓冲区边界。并发压力测试、模糊测试、异常路径测试及性能基线都应在迁移过程中持续执行,而不是等旧 C 代码删除后再补。

第五步:确定旧代码的删除条件

只有调用方和构建目标完成切换、设备覆盖到位且回滚方案明确后,才能删除对应旧实现及兼容胶水层。混合阶段允许 C/C++ 与 Rust 产物并存,但旧调用方必须逐步退出,生成代码和构建辅助文件也不能重复计入“退场”行数。

按依赖关系还原的迁移流程如下:

接口与 ABI 契约盘点
          ↓
所有权与生命周期建模
          ↓
叶子模块迁移 → 建立窄 FFI / unsafe 边界
          ↓
核心共享状态迁移
          ↓
差分测试 + 并发/模糊/性能基线
          ↓
灰度启用 + 运行监控 + 保留回滚路径
          ↓
调用方、构建目标与设备覆盖完成
          ↓
删除遗留 C 实现及兼容胶水

Rust 的安全收益与限制:内存错误减少不等于漏洞消失

在正确建立的抽象边界内,安全 Rust 可以限制释放后使用、重复释放和越界访问。例如,引用失效后继续使用通常会被编译器阻止,所有权规则也会减少手工维护引用计数时常见的错误。

但 Binder 还存在另一类风险:锁顺序不一致造成的竞争、生命周期假设错误、协议状态机缺陷、权限校验遗漏以及错误恢复不完整。这些问题不会因为代码换成 Rust 就自动消失。

资料记录了一个有代表性的案例:CVE-2025-68260 涉及 Android Binder Rust 重写版本中的侵入式双向链表。Node::release 遍历链表时,NodeDeath::set_cleared 同时尝试将自身移除,二者对锁和 unsafe 前提的假设不一致,可能造成内存损坏;现有评估认为主要影响是系统崩溃,尚未证实可以远程执行代码。

修复最终修改了安全代码中的 core::mem::take,因为该安全代码破坏了 unsafe 块所依赖的不变量。这个案例不是对所有 Rust 内核代码的泛化评价,它说明 Rust 的关键收益在于把危险集中到明确边界,而不是提供“零漏洞”保证。

性能结论的适用范围:17% 延迟下降和 16% 内存下降

一份二手资料称,Rust 版 Binder 在其并发测试中延迟降低 17%、内存占用减少 16%。但材料没有给出完整的硬件信息、负载、编译配置和基线,因此不能把它外推为 Android 设备的普遍性能结论。

可复现的性能对比至少应覆盖吞吐量、P50/P95/P99 延迟、CPU 占用、内存峰值、锁竞争和长时间稳定性。测试还要控制编译器版本、优化选项、系统负载及 Binder 配置,避免把调度差异误算成语言收益。

Rust 的所有权建模、边界检查和同步抽象也可能带来额外成本;泛型单态化、代码体积或不同缓存行为也可能影响结果。性能是否改善,应由可复现的系统级数据决定,而不是由语言标签推导。

应用兼容性与升级约束:ABI、工具链和旧设备

只要 Java 层公开 API、Parcel 格式、事务编号和调用语义保持稳定,现有应用通常不需要重写。直接依赖内部原生符号的应用则需要单独审查;这类应用虽然不常见。

主要难点是系统组件之间的二进制兼容:

组合 是否可能成立 必须核对的内容 建议处置
新内核驱动 + 新用户态 目标组合 全部命令、布局、错误码及生命周期 完整设备测试后启用
新内核驱动 + 旧用户态 取决于 ABI 是否保持 ioctl 命令、结构布局、引用约定 不兼容时禁用新路径或回退
旧内核驱动 + 新用户态 取决于用户态是否只依赖旧基线 新特性探测、最低内核版本 做版本门控和能力降级
旧内核 + 旧用户态 可作为回滚路径 原版本缺陷和长期维护周期 保留到覆盖率达到要求
OEM 定制内核或预编译模块 + 新组件 风险最高 厂商修改、符号导出和模块依赖 厂商回植、兼容层或分批升级

用户态与内核态的兼容不能只凭“源码能够编译”判断,还要保持公开原生符号、ioctl 命令号、参数布局、错误码和对象生命周期一致。构建系统则需要同时管理 Android 构建配置、Rust 工具链版本、C/C++ 链接、符号可见性和依赖关系。

OEM 还会带来内核和预编译模块的兼容问题。厂商可能修改内核、携带预编译模块,或者只升级系统分区而不升级设备内核,从而形成用户态与内核版本错配。没有厂商回植或兼容路径,就不能笼统宣称“全部设备兼容”。对于无法升级相关组件的旧设备,它们也享受不到这次改造带来的直接安全收益。

核心 IPC 上线:灰度、监控和回滚

Binder 属于关键基础设施,验证、灰度和回退机制需要与代码迁移同步设计。合入前应完成针对事务一致性、引用生命周期和并发竞争的测试,以及模糊测试和异常路径测试,并通过构建开关或运行时开关控制新旧实现。

上线可先覆盖内部设备和少量目标机型,再逐步扩大范围。运行期间需要持续观察 Binder 事务失败率、ANR、系统崩溃、延迟分位数、CPU 与内存指标;在更新机制允许的情况下,可以使用 A/B 更新保留稳定版本。

旧实现及其稳定 ABI 不应在覆盖率尚未达标时过早删除。合入 Rust 代码也不是维护终点,unsafe 审查、依赖升级、生成代码一致性和后续漏洞修复都需要明确的责任人。

待核验的信息:官方范围、性能数据与 OEM 覆盖率

评估这次改造,需要核验三组信息。首先需要确认“1.1 万行”的统计口径,明确是毛删除量、净减少量还是迁移量,并区分 C、C++、生成代码和构建辅助文件。其次需要评估官方性能结论能否复现,并公开硬件、负载、基线和完整统计方法,而不是只引用百分比。最后必须确认 OEM 与旧设备覆盖范围,明确哪些机型已经同步更新用户态与内核,哪些仍依赖兼容层或厂商分支。

对开发者而言,这次改造展示了用类型、所有权和生命周期约束维护系统级 IPC 的可能性;对普通用户而言,它更可能表现为稳定性和安全性的间接改善,而不是新的应用接口。

Rust 改变的是漏洞出现的位置、审查方式和维护成本,并没有消除 Binder 作为跨进程通信系统所固有的复杂度。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容