news 2026/9/4 11:02:22

STM32 TrustZone实战:从原理到安全双工程配置

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32 TrustZone实战:从原理到安全双工程配置

TrustZone这个词,做M系列的朋友最近两年应该没少听。它最早是Arm在Cortex-A上推出的硬件隔离方案,用来保护Android、Linux这类复杂系统里的密钥和支付数据。后来Arm把TrustZone下放到Cortex-M,在Armv8-M架构里重新实现了一套,ST把它落到了STM32L5和STM32U5这两个系列上。如果你拿到的任务是把程序拆成安全和非安全两半,或者要给物联网设备做安全启动、防抄板、密钥保护,那TrustZone大概率是你绕不开的东西。

在动手调之前,先要有一个认知:STM32L5和U5上的TrustZone并不是简单加一个“安全开关”,它要求你在项目规划阶段就分出两个世界。ST的做法也很直接:STM32CubeMX里选芯片时打开TrustZone,会直接生成两个工程,一个安全工程,一个非安全工程。很多第一次接触的人会在这个环节懵掉,不知道怎么把这个“双胞胎”工程跑起来。这篇文章就把我从原理到实战的完整路径讲清楚,涉及的工具、配置和踩坑点都是实际验证过的,希望对准备上手安全分区的朋友有参考价值。

1. 项目概述与产品定位

1.1 TrustZone到底在解决什么

先抛开指令集和寄存器,用业务语言说:TrustZone就是在同一颗MCU上划出两个互不信任的执行环境。安全环境可以访问所有资源,非安全环境只能访问被授权的资源。两者的隔离是硬件层面的,不是MPU那种“软”隔离,也不是靠软件约定。

传统MCU做安全,通常靠三板斧:MPU隔离、RDP读保护、外部加密芯片。MPU能做到运行时保护,但一旦非安全代码把自己提升到特权模式,再改MPU配置是可能的,也就是说它防的不是“自己人”。RDP只能整体保护Flash,没法把Flash里的一部分开放给调试器、另一部分保护起来。外部加密芯片成本高,通信链路还可能被监听。TrustZone的隔离发生在总线层面上,处理器的每一次访问都带有安全属性,访问被拒就直接拒绝,和固件是否被攻破没有关系。

在实际产品中,TrustZone最常见的用法是:密钥、证书、安全升级逻辑放在安全侧;通信协议栈、UI、应用逻辑放在非安全侧。即使非安全侧被漏洞利用,攻击者拿到的也只是一个没有密钥的世界。我在做L5的仪表类项目时,就是把计费密钥和AES引擎锁在安全侧,App侧无线升级随便折腾,最坏情况也只是恢复出厂,底线数据始终在安全区内。

1.2 STM32L5与STM32U5:怎么选

STM32L5是目前ST基于Armv8-M架构的入门级TrustZone产品,Cortex-M33内核,主频110MHz,Flash最大512KB,SRAM 256KB。STM32U5则是L5的升级版,主频拉到160MHz,存储容量大幅提升,最高2MB Flash、786KB SRAM,还带了CORDIC/FMAC数学加速器、图形加速、LPBAM低功耗后台自主运行等能力。两者都有TrustZone,都使用ST的GTZC做安全属性管理。

选型时我一般这样判断:如果产品只做安全启动、密钥存储、代码防抄板,对算力和存储要求不高,L5足够,成本低、功耗也低。如果产品需要跑模型推理、图形界面,或者要在休眠时让外设自主采集数据,U5更合适,毕竟大存储和LPBAM是实打实的优势。U5上ST还提供Secure Manager方案,属于“开箱即用”的预配置安全固件,如果不想从零设计安全架构,可以走这条捷径。另外U5的硬件加解密单元更强,AES、HASH、PKA、OTFDEC都有,对安全启动和固件加密是很大的加分项。

在TrustZone的用法上,L5和U5基本一致,下面讲的原理和步骤,换个芯片型号也一样能跟下来。真正差别大的是芯片外设和低功耗策略,TrustZone本身的使用逻辑完全通用。

2. 核心原理:Armv8-M TrustZone到底是怎么工作的

2.1 安全状态与非安全状态

Armv8-M的TrustZone为Cortex-M处理器引入了两种状态:Secure和Non-Secure。处理器每时每刻都处于其中一种状态。安全状态可以访问全部内存和外设;非安全状态只能访问被标记为Non-Secure的资源。切换不是简单的函数调用,而是有专用的指令和时序要求。

