10、SELinux安全策略:安全上下文、策略规则编写、avc审计日志分析
说到嵌入式Android的安全性,SELinux是个绕不开的话题。我刚开始接触它的时候,觉得这东西就是个「拦路虎」——动不动就权限 denied,日志里一堆看不懂的 avc 错误。但后来我明白了,它其实是系统安全的最后一道防线。
说白了,SELinux 就是给系统里的每个进程、每个文件都贴了个「标签」,然后规定谁可以碰谁。你想想看,如果没有这套机制,一个第三方 App 拿到 root 权限后就能为所欲为,那还得了?
10.1 安全上下文:每个进程的「身份证」
在 SELinux 的世界里,每个主体(进程)和客体(文件、socket 等)都有一个安全上下文。你可以把它理解成身份证,上面写着这个进程或文件属于哪个域、哪个类型。
格式长这样:
user:role:type:level
举个例子:
u:r:init:s0
u:object_r:system_file:s0
这里:
- user:SELinux 用户,Android 里基本都是
u - role:角色,进程通常是
r,文件是object_r - type:类型,这是最关键的。进程叫「域」(domain),文件叫「类型」(type)
- level:安全级别,Android 里默认
s0
我记得有一次排查问题,发现某个系统服务老是启动失败。用 ps -Z 一看,它的安全上下文居然是 u:r:untrusted_app:s0——一个系统服务跑在 untrusted_app 域里,这肯定不行。后来发现是 init.rc 里忘了配 seclabel。
查看安全上下文的命令:
# 查看进程
ps -Z
# 查看文件
ls -Z
# 查看当前进程
id -Z
10.2 策略规则编写:让该通的通,该堵的堵
策略规则说白了就是「谁可以做什么」。在 Android 里,策略文件通常放在 system/sepolicy 目录下。我个人习惯把规则按模块拆分,比如 hal_foo.te、init.te,这样好维护。
最基本的规则格式:
allow 源类型 目标类型:对象类 许可集;
举个例子:
allow hal_graphics_composer default_android_hwservice:hwservice_manager find;
这条规则的意思是:允许 hal_graphics_composer 这个域,在 hwservice_manager 这个对象类上,对 default_android_hwservice 这个类型执行 find 操作。
嗯,这里要注意:对象类有很多种,比如 file、dir、socket、binder、property_service 等等。许可集则对应具体的操作,比如 read、write、open、create。
我的小技巧:刚开始写策略时,别想着一次写对。先用 permissive 模式跑起来,收集 avc 日志,再一条条加 allow 规则。我曾经在一个项目里直接写 enforcing 模式,结果系统起不来,折腾了两天。
还有一种常用的规则是 dontaudit,用来屏蔽某些已知的、无害的拒绝日志:
dontaudit hal_graphics_composer self:capability sys_rawio;
但别滥用。我见过有人把所有 avc 错误都用 dontaudit 屏蔽了,结果真正的问题被掩盖了。这就像家里装了监控,但把所有报警都关掉——那还装它干嘛?
10.3 AVC审计日志分析:从拒绝中学习
当 SELinux 拒绝一个操作时,会在 logcat 或 dmesg 里输出一条 avc 日志。格式大概这样:
avc: denied { read } for pid=1234 comm="mediaserver" name="config.txt" dev="mmcblk0p1" ino=5678 scontext=u:r:mediaserver:s0 tcontext=u:object_r:system_file:s0 tclass=file permissive=0
这条日志告诉我们:
- denied { read }:被拒绝的操作是 read
- comm="mediaserver":发起操作的进程名
- scontext:源安全上下文,这里是 mediaserver 域
- tcontext:目标安全上下文,这里是 system_file 类型
- tclass=file:对象类是文件
- permissive=0:当前是 enforcing 模式,所以真的拒绝了
那怎么解决呢?我一般按这个步骤来:
- 确认是不是真的需要这个权限——有时候是代码写错了路径
- 用 audit2allow 工具生成规则:
audit2allow -i avc_log.txt - 把生成的规则加到对应的 .te 文件里
- 重新编译、刷机、验证
避坑指南:我曾经直接用 audit2allow 生成的规则全盘照抄,结果发现它生成了很多不必要的权限。比如一个进程只需要读某个文件,但 audit2allow 可能连 write、create 都加上了。这会让攻击面变大。所以,手动审查一下生成的规则,只加真正需要的。
10.4 知识体系图:SELinux 策略核心逻辑
下面这张图帮你理清 SELinux 策略的核心逻辑。我画的时候特意把「审计日志分析」放在了中间,因为在实际开发中,你大部分时间都在跟 avc 日志打交道。
10.5 实战:从 avc 日志到策略规则
假设你在 logcat 里看到这样一条错误:
avc: denied { write } for pid=2345 comm="my_daemon" name="data.txt" dev="mmcblk0p1" ino=7890 scontext=u:r:my_daemon:s0 tcontext=u:object_r:app_data_file:s0 tclass=file permissive=0
分析一下:
- 源域:
my_daemon - 目标类型:
app_data_file - 对象类:
file - 操作:
write
那么对应的策略规则就是:
allow my_daemon app_data_file:file write;
但等等——你真的要让 my_daemon 能写所有 app_data_file 吗?这范围太大了。更好的做法是创建一个专门给 my_daemon 用的文件类型,比如 my_daemon_data_file,然后只允许它写这个类型。
最佳实践:权限粒度越细越好。不要因为省事就写 allow my_daemon app_data_file:file { read write create open };。你想想看,如果 my_daemon 被攻破了,攻击者就能读写所有 App 的数据文件——这后果很严重。
好了,关于 SELinux 安全策略,核心就是这三件事:理解安全上下文、编写策略规则、分析 avc 日志。刚开始可能会觉得繁琐,但多做几个项目后,你会发现它其实挺有逻辑的。记住:SELinux 不是敌人,它是帮你守住底线的保安。
公众号:蓝海资料掘金营,微信deep3321