24. 安全启动链:Verified Boot、dm-verity、AVB校验

安全启动链,说白了就是一套「从开机第一行代码开始,就确保系统没被篡改」的机制。我早年调试一台设备时,发现它明明刷了官方固件,却总是报内核校验失败。查了三天,最后发现是 bootloader 分区被一个坏块搞出了比特翻转。嗯,从那以后我对安全启动的敬畏心就上来了。

你想想看,如果手机一开机就跑着被篡改过的代码,那后面所有的安全机制——权限模型、沙箱、SELinux——全都是空中楼阁。所以安全启动链必须从硬件信任根开始,一级验一级,直到整个系统启动完毕。

24.1 信任根与信任链

安全启动的核心概念是「信任根」(Root of Trust)。这个根通常固化在芯片的 ROM 里,物理上不可写。我习惯把它理解成「上帝之手」——它是最初的、绝对可信的代码。

信任链的传递逻辑很简单:

  • ROM 代码校验 bootloader 的签名
  • bootloader 校验 boot 分区的签名
  • boot 分区中的内核校验 system 分区的完整性
  • system 分区中的 init 进程继续校验 vendor、product 等分区

每一级只信任上一级,上一级只签名下一级。这个链条一旦断裂,启动就会中止。

关键点:信任链不是「信任所有人」,而是「只信任被签名的代码」。任何未签名的修改都会导致启动失败。

24.2 Verified Boot 的基本原理

Verified Boot 是 Android 最早引入的安全启动方案。它的工作方式其实很朴素:

  1. 每个分区(boot、system、vendor 等)都有一个对应的哈希值
  2. 这些哈希值被签名后存储在 vbmeta 分区中
  3. 启动时,bootloader 读取 vbmeta,验证签名,然后比对实际分区的哈希
  4. 如果哈希不匹配,说明分区被篡改,系统拒绝启动或进入恢复模式

我在项目中遇到过一种情况:OTA 升级后,vbmeta 的签名没更新,导致所有分区校验失败。当时团队差点要回退版本,后来发现是签名脚本里漏传了一个参数。这种低级错误,排查起来最折磨人。

个人建议:在开发阶段,可以用 adb disable-verity 临时关闭校验。但发布版本一定要打开,否则你的设备就是裸奔的。

24.3 dm-verity:块设备层的完整性保护

Verified Boot 只在校验启动时检查一次。那系统运行起来之后呢?如果有人运行时篡改了 system 分区怎么办?

这就是 dm-verity 登场的地方。它是 Linux 内核的一个设备映射器(device mapper)目标,专门用来做块设备层的实时完整性校验。

它的工作流程是这样的:

  • 系统将 system 分区映射为一个 dm-verity 设备
  • 每次读取一个块时,dm-verity 都会计算该块的哈希值
  • 将计算出的哈希值与预先存储的哈希树(hash tree)比对
  • 如果匹配,数据返回给上层;如果不匹配,返回 I/O 错误

说白了,dm-verity 就是给每个磁盘块都配了一个「指纹」。你读到的每一字节,它都要确认指纹对得上。

# 查看 dm-verity 状态
adb shell dmesg | grep dm-verity

# 输出示例
[    3.456789] dm-verity: mapping system on /dev/block/mmcblk0p23
[    3.456812] dm-verity: hash device /dev/block/mmcblk0p24
[    3.456834] dm-verity: root hash f1a2b3c4d5e6...
[    3.456856] dm-verity: device verified successfully

注意:dm-verity 只能检测篡改,不能阻止篡改。如果有人直接写坏块,dm-verity 会报错,但不会修复。我曾经见过一个案例,攻击者通过物理手段短接 eMMC 引脚,绕过了 dm-verity 的校验。嗯,物理攻击是另一门学问了。

24.4 AVB(Android Verified Boot)2.0

AVB 2.0 是 Google 在 Android 8.0 引入的新一代安全启动方案。它比旧版 Verified Boot 更灵活,也更强大。

