24、SELinux策略测试框架:单元测试、集成测试与自动化工具
说实话,SELinux策略写完了,最怕什么?
最怕一上线就avc: denied刷屏。或者更惨——系统直接起不来。
我早期做Android系统开发时,就吃过这个亏。写了一个看起来很完美的te文件,结果刷机后连adb都连不上。嗯,从那以后,我养成了一个习惯:策略必须测试,而且要系统化地测试。
今天我们就聊聊SELinux策略的测试框架。说白了,就是怎么保证你写的策略是对的、完整的、不会炸系统的。
测试的三个层次
我个人习惯把SELinux策略测试分成三层:
- 单元测试——验证单个规则或单个域的策略是否正确
- 集成测试——验证多个域之间的交互是否正常
- 系统级测试——验证整个系统的SELinux策略是否一致、无漏洞
你想想看,这和软件开发中的测试金字塔是一个道理。底层单元测试跑得快、定位准;上层集成测试覆盖场景更全。
核心原则:先写测试,再写策略。或者至少,策略写完立刻补测试。
我在项目中见过太多人写完策略就扔一边,结果上线后出问题,排查起来比写策略还痛苦。
SELinux策略单元测试
单元测试,说白了就是验证某一条规则或者某一个域的策略行为是否符合预期。
Android AOSP里其实有一套现成的工具——sepolicy-tests。它位于system/sepolicy/tests/目录下。
举个例子,我想验证untrusted_app域是否能访问sdcard_external类型:
# 在 system/sepolicy/tests/ 下添加测试文件
# untrusted_app_test.te
# 测试:untrusted_app 应该能读 sdcard_external
allow_test(untrusted_app, sdcard_external, dir, read)
# 测试:untrusted_app 不应该能写 system_data_file
dontallow_test(untrusted_app, system_data_file, file, write)
然后运行测试:
make sepolicy_tests
# 或者单独跑
./out/host/linux-x86/bin/sepolicy-tests
这里有个坑,我提醒一下:单元测试只检查策略文件本身的语法和逻辑,它不会真的去跑进程。所以它快,但覆盖不了运行时行为。
我的经验:单元测试适合用来做回归验证。每次修改策略后,先跑一遍单元测试,确保没有引入明显的权限泄露或语法错误。这个过程应该集成到CI里,每次提交代码自动触发。
集成测试方法
集成测试就不一样了。它要验证的是:真实进程在SELinux约束下,能不能正常工作。
Android里最常用的集成测试框架是CTS(Compatibility Test Suite)中的SELinux相关测试项。但说实话,CTS覆盖的场景有限,很多定制化的策略需要自己写测试。
我常用的方法有两种:
方法一:基于avc_log的验证
启动目标服务或执行目标操作,然后抓取avc_log,检查是否有预期外的denied日志。
# 启动服务
adb shell setenforce 0
adb shell start my_service
# 抓取avc日志
adb logcat -b events | grep "avc: denied" > avc_denied.log
# 分析日志
# 如果出现预期外的denied,说明策略有遗漏
这个方法简单粗暴,但有个问题:你必须在permissive模式下跑,否则进程可能直接挂掉。而且,permissive模式下有些行为会和enforcing模式不一样。
注意:千万不要在enforcing模式下做集成测试,除非你确定策略已经完全覆盖。我曾经有一次在enforcing模式下测试新服务,结果服务启动失败,连带着system_server都挂了,最后只能强刷。
方法二:使用test_domain宏
Android SELinux提供了一套test_domain宏,可以快速创建测试用的域和规则。这在开发阶段特别有用。
# 在测试te文件中
type test_domain, domain;
type test_exec, file_type, exec_type;
# 使用test_domain宏
test_domain(test_domain)
# 添加测试规则
allow test_domain test_exec:file { read execute };
然后编译进系统,刷机,启动测试进程。这种方式更接近真实环境,但需要刷机,迭代慢。
自动化测试工具
手动测试终究不是长久之计。我建议搭建一套自动化测试框架。
Android里最成熟的方案是Tradefed + SELinux测试用例。但如果你做的是非AOSP项目,或者想更灵活,可以考虑自己写脚本。
我分享一个我常用的自动化测试脚本框架:
#!/bin/bash
# selinux_auto_test.sh
# 自动化SELinux策略测试脚本
# 配置
DEVICE_IP="192.168.1.100"
TEST_CASES=(
"test_untrusted_app_sdcard"
"test_system_app_system_data"
"test_vendor_app_vendor_data"
)
# 1. 编译策略并推送
echo "编译策略..."
make selinux_policy
adb push out/target/product/xxx/vendor/etc/selinux /vendor/etc/
# 2. 重启设备
adb reboot
sleep 30
# 3. 执行测试用例
for case in "${TEST_CASES[@]}"; do
echo "运行测试: $case"
adb shell $case
if [ $? -ne 0 ]; then
echo "测试失败: $case"
# 抓取avc日志
adb logcat -d | grep avc > avc_${case}.log
fi
done
# 4. 生成报告
echo "生成测试报告..."
python3 generate_report.py
这个脚本虽然简单,但已经能覆盖大部分场景。你可以把它集成到Jenkins或GitLab CI里。
自动化测试的关键点:
- 每次策略变更后自动触发测试
- 测试结果要包含avc日志分析
- 失败用例要能自动复现并抓取现场信息
- 测试报告要清晰,能一眼看出哪个域、哪个操作出了问题
测试框架的整体架构
说了这么多,我画一张图帮你理清思路:
这张图展示了从策略输入到最终CI集成的完整流程。你想想看,如果没有这套框架,每次改策略都要手动刷机、手动测试、手动分析日志,那效率得多低?
常见问题与避坑指南
最后,我分享几个实际项目中踩过的坑:
我曾经犯过的错:
- 只测permissive模式:有些权限在permissive下能过,enforcing下就炸。因为permissive模式下内核不会真正拒绝访问,只是记录日志。所以一定要在enforcing模式下做最终验证。
- 忽略neverallow规则:单元测试能检查语法,但检查不了neverallow冲突。我遇到过写了一条allow规则,结果和底层的neverallow冲突,编译能过,但运行时直接panic。
- 测试覆盖不全:只测了正常流程,没测异常流程。比如文件不存在、权限不足等场景。这些场景下SELinux的行为可能完全不同。
我的建议:
- 单元测试、集成测试、系统级测试都要做,缺一不可
- 自动化测试脚本要包含avc日志的自动分析,不要人工看日志
- 每次策略变更后,先跑单元测试,再刷机做集成测试
- 测试报告要保留历史记录,方便回溯
好了,关于SELinux策略测试框架,就聊到这里。这套方法我在多个Android项目中实践过,效果不错。你如果刚开始搭建测试体系,建议从单元测试入手,逐步扩展到集成测试和自动化。别想着一步到位,先跑起来,再优化。
公众号:蓝海资料掘金营,微信deep3321