Android 12 S 系统开机流程分析 - SetupSelinux(二)

news/2024/6/17 10:32:00 标签: android

本文接着上文开始讲解,上文中最后一步执行后会执行init启动过程中的第二步SetupSelinux(Selinux配置阶段),这样又会走到main.cpp中的main方法。

1. SetupSelinux

由于上一篇中最后一步在重新执行init的时候携带了参数selinux_setup,所以此处会走入SetupSelinux方法,加载selinux的策略。

SetupSelinux主要功能:

1)open sepolicy

2)load sepolicy

3)使能/关闭selinux

int main(int argc, char** argv) {
...
        if (!strcmp(argv[1], "selinux_setup")) {
//执行此处
            return SetupSelinux(argv);
        }

        if (!strcmp(argv[1], "second_stage")) {
            return SecondStageMain(argc, argv);
        }
    }

    return FirstStageMain(argc, argv);
}

1.1 初始化kernel log系统

1)初始化kernel log

2)若系统发生了panic,则走InstallRebootSignalHandlers

InstallRebootSignalHandlers中主要做了以下几件事:

    1. 初始化了一个自定义信号集,将其所有信号都填充满,即将信号集中的所有的标志位都置为1,使得这个集合包含所有可接受的信号,也就是阻塞所有信号。这个函数可以用于快速创建一个包含所有信号的信号集,然后可以根据需要删除其中的某些信号。

    2. init创建出来的子进程不做处理,直接exit;如果不是子进程,则代表是init进程,则执行InitFatalReboot

    3. 通过syscall向内核发送重启命令

    4. 捕获一些信号

int SetupSelinux(char** argv) {
    SetStdioToDevNull(argv);
    InitKernelLogging(argv);

    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }

    boot_clock::time_point start_time = boot_clock::now();
//这是R上为了R system.img/system_ext.img作为system_ext.img工作在old vendor.img上。
//我们在init第二阶段挂载system_ext,因为在system-only OTA场景中boot.img的init第一阶段是不会被更新的。
    MountMissingSystemPartitions();

    SelinuxSetupKernelLogging();

    LOG(INFO) << "Opening SELinux policy";

 SelinuxGetVendorAndroidVersion主要作用是读/vendor/etc/selinux/plat_sepolicy_vers.txt中的第一行,获取Selinux vendor version。

int SelinuxGetVendorAndroidVersion() {
    static int vendor_android_version = [] {
        if (!IsSplitPolicyDevice()) {
//如果这个设备不拆分sepolicy文件,它就不是一个Treble设备
            return __ANDROID_API_FUTURE__;
        }

        std::string version;
//获取vendor Selinux version
//读/vendor/etc/selinux/plat_sepolicy_vers.txt中的第一行
//可以adb看下vendor版本:
//adb shell cat /vendor/etc/selinux/plat_sepolicy_vers.txt
//32.0
        if (!GetVendorMappingVersion(&version)) {
            LOG(FATAL) << "Could not read vendor SELinux version";
        }
//解析version值,version一般是带小数点的,比如32.0。
        int major_version;
        std::string major_version_str(version, 0, version.find('.'));
        if (!ParseInt(major_version_str, &major_version)) {
            PLOG(FATAL) << "Failed to parse the vendor sepolicy major version "
                        << major_version_str;
        }
//返回version中整数
        return major_version;
    }();
    return vendor_android_version;
}

1.2 ReadPolicy

ReadPolicy主要做了两件事

1)open split policy

2)读sepolicy

这个是SetupSelinux的主要功能之一。

    // Read the policy before potentially killing snapuserd.
    std::string policy;
    ReadPolicy(&policy);

    auto snapuserd_helper = SnapuserdSelinuxHelper::CreateIfNeeded();
    if (snapuserd_helper) {
        // Kill the old snapused to avoid audit messages. After this we cannot
        // read from /system (or other dynamic partitions) until we call
        // FinishTransition().
        snapuserd_helper->StartTransition();
    }

ReadPolicy的主要功能是:

    如果设备中存在/system/etc/selinux/plat_sepolicy.cil文件,并可以访问,则IsSplitPolicyDevice为true,会走OpenSplitPolicy流程,这个设备中默认都有这个文件的。

