Android 系统启动之 Init 进程启动分析二

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

1. SetupSelinux 初始化 SeLinux

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

cpp 复制代码
// 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 日志重定向到内核,其具体实现如下:

cpp 复制代码
// 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 就是具体的回调函数:

cpp 复制代码
// 将 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 的初始化:

cpp 复制代码
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. 跳转第二阶段

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

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

参考资料

相关推荐
二流小码农2 小时前
鸿蒙开发:填充剩余空间
android·ios·harmonyos
H309192 小时前
设计模式-生成器模式
android·java·设计模式
阿豪元代码3 小时前
Perfetto 上手指南2 —— 基础使用
android
QING6183 小时前
一文带你吃透Kotlin中 lateinit 和 by lazy 的区别和用法
android·kotlin·app
QING6183 小时前
Android Context 详解:原理、类型与使用指南
android·kotlin·app
七郎的小院3 小时前
ANR系列之ContentProvider ANR原理
android·性能优化
法欧特斯卡雷特3 小时前
Kotlin v2.1.20 发布,标准库又有哪些变化?
android·前端·后端
ForteScarlet5 小时前
Kotlin v2.1.20 发布,标准库又有哪些变化?
android·开发语言·kotlin
JiaoJunfeng13 小时前
Android App安装列表获取
android·android15适配·获取安装列表
小墙程序员15 小时前
一文了解 Android 中的 UID、GID、PID
android