Skip to content

Init Domain

追踪 Android 17 init 域从 first-stage、策略加载、rc service 到 domain transition、seclabel 和失败回收。

基于android-17.0.0_r1
AndroidSELinuxinitdomain transition源码阅读

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

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

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

text
type init, domain, mlstrustedsubject;
type init_exec, system_file_type, exec_type, file_type;
type init_tmpfs, file_type;

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

text
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

text
/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:s0

file_contexts 只提供 executable label。内核再以 source domain、file type 和 process class 查询 transition。

2.3 宏关系 ​

源码文件:system/sepolicy/private/te_macros

text
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

text
service init_dev_config /system/bin/init_dev_config
    class core
    user root
    group root
    seclabel u:r:init_dev_config:s0

service 的 class、uid/gid、seclabel 和 restart action 由 init 的 Service 对象拥有。Service::Start 在 child 中执行 namespace、descriptor、属性设置和 exec。

3.2 context计算 ​

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

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

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

text
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

text
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

text
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

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

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

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 设备诊断 ​

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

  1. system/core/init/main.cpp、system/core/init/selinux.cpp:阶段入口和策略后 exec。
  2. system/sepolicy/public/init.te、system/sepolicy/private/init.te:init 类型、transition 和条件分支。
  3. system/sepolicy/private/te_macros:domain_trans、domain_auto_trans。
  4. system/core/rootdir/init.rc 与设备 rc:service 状态、seclabel、class 和 restart。
  5. system/core/init/service.cpp:context 计算、child 属性、setexeccon、execv 和回收。
  6. system/sepolicy/private/file_contexts:executable file type。
  7. 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 权限还是条件策略。