本文基于 AOSP android-10.0.0_r41 版别解说,内核版别 android-goldfish-4.14-gchips

1. SetupSelinux 初始化 SeLinux

上一节咱们分析了 init 进程的第一阶段—— FirstStageMain 函数的执行过程,FirstStageMain 函数的最终阶段会执行到 SetupSelinux 函数:

// system/core/init/selinux.cpp
// This function initializes SELinux then execs init to run in the init SELinux context.
int SetupSelinux(char** argv) {
    //初始化本阶段内核日志
    InitKernelLogging(argv);
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }
     //  初始化 SELinux,加载 SELinux 战略
    // Set up SELinux, loading the SELinux policy.
    SelinuxSetupKernelLogging(); // selinux 日志重定向到内核
    // LoadPolicy 加载 selinux_policy 战略文件
    // security_setenforce() 设置默许 selinux 形式
    SelinuxInitialize();
    // We're in the kernel domain and want to transition to the init domain.  File systems that
    // store SELabels in their xattrs, such as ext4 do not need an explicit restorecon here,
    // but other file systems do.  In particular, this is needed for ramdisks such as the
    // recovery image for A/B devices.
    //  再次调用 main 函数,并传入 second_stage 进入第二阶段
    //  而且此次发动就已经在 SELinux 上下文中运行
    // 加载可执行文件 `/system/bin/init` 的安全上下文
    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));
    // execv() only returns if an error happened, in which case we
    // panic and never return from this function.
    PLOG(FATAL) << "execv("" << path << "") failed";
    return 1;
}
  • 先调用 InitKernelLogging(argv) 初始化内核日志,再调用 InstallRebootSignalHandlers() 注册需要处理的信号,这个和第一阶段是一样的
  • 接着调用 SelinuxSetupKernelLogging() 将 selinux 日志重定向到内核
  • 接着调用 SelinuxInitialize 加载 selinux_policy 战略文件,设置默许 selinux 形式
  • 最终调用 selinux_android_restorecon 来设置 init 的安全上下问,接着经过 execv 跳转到第二阶段

接下来逐渐分析

2. SelinuxSetupKernelLogging selinux 日志重定向

SelinuxSetupKernelLogging 将 selinux 日志重定向到内核,其详细实现如下:

// system/core/init/selinux.cpp
//selinux 日志重定向到内核
// This function sets up SELinux logging to be written to kmsg, to match init's logging.
void SelinuxSetupKernelLogging() {
    selinux_callback cb;
    cb.func_log = SelinuxKlogCallback;
    // 设置 selinux 日志的回调
    selinux_set_callback(SELINUX_CB_LOG, cb);
}

这儿调用了 selinux_set_callback 函数,第一个参数 SELINUX_CB_LOG 表明要设置 Selinux 日志的回调,第二个参数 cb 的成员 func_log 便是详细的回调函数

// 将 selinux 日志重定向到 /dev/kmsg
int SelinuxKlogCallback(int type, const char* fmt, ...) {
    android::base::LogSeverity severity = android::base::ERROR;
    if (type == SELINUX_WARNING) {
        severity = android::base::WARNING;
    } else if (type == SELINUX_INFO) {
        severity = android::base::INFO;
    }
    char buf[1024];
    va_list ap;
    va_start(ap, fmt);
    vsnprintf(buf, sizeof(buf), fmt, ap);
    va_end(ap);
    android::base::KernelLogger(android::base::MAIN, severity, "selinux", nullptr, 0, buf);
    return 0;
}

这儿会调用 KernelLogger 将 Selinux 日志写入 /dev/kmsg 中。和上文分析的 InitKernelLogging(argv) 类似。

3. SelinuxInitialize 初始化 Selinux 环境

SelinuxInitialize 用于执行 Selinux 的初始化:

void SelinuxInitialize() {
    Timer t;
    LOG(INFO) << "Loading SELinux policy";
    // 加载 selinux_policy 战略文件
    if (!LoadPolicy()) {
        LOG(FATAL) << "Unable to load SELinux policy";
    }
    bool kernel_enforcing = (security_getenforce() == 1);
    bool is_enforcing = IsEnforcing();
    if (kernel_enforcing != is_enforcing) {
        // 设置默许 selinux 形式
        if (security_setenforce(is_enforcing)) { 
            PLOG(FATAL) << "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false");
        }
    }
    if (auto result = WriteFile("/sys/fs/selinux/checkreqprot", "0"); !result) {
        LOG(FATAL) << "Unable to write to /sys/fs/selinux/checkreqprot: " << result.error();
    }
    // init's first stage can't set properties, so pass the time to the second stage.
    setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1);
}

核心的调用有两个:

  • LoadPolicy:加载 selinux_policy 战略文件
  • security_setenforce:设置 selinux 的作业形式

selinux 配置文件的加载过程咱们放到 权限体系 专题给大家解说,这儿了解流程即可

4. 跳转第二阶段

const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage", nullptr};
execv(path, const_cast<char**>(args));

这儿跳转 init 时,带了一个参数 “second_stage”,程序进入第二阶段。

参考资料