Android SELinux 策略调试实战:从 AVC 日志到策略修复
本文记录了一次完整的 Android 车载系统 SELinux 策略调试过程,涵盖 AVC denied 日志解读、TE 规则编写、MLS 约束排查、neverallow 冲突处理等典型场景。适合做车机/Android 系统开发的工程师参考。
一、背景
在 Android 车载系统的开发中,自定义 vendor 域(如 trace_copy、trace_deletelog)在运行 shell 脚本(ls、cp、rm、find、chmod、awk 等)时,频繁触发 SELinux AVC denied 日志。本文从零开始,逐步将这些日志转换为 SELinux 策略语句,并解决过程中遇到的 MLS 约束、mlstrustedsubject 语法错误、app_data_file neverallow 风险等问题。
二、AVC 日志如何读懂
一条典型的 AVC denied 日志格式如下:
bash
01-01 08:19:08.013 24575 24575 W ls : type=1400 audit(0.0:495): avc: denied { search } for name="0" dev="dm-44" ino=250 scontext=u:r:trace_copy:s0 tcontext=u:object_r:system_data_file:s0:c512,c768 tclass=dir permissive=0
关键字段解读:
| 字段 | 含义 | 示例值 |
|---|---|---|
denied |
被拒绝的权限 | { search } |
name |
目标对象名 | "0"(即 /data/user/0) |
dev |
所在块设备 | dm-44(userdata) |
scontext |
主体(source)的安全上下文 | u:r:trace_copy:s0 |
tcontext |
客体(target)的安全上下文 | u:object_r:system_data_file:s0:c512,c768 |
tclass |
对象类 | dir |
permissive |
是否 permissive 模式 | 0(强制模式,真拒绝) |
转换公式
xml
allow <scontext的domain> <tcontext的type>:<tclass> <perm>;
注意:scontext/tcontext 里的
:s0:c512,c768这部分是 MLS 类别,不写进 allow 规则。allow 只按 type 匹配。
三、第一批 AVC:基础 TE 规则
日志
ini
avc: denied { search } for name="0" tcontext=u:object_r:system_data_file:s0:c512,c768 tclass=dir
avc: denied { setattr } for name="wkk" tcontext=u:object_r:mnt_media_rw_file tclass=dir
avc: denied { read } for name="wkk" tcontext=u:object_r:mnt_media_rw_file tclass=dir
转换结果
bash
allow trace_copy system_data_file:dir search;
allow trace_copy mnt_media_rw_file:dir { read setattr };
说明
name="0"→/data/user/0,type 为system_data_filename="wkk"→ U 盘挂载点下的目录,type 为mnt_media_rw_filesetattr由chmod触发,read由ls触发
四、CAP_FSETID:capability 类的权限
日志
ini
avc: denied { fsetid } for capability=4 scontext=u:r:trace_copy:s0 tcontext=u:r:trace_copy:s0 tclass=capability
转换结果
css
allow trace_copy self:capability fsetid;
说明
capability=4即CAP_FSETID- 当
chmod修改带 setgid/setuid 位的文件时,内核需要清除这些位,此时校验CAP_FSETID - 当
tcontext == scontext时,规范写法用self
五、MLS 约束:加了 allow 还是被拒
问题现象
已经加了 allow trace_copy system_data_file:dir search;,但同样的 AVC 仍然出现。
根本原因
看 scontext 和 tcontext 的 MLS 部分:
ini
scontext=u:r:trace_copy:s0 ← 主体:s0,无类别
tcontext=u:object_r:system_data_file:s0:c512,c768 ← 客体:s0,带 c512,c768
SELinux 在 Android 上有两层校验:
- TE 层(Type Enforcement) :
allow规则 → 已通过 - MLS 层(mlsconstrain) :检查主体级别是否支配客体级别
MLS 约束大致形如:
ini
mlsconstrain dir search ( l1 dom l2 or t1 == mlstrustedsubject );
主体 s0:{} 不支配 客体 s0:{c512,c768}(空集不是 {c512,c768} 的超集)→ MLS 拒绝。
c512,c768 是 user 0 的 per-user 隔离类别,用于把不同用户的数据互相隔离。
解决方案
将域标记为 MLS 可信主体:
ini
typeattribute trace_copy mlstrustedsubject;
踩坑:mlstrustedsubject 语法错误
错误写法 (会编译报错 syntax error):
ini
mlstrustedsubject trace_copy; ← 错!这是 m4 宏,没加括号
正确写法(二选一):
ini
# 方式 A:m4 宏调用(带括号)
mlstrustedsubject(trace_copy)
# 方式 B:直接 typeattribute(推荐,最不容易出错)
typeattribute trace_copy mlstrustedsubject;
mlstrustedsubject在system/sepolicy/public/te_macros中是一个 m4 宏,展开后就是typeattribute ... mlstrustedsubject;。vendor.te可以直接引用该 attribute。
如何确认是 MLS 在拦
bash
# 1. 确认 TE 规则已编进 policy
adb shell sesearch -A -s trace_copy -t system_data_file -c dir -p search /sys/fs/selinux/policy
# 2. 临时切 permissive 验证
adb shell setenforce 0
# 跑一次 ls/cp,若成功且 AVC 变 permissive=1 → 策略问题
adb shell setenforce 1
六、execute_no_trans:执行系统二进制
日志
ini
avc: denied { execute_no_trans } for path="/system/bin/awk" tcontext=u:object_r:system_file tclass=file
转换结果
lua
allow trace_deletelog system_file:file { read getattr open execute execute_no_trans };
说明
execute_no_trans= 执行该文件但不发生域转换(awk 在当前域内运行)- 通常配套需要
execute、read、getattr、open
七、unlabeled:未打标签的文件
日志
ini
avc: denied { rmdir } for name="kernel" tcontext=u:object_r:unlabeled tclass=dir
avc: denied { read } for name="dropbox" tcontext=u:object_r:unlabeled tclass=lnk_file
avc: denied { getattr } for path="/la_log/vendor/log/android/anr" tcontext=u:object_r:unlabeled tclass=lnk_file
avc: denied { unlink } for name="anr" tcontext=u:object_r:unlabeled tclass=lnk_file
转换结果
bash
allow trace_deletelog unlabeled:lnk_file { read getattr unlink };
allow trace_deletelog unlabeled:dir { search read getattr write rmdir remove_name };
allow trace_deletelog unlabeled:file { unlink getattr };
说明
tcontext=u:object_r:unlabeled表示该文件/目录没有 file_contexts 规则匹配 ,落到默认unlabeledtype- 本例中
/la_log/vendor/fawvw/log/android/下的anr、dropbox、tombstones等符号链接都是unlabeled dev=mmcblk0p29是独立持久分区(la_log),不是 userdata(dm-44)
风险与建议
用 unlabeled 放权限安全收敛性差 (unlabeled 是全局兜底 type,给它放权等于对所有未标 label 的对象放权)。
推荐做法 :给 /la_log 路径打专用 label:
bash
# file_contexts
/la_log(/.*)? u:object_r:la_log_file:s0
bash
# trace_deletelog.te
allow trace_deletelog la_log_file:lnk_file { read getattr unlink };
allow trace_deletelog la_log_file:dir { search read getattr write rmdir remove_name };
allow trace_deletelog la_log_file:file { read getattr unlink };
八、rootfs:根目录遍历
日志
ini
avc: denied { read } for name="/" dev="dm-0" tcontext=u:object_r:rootfs tclass=dir
avc: denied { open } for path="/" dev="dm-0" tcontext=u:object_r:rootfs tclass=dir
转换结果
python
allow trace_deletelog rootfs:dir { read search getattr open };
说明
find /从根目录开始遍历,需要read(读目录项)和open(打开目录)- 配套补齐
search、getattr
九、app_data_file:App 私有数据目录
日志
ini
avc: denied { search } for name="com.vehicle.navi.app" tcontext=u:object_r:app_data_file:s0:c512,c768 tclass=dir
avc: denied { getattr } for path="/data/user_de/0/com.vehicle.navi.app/files/J01/BaiduMapAutoSDK/log" tcontext=u:object_r:app_data_file:s0:c512,c768 tclass=dir
avc: denied { read } for name="log" tcontext=u:object_r:app_data_file:s0:c512,c768 tclass=dir
转换结果
perl
allow trace_deletelog app_data_file:dir { search getattr read open write remove_name rmdir };
allow trace_deletelog app_data_file:file { read getattr open unlink };
allow trace_deletelog app_data_file:lnk_file { read getattr unlink };
neverallow 风险
app_data_file 是 AOSP 重点保护 的 type,system/sepolicy 里有大量 neverallow:
bash
neverallow { domain -appdomain -installd -system_app -platform_app ... } app_data_file:dir search;
自定义 vendor 域不在白名单里,直接加 allow 可能编译失败。
方案权衡
| 方案 | 说明 | 代价 |
|---|---|---|
| A. 直接加 allow | 先试编译,看是否触发 neverallow | 可能编译失败 |
| B. 改脚本绕开 | trace_deletelog 不碰 app 私有目录 | 需改脚本逻辑 |
| C. 换共享路径 | 让 app 把 trace 写到 /data/vendor/trace |
需改 app 代码 |
| D. 加 neverallow 例外 | 在 neverallow 里加 -trace_deletelog |
侵入 AOSP |
本案例中方案 A 编译通过(该项目的 sepolicy 对
app_data_file:dir search限制比纯 AOSP 宽松)。
十、完整策略汇总
trace_copy.te
lua
allow trace_copy system_data_file:dir search;
allow trace_copy mnt_media_rw_file:dir { read setattr };
allow trace_copy mnt_media_rw_file:file { create read write getattr setattr open unlink rename };
allow trace_copy self:capability fsetid;
typeattribute trace_copy mlstrustedsubject;
trace_deletelog.te
perl
# 1. 执行系统二进制
allow trace_deletelog system_file:file { read getattr open execute execute_no_trans };
# 2. /la_log 分区(当前 unlabeled)
allow trace_deletelog unlabeled:lnk_file { read getattr unlink };
allow trace_deletelog unlabeled:dir { search read getattr write rmdir remove_name };
allow trace_deletelog unlabeled:file { unlink getattr };
# 3. 根目录遍历
allow trace_deletelog rootfs:dir { read search getattr open };
# 4. /data/user/0(TE + MLS)
allow trace_deletelog system_data_file:dir search;
typeattribute trace_deletelog mlstrustedsubject;
# 5. app 私有数据目录
allow trace_deletelog app_data_file:dir { search getattr read open write remove_name rmdir };
allow trace_deletelog app_data_file:file { read getattr open unlink };
allow trace_deletelog app_data_file:lnk_file { read getattr unlink };
十一、调试方法论总结
1. AVC 日志转换三步法
- 提取
scontext(domain)、tcontext(type)、tclass、denied(perm) - 套用公式:
allow <domain> <type>:<class> <perm>; - 去重合并同类规则
2. 加了 allow 还是报?排查 MLS
ini
scontext=u:r:xxx:s0 ← 无类别
tcontext=u:object_r:yyy:s0:c512,c768 ← 带类别
→ 加 typeattribute xxx mlstrustedsubject;
3. 编译报 syntax error?检查宏语法
mlstrustedsubject xxx;→ 错mlstrustedsubject(xxx)→ 对typeattribute xxx mlstrustedsubject;→ 对(推荐)
4. 编译报 neverallow?评估方案权衡
- 先试编译,看报错的具体 neverallow 规则
- 优先考虑绕开(改脚本/换路径),其次才考虑加例外
5. 配套权限预判
基于命令的典型行为预判可能需要的权限,一次补齐:
| 命令 | 典型权限 |
|---|---|
ls |
dir: search read getattr open |
cp |
dir: search; file: read getattr open |
rm -r |
dir: search read getattr write rmdir remove_name; file: unlink getattr |
find |
dir: read search getattr open |
chmod |
dir/file: setattr; self:capability fsetid |
| 执行二进制 | file: execute execute_no_trans read getattr open |
6. unlabeled 的规范化
bash
临时方案:allow xxx unlabeled:... ;
规范方案:定义专用 type + file_contexts + allow 专用 type
十二、附录:相关 Android 知识点
/data/data 与 /data/user/0 的区别
- 单用户(user 0) :两者等价,
/data/user/0是/data/data的多用户化别名(符号链接或 bind mount) - 多用户 :
/data/user/<id>才是标准路径,/data/data只对应主用户 - 验证方法:
ls -di /data/data /data/user/0(inode 相同 → 同一物理目录)
BOOT_IMAGE_STORAGE_REQUIREMENT
arduino
private static final long BOOT_IMAGE_STORAGE_REQUIREMENT = DataUnit.MEBIBYTES.toBytes(250);
- 判断
/data分区剩余空间是否够 boot image 使用的阈值(250 MiB) - 与用户提示文案
low_internal_storage_view_text_no_boot里硬编码的 "250MB" 对应 - 不影响 data 分区大小,只决定告警时机
结语
SELinux 策略调试是一个迭代收敛的过程:贴 AVC → 转 allow → 编译 → 刷机 → 再贴 AVC。关键经验:
- 先解决 TE 层,再解决 MLS 层 ------
mlstrustedsubject是万能钥匙 - 注意宏语法 ------
typeattribute比 m4 宏更不容易出错 - 配套权限一次补齐------避免反复编译刷机
- unlabeled 要规范化------临时放行后记得补 file_contexts
- neverallow 是红线------优先绕开,不轻易加例外