ARM TrustZone-M 安全分区实践指南从 SAU/IDAU 配置到安全函数的调用约定详解一、物联网设备固件的安全裂缝固件提取与密钥暴露的现实威胁基于 Cortex-M33/M23 的 IoT 设备固件通常存储在外部 SPI Flash 中。攻击者只需一只逻辑分析仪夹在 QSPI 总线上即可在启动阶段抓取完整的固件二进制。如果密钥、证书或私有算法直接放在固件镜像中这一泄露通道是致命的。ARMv8-M 架构引入的 TrustZone-M 技术在单片机层面提供了硬件隔离方案将 CPU 执行态、内存和外设划分为安全世界Secure World和非安全世界Non-secure World。安全世界中的代码和数据对非安全世界不可见——即使攻击者获得了非安全世界的完整控制权也无法读取安全世界中的密钥存储。本文以 Cortex-M33搭载 TrustZone 扩展为目标讲解从硬件配置到安全函数调用的完整链路。二、TrustZone-M 的硬件隔离机制SAU、IDAU 与内存属性划分TrustZone-M 的内存隔离由两级地址过滤实现处理器内置的 SAUSecurity Attribution Unit和 SoC 厂商实现的 IDAUImplementation Defined Attribution Unit。两者输出经过 OR 运算后产生最终的安全属性。flowchart TD CPU[Cortex-M33 处理器] --|地址总线| IDAU[IDAUbr/厂商定义] CPU --|地址总线| SAU[SAUbr/可编程配置] IDAU --|安全属性| OR_GATE[OR 逻辑合并] SAU --|安全属性| OR_GATE OR_GATE --|最终安全属性| MEM[内存/外设访问] subgraph Secure[安全世界 ] S_CODE[Secure Codebr/Flash 0x0C00_0000] S_DATA[Secure Databr/SRAM 0x3000_0000] end subgraph NonSecure[非安全世界] NS_CODE[Non-secure Codebr/Flash 0x0800_0000] NS_DATA[Non-secure Databr/SRAM 0x2000_0000] end SAU -.-|默认: 全 Non-secure| OR_GATE IDAU -.-|厂商固定属性| OR_GATESAU 的关键寄存器寄存器功能典型值SAU_CTRL使能 SAUbit0ENABLE0x1SAU_TYPE只读SAU 区域数量0x88 个区域SAU_RNR当前选中的区域编号0-7SAU_RBAR区域基地址 标志位addr[31:5] SECURE[4] NSC[0]SAU_RLAR区域限地址 使能位limit[31:5] ENABLE[0]NSCNon-Secure Callable区域是一个特殊概念它位于安全 Flash 中但允许非安全代码通过 SGSecure Gateway指令进入。NSC 区域只能包含 SG 指令后跟的跳转表Veneer实际的安全函数体在 NSC 区域之外。三、SAU 配置代码与安全函数调用实现以下配置将 128KB 的 Flash 区域0x0C00_0000-0x0C01_FFFF设置为安全 Flash并将其中的 4KB0x0C00_0000-0x0C00_0FFF设置为 NSC 区域用于非安全世界的函数调用入口。/* tz_sau_config.c —— TrustZone-M SAU 配置安全世界初始化代码 */ #include stdint.h /* ——— SAU 寄存器基地址Cortex-M33 ——— */ #define SAU_BASE (0xE000EDD0UL) #define SAU_CTRL (*(volatile uint32_t *)(SAU_BASE 0x00)) #define SAU_TYPE (*(volatile uint32_t *)(SAU_BASE 0x04)) #define SAU_RNR (*(volatile uint32_t *)(SAU_BASE 0x08)) #define SAU_RBAR (*(volatile uint32_t *)(SAU_BASE 0x0C)) #define SAU_RLAR (*(volatile uint32_t *)(SAU_BASE 0x10)) /* ——— 安全属性标志定义 ——— */ #define SAU_RBAR_SECURE (1UL 4) /* bit4: 区域标记为 Secure */ #define SAU_RBAR_NSC (1UL 0) /* bit0: 区域标记为 Non-Secure Callable */ #define SAU_RLAR_ENABLE (1UL 0) /* bit0: 区域使能 */ /* * 配置 SAU 区域映射 * 区域0: 安全 Flash 代码区 (0x0C00_1000 ~ 0x0C01_FFFF) * 区域1: NSC 入口区 (0x0C00_0000 ~ 0x0C00_0FFF) * 区域2: 安全 SRAM 数据区 (0x3000_0000 ~ 0x3000_FFFF) */ void sau_configure(void) { /* ——— 区域0安全 Flash代码 数据不含 NSC 入口 ——— */ SAU_RNR 0; // 选中区域 0 SAU_RBAR (0x0C001000UL 0xFFFFFFE0UL) | SAU_RBAR_SECURE; SAU_RLAR (0x0C01FFFFUL 0xFFFFFFE0UL) | SAU_RLAR_ENABLE; /* ——— 区域1NSC 入口非安全世界可调用的跳板 ——— */ SAU_RNR 1; // 选中区域 1 /* * RBAR 中同时设置 SECURE 和 NSC * — SECURE1, NSC1 表示安全内存中的 NSC 子区域 * — 非安全代码只能通过 SG 指令跳转到此区域 */ SAU_RBAR (0x0C000000UL 0xFFFFFFE0UL) | SAU_RBAR_SECURE | SAU_RBAR_NSC; SAU_RLAR (0x0C000FFFUL 0xFFFFFFE0UL) | SAU_RLAR_ENABLE; /* ——— 区域2安全 SRAM密钥、安全上下文存储 ——— */ SAU_RNR 2; SAU_RBAR (0x30000000UL 0xFFFFFFE0UL) | SAU_RBAR_SECURE; SAU_RLAR (0x3000FFFFUL 0xFFFFFFE0UL) | SAU_RLAR_ENABLE; /* ——— 全局使能 SAU ——— */ SAU_CTRL | 1; // ENABLE bit /* ——— 确认配置 ——— */ if (!(SAU_CTRL 1)) { /* SAU 使能失败——可能硬件不支持 TrustZone */ __BKPT(0); // 断点调试用 } }安全函数的 Veneer 入口汇编/* secure_veneer.s —— NSC 区域的跳板表 * 每个安全函数对应一个 veneer 条目 * SG Secure Gateway 指令切换为安全态 * B func_name 跳转到实际函数体 * 必须位于 NSC 区域内0x0C00_0000 ~ 0x0C00_0FFF */ .section .gnu.sgstubs, ax /* 链接脚本确保此段在 NSC 区域 */ .syntax unified .thumb_func .align 2 .global secure_aes_encrypt_veneer .type secure_aes_encrypt_veneer, %function secure_aes_encrypt_veneer: SG /* 从 Non-secure 切换到 Secure 态 */ B.W secure_aes_encrypt /* 跳转到实际的安全函数 */ .size secure_aes_encrypt_veneer, .-secure_aes_encrypt_veneer .global secure_get_random_veneer .type secure_get_random_veneer, %function secure_get_random_veneer: SG B.W secure_get_random .size secure_get_random_veneer, .-secure_get_random_veneer .end非安全世界调用示例/* ns_app.c —— 非安全世界应用代码 */ // 声明 veneer 函数原型链接器将解析为 NSC 区域的 veneer 地址 extern void secure_aes_encrypt_veneer( uint8_t *plaintext, uint32_t len, uint8_t *key, // 此 key 将被 FZ 寄存器清零归零 uint8_t *output); void encrypt_privileged_data(uint8_t *data, uint32_t len) { uint8_t derived_key[16]; // 会话派生密钥用完即弃 generate_session_key(derived_key, sizeof(derived_key)); // 调用安全世界 AES —— 非安全代码看不到密钥处理过程 secure_aes_encrypt_veneer(data, len, derived_key, data); // 安全函数调用完成后清理本地 key 拷贝 secure_clear(derived_key, sizeof(derived_key)); }四、架构边界上下文切换开销与调试可见性的取舍TrustZone-M 调用模型的核心开销是非安全/安全世界切换时的寄存器上下文保护。SG 指令触发硬件自动处理以下操作操作耗时Cortex-M33 96MHz 实测SG 指令 状态切换~24 周期250ns调用返回BXNS~22 周期229ns合计函数调用往返~46 周期479ns这意味着如果一个安全函数仅处理 100ns 的计算如简单异或调用开销是计算时间的 4.8 倍。安全函数的设计应保持粗粒度单次调用至少完成 1us 以上的有效工作摊薄上下文切换比例。调试可见性是另一个权衡。Non-secure 调试器无法查看 Secure 世界的寄存器和内存。需要 JTAG/SWD 调试器支持 TrustZone 认证级别的调试通道如 J-Link 的 Secure Debug 模式且需配置对应的 DBG_AUTH 寄存器。这增加了开发阶段的复杂度。五、总结TrustZone-M 通过 SAU/IDAU 的联合地址过滤和 NSC 区域的跳板调用机制在 Cortex-M 级别实现了硬件强制隔离。核心实践点是SAU 配置需精确覆盖安全代码、NSC 跳板表和安全数据三种区域安全函数应通过 SG 指令的 veneer 入口暴露调用粒度需足够粗以摊薄约 500ns 的上下文切换开销。在安全启动链中SAU 配置应在复位后、跳转到非安全代码之前由安全固件完成且配置完成后可以通过锁定寄存器禁止后续修改形成完整的信任根。