有一个关键点:在TrustZone使能后,处理器内部很多寄存器都变成了“两份”。比如MSP有MSP_S和MSP_NS,CONTROL有CONTROL_S和CONTROL_NS。中断向量表也分成安全和非安全两部分。所以在调试时你经常会看到“S”和“NS”的标记,这不只是命名习惯,而是代表着硬件上物理隔离的两套状态。

状态切换的规则是:从非安全状态进入安全状态,只能通过“非安全可调用”区域(NSC)里的SG指令;从安全状态回到非安全状态,使用带NS后缀的跳转指令,比如BXNS。普通BL/BX跳转无法跨状态。这样设计的目的是保证进入安全代码的路径完全可控,非安全代码不能随便跳到安全代码的任意位置。如果你在NS侧直接取一个Secure函数的地址并调用,Cortex-M33会直接触发异常,而不是帮你“宽容”地进入安全状态。

2.2 IDAU、SAU与安全属性的判定

每个地址在访问时该怎么判定安全属性?靠两套硬件单元:IDAU和SAU。

IDAU是芯片厂商实现、不可编程的单元,它把系统内存和总线地址“预先打标签”——哪些地址是安全的,哪些是非安全的,哪些是NSC,在上电那一刻就是固定的。SAU是软件可编程的单元,可以覆盖IDAU的部分判定。规则是:IDAU判定为Non-Secure的区域,SAU无法把它变成Secure;IDAU判定为Secure的区域,SAU可以把它改成Non-Secure或者NSC。也就是说,IDAU定义了“底线”,SAU在这个基础上只能做“放宽”。

在STM32L5/U5上,TZEN选项字节使能后,内部Flash和SRAM默认都属于Secure。所以要跑非安全代码,你必须在启动阶段显式地把一部分存储区标成Non-Secure。这个工作由GTZC下的MPC(内存保护控制器)完成,它把Flash和SRAM切成一个个区块,每个区块都可以单独设置安全属性。外设的安全属性由TZSC配置,中断的安全属性由TZIC配置。这三者合起来,就是ST的GTZC体系。

为什么这么设计?上电阶段默认全Secure,是为了保证安全启动链:复位后CPU先跑安全代码,由安全代码初始化并“释放”非安全区域,之后才把控制权交给非安全代码。这样非安全代码从出生起就被限制在笼子里,这个“先安全后非安全”的时序是TrustZone方案的核心。

2.3 NSC与SG指令:跨世界的桥

非安全代码要调用安全代码里的函数,不能直接BL过去,那会触发异常。必须经过一座“桥”,这座桥就叫NSC区域。NSC区域是一段特殊的内存,它的安全属性被标记为Non-Secure Callable。非安全代码可以调用NSC区域中的地址,但在NSC区域里必须有一条SG指令,SG指令执行后处理器状态才从Non-Secure切换到Secure,然后才跳到真正的安全函数体。

C编译器把这座桥的搭建自动化了。你写安全函数时只要加上__attribute__((cmse_nonsecure_entry)),编译器会帮你生成相应的入口和跳板(veneer),并把它们放在NSC区域。你不需要手工写SG指令。类似地,从安全代码调用非安全代码(比如安全模块想调用非安全侧的状态上报回调),要使用cmse_nonsecure_call属性,编译器会生成正确的BXNS调用序列。

需要注意的是,跨状态调用的参数传递遵循标准AAPCS,通常通过r0到r3传递。但CMSE还做了一些额外的安全检查,比如会在入口处验证函数的调用地址确实在NSC区域、验证参数指针指向非安全内存等。这些检查是为了防止安全侧被非安全侧的恶意参数利用。不过要注意,指针指向是否合法,最终还是要靠在安全函数体内部做手动校验,编译器不会替你把所有业务逻辑都验证完。

2.4 GTZC:ST的TrustZone控制器体系

GTZC的全称是Global TrustZone Controller,它管三件事:外设安全属性(TZSC)、中断安全属性(TZIC)、内部存储器安全分区(MPC)。

  • MPC把Flash和SRAM分成若干区块,每个区块的安全属性可独立设置。注意区块大小是固定的,从几KB到几十KB不等,分区时要考虑对齐。
  • TZSC决定每个外设的寄存器访问属于安全还是非安全。比如可以把UART分给非安全侧做日志,把AES留在安全侧做加解密。
  • TZIC决定每个中断源由哪个世界处理。安全中断事件只能被安全状态下的ISR响应,非安全中断则相反。

