Init Domain
本文面向已经读过 类型与属性、自动类型转换、TE宏库 和 内核加载流程 的读者。本文追踪 Android 17 中 first-stage init、second-stage init、rc service、executable label 和 SELinux domain transition 的真实调用链。
1. 阶段入口
1.1 main分流
源码文件:system/core/init/main.cpp
int main(int argc, char** argv) {
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
if (argc > 1) {
if (!strcmp(argv[1], "selinux_setup")) {
return SetupSelinux(argv);
}
if (!strcmp(argv[1], "second_stage")) {
return SecondStageMain(argc, argv);
}
}
#if defined(FIRST_STAGE_INIT) || defined(RECOVERY)
return FirstStageMain(argc, argv);
#else
LOG(FATAL) << "Second-stage init requires an argument to main()";
#endif
}1.2 策略后exec
源码文件:system/core/init/selinux.cpp
int SetupSelinux(char** argv) {
SelinuxSetupKernelLogging();
LoadSelinuxPolicyAndroid();
SelinuxSetEnforcement();
// Label init before the second exec.
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 failed";
return 1;
}restorecon 的消费者是下一次 exec:/system/bin/init 必须先得到 init_exec file type,内核才有条件从 kernel SID 转入 init domain。
2. 类型与转换
2.1 类型声明
源码文件:system/sepolicy/public/init.te
type init, domain, mlstrustedsubject;
type init_exec, system_file_type, exec_type, file_type;
type init_tmpfs, file_type;源码文件:system/sepolicy/private/init.te
typeattribute init coredomain;
tmpfs_domain(init)
domain_trans(init, shell_exec, shell)
domain_trans(init, init_exec, ueventd)
domain_trans(init, init_exec, vendor_init)
domain_auto_trans(init, charger_exec, charger)
domain_auto_trans(init, e2fs_exec, e2fs)
domain_auto_trans(init, bpfloader_exec, bpfloader)public 声明类型,private 建立 coredomain、transition 和访问规则。init_exec 是文件 type,init 是 process domain,二者不能混写。
2.2 file_contexts
源码文件:system/sepolicy/private/file_contexts
/init u:object_r:init_exec:s0
/system/bin/init u:object_r:init_exec:s0
/system/bin/charger u:object_r:charger_exec:s0
/system/bin/e2fsck u:object_r:e2fs_exec:s0file_contexts 只提供 executable label。内核再以 source domain、file type 和 process class 查询 transition。
2.3 宏关系
源码文件:system/sepolicy/private/te_macros
domain_trans(init, shell_exec, shell)
domain_auto_trans(init, charger_exec, charger)domain_trans 的展开包含 execute、process transition、entrypoint、fd/fifo 和 SIGCHLD;domain_auto_trans 额外增加 type_transition。type_transition 只选择新域,不能替代 entrypoint allow。
3. rc服务
3.1 service状态
源码文件:system/core/rootdir/init.rc
service init_dev_config /system/bin/init_dev_config
class core
user root
group root
seclabel u:r:init_dev_config:s0service 的 class、uid/gid、seclabel 和 restart action 由 init 的 Service 对象拥有。Service::Start 在 child 中执行 namespace、descriptor、属性设置和 exec。
3.2 context计算
源码文件:system/core/init/service.cpp
static Result<std::string> ComputeContextFromExecutable(
const std::string& service_path) {
char* raw_con = nullptr;
char* raw_filecon = nullptr;
if (getcon(&raw_con) == -1) {
return Error() << "Could not get security context";
}
std::unique_ptr<char, decltype(&freecon)> mycon(raw_con, freecon);
if (getfilecon(service_path.c_str(), &raw_filecon) == -1) {
return Error() << "Could not get file context";
}
std::unique_ptr<char, decltype(&freecon)> filecon(raw_filecon, freecon);
char* new_con = nullptr;
int rc = security_compute_create(
mycon.get(), filecon.get(),
string_to_security_class("process"), &new_con);
if (rc == 0) {
std::string computed_context(new_con);
free(new_con);
if (computed_context == mycon.get()) {
return Error() << "incorrect label or no domain transition";
}
return computed_context;
}
return Error() << "Could not get process context";
}init 读取当前 process context 和 executable file context,通过 security_compute_create 计算目标 context。结果仍是 init context 时,即使 permissive 也会报告 label 错误或缺少 transition。
3.3 child执行
源码文件:system/core/init/service.cpp
void Service::RunService(const std::vector<Descriptor>& descriptors,
InterprocessFifo cgroups_activated,
InterprocessFifo setsid_finished) {
if (auto result = EnterNamespaces(namespaces_, name_, mount_namespace_);
!result.ok()) {
LOG(FATAL) << "failed to set up namespaces";
}
for (const auto& descriptor : descriptors) {
descriptor.Publish();
}
SetProcessAttributesAndCaps(std::move(setsid_finished));
if (!ExpandArgsAndExecv(args_, sigstop_)) {
PLOG(ERROR) << "cannot execv('" << args_[0] << "')";
}
}child 先进入 namespace、发布 descriptor、等待 cgroup,再设置 uid/gid/context/capabilities 和 exec。显式 seclabel 由 SetProcessAttributesAndCaps 中的 setexeccon 应用到 child 的下一次 exec。
4. 权限和失败
4.1 init权限
源码文件:system/sepolicy/private/init.te
allow init properties_device:dir relabelto;
allow init property_type:file { append create getattr map open read relabelto rename setattr unlink write };
allow init block_device:dir relabelto;
allow init block_device:blk_file relabelto;
allow init kmsg_device:chr_file { getattr write relabelto };
allow init proc_drop_caches:file rw_file_perms;这些权限服务于 early boot restorecon、属性树、设备节点和 proc;子进程进入自己的 domain 后不会继承 init 的 allow。
4.2 vendor域
源码文件:system/sepolicy/public/vendor_init.te、system/sepolicy/private/init.te
type vendor_init, domain, mlstrustedsubject;
type vendor_init_exec, exec_type, file_type, vendor_file_type;
domain_trans(init, init_exec, vendor_init)
userdebug_or_eng(`
allow init su:process transition;
dontaudit init su:process noatsecure;
allow init su:process { siginh rlimitinh };
')vendor_init 是 vendor rc 命令的受限 domain;su transition 只在 userdebug/eng 展开。调试设备上显式启动 su 成功,不能外推到 user 构建。
4.3 recovery分支
源码文件:system/sepolicy/private/init.te
recovery_only(`
domain_trans(init, rootfs, adbd)
domain_trans(init, rootfs, fastbootd)
domain_trans(init, rootfs, recovery)
')recovery 镜像中的可执行文件常被标为 rootfs;普通系统构建不会展开这些规则。
4.4 调度与exec错误
源码文件: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.";
}这是延迟调度,不是 SELinux denial。setexeccon 失败是 FATAL;execv 失败记录 ERROR,父进程再通过 SIGCHLD/reap 和 restart/oneshot 状态处理。
5. 测试与排障
5.1 parser测试
源码文件:system/core/init/service_test.cpp
TEST(ServiceTest, ParseExecWithoutCommand) {
std::vector<std::string> args = {"exec", "--"};
auto result = ParseExec(args);
// An exec action without a command must be rejected.
ASSERT_FALSE(result.ok());
}输入为 exec --,断言 parser 返回错误;它只证明 action 输入校验。
源码文件:system/core/init/init_test.cpp
TEST(init, ExecuteCommandsInOrder) {
int num_executed = 0;
auto execute = [&num_executed](const BuiltinArguments& args) {
// Verify ActionManager callback order.
EXPECT_EQ(++num_executed, std::stoi(args[1]));
};
// ... install test function map and trigger actions ...
EXPECT_EQ(3, num_executed);
}输入为三个 execute action,断言消费顺序为 1、2、3;它证明 ActionManager 队列,不证明 fork/exec label。
5.2 设备诊断
# Confirm executable labels selected by file_contexts.
adb shell 'ls -Z /system/bin/charger /system/bin/init'
# Locate transition and entrypoint declarations.
rg -n 'domain_(auto_)?trans\(init|type_transition init' \
system/sepolicy/public system/sepolicy/private
# Find transition and exec errors.
adb shell 'logcat -b all | grep -E "no domain transition|setexeccon|cannot execv"'
# Confirm the running process domain.
adb shell 'ps -AZ | grep -E "charger|zygote|init_dev_config"'排障顺序是 executable file type → transition/entrypoint → rc seclabel → exec 日志 → ps -AZ。userdebug/eng、vendor_init 和 recovery 分支必须单独核对。
6. 源码导航
- system/core/init/main.cpp、system/core/init/selinux.cpp:阶段入口和策略后 exec。
- system/sepolicy/public/init.te、system/sepolicy/private/init.te:init 类型、transition 和条件分支。
- system/sepolicy/private/te_macros:domain_trans、domain_auto_trans。
- system/core/rootdir/init.rc 与设备 rc:service 状态、seclabel、class 和 restart。
- system/core/init/service.cpp:context 计算、child 属性、setexeccon、execv 和回收。
- system/sepolicy/private/file_contexts:executable file type。
- system/core/init/service_test.cpp、init_test.cpp:parser 和 ActionManager 测试。
用 charger 做练习:找到 rc service、/system/bin/charger 的 file context 和 domain_auto_trans(init, charger_exec, charger),再用 ps -AZ 验证最终 domain。删除 entrypoint、改成普通 system_file 或切换 user 构建时,应能预测失败发生在 context 计算、exec 权限还是条件策略。
