1. 初识SELinux权限问题从avc denied报错开始第一次在Android开发中看到SELinux: avc: denied的日志时我整个人都是懵的。这种报错通常长这样type1400 audit(0.0:2346): avc: denied { write } for commcom.test name/ devdm-5 ino2 scontextu:r:system_app:s0 tcontextu:object_r:system_data_root_file:s0 tclassdir permissive0这段日志其实在告诉我们system_app进程scontext试图对system_data_root_file类型的目录tcontext执行write操作但被SELinux拒绝了。理解这个日志结构很关键scontext发起操作的主体通常是进程tcontext被操作的对象文件、目录等tclass对象类型dir表示目录denied { write }被拒绝的具体操作遇到这种情况很多新手的第一反应是直接关闭SELinuxsetenforce 0这确实能让问题消失但绝对是个坏习惯。我在早期项目中也这么干过结果上线后遇到严重的安全问题。正确的做法是通过audit2allow工具生成权限规则然后精准添加到te文件中。2. 搭建调试环境准备工作不可少在开始处理SELinux权限问题前我们需要准备好开发环境。这里我分享几个容易踩坑的点首先确保AOSP源码已经完成完整编译然后执行source build/envsetup.sh lunch 你的设备型号如果不执行这两步直接运行audit2allow会报错ANDROID_HOST_OUT not set. Have you run lunch?。这个错误我遇到过无数次现在每次都会先检查环境变量。工具路径在external/selinux/prebuilts/bin/audit2allow为了方便使用我习惯在~/.bashrc中添加别名alias audit2allowexternal/selinux/prebuilts/bin/audit2allow这样在任何目录都能直接调用。另外建议安装这些辅助工具sudo apt-get install policycoreutils setools它们提供的seinfo、sesearch等命令在分析策略时非常有用。比如查看现有权限sesearch -A -s system_app -t system_data_root_file -c dir3. 日志处理技巧从原始log到干净输入原始日志通常带着时间戳和其他无关信息我们需要提取出纯粹的avc denied部分。常见错误是直接把带时间戳的日志扔给audit2allow结果工具无法识别。正确做法是创建一个avc_log.txt文件只保留从avc: denied开始的部分avc: denied { write } for commcom.test name/ devdm-5 ino2 scontextu:r:system_app:s0 tcontextu:object_r:system_data_root_file:s0 tclassdir permissive0我常用这个sed命令快速过滤adb logcat -d | sed -n /avc: denied/p | sed s/.*avc: denied/avc: denied/ avc_log.txt如果遇到多条相关denied日志建议都保存下来一起处理。有时候一个功能需要多个权限批量处理更高效。我曾经处理过一个相机模块的问题发现需要同时添加7个不同权限才能正常工作。4. 使用audit2allow生成规则有了干净的日志文件生成规则就很简单了audit2allow -i avc_log.txt典型输出如下# system_app allow system_app system_data_root_file:dir write;但这里有几个注意事项如果命令没输出可能是日志格式不对尝试多复制几行相同日志生成的规则可能需要调整比如使用预定义的权限组重要规则建议添加注释说明原因我更喜欢用这个命令它会包含更多上下文信息audit2allow -i avc_log.txt -p有时候生成的规则会包含一些看似多余的权限这时候不要随意删减。有次我删掉了getattr权限结果导致应用在Android 9上正常但在Android 11上崩溃。5. 定位和修改te文件找到正确的te文件位置很关键。首先获取策略目录get_build_var BOARD_SEPOLICY_DIRS然后根据主体类型scontext选择te文件。比如system_app就找system_app.te。我常用的查找命令find . -name *system_app*.te添加规则时要注意保持文件原有风格比如有的项目要求每行一个权限添加合理的注释尽量使用预定义的权限组比如代替allow system_app system_data_root_file:dir { write read open search };使用allow system_app system_data_root_file:dir rw_dir_perms;这些宏定义通常在global_macros中可以通过grep -r define(\rw_dir_perms .6. 处理neverallow冲突最头疼的问题莫过于遇到neverallow冲突。比如libsepol.report_failure: neverallow on line 634 of system/sepolicy/public/init.te violated by allow system_app system_data_root_file:dir { write };这说明我们尝试添加的权限违反了系统的基本安全策略。处理方案有检查是否有其他替代权限可用修改对象的安全上下文在极特殊情况下可能需要修改neverallow规则本身比如上面的错误可能需要修改init.te-neverallow { domain -init -toolbox -vendor_init -vold } system_data_root_file:dir { write add_name remove_name }; neverallow { domain -init -toolbox -vendor_init -vold } system_data_root_file:dir { add_name remove_name };但修改neverallow要非常谨慎最好先和团队的安全工程师讨论。我有次擅自修改导致设备无法通过CTS测试。7. 编译验证与常见错误修改后建议先单编sepolicy测试make -j12 sepolicy常见编译错误及解决方法文件格式问题Could not read line from vendor/etc/selinux/vendor_property_contexts: Match operation empty is not valid解决方法确保所有te和contexts文件末尾有空行语法错误Error while expanding policy通常是因为括号不匹配或拼写错误仔细检查修改的部分类型未定义type system_data_root_file is not defined可能需要添加类型声明或检查依赖关系单编通过后建议进行全编译并刷机测试。测试时可以用这个命令监控新的denied日志adb logcat | grep avc: denied8. 高级技巧与最佳实践经过多次项目实战我总结出这些经验权限最小化原则只添加必要的权限能用read就不给write使用类型转换有时比直接添加权限更安全比如type_transition system_app system_data_file:dir app_data_file;属性使用对于跨多个域共享的权限可以考虑定义属性attribute system_app_domain; allow system_app_domain system_data_file:dir r_dir_perms;调试技巧adb shell setenforce 0 # 临时关闭SELinux adb shell dmesg | grep avc # 查看内核denied日志版本兼容性不同Android版本的SELinux策略可能有差异特别是升级Android版本时要重新检查记得有次在Android 10上正常的策略在Android 12上就触发了neverallow。后来发现是Google收紧了对system分区写入的限制。最后提醒大家修改SELinux策略后一定要进行充分测试包括正常功能测试和安全性测试。我习惯建立一个检查清单基本功能是否正常是否引入了新的avc denied是否违反了CTS要求是否有过度赋权的情况