3. 信任链的建立:从RoT到BootROM、BootROM到Bootloader、Bootloader到OS的逐级验证
好,咱们进入正题。上一章聊了硬件可信根(RoT)是什么,说白了就是那个「永远可信的起点」。但光有一个起点没用,你得让信任像接力棒一样,一级一级传下去,直到整个系统跑起来。这个过程,就是信任链的建立。
我个人习惯把信任链比作「洋葱」。你一层层剥开,每一层都要验证下一层的完整性。剥到最里面,就是那颗谁都动不了的RoT。今天我们就从里到外,把这三层验证讲透。
3.1 第一跳:从RoT到BootROM
这是整个链条的起点。RoT通常是一段不可修改的硬件逻辑,或者是一次性熔断的fuse。它要做的第一件事,就是验证BootROM的完整性。
你可能会问:「BootROM不也是只读的吗?为什么还要验?」
嗯,这里要注意。BootROM虽然是掩膜ROM,理论上出厂后改不了。但我在项目中遇到过一种情况——芯片有测试模式,或者JTAG没完全锁死,攻击者可以通过某些调试接口篡改BootROM的执行流。所以,RoT必须做一次「自检」,确认BootROM的哈希值跟fuse里烧录的值一致。
关键点:RoT对BootROM的验证,通常是在芯片上电瞬间完成的。这个过程不依赖任何外部代码,是纯硬件逻辑。一旦哈希比对失败,芯片直接进入安全锁定状态,不再执行任何后续代码。
具体怎么做?我见过两种主流方案:
- 方案A:硬件哈希比对——RoT内部集成SHA-256引擎,上电后自动读取BootROM内容,计算哈希,跟fuse里的参考值比对。速度快,但fuse区域要提前烧好。
- 方案B:数字签名验证——RoT用内置的公钥验证BootROM的数字签名。灵活性高,可以更换BootROM版本,但需要额外的非对称加密引擎。
我个人更倾向方案A。为什么?因为简单。你想想看,BootROM本身就不该频繁更新,用哈希比对,硬件开销小,逻辑也容易做形式化验证。我在一个车规级项目里就用的方案A,跑了三年没出过问题。
3.2 第二跳:从BootROM到Bootloader
BootROM验证通过后,控制权交给它。BootROM的任务是加载并验证下一级——也就是Bootloader。
这里有个常见的坑:BootROM通常很小,可能只有几十KB。它没有能力加载完整的操作系统,所以它只负责把Bootloader从存储介质(比如SPI Flash、eMMC)里读出来,验签,然后跳转。
避坑指南:我曾经在一个IoT项目里踩过这个坑——BootROM验签通过了,但跳转到Bootloader后系统直接挂死。查了两天才发现,BootROM把Bootloader加载到了错误的地址。所以,一定要在BootROM代码里硬编码Bootloader的加载地址和最大长度,不要用可配置参数。攻击者改个配置项就能让你跳转到恶意代码。
BootROM验证Bootloader的流程,我总结为三步:
- 读取——从固定地址读取Bootloader镜像和它的签名/哈希值。
- 验证——用RoT阶段建立的信任锚(公钥或哈希参考值)验证Bootloader的完整性。
- 跳转——验证通过后,清空敏感寄存器,跳转到Bootloader的入口。
这里要特别强调一下「清空敏感寄存器」。为什么?因为BootROM里可能存了密钥、哈希值、临时缓冲区。如果不清理,Bootloader启动后可以直接把这些数据读出来。我在一个安全审计项目里就见过这种漏洞——攻击者通过Bootloader的调试接口,把BootROM残留的密钥dump了出来。
3.3 第三跳:从Bootloader到OS
Bootloader拿到控制权后,它的任务更重了。它要加载操作系统内核,可能还要加载设备树、ramdisk、以及一些安全相关的驱动。
这一跳的验证逻辑跟第二跳类似,但有一个关键区别:Bootloader通常运行在更复杂的环境中,它可能要初始化DDR、文件系统、网络协议栈。攻击面比BootROM大得多。
我建议的做法是:把Bootloader分成两个阶段。
| 阶段 | 职责 | 安全要求 |
|---|---|---|
| SPL (Secondary Program Loader) | 初始化DDR、时钟,加载完整的Bootloader | 必须由BootROM验签,代码量控制在4KB以内 |
| 完整的Bootloader (如U-Boot) | 加载OS内核、设备树、ramdisk | 必须验签OS内核,支持回滚保护 |
为什么要分两个阶段?因为DDR初始化代码非常敏感。如果DDR时序配错了,攻击者可以通过Rowhammer之类的攻击篡改内存数据。把DDR初始化放在一个极简的SPL里,更容易做安全审计。
Bootloader验证OS内核时,我遇到过最头疼的问题是「回滚攻击」。攻击者拿一个旧版本但有已知漏洞的内核,替换掉新内核。Bootloader验签时发现签名是对的(因为旧内核也是官方签的),就放行了。
我的经验:解决回滚攻击,要在Bootloader里维护一个「最小可接受版本号」。这个版本号可以存在RoT的fuse区域,或者存在一个受保护的OTP区域。每次启动时,Bootloader比较内核的版本号是否大于等于这个最小值。小于就拒绝启动。
3.4 信任链的完整视图
说了这么多,咱们用一张图把整个链条串起来。下面这张SVG图,展示的就是从RoT到OS的完整信任传递过程。
这张图里,每个箭头都代表一次「验证+跳转」。箭头上的文字虽然简单,但背后是完整的签名验证、哈希比对、版本检查逻辑。任何一个箭头断了,整个系统就不能启动。
3.5 我踩过的坑:信任链断裂的典型案例
说了这么多理论,分享一个真实案例吧。
几年前我做一款安防摄像头,用的是一颗ARM Cortex-A7芯片。信任链设计得挺完善——RoT验BootROM,BootROM验SPL,SPL验U-Boot,U-Boot验内核。所有环节都加了签名验证。
结果呢?产品上市三个月,被安全团队发现了一个漏洞。
问题出在U-Boot的环境变量上。U-Boot支持从SPI Flash读取环境变量,里面可以配置启动参数。攻击者通过物理接触,把SPI Flash拆下来,改了环境变量里的「bootargs」,加了一条「init=/bin/sh」。然后系统启动时,直接跳到了攻击者的shell。
你可能会说:「U-Boot不是验签内核了吗?」
对,U-Boot确实验了内核的签名。但环境变量不在验签范围内。攻击者没改内核,只改了启动参数。内核验签通过,但启动参数被篡改了,系统还是被攻破了。
教训:信任链不仅要验证代码镜像,还要验证所有影响执行流的配置数据。从那以后,我所有项目里,Bootloader的环境变量、设备树、ramdisk,全部要跟内核一起打包签名。一个都不能少。
3.6 小结
信任链的建立,说白了就是「一级验一级,环环相扣」。RoT是根,BootROM是干,Bootloader是枝,OS是叶。任何一个环节断了,整棵树就死了。
实际项目中,我建议你记住三条原则:
- 最小化——每一级代码量越少越好,越小越容易审计。
- 单向性——信任只能从低级别流向高级别,不能反向。
- 完整性——验证范围要覆盖所有可执行代码和配置数据。