版本号管理机制:版本号存储、单调递增计数器、版本号验证逻辑
版本号管理,说白了就是给每一版固件发一张「身份证」。这张身份证不仅要防伪造,还得防篡改。我见过不少团队,版本号随便找个全局变量存着,结果被攻击者一锅端。嗯,今天咱们就把这块掰开揉碎了讲。
版本号存储:放哪儿才安全?
版本号不能只放在Flash的某个固定地址。为什么?因为攻击者可以直接改Flash内容。我建议至少分三个层级来存:
- 第一层:片上OTP(One-Time Programmable)区域。出厂时烧录一个基础版本号,之后只能递增,不能回退。这是最后的防线。
- 第二层:安全存储区(如TrustZone的Secure World或HSM内部)。存放当前活跃版本号,由安全固件管理,普通代码无权访问。
- 第三层:应用层缓存。启动时从安全区读取,放在RAM里加速访问。但注意,这个缓存只做读加速,不做写回。
核心原则:版本号的「权威副本」必须放在攻击者无法直接写入的硬件安全边界内。OTP和HSM是首选,其次是安全区内存。
我在一个IoT网关项目里遇到过,客户把版本号存在外部SPI Flash里,结果攻击者直接拆芯片、用编程器改了版本号,然后刷回了旧固件。那次之后,我所有项目的版本号都强制走OTP或HSM。
单调递增计数器:怎么保证只增不减?
单调递增计数器是防回滚的「机械锁」。它必须满足三个条件:
- 硬件级原子操作:写入操作不可中断,不会出现写一半断电的情况。
- 单向递增:只能加,不能减。即使重新烧录整个固件,计数器也只增不减。
- 持久化:断电不丢失,重启后值还在。
具体实现上,我推荐两种方式:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| OTP位域 | 物理不可逆,安全性最高 | 容量有限,通常只有几十到几百位 | 高安全等级设备(金融、车规) |
| HSM内部NVRAM | 容量大(KB级),可擦写但受控 | 依赖HSM完整性,成本较高 | 中高端MCU/SoC |
一个小技巧:如果OTP位不够用,可以用「版本号+计数器」组合。比如版本号占8位,计数器占24位,每次升级版本号不变、计数器加1。这样既节省OTP,又能记录升级次数。
你想想看,如果攻击者想回滚,他必须同时把计数器也减回去。但OTP一旦烧了就是永久熔断,HSM的NVRAM也只允许安全固件写入——这条路基本堵死了。
版本号验证逻辑:启动链上的每一环都要查
版本号验证不是只在系统启动时做一次就完事了。它应该嵌入到整个安全启动链中,从BootROM到应用层,每一级都要检查。
我画了一张流程图,帮你理清这个逻辑:
这个流程里,最关键的一步是「版本号 ≥ 当前计数器值」。注意,是「≥」不是「=」。为什么?因为允许跨版本升级。比如从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,你的防回滚就形同虚设。
- 不要用软件模拟单调计数器。软件计数器可以被调试器跳过或篡改,必须依赖硬件。
- 不要在验证通过前更新计数器。先验证,再更新,顺序不能错。
嗯,版本号管理这块,说白了就是「存得安全、增得可靠、验得彻底」。把这三件事做好了,防回滚攻击的根基就稳了。下一层咱们会聊签名验证,但那是后话了。