Skip to content

Kernel Domain

追踪 Android 17 kernel domain 从 initial SID、first-stage init 到策略加载、设备重标记、内核线程访问和 init transition。

基于android-17.0.0_r1
AndroidSELinuxkernelfirst-stage initVirtual A/B源码阅读

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

text
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

text
sid kernel u:r:kernel:s0

initial SID 由策略加载和内核 SELinux 初始化共同解释。它不是 file_contexts 对某个路径的匹配结果;/init 的文件标签在后续 restorecon 中单独得到 init_exec。

1.3 不可切入 ​

源码文件:system/sepolicy/private/kernel.te

text
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

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

cpp
// 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 权限。

text
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

text
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

text
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

text
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

text
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

text
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

text
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

text
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

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 denialkernel neverallowusermode helper 是否有专属 domain
snapuserd 过渡失败Virtual A/B 临时规则ublk/dm 设备 relabel 和 transition
proc/cmdline 读取失败kernel file readgenfs/file type 是否正确
启动后 daemon denialinit/daemon domain不要继续扩大 kernel 权限

6.2 静态查询 ​

sh
# 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 设备观察 ​

sh
# 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. 源码导航 ​

  1. system/sepolicy/public/kernel.te、private/kernel.te:kernel 类型、initial 访问、设备重标记和 neverallow。
  2. system/sepolicy/private/initial_sid_contexts:kernel initial SID。
  3. system/core/init/main.cpp、selinux.cpp:first-stage 参数分流、策略加载、restorecon 和 second-stage exec。
  4. system/core/init/snapuserd_transition.cpp:Virtual A/B 设备重标记和 snapuserd handoff。
  5. system/sepolicy/private/init.te、snapuserd.te:kernel/init/snapuserd 的交接资源。
  6. 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 失败。