23、安全启动与OTA:Verified Boot、dm-verity、AB分区升级

嵌入式Android设备的安全,说白了就是两件事:启动时别被人篡改升级时别变砖

我最早接触安全启动是在一个车机项目上。客户要求系统必须通过Google的CTS认证,其中Verified Boot是硬性指标。当时我心想,不就是校验一下签名吗?结果踩了一堆坑。今天我把这些经验掰开揉碎讲给你听。

23.1 安全启动(Verified Boot)

安全启动的核心逻辑很简单:信任链。从硬件开始,一级一级往上校验,每一级都确认下一级的签名是合法的。

Android的信任链长这样:

  1. Boot ROM(固化在芯片里,不可修改)
  2. Bootloader(校验下一级)
  3. Boot image(包含kernel和dtb)
  4. System分区(通过dm-verity校验)

每一级都只信任上一级。如果Boot ROM发现Bootloader被改了,直接断电重启。这就是所谓的「硬件根信任」

关键点:Verified Boot不是「防破解」的,而是「防篡改」的。它保证你拿到的系统是厂商签过名的版本,但不保证这个版本没有漏洞。

23.2 dm-verity:文件系统的守护者

dm-verity是Linux内核的一个设备映射器(device mapper)目标。它做的事情很纯粹:对块设备做哈希校验

我举个例子你就明白了。假设system分区有1GB数据,dm-verity会把这1GB切成4KB的块,每个块算一个哈希值。然后把这些哈希值组成一棵Merkle树,根哈希存在boot image里。

读数据时,dm-verity会实时校验:

  • 读到的块,哈希对不对?
  • 如果不对,是哪个块被改了?
  • 能不能用Merkle树恢复?

嗯,这里要注意:dm-verity有两种模式:

模式 行为 适用场景
EIO(错误) 校验失败直接返回I/O错误 生产环境,严格安全
LOGGING(日志) 校验失败只打日志,不阻止访问 调试阶段,方便排查
RESTART(重启) 校验失败直接重启设备 车机、医疗等关键设备

我在项目中遇到过一个问题:某款芯片的eMMC读性能很差,开了dm-verity后,系统启动慢了3秒。后来发现是哈希树太大,频繁读eMMC导致的。解决办法是把哈希树放到cache分区,用ramdisk缓存一部分。

小技巧:调试dm-verity时,可以用adb shell dmesg | grep dm-verity查看校验日志。如果看到dm-verity corruption,说明分区被改过。

23.3 AB分区升级

AB分区,也叫无缝升级(Seamless Update)。说白了就是准备两套系统分区:slot Aslot B

你想想看,传统升级方式是这样的:

  • 下载升级包 → 写入system分区 → 重启
  • 如果写入过程中断电了?变砖。

AB分区怎么做的?

  1. 当前运行在slot A
  2. 下载升级包,写入slot B(后台进行)
  3. 写入完成后,标记slot B为「可启动」
  4. 重启,bootloader切换到slot B
  5. 如果slot B启动失败,自动回退到slot A

这就是所谓的「原子性升级」——要么成功,要么回退,没有中间状态。

23.4 分区布局与AB切换

一个典型的AB分区布局长这样:

/dev/block/bootdevice/by-name/
├── boot_a
├── boot_b
├── system_a
├── system_b
├── vendor_a
├── vendor_b
├── misc          # 存储slot状态
├── metadata      # 存储动态分区信息
└── super         # 动态分区(包含system、vendor等)

bootloader通过读取misc分区中的slot_suffix字段,决定启动哪个slot。这个字段的值要么是_a,要么是_b

我曾经踩过一个坑:某次OTA升级后,设备反复重启。查了半天,发现是misc分区被意外清空了,bootloader读不到slot信息,默认选了slot A,但slot A已经被标记为「旧版本」。解决办法是在升级脚本里加一个保护:写入slot标记前,先备份到另一个位置。

警告:千万不要手动修改misc分区!我见过有人用dd命令直接写,结果把分区表搞坏了。AB切换应该通过update_enginebootctl工具来做。

23.5 OTA升级流程实战

一个完整的OTA升级流程,我习惯把它分成三个阶段:

阶段一:准备

  • 生成升级包(差分或全量)
  • 签名升级包
  • 上传到服务器

阶段二:下载与校验

  • 设备下载升级包
  • 校验签名(防止中间人攻击)
  • 校验哈希(防止传输损坏)

阶段三:应用与切换

  • 解压升级包到非活跃slot
  • 更新bootloader环境变量
  • 重启,bootloader切换slot
  • 新系统首次启动,执行post-install脚本

差分升级(delta OTA)能省不少流量。比如一个500MB的系统,差分包可能只有50MB。但差分升级对分区布局很敏感——如果分区大小变了,差分算法会失败。

23.6 安全启动与OTA的联动

安全启动和OTA不是孤立的。它们之间有一个关键纽带:AVB(Android Verified Boot)

AVB做的事情包括:

  • 在boot image中嵌入dm-verity的根哈希
  • 在分区头部嵌入哈希树和签名
  • 提供avbtool命令行工具,用于生成和验证

OTA升级时,新系统的boot image必须包含正确的dm-verity根哈希。否则,升级后安全启动会失败,设备直接进入「橙色状态」(表示系统被修改过)。

我记得有一次,同事做OTA升级,新系统启动后一直显示「Your device is corrupted」。排查后发现,他忘了用avbtool重新计算哈希树,直接拿旧系统的boot image打包了。嗯,这种低级错误,犯过一次就不会再犯了。

23.7 知识体系总览

下面这张图,我把安全启动、dm-verity、AB分区升级的关系画出来了。你看一眼就能明白整体架构。

嵌入式Android安全启动与OTA知识体系 安全启动(Verified Boot) Boot ROM → Bootloader → Boot image → System dm-verity Merkle树哈希校验 EIO / LOGGING / RESTART AB分区升级 Slot A ↔ Slot B 原子性升级 / 自动回退 AVB(Android Verified Boot) 连接安全启动与OTA的桥梁 OTA升级完整流程 准备升级包 下载与校验 写入非活跃slot 切换与回退 三者结合:启动时防篡改 + 运行时校验 + 升级时原子性

23.8 避坑指南

最后,我把自己踩过的坑整理一下,你遇到了可以直接翻出来看:

  • 哈希树太大导致启动慢:把哈希树放到cache分区,或者用更小的块大小(比如2KB代替4KB)
  • AB分区空间不够:差分升级比全量升级省空间,但差分算法对分区布局敏感
  • OTA升级后安全启动失败:检查boot image中的dm-verity根哈希是否更新
  • misc分区被误写:用bootctl工具操作,别用dd
  • 回退后数据丢失:AB升级只保护系统分区,用户数据(data分区)不保护。升级前记得备份

我曾经在一个量产项目上,因为忘了更新AVB的哈希树,导致3000台设备全部无法OTA。最后只能一台一台用USB刷机。那滋味,你懂的。

所以,安全启动和OTA不是「加上去就行」的功能。你得理解它们的原理,才能在出问题时快速定位。希望今天的内容对你有帮助。

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