Android Native 之 init初始化selinux机制
Selinux其实关系到linux kernel阶段和android第一个进程init的几个阶段,下面依次针对这些阶段进行剖析。
1、linux kernel初始化selinux
\par \cf0\highlight0 Shutting Down UEFI Boot Services: 3406 ms\cf1\highlight2
\par \cf0\highlight0 Start EBS [ 3407]\cf1\highlight2
\par \cf0\highlight0 BDS: LogFs sync skipped, Unsupported\cf1\highlight2
\par \cf0\highlight0 App Log Flush : 78 ms\cf1\highlight2
\par \cf0\highlight0 Exit EBS [ 3506] UEFI End\cf1\highlight2
\par \cf0\highlight0 [ 0.000000][ T0] Booting Linux on physical CPU 0x0000000000 [0x51af8014]\cf1\highlight2
\par \cf0\highlight0 [ 0.000000][ T0] Linux version 5.15.167-android13-8-g8c2d35e2e090-abHUACE.01 (build-user@build-host) (Android (8508608, based on r450784e) clang version 14.0.7 (https://android.googlesource.com/toolchain/llvm-project 4c603efb0cca074e9238af8b4106c30add4418f6), LLD 14.0.7) #1 SMP PREEMPT Fri May 23 02:24:42 UTC 2025\cf1\highlight2
\par \cf0\highlight0 [ 0.002810][ T0] LSM: Security Framework initializing\cf1\highlight2
\par \cf0\highlight0 [ 0.002852][ T0] SELinux: Initializing.\cf1\highlight2
\par \cf0\highlight0 [ 0.002999][ T0] Mount-cache hash table entries: 8192 (order: 4, 65536 bytes, linear)\cf1\highlight2
\par \cf0\highlight0 [ 0.003017][ T0] Mountpoint-cache hash table entries: 8192 (order: 4, 65536 bytes, linear)\cf1\highlight2
如上日志为设备上电之后,启动linux kernel的流程,在0.002秒的时候开始进行LSM Security进行初始化,和SELinux Initializing初始化。
1.1 LSM: Security Framework initializing
1.2 SELinux: Initializing
1.3 linux kernel是如何设置当前状态?
如上核心代码逻辑为selinux_enforcing_boot如果为1者设置强制模式,否则设置宽容模式。这个变量的定义如下:
//vendor/kernel_platform/common/security/selinux/hooks.c
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
//如果宏CONFIG_SECURITY_SELINUX_DEVELOP被定义执行如下代码
static int selinux_enforcing_boot __initdata; //定义变量 selinux_enforcing_boot
static int __init enforcing_setup(char *str)
{unsigned long enforcing;//接收来自内核启动参数到enforcing,通过内核启动参数来决定 selinux_enforcing_boot的值if (!kstrtoul(str, 0, &enforcing))selinux_enforcing_boot = enforcing ? 1 : 0;return 1;
}
__setup("enforcing=", enforcing_setup); //注册启动参数处理函数,当内核启动参数中有"enforcing=xxx"时,调用enforcing_setup函数
#else
//如果宏CONFIG_SECURITY_SELINUX_DEVELOP未被定义,宏定义 selinux_enforcing_boot 为1
#define selinux_enforcing_boot 1
#endif
根据这段代码可以知道,我们必须要打开linux kernel宏控CONFIG_SECURITY_SELINUX_DEVELOP,才可以在linux kernel层面去关闭selinux。
1.4 linux selinuxfs实现了什么?
第一步:linux kernel在初始化阶段会主动进行系统调用__initcall
执行init_sel_fs函数,在此函数中,注册了文件系统结构体sel_fs_type,linux kernel在挂此结构体的时候,即2233行就会自动调用.init_fs_context指向的函数指针,反之在销毁的时候的会自动调用.kill_sb指向的函数指针
第二步:最终回调到sel_filll_super函数
在此函数中,在selinux文件系统目录下创建不同的子文件,个人不是很了解,猜测上层可以通过文件操作方式去回调后面对应的结构体,例如操作enforce就会指向对于的sel_enforce_ops
第三步:enforce的终极解密
如下代码,我们指向getenforce和setenforce其实就是对selinux/enforce文件进行读写操作, 最终走到了如下的两个函数,可以看到我们任何版本都可以进行getenforce,但是如果CONFIG_SECURITY_SELINUX_DEVELOP宏没有被定义,那么无法进行setenforce
//vendor/kernel_platform/common/security/selinux/selinuxfs.c
//enforce指定的结构体,即对selinux/enforce文件进行读操作回调sel_read_enforce函数,对selinux/enforce文件进行写操作回调sel_write_enforce
static const struct file_operations sel_enforce_ops = {.read = sel_read_enforce,.write = sel_write_enforce,.llseek = generic_file_llseek,
};
//对selinux/enforce文件进行读操作
static ssize_t sel_read_enforce(struct file *filp, char __user *buf, size_t count, loff_t *ppos) {struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info;char tmpbuf[TMPBUFLEN];ssize_t length;length = scnprintf(tmpbuf, TMPBUFLEN, "%d", enforcing_enabled(fsi->state));return simple_read_from_buffer(buf, count, ppos, tmpbuf, length);
}
//对selinux/enforce文件进行写操作
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
//如果CONFIG_SECURITY_SELINUX_DEVELOP宏被定义,定义sel_write_enforce函数
static ssize_t sel_write_enforce(struct file *file, const char __user *buf, size_t count, loff_t *ppos) {struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;struct selinux_state *state = fsi->state;char *page = NULL;ssize_t length;int old_value, new_value;if (count >= PAGE_SIZE) return -ENOMEM;if (*ppos != 0) return -EINVAL;page = memdup_user_nul(buf, count);if (IS_ERR(page)) return PTR_ERR(page);length = -EINVAL;if (sscanf(page, "%d", &new_value) != 1) goto out;new_value = !!new_value;old_value = enforcing_enabled(state);if (new_value != old_value) {length = avc_has_perm(&selinux_state, current_sid(), SECINITSID_SECURITY, SECCLASS_SECURITY, SECURITY__SETENFORCE, NULL);if (length) goto out;audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS, "enforcing=%d old_enforcing=%d auid=%u ses=%u" " enabled=1 old-enabled=1 lsm=selinux res=1", new_value, old_value, from_kuid(&init_user_ns, audit_get_loginuid(current)), audit_get_sessionid(current));enforcing_set(state, new_value);if (new_value) avc_ss_reset(state->avc, 0);selnl_notify_setenforce(new_value);selinux_status_update_setenforce(state, new_value);if (!new_value) call_blocking_lsm_notifier(LSM_POLICY_CHANGE, NULL);selinux_ima_measure_state(state);}length = count;
out:kfree(page);return length;
}
#else //注意:如果宏CONFIG_SECURITY_SELINUX_DEVELOP没有被定义,此函数指针为null,即无法进行设置
#define sel_write_enforce NULL
#endif
2、Vendor init进程第一阶段启动
|
如上代码,vendor init进程的第一阶段关键日志如上,并且没有关于selinux相关的打印,至于linux kernel如果启动到vendor init第一阶段,和vendor init第一阶段如何启动到system init的selinux阶段,参考Android Native 之 Init进程分析
3、System init进程selinux_setup启动
行 86: 06-19 12:02:09.620936 0 0 I (0)[0:swapper/0]SELinux: Initializing.
行 3636: 06-19 12:02:11.360570 1 1 E (0)[1:init]init 3: Init Selinux SetupSelinux A
行 3638: 06-19 12:02:11.368940 1 1 I (0)[1:init]init 3: Opening SELinux policy
行 3639: 06-19 12:02:11.368982 1 1 E (0)[1:init]init 3: Init Selinux SetupSelinux B
行 3642: 06-19 12:02:11.375320 1 1 I (1)[1:init]init 3: Loading APEX Sepolicy from /system/etc/selinux/apex/SEPolicy.zip
行 3643: 06-19 12:02:11.375595 1 1 E (1)[1:init]init 3: Failed to open package /system/etc/selinux/apex/SEPolicy.zip: No such file or directory
行 3644: 06-19 12:02:11.376426 1 1 E (1)[1:init]init 3: selinux root:1
行 3645: 06-19 12:02:11.376561 1 1 I (1)[1:init]init 4: Using userdebug system sepolicy /system/etc/selinux/userdebug_plat_sepolicy_cil
行 3646: 06-19 12:02:11.376651 1 1 I (1)[1:init]init 4: Compiling SELinux policy
行 4385: 06-19 12:02:14.550937 1 1 E (1)[1:init]init 4: Init Selinux SetupSelinux C
行 4386: 06-19 12:02:14.551093 1 1 E (1)[1:init]init 4: Init Selinux SetupSelinux D
行 4387: 06-19 12:02:14.551178 1 1 I (1)[1:init]init 4: Loading SELinux policy
行 4388: 06-19 12:02:14.689998 1 1 I (1)[1:init]SELinux: policy capability network_peer_controls=1
行 4389: 06-19 12:02:14.690014 1 1 I (1)[1:init]SELinux: policy capability open_perms=1
行 4390: 06-19 12:02:14.690019 1 1 I (1)[1:init]SELinux: policy capability extended_socket_class=1
行 4391: 06-19 12:02:14.690023 1 1 I (1)[1:init]SELinux: policy capability always_check_network=0
行 4392: 06-19 12:02:14.690028 1 1 I (1)[1:init]SELinux: policy capability cgroup_seclabel=0
行 4393: 06-19 12:02:14.690033 1 1 I (1)[1:init]SELinux: policy capability nnp_nosuid_transition=1
行 4394: 06-19 12:02:15.148882 1 1 I (0)[1:init]selinux 4: SELinux: Loaded file context from:
行 4395: 06-19 12:02:15.148908 1 1 I (0)[1:init]selinux 4: /system/etc/selinux/plat_file_contexts
行 4396: 06-19 12:02:15.149049 1 1 I (0)[1:init]selinux 3: /system_ext/etc/selinux/system_ext_file_contexts
行 4397: 06-19 12:02:15.149064 1 1 I (0)[1:init]selinux 3: /vendor/etc/selinux/vendor_file_contexts
行 4398: 06-19 12:02:15.149310 1 1 E (0)[1:init]selinux 3: SELinux: Could not stat /dev/selinux: No such file or directory.
行 4399: 06-19 12:02:15.149380 1 1 E (0)[1:init]init 3: Init Selinux SetupSelinux E SelinuxSetEnforcement
行 4400: 06-19 12:02:15.149495 1 1 E (0)[1:init]init 3: Init Selinux SelinuxSetEnforcement kernel_enforcing is 0
行 4401: 06-19 12:02:15.149519 1 1 E (0)[1:init]init 3: Init Selinux SelinuxSetEnforcement kernel_enforcing is 0
行 4402: 06-19 12:02:15.153931 1 1 E (0)[1:init]init 3: Init Selinux SetupSelinux E
行 4404: 06-19 12:02:15.338551 1 1 I (0)[1:init]selinux: SELinux: Loaded file context from:
行 4405: 06-19 12:02:15.338575 1 1 I (0)[1:init]selinux: /system/etc/selinux/plat_file_contexts
行 4406: 06-19 12:02:15.338586 1 1 I (0)[1:init]selinux: /system_ext/etc/selinux/system_ext_file_contexts
行 4407: 06-19 12:02:15.338597 1 1 I (0)[1:init]selinux: /vendor/etc/selinux/vendor_file_contexts
行 4486: 06-19 12:02:15.503784 1 1 I (0)[1:init]selinux 6: SELinux: Loaded file context from:
行 4487: 06-19 12:02:15.503797 1 1 I (0)[1:init]selinux 6: /system/etc/selinux/plat_file_contexts
行 4488: 06-19 12:02:15.503808 1 1 I (0)[1:init]selinux 6: /system_ext/etc/selinux/system_ext_file_contexts
行 4489: 06-19 12:02:15.503819 1 1 I (0)[1:init]selinux 6: /vendor/etc/selinux/vendor_file_contexts)
行 4767: 06-19 12:02:15.783488 313 313 I (1)[313:ueventd]selinux: SELinux: Loaded file context from:
行 4768: 06-19 12:02:15.783509 313 313 I (1)[313:ueventd]selinux: /system/etc/selinux/plat_file_contexts
行 4769: 06-19 12:02:15.783519 313 313 I (1)[313:ueventd]selinux: /system_ext/etc/selinux/system_ext_file_contexts
行 4770: 06-19 12:02:15.783528 313 313 I (1)[313:ueventd]selinux: /vendor/etc/selinux/vendor_file_contexts
行 5192: 06-19 12:02:17.655100 403 403 I (3)[403:servicemanager]SELinux: SELinux: Loaded service context from:
行 5193: 06-19 12:02:17.655184 403 403 I (3)[403:servicemanager]SELinux: /system/etc/selinux/plat_service_contexts
行 5194: 06-19 12:02:17.655226 403 403 I (3)[403:servicemanager]SELinux: /system_ext/etc/selinux/system_ext_service_contexts
行 5195: 06-19 12:02:17.655266 403 403 I (3)[403:servicemanager]SELinux: /vendor/etc/selinux/vendor_service_context
行 5311: 06-19 12:02:18.024645 1 1 E (1)[1:init]selinux 21: [8398][0]SELinux: Could not stat /sys/kernel/debug: No such file or directory.
行 5314: 06-19 12:02:18.037227 1 1 I (1)[1:init]selinux 5: [8402][0]SELinux: Skipping restorecon on directory(/metadata)
行 5315: 06-19 12:02:18.040521 1 1 I (2)[1:init]selinux 5: [8413][0]SELinux: Skipping restorecon on directory(/metadata/apex)
行 5608: 06-19 12:02:18.807752 1 1 I (3)[1:init]selinux 23: [9180][0]SELinux: Skipping restorecon on directory(/data/system/shutdown-checkpoints)
行 5716: 06-19 12:02:19.468904 1 1 I (0)[1:init]selinux 5: [9841][85]SELinux: Skipping restorecon on directory(/data)
行 5801: 06-19 12:02:20.321053 1 1 E (1)[1:init]selinux 21: [10654][25]SELinux: Could not stat /data/dalvik-cache/arm64: No such file or directory.
行 5802: 06-19 12:02:20.321185 1 1 E (1)[1:init]selinux 21: [10654][25]SELinux: Could not stat /data/dalvik-cache/riscv64: No such file or directory.
行 5803: 06-19 12:02:20.321304 1 1 E (1)[1:init]selinux 5: [10654][25]SELinux: Could not stat /data/dalvik-cache/x86: No such file or directory.
行 5804: 06-19 12:02:20.321393 1 1 E (1)[1:init]selinux 5: [10654][25]SELinux: Could not stat /data/dalvik-cache/x86_64: No such file or directory.
行 5811: 06-19 12:02:20.328629 1 1 I (1)[1:init]selinux 5: [10701][0]SELinux: Skipping restorecon on directory(/data/misc/apexdata/com.android.wifi)
行 10868: 06-19 12:02:43.491842 2125 2125 I (0)[2125:init]selinux 5: SELinux: Skipping restorecon on directory(/data/vendor_ce/0)
行 10872: 06-19 12:02:43.510338 2127 2127 I (3)[2127:init]selinux 5: SELinux: Skipping restorecon on directory(/data/misc_ce/0)
行 10959: 06-19 12:02:43.772735 1 1 E (3)[1:init]selinux 5: [34144][0]SELinux: Could not get canonical path for /sys/kernel/debug/tracing/instances/wifi restorecon: No such file or directory.
行 10981: 06-19 12:02:43.989663 1 1 I (1)[1:init]selinux 5: [34359][0]SELinux: Skipping restorecon on directory(/data/misc_ce/0/apexdata/com.android.wifi)
如上日志,对应如下源码:
// The SELinux setup process is carefully orchestrated around snapuserd. Policy must be loaded off dynamic partitions, and during an OTA, those partitions cannot be read without snapuserd.
// But, with kernel-privileged snapuserd running, loading the policy will immediately trigger audits. We use a five-step process to address this:
// (1) Read the policy into a string, with snapuserd running.
// (2) Rewrite the snapshot device-mapper tables, to generate new dm-user devices and to flush I/O.
// (3) Kill snapuserd, which no longer has any dm-user devices to attach to.
// (4) Load the sepolicy and issue critical restorecons in /dev, carefully avoiding anything that would read from /system.
// (5) Re-launch snapuserd and attach it to the dm-user devices from step (2).
// After this sequence, it is safe to enable enforcing mode and continue booting.
int SetupSelinux(char** argv) {//第一步:初始化阶段SetStdioToDevNull(argv); //重定向标准IO到/dev/nullInitKernelLogging(argv); //初始化内核日志系统LOG(ERROR) << "Init Selinux SetupSelinux A";if (REBOOT_BOOTLOADER_ON_PANIC) InstallRebootSignalHandlers();//安装崩溃重启处理器boot_clock::time_point start_time = boot_clock::now();MountMissingSystemPartitions(); //挂载缺失的系统分区SelinuxSetupKernelLogging();//第二步:策略预加载阶段LOG(INFO) << "Opening SELinux policy";LOG(ERROR) << "Init Selinux SetupSelinux B";PrepareApexSepolicy();//准备APEX模块的SELinux策略// Read the policy before potentially killing snapuserd.std::string policy;ReadPolicy(&policy); //将策略文件读入内存字符串(对应五步法的第1步)CleanupApexSepolicy();//清理临时APEX策略//第三步:snapuserd过渡阶段LOG(ERROR) << "Init Selinux SetupSelinux C";auto snapuserd_helper = SnapuserdSelinuxHelper::CreateIfNeeded();//创建snapuserd辅助对象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();// 终止旧snapuserd并重写设备映射表(对应五步法的第2-3步)}//第四步:策略生效阶段LOG(ERROR) << "Init Selinux SetupSelinux D";LoadSelinuxPolicy(policy); //加载内存中的策略(对应五步法的第4步)if (snapuserd_helper) {// Before enforcing, finish the pending snapuserd transition.snapuserd_helper->FinishTransition(); //重启snapuserd并绑定新设备(对应五步法的第5步)snapuserd_helper = nullptr;}//第五步:强制模式激活阶段// This restorecon is intentionally done before SelinuxSetEnforcement because the permissions needed to transition files from tmpfs to *_contexts_file context should not be granted to any process after selinux is set into enforcing mode.//安全上下文预恢复阶段:释说明restorecon必须在强制模式前执行,因为从tmpfs转换到*_contexts_file上下文需要特殊权限,这些权限在强制模式下不应被授予。递归修复SELinux策略目录的安全上下文,失败则触发致命错误,/dev/selinux/包含策略文件和运行时状态,其上下文正确性直接影响SELinux功能。if (selinux_android_restorecon("/dev/selinux/", SELINUX_ANDROID_RESTORECON_RECURSE) == -1) {PLOG(FATAL) << "restorecon failed of /dev/selinux failed";}LOG(ERROR) << "Init Selinux SetupSelinux E SelinuxSetEnforcement";//将SELinux从宽容模式切换为强制模式,此后所有进程必须严格遵循策略规则SelinuxSetEnforcement();// 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.//注释解释某些文件系统(如ramdisk)需要显式恢复上下文,而ext4等支持xattr的文件系统可自动处理。对/system/bin/init执行非递归的上下文修复(0表示不递归),确保init进程域转换正确,该操作特别针对A/B设备的恢复镜像场景。if (selinux_android_restorecon("/system/bin/init", 0) == -1) {PLOG(FATAL) << "restorecon failed of /system/bin/init failed";}//第六步:进入init进程的第二阶段LOG(ERROR) << "Init Selinux SetupSelinux F";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)); //启动system/bin/init并传递second_stage参数// 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;
}
3.1 system init如何进行selinux模式激活?
//system/system/core/init/selinux.cpp
//定义枚举表示当前selinux状态:宽容模式、强制模式
enum EnforcingStatus { SELINUX_PERMISSIVE, SELINUX_ENFORCING };
//通过属性来设置当前selinux状态
EnforcingStatus StatusFromProperty() {//默认强制模式EnforcingStatus status = SELINUX_ENFORCING;//遍历kernel传递上来的参数ImportKernelCmdline([&](const std::string& key, const std::string& value) {//如果找到键值对:androidboot.selinux=permissive,表示设置selinux状态为宽容模式if (key == "androidboot.selinux" && value == "permissive") {status = SELINUX_PERMISSIVE;LOG(ERROR) << "Init Selinux StatusFromProperty A " << " set status is permissive";}});//如果kernel参数没有找到宽容模式的设置,继续在ImportBootconfig中查找if (status == SELINUX_ENFORCING) {ImportBootconfig([&](const std::string& key, const std::string& value) {//如果找到键值对:androidboot.selinux=permissive,表示设置selinux状态为宽容模式if (key == "androidboot.selinux" && value == "permissive") {status = SELINUX_PERMISSIVE;LOG(ERROR) << "Init Selinux StatusFromProperty B" << " set status is permissive";}});}LOG(ERROR) << "Init Selinux StatusFromProperty C" << "status is " << status;return status;
}
bool IsEnforcing() {//如果宏开启进入判断,注意在init/Android.bp中,user版本此宏被设置为0,debug版本此宏被设置为1if (ALLOW_PERMISSIVE_SELINUX) {//如果是debug版本,androidboot.selinux=permissive满足,那么返回false,表示设置为宽容模式return StatusFromProperty() == SELINUX_ENFORCING;}//如果是user版本,默认返回true,表示设置为强制模式return true;
}
void SelinuxSetEnforcement() {bool kernel_enforcing = (security_getenforce() == 1); //获取kernel的selinux状态bool is_enforcing = IsEnforcing(); //获取init的selinux状态LOG(ERROR) << "Init Selinux SelinuxSetEnforcement " << "kernel_enforcing is " << kernel_enforcing;LOG(ERROR) << "Init Selinux SelinuxSetEnforcement " << "kernel_enforcing is " << is_enforcing;//如果kernel和init的selinux状态不一致if (kernel_enforcing != is_enforcing) {//设置selinux状态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();}
}
3.2 如何获取kernel参数和boot参数?
3.3 ALLOW_PERMISSIVE_SELINUX宏控是如何被设置?
3.4 getenforce与setenforce的本质
其实第一章在linux kernel阶段就介绍了enforce相关的内容,这里详细的实现了setenforce和getenforce的源码,并且init进程也是通过getenforce去获取kernel selinux状态,也是通过setenforce去设置kernel selinux状态
4、案例之user版本在init selinux中强制宽容模式无法开机
参考Android 关闭SE权限后编译user版本无法开机,开机直接进入FastBoot模式文档,在init selinux.cpp的直接强制返回false会使user版本进入fastboot模式,需要配置CONFIG_SECURITY_SELINUX_DEVELOP宏
根据如上分析,要同时满足kernel CONFIG_SECURITY_SELINUX_DEVELOP=y宏控配置,和selinux.cpp ALLOW_PERMISSIVE_SELINUX=1宏控配置,和androidboot.selinux=permissive即可。当然不需要三个条件都满足,后面两个条件可以合并,所以如下改法:
-
步骤一:kernel配置CONFIG_SECURITY_SELINUX_DEVELOP=y
注意:在高通A14 T576项目上此宏默认是开启的?有些不可思议
-
步骤二:init进程强制permissive模式
- 除了上述暴力强制返回false之外,还可以温柔一点,按照selinux.cpp原有判断逻辑:
- 配置kernel cmd参数