4、Android安全策略编译流程:策略源文件、编译工具链、生成策略二进制文件

好,咱们今天聊聊安全策略是怎么从一堆文本文件,变成手机内核能认的二进制文件的。

说实话,我刚接触AOSP安全框架那会儿,也觉得这玩意儿挺玄乎。不就是写几条规则吗?怎么还要编译?后来踩了几个坑才明白——策略编译是整个安全体系的基石,这一步要是错了,后面全白搭。

4.1 策略源文件长什么样?

先看看我们手头有什么原料。在AOSP源码里,安全策略文件主要散落在几个地方:

  • system/sepolicy/ —— 核心策略,所有设备通用
  • device/<vendor>/<device>/sepolicy/ —— 厂商定制策略
  • build/target/board/generic/sepolicy/ —— 通用板级策略

这些目录里,你会看到一堆后缀为 .te 的文件。对,就是 Type Enforcement 的缩写。每个 .te 文件定义了一组规则,比如:

# 允许 system_app 读取 /data 目录下的文件
allow system_app data_file:dir r_dir_perms;
allow system_app data_file:file r_file_perms;

我个人习惯把策略文件分成三类:

  • 类型声明文件types.te)—— 定义各种安全上下文
  • 权限规则文件domain.te)—— 定义主体对客体的访问权限
  • 属性文件attributes.te)—— 定义属性,方便批量管理

嗯,这里要注意:千万别把规则写到类型声明文件里。我见过有同事图省事,把 allow 语句直接塞进 types.te,结果编译报错,排查了半天。

4.2 编译工具链:谁在干活?

策略源文件写好了,谁来把它变成二进制?答案是 checkpolicysepolicy-analyze 这两个工具。

在AOSP的编译流程里,它们被封装在 BuildSystemsepolicy.mk 里。大致流程是这样的:

步骤 工具 输入 输出
1. 预处理 m4 + cpp .te 文件 + .conf 配置 展开后的 .te 文件
2. 编译 checkpolicy 展开后的 .te 文件 sepolicy 二进制策略
3. 打包 secilc 多个 .cil 文件 sepolicy 最终镜像

为什么需要预处理?因为策略文件里可以用 include 和宏定义。比如:

# 在 .te 文件里引用公共规则
include <global_macros.te>

# 定义一个宏
define(`my_app_domain', `my_app')

预处理阶段会把所有 include 展开,把宏替换掉。这一步要是漏了,编译出来的策略就不完整。

我曾经踩过的坑: 有一次我修改了 global_macros.te 里的一个宏定义,但忘了重新编译所有依赖它的策略文件。结果刷机后,某个系统服务直接崩溃,因为权限规则没生效。从那以后,我每次改宏定义都会 make clean 再重新编译。

4.3 生成策略二进制文件

预处理完,就该 checkpolicy 上场了。它会读取展开后的 .te 文件,进行语法检查,然后生成一个 策略二进制文件(通常叫 sepolicy)。

这个二进制文件是内核能直接解析的格式。它包含了:

  • 所有类型和属性的定义
  • 所有 allowdontauditneverallow 规则
  • 角色和用户的映射关系
  • 初始安全上下文(initial_sid_context

你可以用 sepolicy-analyze 来查看这个二进制文件的内容:

# 查看策略中的类型列表
sepolicy-analyze sepolicy -t

# 查看某个类型的权限
sepolicy-analyze sepolicy -a system_app

# 检查是否有冲突规则
sepolicy-analyze sepolicy -c

我个人习惯在编译完成后,先用 sepolicy-analyze 快速检查一下,确保没有明显的错误。比如,我会检查 neverallow 规则是否被违反:

sepolicy-analyze sepolicy -n

如果输出为空,说明所有 neverallow 规则都得到了遵守。如果有输出,那就要回去改策略了。

4.4 编译流程中的避坑指南

说了这么多,总结几个我实际项目中遇到的坑:

  1. 文件编码问题:策略文件必须是 UTF-8 编码,不能有 BOM。我遇到过从 Windows 拷贝过来的文件,带了个 BOM 头,编译直接报错。
  2. 宏定义冲突:不同厂商的 .te 文件里可能定义了同名的宏,导致预处理阶段互相覆盖。解决办法是给宏加前缀,比如 VENDOR_MY_MACRO
  3. 依赖顺序:如果 A 文件引用了 B 文件里定义的类型,那么 B 文件必须在 A 文件之前被编译。AOSP 的 sepolicy.mk 已经处理了大部分依赖,但如果你自己加文件,要小心。
  4. 二进制兼容性:不同 Android 版本生成的策略二进制格式可能不同。比如 Android 10 和 Android 11 的 sepolicy 文件就不能互换。刷机前一定要确认版本匹配。
一个小技巧: 如果你不确定策略文件有没有问题,可以用 checkpolicy -d 开启调试模式,它会输出详细的编译过程信息,方便定位问题。

好了,关于策略编译流程,今天就聊到这儿。说白了,就是三步:写源文件、用工具链编译、生成二进制。但每一步都有细节,稍不注意就会翻车。下次咱们聊聊怎么把编译好的策略刷进设备,以及如何验证它是否生效。