第23章 安全编译:SELinux 策略编译、密钥配置、安全启动
说到 Android 安全,很多人第一反应是权限管理、应用沙箱这些。但真正深入到系统底层,你会发现 SELinux、密钥配置 和 安全启动 这三样东西,才是 Android 安全的「地基」。
我刚开始接触这部分时,也觉得头大。尤其是 SELinux,光看那些 .te 文件就够晕的。但后来在项目中踩过几次坑,才明白这些机制到底有多重要。今天我就把这三块内容掰开揉碎了讲给你听。
23.1 SELinux 策略编译
SELinux,全称 Security-Enhanced Linux。说白了,它就是给 Android 系统装了一套「门禁系统」。每个进程能访问什么资源,都得按规则来。
在 Android 构建系统中,SELinux 策略的编译是一个独立环节。我们来看它的工作流程。
23.1.1 策略源文件结构
SELinux 策略源文件通常放在 device/ 或 system/sepolicy/ 目录下。常见的文件类型有:
- .te 文件:类型强制文件,定义域和规则
- .fc 文件:文件上下文,定义文件的安全标签
- .if 文件:接口文件,封装可复用的规则
- file_contexts:全局文件上下文映射
- property_contexts:属性服务上下文映射
举个例子,一个简单的 .te 文件长这样:
# 定义一个名为 my_daemon 的域
type my_daemon, domain;
type my_daemon_exec, exec_type, file_type;
# 允许 my_daemon 读取系统日志
allow my_daemon logd:unix_dgram_socket sendto;
allow my_daemon self:capability { dac_override };
嗯,这里要注意:每个域都必须有明确的 allow 规则。缺一条,你的守护进程可能就跑不起来。
23.1.2 编译流程
在 Android 构建系统中,SELinux 策略的编译由 sepolicy.mk 或 Android.bp 驱动。核心步骤是:
- 收集所有 .te、.fc、.if 文件
- 调用 checkpolicy 工具,将源文件编译成二进制策略文件
- 生成 file_contexts.bin,供 init 进程加载
- 打包到 boot.img 或 system.img 中
我记得有一次,我在编译时遇到一个奇怪的错误:
libsepol.report_failure: neverallow on line 123 of system/sepolicy/public/domain.te
violated by allow my_daemon self:capability { sys_admin };
这个错误的意思是:你写的 allow 规则,违反了全局的 neverallow 约束。说白了,就是系统不允许某个域拥有 sys_admin 权限。我当时查了半天,最后发现是策略文件写错了。
adb shell dmesg | grep avc 检查 AVC 拒绝日志。
23.1.3 策略编译的 Makefile 示例
在 Android 10 及之后版本,策略编译主要通过 Android.bp 配置。但老项目里还是能看到 Makefile 的方式:
# 编译 SELinux 策略
include $(CLEAR_VARS)
LOCAL_MODULE := sepolicy
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/selinux
LOCAL_SRC_FILES := $(LOCAL_PATH)/sepolicy
include $(BUILD_PREBUILT)
我个人习惯在 BoardConfig.mk 中指定策略路径:
BOARD_SEPOLICY_DIRS += \
device/xxx/sepolicy \
vendor/xxx/sepolicy
23.2 密钥配置
密钥配置是安全编译的另一大块。Android 使用密钥来签名系统镜像、验证 OTA 更新、保护应用签名等。
23.2.1 密钥类型
| 密钥名称 | 用途 | 存储位置 |
|---|---|---|
| releasekey | 系统镜像签名 | build/target/product/security/ |
| platform | 平台应用签名 | 同上 |
| shared | 共享 UID 应用签名 | 同上 |
| media | 媒体进程签名 | 同上 |
| verity | dm-verity 哈希表签名 | 同上 |
你想想看,如果这些密钥泄露了,攻击者就能伪造系统签名,后果不堪设想。所以 密钥文件必须严格保护,不能提交到版本控制系统中。
23.2.2 配置密钥路径
在 device/xxx/device.mk 中,你需要指定密钥路径:
# 指定 release 密钥
PRODUCT_DEFAULT_DEV_CERTIFICATE := device/xxx/security/releasekey
# 指定 verity 密钥
PRODUCT_VERITY_SIGNING_KEY := device/xxx/security/verity_key
# 指定 OTA 签名密钥
PRODUCT_OTA_PUBLIC_KEYS := device/xxx/security/ota_key
我个人建议:开发阶段使用 testkey,发布阶段再换成 releasekey。testkey 是公开的,任何人都能拿到,但方便调试。
make generate_key 命令生成新的密钥对。生成的密钥会放在 build/target/product/security/ 下。
23.3 安全启动
安全启动(Verified Boot)是 Android 安全链的起点。它确保设备从开机到系统加载,每一步都是可信的。
23.3.1 安全启动的流程
我用一张图来展示安全启动的核心逻辑:
从图中你能看到,安全启动是一条信任链。每一级都验证下一级的签名,直到系统完全启动。如果任何一级验证失败,设备就会进入 警告状态 或直接关机。
23.3.2 配置安全启动
在构建系统中,安全启动的配置主要在 BoardConfig.mk 中:
# 启用 dm-verity
BOARD_AVB_ENABLE := true
# 指定 AVB 密钥
BOARD_AVB_KEY_PATH := device/xxx/security/avb_key.priv.pem
BOARD_AVB_ALGORITHM := SHA256_RSA4096
# 启用系统分区验证
BOARD_AVB_SYSTEM_KEY_PATH := device/xxx/security/avb_system.priv.pem
BOARD_AVB_SYSTEM_ALGORITHM := SHA256_RSA2048
我记得有一次,我在配置 AVB 时忘了设置 BOARD_AVB_ENABLE := true,结果编译出来的镜像没有签名信息。刷到设备上后,安全启动直接报错,设备变砖了。嗯,从那以后我每次编译前都会检查这个变量。
23.3.3 调试安全启动问题
当安全启动失败时,设备通常会显示一个黄色警告图标。你可以通过以下方式排查:
- 查看内核日志:
adb shell dmesg | grep -i avb - 检查分区哈希:
adb shell avbtool verify_image --image /dev/block/boot - 验证签名:
openssl dgst -verify public_key.pem -signature sig.bin boot.img
我曾经在项目中遇到一个诡异的问题:安全启动在开发板上正常,但在量产设备上总是失败。排查了半天,发现是 密钥长度不匹配。开发板用的是 2048 位密钥,但量产设备的 Boot ROM 只支持 4096 位。所以,密钥配置一定要和硬件规格对齐。
23.4 总结
这一章我们聊了三个安全编译的核心模块:
- SELinux 策略编译:从 .te 文件到二进制策略,每一步都要小心 neverallow 约束
- 密钥配置:releasekey、platform、verity 等密钥各有用途,开发阶段用 testkey,发布阶段换 releasekey
- 安全启动:信任链从 Boot ROM 开始,每级验证上一级的签名,AVB 是核心配置
说实话,这三块内容单独拎出来都能讲一整天。但作为入门,你只要掌握它们的基本原理和配置方法就够了。在实际项目中,多踩几次坑,自然就熟练了。
公众号:蓝海资料掘金营,微信deep3321