Custom Daemon Domain
本文面向已经读过 Init Domain、Vendor Domain、HAL Domain Template 和 AVC 日志 的读者。本文不是一份可以直接复制到产品的万能 .te 模板,而是沿 Android 17 已有的 rild、hal_audio_default 和 init_daemon_domain 源码,展示新增 daemon 时必须建立的对象、调用链和验证证据。
示例使用 vendor daemon 名称 my_daemon,但所有通用规则都来自 AOSP 真实宏和真实 daemon。硬件节点、属性、Binder service 的具体 type 必须替换成产品已有或新声明的类型;不能因为进程叫 vendor daemon 就给它 vendor_file_type 的通配读写。
1. 先定边界
1.1 资源清单
在写 policy 前,把 daemon 的入口和消费者列出来:
| 资源 | 需要回答的问题 | 对应 contexts/class |
|---|---|---|
| executable | 谁启动、文件放哪一分区 | file_contexts / process transition |
| runtime data | daemon 自己拥有哪些目录 | file type / dir/file |
| device node | 哪个 ioctl/read/write 真正需要 | dev_type / chr_file |
| property | 读还是写,property type 是什么 | property_contexts / property_service |
| IPC | server 还是 client,谁 add/find/call | service/hwservice/Binder |
| lifecycle | 谁 start、stop、reap、restart | init Service |
1.2 最小拓扑
图中每条边都需要独立证据:*_exec 证明 init 能启动,data/device/property/service type 证明 daemon 运行后的消费者权限,AVC 和测试只验证实际走到的路径。
1.3 AOSP参照
源码文件:system/sepolicy/vendor/rild.te、system/sepolicy/vendor/hal_audio_default.te
type rild, domain;
hal_server_domain(rild, hal_telephony)
net_domain(rild)
type rild_exec, exec_type, vendor_file_type, file_type;
init_daemon_domain(rild)
type hal_audio_default, domain;
hal_server_domain(hal_audio_default, hal_audio)
type hal_audio_default_exec, exec_type, vendor_file_type, file_type;
init_daemon_domain(hal_audio_default)
hal_client_domain(hal_audio_default, hal_allocator)这两个例子说明最小 daemon 骨架只负责声明 domain、exec type、init transition 和接口角色;具体硬件、属性、Binder、数据文件规则分布在对应 private policy 中。
2. 类型声明
2.1 进程与入口
源码文件:system/sepolicy/vendor/rild.te
type my_daemon, domain;
type my_daemon_exec, exec_type, vendor_file_type, file_type;
init_daemon_domain(my_daemon)my_daemon 是 process domain,my_daemon_exec 是 executable file type。vendor_file_type 只表达文件属于 vendor 文件集合;exec_type 使它能作为 domain transition 的入口。两者缺一,init 可能无法计算或执行 transition。
2.2 数据与设备
源码文件:system/sepolicy/vendor/hal_audio_default.te、system/sepolicy/vendor/file.te
type my_daemon_data_file, file_type, data_file_type;
type my_hw_device, dev_type;数据目录 type 与设备节点 type 是不同 object class。data file 需要 dir/file/lnk_file 权限,设备需要 chr_file 的实际读写和 ioctl;不要用同一 type 覆盖两个对象。
2.3 属性与服务
源码文件:system/sepolicy/private/property_contexts、system/sepolicy/public/service.te
vendor.my_daemon. u:object_r:vendor_default_prop:s0下面切换到 Binder service type;它由 ServiceManager 的名字查找和注册规则消费。
type my_daemon_service, service_manager_type;property_contexts 的 vendor.my_daemon. 只是把属性名映射到 target type;daemon 是否能 set/get 仍要在 policy 中调用 set_prop/get_prop。Binder service type 需要 service_manager_type,若是 HAL/HwBinder 则应使用相应 hal_*_service/hal_*_hwservice type 和 attribute 宏。
3. 启动链路
3.1 rc service
源码文件:system/core/rootdir/init.rc、system/core/init/service.cpp
service my_daemon /vendor/bin/my_daemon
class main
user system
group system源码文件:system/core/init/service.cpp
std::string scon;
if (!seclabel_.empty()) {
scon = seclabel_;
} else {
auto result = ComputeContextFromExecutable(args_[0]);
if (!result.ok()) {
return result.error();
}
scon = *result;
}未写 seclabel 时,init 读取 executable file context 并调用 security_compute_create;写了显式 seclabel 时,rc 值优先。产品通常应让 init_daemon_domain(my_daemon) 提供自动 transition,而不是在 rc 中硬编码一个不受 policy 验证的 context。
3.2 宏展开
源码文件:system/sepolicy/public/te_macros
define(`domain_auto_trans', `
domain_trans($1,$2,$3)
type_transition $1 $2:process $3;
')
define(`domain_trans', `
allow $1 $2:file { getattr open read execute map };
allow $1 $3:process transition;
allow $3 $2:file { entrypoint open read execute getattr map };
ifelse($1, `init', `', `allow $3 $1:process sigchld;')
dontaudit $1 $3:process noatsecure;
allow $1 $3:process { siginh rlimitinh };
')对 my_daemon 展开后,init 可以执行 my_daemon_exec,内核可以把 process 切到 my_daemon,新域拥有 entrypoint;因为 old domain 是 init,宏不会额外生成 daemon → init 的 SIGCHLD 规则。不要把宏的“默认转换”误认为 rc service 已经启动成功。
3.3 实际生效顺序
4. 最小权限
4.1 数据目录
源码文件:system/sepolicy/private/installd.te(目录标签模式参考)
allow my_daemon my_daemon_data_file:dir create_dir_perms;
allow my_daemon my_daemon_data_file:file create_file_perms;
allow my_daemon my_daemon_data_file:lnk_file create_file_perms;这些规则只覆盖 daemon 自己的 data type;目录实际创建还需要 file_contexts 路径规则、父目录 search/write 和 Unix owner/mode。若 daemon 只读配置,应写 r_dir_file(my_daemon, my_daemon_data_file),不要直接使用 create_file_perms。
4.2 设备节点
源码文件:system/sepolicy/private/hal_audio.te
allow my_daemon my_hw_device:chr_file { open read write ioctl getattr };真实 permission 应由 open/read/write/ioctl 的调用点决定;如果 denial 显示特定 ioctl,应使用 allowxperm 限定命令集合,而不是扩大整个 chr_file。设备节点的 file_contexts 和 ueventd 权限也要分别核对。
4.3 属性
源码文件:system/sepolicy/private/vendor_init.te、system/sepolicy/private/property.te
set_prop(my_daemon, vendor_default_prop)
get_prop(my_daemon, vendor_default_prop)属性访问必须与 property_contexts 的 target type 一致。若只需要读取状态,只保留 get_prop;如果由 init 代写属性,daemon 不应拥有 set_prop。
4.4 Binder服务
源码文件:system/sepolicy/public/te_macros、system/sepolicy/private/hal_audio.te
binder_use(my_daemon)
binder_service(my_daemon)
add_service(my_daemon, my_daemon_service)
binder_call(my_daemon, system_server)binder_service/add_service 解决注册,binder_call 解决事务和 fd,ServiceManager 的 client find 还必须允许目标 service type。若 daemon 是 HAL server,应改用 hal_server_domain 与 hal_attribute_service/hal_attribute_hwservice,不要同时随意暴露普通 service 和 HAL service。
5. 生命周期
5.1 fork与cgroup
源码文件:system/core/init/service.cpp
if (pid == 0) {
umask(077);
cgroups_activated.CloseWriteFd();
setsid_finished.CloseReadFd();
RunService(descriptors, std::move(cgroups_activated),
std::move(setsid_finished));
_exit(127);
} else {
cgroups_activated.CloseReadFd();
setsid_finished.CloseWriteFd();
}init 用两个 FIFO 处理 parent/child 协调,child 在 RunService 中设置 namespace、descriptor、uid/gid/context 后 exec。父 Service 持有 pid 和 running 状态;daemon 自己不拥有 init 的重启状态。
5.2 延迟与重启
源码文件:system/core/init/service.cpp
if (is_updatable() && !IsDefaultMountNamespaceReady()) {
ServiceList::GetInstance().DelayService(*this);
return Error() << "Cannot start an updatable service before configs are loaded. "
<< "Queued for execution.";
}
if (pid_) {
LOG(INFO) << "service '" << name_
<< "' requested start, but it is already running";
return {};
}updatable service 的延迟是 mount namespace 条件;重复 start 是 Service 状态判断。二者都发生在 fork 前,和 SELinux transition denial 不是同一错误。
5.3 清理
源码文件:system/core/init/service.cpp
if (!ExpandArgsAndExecv(args_, sigstop_)) {
PLOG(ERROR) << "cannot execv('" << args_[0] << "')";
}execv 失败时 child 返回错误,父 init 通过 SIGCHLD/reap 更新状态并按 class/restart/oneshot 处理。setexeccon 或 context 计算失败则更早终止,daemon 主函数不会运行。
6. 构建与排障
6.1 文件组可见性
源码文件:system/sepolicy/Android.bp
vendor_policy = [
":se_build_files{.plat_vendor}",
":se_build_files{.vendor}",
":ashmem_build_file",
];
se_policy_conf {
name: "vendor_sepolicy.conf",
srcs: plat_public_policy +
system_ext_public_policy +
product_public_policy +
reqd_mask_policy +
vendor_policy,
vendor: true,
installable: false,
}vendor daemon 的 type 若放错目录,可能根本不进入目标 policy.conf;但它仍可能在源码目录里看起来“存在”。先检查 filegroup 输入,再检查最终 CIL 和安装镜像。
6.2 静态与设备检查
# Confirm executable and data labels.
adb shell 'ls -Z /vendor/bin/my_daemon /data/vendor/my_daemon'
# Confirm process domain and parent.
adb shell 'ps -AZ | grep my_daemon'
# Query the exact classes used by the daemon.
sesearch -A -s my_daemon -c file
sesearch -A -s my_daemon -c chr_file
sesearch -A -s my_daemon -c property_service
sesearch -A -s my_daemon -c binder输入是路径、进程和最终 policy;输出分别回答 file type、process scontext 和 class/permission 是否存在。sesearch 证明策略规则存在,不证明 daemon 代码已经执行到该访问。
6.3 AVC决策
# Extract the source, target, class and permission from denials.
adb shell 'dmesg | grep "avc: denied" | grep my_daemon'
# Check init errors before changing allow rules.
adb shell 'logcat -b all | grep -E "no domain transition|cannot execv|setexeccon"'
# Build the policy after every source/type change.
m my_daemon vendor_sepolicy.cil若 source 不是 u:r:my_daemon:s0,先修 executable label/transition;若 source 正确但 target/class/permission 被拒绝,再补最小规则;若没有 AVC 而 service 不存在,先查 rc、path、uid/gid 和父进程状态。
6.4 测试边界
system/core/init/service_test.cpp 适合验证 rc parser、seclabel 保存和临时 service 参数;system/sepolicy/tests 与 sepolicy-analyze 适合验证 neverallow/策略结构;真实设备测试才可证明 daemon 能否打开特定节点、注册服务和完成业务协议。三类证据不能互相替代。
7. 学习练习
给定一个新 daemon 的 denial,按以下顺序复述:rc service → executable file type → init_daemon_domain transition → process scontext → target type/class/permission → 业务消费者。再分别删除 executable label、transition、data allow 和 Binder find,预测错误会出现在哪个阶段。最后用 ls -Z、ps -AZ、sesearch 和 AVC 字段验证预测。
