前面一节我们已经讲解了init进程对目录生成和挂载、日志初始化和设置,接下来init进程将初始化SELinux[1]并设置policy文件,如下面代码所示。若要详细了解SELinux的设计原理和工作机制,需要用一整本书来讲解,由于篇幅所限,在此我们不过多涉及这方面的内容。init进程运行在用户空间,主要涉及对SELinux的挂载和配置,下面我们把重点放在这个过程上。
static void selinux_initialize(bool in_kernel_domain) {
selinux_callback cb;
cb.func_log = selinux_klog_callback;
selinux_set_callback(SELINUX_CB_LOG, cb); <---(1)
cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
if (in_kernel_domain) {
INFO("Loading SELinux policy...\n");
if (selinux_android_load_policy() < 0) { <--- (2)
ERROR("failed to load policy: %s\n", strerror(errno));
security_failure();
}
bool kernel_enforcing = (security_getenforce() == 1);
bool is_enforcing = selinux_is_enforcing();
if (kernel_enforcing != is_enforcing) { <--- (3)
if (security_setenforce(is_enforcing)) {
ERROR("security_setenforce(%s) failed: %s\n",
is_enforcing ? "true" : "false", strerror(errno));
security_failure();
}
}
if (write_file("/sys/fs/selinux/checkreqprot", "0") == -1) { <--- (4)
security_failure();
}
...
}
int main(int argc, char** argv) {
...
selinux_initialize(is_first_stage);
...
}
(1) selinux_initialize()函数调用selinux_set_callback()函数设置两个全局的回调函数指针“selinux_log”和“selinux_audit”。
(2) 调用selinux_android_load_policy()[2]函数加载并向内核设置策略,selinux_initialize()函数的主要作用就是从文件中读取SELinux的配置文件,然后把它设置到内核中,这样SELinux才能开始工作。
(3) 检查是否强制使用用SELinux,有两种方法可以配置,一是SELinux挂载目录下的enforce文件,其中只有一个数字值表示SELinux的当前状态,0表示不强制使用(permissive[3]),1则表示强制使用(enforcing);其次是/proc/cmdline文件,在其中加入“androidboot.selinux= permissive”字段表示不强制使用(permissive),否则强制使用(enforcing),这种方法主要用于开发或调试版本。
(4) 在“/sys/fs/selinux/checkreqprot”文件中写入校验结果,如果写入成功,则SELinux设置完毕,可正常使用。
上述代码的(2)所示selinux_android_load_policy()函数是加载SELinux的主要逻辑代码,如下面的代码所示,selinux_android_load_policy()函数尝试将SELinux的虚拟文件系统selinuxfs挂载到“/sys/fs/selinux”节点上,形成下面的图所示的层次结构。
rc = mount(SELINUXFS, mnt, SELINUXFS, 0, NULL);
如果内核没有启用SELinux,则“/sys/fs/selinux”挂载失败,selinux_android_load_policy()函数继续尝试在根目录下创建“/selinux”节点并挂载,如果再次失败则SELinux初始化失败。
在SELinux挂载成功后,调用selinux_android_load_policy_helper()函数装载policy文件。selinux_android_load_policy_helper()函数首先调用set_policy_index()函数设置policy文件的索引,目的是为了打开在sepolicy_file数组中定义的policy文件,然后调用mmap()函数把它映射到内存中,最后调用函数security_load_policy()把policy设置到内核中。policy文件的文件名保存在数组sepolicy_file中,定义如下面的代码所示。
static const char *const sepolicy_file[] = {
"/sepolicy",
"/data/security/current/sepolicy",
NULL };
至此,SELinux的初始化就讲解完了。如前一篇鸟人的Android揭秘(10)——Init进程源代码分析(一)所说,init进程将切换到第二阶段的初始化过程。下一节我们将讲解init进程对属性的初始化和属性服务的启动。
[1] SELinux(Security-Enhanced Linux)是由美国国家安全局(NSA)实现的一种基于“域-类型”模型(domain-type)的强制访问控制(MAC)安全系统,在这种访问控制体系的限制下,进程只能访问它的任务中所需要文件。Linux内核在2.6版本中引入了SELinux,并提供一个可定制的安全策略,同时也提供了很多用户层的库和工具。SELinux是目前为止功能最全面、测试最充分的Linux安全模块,相应的某些安全相关的应用也被打了SELinux的补丁。
[2] 该函数定义在external/libselinux/src/android.c中。
[3] permissive模式表示即使违反了安全策略,也只是会发出警告,而不会真的拒绝执行。