Skip to content

Custom Daemon Domain

从真实 Android 17 daemon 规则出发,构建一个最小自定义进程域并验证 executable、数据、设备、属性和 IPC 权限。

基于android-17.0.0_r1
AndroidSELinuxdaemoninitvendor源码阅读

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 datadaemon 自己拥有哪些目录file type / dir/file
device node哪个 ioctl/read/write 真正需要dev_type / chr_file
property读还是写,property type 是什么property_contexts / property_service
IPCserver 还是 client,谁 add/find/callservice/hwservice/Binder
lifecycle谁 start、stop、reap、restartinit 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

text
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

text
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

text
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

text
vendor.my_daemon.        u:object_r:vendor_default_prop:s0

下面切换到 Binder service type;它由 ServiceManager 的名字查找和注册规则消费。

text
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

text
service my_daemon /vendor/bin/my_daemon
    class main
    user system
    group system

源码文件:system/core/init/service.cpp

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

text
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(目录标签模式参考)

text
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

text
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

text
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

text
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

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

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

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

make
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 静态与设备检查 ​

sh
# 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决策 ​

sh
# 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 字段验证预测。