android6.0 init进程main之selinux_initialize
对应代码android6.0_r72,kernel对应linux3.18
前言
selinux 的初始化 selinux_initialize
涉及文件
/system/core/init/Init.cpp
/external/libselinux/callbacks.c
/system/core/init/log.cpp
/system/core/init/Android.mk
/external/libselinux/src/android.c
/external/libselinux/src/init.c
/external/libselinux/src/load_policy.c
/system/core/init/setenforce.c
/system/core/libcutils/android_reboot.c
selinux_initialize
init.cpp 文件 main() 函数中调用 selinux_initialize 参数 is_first_stage 为启动的阶段
// /system/core/init/Init.cpp
selinux_initialize(is_first_stage);
selinux_initialize 整体代码如下:
// /system/core/init/Init.cpp
static void selinux_initialize(bool in_kernel_domain) { // in_kernel_domain 区分内核态和用户态
Timer t; //使用 Timer 计时,计算 selinux 初始化耗时
selinux_callback cb;
cb.func_log = selinux_klog_callback; // 用于打印 Log 的回调函数
selinux_set_callback(SELINUX_CB_LOG, cb); // 设置 log 的打印回调函数,使用 type 为 SELINUX_CB_LOG
cb.func_audit = audit_callback; // 用于检查权限的回调函数
selinux_set_callback(SELINUX_CB_AUDIT, cb); // 设置审查回调函数,使用 type 为 SELINUX_CB_AUDIT
if (selinux_is_disabled()) { // 如果 SELinux 没有 enable,退出。
return;
}
if (in_kernel_domain) { // 内核态处理流程,第一阶段 in_kernel_domain 为 true
INFO("Loading SELinux policy...\n"); // 该行 log 打印不出,INFO 级别 (因为设置了打印输出的级别为 5)
// 用于加载 sepolicy 文件。该函数最终将 sepolicy 文件传递给 kernel,这样 kernel 就有了安全策略配置文件
if (selinux_android_load_policy() < 0) {
ERROR("failed to load policy: %s\n", strerror(errno));
security_failure(); // 将重启进入 recovery mode
}
bool is_enforcing = selinux_is_enforcing(); // 命令行中得到的信息
// 用于设置 selinux 的工作模式。selinux 有两种工作模式:
// 1、”permissive”,所有的操作都被允许(即没有MAC),但是如果违反权限的话,会记录日志
// 2、”enforcing”,所有操作都会进行权限检查。在一般的终端中,应该工作于 enforing 模式
security_setenforce(is_enforcing); //设置 selinux 的模式,是开还是关
if (write_file("/sys/fs/selinux/checkreqprot", "0") == -1) {
security_failure(); // 将重启进入 recovery mode
}
NOTICE("(Initializing SELinux %s took %.2fs.)\n",
is_enforcing ? "enforcing" : "non-enforcing", t.duration()); // 输出 selinux 的模式,与初始化耗时
} else {
// selinux_init_all_handles初始化file_context,seapp_context以及property_context相关内容
selinux_init_all_handles(); // 如果启动第二阶段,调用该函数
}
}
callback 设置
union selinux_callback {
// 记录printf样式的格式和参数,类型代码指示消息的类型
int
#ifdef __GNUC__
__attribute__ ((format(printf, 2, 3)))
#endif
(*func_log) (int type, const char *fmt, ...);
// 将 auditdata 的字符串表示形式(对应于给定的安全类)存储到 msgbuf 中。
int (*func_audit) (void *auditdata, security_class_t cls,
char *msgbuf, size_t msgbufsize);
// 验证提供的上下文,必要时进行修改
int (*func_validate) (char **ctx);
// setenforce 消息的 netlink 回调
int (*func_setenforce) (int enforcing);
// policyload 消息的 netlink 回调
int (*func_policyload) (int seqno);
};
打印 Log 的回调函数,也是往 /dev/kmsg 文件中写入 log 内容:
// /system/core/init/log.cpp
int selinux_klog_callback(int type, const char *fmt, ...) {
int level = KLOG_ERROR_LEVEL;
if (type == SELINUX_WARNING) {
level = KLOG_WARNING_LEVEL;
} else if (type == SELINUX_INFO) {
level = KLOG_INFO_LEVEL;
}
va_list ap;
va_start(ap, fmt);
init_klog_vwrite(level, fmt, ap);
va_end(ap);
return 0;
}
selinux_set_callback() 的作用是对一些全局的函数指针赋值,selinux_initialize 调用函数 selinux_set_callback() 的结果是将 selinux_log 的值设置成 klog_write, 将 selinux_audit 的值设置成 audit_callback。
// /external/libselinux/callbacks.c
void selinux_set_callback(int type, union selinux_callback cb)
{
switch (type) {
case SELINUX_CB_LOG:
selinux_log = cb.func_log;
break;
case SELINUX_CB_AUDIT:
selinux_audit = cb.func_audit;
break;
case SELINUX_CB_VALIDATE:
selinux_validate = cb.func_validate;
break;
case SELINUX_CB_SETENFORCE:
selinux_netlink_setenforce = cb.func_setenforce;
break;
case SELINUX_CB_POLICYLOAD:
selinux_netlink_policyload = cb.func_policyload;
break;
}
}
判断 selinux 是否关闭
selinux_is_disabled 判断 selinux 是否关闭,如果关闭就直接退出,没必要再初始化了。
selinux_is_disabled() 当设置允许关闭时,函数判断目录 /sys/fs/selinux 是否可以访问,因为这个目录是 SELinux 的虚拟文件系统所在的目录,不能返回说明 SELinux 被 disable 了。同时还要判断属性 ro.boot.selinux 是否等于 “disabled”, 这个属性值表示 SELinux 的状态。
// /system/core/init/Init.cpp
static bool selinux_is_disabled(void)
{
// ALLOW_DISABLE_SELINUX 定义在了 Android.mk
if (ALLOW_DISABLE_SELINUX) {
if (access("/sys/fs/selinux", F_OK) != 0) {
// SELinux 未编译到内核中,或者已通过内核命令行 “SELinux=0” 禁用。
return true;
}
// 没有被禁用的情况时
// 通过 androidboot.selinux 判断 SELinux 是否被禁用
return selinux_status_from_cmdline() == SELINUX_DISABLED;
}
return false;
}
其中 ALLOW_DISABLE_SELINUX 定义在了 Android.mk 文件中了:
基本上输出的版本 DALLOW_DISABLE_SELINUX 都是 0 即不允许关闭
// /system/core/init/Android.mk
ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
init_options += -DALLOW_LOCAL_PROP_OVERRIDE=1 -DALLOW_DISABLE_SELINUX=1
else
init_options += -DALLOW_LOCAL_PROP_OVERRIDE=0 -DALLOW_DISABLE_SELINUX=0
endif
获取 selinux 状态信息
// /system/core/init/Init.cpp
static selinux_enforcing_status selinux_status_from_cmdline() {
selinux_enforcing_status status = SELINUX_ENFORCING;
std::function<void(char*,bool)> fn = [&](char* name, bool in_qemu) {
char *value = strchr(name, '=');
if (value == nullptr) { return; }
*value++ = '\0';
if (strcmp(name, "androidboot.selinux") == 0) {
if (strcmp(value, "disabled") == 0) {
status = SELINUX_DISABLED;
} else if (strcmp(value, "permissive") == 0) {
status = SELINUX_PERMISSIVE;
}
}
};
import_kernel_cmdline(false, fn);
return status;
}
import_kernel_cmdline 将从 /proc/cmdline 读取到的 cmdline 进行分割,回调 import_kernel_nv() 进行判断;返回 selinux 状态。
// /system/core/init/util.cpp
void import_kernel_cmdline(bool in_qemu, std::function<void(char*,bool)> import_kernel_nv)
{
char cmdline[2048];
char *ptr;
int fd;
fd = open("/proc/cmdline", O_RDONLY | O_CLOEXEC);
if (fd >= 0) {
int n = read(fd, cmdline, sizeof(cmdline) - 1);
if (n < 0) n = 0;
/* get rid of trailing newline, it happens */
if (n > 0 && cmdline[n-1] == '\n') n--;
cmdline[n] = 0;
close(fd);
} else {
cmdline[0] = 0;
}
ptr = cmdline;
while (ptr && *ptr) {
char *x = strchr(ptr, ' ');
if (x != 0) *x++ = 0;
import_kernel_nv(ptr, in_qemu);
ptr = x;
}
}
启动第一阶段的 selinux 初始化
经过上述判断过程后,满足条件则进入启动第一阶段的 selinux 初始化
即进入如下if语句中:
// /system/core/init/Init.cpp(static void selinux_initialize(bool in_kernel_domain)函数中)
if (in_kernel_domain) { // 内核态处理流程,第一阶段 in_kernel_domain 为 true
INFO("Loading SELinux policy...\n"); // 该行 log 打印不出,INFO 级别 (因为设置了打印输出的级别为 5)
// 用于加载 sepolicy 文件。该函数最终将 sepolicy 文件传递给 kernel,这样 kernel 就有了安全策略配置文件
if (selinux_android_load_policy() < 0) {
ERROR("failed to load policy: %s\n", strerror(errno));
security_failure(); // 将重启进入 recovery mode
}
bool is_enforcing = selinux_is_enforcing(); // 命令行中得到的信息
// 用于设置 selinux 的工作模式。selinux 有两种工作模式:
// 1、”permissive”,所有的操作都被允许(即没有MAC),但是如果违反权限的话,会记录日志
// 2、”enforcing”,所有操作都会进行权限检查。在一般的终端中,应该工作于 enforing 模式
security_setenforce(is_enforcing); //设置 selinux 的模式,是开还是关
if (write_file("/sys/fs/selinux/checkreqprot", "0") == -1) {
security_failure(); // 将重启进入 recovery mode
}
NOTICE("(Initializing SELinux %s took %.2fs.)\n",
is_enforcing ? "enforcing" : "non-enforcing", t.duration()); // 输出 selinux 的模式,与初始化耗时
}
selinux_android_load_policy()
用于加载 sepolicy 文件。该函数最终将 sepolicy 文件传递给 kernel,这样 kernel 就有了安全策略配置文件
// /external/libselinux/src/android.c
int selinux_android_load_policy(void)
{
// /external/libselinux/src/policy.h:#define SELINUXMNT "/sys/fs/selinux"
const char *mnt = SELINUXMNT;
int rc;
// /external/libselinux/src/policy.h:20:#define SELINUXFS "selinuxfs"
rc = mount(SELINUXFS, mnt, SELINUXFS, 0, NULL);
if (rc < 0) {
if (errno == ENODEV) {
/* SELinux not enabled in kernel */
return -1;
}
if (errno == ENOENT) {
/* Fall back to legacy mountpoint. */
// /external/libselinux/src/policy.h:#define OLDSELINUXMNT "/selinux"
mnt = OLDSELINUXMNT;
rc = mkdir(mnt, 0755);
if (rc == -1 && errno != EEXIST) {
selinux_log(SELINUX_ERROR,"SELinux: Could not mkdir: %s\n",
strerror(errno));
return -1;
}
rc = mount(SELINUXFS, mnt, SELINUXFS, 0, NULL);
}
}
if (rc < 0) {
selinux_log(SELINUX_ERROR,"SELinux: Could not mount selinuxfs: %s\n",
strerror(errno));
return -1;
}
// sys/fs/selinux 为 userspace 和 kernel 的 SELinux 模块交互通道
// 用户空间进程可以读写 /sys/fs/selinux 的各个文件或其中的子目录来通知 kernel 中的 SELinux 完成相关的操作。
set_selinuxmnt(mnt);
// //加载 selinux policy。
return selinux_android_load_policy_helper(false);
}
其中 set_selinuxmnt 如下:
// /external/libselinux/src/init.c
void set_selinuxmnt(const char *mnt)
{
// selinux_mnt 为写入策略时的文件
// strdup 将字符串拷贝到新建的位置处,返回一个指针,指向为复制字符串分配的空间;如果分配空间失败,则返回NULL值。
selinux_mnt = strdup(mnt);
}
selinux_android_load_policy_helper 函数实体:
// /external/libselinux/src/android.c
static int selinux_android_load_policy_helper(bool reload)
{
int fd = -1, rc;
struct stat sb;
void *map = NULL;
int old_policy_index = policy_index;
// 如果重新加载策略时没有/data策略,或者/data策略的版本错误,并且之前的加载来自/policy,那么只需返回即可。
set_policy_index();
if (reload && !policy_index && !old_policy_index)
return 0;
// sepolicy_file 指向的是 sepolicy 文件的路径,即 root 目录下的 sepolicy 文件。
fd = open(sepolicy_file[policy_index], O_RDONLY | O_NOFOLLOW);
if (fd < 0) {
selinux_log(SELINUX_ERROR, "SELinux: Could not open sepolicy: %s\n",
strerror(errno));
return -1;
}
if (fstat(fd, &sb) < 0) {
selinux_log(SELINUX_ERROR, "SELinux: Could not stat %s: %s\n",
sepolicy_file[policy_index], strerror(errno));
close(fd);
return -1;
}
map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (map == MAP_FAILED) {
selinux_log(SELINUX_ERROR, "SELinux: Could not map %s: %s\n",
sepolicy_file[policy_index], strerror(errno));
close(fd);
return -1;
}
// 将 sepolicy 文件加载到内核中, map 需要导入的 data 内容
rc = security_load_policy(map, sb.st_size);
if (rc < 0) {
selinux_log(SELINUX_ERROR, "SELinux: Could not load policy: %s\n",
strerror(errno));
munmap(map, sb.st_size);
close(fd);
return -1;
}
munmap(map, sb.st_size);
close(fd);
selinux_log(SELINUX_INFO, "SELinux: Loaded policy from %s\n", sepolicy_file[policy_index]);
return 0;
}
init 通过 mmap 的方式,将 sepolicy 文件传递给 kernel。init 使用了 libselinux 提供的 API 完成相关操作。而 libselinux 则是通过 /sys/fs/selinux 下的文件来完成和 Kernel 中 SELinux 模块的交互。
set_policy_index 和 security_load_policy 如下:
// /external/libselinux/src/android.c
static void set_policy_index(void)
{
int fd_base = -1, fd_override = -1;
struct stat sb_base;
struct stat sb_override;
void *map_base, *map_override;
policy_index = 0;
fd_base = open(POLICY_BASE_VERSION, O_RDONLY | O_NOFOLLOW);
if (fd_base < 0)
return;
if (fstat(fd_base, &sb_base) < 0)
goto close_base;
fd_override = open(POLICY_OVERRIDE_VERSION, O_RDONLY | O_NOFOLLOW);
if (fd_override < 0)
goto close_base;
if (fstat(fd_override, &sb_override) < 0)
goto close_override;
if (sb_base.st_size != sb_override.st_size)
goto close_override;
map_base = mmap(NULL, sb_base.st_size, PROT_READ, MAP_PRIVATE, fd_base, 0);
if (map_base == MAP_FAILED)
goto close_override;
map_override = mmap(NULL, sb_override.st_size, PROT_READ, MAP_PRIVATE, fd_override, 0);
if (map_override == MAP_FAILED)
goto unmap_base;
if (memcmp(map_base, map_override, sb_base.st_size) != 0)
goto unmap_override;
if (access(sepolicy_file[1], R_OK) != 0)
goto unmap_override;
if (access(seopts[1].value, R_OK) != 0)
goto unmap_override;
if (access(seopts_prop[1].value, R_OK) != 0)
goto unmap_override;
if (access(seopts_service[1].value, R_OK) != 0)
goto unmap_override;
if (access(seapp_contexts_file[1], R_OK) != 0)
goto unmap_override;
policy_index = 1;
unmap_override:
munmap(map_override, sb_override.st_size);
unmap_base:
munmap(map_base, sb_base.st_size);
close_override:
close(fd_override);
close_base:
close(fd_base);
return;
}
将策略内容写入 /sys/fs/selinux/load
// /external/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;
}
// selinux_mnt 被设置为了 /sys/fs/selinux
snprintf(path, sizeof path, "%s/load", selinux_mnt);
// fd 对应的是 /sys/fs/selinux/load
fd = open(path, O_RDWR);
if (fd < 0)
return -1;
// 传入参数 data 为策略文件读取到的内容; fd 为 /sys/fs/selinux/load 文件句柄
// 即 将策略内容写入 /sys/fs/selinux/load
ret = write(fd, data, len);
close(fd);
if (ret < 0)
return -1;
return 0;
}
selinux_is_enforcing()
// /system/core/init/Init.cpp
// 命令行中得到的信息
static bool selinux_is_enforcing(void)
{
// ALLOW_DISABLE_SELINUX 定义在了 Android.mk
if (ALLOW_DISABLE_SELINUX) {
return selinux_status_from_cmdline() == SELINUX_ENFORCING;
}
return true;
}
这里调用的 selinux_status_from_cmdline() 文章 selinux_is_disabled 部分已经分析,这里略过了。
设置 selinux 的模式:
security_setenforce 用于设置 selinux 的工作模式。selinux 有两种工作模式:
1、”permissive”,所有的操作都被允许(即没有MAC),但是如果违反权限的话,会记录日志
2、”enforcing”,所有操作都会进行权限检查。在一般的终端中,应该工作于 enforing 模式
// /system/core/init/setenforce.c
int security_setenforce(int value)
{
int fd, ret;
char path[PATH_MAX];
char buf[20];
if (!selinux_mnt) {
errno = ENOENT;
return -1;
}
// selinux_mnt 被设置为了 /sys/fs/selinux
snprintf(path, sizeof path, "%s/enforce", selinux_mnt);
// fd 对应的是 /sys/fs/selinux/enforce
fd = open(path, O_RDWR);
if (fd < 0)
return -1;
snprintf(buf, sizeof buf, "%d", value);
// 即 将策略内容 value 值写入 /sys/fs/selinux
ret = write(fd, buf, strlen(buf));
close(fd);
if (ret < 0)
return -1;
return 0;
}
导入和设置失败时
当 selinux_android_load_policy() 和 security_setenforce(is_enforcing) 失败时,会调用 security_failure() 重启 Android 进行 recovery
// /system/core/init/init.cpp
static void security_failure() {
ERROR("Security failure; rebooting into recovery mode...\n");
android_reboot(ANDROID_RB_RESTART2, 0, "recovery");
while (true) { pause(); } // never reached
}
android_reboot 代码如下:
// /system/core/libcutils/android_reboot.c
int android_reboot(int cmd, int flags UNUSED, const char *arg)
{
int ret;
sync();
remount_ro();
switch (cmd) {
case ANDROID_RB_RESTART:
ret = reboot(RB_AUTOBOOT);
break;
case ANDROID_RB_POWEROFF:
ret = reboot(RB_POWER_OFF);
break;
case ANDROID_RB_RESTART2:
// 走这条 case 进行 recovery
ret = syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
LINUX_REBOOT_CMD_RESTART2, arg);
break;
default:
ret = -1;
}
return ret;
}
启动第二阶段的 selinux 初始化
第二阶段的启动 selinux 初始化执行 selinux_init_all_handles() 函数,创建并设置文件 handler,创建 prophandler。
// /system/core/init/init.cpp
static void selinux_init_all_handles(void)
{
sehandle = selinux_android_file_context_handle();
selinux_android_set_sehandle(sehandle);
sehandle_prop = selinux_android_prop_context_handle();
}
创建文件 handler
// /external/libselinux/src/android.c
struct selabel_handle* selinux_android_file_context_handle(void)
{
struct selabel_handle *sehandle;
set_policy_index();
sehandle = selabel_open(SELABEL_CTX_FILE, &seopts[policy_index], 1);
if (!sehandle) {
selinux_log(SELINUX_ERROR, "%s: Error getting file context handle (%s)\n",
__FUNCTION__, strerror(errno));
return NULL;
}
if (!compute_contexts_hash(seopts, fc_digest)) {
selabel_close(sehandle);
return NULL;
}
selinux_log(SELINUX_INFO, "SELinux: Loaded file_contexts contexts from %s.\n",
seopts[policy_index].value);
return sehandle;
}
设置文件 handler
// /external/libselinux/src/android.c
void selinux_android_set_sehandle(const struct selabel_handle *hndl)
{
fc_sehandle = (struct selabel_handle *) hndl;
}
创建 prophandler。
// /external/libselinux/src/android.c
struct selabel_handle* selinux_android_prop_context_handle(void)
{
struct selabel_handle* sehandle;
set_policy_index();
sehandle = selabel_open(SELABEL_CTX_ANDROID_PROP,
&seopts_prop[policy_index], 1);
if (!sehandle) {
selinux_log(SELINUX_ERROR, "%s: Error getting property context handle (%s)\n",
__FUNCTION__, strerror(errno));
return NULL;
}
selinux_log(SELINUX_INFO, "SELinux: Loaded property_contexts from %s.\n",
seopts_prop[policy_index].value);
return sehandle;
}
至此,selinux_initialize 分析完毕。