Kernel Domain
本文面向已经读过 Init Domain、Genfs Contexts 和 内核加载流程 的读者。本文聚焦 Android 17 中 kernel domain 的特殊生命周期:它如何由 initial_sid_contexts 标识,如何在 first-stage init 尚未切换到 init 时读取策略、重标记设备并支持 Virtual A/B,最后如何通过 /init 的 exec 进入 init domain。
这里的 kernel domain 不是“所有内核代码共享的普通进程域”,也不是一个可以被其他 domain 切入的目标。它主要描述启动阶段的初始 task 和部分内核线程发起的访问;kernel 对 proc、sysfs、设备、loop/APEX 的访问与普通用户进程的 file label 仍是不同的安全对象。
1. 初始身份
1.1 类型声明
源码文件:system/sepolicy/public/kernel.te、system/sepolicy/private/kernel.te
type kernel, domain, mlstrustedsubject;
typeattribute kernel coredomain;kernel 具有 domain 属性,因此可以作为 allow/neverallow 的 source 或 target;mlstrustedsubject 和 coredomain 是平台策略属性。它没有通过 init service 或 exec 进入,而是由 SELinux 的 initial SID 机制赋予启动 task。
1.2 initial SID
源码文件:system/sepolicy/private/initial_sid_contexts
sid kernel u:r:kernel:s0initial SID 由策略加载和内核 SELinux 初始化共同解释。它不是 file_contexts 对某个路径的匹配结果;/init 的文件标签在后续 restorecon 中单独得到 init_exec。
1.3 不可切入
源码文件:system/sepolicy/private/kernel.te
neverallow * kernel:process { transition dyntransition };
neverallow * kernel:process ptrace;
neverallow kernel *:file { entrypoint execute_no_trans };
neverallow kernel self:global_capability_class_set {
dac_override dac_read_search
};这些规则表达四个不变量:没有 domain 可以 transition/dyntransition 到 kernel;kernel 不能作为普通 executable entrypoint;kernel 不能无 transition 执行任意文件;内核访问用户文件时不能依赖 DAC override。若出现 kernel 的 execute_no_trans denial,优先寻找缺少专用 domain 的 usermode helper,而不是给 kernel 放行。
2. 策略加载窗口
2.1 first-stage入口
源码文件:system/core/init/selinux.cpp
int SetupSelinux(char** argv) {
SelinuxSetupKernelLogging();
LoadSelinuxPolicyAndroid();
SelinuxSetEnforcement();
if (selinux_android_restorecon("/system/bin/init", 0) == -1) {
PLOG(FATAL) << "restorecon failed of /system/bin/init failed";
}
const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage", nullptr};
execv(path, const_cast<char**>(args));
PLOG(FATAL) << "execv(\"" << path << "\") failed";
return 1;
}SetupSelinux 仍在 kernel domain 时加载策略、设置 enforcing、给 /system/bin/init restorecon,随后 exec 第二阶段。此时 init 的 process domain 还没有生效,所以 kernel.te 中的临时 allow 是启动闭环的一部分。
2.2 Virtual A/B
源码文件:system/core/init/selinux.cpp、system/sepolicy/private/kernel.te
// Read policy before killing snapuserd, then load it without audits.
std::string policy;
ReadPolicy(&policy);
auto snapuserd_helper = SnapuserdSelinuxHelper::CreateIfNeeded();
if (snapuserd_helper) {
snapuserd_helper->StartTransition();
}
LoadSelinuxPolicy(policy);
if (snapuserd_helper) {
snapuserd_helper->FinishTransition();
snapuserd_helper = nullptr;
}上面的 C++ 代码描述 snapuserd handoff 的时序;下面的策略片段是这个 handoff 使用的具体 transition 和 relabel 权限。
domain_auto_trans(kernel, snapuserd_exec, snapuserd)
allow kernel snapuserd_exec:file relabelto;
allow kernel dm_user_device:dir { read open search relabelto };
allow kernel dm_user_device:chr_file relabelto;
dontaudit kernel sysfs_ublk:dir r_dir_perms;这些规则是上面 Virtual A/B 流程中的 policy 消费者;它们与 first-stage 的通用 rootfs/file_contexts 读取规则属于不同阶段。
Virtual A/B 的 transition window 先读取 policy,再暂停旧 snapuserd,加载 policy 和重标记设备,最后恢复 snapuserd。dontaudit 只抑制过渡期预期噪声;它不是授予读写权限,也不表示 sysfs/ublk 永久对 kernel 开放。
2.3 重标记规则
源码文件:system/sepolicy/private/kernel.te
allow kernel tmpfs:blk_file { getattr relabelfrom };
allow kernel tmpfs:chr_file { create getattr setattr relabelfrom };
allow kernel tmpfs:dir { open read relabelfrom };
allow kernel block_device:blk_file relabelto;
allow kernel dm_device:chr_file relabelto;
allow kernel dm_device:blk_file { getattr open read relabelto };
allow kernel kmsg_device:chr_file relabelto;
allow kernel null_device:chr_file relabelto;
allow kernel random_device:chr_file relabelto;
allow kernel snapuserd_exec:file relabelto;重标记需要 source object 的 relabelfrom 和 target object 的 relabelto。两者缺一都会在 first-stage restorecon 或 snapuserd transition 时失败;这与普通文件读写 denial 的 class/permission 不同。
3. 启动访问
3.1 根文件系统与参数
源码文件:system/sepolicy/private/kernel.te
r_dir_file(kernel, rootfs)
allow kernel { proc_bootconfig proc_cmdline }:file r_file_perms;
allow kernel selinuxfs:dir r_dir_perms;
allow kernel selinuxfs:file r_file_perms;
allow kernel file_contexts_file:file r_file_perms;
allow kernel rootfs:file relabelfrom;
allow kernel init_exec:file relabelto;这些规则让 first-stage 读取 rootfs、androidboot 参数、SELinux 状态和 file_contexts,并为 init_exec 准备标签。读取 proc_cmdline 不是读取普通 proc 的通用授权,目标 type 仍由 genfs/file context 区分。
3.2 security类
源码文件:system/sepolicy/private/kernel.te、system/sepolicy/private/domain.te
dontaudit kernel self:security setenforce;
allow kernel self:security setcheckreqprot;
allow kernel self:global_capability_class_set sys_resource;
allow kernel self:global_capability_class_set sys_boot;
allow kernel proc_sysrq:file w_file_perms;setcheckreqprot 和早期 reboot/sysrq 是 first-stage 需要的特殊操作;setenforce 使用 dontaudit,避免出现可由内核启动用户进程关闭 SELinux 的显式 allow。最终的 neverallow 还禁止其他 domain 使用 kernel:security load_policy/setenforce。
3.3 设备与文件描述符
源码文件:system/sepolicy/private/kernel.te
allow kernel kmsg_device:chr_file write;
allow kernel gsid:fd use;
allow kernel media_rw_data_file:dir create_dir_perms;
allow kernel media_rw_data_file:file create_file_perms;
allow kernel apexd:fd use;
allow kernel { apex_data_file staging_data_file vendor_apex_file }:file read;APEX loopback、media 和 gsid 场景可能由内核线程代为访问,故 kernel domain 需要 fd use 或目标文件读写。这里的 apeXd:fd use 是使用 apexd 传来的 descriptor,不是直接访问 apexd 进程内存。
4. 内核线程
4.1 loop0例外
源码文件:system/sepolicy/private/kernel.te
allow kernel { sdcard_type fuse }:file { read write };
allow kernel vold:fd use;
allow kernel { app_data_file privapp_data_file }:file read;
allow kernel asec_image_file:file read;loop block device 的请求可能在 kernel thread 上执行。vold 传递 fd 后,kernel context 读取 OBB/ASEC 相关对象;规则同时覆盖 app_data_file 和 privapp_data_file,但只授予 file read,不是目录遍历或任意 app 数据访问。
4.2 内核线程
内核线程没有用户空间 executable file,因此不要用 domain_auto_trans(kernel, kthread_exec, ...) 解释所有内核线程。只有当内核通过 usermode helper 执行一个可执行文件时,才需要为该 helper 建立专属 exec type 和 domain transition;普通 kthread 的访问在相应内核 hook 的 source context 下检查。
4.3 内核能力
源码文件:system/sepolicy/private/kernel.te
allow kernel self:global_capability_class_set sys_nice;
allow kernel self:global_capability_class_set sys_resource;
allow kernel self:global_capability_class_set sys_boot;
dontaudit kernel self:capability { sys_admin setgid mknod };能力规则是内核启动/调度/重启所需的最小集合。dontaudit 不会把 capability 变成 allow;它只隐藏特定过渡期尝试的审计记录。
5. init交接
5.1 transition
源码文件:system/sepolicy/private/kernel.te、system/sepolicy/private/init.te
domain_auto_trans(kernel, init_exec, init)
neverallow { domain -kernel userdebug_or_eng(`-overlay_remounter') } init:process transition;kernel → init 是启动时唯一的主交接;init.te 的 neverallow 防止其他 domain 伪造 transition 到 init。userdebug/eng 的 overlay_remounter 是显式例外,用于调试 overlay 重挂载。
5.2 生效时机
源码文件:system/core/init/selinux.cpp
if (selinux_android_restorecon("/system/bin/init", 0) == -1) {
PLOG(FATAL) << "restorecon failed of /system/bin/init failed";
}
const char* args[] = {"/system/bin/init", "second_stage", nullptr};
execv("/system/bin/init", const_cast<char**>(args));restorecon 先更新 file label,execv 再触发 policy 中的 init_exec→init transition。切换成功后,后续 rc 解析和 daemon 启动由 init domain 负责;kernel 过渡权限不会随意变成 init 的长期同义词。
6. 错误与验证
6.1 错误分类
| 现象 | 代码/策略边界 | 判断方向 |
|---|---|---|
/system/bin/init restorecon 失败 | kernel relabelto/文件上下文 | init_exec 标签与 file_contexts |
| execute_no_trans denial | kernel neverallow | usermode helper 是否有专属 domain |
| snapuserd 过渡失败 | Virtual A/B 临时规则 | ublk/dm 设备 relabel 和 transition |
| proc/cmdline 读取失败 | kernel file read | genfs/file type 是否正确 |
| 启动后 daemon denial | init/daemon domain | 不要继续扩大 kernel 权限 |
6.2 静态查询
# Confirm initial SID and kernel transition rules.
rg -n 'sid kernel|domain_auto_trans\(kernel|neverallow .*kernel' \
system/sepolicy/private system/sepolicy/public
# Inspect the first-stage policy inputs and generated policy.
rg -n 'proc_bootconfig|file_contexts_file|snapuserd_exec|relabelto' \
system/sepolicy/private/kernel.te system/sepolicy/private/init.te
sesearch -A -s kernel -c process -p transition,dyntransition
sesearch -A -s kernel -c file -p execute_no_trans,entrypoint,relabelto第一条确认 initial SID/transition 声明,第二条确认临时 relabel 输入,sesearch 验证最终 policy 中是否存在或被 neverallow 排除。它们不能证明设备实际处于 Virtual A/B,也不能证明内核线程正在触发某条规则。
6.3 设备观察
# Observe process labels during and after boot.
adb shell 'ps -AZ | grep -E "init|snapuserd"'
# Check labels of files used by first-stage init.
adb shell 'ls -Z /system/bin/init /dev/kmsg /dev/block'
# Read kernel audit records for the source/target/class/permission tuple.
adb shell 'dmesg | grep "avc: denied" | grep -E "scontext=u:r:kernel:s0|tcontext="'ps -AZ 只能观察用户空间 task;内核线程通常不会像普通 daemon 一样出现在相同列表中。first-stage 失败要结合 early boot kmsg、restorecon 日志和策略中的 source/target/class/permission 判断。
6.4 测试边界
kernel domain 的主要反向证据是策略编译、neverallow、first-stage boot、Virtual A/B 过渡测试和相关 CTS/VTS;文章中列出的静态检查证明规则存在性,不证明每个硬件内核版本都会走相同 loop/APEX/ublk 分支。
7. 源码导航
system/sepolicy/public/kernel.te、private/kernel.te:kernel 类型、initial 访问、设备重标记和 neverallow。system/sepolicy/private/initial_sid_contexts:kernel initial SID。system/core/init/main.cpp、selinux.cpp:first-stage 参数分流、策略加载、restorecon 和 second-stage exec。system/core/init/snapuserd_transition.cpp:Virtual A/B 设备重标记和 snapuserd handoff。system/sepolicy/private/init.te、snapuserd.te:kernel/init/snapuserd 的交接资源。system/sepolicy/private/fs_use、genfs_contexts:内核对象和虚拟文件系统的标签来源。
用 /system/bin/init 做练习:先确认 initial SID,再追 restorecon(init) 的 init_exec 标签和 kernel→init transition;然后选择一个 loop0 或 snapuserd denial,区分它是 file read、fd use、relabelto 还是 process transition。若把 kernel 的 execute_no_trans 放行、删除 init_exec relabelto 或删掉 snapuserd 过渡规则,应能预测策略编译失败、first-stage 无法交接或 Virtual A/B 设备 handoff 失败。
