11、SELinux策略调试工具:avc log解析,audit2allow工具使用,setenforce/permissive模式切换,dmesg与logcat配合
说实话,SELinux 策略调试是很多 Android 工程师的噩梦。我刚入行那会儿,一看到 avc: denied 就头皮发麻。后来摸清了门道,才发现其实就那么几板斧——avc log 解析、audit2allow 自动生成策略、setenforce 临时切换模式,再加上 dmesg 和 logcat 配合定位。今天我把这些工具串起来讲,保证你听完就能上手。
核心思路:先放行(permissive),再分析(avc log),最后固化(te 文件)。千万别一上来就写策略,那是给自己挖坑。
11.1 avc log 解析——读懂内核的“拒绝信”
avc log 是 SELinux 的审计日志。每次访问被拒绝,内核都会打印一条记录。你想想看,这就像系统在跟你说:“嘿,有人想干坏事,被我拦下来了!”
一条典型的 avc log 长这样:
type=1400 audit(123456.789:0): avc: denied { read } for pid=1234 comm="mediaserver" name="config.xml" dev="mmcblk0p25" ino=5678 scontext=u:r:mediaserver:s0 tcontext=u:object_r:system_file:s0 tclass=file permissive=0
我来拆解一下关键字段:
| 字段 | 含义 | 我的经验 |
|---|---|---|
| denied { read } | 被拒绝的权限 | 可能是 read/write/execute 等 |
| scontext | 发起访问的主体(源上下文) | 比如 mediaserver 进程 |
| tcontext | 被访问的客体(目标上下文) | 比如 system_file 类型的文件 |
| tclass | 客体的类别 | file/dir/socket 等 |
| permissive=0 | 当前是否强制模式 | 0=强制,1=宽容 |
我在项目中遇到过最坑的一次:avc log 里明明有 denied 记录,但系统就是不打印。后来发现是内核的 audit 缓冲区满了,日志被丢弃了。嗯,这里要注意——avc log 不是 100% 可靠的,如果拒绝太频繁,内核会丢日志。
小技巧:用 adb shell dmesg | grep avc 抓取所有 avc 日志。如果日志太多,可以加 | tail -n 50 只看最后 50 条。
11.2 audit2allow——自动生成策略的“偷懒神器”
audit2allow 是我最喜欢的工具。说白了,它就是个翻译器——把 avc log 翻译成 te 文件里的 allow 语句。
基本用法:
# 从 dmesg 提取 avc log,生成策略
adb shell dmesg | grep avc | audit2allow -p policy.conf
# 或者从 logcat 提取
adb logcat -b events | grep avc | audit2allow -p policy.conf
输出示例:
#============= mediaserver ==============
allow mediaserver system_file:file read;
我曾经犯过一个错误:直接把 audit2allow 生成的策略全部塞进 te 文件。结果系统是能跑了,但安全策略形同虚设。你想想看,audit2allow 只是把拒绝的权限补上,它不会帮你判断这个权限是否合理。
警告:audit2allow 生成的策略一定要人工审核。特别是涉及 unlabeled 或 init 上下文的规则,很可能是因为文件上下文没打对,而不是真的需要加权限。
11.3 setenforce/permissive 模式切换——调试时的“免死金牌”
调试 SELinux 策略时,我最常用的命令就是 setenforce。它允许你在强制模式和宽容模式之间切换,不用重启系统。
# 查看当前模式
getenforce
# 切换到宽容模式(只记录不阻止)
setenforce 0
# 切换回强制模式
setenforce 1
我个人习惯是:先 setenforce 0 让系统跑起来,同时抓取 avc log。等所有功能都验证通过后,再 setenforce 1 验证策略是否完整。
但要注意——setenforce 0 只是临时生效,重启后恢复。如果你需要永久修改,得改 /sys/fs/selinux/enforce 或者内核启动参数。
避坑指南:我曾经在生产环境的设备上忘了切回强制模式,结果跑了三天才发现。从那以后,我每次调试完都会用 adb shell getenforce 确认一下模式。
11.4 dmesg 与 logcat 配合——双管齐下定位问题
avc log 主要出现在 dmesg 里,但有些场景下 logcat 也能抓到。我建议两个一起看,互相印证。
为什么?因为 dmesg 是内核日志,记录的是最原始的 avc 拒绝信息。而 logcat 是用户空间日志,有些应用层框架(比如 servicemanager)会二次封装 avc 错误,打印出更友好的提示。
举个例子:
# dmesg 里的原始记录
avc: denied { read } for pid=1234 comm="mediaserver" ...
# logcat 里的应用层提示
E/SELinux ( 1234): SELinux: denied read for path=/data/config.xml
我在项目中遇到过:dmesg 里明明没有 avc 日志,但 logcat 里报 SELinux 错误。后来发现是某个服务自己捕获了 avc 异常,然后打印了自定义日志。所以,两个渠道都要看,别只盯着一个。
配合技巧:同时开两个终端窗口,一个跑 adb logcat -b events | grep avc,另一个跑 adb shell dmesg -w | grep avc。这样能实时看到两边的日志。
11.5 实战流程——从拒绝到放行
说了这么多,我们来走一遍完整流程。假设你的 App 在启动时崩溃,怀疑是 SELinux 的问题。
- 确认模式:
adb shell getenforce,如果是 Enforcing,先切到 Permissive。 - 复现问题:重新启动 App,触发崩溃路径。
- 抓取日志:
adb shell dmesg | grep avc > avc.log,同时adb logcat -b events | grep avc >> avc.log。 - 生成策略:
cat avc.log | audit2allow -p policy.conf > new.te。 - 审核策略:打开 new.te,逐条检查。问自己三个问题:
- 这个权限真的需要吗?
- 有没有更细粒度的控制方式?
- 会不会引入安全风险?
- 编译部署:将审核后的策略加入对应的 te 文件,重新编译 bootimage,刷机验证。
- 回归测试:切回 Enforcing 模式,确保所有功能正常。
核心原则:永远不要在 Enforcing 模式下调试新策略。先放行,再分析,最后固化。这是 SELinux 调试的黄金法则。
11.6 知识体系总览
下面这张图总结了本章的核心逻辑,我建议你保存下来,调试时对照着看:
这张图把四个工具串成了一个闭环。你从 avc log 入手,用 audit2allow 生成策略,用 setenforce 切换模式验证,用 dmesg 和 logcat 双通道确认。每一步都有对应的工具,每一步都有坑要避。
好了,这一章的内容就到这里。记住我说的:先放行,再分析,最后固化。这是 SELinux 调试的不二法门。
公众号:蓝海资料掘金营,微信deep3321