20、SELinux策略模块化:策略模块设计,加载顺序,维护与更新

各位同学,今天我们来聊聊SELinux策略的模块化。说实话,我刚接触SELinux那会儿,看到那动辄几万行的te文件,头都是大的。后来Android引入了策略模块化设计,才算是把我们从泥潭里拉了出来。

模块化,说白了就是把一个大策略拆成多个小模块。每个模块负责一块功能,比如网络、蓝牙、相机等等。这样做的好处很明显——你改相机策略,不会影响到蓝牙,维护起来清爽多了。

策略模块(sepolicy modules)设计

在Android系统中,每个策略模块本质上就是一个独立的.cil文件(CIL是Common Intermediate Language的缩写)。我习惯把模块分成三类:

  • 基础模块:包含核心类型、属性、初始SID等,是所有模块的基石
  • 功能模块:对应具体子系统,比如netd.temediaserver.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.mkAndroid.mk中的BOARD_SEPOLICY_M4DEFSBOARD_SEPOLICY_UNION等变量控制的。我个人建议,除非你非常清楚自己在做什么,否则不要轻易改动这个顺序。

小技巧:如果你想查看当前系统的模块加载顺序,可以执行adb shell ls -la /sys/fs/selinux/policy_capabilities,或者直接查看/sepolicy文件。不过更直接的方法是看编译时的日志,里面会打印出模块的加载顺序。

模块化策略的维护与更新

维护模块化策略,说白了就是两件事:增删改查。但实际操作起来,坑还是不少的。

添加新模块

  • system/sepolicyvendor/sepolicy下创建新的.te文件
  • Android.bpAndroid.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,查了半天才发现是某个模块的规则写得太宽泛了。

SELinux策略模块化架构 基础策略模块(初始SID、安全类、权限定义) 系统核心模块(init、zygote、system_server) 服务模块(netd、mediaserver、surfaceflinger) 应用域模块(untrusted_app、platform_app) 厂商定制模块(设备特有策略) 加载顺序 依赖方向

最后说一句,模块化策略的维护,核心就是「高内聚、低耦合」。每个模块只做自己该做的事,不要越界。这样不管是升级Android版本,还是适配新硬件,你都能从容应对。

好了,关于策略模块化就聊这么多。记住,好的模块设计能让你少掉一半头发——这话是我做项目时最深的体会。


公众号:蓝海资料掘金营,微信deep3321