赞
踩
Selinux,全称为Security-Enhanced Linux,即安全增强型Linux,是Linux中的一种安全管控策略。传统的Linux访问控制通过DAC(Discretionary Access Control)控制,即给用户、用户组、其他用户授予不同的读写和可执行权限来控制文件的访问。Selinux基于MAC(Mandatory Access Control)控制,基于安全上下文和安全策略的安全机制,只有定义了允许访问的规则的才能访问,否则默认不能访问。
MAC在DAC的基础之上,首先进行DAC检查,然后才进行MAC检查,检查通过后才能进行访问。
违背了selinux规则会打印日志,日志关键字为avc
LocTimerMsgTask: type=1400 audit(0.0:8): avc: denied { call } for scontext=u:r:location:s0 tcontext=u:r:qtidataservices_app:s0:c56,c256,c512,c768 tclass=binder permissive=0
上面就是一个典型的selinux问题,透露出了以下信息
scontext=u:r:location:s0 请求访问者的selinux标签,这个通常是进程
tcontext=u:r:qtidataservices_app:s0 被请求者的selinux标签,通常是文件或者服务等
tclass=binder 被请求的类型是binder
denied { call } 表示请求者对访问者请求被selinux拒绝的操作是call操作
permissive=0 selinux拒绝了这个访问
接下来一一解析上面的,首先是selinux标签
- u:r:location:s0这个就是selinux的上下文信息,其中u指用户user,r指角色role,location是类型type,s0是range,表示安全级别。所以selinux的基本格式为user:role:type[:range]。在Android中,只有一个user,一个range。而所有的进程,都是role都是r,所有的文件,role都是object_r。
- tclass表示的是所要访问的对象的类别,通常有file,binder,dir等
- call是操作,针对不同的class,有不同的操作,比如对于file来说,有create,remove等文件的操作,对于binder来说,就有bind,call这种操作
- permissive是selinux的模式,selinux可以设置两种模式,通常默认是enforcing模式,即强制模式,所有没有通过selinux检查的操作都被阻止,并打印日志,这里的permissive=0表示的就是enforcing模式。还有一种就是permissive宽容模式,违反selinux规则的操作并不会被阻止,二是允许其访问,但是会打印avc日志。
对于上面的avc报错,如果需要修改的话,需要添加如下:
allow location qtidataservices_app:binder {call};
这个就是对avc日志的修改,允许location这个type,对qtidataservices_app的binder进行call的操作。这一行通常添加到location.te文件中,因为很多模块都有自己的te文件进行管理。
通常在domain.te中定义了相关的selinux的neverallow规则,如果在添加allow规则时,编译出现neverallow规则,则需要进行相应的修改。
system/core/init/main.cpp
if (!strcmp(argv[1], "selinux_setup")) {
return SetupSelinux(argv);
}
system/core/init/selinux.cpp
int SetupSelinux(char** argv) {
...
// Read the policy before potentially killing snapuserd.
std::string policy;
ReadPolicy(&policy); //读取策略文件
CleanupApexSepolicy();
...
LoadSelinuxPolicy(policy); //感觉像是写入内核,外部依赖的selinux开源项目代码
...
SelinuxSetEnforcement(); //设置selinux模式为enforcing
...
const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage", nullptr};
execv(path, const_cast<char**>(args)); //再次调用init可执行文件,参数为second_stage
...
首先看ReadPolicy
void ReadPolicy(std::string* policy) {
PolicyFile policy_file;
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;
}
}
bool IsSplitPolicyDevice() {
return access(plat_policy_cil_file, R_OK) != -1;
}
constexpr const char plat_policy_cil_file[] = "/system/etc/selinux/plat_sepolicy.cil";
IsSplitPolicyDevice用于判断设备是不是分策略设备,android8之后将策略拆分为平台策略和供应商策略。所以目前的版本,这个函数返回为true,是根据手机中这个plat_sepolicy.cil文件是否存在来判断的。
很明显,这个文件是存在的。
所以将调用OpenSplitPolicy,盲猜这个就是实际添加策略的函数了,传入的参数是policy_file,首先看看PolicyFile是个啥
struct PolicyFile {
unique_fd fd;
std::string path;
};
PolicyFile是一个结构体,包含一个文件描述符和一个string字符串的path。
bool OpenSplitPolicy(PolicyFile* policy_file) {
// IMPLEMENTATION NOTE: Split policy consists of three or more CIL files:
// * platform -- policy needed due to logic contained in the system image,
// * vendor -- policy needed due to logic contained in the vendor image,
// * mapping -- mapping policy which helps preserve forward-compatibility of non-platform policy
// with newer versions of platform policy.
// * (optional) policy needed due to logic on product, system_ext, odm, or apex.
// secilc is invoked to compile the above three policy files into a single monolithic policy
// file. This file is then loaded into the kernel.
//android8以后策略分离。分离的策略至少包含3部分以上,platform平台策略,vendor供应商策略,mapping用于向后兼容的策略。其他的策略则包括product,system_ext,odm或者是apex。secilc就是将上面的这些策略文件编译成一个单个的整体策略文件,然后加载到内核中。
const auto userdebug_plat_sepolicy = GetUserdebugPlatformPolicyFile();
const bool use_userdebug_policy = userdebug_plat_sepolicy.has_value();
if (use_userdebug_policy) {
LOG(INFO) << "Using userdebug system sepolicy " << *userdebug_plat_sepolicy;
}
// Load precompiled policy from vendor image, if a matching policy is found there. The policy
// must match the platform policy on the system image.
// use_userdebug_policy requires compiling sepolicy with userdebug_plat_sepolicy.cil.
// Thus it cannot use the precompiled policy from vendor image.
if (!use_userdebug_policy) {
if (auto res = FindPrecompiledSplitPolicy(); res.ok()) {
unique_fd fd(open(res->c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
if (fd != -1) {
policy_file->fd = std::move(fd);
policy_file->path = std::move(*res);
return true;
}
} else {
LOG(INFO) << res.error();
}
}
// 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";
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;
}
// Determine which mapping file to include
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();
}
std::string system_ext_mapping_file("/system_ext/etc/selinux/mapping/" + vend_plat_vers +
".cil");
if (access(system_ext_mapping_file.c_str(), F_OK) == -1) {
system_ext_mapping_file.clear();
}
std::string system_ext_compat_cil_file("/system_ext/etc/selinux/mapping/" + vend_plat_vers +
".compat.cil");
if (access(system_ext_compat_cil_file.c_str(), F_OK) == -1) {
system_ext_compat_cil_file.clear();
}
std::string product_policy_cil_file("/product/etc/selinux/product_sepolicy.cil");
if (access(product_policy_cil_file.c_str(), F_OK) == -1) {
product_policy_cil_file.clear();
}
std::string product_mapping_file("/product/etc/selinux/mapping/" + vend_plat_vers + ".cil");
if (access(product_mapping_file.c_str(), F_OK) == -1) {
product_mapping_file.clear();
}
std::string vendor_policy_cil_file("/vendor/etc/selinux/vendor_sepolicy.cil");
if (access(vendor_policy_cil_file.c_str(), F_OK) == -1) {
LOG(ERROR) << "Missing " << vendor_policy_cil_file;
return false;
}
std::string plat_pub_versioned_cil_file("/vendor/etc/selinux/plat_pub_versioned.cil");
if (access(plat_pub_versioned_cil_file.c_str(), F_OK) == -1) {
LOG(ERROR) << "Missing " << plat_pub_versioned_cil_file;
return false;
}
// odm_sepolicy.cil is default but optional.
std::string odm_policy_cil_file("/odm/etc/selinux/odm_sepolicy.cil");
if (access(odm_policy_cil_file.c_str(), F_OK) == -1) {
odm_policy_cil_file.clear();
}
// apex_sepolicy.cil is default but optional.
std::string apex_policy_cil_file("/dev/selinux/apex_sepolicy.cil");
if (access(apex_policy_cil_file.c_str(), F_OK) == -1) {
apex_policy_cil_file.clear();
}
const std::string version_as_string = std::to_string(SEPOLICY_VERSION);
// clang-format off
std::vector<const char*> compile_args {
"/system/bin/secilc",
use_userdebug_policy ? *userdebug_plat_sepolicy : 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
if (!plat_compat_cil_file.empty()) {
compile_args.push_back(plat_compat_cil_file.c_str());
}
if (!system_ext_policy_cil_file.empty()) {
compile_args.push_back(system_ext_policy_cil_file.c_str());
}
if (!system_ext_mapping_file.empty()) {
compile_args.push_back(system_ext_mapping_file.c_str());
}
if (!system_ext_compat_cil_file.empty()) {
compile_args.push_back(system_ext_compat_cil_file.c_str());
}
if (!product_policy_cil_file.empty()) {
compile_args.push_back(product_policy_cil_file.c_str());
}
if (!product_mapping_file.empty()) {
compile_args.push_back(product_mapping_file.c_str());
}
if (!plat_pub_versioned_cil_file.empty()) {
compile_args.push_back(plat_pub_versioned_cil_file.c_str());
}
if (!vendor_policy_cil_file.empty()) {
compile_args.push_back(vendor_policy_cil_file.c_str());
}
if (!odm_policy_cil_file.empty()) {
compile_args.push_back(odm_policy_cil_file.c_str());
}
if (!apex_policy_cil_file.empty()) {
compile_args.push_back(apex_policy_cil_file.c_str());
}
compile_args.push_back(nullptr);
if (!ForkExecveAndWaitForCompletion(compile_args[0], (char**)compile_args.data())) {
unlink(compiled_sepolicy);
return false;
}
unlink(compiled_sepolicy);
policy_file->fd = std::move(compiled_sepolicy_fd);
policy_file->path = compiled_sepolicy;
return true;
}
接下来分析下这个函数
// 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";
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;
}
首先,使用mkostemp函数创建了一个临时的文件,并返回其文件描述符compiled_sepolicy_fd
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();
}
类似这种是遍历各个镜像包中对应目录下是否有编译后的selinux规则文件
// clang-format off
std::vector<const char*> compile_args {
"/system/bin/secilc",
use_userdebug_policy ? *userdebug_plat_sepolicy : 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
这里创建了一个vector,并存储了一些默认参数。第一个参数是一个可执行文件。
if (!plat_compat_cil_file.empty()) {
compile_args.push_back(plat_compat_cil_file.c_str());
}
上面遍历了哪些镜像中存在cil文件,这里把存在的都添加到compile_args这个vector中。
if (!ForkExecveAndWaitForCompletion(compile_args[0], (char**)compile_args.data())) {
unlink(compiled_sepolicy);
return false;
}
unlink(compiled_sepolicy);
添加完成后调用ForkExecveAndWaitForCompletion函数,传入的参数是第一个就是上面的那个可执行文件,第二个参数是整个vector的数据指针。
// Forks, executes the provided program in the child, and waits for the completion in the parent.
// Child's stderr is captured and logged using LOG(ERROR).
bool ForkExecveAndWaitForCompletion(const char* filename, char* const argv[]) {
// Create a pipe used for redirecting child process's output.
// * pipe_fds[0] is the FD the parent will use for reading.
// * pipe_fds[1] is the FD the child will use for writing.
int pipe_fds[2];
if (pipe(pipe_fds) == -1) {
PLOG(ERROR) << "Failed to create pipe";
return false;
}
pid_t child_pid = fork();
if (child_pid == -1) {
PLOG(ERROR) << "Failed to fork for " << filename;
return false;
}
if (child_pid == 0) {
// fork succeeded -- this is executing in the child process
// Close the pipe FD not used by this process
close(pipe_fds[0]);
// Redirect stderr to the pipe FD provided by the parent
if (TEMP_FAILURE_RETRY(dup2(pipe_fds[1], STDERR_FILENO)) == -1) {
PLOG(ERROR) << "Failed to redirect stderr of " << filename;
_exit(127);
return false;
}
close(pipe_fds[1]);
if (execv(filename, argv) == -1) {
PLOG(ERROR) << "Failed to execve " << filename;
return false;
}
// Unreachable because execve will have succeeded and replaced this code
// with child process's code.
_exit(127);
return false;
} else {
// fork succeeded -- this is executing in the original/parent process
// Close the pipe FD not used by this process
close(pipe_fds[1]);
// Log the redirected output of the child process.
// It's unfortunate that there's no standard way to obtain an istream for a file descriptor.
// As a result, we're buffering all output and logging it in one go at the end of the
// invocation, instead of logging it as it comes in.
const int child_out_fd = pipe_fds[0];
std::string child_output;
if (!android::base::ReadFdToString(child_out_fd, &child_output)) {
PLOG(ERROR) << "Failed to capture full output of " << filename;
}
close(child_out_fd);
if (!child_output.empty()) {
// Log captured output, line by line, because LOG expects to be invoked for each line
std::istringstream in(child_output);
std::string line;
while (std::getline(in, line)) {
LOG(ERROR) << filename << ": " << line;
}
}
// Wait for child to terminate
int status;
if (TEMP_FAILURE_RETRY(waitpid(child_pid, &status, 0)) != child_pid) {
PLOG(ERROR) << "Failed to wait for " << filename;
return false;
}
if (WIFEXITED(status)) {
int status_code = WEXITSTATUS(status);
if (status_code == 0) {
return true;
} else {
LOG(ERROR) << filename << " exited with status " << status_code;
}
} else if (WIFSIGNALED(status)) {
LOG(ERROR) << filename << " killed by signal " << WTERMSIG(status);
} else if (WIFSTOPPED(status)) {
LOG(ERROR) << filename << " stopped by signal " << WSTOPSIG(status);
} else {
LOG(ERROR) << "waitpid for " << filename << " returned unexpected status: " << status;
}
return false;
}
}
创建了两个管道,用于子进程和父进程之间的通信。子进程中通过execv执行传入的可执行文件,执行参数就是存储各个镜像包中selinux规则cil的文件路径,父进程等待子进程执行完成后返回。这里就是将所有的策略文件编译成一个整体。
这里执行完成后执行unlink函数应该是减少一个文件的访问进程数。因为ForkExecveAndWaitForCompletion中创建了一个子进程,也访问了这个文件描述符,但是子进程已经执行完了。
policy_file->fd = std::move(compiled_sepolicy_fd);
policy_file->path = compiled_sepolicy;
最后将这个临时fd和fd的路径封装到policy_file中返回。
void ReadPolicy(std::string* policy) {
PolicyFile policy_file;
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;
}
}
ReadFdToString将policy从fd中读取出来,返回给policy这个字符串。
读取policy结束后,接下来分析LoadSelinuxPolicy,加载policy,这个函数传入的参数就是ReadPolicy中的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";
}
}
sys/fs目录是linux中描述文件系统的,手机中存在selinux目录。
external/selinux/libselinux/src/init.c
void set_selinuxmnt(const char *mnt)
{
selinux_mnt = strdup(mnt);
}
``
这段代码已经位于selinux核心库中了,这里是设置selinux文件目录,即sys/fs/selinux。
***external/selinux/libselinux/src/load_policy.c***
```cpp
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;
}
将selinux的cil文件路径写到了sys/fs/selinux/load中,应该就是将编译后的整个策略文件加载进内核。后面怎么处理,目前尚不清楚,查阅发现selinux很多检查处于内核,所以后面应该是内核处理访问权限,并报avc日志。
selinux分为enforcing强制模式和permissive宽容模式,默认是开启强制模式的
void SelinuxSetEnforcement() {
bool kernel_enforcing = (security_getenforce() == 1);
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();
}
}
首先判断内核设置的selinux模式
external/selinux/libselinux/src/getenforce.c
int security_getenforce(void)
{
int fd, ret, enforce = 0;
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_RDONLY | O_CLOEXEC);
if (fd < 0)
return -1;
memset(buf, 0, sizeof buf);
ret = read(fd, buf, sizeof buf - 1);
close(fd);
if (ret < 0)
return -1;
if (sscanf(buf, "%d", &enforce) != 1)
return -1;
return !!enforce;
}
这里是读取sys/fs/selinux/enforce来获取内核设置的selinux模式。
再来看看IsEnforcing函数
bool IsEnforcing() {
if (ALLOW_PERMISSIVE_SELINUX) {
return StatusFromProperty() == SELINUX_ENFORCING;
}
return true;
}
ALLOW_PERMISSIVE_SELINUX是在init的bp文件中添加的宏定义,对于debug模式的才设置为1,非debug模式设置为0。所以对于非debug模式,默认为enforcing。
EnforcingStatus StatusFromProperty() {
EnforcingStatus status = SELINUX_ENFORCING;
ImportKernelCmdline([&](const std::string& key, const std::string& value) {
if (key == "androidboot.selinux" && value == "permissive") {
status = SELINUX_PERMISSIVE;
}
});
if (status == SELINUX_ENFORCING) {
ImportBootconfig([&](const std::string& key, const std::string& value) {
if (key == "androidboot.selinux" && value == "permissive") {
status = SELINUX_PERMISSIVE;
}
});
}
return status;
}
这里判断enforcing模式有两个判断,一个是启动模块发送给内核的cmdline参数,另一个是属性androidboot.selinux是否为permissive,只要其中一个为permissive,则是permissive的。
void SelinuxSetEnforcement() {
bool kernel_enforcing = (security_getenforce() == 1);
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();
}
}
判断之后,如果内核的selinux与IsEnforcing获取到的不一致,则以获取的为准。所以如果我们想把整个都设置成permissive的,则将is_enforcing这个变量设置成false即可。也可以通过IsEnforcing函数里面的判断,通过设置cmdline和属性来达到。
const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage", nullptr};
execv(path, const_cast<char**>(args));
接下来回到selinux设置的主函数,这里selinux设置完成之后再次调用init,执行init的第二阶段启动。
selinux初始化流程为:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。