不积跬步无以至千里,补全自己的短板,完善体系,站在巨人的肩膀上,看到的更远,写这篇文章也算是对这个知识点的总结。
SElinux出现之前,Linux上的安全模型叫DAC(Discretionary Access Control 自主访问控制),它的核心思想就是:进程拥有的权限与执行它的用户权限相同,比如,以root用户启动camera, 那么camera就拥有root的用户权限,我们知道root的权限是很大的,可以说为所欲为。
所以DAC方式管理太过宽松,只要应用获得了root权限,可以在后台做很多我们不知道的事情,所以在4.4之前都建议不要root破解,我记得5.0之前还有很多应用提示“如果想要运行应用,需要root”,现在想想,不知道该应用做了哪些不可告人的事情。
后来,研究过AOSP的应该知道Android5.0算是一次大的跨度升级,在5.0上google启用了新的权限管理,增加了系统安全性,
SElinux是一种新的安全模型,称为MAC(Mandatory Access Control 强制访问控制),他的思想就是:任何进程想要在SElinux系统中干任何事, 都必须在安全策略配置文件中赋予权限,凡是没有出现在安全策略配置文件中的权限,进程没有权限操作任何事。
我们来看个例子
下面这条SELinux语句表示 允许(allow )hal_nfc_default域(domain)中的进程 ”创建“类型为nfc_vendor_data_file的文件
allow hal_nfc_default nfc_vendor_data_file:file { create rw_file_perms };
如果没有在hal_nfc.te中使用上例中的权限配置allow语句,则hal_nfc_default就无法在data目录创建文件,即使hal_nfc_default具有root权限
DAC和MAC对比总结:
1)Android 系统先做DAC检查, 如果没有通过DAC检查,操作直接失败,通过DAC后,再做MAC权限检查。
2)SELinux中也有用户的概念,但和Linux中原有的user概念不同, Linux中的超级用户root在SELinux中可能就是一个没权限,没地位打打酱油的“路人甲”。
写这篇文章的目的是跟着前辈的经验,能够看懂现有的策略文件,能够编写新添加的权限文件或语句。
Linux有种思想,即万事万物皆文件,普通文件是文件,目录是文件,设备是文件。
SELinux中,每种东西都会被赋予一个安全属性,官方说法叫Security Context。Security Context(以后用SContext表示)是一个字符串,主要由三部分组成,分为进程和文件两种。
SEAndroid中,进程的SContext可通过ps -ZA命令查看,
最左边的LABEL列值进程的SContext,以第一个u:r:init:s0为例,说明代表含义
文件的Security Context,可通过ls -lZ命令查看,
上图中倒数第二列为手机内根目录下文件和目录的SContext信息,同样以第一行u:object_r:vendor_file:s0为例,说明代表含义
根据SELinux规范,完整的SContext字符串为:
user:role:type[:range]
注意,方括号中的内容表示可选项,s0属于range中的一部分。
MAC(Mandatory Access Control 强制访问控制)基本管理单位是TEAC(Type Enforcement Access Control), 然后是高一级别的Role Based Accesc Control。RBAC是基于TE的,而TE也是SELinux中最主要的部分。我们常添加的allow就是TE的范畴。
3.1 根据 SELinux 规范,完整的 SELinux 策略规则语句格式为:
allow domains types:classes permissions;
- Domain - 一个进程或一组进程的标签。也称为域类型,因为它只是指进程的类型。
- Type - 一个对象(例如,文件、套接字)或一组对象的标签。
- Class - 要访问的对象(例如,文件、套接字)的类型。
- Permission - 要执行的操作(例如,读取、写入)。
= allow : 允许主体对客体进行操作
= neverallow :拒绝主体对客体进行操作
= dontaudit : 表示不记录某条违反规则的决策信息
= auditallow :记录某项决策信息,通常 SElinux 只记录失败的信息,应用这条规则后会记录成功的决策信息。
使用政策规则时将遵循的结构示例:
语句:
allow hal_nfc_default nfc_vendor_data_file:file { create rw_file_perms };
下面根据几个实例了解权限的写法
#允许zygote域中的进程search或getattr类型为appdomain的目录。注意,多个perm_set
#可用{}括起来
allow zygote appdomain:dir { getattr search };
#来个复杂点的:
#source_type为unconfineddomain target_type为一组type,由
#{ fs_type dev_type file_type }构成。object_class也包含两个,为{ chr_file file }#perm_set语法比较奇特,前面有一个~号。它表示除了{entrypoint relabelto}之外,{chr_file #file}这两个object_class所拥有的其他操作
allow unconfineddomain {fs_type dev_type file_type}:{ chr_file file } \
~{entrypoint relabelto};
#特殊符号除了~外,还有-号和*号,其中:
# 1):-号表示去除某项内容。
# 2):*号表示所有内容。
#下面这条语句中,source_type为属于appdomain,但不属于unconfinedomain的进程。
#而 *表示所有和capability2相关的权限
#neverallow:表示绝不允许。
neverallow { appdomain -unconfineddomain } self:capability2 *;
特别注意,前面曾提到说权限必须显示声明,没有声明的话默认就没有权限。那neverallow语句就没必要存在了。因为”无权限“是不需要声明的。确实如此,neverallow语句的作用只是在生成安全策略文件时进行检查,判断是否有违反neverallow语句的allow语句。
3.2 Object Class有哪些?
文件路径: system/sepolicy/private/security_classes
# file-related classes
class filesystem
class file #代表普通文件
class dir #代表目录
class fd #代表文件描述符
class lnk_file #代表链接文件
class chr_file #代表字符设备文件# network-related classes
class socket #socket
class tcp_socket
class udp_socket......
class binder #Android 平台特有的 binder
class zygote #Android 平台特有的 zygote
3.3 Permissions
指的是某种class 所拥有的权限,以file这种object class而言,拥有的permission如下
查看路径:system/sepolicy/private/access_vectors
#
# Define common prefixes for access vectors
#
# common common_name { permission_name ... }
#
# Define a common prefix for file access vectors.
#common file
{
ioctl
read
write
create
getattr
setattr
lock
relabelfrom
relabelto
append
map
unlink
link
rename
execute
quotaon
mounton
audit_access
open
execmod
watch
watch_mount
watch_sb
watch_with_perm
watch_reads
}
从上面例子可以看出SELinux定义perm set方式使用common命令,实际使用中我们可以参考该文件的定义
#除了common外,还有一种class命令也可定义perm set,如下面的例子:
#class命令的完整格式是:
#class class_name [ inherits common_name ] { permission_name ... }
#inherits表示继承了某个common定义的权限 注意,class命令定义的权限其实针对得就是
#某个object class。它不能被其他class继承
class dir inherits file {
add_name remove_name reparent search rmdir open audit_access execmod
}
#来看SEAndroid中的binder和property_service这两个Object class定义了哪些操作权限
class binder {
impersonate call set_context_mgr transfer }
class property_service { set }
3.4 type
type命令的完整格式为:type type_id [alias alias_id,] [attribute_id]
其中,方括号中的内容为可选。alias指定了type的别名,可以指定多个别名。
上面理论讲述了一堆,估计你已经有点不耐烦,下面我们以实际使用解决问题来说说,
SELinux分两种模式:enforceing mode(限制访问) 和permissive mode(只审查权限,不限制)
4.1怎么关闭这个权限限制呢?
4.1.1 临时关闭,当你的手机是debug模式时可以通过adb shell设置
setenfroce 0
setenforce 命令修改的是 /sys/fs/selinux/enforce 节点的值,0 表示permissive,1 表示 enforcing,是 kernel 意义上的修改 selinux 的策略。系统重启后,节点值会复位
4.1.2永久关闭
SECURITY_SELINUX 设置为 false,重新编译 kernel
make bootimage 重新编译boot,刷机验证
4.1.3修改代码
设置 ro.boot.selinux=permissive 属性,并且修改在 system/core/init/Android.mk 中设置用于 user 版本下 selinux 模式为 permissive
ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
init_options += \
-DALLOW_LOCAL_PROP_OVERRIDE=1 \
-DALLOW_PERMISSIVE_SELINUX=1 \
-DREBOOT_BOOTLOADER_ON_PANIC=1 \
-DDUMP_ON_UMOUNT_FAILURE=1
else
init_options += \
-DALLOW_LOCAL_PROP_OVERRIDE=0 \
-DALLOW_PERMISSIVE_SELINUX=1 \ // 修改为1,表示允许 selinux 为 permissive
-DREBOOT_BOOTLOADER_ON_PANIC=0 \
-DDUMP_ON_UMOUNT_FAILURE=0
endif
4.2 系统运行权限不够,avc异常处理
在系统开发过程中,经常遇到的一个问题就是没有权限操作某个动作,直观的就是功能未生效。出现此类问题我们可以通过adb logcat 抓取系统log,过滤log中avc子串,看看是否有报对应进程的权限问题。或者关掉权限看看功能是否正常。
案例1,我们抓到一份异常
可以看到INxpNfc没有find权限,分析如下:
谁缺少权限:scontext=u:r:hal_nfc_default:s0 ---->hal_nfc_default
对什么缺少权限:tcontext=u:object_r:default_android_hwservice:s0 ---> default_android_hwservice
什么类型的权限:tclass=hwservice_manager ---> hwservice_manager
缺少什么权限:denied{find} --->find
找到关于nfc的te文件按照套路添加定义
allow scontext_type tcontext_type:tclass_type { denied }
故 allow hal_nfc_default default_android_hwservice:hwservice_manager {find}
也可以使用audit2allow工具生成这个sepolicy规则,路径:AOSP/external/selinux/prebuilts/bin
注意:使用这个工具,需要先 souce build/envsetup.sh -->lunch 之后,audit2allow -i avc_log.txt 会在终端输出,如果想输出到指定文件,使用-p 后面跟路径名
BOARD_SEPOLICY_DIRS += device/mediatek/sepolicy 通过这个命令添加厂家自定义的 sepolicy 规则
新添加的这个权限有可能会引起neverallowed,出现问题可以重新定义相同类型的type,以规避权限检查
案例2
SELinux : avc: denied { find } for interface=android.hardware.secure_element::ISecureElement sid=u:r:nfc:s0 pid=21355 scontext=u:r:nfc:s0 tcontext=u:object_r:hal_secure_element_hwservice:s0 tclass=hwservice_manager permissive=0
添加权限allow nfc hal_secure_element_hwservice:hwservice_manager find;
会发现编译报错
libsepol.report_failure: neverallow on line 5 of system/sepolicy/public/hal_secure_element.te (or line 19894 of policy.conf) violated by allow nfc hal_secure_element_hwservice:hwservice_manager { find };
libsepol.check_assertions: 1 neverallow failures occurred
Error while expanding policy
解决方案添加hal_client_domain(nfc, hal_secure_element)
4.3 SELinux Android 源码架构
- externel/selinux:包含编译 sepolicy 策略文件的一些实用构建工具
- external/selinux/libselinux:提供了帮助用户进程使用 SELinux 的一些函数
- external/selinux/libsepol:提供了供安全策略文件编译时使用的一个工具 checkcon
- system/sepolicy:包含 Android SELinux 核心安全策略(te 文件),编译生成 sepolicy 文件
- file_contexts: 系统中所有文件的安全上下文
- property_contexts: 系统中所有属性的安全上下文
- seapp_contexts:定义用户、seinfo和域之间的关系,用于确定用户进程的安全上下文
- sepolicy:二进制文件,保存系统安全策略,系统初始化时会把它设置到内核中SELinux 虚拟文件系统在 sys/fs/selinux 下,该目录下的文件是 SELinux 内核和用户进程进行通信的接口,libselinux 就是利用这边的接口进行操作
4.3.1 init进程SEAndroid启动源码分析
文件路径:/system/core/init/init.cpp
int main(int argc, char** argv) {
...
if (is_first_stage) {
...
// Set up SELinux, loading the SELinux policy.
selinux_initialize(true);
// We're in the kernel domain, so re-exec init to transition to the init domain now
// that the SELinux policy has been loaded.
if (selinux_android_restorecon("/init", 0) == -1) {
PLOG(ERROR) << "restorecon failed";
security_failure();
}
...
}
}
可以看到 SEAndroid 的启动设置在 init 进程内核态执行(first_stage)过程中,selinux_initialize 函数就是主要的初始化过程,包含加载 sepolicy 策略文件到内核的 LSM 模块中。
文件路径:/system/core/init/init.cpp
static void selinux_initialize(bool in_kernel_domain) {
Timer t;
selinux_callback cb;
cb.func_log = selinux_klog_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
// 标识 init 进程的内核态执行和用户态执行
if (in_kernel_domain) {
LOG(INFO) << "Loading SELinux policy";
if (!selinux_load_policy()) {
panic();
}
bool kernel_enforcing = (security_getenforce() == 1);
bool is_enforcing = selinux_is_enforcing();
if (kernel_enforcing != is_enforcing) {
if (security_setenforce(is_enforcing)) {
PLOG(ERROR) << "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false");
security_failure();
}
}
std::string err;
if (!WriteFile("/sys/fs/selinux/checkreqprot", "0", &err)) {
LOG(ERROR) << err;
security_failure();
}
// init's first stage can't set properties, so pass the time to the second stage.
setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1);
} else {
selinux_init_all_handles();
}
}
selinux_set_callback
selinux_set_callback 用来向 libselinux 设置 SEAndroid 日志和审计回调函数
selinux_load_policy
这个函数用来加载 sepolicy 策略文件,并通过 mmap 映射的方式将 sepolicy 的安全策略加载到 SELinux LSM 模块中去。
下面具体分析一下流程:
文件路径:system/core/init/init.cpp
static bool selinux_load_policy() {
return selinux_is_split_policy_device() ? selinux_load_split_policy()
: selinux_load_monolithic_policy();
}
这里区分了从哪里加载安全策略文件,第一个是从 /vendor/etc/selinux/precompiled_sepolicy 读取,第二个是直接从根目录 /sepolicy
中读取,最终调用的方法都是 selinux_android_load_policy_from_fd
int selinux_android_load_policy_from_fd(int fd, const char *description)
{
int rc;
struct stat sb;
void *map = NULL;
static int load_successful = 0;
/*
* Since updating policy at runtime has been abolished
* we just check whether a policy has been loaded before
* and return if this is the case.
* There is no point in reloading policy.
*/
if (load_successful){
selinux_log(SELINUX_WARNING, "SELinux: Attempted reload of SELinux policy!/n");
return 0;
}
set_selinuxmnt(SELINUXMNT);
if (fstat(fd, &sb) < 0) {
selinux_log(SELINUX_ERROR, "SELinux: Could not stat %s: %s\n",
description, strerror(errno));
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",
description, strerror(errno));
return -1;
}
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);
return -1;
}
munmap(map, sb.st_size);
selinux_log(SELINUX_INFO, "SELinux: Loaded policy from %s\n", description);
load_successful = 1;
return 0;
}
(1)set_selinuxmnt 函数设置内核 SELinux 文件系统路径,这里的值为 /sys/fs/selinux,SELinux 文件系统用来与内核空间 SELinux LSM 模块空间。
(2)通过 fstat 获取 sepolicy 文件(fd 值)的状态信息,通过 mmap 函数将文件内容映射到内存中,起始地址为 map
(3)security_load_policy 调用另一个 security_load_policy 函数将已经映射到内存中的 SEAndroid 的安全策略加载到内核空间的 SELinux LSM 模块中去。
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;
}
函数 security_load_policy 的实现很简单,它首先打开 /sys/fs/selinux/load 文件,然后将参数 data 所描述的安全策略写入到这个文件中去。由于 /sys/fs/selinux 是由内核空间的 SELinux LSM 模块导出来的文件系统接口,因此当我们将安全策略写入到位于该文件系统中的 load 文件时,就相当于是将安全策略从用户空间加载到 SELinux LSM 模块中去了。
以后 SELinux LSM 模块中的 Security Server 就可以通过它来进行安全检查
(4)加载完成,释放 sepolicy 文件占用的内存,并且关闭 sepolicy 文件
selinux_init_all_handles
在 init 进程的用户态启动过程中会调用这个函数初始化 file_context、 property_context 相关内容 handler,根据前面的描述,init 进程给一些文件或者系统属性进行安全上下文检查时会使用 libselinux 的 API,查询文件根目录下 file_context、file_context 的安全上下文内容。所以需要先得到这些文件的 handler,以便可以用来查询系统文件和系统属性的安全上下文。
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();
}
4.3.2 SEAndroid 编译
audit2allow -i avc_log.txt 即可自动输出生成的policy
source build/envsetup.sh
lunch
mma system/sepolicy/
adb push out/target/product/xx/vendor/etc/selinux/ vendor/etc/selinux/
adb push out/target/product/xx/system/etc/selinux/ system/etc/selinux/
参考:
深入理解SELinux/SEAndroid(第一部分) - 邓凡平的个人页面 - OSCHINA - 中文开源技术交流社区
android 8.1 安全机制 — SEAndroid & SELinux_岁月斑驳7的博客-CSDN博客_android selinux