14、属性系统SELinux策略:property_contexts编写,属性读写权限控制,系统属性安全策略定制
属性系统,说白了就是Android的“全局配置中心”。你平时用getprop和setprop看到的那些键值对,背后都有一套严格的SELinux管控机制。我刚开始接触这块时,觉得不就是读写个属性嘛,能有多复杂?直到有一次,某个系统服务因为写了一个不该写的属性,直接导致整个开机流程卡死……嗯,从那以后,我再也不敢小看property_contexts了。
14.1 property_contexts 文件结构
这个文件位于/system/etc/selinux/目录下,它的作用很简单——给每个属性打上SELinux标签。你想想看,系统里成百上千个属性,如果没有分类管理,那权限控制就是一团乱麻。
文件格式长这样:
# 系统核心属性
net. u:object_r:net_radio_prop:s0
sys. u:object_r:system_prop:s0
persist. u:object_r:persist_prop:s0
ctl. u:object_r:ctl_prop:s0
# 厂商定制属性
vendor. u:object_r:vendor_prop:s0
ro.vendor. u:object_r:vendor_prop:s0
# 特定属性精确匹配
ro.build.fingerprint u:object_r:build_prop:s0
persist.sys.timezone u:object_r:system_prop:s0
每一行由两部分组成:
- 属性匹配模式:支持通配符,
.作为分隔符 - SELinux上下文:格式为
u:object_r:类型:s0
ro.build.fingerprint和ro.build.同时存在,精确匹配优先。我曾经踩过这个坑——写了个模糊匹配把不该覆盖的属性给覆盖了,调试了一整天。
14.2 属性读写权限控制机制
属性权限控制,本质上就是问三个问题:
- 谁想读?——发起操作的进程是什么域(domain)
- 读什么?——目标属性是什么类型(type)
- 允许吗?——TE规则里有没有放行
对应的TE规则长这样:
# 允许system_app域读取system_prop类型的属性
allow system_app system_prop:property_service { read };
# 允许init域设置ctl_prop类型的属性(控制服务启停)
allow init ctl_prop:property_service { set };
# 允许shell域读取和转储所有属性
allow shell property_type:property_service { read dump };
核心要点:属性的读写权限是分开控制的。你可以让某个进程只能读不能写,或者只能写特定的属性。我在项目中遇到过,某个三方应用疯狂尝试写入persist.开头的属性,被SELinux直接拦截,日志里全是avc denial。这就是权限控制的价值。
14.3 系统属性安全策略定制实战
假设我们要定制一个厂商专属属性vendor.custom.feature,只有特定的系统服务能写,其他进程只能读。怎么做?
第一步:定义属性类型
在property_contexts中添加:
vendor.custom.feature u:object_r:vendor_custom_prop:s0
第二步:编写TE规则
# 定义类型
type vendor_custom_prop, property_type;
# 允许my_service域写入
allow my_service vendor_custom_prop:property_service { set };
# 允许所有域读取(通过property_type的通用规则)
# 注意:这里不需要额外写allow,因为property_type已经包含了read权限
第三步:验证策略
编译后刷机,用avcstat和dmesg | grep avc检查是否有权限拒绝。我建议你写一个测试脚本:
#!/system/bin/sh
# 测试写入
setprop vendor.custom.feature "test_value"
echo "写入结果: $?"
# 测试读取
getprop vendor.custom.feature
echo "读取结果: $?"
property_contexts的匹配顺序,看看是不是被其他模糊匹配给截胡了。我曾经遇到过,vendor.前缀的模糊匹配把精确匹配给覆盖了,折腾了两天才发现。
14.4 属性系统的SELinux策略架构图
下面这张图展示了属性从请求到响应的完整流程,以及SELinux在其中的拦截点:
14.5 常见问题与避坑指南
问题1:属性写不进去,但日志里没有avc denial
这种情况我遇到过好几次。原因往往是property_contexts里没有匹配到该属性,导致它被分配了默认的default_prop类型。而default_prop的权限通常很严格。解决办法:检查property_contexts的匹配规则,确保你的属性被正确分类。
问题2:persist属性重启后丢失
嗯,这里要注意。persist.开头的属性会写入/data/property/目录。如果SELinux策略阻止了property_service写入该目录,属性就存不下来。我曾经排查过一个bug,就是persist.vendor.xxx属性每次重启都重置,最后发现是file_contexts里/data/property的标签配错了。
问题3:ctl属性控制服务失败
ctl.开头的属性用于控制服务的启动和停止。只有init域才有权限设置这些属性。如果你想让某个系统服务也能控制服务启停,需要在TE规则里显式放行:
allow my_service ctl_prop:property_service { set };
adb shell dmesg | grep "property"可以快速过滤出属性相关的SELinux拒绝日志。我习惯把这个命令做成一个alias,调试时直接敲propavc就能看到所有属性权限问题。
14.6 属性安全策略的最佳实践
| 场景 | 推荐做法 | 不推荐做法 |
|---|---|---|
| 厂商定制属性 | 使用vendor.前缀,单独定义类型 |
复用系统属性类型,容易权限泄露 |
| 持久化属性 | 使用persist.前缀,严格控制写入权限 |
允许所有域写入persist属性 |
| 控制类属性 | 只放行给init和特定系统服务 | 放行给shell或普通应用 |
| 只读属性 | 使用ro.前缀,只赋予read权限 |
同时赋予read和set权限 |
最后说一句,属性系统的SELinux策略,说白了就是“谁能在什么条件下读写什么属性”。把这个逻辑理清楚了,策略文件写起来就顺了。我在项目里见过最离谱的情况,是一个同事把allow * property_type:property_service { read };写进了策略里——这等于把所有属性的读权限都放开了,还好在code review阶段被拦下来了。
嗯,关于property_contexts和属性权限控制,今天就聊到这儿。记住一点:权限最小化,类型精确化,这是定制属性安全策略的核心原则。
公众号:蓝海资料掘金营,微信deep3321