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

参考资料

相关推荐
rocpp8 小时前
Android 相册选择与拍照接入实践:MediaStore 分页、权限适配与 FileProvider
android
Flynt9 小时前
升级Flutter 3.44,我踩了HCPP和AGP 9的坑
android·flutter·dart
白色牙膏9 小时前
Cocos Creator 2.4.x 接入 AdMob 插件的迁移实践
android
我命由我1234511 小时前
C++ - 面向对象 - 常成员函数
android·java·linux·c语言·开发语言·c++·算法
tryqaaa_12 小时前
学习日志(四)【php反序列化魔术方法以及pop构造配实战】
android
Java小学生丶13 小时前
记录一下我的 Gradle 开发环境配置过程
android·java·gradle·maven·安卓
问心无愧051314 小时前
ctf show web 入门256
android·前端·笔记
霸道流氓气质14 小时前
MySQL 索引设计实战指南
android·数据库·mysql
R语言爱好者14 小时前
叠氮酸介绍
android
方白羽14 小时前
Android WebView 中实现第三方 QQ 登录的架构与流程详解
android·app