21、Binder漏洞利用缓解:Android安全补丁策略、Binder驱动加固、SELinux策略强化
好,咱们进入第二十一节。前面讲了那么多Binder漏洞怎么挖、怎么利用,这节课咱们聊聊怎么防。说白了,就是怎么给Binder这个“高速公路”装上护栏、配上交警、再搞个黑名单系统。
我这些年做安全加固,最深的体会就是:没有银弹。单一防御手段总会被绕过,必须搞纵深防御。Binder这块尤其如此——它太核心了,任何一个小疏漏都可能被放大成提权漏洞。
核心思路:Binder安全防护 = 补丁策略(及时止损)+ 驱动加固(增强韧性)+ SELinux(强制访问控制)。三者缺一不可。
21.1 Android安全补丁策略:从源头堵漏
先说说补丁。你可能觉得补丁这事儿没啥技术含量,不就是等Google发公告、合入代码吗?嗯,实际操作起来坑不少。
我记得有一次,某个客户的安全团队拿着CVE列表问我:“这些Binder漏洞我们是不是都得修?”我一看,好家伙,几十个。但仔细分析后发现,有些漏洞的利用条件极其苛刻,有些则已经被SELinux策略挡住了。所以我的建议是:补丁要打,但得排优先级。
21.1.1 补丁优先级评估
我个人习惯把Binder相关补丁分成三类:
| 优先级 | 漏洞特征 | 典型CVE示例 | 响应时间 |
|---|---|---|---|
| P0(紧急) | 无需权限即可触发、影响system_server或内核 | CVE-2019-2215(Binder UAF) | 24小时内合入 |
| P1(高) | 需要普通APP权限、可提权至system | CVE-2020-0041(Binder整数溢出) | 1周内合入 |
| P2(中) | 需要特定条件或已受SELinux限制 | CVE-2016-5340(Binder驱动信息泄露) | 月度更新 |
你可能会问:“P2的漏洞就不管了?”不是不管,而是可以等月度安全更新一起修。因为如果SELinux已经挡住了攻击路径,你就有时间做更充分的测试。
21.1.2 补丁合入的坑
注意:千万不要直接合入AOSP的补丁就完事。很多OEM厂商改了内核代码,补丁可能冲突。我曾经遇到过合入补丁后Binder性能下降30%的情况——因为补丁里有个锁的改动和我们自己的优化冲突了。
我的做法是:
- 先做代码审查,理解补丁到底改了啥
- 然后在测试机上跑Binder压力测试(比如同时开100个线程做IPC)
- 最后才合入正式分支
21.2 Binder驱动加固:内核层的硬防护
补丁是事后补救,驱动加固才是主动防御。说白了,就是让Binder驱动本身变得更“抗揍”。
21.2.1 内核配置加固
先看几个关键的内核配置项:
# 开启Binder安全检查
CONFIG_ANDROID_BINDER_IPC=y
CONFIG_ANDROID_BINDERFS=y
# 开启内核地址随机化(让漏洞利用更难)
CONFIG_RANDOMIZE_BASE=y
# 开启SLAB_FREELIST_RANDOM(防止堆喷)
CONFIG_SLAB_FREELIST_RANDOM=y
# 开启用户态指针校验
CONFIG_STRICT_USERCOPY_CHECKS=y
嗯,这里要注意:CONFIG_ANDROID_BINDERFS 是Android 8.0之后引入的,它把Binder设备节点从/dev/移到了binderfs虚拟文件系统。这样做的好处是——你可以用挂载选项控制谁能访问binderfs,而不是依赖文件权限。
21.2.2 驱动代码层面的加固
我在做内核驱动开发时,最常加的加固手段是这几条:
- 边界检查强化:所有从用户态传来的size、offset,必须做双重校验。不光在入口处检查,在内部函数也要再查一次——防止TOCTOU(Time-of-Check Time-of-Use)攻击。
- 引用计数保护:Binder对象有复杂的引用关系。我习惯在每次操作binder_node或binder_ref时,用
atomic_inc_not_zero而不是atomic_inc——这样能防止UAF。 - 锁的粒度优化:Binder驱动早期用一把大锁(binder_main_lock),性能差还容易死锁。现在改成细粒度锁,每个proc有自己的锁。但要注意——锁顺序必须一致,否则会死锁。
小技巧:我写Binder驱动代码时,会在每个可能出错的路径上加BUG_ON()或WARN_ON()。虽然会影响性能,但在调试阶段能快速定位问题。上线前再关掉这些断言。
21.3 SELinux策略强化:最后的防线
说实话,SELinux才是Binder安全里最让我头疼的部分。策略文件动辄上万行,写错一条就可能让系统起不来。但它的威力也是最大的——即使内核有漏洞,SELinux也能拦住攻击。
21.3.1 Binder相关的SELinux核心概念
先理清几个关键点:
- binder_call:允许一个domain调用另一个domain的Binder服务
- binder_transfer:允许一个domain把Binder对象传给另一个domain
- binder_service:允许一个domain注册为Binder服务
举个例子,假设我们想让my_app这个domain能调用system_server的服务:
# 允许my_app调用system_server的Binder服务
allow my_app system_server:binder { call };
# 允许system_server向my_app传递Binder对象
allow system_server my_app:binder { transfer };
# 允许my_app注册Binder服务(一般只给系统服务用)
allow my_app self:binder { service };
你可能会问:“为什么不让所有APP都能调用所有服务?”嗯,这就是SELinux的精髓——最小权限原则。每个APP只能调用它真正需要的服务。
21.3.2 实战中的SELinux策略强化
我参与过一个项目,客户要求加固Binder通信。我们做了三件事:
- 收紧system_server的binder_call权限:只允许预置的系统APP调用system_server的敏感接口。第三方APP只能调用公开的Service。
- 限制binder_transfer:默认禁止所有domain之间传递Binder对象。只有明确需要传递的场景才开白名单。
- 增加neverallow规则:防止某些高危domain(如untrusted_app)获得不该有的权限。
# 禁止untrusted_app调用system_server的敏感服务
neverallow untrusted_app system_server:binder { call };
# 禁止任何domain向untrusted_app传递Binder对象
neverallow { domain -untrusted_app } untrusted_app:binder { transfer };
避坑指南:我曾经在neverallow规则里写漏了一个domain,结果导致系统服务无法启动。排查了两天才发现——原来system_server自己也需要向自己传递Binder对象。所以写neverallow时,一定要把例外domain列全。
21.4 知识体系总览
说了这么多,咱们用一张图把Binder漏洞缓解的整个体系串起来:
你看,这三层防御是层层递进的。补丁策略解决已知漏洞,驱动加固提升攻击门槛,SELinux提供兜底保护。少了任何一层,防御体系都有缺口。
21.5 总结与建议
最后,我分享几个实战中的经验:
- 别迷信单一防御:SELinux再强,也防不住内核态的直接内存访问。驱动加固再硬,也挡不住0day漏洞。三者必须配合使用。
- 自动化测试是关键:我每次改完SELinux策略,都会跑一遍
selinux-testsuite。改完驱动,会跑binder_benchmark和binder_stress。没有自动化测试,你根本不知道改坏了什么。 - 关注上游社区:Binder驱动的维护者(比如Todd Kjos)经常在LKML上讨论安全问题。我习惯每周扫一遍邮件列表,提前了解潜在风险。
一句话总结:Binder安全没有捷径。老老实实打补丁、扎扎实实做加固、认认真真写策略——这才是最有效的“捷径”。
公众号:蓝海资料掘金营,微信deep3321