4、启动流程详解:BootROM → SPL → U-Boot → Kernel → Rootfs,逐级验证的信任链传递

安全启动说白了,就是一条信任链。每一级都验下一级的签名,验不过就停。我见过太多团队只盯着U-Boot做签名,结果BootROM到SPL那一步根本没锁——等于白干。

今天我把这条链从头到尾拆开讲。你跟着走一遍,就知道坑在哪了。

核心原则:信任链必须从硬件根信任开始,逐级验证,不可跳过任何一级。任何一级的验证缺失,都会导致整个安全启动体系失效。

安全启动信任链传递流程 BootROM 硬件根信任 SPL 一级Bootloader U-Boot 二级Bootloader Kernel 操作系统内核 Rootfs 验签名 验签名 验签名 验签名 每一级验证通过后,才将控制权交给下一级。任何一级验证失败,系统立即停止启动。

4.1 BootROM:硬件根信任

BootROM是芯片出厂时固化在ROM里的代码。它不可修改,这就是信任链的起点。我个人习惯把BootROM叫做「铁打的营盘」——它永远不会变,所以必须绝对可靠。

BootROM做的事很简单:

  • 初始化最基本的硬件(时钟、内存控制器、存储接口)
  • 从固定存储位置(比如eMMC的boot分区、SPI Flash的起始地址)读取SPL
  • 用芯片内置的公钥验证SPL的签名

我曾经踩过的坑:某次项目,BootROM配置为从eMMC boot分区加载SPL。结果硬件团队把boot分区的数据线焊反了,BootROM读出来的全是乱码。签名验证当然失败,系统直接变砖。后来我们在产线测试里加了一道「BootROM回读校验」,确保硬件连接没问题再往下走。

BootROM里存的公钥,一般是通过eFuse烧进去的。eFuse是一次性可编程存储,烧了就改不了。这也是安全启动的物理基础——你想想看,如果公钥能改,那签名验证还有什么意义?

4.2 SPL:一级Bootloader

SPL(Secondary Program Loader)是BootROM加载的第一个可执行程序。它体积很小,通常只有几十KB。为什么这么小?因为BootROM能访问的内存资源极其有限,很多芯片连DDR都还没初始化。

SPL的主要任务:

  1. 初始化DDR内存
  2. 从存储设备加载U-Boot到DDR
  3. 验证U-Boot的签名
  4. 跳转到U-Boot执行

这里有个关键点:SPL本身必须被BootROM验证过。如果SPL被篡改,那它加载的U-Boot再安全也没用。我在项目中遇到过有人只给U-Boot签名,SPL裸奔——这等于把大门钥匙放在门口脚垫下面。

我的建议:SPL和U-Boot使用不同的签名密钥。这样即使SPL的密钥泄露,攻击者也无法伪造U-Boot。反过来也一样。这叫「密钥分层」,是安全启动的基本功。

4.3 U-Boot:二级Bootloader

U-Boot是功能完整的Bootloader。它负责加载内核和设备树(DTB)。到了这一级,你已经有了完整的内存环境,U-Boot可以做更多事:文件系统支持、网络启动、环境变量管理等等。

U-Boot验证内核的流程:

  • 从存储介质读取内核镜像(通常是zImage或Image)
  • 读取对应的签名文件(.sig)
  • 用内置公钥验证签名
  • 验证通过后,将内核解压到内存指定位置
  • 跳转到内核入口点

这里有个容易忽略的细节:设备树(DTB)也要验签名。我见过不止一个项目,内核验了签名,DTB没验。攻击者可以替换DTB,修改内存布局或外设配置,照样能搞破坏。

// U-Boot中验证内核和DTB的典型配置
// 使能签名验证
CONFIG_SECURE_BOOT=y
CONFIG_FIT_SIGNATURE=y
// 指定公钥位置
CONFIG_FIT_SIGNATURE_STRICT=y
// 验证DTB
CONFIG_FIT_CIPHER=y

4.4 Kernel:操作系统内核

内核被U-Boot验证通过后,获得执行权。内核本身也要做一件事:验证根文件系统(Rootfs)的完整性。这一步通常通过内核模块或initramfs来实现。

内核验证Rootfs的方式:

验证方式 说明 安全性
IMA(Integrity Measurement Architecture) 内核内置的完整性测量框架,可以验证文件哈希 高
dm-verity 设备映射器机制,提供块设备级别的完整性校验 高
initramfs签名验证 在initramfs中手动验证Rootfs的签名 中
只读Rootfs + 硬件保护 Rootfs放在只读分区,由硬件写保护 中

我个人比较推荐dm-verity。它工作在块设备层,对上层应用完全透明。你只要在构建Rootfs时生成一个哈希树,内核在读取每个块时都会校验哈希。一旦发现篡改,内核会返回I/O错误,而不是加载恶意数据。

注意:dm-verity只能检测篡改,不能阻止读取。如果你的Rootfs包含敏感数据,还需要配合加密(dm-crypt)。我在一个IoT项目中就吃过这个亏——只做了完整性校验,没做加密,结果攻击者虽然改不了文件,但能直接读出来。

4.5 Rootfs:根文件系统

Rootfs是信任链的最后一环。它包含所有用户空间程序、库文件、配置文件。如果Rootfs被篡改,攻击者可以植入后门、修改系统服务、窃取数据。

Rootfs的安全启动要求:

  • 完整性校验:确保文件没有被篡改
  • 来源验证:确保文件来自可信的构建系统
  • 防回滚:防止攻击者降级到有漏洞的旧版本

防回滚这一点,很多人会忘。你想想看,即使所有文件都验了签名,如果攻击者把整个Rootfs替换成半年前的旧版本,而那个版本有已知漏洞——那你的安全启动就白做了。

实现防回滚的方法:

  1. 在BootROM或SPL中记录当前版本号(写入eFuse或OTP)
  2. 每次启动时比较Rootfs版本号和记录的版本号
  3. 如果Rootfs版本号低于记录值,拒绝启动

我的经验:版本号最好用单调递增的计数器,不要用日期或字符串。日期可以改,计数器一旦增加就减不回去。我在一个车机项目里用的就是eFuse计数器,每次OTA升级成功后递增一次。这样即使攻击者拿到了旧镜像,也刷不回去。

4.6 信任链验证清单

最后,我整理了一份检查清单。每次做安全启动部署,我都会对着它过一遍:

层级 验证内容 常见遗漏
BootROM SPL签名 eFuse未烧录公钥
SPL U-Boot签名 SPL本身未签名
U-Boot Kernel + DTB签名 DTB未验签名
Kernel Rootfs完整性 未启用dm-verity
Rootfs 版本防回滚 未实现版本计数器

嗯,这条链走下来,每一步都不能省。你可能会觉得麻烦,但想想看——攻击者只要找到链上最弱的一环,就能绕过整个安全体系。所以,每一级都要锁死。