23、安全启动与OTA:Verified Boot、dm-verity、AB分区升级
嵌入式Android设备的安全,说白了就是两件事:启动时别被人篡改,升级时别变砖。
我最早接触安全启动是在一个车机项目上。客户要求系统必须通过Google的CTS认证,其中Verified Boot是硬性指标。当时我心想,不就是校验一下签名吗?结果踩了一堆坑。今天我把这些经验掰开揉碎讲给你听。
23.1 安全启动(Verified Boot)
安全启动的核心逻辑很简单:信任链。从硬件开始,一级一级往上校验,每一级都确认下一级的签名是合法的。
Android的信任链长这样:
- Boot ROM(固化在芯片里,不可修改)
- Bootloader(校验下一级)
- Boot image(包含kernel和dtb)
- 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 A和slot B。
你想想看,传统升级方式是这样的:
- 下载升级包 → 写入system分区 → 重启
- 如果写入过程中断电了?变砖。
AB分区怎么做的?
- 当前运行在slot A
- 下载升级包,写入slot B(后台进行)
- 写入完成后,标记slot B为「可启动」
- 重启,bootloader切换到slot B
- 如果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_engine或bootctl工具来做。
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分区升级的关系画出来了。你看一眼就能明白整体架构。
23.8 避坑指南
最后,我把自己踩过的坑整理一下,你遇到了可以直接翻出来看:
- 哈希树太大导致启动慢:把哈希树放到cache分区,或者用更小的块大小(比如2KB代替4KB)
- AB分区空间不够:差分升级比全量升级省空间,但差分算法对分区布局敏感
- OTA升级后安全启动失败:检查boot image中的dm-verity根哈希是否更新
- misc分区被误写:用
bootctl工具操作,别用dd - 回退后数据丢失:AB升级只保护系统分区,用户数据(data分区)不保护。升级前记得备份
我曾经在一个量产项目上,因为忘了更新AVB的哈希树,导致3000台设备全部无法OTA。最后只能一台一台用USB刷机。那滋味,你懂的。
所以,安全启动和OTA不是「加上去就行」的功能。你得理解它们的原理,才能在出问题时快速定位。希望今天的内容对你有帮助。