在CubeMX里,这三个配置都有图形化界面。但即便你不在CubeMX里配置,直接在S工程的代码里调HAL库函数也能完成。无论用哪种方式,都要理解一件事:GTZC的配置生效时机非常关键,必须在跳到非安全代码之前完成,否则非安全代码连自己所在的Flash都读不了,上电就是HardFault。

3. 实操准备:工具链与CubeMX配置

3.1 工具链选择

TrustZone工程对编译器有要求。用户手册里说得比较明确:Arm Compiler 6从6.7开始完整支持Cortex-M33的CMSE扩展,所以Keil MDK要使用AC6编译器,不要再用AC5。IAR从8.40.x开始支持TrustZone。STM32CubeIDE自带GCC工具链,但注意GCC对CMSE的支持是从arm-none-eabi-gcc 10开始的,老版本没有-mcmse选项,编译会报错。我自己的经验是,刚开始做TrustZone项目时用STM32CubeIDE最省心,生成的S/NS工程模板是配套好的,不用自己去拼链接脚本。

调试器方面,ST-LINK、J-Link都能用,但要注意调试器固件版本不要太老,否则识别不了双工程的debug会话。烧录工具推荐STM32CubeProgrammer,它和TrustZone的选项字节、RDP等级管理配合得最完整。另外,在x86主机上如果想在买板子之前先验证M33的TrustZone行为,可以试试QEMU对Cortex-M33的模拟,但QEMU对GTZC这类芯片特定外设支持不完善,验证平台相关代码还是得用真板子。

3.2 CubeMX里怎么配置TrustZone

我用STM32CubeMX 6.x为例。新建工程选好L5或U5型号后,左侧“Security”分类里能找到TrustZone选项。打开它,CubeMX会提示需要设置选项字节中TZEN为1,同时会让你规划Flash/SRAM的安全分区。和普通工程一个明显的区别是:生成代码时,CubeMX会生成两个独立的工程目录,一个带_S后缀,一个带_NS后缀。

说一下分区规划。默认情况下CubeMX会给出一个比例,比如把Flash前64KB分给Secure,剩余给Non-Secure。你可以按实际需求改。分区的粒度受GTZC的MPC区块大小限制,并不是任意字节都能切,需要对齐到区块边界。SRAM也是同理。还有一个隐含要求:NSC区域必须落在Secure Flash中,建议单独划出一块(比如4KB),并在GTZC中把这块配成NSC属性。

另外,在CubeMX的Project Manager里可以设置“TrustZone Security”相关的选项,比如是否把某个外设默认配置到Secure或Non-Secure。但这些其实在代码里也能改,不必太纠结于GUI里的初始化状态。真正要提前想清楚的是:你的安全边界面在哪里,哪些外设归S,哪些归NS,这决定了后面所有代码的存放位置。

3.3 双工程结构与启动流程

生成后的双工程,S工程和NS工程是分开编译、分开烧录的。两者共享一个HAL库,但S工程带全部启动文件和GTZC配置,NS工程只在生成的链接脚本里做了内存布局。发布产品时通常要做一个打包烧录,S镜像和NS镜像合并后一次烧进去。

启动流程可以这样理解:

  • 复位后,CPU进入Secure状态,从S工程的启动向量
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 9:35:58

paperclipai实战:用Python打造AI文件自动整理与归档工具

之前在业务迭代中接触到一个叫paperclipai / paperclip的项目命名,起初以为只是某个回形针图标的开源库,真正动手后发现:paperclip这个词在软件工程里本身就承担着好几层含义,从文件上传组件到轻量 AI 工具,甚至还能演…

作者头像 李华
网站建设 2026/8/31 19:46:25

单片机事件监测器设计:从硬件消抖到软件状态机的嵌入式实战

1. 项目缘起与核心价值 最近在准备蓝桥杯电子类单片机组的比赛,发现很多同学在应对“事件监测器”这类综合性模块题目时,常常感到无从下手。这类题目往往不会直接告诉你“请用定时器中断实现一个秒表”,而是会用一个更抽象、更贴近实际应用场…

作者头像 李华