4、启动流程详解:BootROM → SPL → U-Boot → Kernel → Rootfs,逐级验证的信任链传递
安全启动说白了,就是一条信任链。每一级都验下一级的签名,验不过就停。我见过太多团队只盯着U-Boot做签名,结果BootROM到SPL那一步根本没锁——等于白干。
今天我把这条链从头到尾拆开讲。你跟着走一遍,就知道坑在哪了。
核心原则:信任链必须从硬件根信任开始,逐级验证,不可跳过任何一级。任何一级的验证缺失,都会导致整个安全启动体系失效。
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的主要任务:
- 初始化DDR内存
- 从存储设备加载U-Boot到DDR
- 验证U-Boot的签名
- 跳转到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替换成半年前的旧版本,而那个版本有已知漏洞——那你的安全启动就白做了。
实现防回滚的方法:
- 在BootROM或SPL中记录当前版本号(写入eFuse或OTP)
- 每次启动时比较Rootfs版本号和记录的版本号
- 如果Rootfs版本号低于记录值,拒绝启动
我的经验:版本号最好用单调递增的计数器,不要用日期或字符串。日期可以改,计数器一旦增加就减不回去。我在一个车机项目里用的就是eFuse计数器,每次OTA升级成功后递增一次。这样即使攻击者拿到了旧镜像,也刷不回去。
4.6 信任链验证清单
最后,我整理了一份检查清单。每次做安全启动部署,我都会对着它过一遍:
| 层级 | 验证内容 | 常见遗漏 |
|---|---|---|
| BootROM | SPL签名 | eFuse未烧录公钥 |
| SPL | U-Boot签名 | SPL本身未签名 |
| U-Boot | Kernel + DTB签名 | DTB未验签名 |
| Kernel | Rootfs完整性 | 未启用dm-verity |
| Rootfs | 版本防回滚 | 未实现版本计数器 |
嗯,这条链走下来,每一步都不能省。你可能会觉得麻烦,但想想看——攻击者只要找到链上最弱的一环,就能绕过整个安全体系。所以,每一级都要锁死。