20、SELinux策略模块化:策略模块设计,加载顺序,维护与更新
各位同学,今天我们来聊聊SELinux策略的模块化。说实话,我刚接触SELinux那会儿,看到那动辄几万行的te文件,头都是大的。后来Android引入了策略模块化设计,才算是把我们从泥潭里拉了出来。
模块化,说白了就是把一个大策略拆成多个小模块。每个模块负责一块功能,比如网络、蓝牙、相机等等。这样做的好处很明显——你改相机策略,不会影响到蓝牙,维护起来清爽多了。
策略模块(sepolicy modules)设计
在Android系统中,每个策略模块本质上就是一个独立的.cil文件(CIL是Common Intermediate Language的缩写)。我习惯把模块分成三类:
- 基础模块:包含核心类型、属性、初始SID等,是所有模块的基石
- 功能模块:对应具体子系统,比如
netd.te、mediaserver.te - 厂商模块:由设备制造商添加,用于支持硬件相关的策略
每个模块内部的结构其实很清晰。拿一个典型的相机服务模块举例:
// camera.te - 相机服务策略模块
type camera_service, domain;
type camera_service_exec, exec_type, file_type;
init_daemon_domain(camera_service)
// 允许相机访问摄像头设备
allow camera_service camera_device:chr_file rw_file_perms;
// 允许相机访问图形缓冲区
allow camera_service gpu_device:chr_file rw_file_perms;
// 允许相机与mediaserver通信
allow camera_service mediaserver:binder { call transfer };
嗯,这里要注意,模块之间是可以互相引用的。比如相机模块引用了gpu_device这个类型,而这个类型可能定义在图形模块里。这就引出了依赖关系的问题。
模块依赖关系:每个模块在编译时都会生成一个.cil文件,同时附带一个.d依赖文件,记录它引用了哪些其他模块的类型或属性。编译系统会根据这些依赖自动排序。
策略模块加载顺序
加载顺序这个问题,我曾经踩过坑。有一次我写了一个自定义模块,结果死活加载不上,后来发现是因为它依赖的基础模块还没加载。
Android系统的策略模块加载顺序是严格规定的,大致如下:
| 加载顺序 | 模块类型 | 说明 |
|---|---|---|
| 1 | 基础策略模块 | 包含初始SID、安全类、权限定义等 |
| 2 | 系统核心模块 | 如init、zygote、system_server等 |
| 3 | 服务模块 | netd、mediaserver、surfaceflinger等 |
| 4 | 应用域模块 | untrusted_app、platform_app等 |
| 5 | 厂商定制模块 | 设备特有的策略 |
为什么是这个顺序?你想想看,基础模块定义了最底层的类型和属性,后面的模块才能引用它们。系统核心模块先加载,是因为它们要为后续的服务提供基础域。
具体到代码层面,加载顺序是由sepolicy.mk或Android.mk中的BOARD_SEPOLICY_M4DEFS和BOARD_SEPOLICY_UNION等变量控制的。我个人建议,除非你非常清楚自己在做什么,否则不要轻易改动这个顺序。
小技巧:如果你想查看当前系统的模块加载顺序,可以执行adb shell ls -la /sys/fs/selinux/policy_capabilities,或者直接查看/sepolicy文件。不过更直接的方法是看编译时的日志,里面会打印出模块的加载顺序。
模块化策略的维护与更新
维护模块化策略,说白了就是两件事:增删改查。但实际操作起来,坑还是不少的。
添加新模块:
- 在
system/sepolicy或vendor/sepolicy下创建新的.te文件 - 在
Android.bp或Android.mk中注册该模块 - 确保所有依赖的模块都已定义
更新已有模块:
- 修改
.te文件后,重新编译策略 - 使用
adb push推送新的策略文件到设备 - 执行
adb shell setenforce 0先切换到宽容模式测试
删除模块:
- 先检查是否有其他模块依赖它
- 从编译系统中移除该模块的注册
- 清理对应的
.cil文件
警告:千万不要在生产设备上直接删除一个正在被其他模块引用的模块!我曾经见过有人删除了一个基础类型模块,结果整个系统直接起不来,所有服务都因为找不到类型定义而崩溃。那场面,啧啧...
说到更新,我建议你养成一个好习惯:每次修改策略后,先用checkpolicy工具验证一下语法。命令很简单:
# 编译策略文件
checkpolicy -M -c 30 -o policy.30 policy.conf
# 或者直接使用Android的编译系统
mmm system/sepolicy
验证通过后,再推送到设备上测试。千万别直接在生产环境上试,那跟闭着眼睛开车没什么区别。
模块化策略的版本管理
随着Android版本的迭代,策略模块也在不断演进。从Android 8.0开始,Google强制要求使用模块化策略。到了Android 10,又引入了Treble架构,进一步把系统策略和厂商策略分离开。
我个人的做法是,在Git仓库里为每个模块单独建一个目录,每个模块的提交信息里注明修改的原因和影响范围。这样半年后回头看,还能知道当时为什么加那条规则。
另外,建议定期使用sesearch工具检查模块间的访问关系:
# 查看某个域可以访问哪些资源
sesearch --allow -s camera_service -t camera_device
# 查看哪些域可以访问某个资源
sesearch --allow -t camera_device
这样做的好处是,你能及时发现一些意外的权限开放。比如有一次我发现untrusted_app居然能访问camera_device,查了半天才发现是某个模块的规则写得太宽泛了。
最后说一句,模块化策略的维护,核心就是「高内聚、低耦合」。每个模块只做自己该做的事,不要越界。这样不管是升级Android版本,还是适配新硬件,你都能从容应对。
好了,关于策略模块化就聊这么多。记住,好的模块设计能让你少掉一半头发——这话是我做项目时最深的体会。