Android P SELinux (一) 基础概念 Android P SELinux (二) 开机初始化与策略文件编译过程 Android P SELinux (三) 权限检查原理与调试 Android P SELinux (四) CTS neverallow处理总结
CtsSecurityHostTestCases 首先CTS测试里面关于neverallow的用例,可以学习下这篇文章,代码是动态生成的 Android CTS中neverallow规则生成过程
下面主要分享一些遇到的 CTS neverallow问题的处理方法:
一、一些权限的解决方案
1.1、区分vendor域和system域
按照以往的经验,我们在Android P上添加客制化的脚本,还是喜欢用 #!/system/bin/sh
但是自从Android 8.0的Treble Project之后,vendor和system已经开始分离了,vendor下面的脚本要使用 #!/vendor/bin/sh ,这样能减少很多权限问题
举个例子:
/vendor/bin/testA.sh
#!/system/bin/sh
...
然后运行的时候会报下面的权限问题
[ 973.048826@1]- type=1400 audit(1619667483.432:167): avc: denied { read execute } for pid=4368 comm="testA.sh"
path="/system/bin/sh" dev="mmcblk0p17" ino=1073 scontext=u:r:testA:s0 tcontext=u:object_r:shell_exec:s0 tclass=file permissive=0
这个时候如果直接在te里面添加权限:
allow testA shell_exec:file { read execute };
添加完之后就会发现违反了neverallow规则
FAILED: out/target/product/xxxxx/obj/ETC/sepolicy_neverallows_intermediates/sepolicy_neverallows
/bin/bash -c "(rm -f out/target/product/xxxxx/obj/ETC/sepolicy_neverallows_intermediates/sepolicy_neverallows ) && (ASAN_OPTIONS=detect_leaks=0 out/host/linux-x86/bin/checkpolicy -M -c 30 -o out/target/product/xxxxx/obj/ETC/sepolicy_neverallows_intermediates/sepolicy_neverallows out/target/product/xxxxx/obj/ETC/sepolicy_neverallows_intermediates/policy.conf )"
libsepol.report_failure: neverallow on line 1047 of system/sepolicy/public/domain.te (or line 11499 of policy.conf) violated by allow testA shell_exec:file { execute };
libsepol.check_assertions: 1 neverallow failures occurred
Error while expanding policy
out/host/linux-x86/bin/checkpolicy: loading policy configuration from out/target/product/xxxxx/obj/ETC/sepolicy_neverallows_intermediates/policy.conf
ninja: build stopped: subcommand failed.
11:42:19 ninja failed with: exit status 1
原因在domain.te的注释里面说的很清楚了,vendor的组件不允许直接运行system的file
full_treble_only(`
neverallow {
修改脚本为:
#!/vendor/bin/sh
...
可以解决上述问题
但是改完之后,有些在/system/bin/ 下的命令就用不了,比如pm、am、logcat这些Android才有的命令,而不是在toybox里面的
如果实在要用,可以这样操作:
#!/vendor/bin/sh
am broadcast -a com.test.broadcast.toast -e Message "hello world" > /dev/console
直接运行也会报错
console:/
可以修改成:
/system/bin/cmd activity broadcast -a com.test.broadcast.toast -e Message "hello world" > /dev/console
te里面要补充关联上属性: vendor_executes_system_violators
type testA, domain, vendor_executes_system_violators;
1.2、setenforce的可行性
这个肯定是没有办法解决的,如果随便一个脚本都可以拥有关闭SELinux的权限,那还得了
在userdebug和eng版本上,可以先这样debug搞:
添加permissive testA;
对testA type以permissive状态运行
不过只能在eng和userdebug版本下才能用
type testA, coredomain, domain, mlstrustedsubject;
type testA_exec, exec_type, file_type;
permissive testA;
init_daemon_domain(testA)
要过CTS,这个权限可是天敌啊,主要就是有些功能想做的事情太多,权限太大,搞得非得要临时关闭SELinux来处理
跟着代码实现,不难发现setenforce命令的操作其实就是写节点/sys/fs/selinux/enforce
结合前面提到的权限检查原理,很快可以发现,这个权限就是我们无法绕过的根本原因了
static ssize_t sel_write_enforce(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
......
length = task_has_security(current, SECURITY__SETENFORCE);
if (length)
goto out;
......
}
这个权限检查就是后面报avc denied的地方了
allow testA kernel:security { setenforce };
对应的neverallow规则如下:
neverallow * kernel:security setenforce;
neverallow { domain -kernel } kernel:security setcheckreqprot;
这儿有个不是办法的办法:就是不影响现有命令的基础上,新增了一个节点,然后直接不做权限检查
diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
index 72c145dd799f..b2332f55415c 100644
--- a/security/selinux/selinuxfs.c
+++ b/security/selinux/selinuxfs.c
@@ -100,6 +100,7 @@ enum sel_inos {
SEL_ROOT_INO = 2,
SEL_LOAD,
SEL_ENFORCE,
+ SEL_ENABLE,
SEL_CONTEXT,
SEL_ACCESS,
SEL_CREATE,
@@ -193,6 +194,57 @@ static const struct file_operations sel_enforce_ops = {
.llseek = generic_file_llseek,
};
+#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
+static ssize_t sel_write_enable(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+
+{
+ char *page = NULL;
+ ssize_t length;
+ int new_value;
+
+ if (count >= PAGE_SIZE)
+ return -ENOMEM;
+
+
+ if (*ppos != 0)
+ return -EINVAL;
+
+ page = memdup_user_nul(buf, count);
+ if (IS_ERR(page))
+ return PTR_ERR(page);
+
+ length = -EINVAL;
+ if (sscanf(page, "%d", &new_value) != 1)
+ goto out;
+
+ if (new_value != selinux_enforcing) {
+ audit_log(current->audit_context, GFP_KERNEL, AUDIT_MAC_STATUS,
+ "enforcing=%d old_enforcing=%d auid=%u ses=%u",
+ new_value, selinux_enforcing,
+ from_kuid(&init_user_ns, audit_get_loginuid(current)),
+ audit_get_sessionid(current));
+ selinux_enforcing = new_value;
+ if (selinux_enforcing)
+ avc_ss_reset(0);
+ selnl_notify_setenforce(selinux_enforcing);
+ selinux_status_update_setenforce(selinux_enforcing);
+ }
+ length = count;
+out:
+ kfree(page);
+ return length;
+}
+#else
+#define sel_write_enable NULL
+#endif
+
+static const struct file_operations sel_enable_ops = {
+ .read = sel_read_enforce,
+ .write = sel_write_enable,
+ .llseek = generic_file_llseek,
+};
+
static ssize_t sel_read_handle_unknown(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
@@ -1790,6 +1842,7 @@ static int sel_fill_super(struct super_block *sb, void *data, int silent)
static struct tree_descr selinux_files[] = {
[SEL_LOAD] = {"load", &sel_load_ops, S_IRUSR|S_IWUSR},
[SEL_ENFORCE] = {"enforce", &sel_enforce_ops, S_IRUGO|S_IWUSR},
+ [SEL_ENABLE] = {"enable", &sel_enable_ops, S_IRUGO|S_IWUSR},
[SEL_CONTEXT] = {"context", &transaction_ops, S_IRUGO|S_IWUGO},
[SEL_ACCESS] = {"access", &transaction_ops, S_IRUGO|S_IWUGO},
[SEL_CREATE] = {"create", &transaction_ops, S_IRUGO|S_IWUGO},
然后开关就用:
关SELinux
echo 0 > /sys/fs/selinux/enable
开SELinux
echo 1 > /sys/fs/selinux/enable
权限还得补一下:
allow testA selinuxfs:file rw_file_perms;
selinux_check_access(testA)
相比setenforce,差异就是去掉了权限检查,开了一个后门,比较bug的存在
1.3、dac_override 解决办法
谷歌官方文档的一段话总结的很精辟
https://source.android.com/security/selinux/device-policy#granting_the_dac_override_capability 详见:selinux dac_override/dac_read_search问题处理思路
比如修改group的:
未修改前:
service testA /system/bin/testA.sh -start
user root
group root
disabled
oneshot
seclabel u:r:testA:s0
修改后:
service testA /system/bin/testA.sh -start
user root
group root system
disabled
oneshot
seclabel u:r:testA:s0
大部分情况下,我们可以直接ls -l 查看要访问的文件或者目录的user 和 group
但是有些情况下,我们很难搞清楚访问的文件或者目录所在的group是哪个,这些信息在avc denied里面是不会有的
可以在kernel中补充这些代码,打印出来(参考文章:Debugging DAC_OVERRIDE)
patch:
kernelcode$ git diff .
diff --git a/fs/namei.c b/fs/namei.c
index a5a05d3..9b8f0da 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -39,6 +39,8 @@
#include <linux/init_task.h>
#include <asm/uaccess.h>
+#include <linux/printk.h>
+
#include "internal.h"
#include "mount.h"
@@ -341,8 +343,12 @@ int generic_permission(struct inode *inode, int mask)
if (S_ISDIR(inode->i_mode)) {
- if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE))
- return 0;
+ if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE)) {
+ return 0;
+ } else {
+ pr_err("dac_override(dir) denied for cap=%3o inode=%lu with uid=%u gid=%u\n",
+ mask&0777, inode->i_ino, inode->i_uid.val, inode->i_gid.val);
+ }
if (!(mask & MAY_WRITE))
if (capable_wrt_inode_uidgid(inode,
CAP_DAC_READ_SEARCH))
@@ -354,9 +360,17 @@ int generic_permission(struct inode *inode, int mask)
* Executable DACs are overridable when there is
* at least one exec bit set.
*/
- if (!(mask & MAY_EXEC) || (inode->i_mode & S_IXUGO))
- if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE))
- return 0;
+
+
+
+ if (!(mask & MAY_EXEC) || (inode->i_mode & S_IXUGO)) {
+ if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE)) {
+ return 0;
+ } else {
+ pr_err("dac_override(file) denied for cap=%3o inode=%lu with uid=%u gid=%u\n",
+ mask&0777, inode->i_ino, inode->i_uid.val, inode->i_gid.val);
+ }
+ }
diff --git a/security/selinux/avc.c b/security/selinux/avc.c
index e60c79d..9b1dfad 100644
--- a/security/selinux/avc.c
+++ b/security/selinux/avc.c
@@ -734,7 +734,11 @@ static void avc_audit_post_callback(struct audit_buffer *ab, void *a)
if (ad->selinux_audit_data->denied) {
audit_log_format(ab, " permissive=%u",
ad->selinux_audit_data->result ? 0 : 1);
- }
+ if (ad->u.cap == 1) {
+ audit_log_format(ab, " cap=dac_override, dumping stack");
+ WARN_ON(ad->u.cap == 1);
+ }
+ }
}
打印信息如下:
[ 44.952920@0]- ------------[ cut here ]------------
[ 44.952983@0]- WARNING: CPU: 0 PID: 3901 at :739 avc_audit_post_callback+0x180/0x184
[ 44.961323@0]- Modules linked in: hardware_dmx(O) jpegenc(O) amvdec_av1(O) amvdec_mavs(O) encoder(O) amvdec_avs2(O) amvdec_vp9(O) amvdec_vc1(O) amvdec_real(O) amvdec_mmpeg4(O) amvdec_mpeg4(O) amvdec_mmpeg12(O) amvdec_mpeg12(O) amvdec_mmjpeg(O) amvdec_mjpeg(O) amvdec_h265(O) amvdec_h264mvc(O) amvdec_mh264(O) amvdec_h264(O) amvdec_avs(O) stream_input(O) decoder_common(O) video_framerate_adapter(O) firmware(O) media_clock(O) tb_detect(PO) mali(O) dnlp_alg(P)
[ 45.000226@0]d CPU: 0 PID: 3901 Comm: cmd Tainted: P W O 4.9.113
[ 45.007459@0]d Hardware name: Generic DT based system
[ 45.012491@0]d [bc4afac4+ 16][<c020dc10>] show_stack+0x20/0x24
[ 45.018343@0]d [bc4afae4+ 32][<c054394c>] dump_stack+0x90/0xac
[ 45.024201@0]d [bc4afb14+ 48][<c0228724>] __warn+0x104/0x11c
[ 45.029897@0]d [bc4afb2c+ 24][<c022880c>] warn_slowpath_null+0x30/0x38
[ 45.036456@0]d [bc4afb5c+ 48][<c04d4fec>] avc_audit_post_callback+0x180/0x184
[ 45.043606@0]d [bc4afbb4+ 88][<c04f20e0>] common_lsm_audit+0x260/0x7c0
[ 45.050165@0]d [bc4afbfc+ 72][<c04d5994>] slow_avc_audit+0x9c/0xb0
[ 45.056376@0]d [bc4afc64+ 104][<c04da364>] cred_has_capability+0x118/0x124
[ 45.063184@0]d [bc4afc74+ 16][<c04da3f0>] selinux_capable+0x38/0x3c
[ 45.069483@0]d [bc4afc9c+ 40][<c04d1370>] security_capable+0x4c/0x68
[ 45.075870@0]d [bc4afcac+ 16][<c0233d58>] ns_capable_common+0x7c/0x90
[ 45.082336@0]d [bc4afcc4+ 24][<c0233e00>] capable_wrt_inode_uidgid+0x28/0x54
[ 45.089409@0]d [bc4afcf4+ 48][<c03a5abc>] generic_permission+0x100/0x208
[ 45.096138@0]d [bc4afd14+ 32][<c0414df0>] kernfs_iop_permission+0x58/0x64
[ 45.102951@0]d [bc4afd34+ 32][<c03a5c84>] __inode_permission2+0xc0/0xf0
[ 45.109589@0]d [bc4afd44+ 16][<c03a5cfc>] inode_permission2+0x20/0x54
[ 45.116057@0]d [bc4afd8c+ 72][<c0396fb4>] SyS_faccessat+0xec/0x220
[ 45.122273@0]d [00000000+ 0][<c02085c0>] ret_fast_syscall+0x0/0x48
[ 45.128879@0]s DI: DI: tasklet schedule cost 128ms.
[ 45.133835@0]- ---[ end trace 40474d395a0d6c86 ]---
[ 45.138462@0]- dac_override(file) denied for cap= 22 inode=5 with uid=1000 gid=1000
[ 45.138640@1]- type=1400 audit(1620051338.180:75): avc: denied { dac_override } for pid=3900 comm="cmd" capability=1 scontext=u:r:testA:s0 tcontext=u:r:testA:s0 tclass=capability permissive=0 cap=dac_override, dumping stack
打印的gid在 system/core/include/private/android_filesystem_config.h 里面查找,就知道init.rc里面要加什么了
1.4、echo打印到终端
allow testA console_device:chr_file rw_file_perms;
1.5、读写U盘内容
读U盘:
allow testA mnt_media_rw_file:dir { search read open };
allow testA vfat:file r_file_perms;
allow testA vfat:dir r_dir_perms;
要写和创建的话,权限给大一点就行了:
allow testA mnt_media_rw_file:dir { search read open };
allow testA vfat:dir create_dir_perms;
allow testA vfat:file create_file_perms;
1.6、am 命令
allow testA activity_service:service_manager { find };
allow testA system_server:binder { call transfer };
binder_use(testA)
binder_call(system_server, testA)
pm命令同理
1.7、ioctl
[ 57.670632] type=1400 audit(1623998437.166:9): avc: denied { ioctl } for pid=4285 comm="Thread-43" path="socket:[30690]" dev="sockfs" ino=30690 ioctlcmd=0x8927 scontext=u:r:untrusted_app_25:s0:c512,c768 tcontext=u:r:untrusted_app_25:s0:c512,c768 tclass=tcp_socket permissive=0
参考: AndroidQ上ioctl变化
添加:
allowxperm untrusted_app_25 self:udp_socket ioctl SIOCGIFHWADDR;
二、常见违反neverallow规则的解决方案
2.1、指定文件、属性、服务的安全上下文,避免权限放大
这一类neverallow规则,其实搞明白为什么要这么做,是很容易解决的
有一个系统属性persist.sys.test
console:/data
u:object_r:system_prop:s0
假如你想要设置这个属性,加了一个权限
set_prop(testA, system_prop)
结果一编译就违反neverallow规则了,这是为什么?
因为没有特别指定,persist.sys. 开头的属性,它的安全上下问题都是u:object_r:system_prop:s0
/android/system/sepolicy/private/property_contexts
persist.sys. u:object_r:system_prop:s0
一旦你给了这个权限,意味着你可以设置的属性不仅仅是persist.sys.test 了,还可以是persist.sys.A 、persist.sys.B 等等
这就是权限放大,neverallow规则的限定,就是为了不给你这么玩
我们要做的就是,指定文件、属性、服务的安全上下文,收缩权限
下面有一些例子:
2.1.1、testNeverallowRules131 解决方案
<Test result="fail" name="testNeverallowRules131">
<Failure message="junit.framework.AssertionFailedError: The following errors were encountered when validating the SELinuxneverallow rule:">
<StackTrace>junit.framework.AssertionFailedError: The following errors were encountered when validating the SELinuxneverallow rule:
neverallow { domain -kernel -init -recovery } block_device:blk_file { open read write };
libsepol.report_failure: neverallow violated by allow testA block_device:blk_file { read write open };
libsepol.check_assertions: 1 neverallow failures occurred
</StackTrace>
</Failure>
</Test>
testA是因为要操作 /dev/block/testblock
console:/
brw------- 1 root root u:object_r:block_device:s0 179, 5 1970-01-01 08:00 /dev/block/testblock
给这个testblock block指定type,不然就会有权限放大的问题
sepolicy/device.te
type testblock_device, dev_type;
sepolicy/file_contexts
/dev/block/testblock u:object_r:testblock_device:s0
接着allow语句修改为:
allow testA testblock_device:blk_file { read write open };
2.1.2、testNeverallowRules198 解决方案
<Test result="fail" name="testNeverallowRules198">
<Failure message="junit.framework.AssertionFailedError: The following errors were encountered when validating the SELinuxneverallow rule:">
<StackTrace>junit.framework.AssertionFailedError: The following errors were encountered when validating the SELinuxneverallow rule:
neverallow { domain -coredomain -appdomain -data_between_core_and_vendor_violators -socket_between_core_and_vendor_violators -vendor_init } { coredomain_socket core_data_file_type unlabeled }:sock_file ~{ append getattr ioctl read write };
libsepol.report_failure: neverallow violated by allow testA property_socket:sock_file { open };
libsepol.check_assertions: 1 neverallow failures occurred
</StackTrace>
</Failure>
</Test>
查脚本,看设置的是哪个属性,然后把这个属性的安全上下文指定到我们自定义的上面去
sepolicy/property_contexts
sys.testA.finish u:object_r:test_prop:s0
sepolicy/testA.te
set_prop(testA, test_prop)
2.1.3、testNeverallowRules154 解决方案
<Test result="fail" name="testNeverallowRules154">
<Failure message="junit.framework.AssertionFailedError: The following errors were encountered when validating the SELinuxneverallow rule:">
<StackTrace>junit.framework.AssertionFailedError: The following errors were encountered when validating the SELinuxneverallow rule:
neverallow * default_android_service:service_manager add;
libsepol.report_failure: neverallow violated by allow testA default_android_service:service_manager { add };
libsepol.check_assertions: 1 neverallow failures occurred
</StackTrace>
</Failure>
</Test>
首先这个违反的neverallow规则如下,禁止所有的进程添加default_android_service类型的service_manager
neverallow * default_android_service:service_manager add;
先看看之前的解决方案: sepolicy/service_contexts
添加
test.service u:object_r:test_service:s0
service.te:
添加
type test_service, app_api_service, system_server_service, service_manager_type;
testA.te 里面如下:
allow testA default_android_service:service_manager { add };
然后再把neverallow规则干掉,神挡杀神,佛挡杀佛
neverallow { domain -testA } default_android_service:service_manager add;
看到这里,比较疑惑的点是service_contexts和service.te,和普通文件一样,service也是可以给它定义一个新的type
但是定义了这个test_service type之后,没有生效?
从报错的信息来看,test.service的type依然还是default_android_service,这是为何?
06-10 08:50:30.172 3062 3062 E SELinux : avc: denied { add } for service=test.service pid=4707 uid=0 scontext=u:r:testA:s0 tcontext=u:object_r:default_android_service:s0 tclass=service_manager permissive=0
这时,我们查看 /system/etc/selinux/plat_service_contexts 里面的内容,发现我们添加的test.service 压根就不在里面,难怪不会生效
在板卡上直接修改添加,重启之后,就会发现报错已经变成了这样了:
06-10 08:55:15.748 3062 3062 E SELinux : avc: denied { add } for service=test.service pid=4732 uid=0 scontext=u:r:testA:s0 tcontext=u:object_r:test_service:s0 tclass=service_manager permissive=0
接下来再添加权限就行了,这个时候就不会违反neverallow规则
透过现象看本质,为什么会导致这个问题呢?
原因要从编译出plat_service_contexts那里说起,回到/android/system/sepolicy/Android.mk
include $(CLEAR_VARS)
LOCAL_MODULE := plat_service_contexts
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_TAGS := optional
ifeq ($(PRODUCT_SEPOLICY_SPLIT),true)
LOCAL_MODULE_PATH := $(TARGET_OUT)/etc/selinux
else
LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
endif
include $(BUILD_SYSTEM)/base_rules.mk
plat_svcfiles := $(call build_policy, service_contexts, $(PLAT_PRIVATE_POLICY))
plat_service_contexts.tmp := $(intermediates)/plat_service_contexts.tmp
$(plat_service_contexts.tmp): PRIVATE_SVC_FILES := $(plat_svcfiles)
$(plat_service_contexts.tmp): PRIVATE_ADDITIONAL_M4DEFS := $(LOCAL_ADDITIONAL_M4DEFS)
$(plat_service_contexts.tmp): $(plat_svcfiles)
@mkdir -p $(dir $@)
$(hide) m4 -s $(PRIVATE_ADDITIONAL_M4DEFS) $(PRIVATE_SVC_FILES) > $@
$(LOCAL_BUILT_MODULE): PRIVATE_SEPOLICY := $(built_sepolicy)
$(LOCAL_BUILT_MODULE): $(plat_service_contexts.tmp) $(built_sepolicy) $(HOST_OUT_EXECUTABLES)/checkfc $(ACP)
@mkdir -p $(dir $@)
sed -e 's/#.*$$//' -e '/^$$/d' $< > $@
$(HOST_OUT_EXECUTABLES)/checkfc -s $(PRIVATE_SEPOLICY) $@
built_plat_svc := $(LOCAL_BUILT_MODULE)
plat_svcfiles :=
plat_service_contexts.tmp :=
mk里面搜索的目录是PLAT_PRIVATE_POLICY,前面有提过,这个目录是/android/system/sepolicy/private 以及 BOARD_PLAT_PRIVATE_SEPOLICY_DIR 所组成的 所以device厂商下面的sepolicy/service_contexts 没有用到
解决办法很简单:
首先指定拓展的私有目录 BOARD_PLAT_PRIVATE_SEPOLICY_DIR
BOARD_PLAT_PRIVATE_SEPOLICY_DIR += \
xxxx/sepolicy/private
然后添加:
xxxx/sepolicy/private/service_contexts
内容为:
test.service u:object_r:test_service:s0
编译selinux_policy,就可以看到这个context被追加到plat_service_contexts了
还有,在这个地方添加的service_contexts,并不是所有情况都没有作用
看看这两段mk:
ifneq ($(PRODUCT_PRECOMPILED_SEPOLICY),false)
LOCAL_REQUIRED_MODULES += precompiled_sepolicy precompiled_sepolicy.plat_and_mapping.sha256
endif
else
LOCAL_REQUIRED_MODULES += \
sepolicy \
vendor_service_contexts
endif
vendor_service_contexts
include $(CLEAR_VARS)
LOCAL_MODULE := vendor_service_contexts
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
include $(BUILD_SYSTEM)/base_rules.mk
vendor_svcfiles := $(call build_policy, service_contexts, $(PLAT_VENDOR_POLICY) $(BOARD_VENDOR_SEPOLICY_DIRS) $(REQD_MASK_POLICY))
vendor_service_contexts.tmp := $(intermediates)/vendor_service_contexts.tmp
$(vendor_service_contexts.tmp): PRIVATE_SVC_FILES := $(vendor_svcfiles)
$(vendor_service_contexts.tmp): PRIVATE_ADDITIONAL_M4DEFS := $(LOCAL_ADDITIONAL_M4DEFS)
$(vendor_service_contexts.tmp): $(vendor_svcfiles)
@mkdir -p $(dir $@)
$(hide) m4 -s $(PRIVATE_ADDITIONAL_M4DEFS) $(PRIVATE_SVC_FILES) > $@
$(LOCAL_BUILT_MODULE): PRIVATE_SEPOLICY := $(built_sepolicy)
$(LOCAL_BUILT_MODULE): $(vendor_service_contexts.tmp) $(built_sepolicy) $(HOST_OUT_EXECUTABLES)/checkfc $(ACP)
@mkdir -p $(dir $@)
sed -e 's/#.*$$//' -e '/^$$/d' $< > $@
$(hide) $(HOST_OUT_EXECUTABLES)/checkfc -s $(PRIVATE_SEPOLICY) $@
built_vendor_svc := $(LOCAL_BUILT_MODULE)
vendor_svcfiles :=
vendor_service_contexts.tmp :=
endif
注释很清楚,也就是说在非treble的设备上,上面路径的service_contexts会编译到vendor_service_contexts里面;如果要生效,就得把它加到private的拓展目录
2.1.4、testNeverallowRules252 解决方案
<Test result="fail" name="testNeverallowRules252">
<Failure message="junit.framework.AssertionFailedError: The following errors were encountered when validating the SELinuxneverallow rule:">
<StackTrace>junit.framework.AssertionFailedError: The following errors were encountered when validating the SELinuxneverallow rule:
neverallow { domain -init -vendor_init -system_server -dumpstate } debugfs:file { { append create link unlink relabelfrom rename setattr write } open read ioctl lock };
libsepol.report_failure: neverallow violated by allow mediaserver debugfs:file { read open };
libsepol.report_failure: neverallow violated by allow hal_memtrack_default debugfs:file { read open };
libsepol.check_assertions: 2 neverallow failures occurred
报错信息如下:
[ 818.411706@1] type=1400 audit(1623295203.581:48): avc: denied { read } for pid=3147 comm="HwBinder:3147_3" name="test_en" dev="debugfs" ino=377 scontext=u:r:mediaserver:s0 tcontext=u:object_r:debugfs:s0 tclass=file permissive=0
拿其中mediaserver的来分析,查找修改记录,很快就可以得知,这个是在读写节点 /sys/kernel/debug/test/test_en 的时候出现的权限问题
和上面的处理思路一样,我们要给这个节点指定一个安全上下文,不然就是权限放大的问题
但是和普通的文件不同,这个不是添加在file_contexts里面了,而是要添加在 genfs_contexts
file_contexts 里面的指定的文件,都是编译之后就有了的,而像 /sys/kernel/debug 、 /proc 这些是系统起来之后动态生成的,这些的节点文件的安全上下文就要在genfs_contexts里面指定的
genfscon debugfs /sys/kernel/debug/test/test_en u:object_r:debugfs_test:s0
这里很容易犯一个错误,就是直接把完整路径写到第三个字段里面去了
实际上,debugfs 指代的就是/sys/kernel/debug这个路径,后面要写的是相对于debugfs 的路径
因此,正确的写法是这样的:
genfscon debugfs /test/test_en u:object_r:debugfs_test:s0
当然,file.te里面还得定义debugfs_test这个type
type debugfs_test, fs_type, sysfs_type, debugfs_type;
修改之后是要编译precompiled_sepolicy的,而不是像file_contexts可以直接在板子上修改,重启之后报错如下:
[ 818.411706@1] type=1400 audit(1623295203.581:48): avc: denied { read } for pid=3147 comm="HwBinder:3147_3" name="test_en" dev="debugfs" ino=377 scontext=u:r:mediaserver:s0 tcontext=u:object_r:debugfs_afc:s0 tclass=file permissive=0
再往下只要添加对应的权限就行了
2.1.5、exec_type
还有一个例子也是很重要的
[ 10.583284] type=1400 audit(10.256:8): avc: denied { entrypoint } for pid=3233 comm="init" path="/vendor/bin/testA.sh" dev="mmcblk0p16" ino=712 scontext=u:r:testA:s0 tcontext=u:object_r:vendor_file:s0 tclass=file permissive=0
添加权限
allow testA vendor_file:file entrypoint;
结果违反了neverallow规则,然后继续干掉规则呗,这还不简单
FAILED: out/target/product/xxxx/obj/ETC/sepolicy_neverallows_intermediates/sepolicy_neverallows
/bin/bash -c "(rm -f out/target/product/xxxx/obj/ETC/sepolicy_neverallows_intermediates/sepolicy_neverallows ) && (ASAN_OPTIONS=detect_leaks=0 out/host/linux-x86/bin/checkpolicy -M -c 30 -o out/target/product/xxxx/obj/ETC/sepolicy_neverallows_intermediates/sepolicy_neverallows out/target/product/xxxx/obj/ETC/sepolicy_neverallows_intermediates/policy.conf )"
libsepol.report_failure: neverallow on line 376 of system/sepolicy/public/domain.te (or line 10221 of policy.conf) violated by allow testA vendor_file:file { entrypoint };
libsepol.check_assertions: 1 neverallow failures occurred
domain.te里面的neverallow规则:
neverallow * { file_type -exec_type -postinstall_file }:file entrypoint;
正确的处理方式和前面提到的是一样的,也是同个道理
sepolicy/file_contexts
/vendor/bin/testA.sh u:object_r:testA_exec:s0
这就是为什么我们之前添加脚本的时候,每次都要加这样一句话的原因
2.2、利用attributes绕开neverallow规则
这一类处理方式并不是从根源上解决,而是利用了规则的定义绕开了而已
8.0以前的不会有这样的一些规则,归根到底还是treble工程
2.2.1、testNeverallowRules184 解决方案
<Test result="fail" name="testNeverallowRules184">
<Failure message="junit.framework.AssertionFailedError: The following errors were encountered when validating the SELinuxneverallow rule:">
<StackTrace>junit.framework.AssertionFailedError: The following errors were encountered when validating the SELinuxneverallow rule:
neverallow { domain -coredomain -appdomain -binder_in_vendor_violators } binder_device:chr_file { { getattr open read ioctl lock map } { open append write lock map } };
libsepol.report_failure: neverallow violated by allow testA binder_device:chr_file { ioctl read write open };
libsepol.check_assertions: 1 neverallow failures occurred
</StackTrace>
</Failure>
</Test>
利用属性 binder_in_vendor_violators 规避这个问题
type testA, domain;
修改为
type testA, domain, binder_in_vendor_violators;
原因很简单,binder_in_vendor_violators 是一个属性,查看上面的neverallow规则就可以明白
2.2.3、testNeverallowRules185 解决方案
<Test result="fail" name="testNeverallowRules185">
<Failure message="junit.framework.AssertionFailedError: The following errors were encountered when validating the SELinuxneverallow rule:">
<StackTrace>junit.framework.AssertionFailedError: The following errors were encountered when validating the SELinuxneverallow rule:
neverallow { domain -coredomain -appdomain -binder_in_vendor_violators } service_manager_type:service_manager find;
libsepol.report_failure: neverallow violated by allow testA activity_service:service_manager { find };
libsepol.check_assertions: 1 neverallow failures occurred
和上面的一样,利用属性 binder_in_vendor_violators 规避这个问题
type testA, domain;
修改为
type testA, domain, binder_in_vendor_violators;
这些属性之所以会存在,说明了Google并没有做到最完美的system和vendor分离,所有有这么一个像是后门的东西
还有一些:
- vendor_executes_system_violators
- data_between_core_and_vendor_violators
- system_writes_vendor_properties_violators
2.3、vendor的脚本,在有权限的目录读写,不要操作system的
这类问题很明显,system和vendor分离之后才有的,没有啥好讲的,多看看报错信息和domain.te里面的注释,就能搞明白了
2.3.1、testNeverallowRules217 解决方案
<Test result="fail" name="testNeverallowRules217">
<Failure message="junit.framework.AssertionFailedError: The following errors were encountered when validating the SELinuxneverallow rule:">
<StackTrace>junit.framework.AssertionFailedError: The following errors were encountered when validating the SELinuxneverallow rule:
neverallow { domain -coredomain -appdomain -vendor_executes_system_violators -vendor_init } { exec_type -vendor_file_type -crash_dump_exec -netutils_wrapper_exec }:file { entrypoint execute execute_no_trans };
libsepol.report_failure: neverallow violated by allow testA shell_exec:file { execute };
libsepol.check_assertions: 1 neverallow failures occurred
</StackTrace>
</Failure>
</Test>
首先,如果脚本在/vendor/bin下面,先将sh改过来
#!/system/bin/sh
修改为:
以上能解决大部分问题,但是如果有些命令只有在/system/bin下面才有的,那这个时候就没有办法了,这个shell_exec:file 一定要加
此时,只能通过vendor_executes_system_violators 来规避问题了
type testA, domain, binder_in_vendor_violators;
修改为:
type testA, domain, binder_in_vendor_violators, vendor_executes_system_violators;
2.3.2、testNeverallowRules203 解决方案
<Test result="fail" name="testNeverallowRules203">
<Failure message="junit.framework.AssertionFailedError: The following errors were encountered when validating the SELinuxneverallow rule:">
<StackTrace>junit.framework.AssertionFailedError: The following errors were encountered when validating the SELinuxneverallow rule:
neverallow { domain -appdomain -coredomain -data_between_core_and_vendor_violators -vendor_init } { core_data_file_type -zoneinfo_data_file }:{ { chr_file blk_file } { file lnk_file sock_file fifo_file } } ~{ append getattr ioctl read write map };
libsepol.report_failure: neverallow violated by allow testA system_data_file:file { open };
libsepol.check_assertions: 1 neverallow failures occurred
</StackTrace>
</Failure>
</Test>
仔细看domain.te里面的注释,就能明白了
把脚本里面的路径修改一下
TEMP_PATH="/data/test"
修改为:
TEMP_PATH="/data/vendor/test"
当然,有些neverallow规则还是可以利用属性绕过的(像data_between_core_and_vendor_violators),不过能正面解决就直接解决了
2.4、dac_override 问题
这个在前面以及提到过了,阅读一下参考文章就能明白了 解决这个问题从来不是添加SELinux处理的,必须要去解决unix user/group/others权限问题
2.5、读写/data数据
这里讨论下另一个解决方案:
2.5.1、testNeverallowRules237 另一种解决方案
<Test result="fail" name="testNeverallowRules237">
<Failure message="junit.framework.AssertionFailedError: The following errors were encountered when validating the SELinuxneverallow rule:">
<StackTrace>junit.framework.AssertionFailedError: The following errors were encountered when validating the SELinuxneverallow rule:
neverallow { domain -system_server -system_app -init -installd -vold_prepare_subdirs } system_data_file:file { append create link unlink relabelfrom rename setattr write };
libsepol.report_failure: neverallow violated by allow testA system_data_file:file { write create setattr unlink };
libsepol.check_assertions: 1 neverallow failures occurred
再次看到这个很好奇,不是已经在上面的处理方法中解决了吗,修改读写的路径,改成有权限的地方就行了
这里再提供另一种解决思路,对于那些我们不方便修改代码的,这是一个福音
以下面的这个权限为例:
allow testA system_data_file:file { write create unlink };
这个权限添加的目的是为了读写、删除这个文件: /data/test.txt 问题就在于进程testA他没有直接权限读写啊
按照以前的思路:干掉规则 -testA
neverallow {
domain
-system_server
-system_app
-init
-installd
-vold_prepare_subdirs
-testA
with_asan(`-asan_extract')
} system_data_file:file no_w_file_perms;
经过上面的处理经验,一个简单的方案出来了,把/data/test.txt 修改成/data/vendor/test.txt
然后权限拉满,就可以正常创建和删除了
allow testA vendor_data_file:dir create_dir_perms;
allow testA vendor_data_file:file create_file_perms;
下面讨论的是另一种解决方案:
data根目录,它所对应的安全上下文为:u:object_r:system_data_file:s0
drwxrwx--x 40 system system u:object_r:system_data_file:s0 4096 2021-06-10 16:09 data
如果创建一个文件 /data/test.txt, 它的安全上下文是继承于父目录的,也就是u:object_r:system_data_file:s0
所以在上面的报错会看到是对system_data_file类型的目录缺少写的权限
但是,如果有一种方式可以实现创建文件或者子目录的时候,不要继承父目录的scontext,改成一个拥有权限的type,那不就可以了嘛?
没错,还真的有这种操作,这个叫做type transition , 类型转移
在前面的基础文档里面已经有介绍了,我们直切主题,用宏 file_type_auto_trans
file_type_auto_trans(testA, system_data_file, vendor_data_file)
define(`file_type_auto_trans', `
file_type_trans($1, $2, $3)
type_transition $1 $2:dir $3;
type_transition $1 $2:notdevfile_class_set $3;
')
意思就是,testA(第一个参数domain)这个type的进程,在system_data_file(第二个参数dir_type)类型的目录下面,创建文件时,指定它的type为vendor_data_file(第三个参数file_type)
同样的,这里同样要给vendor_data_file加权限
这一句将会发生如下变化:
console:/
-rw------- 1 root system u:object_r:system_data_file:s0 5320 2021-06-10 17:06 /data/test.txt
变成
console:/
-rw------- 1 root system u:object_r:vendor_data_file:s0 5320 2021-06-10 17:06 /data/test.txt
接着还有删除文件时需要下面的权限,这个正常添加就行了,下面的权限不会违反neverallow规则
[ 67.886702@2] type=1400 audit(1623315964.145:18): avc: denied { remove_name } for pid=3142 comm="HwBinder:3142_2" name="test.txt" dev="mmcblk0p21" ino=1263 scontext=u:r:testA:s0 tcontext=u:object_r:system_data_file:s0 tclass=dir permissive=0
这种方式不一定适用于app,因为app里面还有其他neverallow规则会限制
它不是针对某个进程,而是针对的是一个域,这样会导致所有在这个域里面的进程都会创建出新的type的文件
三、一些思考
3.1、修改属性的neverallow规则,还能测试通过?
在/android/system/sepolicy/public/property.te 这一段,本来以为会有报错的,因为违反了neverallow规则
compatible_property_only(`
neverallow {
coredomain
-init
-system_writes_vendor_properties_violators
-system_server
-platform_app
-testA
} {
property_type
-audio_prop
-bluetooth_a2dp_offload_prop
-bluetooth_prop
-bootloader_boot_reason_prop
......
-test_prop
}:property_service set;
')
但实际上并没有报错,找到CTS测试的源码:
@RestrictedBuildTest
public void testNeverallowRules436() throws Exception {
String neverallowRule = "neverallow { coredomain -init -system_writes_vendor_properties_violators } { property_type -audio_prop -bluetooth_a2dp_offload_prop -bluetooth_prop -bootloader_boot_reason_prop -boottime_prop -config_prop -cppreopt_prop -ctl_bootanim_prop -ctl_bugreport_prop -ctl_console_prop -ctl_default_prop -ctl_dumpstate_prop -ctl_fuse_prop -ctl_interface_restart_prop -ctl_interface_start_prop -ctl_interface_stop_prop -ctl_mdnsd_prop -ctl_restart_prop -ctl_rildaemon_prop -ctl_sigstop_prop -ctl_start_prop -ctl_stop_prop -dalvik_prop -debug_prop -debuggerd_prop -default_prop -device_logging_prop -dhcp_prop -dumpstate_options_prop -dumpstate_prop -exported2_config_prop -exported2_default_prop -exported2_radio_prop -exported2_system_prop -exported2_vold_prop -exported3_default_prop -exported3_radio_prop -exported3_system_prop -exported_bluetooth_prop -exported_config_prop -exported_dalvik_prop -exported_default_prop -exported_dumpstate_prop -exported_ffs_prop -exported_fingerprint_prop -exported_overlay_prop -exported_pm_prop -exported_radio_prop -exported_secure_prop -exported_system_prop -exported_system_radio_prop -exported_vold_prop -exported_wifi_prop -extended_core_property_type -ffs_prop -fingerprint_prop -firstboot_prop -hwservicemanager_prop -last_boot_reason_prop -log_prop -log_tag_prop -logd_prop -logpersistd_logging_prop -lowpan_prop -mmc_prop -net_dns_prop -net_radio_prop -netd_stable_secret_prop -nfc_prop -overlay_prop -pan_result_prop -persist_debug_prop -persistent_properties_ready_prop -pm_prop -powerctl_prop -radio_prop -restorecon_prop -safemode_prop -serialno_prop -shell_prop -system_boot_reason_prop -system_prop -system_radio_prop -test_boot_reason_prop -traced_enabled_prop -vendor_default_prop -vendor_security_patch_level_prop -vold_prop -wifi_log_prop -wifi_prop }:property_service set;";
boolean fullTrebleOnly = false;
boolean compatiblePropertyOnly = true;
if ((fullTrebleOnly) && (!isFullTrebleDevice()))
{
return;
}
if ((compatiblePropertyOnly) && (!isCompatiblePropertyEnforcedDevice()))
{
return;
}
File policyFile = (isSepolicySplit()) && (this.mVendorSepolicyVersion < 28) ?
this.deviceSystemPolicyFile :
this.devicePolicyFile;
ProcessBuilder pb = new ProcessBuilder(new String[] { this.sepolicyAnalyze.getAbsolutePath(), policyFile
.getAbsolutePath(), "neverallow", "-w", "-n", neverallowRule });
pb.redirectOutput(ProcessBuilder.Redirect.PIPE);
pb.redirectErrorStream(true);
Process p = pb.start();
p.waitFor();
BufferedReader result = new BufferedReader(new InputStreamReader(p.getInputStream()));
StringBuilder errorString = new StringBuilder();
String line;
while ((line = result.readLine()) != null) {
errorString.append(line);
errorString.append("\n");
}
assertTrue("The following errors were encountered when validating the SELinuxneverallow rule:\n" + neverallowRule + "\n" + errorString, errorString
.length() == 0);
}
代码是在这里被return了,也就是没有做检查,所以测试结果是pass的
if ((compatiblePropertyOnly) && (!isCompatiblePropertyEnforcedDevice()))
{
return;
}
public static boolean isCompatiblePropertyEnforcedDevice(ITestDevice device)
throws DeviceNotAvailableException
{
return PropertyUtil.propertyEquals(device, "ro.actionable_compatible_property.enabled", "true");
}
简单追了一下这里面的流程,首先compatible_property_only是一个宏,定义如下
define(`compatible_property_only', ifelse(target_compatible_property, `true', $1,
ifelse(target_compatible_property, `cts',
$1
, )))
值来源于target_compatible_property,这个值是前面提到的transform-policy-to-conf里面的变量传递进来的
-D target_compatible_property=$(PRIVATE_COMPATIBLE_PROPERTY)
继续,值是从PRODUCT_COMPATIBLE_PROPERTY来的
PRIVATE_COMPATIBLE_PROPERTY := $(PRODUCT_COMPATIBLE_PROPERTY)
往下
/android/build/make/core/config.mk
PRODUCT_COMPATIBLE_PROPERTY := false
ifneq ($(PRODUCT_COMPATIBLE_PROPERTY_OVERRIDE),)
PRODUCT_COMPATIBLE_PROPERTY := $(PRODUCT_COMPATIBLE_PROPERTY_OVERRIDE)
else ifeq ($(PRODUCT_SHIPPING_API_LEVEL),)
else ifneq ($(call math_lt,27,$(PRODUCT_SHIPPING_API_LEVEL)),)
PRODUCT_COMPATIBLE_PROPERTY := true
endif
PRODUCT_COMPATIBLE_PROPERTY 的值默认为false,如果有PRODUCT_COMPATIBLE_PROPERTY_OVERRIDE,则用这个
然后是PRODUCT_SHIPPING_API_LEVEL
也就是说,PRODUCT_COMPATIBLE_PROPERTY 最后的值为false
所以前面的neverallow规则不会生效
再看看属性ro.actionable_compatible_property.enabled 是怎么来的?
ifeq ($(PRODUCT_ACTIONABLE_COMPATIBLE_PROPERTY_DISABLE),true)
ADDITIONAL_DEFAULT_PROPERTIES += ro.actionable_compatible_property.enabled=false
else
ADDITIONAL_DEFAULT_PROPERTIES += ro.actionable_compatible_property.enabled=${PRODUCT_COMPATIBLE_PROPERTY}
endif
PRODUCT_ACTIONABLE_COMPATIBLE_PROPERTY_DISABLE 是空的没有定义,也是最后生成的值是false
追查了代码,发现原来以前BoardConfig.mk里面是有定义的
PRODUCT_SHIPPING_API_LEVEL := 28
但是后面居然把这个去掉了?
PRODUCT_SHIPPING_API_LEVEL 这个会影响到一些neverallow规则
还会影响到这个属性ro.product.first_api_level 的生成
3.2、抽离业务场景需求的公共权限
或许我们会有一个疑问,就是为什么不把所有自己的脚本都用同一个安全上下文呢?
这的确是可行的方案,可以更加便捷的完成功能需求,但是从权限管控的初衷来看,分开了之后能够达到权限最小化;
在上面的思路基础上,引发了另一个想法: 定义一个自定义的属性,然后添加脚本的时候,关联上这个属性,就拥有了对应的权限了
比如:
attribute testdomain;
testdomain.te里面定义一些公共的权限,比如:
allow testdomain console_device:chr_file rw_file_perms;
allow testdomain vendor_toolbox_exec:file { execute_no_trans };
allow testdomain vendor_data_file:dir create_dir_perms;
allow testdomain vendor_data_file:file create_file_perms;
然后在对应的type中关联属性
type testA, domain, testdomain;
这只是一个目前的想法~
3.3 其他
对于第三方apk的权限问题,比如:
目前这个在系统端没有找到解决方案,只能从apk实现上入手了
[ 97.450511@1] type=1400 audit(1623116538.872:18): avc: denied { create } for pid=3953 comm="testB" name="shpex98m.tmp" scontext=u:r:testB:s0 tcontext=u:object_r:system_data_file:s0 tclass=file permissive=0
这个是绕不过neverallow规则的
[ 45.533219@3] type=1400 audit(1623985073.895:7): avc: denied { execute } for pid=4052 comm="bootloader" path="/data/data/com.xxxx.xxx/files/test/test.so" dev="mmcblk0p21" ino=1085 scontext=u:r:system_app:s0 tcontext=u:object_r:system_app_data_file:s0 tclass=file permissive=0
可以看看下面这个文章: https://dongdaima.com/article/54042
到这里,基本上我所了解的部分就介绍完了,总结来说,多看看代码,报错信息以及te文件里面的注释,能学到很多东西
能不动到原生的部分,就不要修改
四、参考文章
dac_override
android 用户组
|