3、信任链模型:从BootROM到操作系统启动的完整信任链传递机制

安全启动的核心,说白了就是一条信任链。我经常跟团队里的年轻人讲,别把安全启动想得太玄乎,它本质上就是一个「接力赛」——从芯片上电的第一条指令开始,每一棒都要验证下一棒的身份,确认没问题了才交棒。只要有一棒出了问题,整个系统就停在那里,不往下走了。

这个机制,我们叫它「信任链传递」。今天我就带你把这个链条从头到尾捋一遍。

3.1 信任链的起点:BootROM

芯片一上电,CPU 首先执行的是 BootROM 里的代码。BootROM 是固化在芯片内部的一块只读存储器,出厂后就改不了了。你想想看,这是整个信任链的根,如果它本身就不安全,那后面再怎么折腾都没用。

BootROM 做的事情其实不多,但每件都很关键:

  • 初始化硬件:时钟、内存控制器、堆栈指针,让芯片能跑起来
  • 确定启动介质:是从 eMMC 启动?还是 SPI Flash?还是 UART 下载模式?
  • 验证下一级镜像:读取 Bootloader(通常是 SPL 或 BL1)的第一段,验签

我在项目中遇到过一个问题:某款芯片的 BootROM 里有个调试后门,可以通过拉高某个 GPIO 引脚直接进入下载模式,绕过所有验证。嗯,这就是典型的「信任根被污染」——BootROM 本身没有做物理防护。后来我们在芯片设计阶段就强制要求:BootROM 必须锁定调试接口,量产后不可恢复。

信任根的核心要求:不可篡改、不可绕过、不可降级。BootROM 必须满足这三条,否则整个信任链就是空中楼阁。

3.2 信任链的传递过程

BootROM 验完第一级 Bootloader 后,就把执行权交出去了。但注意,它不是撒手不管——它会在内存里留下一个「信任状态标记」,告诉下一级:「我是可信的,你也要可信。」

完整的信任链一般长这样:

BootROM → BL1 (SPL) → BL2 (U-Boot SPL) → BL3 (U-Boot proper) → Kernel → 文件系统

每一级都做同样的事:

  1. 加载:从存储介质读取下一级镜像到内存
  2. 验签:用公钥验证镜像的数字签名
  3. 跳转:验签通过后,清理敏感数据,跳转到下一级入口

我习惯把这个过程叫做「链式验证」。为什么不用「树状验证」?因为它是线性的,一环扣一环,中间不能断。我曾经在一个项目里看到有人试图跳过 BL2 直接加载 BL3,结果系统直接挂死——因为 BL2 里做了硬件安全引擎的初始化,跳过去之后加密加速器根本没法用。

小技巧:实际项目中,建议在每一级跳转前把当前级别的代码段和栈空间清零。这样做可以防止攻击者通过内存残留数据获取敏感信息,比如私钥或者验签结果。

3.3 信任链的典型架构

不同的芯片厂商,信任链的层级划分不太一样。我整理了一个对比表,方便你理解:

层级 ARM TrustZone 方案 RISC-V 方案 典型大小 存储位置
BL0 BootROM BootROM 32-64 KB 片上 ROM
BL1 Secure Monitor M-mode 启动代码 64-128 KB 片上 SRAM
BL2 Trusted Firmware S-mode 引导 256-512 KB DDR 或 SRAM
BL3 U-Boot / EDK2 U-Boot / opensbi 1-4 MB DDR
BL4 Linux Kernel Linux Kernel 10-50 MB DDR

你看,越往上层,镜像越大,存储位置也从片上 ROM 移到了外部 DDR。为什么?因为片上 ROM 和 SRAM 太贵了,不可能放一个几十兆的内核进去。但这也带来了风险——外部 DDR 容易被物理攻击,比如冷启动攻击或者内存探针。

3.4 信任链的 SVG 流程图

下面这张图是我自己画的信任链传递流程,你可以对照着看:

信任链传递流程 BootROM (BL0) 验签 BL1:使用 OTP 中固化的根公钥 BL1 (SPL) 验签 BL2:使用 BL1 内置的公钥 BL2 (Trusted Firmware) 验签 BL3:使用 BL2 传递的信任状态 信任状态标记 存储在安全寄存器 不可被普通代码读取

这张图里我特意加了一个「信任状态标记」的说明框。为什么?因为很多开发者会忽略这个细节——信任链不只是验签,还要传递一个「我信任你」的状态。这个状态通常存在安全寄存器里,普通代码根本读不到。攻击者就算伪造了镜像,也伪造不了这个状态。

3.5 信任链的常见攻击面

信任链不是万能的。我在做安全审计时,见过太多被绕过的案例。这里列几个最常见的:

  • 回滚攻击:攻击者把系统降级到一个有漏洞的旧版本,然后利用那个漏洞提权。防御手段是版本号防回滚——每次升级时版本号只能递增,BootROM 会检查版本号是否大于等于当前版本。
  • 绕过 BootROM:通过调试接口或 glitch 攻击,让 CPU 跳过 BootROM 直接执行外部代码。防御手段是物理防护——比如电压/频率检测、主动屏蔽层。
  • 中间人攻击:在存储介质和 SoC 之间截获数据,替换成恶意镜像。防御手段是签名验证——只要公钥安全,中间人改不了签名。

注意:我曾经在一个 IoT 设备上看到,厂商把根公钥放在了外部 Flash 里,而且没有做任何完整性保护。攻击者只要替换掉公钥,就能用自己的私钥签一个恶意镜像。这相当于把保险柜的钥匙挂在柜子外面——信任链形同虚设。

3.6 信任链的落地建议

说了这么多,最后给几条我在实际项目中总结的经验:

  1. 根公钥必须放在 OTP 或 eFuse 里,量产时一次性烧录,之后不可更改。不要放在 Flash 里,不要放在文件系统里。
  2. 每一级都要做独立验签,不要依赖上一级的验签结果来跳过本级验证。我见过有人为了省时间,在 BL1 里验完 BL2 后,直接告诉 BL2「你不用验 BL3 了,我已经验过了」——这是典型的偷懒,也是典型的安全漏洞。
  3. 验签失败必须死循环或复位,不能有任何 fallback 路径。有些厂商为了用户体验,验签失败后尝试从网络下载镜像——这等于给攻击者开了一扇门。
  4. 信任链的每一级都要最小化,代码越少,攻击面越小。BootROM 就做 BootROM 的事,别塞一堆驱动进去。

嗯,信任链这个东西,说起来简单,做起来全是细节。但只要你把根守住了,把每一棒的交接流程做扎实了,整个系统的安全基础就稳了。