LWN:安卓Binder的Rust实现!

关注了就能看到更多这么棒的文章哦~

A Rust implementation of Android's Binder

By Jonathan Corbet
November 30, 2023
LPC
ChatGPT translation
https://lwn.net/Articles/953116/

Android 系统以前曾经因其众多在内核代码树外的改动而知名。多年来,许多这类改动已经被消除或推送到了 upstream,使 Android 更加接近主线内核。在“已经推送到 upstream”这个类别中的一个重要组件是 Binder,这是一种仅被 Android 使用的进程间通信机制。有许多因素使 Binder 成为用 Rust 语言中重写的一个很好的选项;在2023 Linux Plumbers Conference上,Carlos Llamas 和 Alice Ryhl 描述了在 Rust 中重写 Binder 的动机和实现。

a95577e853500415e4d055320077444b.png

Llamas 在演讲中开场时把 Binder 称作为 Android 系统的主要进程间通信渠道。它有着悠久的历史,起源于BeOS,并在被 Android 采用之前就用于Palm OS。Binder 驱动程序位于内核中;大多数应用程序通过 libbinder 库访问它。Binder 服务实现了一个能够并行处理来自客户端的请求的线程池;内核和客户端进程之间的事务使用内存映射区域。

Binder 的许多特性使其对 Android 很有吸引力。它在内核中根本不进行任何缓冲(buffering)。它有一整套通过 ioctl() 访问的操作命令,包括了一个组合了读/写的操作命令。Binder 实现了优先级继承和共享文件描述符的能力;它具有许多远程对象管理功能和强大的引用计数实现。

鉴于 Binder 有着悠久的历史,为什么要现在重写它呢?Llamas 表示,该模块具有高度的复杂性和大量的技术欠债;这两者的组合已经证明是导致安全问题的常见原因。分析显示,Binder 每千行代码包含约 3.1 个漏洞,这是一个相对较高的数字—而且情况并没有好转。已知漏洞中约有一半已经有见到过被利用过。Android 中最低特权的沙盒也可以访问 Binder,那么这种安全记录确实令人担忧。

Llamas 表示,在 Binder 中有很多可以改进的地方。有的函数几乎有 1000 行长(示例)这通常是一个不好的迹象。Binder 中的错误处理是“有问题的”,导致了许多 bug。但对于这个复杂的代码库的任何改进往往都具有风险,会导致大量的“fix to the fix to the fix”补丁。Android 开发人员已经厌倦了调试这类 bug,因此正在寻找更好的方式。

新的 Binder

然后,Ryhl 接替他讲解了新的 Binder 实现,该实现于 11 月初发布到邮件列表. Ryhl 表示,在 Rust 中工作的一个重要优势是它可以让对象所有权在类型系统中可见。例如,在使用引用计数对象的 C 代码中,必须在运行时检查引用计数是否非零,但 Rust 代码如果缺少需要的对象引用就根本无法编译成功。在 C 中,可以查看一个充满函数指针的结构,却无法得知其中哪些会改写引用计数;而在 Rust 中,可以通过使用不同的类型来明确这一点。

Rust 还能确保在错误路径中不会忘记清理。演讲的幻灯片指向了C实现中的一些错误处理代码,它的实现方式是在函数末尾写一长串 goto 目标位置;而 Rust 中的相应实现则是结束函数的"}"。Rust 还积极阻止了在 Binder 中发现的几乎所有漏洞;其中超过一半是使用后释放(use-after-free)的错误,这在 Rust 中“永远不会发生”。其他类型的漏洞,如越界数组访问则会在运行时捕获,将可能会发生的攻击转变为拒绝服务问题,也就大大减轻了危险程度。

e37ca26c60f9feea0485b2ed447c8255.png

因此,Ryhl 在 Rust 中重新编写了 Binder。已经实现了 C 实现所提供的所有功能,并通过了整个 Binder test suite 测试;已经能够启动和运行 Android 设备。 Ryhl 展示了一部运行 Rust Binder 实现的手机作为证明,引起了掌声。性能与 C 版本一样好;这是“经过一些优化”,才达到了这一点,但 C 版本也经历了相当多的优化工作。

她表示,Rust 可能比 C 更冗长;代码中必须写清楚许多不变性(invariants)。但由于有较少的错误处理代码,可以抵消掉;最终实现中两个方案的代码大小大致相同。

关于不安全代码(unsafe code),她说内核中总会需要一些不安全的代码,但 Binder 实现需要的不多,主要用于与(仍然是 C 的)binderfs 实现进行交互。与内核的其余部分进行交互的抽象通常涉及一些不安全代码;工作队列的抽象(Ryhl为6.7上游化)包含相当多的不安全代码。但那是一个单一的模块,将在使用工作队列的所有驱动程序之间共享;只需要正确实现一遍就好。

在演讲结束时,她表示,使用 Rust 并不能保证不再出现漏洞;在会议期间在 Rust 实现中发现了一个漏洞。结果发现 C 实现也包含一个类似的错误。在 C 中,该错误可以被利用来造成 use-after-free 的漏洞,这会导致“游戏结束”。在 Rust 中,没有导致 use-after-free,但可能会导致用来映射消息的 mapping 对不上了,这仍然是一个严重的漏洞,尽管更难以利用。她总结说,使用 Rust 可以防止内存安全问题,但逻辑错误仍然存在。

在演讲后的问答环节中,Julia Lawall 询问了 Rust 代码需要哪些优化。Ryhl 表示,性能瓶颈来源之一,是调用 C 语言实现的那些非常小的函数。通过在 Rust 中重写这些函数可以提高性能,但这会导致代码重复以及引发由此而来的可维护性问题。

也有几个问题是关于相关抽象以及跟它们跟 C 代码的关系。一个与会者问,在实现 Binder 的过程中,内核的 C 代码的改动是否迫使需要修改抽象。回答说跟 process freezing 相关的一个改动引起了一些问题。然后我问了关于工作队列(workqueue)抽象的问题;如果有人对(C)工作队列实现进行了不兼容的更改,谁负责使抽象再次可用?对此的答案并不完全清晰;最终,内核通常遵循的“你搞砸了,你负责修复”规则可能会适用,但预计许多开发人员在相当长的时间内缺乏更新这些抽象所需的 Rust 开发能力,因此将需要 Rust-for-Linux 开发人员的帮助。

[感谢 Linux 基金会,LWN 的旅行赞助商,支持我们参加此次活动。]

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

format,png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值