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",程序进入第二阶段。

参考资料

相关推荐
深海呐3 小时前
Android AlertDialog圆角背景不生效的问题
android
ljl_jiaLiang3 小时前
android10 系统定制:增加应用使用数据埋点,应用使用时长统计
android·系统定制
花花鱼3 小时前
android 删除系统原有的debug.keystore,系统运行的时候,重新生成新的debug.keystore,来完成App的运行。
android
落落落sss4 小时前
sharding-jdbc分库分表
android·java·开发语言·数据库·servlet·oracle
消失的旧时光-19436 小时前
kotlin的密封类
android·开发语言·kotlin
服装学院的IT男8 小时前
【Android 13源码分析】WindowContainer窗口层级-4-Layer树
android
CCTV果冻爽9 小时前
Android 源码集成可卸载 APP
android
码农明明9 小时前
Android源码分析:从源头分析View事件的传递
android·操作系统·源码阅读
秋月霜风10 小时前
mariadb主从配置步骤
android·adb·mariadb
Python私教11 小时前
Python ORM 框架 SQLModel 快速入门教程
android·java·python