void ReadPolicy(std::string* policy) {
    PolicyFile policy_file;
//constexpr const char plat_policy_cil_file[] = "/system/etc/selinux/plat_sepolicy.cil";
//bool IsSplitPolicyDevice() {
//    return access(plat_policy_cil_file, R_OK) != -1;
//}
//IsSplitPolicyDevice如上,如果能访问设备中的/system/etc/selinux/plat_sepolicy.cil文件,
//则IsSplitPolicyDevice为true,会走OpenSplitPolicy
    bool ok = IsSplitPolicyDevice() ? OpenSplitPolicy(&policy_file)
                                    : OpenMonolithicPolicy(&policy_file);
    if (!ok) {
        LOG(FATAL) << "Unable to open SELinux policy";
    }

    if (!android::base::ReadFdToString(policy_file.fd, policy)) {
        PLOG(FATAL) << "Failed to read policy file: " << policy_file.path;
    }
}

OpenSplitPolicy的主要功能是:

1)如果设备是userdebug版本+设备unlock+存在/debug_ramdisk/adb_debug.prop并且可以访问,则加载userdebug system sepolicy

 2)从代码看是access system,vendor,system_ext和vendor下etc/selinux/mapping/下的.cil文件
3)再查看对应目录下的.cil文件内容是否为空,不为空则将对应目录下的.cil文件内容push进compile_args
4)执行compile_args并等待,决定include哪个mapping下的.cil文件

5)将临时文件/dev/sepolicy.XXXXXX的fd和path保存进policy_file->fd和 policy_file->path。

bool OpenSplitPolicy(PolicyFile* policy_file) {
//若不存在/force_debuggable,那么在init第一阶段,force_debuggable_env 环境变量已经设置好了
//setenv("INIT_FORCE_DEBUGGABLE", "true", 1);
    const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
//是userdebug版本+设备unlock+存在/debug_ramdisk/adb_debug.prop并且可以访问,
//则加载userdebug system sepolicy
    bool use_userdebug_policy =
            ((force_debuggable_env && "true"s == force_debuggable_env) &&
             AvbHandle::IsDeviceUnlocked() && access(kDebugRamdiskSEPolicy, F_OK) == 0);
    if (use_userdebug_policy) {
        LOG(WARNING) << "Using userdebug system sepolicy";
    }

    if (!use_userdebug_policy) {
...
    }
    // No suitable precompiled policy could be loaded
//没有合适的策略可以加载
    LOG(INFO) << "Compiling SELinux policy";

    // We store the output of the compilation on /dev because this is the most convenient tmpfs
    // storage mount available this early in the boot sequence.
    char compiled_sepolicy[] = "/dev/sepolicy.XXXXXX";
//mkostemp:创建临时文件
//创建临时文件/dev/sepolicy.XXXXXX,其fd为compiled_sepolicy_fd
    unique_fd compiled_sepolicy_fd(mkostemp(compiled_sepolicy, O_CLOEXEC));
    if (compiled_sepolicy_fd < 0) {
        PLOG(ERROR) << "Failed to create temporary file " << compiled_sepolicy;
        return false;
    }
...
//从代码看是access system,vendor,system_ext和vendor下etc/selinux/mapping/下的.cil文件
//再查看对应目录下的.cil文件内容是否为空,不为空则将对应目录下的.cil文件内容push进compile_args
//执行compile_args并等待
//将临时文件/dev/sepolicy.XXXXXX的fd和path保存进policy_file->fd和 policy_file->path。
//决定include哪个mapping下的.cil文件
    std::string vend_plat_vers;
    if (!GetVendorMappingVersion(&vend_plat_vers)) {
        return false;
    }
    std::string plat_mapping_file("/system/etc/selinux/mapping/" + vend_plat_vers + ".cil");

    std::string plat_compat_cil_file("/system/etc/selinux/mapping/" + vend_plat_vers +
                                     ".compat.cil");
    if (access(plat_compat_cil_file.c_str(), F_OK) == -1) {
        plat_compat_cil_file.clear();
    }

    std::string system_ext_policy_cil_file("/system_ext/etc/selinux/system_ext_sepolicy.cil");
    if (access(system_ext_policy_cil_file.c_str(), F_OK) == -1) {
        system_ext_policy_cil_file.clear();
    }
...
//有一些代码,我们并不想让clang-format调整格式,这时可以使用注释临时禁用clang-format
    // clang-format off
    std::vector<const char*> compile_args {
        "/system/bin/secilc",
        use_userdebug_policy ? kDebugRamdiskSEPolicy: plat_policy_cil_file,
        "-m", "-M", "true", "-G", "-N",
        "-c", version_as_string.c_str(),
        plat_mapping_file.c_str(),
        "-o", compiled_sepolicy,
        // We don't care about file_contexts output by the compiler
        "-f", "/sys/fs/selinux/null",  // /dev/null is not yet available
    };
    // clang-format on
...
//将对应目录下的.cil文件内容push进compile_args
    if (!plat_compat_cil_file.empty()) {
        compile_args.push_back(plat_compat_cil_file.c_str());
    }
...
//执行compile_args并等待
    if (!ForkExecveAndWaitForCompletion(compile_args[0], (char**)compile_args.data())) {
        unlink(compiled_sepolicy);
        return false;
    }
    unlink(compiled_sepolicy);
//将临时文件/dev/sepolicy.XXXXXX的fd和path保存进policy_file->fd和 policy_file->path。
    policy_file->fd = std::move(compiled_sepolicy_fd);
    policy_file->path = compiled_sepolicy;
    return true;
}

