15、服务SELinux策略:service_contexts编写,服务启动与通信权限控制,Binder服务SELinux策略

好,咱们今天聊点硬核的。Binder 服务在 Android 系统里,就像人体的神经网络。它负责把各个模块串联起来,让 App 能调用系统服务,让系统服务之间也能互相通信。

但问题来了——如果这个神经网络被恶意攻击了怎么办?比如一个恶意 App 伪装成系统服务,窃取你的通讯录?

嗯,这就是 SELinux 要干的事。它给每个 Binder 服务都贴上了「身份证」和「通行证」。只有身份对、权限够的进程,才能访问对应的服务。

我个人习惯把这一章分成三块来讲:

  • 第一块:service_contexts 文件怎么写,它决定了服务叫什么名字、属于哪个安全上下文。
  • 第二块:服务启动时,SELinux 怎么检查权限,怎么控制通信。
  • 第三块:Binder 通信的 SELinux 策略,这是最容易被忽略但最容易出坑的地方。

15.1 service_contexts 文件:服务的「身份证」

先问一个问题:当系统服务 manager(比如 servicemanager)收到一个请求,说「我要找 activity」——它怎么知道这个「activity」服务是谁?

答案就在 service_contexts 文件里。这个文件定义了一个映射表:服务名称 → SELinux 上下文。

举个例子,在 Android 10 以上的设备上,你可以在 /system/etc/selinux/ 目录下找到它。内容大概长这样:

# service_contexts 示例
activity                                u:object_r:activity_service:s0
package                                 u:object_r:package_service:s0
window                                  u:object_r:window_service:s0
batterystats                            u:object_r:batterystats_service:s0

每一行左边是服务注册时的名字,右边是它的 SELinux 安全上下文。注意看,这里的类型都是 _service 结尾。这是我个人习惯的命名方式,方便一眼看出这是 Binder 服务。

核心要点:service_contexts 文件是 servicemanager 进行访问控制的第一道关卡。当一个进程尝试获取某个服务时,servicemanager 会先查这个文件,拿到目标服务的上下文,然后检查发起者是否有权限。

我在项目中遇到过一个问题:有个第三方服务注册时名字写错了大小写,结果 service_contexts 里匹配不上,servicemanager 直接返回 null。排查了半天,最后发现是大小写敏感的问题。嗯,这种坑踩过一次就不会忘了。

15.2 服务启动与通信权限控制

服务启动时,SELinux 会做两件事:

  1. 检查启动者是否有权限向 servicemanager 注册该服务。
  2. 检查服务进程自身的域(domain)是否允许它运行。

说白了,就是「谁生的孩子谁负责」。系统服务通常由 init 进程启动,它的域是 init。如果 init 想启动一个 system_server 服务,SELinux 会检查 init 域是否有权限 transition 到 system_server 域。

对应的策略写法是这样的:

# 允许 init 域 transition 到 system_server 域
type_transition init system_server_exec:process system_server;

# 允许 system_server 域向 servicemanager 注册服务
allow system_server servicemanager:binder { transfer call };
allow system_server activity_service:service_manager { add };

你想想看,如果没有第二行 allow 规则,就算 system_server 进程跑起来了,它也没法把自己的服务注册到 servicemanager 里。系统会直接报 Permission denied,然后服务注册失败。

避坑指南:我曾经在调试一个系统服务时,发现服务进程已经起来了,但就是注册不到 servicemanager。查了半天的 log,发现 avc 拒绝信息里写着 denied { add } for service=my_service scontext=u:r:my_service:s0 tcontext=u:object_r:my_service_service:s0 tclass=service_manager。加上对应的 allow 规则后,问题解决。

15.3 Binder 服务 SELinux 策略

Binder 通信的 SELinux 策略,其实比 service_manager 的检查更细粒度。它分为三个层面:

层面 检查内容 对应策略类
Binder 节点 是否允许打开/使用 binder 设备 binder
Binder 调用 是否允许跨进程调用(call/transfer) binder
服务查找 是否允许查找/获取某个服务 service_manager

举个例子,假设 App 进程(域为 untrusted_app)想调用 activity_service

# 第一步:允许 untrusted_app 使用 binder 设备
allow untrusted_app binder_device:chr_file { open read write ioctl };

# 第二步:允许 untrusted_app 向 servicemanager 查询 activity_service
allow untrusted_app servicemanager:binder { call };
allow untrusted_app activity_service:service_manager { find };

# 第三步:允许 untrusted_app 与 activity_service 进行 binder 通信
allow untrusted_app activity_service:binder { call transfer };

这三步缺一不可。我曾经见过有人只加了 service_manager find 权限,但忘了加 binder call 权限,结果 App 能查到服务,但一调用就崩溃。log 里报的是 Binder transaction failed,很容易让人误以为是 Binder 驱动的问题。

注意:Binder 通信的权限检查是双向的。不仅调用方需要权限,服务方也需要权限来响应调用。比如服务方需要 allow activity_service untrusted_app:binder { call transfer }; 才能接受来自 untrusted_app 的请求。

15.4 实战:编写一个自定义服务的 SELinux 策略

好,理论说完了,咱们来点实际的。假设我要添加一个名为 my_health_service 的系统服务,步骤如下:

  1. 定义服务类型:在 service_contexts 中添加一行 my_health u:object_r:my_health_service:s0
  2. 定义服务进程域:在 my_health.te 中定义 type my_health, domain;type my_health_exec, exec_type, file_type;
  3. 注册权限allow my_health servicemanager:binder { call transfer };allow my_health my_health_service:service_manager { add };
  4. 调用权限:根据调用方的域,添加对应的 findbinder call 权限。

这里我画了一张流程图,帮你理清整个权限检查的链路:

Binder 服务 SELinux 权限检查流程 调用方进程 例如: untrusted_app ① 查询服务 ServiceManager 检查 service_manager:find ② 返回 Binder 引用 service_contexts 服务名 → 安全上下文 例如: my_health → my_health_service ③ 发起 Binder 调用 Binder 驱动 检查 binder:call / transfer ④ 传递调用 目标服务进程 例如: my_health_service ⑤ 服务端检查 服务端权限检查 allow 服务域 调用方域:binder { call transfer } 调用方 ServiceManager Binder驱动 目标服务

从这张图你可以看到,一次 Binder 服务调用,要经过至少 5 次 SELinux 权限检查。任何一个环节的权限缺失,都会导致调用失败。

我的调试习惯:遇到 Binder 通信权限问题时,先看 dmesg | grep avc 或者 logcat -b events | grep avc。avc 拒绝信息会明确告诉你:哪个进程(scontext)、想访问哪个服务(tcontext)、需要什么权限(tclass 和 requested permission)。照着这个信息去补策略,基本一次搞定。

15.5 总结

好了,这一章的内容就这些。核心就三句话:

  • service_contexts 是服务的「身份证」,servicemanager 靠它做第一道检查。
  • 服务启动和注册 需要 service_manager:add 权限,别忘了。
  • Binder 通信 需要双向权限:调用方要有 binder:call,服务方也要有对应的 binder:calltransfer

嗯,这些规则看起来繁琐,但只要你理解了 SELinux 的「最小权限」原则,写起来其实有章可循。下次遇到 Binder 通信失败,别急着怀疑代码逻辑,先查查 avc log——八成是策略没写全。


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