版本号管理机制:版本号存储、单调递增计数器、版本号验证逻辑

版本号管理,说白了就是给每一版固件发一张「身份证」。这张身份证不仅要防伪造,还得防篡改。我见过不少团队,版本号随便找个全局变量存着,结果被攻击者一锅端。嗯,今天咱们就把这块掰开揉碎了讲。

版本号存储:放哪儿才安全?

版本号不能只放在Flash的某个固定地址。为什么?因为攻击者可以直接改Flash内容。我建议至少分三个层级来存:

  • 第一层:片上OTP(One-Time Programmable)区域。出厂时烧录一个基础版本号,之后只能递增,不能回退。这是最后的防线。
  • 第二层:安全存储区(如TrustZone的Secure World或HSM内部)。存放当前活跃版本号,由安全固件管理,普通代码无权访问。
  • 第三层:应用层缓存。启动时从安全区读取,放在RAM里加速访问。但注意,这个缓存只做读加速,不做写回。

核心原则:版本号的「权威副本」必须放在攻击者无法直接写入的硬件安全边界内。OTP和HSM是首选,其次是安全区内存。

我在一个IoT网关项目里遇到过,客户把版本号存在外部SPI Flash里,结果攻击者直接拆芯片、用编程器改了版本号,然后刷回了旧固件。那次之后,我所有项目的版本号都强制走OTP或HSM。

单调递增计数器:怎么保证只增不减?

单调递增计数器是防回滚的「机械锁」。它必须满足三个条件:

  1. 硬件级原子操作:写入操作不可中断,不会出现写一半断电的情况。
  2. 单向递增:只能加,不能减。即使重新烧录整个固件,计数器也只增不减。
  3. 持久化:断电不丢失,重启后值还在。

具体实现上,我推荐两种方式:

方案 优点 缺点 适用场景
OTP位域 物理不可逆,安全性最高 容量有限,通常只有几十到几百位 高安全等级设备(金融、车规)
HSM内部NVRAM 容量大(KB级),可擦写但受控 依赖HSM完整性,成本较高 中高端MCU/SoC

一个小技巧:如果OTP位不够用,可以用「版本号+计数器」组合。比如版本号占8位,计数器占24位,每次升级版本号不变、计数器加1。这样既节省OTP,又能记录升级次数。

你想想看,如果攻击者想回滚,他必须同时把计数器也减回去。但OTP一旦烧了就是永久熔断,HSM的NVRAM也只允许安全固件写入——这条路基本堵死了。

版本号验证逻辑:启动链上的每一环都要查

版本号验证不是只在系统启动时做一次就完事了。它应该嵌入到整个安全启动链中,从BootROM到应用层,每一级都要检查。

我画了一张流程图,帮你理清这个逻辑:

版本号验证逻辑流程图 版本号验证逻辑流程图 BootROM 读取OTP基础版本号 验证BL1签名 + 版本号 ≥ OTP值 通过? 启动失败 加载BL2并验证版本 更新单调计数器 = 新版本号 注意 每一级BootLoader都要 验证下一级固件的版本号 是否 ≥ 当前计数器值。

这个流程里,最关键的一步是「版本号 ≥ 当前计数器值」。注意,是「≥」不是「=」。为什么?因为允许跨版本升级。比如从v1.0直接升到v3.0,中间跳过了v2.0,只要v3.0的版本号大于计数器值,就应该允许。

我曾经踩过一个坑:在某个车机项目里,我用了「版本号 == 计数器值」的严格相等判断。结果客户从v1.0升级到v2.0后,又想降级到v1.5(因为v2.0有bug)。但v1.5的版本号小于v2.0的计数器,被拦住了。后来我改成「≥」,同时加了一个「强制升级标志位」来处理紧急降级场景。

验证逻辑的代码骨架

下面是一个简化的版本号验证伪代码,你可以参考这个结构:

// 安全启动中的版本号验证
uint32_t current_counter = hsm_read_counter();  // 从HSM读取当前计数器
uint32_t new_firmware_version = parse_firmware_header(signed_image);

if (new_firmware_version < current_counter) {
    // 版本号小于计数器,拒绝启动
    hsm_log_event(EVENT_ROLLBACK_DETECTED);
    boot_fail();
    return;
}

// 验证签名
if (!verify_signature(signed_image, public_key)) {
    boot_fail();
    return;
}

// 签名通过,更新计数器
hsm_increment_counter(new_firmware_version);

// 加载并跳转到新固件
load_and_jump(signed_image);

这段代码有几个要点:

  • 先验版本,再验签名。如果版本号都过不了,没必要浪费算力去验签名。
  • 计数器更新必须在签名验证之后。防止攻击者用无效固件刷高计数器,导致真固件无法升级。
  • hsm_increment_counter() 必须是原子操作。如果写一半断电,重启后计数器可能处于不一致状态。

版本号管理的「三不要」

  • 不要把版本号放在可写Flash里。攻击者直接改Flash,你的防回滚就形同虚设。
  • 不要用软件模拟单调计数器。软件计数器可以被调试器跳过或篡改,必须依赖硬件。
  • 不要在验证通过前更新计数器。先验证,再更新,顺序不能错。

嗯,版本号管理这块,说白了就是「存得安全、增得可靠、验得彻底」。把这三件事做好了,防回滚攻击的根基就稳了。下一层咱们会聊签名验证,但那是后话了。