34、SELinux调试:avc日志、策略编写、enforce模式、permissive模式
SELinux,说实话,刚接触Android系统的人看到它头都大。我当年第一次遇到avc denial时,也是一脸懵——明明权限都给了,为什么还报错?后来才明白,SELinux是Android安全模型的最后一道防线。它不看你是什么用户,它只看进程的上下文。
这一章,我们就来聊聊SELinux调试的那些事。avc日志怎么看?策略怎么写?enforce和permissive模式到底怎么用?嗯,一个一个来。
1. avc日志:SELinux的“黑匣子”
avc日志,全称是Access Vector Cache。说白了,就是SELinux拒绝访问时留下的记录。你想想看,系统里某个服务突然挂了,或者某个功能用不了,第一反应是什么?看logcat。但logcat里不一定有avc信息,avc日志通常出现在dmesg或logcat -b events里。
我个人习惯,遇到权限问题,先跑这条命令:
adb shell dmesg | grep avc
或者更直接一点:
adb shell logcat -b events | grep avc
avc日志长什么样?我给你看个真实例子:
type=1400 audit(0.0:123): 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 }:被拒绝的操作是“读”。
- comm="mediaserver":发起操作的进程是mediaserver。
- name="config.xml":它想访问的文件是config.xml。
- scontext=u:r:mediaserver:s0:源上下文,也就是mediaserver的安全上下文。
- tcontext=u:object_r:system_file:s0:目标上下文,config.xml属于system_file类型。
- tclass=file:目标类型是文件。
- permissive=0:当前是enforce模式,所以真的拒绝了。
我在项目中遇到过,有一次某个第三方应用无法读取配置文件,查了半天logcat没发现异常。后来用dmesg一看,avc denial一大堆。所以,记住:avc日志是SELinux调试的第一手资料。
grep -v 过滤掉已知的、无害的denial。比如 dmesg | grep avc | grep -v "kernel"。
2. enforce模式 vs permissive模式
这两个模式,是SELinux的“开关”。
- Enforce模式:严格模式。avc日志里记录的denial,会真的阻止操作。系统会报错,功能可能不可用。
- Permissive模式:宽容模式。avc日志里记录的denial,只记录,不阻止。操作照常进行,但日志里会留下记录。
调试时,我建议先用permissive模式。为什么呢?因为你可以先让功能跑起来,同时收集所有avc denial。等收集全了,再切回enforce模式,一条一条修复。
切换模式的方法:
# 查看当前模式
adb shell getenforce
# 临时切换为permissive(重启后失效)
adb shell setenforce 0
# 切换回enforce
adb shell setenforce 1
注意,setenforce 0 需要root权限。如果你在userdebug版本上,通常没问题。但如果是user版本,可能被限制。
3. 策略编写:从avc日志到.te文件
avc日志看懂了,模式也切了,接下来就是写策略。SELinux策略文件以.te结尾,放在system/sepolicy目录下。
还是拿刚才那条avc日志举例:
avc: denied { read } for comm="mediaserver" scontext=u:r:mediaserver:s0 tcontext=u:object_r:system_file:s0 tclass=file
这条日志告诉我们:mediaserver进程想读system_file类型的文件,但被拒绝了。要修复它,我们需要在mediaserver的te文件中添加一条allow规则。
找到mediaserver.te文件,添加:
allow mediaserver system_file:file read;
就这么简单?嗯,大部分情况是的。但有时候,你还需要考虑更多。比如,如果目标文件是system_file类型,但它的路径比较特殊,你可能需要创建一个新的文件类型,或者使用file_type_auto_trans。
我整理了一个常见的策略编写对照表:
| avc日志中的操作 | 对应的allow规则 | 说明 |
|---|---|---|
| { read } | allow src_type tgt_type:file read; | 读文件 |
| { write } | allow src_type tgt_type:file write; | 写文件 |
| { open } | allow src_type tgt_type:file open; | 打开文件 |
| { create } | allow src_type tgt_type:file create; | 创建文件 |
| { execute } | allow src_type tgt_type:file execute; | 执行文件 |
| { search } | allow src_type tgt_type:dir search; | 搜索目录 |
| { connectto } | allow src_type tgt_type:tcp_socket connectto; | TCP连接 |
写策略时,有几点要注意:
- 不要滥用
allow *:我曾经见过有人为了省事,直接写allow mediaserver *:* *;。这等于把SELinux的防护全废了。一定要精确到具体的类型和操作。 - 使用宏定义:Android SELinux提供了很多宏,比如
unix_socket_connect、file_type_auto_trans等。用宏可以减少代码量,也更容易维护。 - 编译后验证:写完策略,编译刷机后,记得切回enforce模式,然后复现之前的操作,看看avc日志是否还有新的denial。
4. 实战:一次完整的SELinux调试流程
光说不练假把式。我拿一个真实案例,带你走一遍流程。
场景: 某个系统服务无法读取/data/local/tmp/test.txt文件。
第一步:查看avc日志
adb shell dmesg | grep avc
输出:
avc: denied { read } for pid=5678 comm="system_server" name="test.txt" dev="mmcblk0p29" ino=1234 scontext=u:r:system_server:s0 tcontext=u:object_r:shell_data_file:s0 tclass=file permissive=0
第二步:分析
源上下文是system_server,目标上下文是shell_data_file。system_server想读shell_data_file类型的文件,被拒绝了。
第三步:临时切到permissive模式
adb shell setenforce 0
确认功能恢复正常。然后收集所有avc日志。
第四步:编写策略
在system/sepolicy/private/system_server.te中添加:
allow system_server shell_data_file:file read;
第五步:编译并验证
编译后刷机,切回enforce模式,复现操作。再次查看avc日志,确认没有新的denial。
5. 知识体系:一张图看懂SELinux调试
下面这张SVG图,总结了SELinux调试的核心流程。你可以把它当作一个快速参考。
这张图里,我特意把“切Permissive模式”放在“编写策略”之前。为什么?因为只有先放行,你才能确认问题确实出在SELinux上,而不是其他原因。我曾经跳过这一步,直接写策略,结果发现根本就不是SELinux的问题——白忙活一场。
6. 避坑指南
最后,分享几个我踩过的坑:
- 不要忽略
neverallow规则:Android SELinux有全局的neverallow规则,你写的allow规则如果和neverallow冲突,编译会报错。这时候,你需要考虑是不是设计上就有问题,而不是强行绕过。 - 注意文件上下文:有时候,avc日志里显示的是
system_file类型,但你以为文件是data_file类型。这是因为文件上下文(file_contexts)没有正确配置。记得检查file_contexts文件。 - permissive模式下的“假象”:在permissive模式下,功能正常不代表策略写对了。因为permissive模式只是不阻止,但avc日志里可能还有大量denial。一定要切回enforce模式验证。
嗯,SELinux调试其实没那么可怕。掌握avc日志、理解enforce和permissive模式、学会写策略,这三板斧用好了,大部分问题都能解决。记住,耐心一点,一条一条处理avc denial,系统会越来越安全。