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通信。我们做了三件事:

  1. 收紧system_server的binder_call权限:只允许预置的系统APP调用system_server的敏感接口。第三方APP只能调用公开的Service。
  2. 限制binder_transfer:默认禁止所有domain之间传递Binder对象。只有明确需要传递的场景才开白名单。
  3. 增加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漏洞缓解的整个体系串起来:

Binder漏洞缓解知识体系 补丁策略 及时止损 驱动加固 增强韧性 SELinux策略 强制访问控制 补丁策略要点 • P0/P1/P2优先级划分 • 24小时内响应紧急漏洞 • 合入前做代码审查 • 压力测试验证性能 • 注意OEM定制代码冲突 • 月度安全更新机制 驱动加固要点 • 内核配置加固 • 边界检查双重校验 • 引用计数保护(UAF) • 细粒度锁优化 • binderfs虚拟文件系统 • 调试断言(BUG_ON) SELinux策略要点 • binder_call/transfer/service • 最小权限原则 • 收紧system_server权限 • 限制binder_transfer • neverallow规则 • 白名单机制 纵深防御:补丁 + 驱动 + SELinux = 三位一体

你看,这三层防御是层层递进的。补丁策略解决已知漏洞,驱动加固提升攻击门槛,SELinux提供兜底保护。少了任何一层,防御体系都有缺口。

21.5 总结与建议

最后,我分享几个实战中的经验:

  • 别迷信单一防御:SELinux再强,也防不住内核态的直接内存访问。驱动加固再硬,也挡不住0day漏洞。三者必须配合使用。
  • 自动化测试是关键:我每次改完SELinux策略,都会跑一遍selinux-testsuite。改完驱动,会跑binder_benchmarkbinder_stress。没有自动化测试,你根本不知道改坏了什么。
  • 关注上游社区:Binder驱动的维护者(比如Todd Kjos)经常在LKML上讨论安全问题。我习惯每周扫一遍邮件列表,提前了解潜在风险。

一句话总结:Binder安全没有捷径。老老实实打补丁、扎扎实实做加固、认认真真写策略——这才是最有效的“捷径”。


公众号:蓝海资料掘金营,微信deep3321