1.3 加载策略

这也是SetupSelinux中最重要的功能之一。

   LoadSelinuxPolicy(policy);

 上一节中对policy进行了赋值。

static void LoadSelinuxPolicy(std::string& policy) {
    LOG(INFO) << "Loading SELinux policy";

    set_selinuxmnt("/sys/fs/selinux");
    if (security_load_policy(policy.data(), policy.size()) < 0) {
        PLOG(FATAL) << "SELinux:  Could not load policy";
    }
}

 security_load_policy主要功能是:

1)open $selinux_mnt/load,获取其对应fd

2)根据fd将/dev/sepolicy.XXXXXX中的内容写入$selinux_mnt/load中。selinux_mnt如下#define SELINUXMNT "/sys/fs/selinux"

external/selinux/libselinux/src/load_policy.c
int security_load_policy(void *data, size_t len)
{
	char path[PATH_MAX];
	int fd, ret;

	if (!selinux_mnt) {
		errno = ENOENT;
		return -1;
	}

	snprintf(path, sizeof path, "%s/load", selinux_mnt);
	fd = open(path, O_RDWR | O_CLOEXEC);
	if (fd < 0)
		return -1;

	ret = write(fd, data, len);
	close(fd);
	if (ret < 0)
		return -1;
	return 0;
}

1.4 使能/关闭Selinux并写入节点

获取selinux状态是否是enforce,然后将selinux状态is_enforcing写入 $selinux_mnt/enforce中。selinux_mnt如下

#define SELINUXMNT "/sys/fs/selinux"

    if (snapuserd_helper) {
        // Before enforcing, finish the pending snapuserd transition.
        snapuserd_helper->FinishTransition();
        snapuserd_helper = nullptr;
    }

    SelinuxSetEnforcement();
void SelinuxSetEnforcement() {
    bool kernel_enforcing = (security_getenforce() == 1);
//check selinux是否是enforce状态(开启selinux)
    bool is_enforcing = IsEnforcing();
    if (kernel_enforcing != is_enforcing) {
        if (security_setenforce(is_enforcing)) {
            PLOG(FATAL) << "security_setenforce(" << (is_enforcing ? "true" : "false")
                        << ") failed";
        }
    }

    if (auto result = WriteFile("/sys/fs/selinux/checkreqprot", "0"); !result.ok()) {
        LOG(FATAL) << "Unable to write to /sys/fs/selinux/checkreqprot: " << result.error();
    }
}
bool IsEnforcing() {
//如果关闭系统的selinux,即将selinux置为permissive,此处直接返回false即可
//return false;
    if (ALLOW_PERMISSIVE_SELINUX) {
        return StatusFromProperty() == SELINUX_ENFORCING;
    }
    return true;
}
/external/selinux/libselinux/src/setenforce.c
int security_setenforce(int value)
{
	int fd, ret;
	char path[PATH_MAX];
	char buf[20];

	if (!selinux_mnt) {
		errno = ENOENT;
		return -1;
	}

	snprintf(path, sizeof path, "%s/enforce", selinux_mnt);
	fd = open(path, O_RDWR | O_CLOEXEC);
	if (fd < 0)
		return -1;

	snprintf(buf, sizeof buf, "%d", value);
	ret = write(fd, buf, strlen(buf));
	close(fd);
	if (ret < 0)
		return -1;

	return 0;
}

