Android 系统服务 SELinux 配置的正确姿势(设备专用目录)
对于你自己新增的系统服务(比如 TestService 通过 publishBinderService("testtld", ...) 发布的服务),不应该直接修改 system/sepolicy 目录 ,而应该放在你项目的 板级(device)目录 下,这样才不会污染 AOSP 原始代码,方便移植和维护。
1. 策略文件应该放在哪里?
路径格式为:
bash
device/<vendor>/<board>/sepolicy/
比如你的开发板是 KICKPI_3576,那么完整路径可能是:
bash
device/rockchip/rk3576/sepolicy/
2. 需要创建的文件和内容
在 device/<vendor>/<board>/sepolicy/ 下新建两个文件:
文件一:service.te
bash
type testtld_service, service_manager_type;
含义 :定义一个名为 testtld_service 的安全类型,并把它标记为"服务管理器能识别的类型"(service_manager_type)。
文件二:service_contexts
bash
testtld u:object_r:testtld_service:s0
含义 :当系统看到服务名为 testtld 的服务时,自动给它贴上 u:object_r:testtld_service:s0 这个安全标签。
注意 :testtld 这个名字必须和你在 Java 代码里 publishBinderService("testtld", ...) 用的名字完全一致。
3. 让编译系统找到你的策略文件
编辑 device/<vendor>/<board>/BoardConfig.mk,添加:
bash
BOARD_SEPOLICY_DIRS += device/<vendor>/<board>/sepolicy
这样 make 时会自动把你定义的文件合并到最终策略中。
详解 service_contexts 那一行配置
你看到的这一行:
bash
testtld u:object_r:testtld_service:s0
本质上是一张 "服务名 → 安全标签" 的对照表。SELinux 通过它来决定哪个安全类型管理着这个服务。
下面逐段拆解:
| 字段 | 值 | 说明 |
|---|---|---|
| 服务名 | testtld |
你在 publishBinderService("testtld", ...) 里起的名字,必须一模一样。 |
| 用户 | u |
SELinux 用户,Android 里固定为 u,照写即可。 |
| 角色 | object_r |
对于文件、服务这类"被动实体",固定用 object_r,照写即可。 |
| 类型 | testtld_service |
这是核心 。它就是我们刚刚在 service.te 里定义的自定义类型,用于给这个服务贴上唯一的"身份证"。 |
| 安全级别 | s0 |
多级安全(MLS)中的最低级别,Android 中普通服务都固定使用 s0,照写即可。 |
所以,这一行实际上是在说:
"名字叫 testtld 的服务,安全性上就交给 testtld_service 这个类型来管了。"
有了这行映射之后,我们才能在后续为 testtld_service 编写具体的访问规则(比如哪些进程可以找到它、使用它)。
附加部分:权限规则(按需添加)
一般情况下,system_server 注册和查找自己创建的服务不需要额外 allow 规则。如果后续你遇到 avc: denied 日志(比如某个 system_app 访问你的服务被拒绝),可以在 device/<vendor>/<board>/sepolicy/ 下新建对应的 .te 文件补充权限,例如:
system_app.te(如果服务只给系统应用使用):
bash
allow system_app testtld_service:service_manager find;
总结
-
设备专用策略别动
system/sepolicy,全放在device/<vendor>/<board>/sepolicy/ -
通过
service.te声明你的服务类型 -
通过
service_contexts把服务名和类型绑定 -
在
BoardConfig.mk中加入BOARD_SEPOLICY_DIRS让编译找到这些文件 -
service_contexts里那一长串不用怕,你只需要关心 服务名 和 类型 这两个自定义部分,其余照抄就行
这样你的 TestService 就会被 SELinux 正确识别,不会再被安全策略拦截了。
如何和framework交互
第一层:Framework 上层 ↔ TestService 系统服务
这一层是你自己定义的一个系统服务 ,别人通过 servicemanager 找到它。
| 位置 | 关键词 | 作用 |
|---|---|---|
TestService.java |
publishBinderService("testtld", ...) |
向 servicemanager 注册你的服务,名字固定为 testtld |
| 上层客户端代码 | ServiceManager.getService("testtld") |
通过相同的名字找到你的服务 |
service_contexts |
testtld u:object_r:testtld_service:s0 |
告诉 SELinux:servicemanager 中叫 testtld 的服务,必须贴上 testtld_service 这个安全标签 |
service.te |
type testtld_service, service_manager_type; |
声明 testtld_service 是一种"被 servicemanager 认可"的类型 |
| 客户端权限文件 | allow system_app testtld_service:service_manager find; |
允许 system_app 标签的进程通过 servicemanager 查找 testtld_service 标签的服务 |
所以这一层交互的关键词是:testtld 这个名字。
只要 Java 代码里的服务名和 SELinux 配置文件里的名字完全一致,系统启动时就能通过标签安全地匹配上。
第二层:TestService 系统服务 ↔ C++ HAL 服务
这一层是你写的 TestService 作为一个客户端,去调用真正的硬件抽象层(HAL)。
HAL 服务运行在独立的进程,通过 hwservicemanager 管理,和第一层的 servicemanager 是两套不同的机制。
| 位置 | 关键词 | 作用 |
|---|---|---|
TestService.java |
ServiceManager.waitForDeclaredService(IHelloTest.DESCRIPTOR + "/default") |
在 hwservicemanager 中查找 HAL 服务。 DESCRIPTOR 自动生成为 "android.hardware.testtld.IHelloTest" 加上实例后缀就变成 "android.hardware.testtld.IHelloTest/default" |
IHelloTest.aidl |
package android.hardware.testtld; 且接口名就是 IHelloTest |
这个完全决定了 DESCRIPTOR 的值。 |
| C++ HAL 服务注册代码 | 通常会调用类似 registerAsService("default") |
在 hwservicemanager 中注册为默认实例,实例名就是 "default" |
| C++ HAL 头文件 | 由 IHelloTest.h 自动生成 |
同样包含 descriptor 为 "android.hardware.testtld.IHelloTest",用于匹配 |
所以这一层交互的关键词是:IHelloTest.DESCRIPTOR + 实例名 default。
即 "android.hardware.testtld.IHelloTest/default" 这个完整字符串。
Java 用这个字符串去 hwservicemanager 查找,C++ HAL 服务也必须用完全相同的 descriptor 和实例名注册。
一句话理解整个交互
bash
上层应用
↓ 通过 servicemanager 查找 "testtld" 服务 (ITestHalManager 接口)
TestService.java
↓ 通过 hwservicemanager 查找 "android.hardware.testtld.IHelloTest/default" 服务 (IHelloTest 接口)
C++ HAL 服务
所有的连接点都是字符串:
-
"testtld"连接了 Framework 上层和你的 TestService -
"android.hardware.testtld.IHelloTest/default"连接了 TestService 和 C++ HAL -
SELinux 规则又分别根据这些字符串给服务打上安全标签,确保只有授权者能访问
你只要确保:
-
publishBinderService的名字 =service_contexts里的服务名 = 别人查找的名字 -
IHelloTest.DESCRIPTOR(由 AIDL 自动生成) = C++ 侧注册的 descriptor -
实例名
"default"与 C++ 侧注册的实例名一致
整个链路就完美联通了。没有哪一处是"自动"知道另一处的,全靠这些字符串对上号。