ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32H533 OEMiROT安全启动实战:non-secure工程与AXI配置详解

STM32H533 OEMiROT安全启动实战:non-secure工程与AXI配置详解 1. 项目解析与整体设计思路1.1 一个“看似普通”的non-secure工程背后是整个信任链第一次接触“STM32H533 OEMiROT non secure project”这个需求时我差点把它当成一个普通的STM32CubeMX工程来处理——选个芯片、配个时钟、点两盏灯、编译下载完事。但实际动手之后才发现这个non-secure工程根本不是一个孤立的应用层代码工程它是整个OEMiROT安全启动方案里最容易被误解、也最容易踩坑的一环。OEMiROT全称是OEM immutable Root Of Trust翻译成大白话就是“由你自己掌控的不可变信任根”。ST在H5系列芯片里内置了一个出厂固件ST Boot负责把芯片初始化到可用状态。但ST Boot毕竟在出厂时就已经烧死在ROM里ST不能替每一家客户决定“你的产品该信任谁的签名、该跑谁的固件”。所以ST提供了两条路一条是STiROT信任根和验证逻辑都在ST的参考实现里客户用ST提供的密钥体系另一条就是OEMiROT客户自己生成根密钥、自己管理整个验证链芯片上电后先执行OEMiROT代码OEMiROT验证完自身之后再去验证下一级固件也就是你的non-secure应用工程。non-secure在这里并不是“不安全”的意思而是指Cortex-M33处理器中TrustZone架构下的Normal World普通世界。启用TrustZone之后M33核心被划分为Secure World安全世界和Normal World普通世界。OEMiROT运行在Secure World里而你的业务代码、RTOS、协议栈、业务逻辑统统跑在Normal World也就是non-secure工程。两者通过ARM的SAUSecurity Attribution Unit和ST的GTZCGlobal TrustZone Controller做物理隔离不是靠软件约定而是靠总线级别的硬性拦截。这个项目适合谁看如果你正在用STM32H5系列做对固件安全有要求的设备比如IoT网关、工业控制器、金融终端、充电桩、医疗设备或者你只是被老板一句“这个产品要防止别人抄固件、防止非法升级”逼到了墙角那么这篇博文就是写给你的。你不需要是安全专家只要你手里有STM32H533的板子、会一点CubeMX和基本C语言就能跟着把整条链路跑通。1.2 方案选型为什么选OEMiROT而不是STiROT很多人第一次看到ROT相关文档时都会懵STiROT和OEMiROT到底什么区别我该选哪个这里我给出一个非常实际的判断标准看你想不想在“信任根”这层跟ST绑定。STiROT模式下根密钥由ST提供或按ST的参考流程生成芯片出厂后首先运行ST的代码ST代码验证你的应用固件签名。开发效率确实高安全等级也够但缺点是整个安全架构的“命门”不在你手里工程上遇到问题需要依赖ST的更新。OEMiROT模式下你的OEMiROT代码占据Flash的起始区域芯片复位后从你的OEMiROT开始执行它负责验证自身上下文、初始化安全世界、校验下一级固件的签名与完整性最后才跳转到non-secure应用。密钥在你手里固件由你签签名的校验算法实现也在你的工程代码里。这意味着即便某个版本的OEMiROT被攻破责任边界和修复节奏也都是你自己掌控的。用大白话讲STiROT相当于你找了一家安保公司由他们的保安站在你公司门口核验访客OEMiROT则是你雇了几个自己人自己定核验规则你自己发门禁卡。后者前期工作量更大但长期来看更灵活、可控性更强。STM32H533这颗芯片本身定位就是“中高端安全物联网MCU”2MB Flash、620KB SRAM、250MHz主频的Cortex-M33核心在资源和性能上都给OEMiROT留下了充足的发挥空间所以选OEMiROT并不是杀鸡用牛刀而是这颗芯片的主战场。1.3 一条信任链上的三段角色OEMiROT Boot、NSC工程、Non-secure App把整个项目拆开看一条完整的OEMiROT安全启动方案通常包含三个独立工程ST在STM32CubeH5固件包里也一直是这么划分的。第一个是OEMiROT_Boot工程它生成的是整个系统最底层信任锚固件烧录在Flash最靠前的区域比如0x08000000偏移处随芯片上电最先被执行。它负责禁用调试接口的敏感访问、配置GTZC/SAU安全区域、校验下一级固件的签名和散列值。有些方案里还会在这个工程里做固件解密因为为了防抄板产品出厂时Flash里存的是密文固件OEMiROT运行时现场解密后跳转执行。第二个是NSCNon-Secure Callable工程它像一座桥提供一组“安全可调用函数”让non-secure世界能够安全地调用secure世界里的服务。比如说你的业务代码想读取一个只有Secure世界能访问的密钥、想往安全存储区写数据都必须通过NSC工程里预埋的可调用API。如果业务不需要这些能力这个工程可以精简甚至不建但要注意OEMiROT的secure世界里通常跑着一个轻量级的Secure Manager或TF-M你需要知道它的接口规范否则后面联调会出问题。第三个就是本文的主线non-secure工程。它就是你的业务应用跑在Normal World被OEMiROT校验签名之后引导执行。你平时写的RTOS任务、通信协议、算法逻辑都在这个工程里。它不能直接访问Secure World的资源但可以正常使用大部分外设和内存。很多人做这个项目时把大量精力花在OEMiROT_Boot的移植上反而把non-secure工程当成一个普通工程来写结果上电后发现进了HardFault或者外设访问直接卡死一查根因几乎都是non-secure侧的配置没有跟Secure侧对齐。所以请记住这个三角关系OEMiROT_Boot是“看门大爷”验证完身份后开门放行NSC是“办事窗口”non-secure要办什么都得走它non-secure工程才是真正干活的员工。本文后面提到的Axi Non-Secure Enablement正是在“开门放行”和“员工干活”之间那一层最关键的物理通路配置。2. 核心细节解析从启动链路到AXI Non-Secure Enablement2.1 上电之后发生了什么OEMiROT的完整启动链路很多人在做这个项目时最大的困惑是我明明把固件烧进去了怎么一上电就是黑屏/红灯/没反应要回答这个问题得先把启动链路完整走一遍。STM32H533复位后首先是ST出厂烧录在ROM里的代码运行。它读取选项字节判断当前处于什么配置模式。如果TZEN选项位为1并且配置为OEMiROT模式那么ROM代码将执行权交给OEMiROT代码区。从这一刻起链路上的每一步都由你自己写的代码和密钥体系控制。OEMiROT启动的第一步是校验自身的完整性。它会读取自己所在的Flash区域内容用内置的根密钥做签名验证确保OEMiROT代码没有被篡改过。如果校验失败进入错误处理流程。这步的重要性在于如果OEMiROT自己都能被改那整个信任链就是空谈。第二步是配置安全世界。OEMiROT需要初始化Secure World环境包括配置SAU区域划分、GTZC外设安全策略、中断控制器中Secure中断的归属以及栈指针和向量表设置。如果这一步没做好后续non-secure应用一运行立刻会触发SecureFault。我在调试时最常看到的现象就是程序死在HardFault_Handler里单步也追不出来最后定位到就是Secure侧给non-secure分配的RAM地址有重合两边打架。第三步是加载并校验non-secure应用固件。OEMiROT根据预定义的区域信息找到non-secure应用的固件头读取头部里记录的固件版本、大小、签名值、可选加密标志字段然后执行签名校验。校验通过后如果不是加密固件直接跳转到non-secure应用的复位向量如果是加密固件则先解密到RAM或指定区域再跳转。这里有一个很多人忽略的点跳转前的最后一步并不是简单地“跳到0x08020000”而是需要临时关闭中断、重置栈指针、设置向量表偏移寄存器VTOR确保CPU以干净的状态进入non-secure世界。如果这些细节没处理干净应用启动后会随机crash而且每次崩的位置还不一样非常难排查。2.2 Axi Non-Secure Enablement到底在配置什么最近“axi non secure enablement”这个说法在工程师圈子里讨论热度很高其实它不是ST官方文档里的专有名词更像是社区对这些年在实际调试中总结出来的一个形象说法。它的核心含义是让non-secure CPU能够通过AXI总线的过滤检查真正访问到它被允许访问的片上存储和外设。STM32H533作为基于Cortex-M33内核的芯片内部采用了AXI-S总线架构。有一个很关键的点即便你在SAU里把某段地址标成了Non-secure但如果总线层面上的过滤器没有放行non-secure核心取指或读写数据时依然会卡死。这类问题在传统的L5系列上表现得不明显因为L5系列的总线拓扑相对简单而在H5系列上由于引入了GTZCGlobal TrustZone Controller内存保护不只是SAU那一段还有MPCBB基于块的MPC、MPCWM基于水印的MPC等好几层。用一句形象的话解释SAU是楼层门口的保安GTZC/MPC是楼道里的闸机。你过了楼层保安但楼道闸机没开照样进不去。在OEMiROT启动流程里OEMiROT代码在跳转之前会调用GTZC的初始化函数把non-secure应用需要用到的Flash区域、SRAM区域、以及特定外设比如UART、GPIO、DMA逐个配置为“允许Non-secure访问”。这些配置包括设置MPCBB的正确块数、设置MPCWM的水印边界地址、配置外设的SECCFG位等。所以当你排查问题的时候不要看到SAU里标记了NS就万事大吉。经典场景是non-secure工程里初始化一个串口用到了DMA结果启动后DMA传输完成中断进不来或者数据总线错误查了SAU是对的最终定位到是DMA控制器在GTZC里的安全属性没有设置为Non-secureDMA无法代表non-secure核心执行总线传输。记住一个原则任何总线主控参与的访问都要考虑它在GTZC里的安全属性。DMA是总线主控它访问的内存区域也必须对它的安全状态可见否则它会直接总线错误或者被过滤器拦下。2.3 链接脚本与内存布局non-secure工程最容易翻车的点在STM32H533 OEMiROT项目里non-secure工程的链接脚本和普通裸机工程的差异非常大如果你直接拿默认模板改大概率会翻车。普通CubeMX生成的H533工程Flash从0x08000000开始RAM从0x20000000开始。但在OEMiROT环境下Flash起始区域被OEMiROT占用了non-secure应用通常被放置在0x08020000这样的偏移地址处这个偏移量取决于OEMiROT占用的大小、是否预留升级标志区、是否有额外的非安全Bootloader等并不是固定值。在你用CubeMX生成OEMiROT工程时软件会在Secure侧的链接脚本里定义好这些地址non-secure工程需要保持完全一致。RAM区域也一样。H533的SRAM总共有620KB但其中一部分会被Secure世界占用比如用作Secure Manager的堆栈、安全存储区域等。non-secure工程能用的RAM起始地址通常在0x20000000的基础上偏后一些具体跟你实际使用的RAM块以及Secure世界的配置有关。这一点无法一概而论必须看你的OEMiROT工程生成的“内存分区图”。我在实际项目中用过一种很实用的检查方法在non-secure工程的SystemInit()函数开头定义一个大的全局数组并填充0xA5然后在Secure世界的初始化代码里检查这些区域的访问是否正常。更保险的做法是在两边的链接脚本里反向核对符号地址。CubeMX生成的工程里Secure侧链接脚本会导出一些符号比如__NS_APP_START____NS_RAM_START____NS_RAM_END__non-secure工程可以直接引用这些符号避免硬编码地址导致两边不一致。这个习惯我从一个ST的FAE那里学来后一直沿用省了不少排障时间。3. 实操过程与核心环节实现3.1 用CubeMX快速生成带TrustZone的工程骨架现在来走一遍实操流程。我假设你手上有一块STM32H533系列的板子比如NUCLEO-H533RE软件环境是STM32CubeIDE或任意支持M33的交叉编译工具链并已安装STM32CubeH5固件包。打开CubeMX新建工程在MCU选型里搜索并选择STM32H533RETx。注意在Project Manager页签里有一个“TrustZone”选项一定要勾选“Enabled”。这一步决定后续生成出来的工程会同时包含Secure和NonSecure两个子工程。如果你不打开这个开关后面做的一切都不成立。在System Core - TrustZone页面里注意默认配置Enable Security也就是TZEN选项位会被设置同时你可以选择预定义的工程类型这里选“OEMiROT”。CubeMX会自动帮你把后续的启动模式固定为OEMiROT。这个配置会反映到生成的选项字节列表里烧录时会写进芯片。配置完时钟和基础外设后建议直接在引脚视图里把需要用的引脚配置好比如一个LED GPIO、一个UART。然后点击生成代码。生成完成后你会看到工作区里出现两个并列的工程app_secure和app_non_secure。其中secure工程里面含有OEMiROT相关代码non-secure工程则是我们后续主要改动的对象。可能有人会问为什么不是三个工程NSC工程在哪在部分CubeMX版本里生成时还会附带一个NSC工程或者通过一个单独的选项生成。如果你的版本里没有也不用慌后面两种做法任选其一一种是在CubeMX里额外新建一个“Non-Secure Callable”类型的工程另一种是使用secure工程中提供的现成的stm32h5xx_nsc.c接口文件它本身可以作为NSC工程的等价物。实际开发中我发现很多简单应用根本用不到NSC直接绕过它跑起来也是可以的。3.2 密钥生成、固件签名与烧录三件套OEMiROT和普通固件开发最大的区别是编译出来的二进制不能直接烧录必须经过签名、可能还要加密然后配上对应的头部信息OEMiROT才会“认账”。密钥体系方面你需要生成一对或两对密钥。ST提供的工具在固件包Utilities/PC_Software下面各版本的名称略有差异但基本都包含密钥生成和固件签名两个工具。以常见的为例先用工具生成根密钥再在配置脚本中指定OEMiROT_Boot使用的根密钥、OEMiROT_App使用的根密钥。生成过程中会让你输入口令这个口令用于加密私钥文件非常重要。私钥文件一旦丢失或泄露整个产品的安全体系就崩了——丢失意味着你以后再也无法发布合法固件泄露意味着任何人都可以签出带正确签名的固件产品形同虚设。签名过程大致是将编译好的non-secure应用二进制可能还有NSC工程的二进制如果存在的话输入签名工具工具会计算固件哈希用私钥生成签名然后把“固件头签名固件内容”打包成一个新的二进制文件扩展名通常是.bin或.hex这个产物才允许被烧录。如果你的产品还考虑防抄板需要在固件里删除调试符号、禁用调试接口这个可以配合选项字节RDP的等级来实施。烧录环节推荐使用STM32CubeProgrammer可以图形界面操作也可以用命令行脚本。一个典型流程是先用-ob Disconnect...之类的命令把芯片复位到系统Bootloader模式然后设置TZEN选项字节烧录OEMiROT_Boot的二进制到0x08000000烧录签名后的non-secure应用到0x08020000。烧录顺序很重要先烧OEMiROT_Boot、再烧应用避免应用先存在但无人校验的尴尬状态。用命令行脚本的话大致长这样STM32_Programmer_CLI -c portSWD modeHOTPLUG STM32_Programmer_CLI -c portSWD modeHOTPLUG -ob TZEN1 STM32_Programmer_CLI -c portSWD modeHOTPLUG -w OEMiROT_Boot.bin 0x08000000 STM32_Programmer_CLI -c portSWD modeHOTPLUG -w Application_Signed.bin 0x08020000这里的地址要根据你的CubeMX配置调整不要直接照抄。我在一次培训里讲过这个流程有学员原封不动跑结果因为地址和引脚配置不对卡了半天。3.3 最小验证让LED在OEMiROT引导下闪起来为了验证整条链路是否通畅我建议第一版non-secure工程不要写复杂功能一个LED闪烁就够了。这样出问题时排查范围最小。在CubeMX生成的non-secure工程里main.c中先确认复位向量是否是SystemInit再检查main函数是否被正确执行。一个简单的LED闪灯程序基本10行代码搞定。编译时注意看链接脚本用的哪个如果是stm32h533xx_flash_ns.ld说明链接地址已经自动指向了non-secure区域如果还是stm32h533xx_flash.ld那就要回头检查生成配置。编译通过后按照上一小节的签名和烧录流程操作。如果一切顺利上电后LED开始闪烁。此时你还可以做一个反向验证找一份没有签名的原始bin文件直接烧到应用区然后复位。正常情况下系统会卡住LED不会闪。这个过程能直观展示OEMiROT的签名校验在起作用也方便你后续放心地把调试口关掉。这里有一个调试技巧在开发阶段不要把RDP选项字节设得过高否则后续想用调试器看变量、打端点会非常痛苦。我的习惯是开发阶段保持RDP Level 0或者Level 1量产前再统一设置为Level 2如果产品要求的话。所有段子的调试经验都是这么得来的——先在“能调试的版本”里把逻辑调通再在“锁死的版本”里做最终全链路验证。3.4 如何确认Axi Non-Secure访问真的“放行”了很多人在系统能跑起来之后就不再关注Axi Non-Secure Enablement这个层面直到某次改动里加了一个新外设整个系统立刻崩掉才意识到问题远没有结束。所以我额外分享一个主动验证的思路。在non-secure工程中你可以写一个自检函数遍历并访问所有计划使用的SRAM区域和外设寄存器。比如读取目标外设的ID寄存器、读取SRAM地址并写入回读检查结果一致。如果中途发生HardFault或SecureFault就把出错地址记录下来在Fault处理函数里读BFAR/MMFAR寄存器然后对照地址去查GTZC配置看哪个区域漏配了。这项工作看起来繁琐但比产品发布后用户现场崩溃要划算得多。另一种验证方式是使用CubeMX生成的GTZC图形化配置界面。在Secure工程里打开GTZC配置你可以直观看到每个MPCBB块对应哪些外设或内存区域其安全属性是Secure还是Non-secure。把non-secure工程用到的外设块全部置为Non-secure重新编译Secure工程再烧录。这个过程一定要和non-secure工程同步进行两边的“共识区域”必须一致否则总有一方会踩坑。注意当你在CubeMX里改动GTZC配置后重新生成的secure工程代码里会包含对应的初始化函数比如MX_GTZC_Config()。你需要确认它被OEMiROT启动流程调用而不是只生成了没执行。这个函数是否被调用是排查“为什么新配置没生效”的首选检查点。4. 常见问题与排查技巧实录4.1 现象-原因-解法速查表我把做STM32H533 OEMiROT项目以来遇到过的典型问题整理成一张表方便大家对照排查。现象最常见的根因快速解法上电后程序卡死LED不闪签名校验未通过OEMiROT不跳转重新用私钥签名确认烧录地址正确程序偶尔跑飞、无规律HardFaultNon-secure和Secure的RAM区域重叠核对两边的链接脚本确认NS RAM范围唯一加了一个外设后初始化时卡死新外设的GTZC安全属性还是Secure在Secure工程GTZC配置中把该外设设为Non-secureDMA传输不执行或死等DMA控制器或目标内存的SAU/GTZC配置不一致检查DMA控制器安全属性和内存区域MPC配置程序能跑但串口输出乱码时钟配置被OEMiROT重新初始化覆盖确认OEMiROT里的时钟树和NS应用一致或NS应用重设时钟烧录时报写保护错误RDP或WRP保护了Flash区域先解除RDP/设置WRP保护范围重新擦除再烧调试器连接不上芯片RDP Level过高调试口被锁检查RDP等级开发期保持Level 0/1这张表里的每条我都踩过或见过同事踩过。最典型的是“RAM区域重叠”问题H533的SRAM又不是只有一块有些是可被Secure和Non-secure分别访问的看似没有重叠但一旦用到了同一块物理SRAM的不同地址GTZC是按块划分属性的两个块如果都映射到了同一个物理后端依然会有异常。这个问题在参考手册里写得很隐晦遇到时建议优先检查。4.2 一次“VTOR没设对”的经典排查过程我想重点分享一次经历。有一次帮一个客户调试现象是non-secure应用自己单独烧到空片子上能跑注意空片子上没有OEMiROT但一旦烧入OEMiROT再加应用应用一启动就卡在一个随机位置反复复位也没规律。我当时怀疑三个方向RAM冲突、GTZC配置漏配、跳转前CPU状态不干净。先排查RAM冲突用__NS_RAM_START__这些符号对比了两边的地址没问题。接着检查GTZC配置UART、GPIO都用到了都是Non-secure也没问题。最后仔细读了一遍OEMiROT的跳转代码发现它虽然设置了向量表偏移VTOR0x08020000但在跳转前没有先等所有总线事务完成也没有禁用正在进行的DMA传输。结果就是OEMiROT跳转瞬间DMA还在往RAM里写数据而CPU已经切换到了non-secure状态DMA的安全属性在那一刻还没完全切换总线过滤器立刻拦了下来整个系统就死锁。解决办法是在跳转前调用一个__DSB()和__ISB()指令同步屏障并且确保所有DMA通道处于禁用状态如果DMA需要继续运行也要在跳转前把所有DMA相关寄存器的安全属性配置好。这个修复只有两行代码但查了两天。分享这个例子的意义是做安全启动调试不要只盯着安全相关的寄存器跳转前后的“瞬时状态”反而更容易出问题。ARM的同步屏障指令在普通MCU开发里你可能几十年都用不上一次但在Secure/Non-secure切换的场景下它是保证稳定性的关键。4.3 几个被反复问到的细节问题问“我的non-secure工程编译不过报Flash区域越界。”答检查链接脚本里FLASH的起始地址CubeMX生成OEMiROT工程时通常会预留一个区域给非安全boot和应用但如果你自己改过Secure工程里的分区规划两边的地址要对齐。另外确认有没有把编译优化等级开得太大这有时会导致某些段被优化到预期之外的位置。问“能不能跳过签名直接调试non-secure应用”答可以但前提是OEMiROT不启用强制校验或者在CubeMX里配置成“Direct Boot”模式——直接跳过签名校验跳转。这适合纯业务逻辑调试调试完再切回OEMiROT模式做整体验证。我不建议一直开着Direct Boot否则安全启动形同虚设。问“用不用每次都重新生成密钥”答绝对不要。密钥是一次性生成、长期使用的。如果你在开发过程中频繁重新生成私钥烧录的OEMiROT和后面签名的应用会变成“两个世界”的产物最典型的症状是OEMiROT能跑起来但就是不跳转。密钥生成之后建议立刻备份到离线环境产品量产前做一次密钥使用清单审计。最后分享一点个人体会。我在这个项目里学到的最重要一课是安全启动不是“加一个保险箱”而是“给整栋楼重新设计了门禁系统”。做OEMiROT工程时你的关注点不能只停留在“我的业务代码能不能跑”而是要把Secure世界当成一个独立运行的系统来对待。每次新增外设、每次调整内存分布、每次改时钟都要同时检查两侧的配置是否依然对齐。这确实比普通MCU开发累但当你看到产品固件被提取出来却无法被分析和复制时你会觉得这份累是值得的。希望这篇博文能帮你少走一些我走过的弯路。
返回列表