1.5 selinux_android_restorecon

从kernel domain域过渡到init domain域
文件系统在xattrs中存储了SELabels,比如ext4不需要restorecon,但其他文件系统需要

//从kernel domain域过渡到init domain域
//文件系统在xattrs中存储了SELabels,比如ext4不需要restorecon,但其他文件系统需要
    if (selinux_android_restorecon("/system/bin/init", 0) == -1) {
        PLOG(FATAL) << "restorecon failed of /system/bin/init failed";
    }

1.6  重新执行init进程并携带参数second_stage

    setenv(kEnvSelinuxStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(), 1);

    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;
}

到了SetupSelinux的最后一步,这一步还是重新执行/system/bin/init,这样就会再次走init bin程序的main函数,由代码可以看到,此次携带了参数second_stage。

 


http://www.niftyadmin.cn/n/5161067.html

相关文章

ModuleNotFoundError: No module named ‘torchvision.models.utils‘

如图报错&#xff1a;No module named torchvision.models.utils解决方案&#xff1a;由于当前python环境高版本的torch&#xff0c; 代码用的是低版本的语法 将 from torchvision.models.utils import load_state_dict_from_url换成 from torch.hub import load_state_dict_fr…

探秘Python闭包与作用域

文章目录 闭包的定义与作用LEGB规则nonlocal与global关键字在Python的世界里,理解闭包(Closure)和作用域(Scope)是提升编程技巧和深度的一大步。这篇文章将带你深入了解闭包的神秘面纱,掌握LEGB规则,并使用nonlocal与global关键字来巧妙控制变量作用域。 闭包的定义与作…

提升ChatGPT答案质量和准确性的方法Prompt专家

文章目录 1、提供示例2、分步推理3、表格格式4、prompt转换器5、批判性提示6、比较提示7、逆向提示生成器1、提供示例 当你想模仿 某个事物的时候,比如:文案/风格/语气/语法的时候,模仿李白、马云、马斯克 当你想复制 一种难以明确描述,抽象形式的时候; 我们为chatgpt提供…

红队专题-从零开始VC++C/S远程控制软件RAT-MFC-远程控制软件总结

红队专题 招募六边形战士队员[30]远控班第一期课程与远控总结 招募六边形战士队员 一起学习 代码审计、安全开发、web攻防、逆向等。。。 私信联系 [30]远控班第一期课程与远控总结 一.Bug修复(1)生成路径(2)显示系统版本号二.内存泄露(1)如何检查内存泄露 #define CRTDBG_…

Mysql进阶-视图篇

介绍 视图&#xff08;View&#xff09;是一种虚拟存在的表。视图中的数据并不在数据库中实际存在&#xff0c;行和列数据来自定义视图的查询中使用的表&#xff0c;并且是在使用视图时动态生成的。 通俗的讲&#xff0c;视图只保存了查询的SQL逻辑&#xff0c;不保存查询结果。…

卡牌游戏类型定制开发微信卡牌小程序游戏

卡牌类型的游戏开发具有一些独特的特点和挑战&#xff0c;以下是一些主要的特点&#xff1a; 卡牌设计和平衡&#xff1a;卡牌游戏的核心是卡牌设计和平衡。开发团队需要设计各种卡牌&#xff0c;确保它们在游戏中相互平衡&#xff0c;以便提供有趣的游戏体验。卡牌的特性、效…

【Rust日报】2023-11-06 ESP上使用 Rust实现 SNTP协议

ESP上使用 Rust实现 SNTP协议 在这篇文章中&#xff0c;作者使用 ESP 和 Rust 使用 SNTP 协议将设备系统时间与网络时间同步。 SNTP 是 Simple Network Time Protocol 的缩写&#xff0c;它是一种用于在计算机系统之间通过分组交换、可变延迟数据网络进行时钟同步的网络协议。S…

基于Java+SpringBoot+LayUI仓库管理系统

一.项目介绍 本项目是使用JavaSpringBoot开发&#xff0c;可以实现仓库的注册、登录&#xff0c;登录后可进入系统&#xff0c;进行客户管理、供应商管理、商品管理、商品退货查询管理、登录日志及退出等几大模块。系统界面采用传统的后台管理界面&#xff0c;界面简单、直观。…