1 简述
Selinux,全称为Security-Enhanced Linux,即安全增强型Linux,是Linux中的一种安全管控策略。传统的Linux访问控制通过DAC(Discretionary Access Control)控制,即给用户、用户组、其他用户授予不同的读写和可执行权限来控制文件的访问。Selinux基于MAC(Mandatory Access Control)控制,基于安全上下文和安全策略的安全机制,只有定义了允许访问的规则的才能访问,否则默认不能访问。
MAC在DAC的基础之上,首先进行DAC检查,然后才进行MAC检查,检查通过后才能进行访问。
2 Selinux基础
违背了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规则,则需要进行相应的修改。
3 init初始化selinux
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
...
3.1 读取selinux
首先看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 <