Android P SELinux (四) CTS neverallow处理总结

本文详细介绍了Android PSELinux的权限管理,包括基础概念、开机初始化、权限检查原理、CTS测试处理和一些常见的解决方案。重点讨论了区分vendor和system域、setenforce的可行性、DAC_OVERRIDE的处理、echo打印到终端、读写U盘内容、am命令和ioctl等场景的处理方法,并分析了如何避免违反neverallow规则。同时,提出了对权限策略的思考,如利用属性规避权限放大问题和定义公共权限。

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(`
    # Do not allow vendor components to execute files from system
    # except for the ones whitelist here.
    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:/ # /vendor/bin/testA.sh[3]: am: not found

可以修改成:

/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规则如下:

# Only init prior to switching context should be able to set enforcing mode.
# init starts in kernel domain and switches to init domain via setcon in
# the init.rc, so the setenforce occurs while still in kernel. After
# switching domains, there is never any need to setenforce again by init.
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,       /* load policy */
        SEL_ENFORCE,    /* get or set enforcing status */
+       SEL_ENABLE,     /* get or set enforcing status */
        SEL_CONTEXT,    /* validate context */
        SEL_ACCESS,     /* compute access decision */
        SEL_CREATE,     /* compute create labeling decision */
@@ -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;
+
+    /* No partial writes. */
+    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

<think>我们正在解决用户的问题:如何处理Android系统中odrefresh触发大量selinux异常的问题 根据用户提供的引用[1],我们知道在Android系统中,selinux策略非常重要,尤其是在/data分区解密后的初始化阶段。 而odrefresh作为ART的一部分,在系统启动或OTA后运行,可能会因为访问某些资源时违反selinux策略而产生大量异常。 解决思路: 1. 确认异常的具体原因:通过分析selinux的avc denials日志,找到被拒绝的操作(源类型、目标类型、操作类别等) 2. 定制selinux策略:根据日志添加允许规则(neverallow规则除外) 3. 测试并验证策略是否解决问题 具体步骤: 步骤1:获取avc denials日志 在设备上运行以下命令捕获selinux拒绝信息: ```bash adb shell su root dmesg | grep avc > avc_log.txt ``` 或者使用: ```bash adb shell cat /sys/fs/selinux/avc > avc_log.txt ``` 注意:需要root权限,或者使用userdebug版本。 步骤2:分析日志,提取与odrefresh相关的拒绝条目 查找日志中源域(scontext)为`odrefresh`的条目,例如: ``` avc: denied { read } for scontext=u:r:odrefresh:s0 tcontext=u:r:init:s0 tclass=process permissive=0 ``` 步骤3:根据拒绝条目编写selinux策略补丁 假设我们分析得到的拒绝条目为: ``` avc: denied { read } for scontext=u:r:odrefresh:s0 tcontext=u:r:init:s0 tclass=process ``` 我们需要在odrefresh的te文件中添加允许规则。odrefresh的te文件通常位于设备源码的`sepolicy`目录下,如`system/sepolicy/private/odrefresh.te`。 添加规则: ```sepolicy allow odrefresh init:process read; ``` 步骤4:编译并刷入新的sepolicy 将修改后的te文件放入设备源码的相应位置,重新编译sepolicy镜像(通常为`sepolicy`或`precompiled_sepolicy`),然后刷入设备。 步骤5:验证 重新触发odrefresh(如通过重启或命令触发),再次检查avc日志,确认拒绝条目是否消失。 注意:添加selinux规则时,要注意最小权限原则,避免过度授权。同时,要确保不违反`neverallow`规则(可在`system/sepolicy/public/domain.te`等文件中查找)。 如果拒绝条目较多,可以使用工具`audit2allow`来自动生成策略规则(但需要手动审核): ```bash cat avc_log.txt | audit2allow -M myodrefresh ``` 这将生成`myodrefresh.te`文件,包含建议的规则。 另外,用户提到引用[1]中关于/data分区解密后启动bootchart的操作,这提示我们odrefresh可能在某些情况下需要访问/data目录下的文件,而解密后的上下文可能未正确设置,也可能导致selinux问题。因此,确保odrefresh对/data相关目录有正确的访问权限也很重要。 例如,odrefresh需要访问`/data/dalvik-cache`目录,因此需要确保te文件中包含: ```sepolicy allow odrefresh dalvikcache_data_file:dir { read write open search }; allow odrefresh dalvikcache_data_file:file { create read write open lock unlink }; ``` 最后,如果问题仍然存在,可以考虑在调试阶段将odrefresh域设置为permissive模式(不推荐生产环境): ```bash adb shell su root setenforce 0 adb shell setprop odrefresh.debug.permissive 1 ``` 这样可以暂时忽略所有selinux拒绝,但仅用于调试。 综上所述,处理odrefresh的selinux异常的关键是分析avc日志并添加最小权限的策略规则。 相关问题: 1. 如何捕获和分析Androidselinux拒绝日志? 2. 如何为自定义服务编写selinux策略? 3. Android中的neverallow规则是什么?如何避免违反? 4. odrefresh在Android系统启动过程中具体何时执行? </think>### 解决 Android odrefresh 引发大量 SELinux 异常的方法 #### 1. **定位异常源** - **捕获日志**: ```bash adb shell su root dmesg | grep "avc:.*odrefresh" > odrefresh_avc.log adb shell cat /sys/fs/selinux/avc | grep odrefresh >> odrefresh_avc.log ``` 分析输出结果,重点关注: - **拒绝操作**:如 `{ read }`, `{ write }`, `{ open }` - **目标资源**:如 `tcontext=u:object_r:dalvikcache_data_file:s0` - **源上下文**:如 `scontext=u:r:odrefresh:s0`[^1] #### 2. **定制 SELinux 策略** 在设备源码的 SELinux 策略文件中添加规则(如 `odrefresh.te`): ```sepolicy # 示例:允许 odrefresh 访问 /data/dalvik-cache allow odrefresh dalvikcache_data_file:dir { search write add_name }; allow odrefresh dalvikcache_data_file:file { create read write unlink }; ``` #### 3. **处理加密分区问题** 若异常由 `/data` 分区加密引起(引用[1]提及): ```sepolicy # 允许 odrefresh 访问解密后的 /data/bootchart allow odrefresh bootchart_data_file:dir { read open }; allow odrefresh bootchart_data_file:file { getattr }; ``` 或延迟触发 odrefresh 至 `/data` 完全解密后: ```diff # 在 init.rc 中调整触发时机 on post-fs-data + exec /system/bin/odrefresh --compile ``` #### 4. **临时调试方案** - **Permissive 模式**(仅调试): ```bash adb shell setenforce 0 adb shell setprop odrefresh.debug.permissive 1 ``` - **动态添加规则**: ```bash adb shell supolicy --live "allow odrefresh dalvikcache_data_file file *" ``` #### 5. **验证与测试** 1. 编译策略并刷入设备: ```bash make sepolicy && adb push sepolicy /sepolicy ``` 2. 触发 odrefresh 测试: ```bash adb shell cmd activity odrefresh ``` 3. 检查新日志: ```bash adb shell dmesg | grep "avc.*granted" ``` > 📌 **关键点**: > - 策略需遵循 **最小权限原则**,避免过度授权 > - 涉及加密分区操作时,需同步验证 `fscrypt` 密钥加载流程[^1] > - 永久策略需通过 `CTS/VTS` 测试后集成到系统镜像 --- ### 相关问题 1. 如何在不重启设备的情况下动态更新 SELinux 策略? 2. `odrefresh` 运行时如何限制其对系统资源的过度访问? 3. 如何处理因 `/data` 分区加密导致的 SELinux 权限冲突? 4. 如何验证自定义 SELinux 策略是否违反 Android 的 `neverallow` 规则? [^1]: 参考 Android init 阶段流程:`system/core/rootdir/init.rc`
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值