AVB 的核心改进有几点:

  • vbmeta 结构升级:vbmeta 不再只是哈希列表,而是包含完整的描述符,支持多个分区、多个哈希算法
  • 回滚保护:vbmeta 中存储了版本号,防止攻击者刷回旧版系统
  • 锁状态管理:支持锁定/解锁状态,解锁时允许刷写,锁定时强制校验
  • 自定义描述符:OEM 可以添加自己的校验逻辑,比如校验 modem 分区

我个人最喜欢 AVB 的一点是它的「回滚保护」机制。你想想看,如果攻击者找到了一个旧版系统的漏洞,他可以把你的设备刷回那个有漏洞的版本,然后利用漏洞提权。AVB 的版本号机制直接封死了这条路。

# 查看 AVB 版本信息
adb shell avbtool info_image --image vbmeta.img

# 输出示例
Footer version: 1.0
Image size: 4096 bytes
Original image size: 2048 bytes
VBMeta header version: 1.0
Required libavb version: 1.0
Rollback index: 42
Rollback index location: 0
Descriptors:
    Hash descriptor:
        Image name: system
        Algorithm: sha256
        Root digest: a1b2c3d4...
    Hash descriptor:
        Image name: boot
        Algorithm: sha256
        Root digest: e5f6g7h8...

24.5 安全启动链的完整流程

把上面这些串起来,就是一条完整的安全启动链。我画了一张图帮你理解:

Android 安全启动链完整流程 硬件信任根(ROM) Bootloader Boot 分区(内核+dtb) System 分区(dm-verity) Vendor / Product / ODM 校验机制说明 1. ROM 校验 Bootloader 签名 2. Bootloader 校验 vbmeta 3. vbmeta 包含各分区哈希 4. 内核启动后启用 dm-verity 5. dm-verity 实时校验块设备 6. AVB 提供回滚保护 锁状态影响: • 锁定:强制校验,不可刷写 • 解锁:允许刷写,但会清除数据 • 回滚:版本号递减则拒绝启动 注:任何一级校验失败, 系统都会进入恢复模式

这张图展示了从硬件信任根到所有分区的完整校验链条。每一级都依赖上一级的签名,任何一环断裂,系统都不会正常启动。

24.6 实际开发中的避坑指南

我在多个项目中踩过安全启动的坑,这里分享几个最常见的:

  • 签名密钥管理:千万不要把发布密钥放在代码仓库里。我曾经见过一个团队把 release key 提交到了 GitHub,第二天就被自动化脚本扫到了。嗯,那天的 Slack 消息量直接爆表。
  • 回滚版本号递增:每次发布新版本,记得递增 vbmeta 中的回滚版本号。如果忘了,旧版系统可以刷回来,安全启动形同虚设。
  • 测试锁状态:开发时用解锁状态,发布前一定要测试锁定状态下的启动流程。我遇到过解锁时一切正常,锁定后直接变砖的情况——原因是某个分区哈希没更新。
  • dm-verity 的 I/O 性能影响:dm-verity 每次读块都要计算哈希,对性能有轻微影响。在低端设备上,这个影响可能达到 5-10%。如果用户抱怨系统卡顿,可以考虑调整哈希树的大小。

一个小技巧:在开发阶段,可以用 avbtool make_vbmeta_image 手动生成 vbmeta,方便调试。但发布版本一定要用构建系统自动生成,避免人为错误。

24.7 总结

安全启动链是 Android 安全体系的基石。没有它,权限模型、沙箱、SELinux 全都是空中楼阁。Verified Boot 提供了启动时的完整性校验,dm-verity 提供了运行时的实时保护,AVB 2.0 则把这两者整合成了一个完整的、可扩展的框架。

我个人认为,理解安全启动链的关键不在于记住那些命令和参数,而在于理解「信任传递」这个核心思想。每一级只信任上一级,每一级只签名下一级。这个链条一旦建立,整个系统的可信基础就稳了。

嗯,安全启动链的内容就到这里。如果你在实际项目中遇到相关问题,欢迎随时交流。


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