4、安全启动流程(一):上电复位、BootROM执行、一级引导加载程序(SPL)验证

各位同学,今天我们正式开始啃安全启动流程这块硬骨头。说实话,安全启动是整个基站固件防护的第一道防线,也是我最常被问到的一个环节。很多工程师觉得这玩意儿就是按个开关、跑个代码,没什么技术含量——嗯,我以前也这么想,直到我在一个4G基站项目里,因为BootROM的一个小漏洞,差点让整个产线返工。

今天这一讲,我们聚焦在安全启动的前三个关键阶段:上电复位、BootROM执行、以及一级引导加载程序(SPL)的验证。这三个阶段环环相扣,任何一个环节出问题,后面的安全机制全是白搭。

4.1 上电复位:一切从这里开始

基站设备一通电,芯片内部发生了什么?

说白了,就是硬件把系统拉到一个已知的、可信的初始状态。这个过程叫上电复位(POR, Power-On Reset)。我个人习惯把POR看作是「安全启动的守门员」——它必须确保CPU从正确的地址开始取指,而不是被恶意篡改过的地址。

上电复位阶段有几个关键动作:

  • 复位向量锁定:CPU强制从ROM中预设的复位向量地址开始执行。这个地址是硬件写死的,改不了。
  • 硬件初始化:时钟、PLL、内存控制器等基本外设被初始化到安全状态。
  • 安全状态建立:关键安全寄存器被清零或置为默认值,防止任何非授权访问。

重要提示:上电复位阶段,所有非必要的调试接口(如JTAG、SWD)必须被硬件强制禁用。我在项目中遇到过一种攻击手法,攻击者在上电瞬间通过JTAG注入指令,绕过后续所有安全校验。所以,复位逻辑里一定要有「调试接口锁定」这一步。

4.2 BootROM执行:不可篡改的第一行代码

复位完成后,CPU开始执行BootROM中的代码。BootROM是固化在芯片内部ROM里的,出厂后无法修改。你想想看,这玩意儿就是芯片的「基因」,攻击者想改也改不了。

BootROM的主要任务是什么?

  1. 硬件自检(POST):检查CPU、内存、关键外设是否正常。我记得有一次,一个客户反馈基站频繁重启,查了半天发现是DDR内存的某个Bank在高温下不稳定,BootROM的POST检测直接报了错,系统反复复位。嗯,这其实是好事,至少没让问题代码跑起来。
  2. 安全策略初始化:加载芯片熔丝(eFuse)或OTP中的安全配置,比如是否启用安全启动、信任根的公钥哈希值等。
  3. 寻找并验证下一级引导程序:BootROM会去指定的存储介质(比如SPI Flash、NAND、eMMC)中寻找一级引导加载程序(SPL),并对其进行完整性验证。

个人经验:BootROM的代码虽然不能改,但它的行为是可以通过熔丝配置的。比如,你可以通过熔丝设置「强制安全启动」模式,这样BootROM在找不到有效签名时直接死循环,而不是尝试从其他介质启动。这个配置在量产前一定要确认好,否则就是给攻击者留后门。

4.3 一级引导加载程序(SPL)验证:信任链的第一环

BootROM找到SPL之后,并不会直接执行它。它会先做一件事——验证SPL的签名。这一步是整个安全启动信任链的起点,也是我最想强调的部分。

SPL验证的典型流程如下:

步骤 动作 说明
1 读取SPL头部 获取SPL的元数据,包括版本号、镜像大小、签名算法标识等
2 计算镜像哈希 对SPL的代码段进行哈希计算(通常使用SHA-256或SHA-512)
3 验证数字签名 使用存储在BootROM或熔丝中的公钥,验证SPL附带的签名是否匹配
4 检查版本号 防止回滚攻击——旧版本的SPL可能有已知漏洞
5 跳转执行 所有检查通过后,BootROM将控制权交给SPL

避坑指南:我曾经在一个项目里,SPL验证全部通过,但系统还是被攻破了。后来发现,攻击者没有篡改SPL本身,而是修改了SPL头部里的「镜像大小」字段,让BootROM只验证了SPL的前半部分,后半部分被注入了恶意代码。所以,一定要对SPL头部进行完整性保护,最好把头部也纳入签名范围。

4.4 代码示例:SPL签名验证的伪代码

下面是一个简化的SPL验证伪代码,展示了BootROM中的核心逻辑。实际代码会更复杂,但核心思想是一样的。

// BootROM SPL验证伪代码
int verify_spl(uint8_t *spl_addr, uint32_t spl_size) {
    // 1. 解析SPL头部
    spl_header_t *hdr = (spl_header_t *)spl_addr;
    if (hdr->magic != SPL_MAGIC) {
        return ERR_INVALID_MAGIC;  // 魔数不对,直接拒绝
    }

    // 2. 检查版本号,防止回滚
    if (hdr->version < MIN_ALLOWED_VERSION) {
        return ERR_VERSION_ROLLBACK;  // 版本过低,拒绝执行
    }

    // 3. 计算镜像哈希(注意:头部也要参与哈希计算)
    uint8_t hash[SHA256_DIGEST_SIZE];
    sha256_calc(spl_addr, spl_size, hash);

    // 4. 从熔丝中读取公钥
    uint8_t pub_key[PUB_KEY_SIZE];
    read_efuse_pubkey(pub_key);

    // 5. 验证签名
    if (rsa_verify(pub_key, hash, hdr->signature) != 0) {
        return ERR_SIGNATURE_MISMATCH;  // 签名不匹配,拒绝
    }

    // 6. 全部通过,返回成功
    return SUCCESS;
}

注意:上面的代码里,我把头部也纳入了哈希计算范围。这是我在踩过坑之后养成的习惯。另外,公钥的存储位置也很关键——一定要放在一次性可编程(OTP)存储器或熔丝里,不能放在可重写的Flash中,否则攻击者可以替换公钥。

4.5 总结与思考

好了,今天的内容就到这里。我们从上电复位讲到了BootROM执行,再到SPL验证,其实就一句话:安全启动的每一步都在回答「你是谁?你从哪里来?你要到哪里去?」——只不过用的是哈希和签名来回答。

最后留个思考题:如果BootROM本身存在漏洞,怎么办?嗯,这个问题没有标准答案,但你可以想想看,为什么很多芯片厂商会提供「BootROM补丁」机制?下一讲我们会聊到二级引导加载程序(U-Boot)的验证,到时候再展开。

公众号:蓝海资料掘金营,微信deep3321