当你正在开发一个Android系统应用,突然在logcat中看到这样的错误信息:"avc: denied { write } for comm="com.test" name="/" dev="dm-5"...", 这意味着你的应用被SELinux安全机制拦截了。我第一次遇到这个问题时也是一头雾水,后来才明白这是Android系统的重要安全特性在发挥作用。
SELinux(Security-Enhanced Linux)是Linux内核的一个安全模块,Android从4.3版本开始引入。它采用强制访问控制(MAC)机制,即使应用拥有root权限,也必须遵守SELinux策略规则。这就像是在传统的"谁可以做什么"的权限系统上,又加了一层"谁可以对什么对象执行什么操作"的精细控制。
在实际开发中,我们经常会遇到SELinux阻止合法操作的情况。比如你的应用需要写入某个系统目录,虽然已经申请了相应的Android权限,但还是会被SELinux拦截。这时候就需要分析avc denied日志,并添加适当的SELinux权限规则。
在开始处理SELinux权限问题前,我们需要准备好开发环境。首先确保你已经下载了完整的AOSP源码,并且能够成功编译系统镜像。我建议使用Ubuntu 18.04或20.04作为开发系统,这是Google官方推荐的Android开发环境。
进入AOSP源码根目录后,执行以下命令初始化环境:
bash复制source build/envsetup.sh
lunch
选择你要编译的目标设备后,环境变量就设置好了。这一步非常重要,否则后续使用audit2allow工具时会报错"ANDROID_HOST_OUT not set. Have you run lunch?"。
audit2allow工具的位置在external/selinux/prebuilts/bin/audit2allow,但初始化环境后,直接在根目录就可以使用这个命令了。这个工具的作用是将avc denied日志转换成可读的SELinux权限规则建议。
当你的应用被SELinux阻止时,logcat中会产生类似这样的日志:
code复制09-28 09:30:06.221 5734 5734 W Thread-20: type=1400 audit(0.0:2346): avc: denied { write } for comm="com.test" name="/" dev="dm-5" ino=2 scontext=u:r:system_app:s0 tcontext=u:object_r:system_data_root_file:s0 tclass=dir permissive=0
我们需要把这些日志保存到文件中进行分析。建议使用adb logcat命令抓取日志:
bash复制adb logcat | grep "avc: denied" > avc_log.txt
不过直接抓取的日志包含了很多额外信息,我们需要清理一下,只保留从"avc: denied"开始的部分。你可以用文本编辑器处理,或者使用sed命令:
bash复制sed -n 's/.*\(avc: denied.*\)/\1/p' avc_log.txt > cleaned_avc_log.txt
清理后的文件内容应该是这样的:
code复制avc: denied { write } for comm="com.test" name="/" dev="dm-5" ino=2 scontext=u:r:system_app:s0 tcontext=u:object_r:system_data_root_file:s0 tclass=dir permissive=0
有了清理后的avc日志文件,我们就可以使用audit2allow工具生成权限规则了。将cleaned_avc_log.txt文件放在AOSP源码根目录下,执行:
bash复制audit2allow -i cleaned_avc_log.txt
工具会输出类似这样的结果:
code复制#============= system_app ==============
allow system_app system_data_root_file:dir write;
这就是我们需要添加的SELinux规则。有时候一条avc日志可能对应多条权限规则,audit2allow会一并给出。如果命令没有输出,可以尝试在日志文件中复制多行相同的avc日志再试。
生成的权限规则需要添加到相应的.te文件中。首先我们需要找到正确的te文件位置。执行以下命令查看SEPOLICY目录:
bash复制get_build_var BOARD_SEPOLICY_DIRS
通常系统应用的SELinux规则放在system/sepolicy目录下,厂商自定义的规则放在device/<厂商>/sepolicy目录下。根据规则中的主体类型(如system_app),我们应该找到对应的te文件。
例如,如果规则是针对system_app的,就找system_app.te文件。我建议遵循以下原则选择te文件:
找到正确的te文件后,添加audit2allow生成的规则。例如:
bash复制vim device/xxx/common/sepolicy/system_app.te
添加:
code复制allow system_app system_data_root_file:dir write;
添加规则后,编译时可能会遇到neverallow冲突错误。这是Android SELinux的一个安全特性,禁止某些高危权限组合。错误信息通常如下:
code复制libsepol.report_failure: neverallow on line 634 of system/sepolicy/public/init.te violated by allow system_app system_data_root_file:dir { write };
遇到这种情况,我们需要分析冲突原因。通常有两种解决方案:
例如,如果错误显示init.te中有如下neverallow规则:
code复制neverallow { domain -init -toolbox -vendor_init -vold } system_data_root_file:dir { write add_name remove_name };
我们可以尝试从domain中排除system_app:
code复制neverallow { domain -init -toolbox -vendor_init -vold -system_app } system_data_root_file:dir { write add_name remove_name };
修改后,可以单独编译sepolicy验证:
bash复制make -j12 sepolicy
或者进入system/sepolicy目录执行:
bash复制mma -j12
在实际开发中,我遇到过各种SELinux相关问题,这里分享几个常见问题的解决方法:
问题1:编译时报错"Match operation 'empty' is not valid"
这是因为te或contexts文件末尾没有空行。解决方法很简单,确保所有te文件和contexts文件最后都有一个空行。
问题2:需要添加多个权限
如果需要对同一对象类型添加多个权限,可以使用预定义的权限宏。例如:
code复制allow system_app hidraw_device:chr_file rw_file_perms;
这比逐个列出read、write等权限更简洁。常用的权限宏定义在system/sepolicy/prebuilts/api/28.0/public/global_macros中。
问题3:权限添加后仍然被拒绝
这可能是因为:
可以使用以下命令检查最终策略:
bash复制adb shell seinfo -a
问题4:如何临时调试SELinux问题
在开发阶段,可以临时将SELinux设为permissive模式:
bash复制adb shell setenforce 0
这样SELinux只会记录违规而不会真正阻止操作。但切记不要在产品中使用这个设置。
当简单的权限添加不能解决问题时,可能需要更深入的调试。以下是我总结的一些高级技巧:
使用sesearch分析策略
sesearch工具可以查询策略中的规则:
bash复制adb shell sesearch --allow -s system_app -t system_data_root_file -c dir -p write
查看完整的安全上下文
有时问题出在文件或进程的安全上下文不正确。可以使用以下命令查看:
bash复制adb shell ls -Z # 查看文件上下文
adb shell ps -Z # 查看进程上下文
使用audit2why
audit2why工具可以提供更详细的权限拒绝解释:
bash复制audit2why -i avc_log.txt
自定义类型转换
如果默认的类型转换不符合需求,可以定义自己的转换规则。例如:
code复制type_transition system_app system_data_file:dir system_app_data_file;
使用宏简化规则
对于重复的规则模式,可以定义自己的宏。例如:
code复制define(`system_app_file_trans', `
type_transition system_app $1:dir $2;
type_transition system_app $1:file $2;
')
记得在修改SELinux策略后,不仅要测试你的特定用例,还要确保没有破坏其他功能。最好能运行完整的CTS测试来验证系统整体安全性没有降低。