4、内存隔离机制:安全内存与非安全内存的地址映射、内存保护单元(MPU)与TrustZone的协同
在ARM Cortex-M33的TrustZone架构中,内存隔离是保障系统安全的基础防线。与Cortex-A系列依赖MMU进行页级虚拟内存管理不同,Cortex-M33作为微控制器内核,采用基于物理地址的MPU(Memory Protection Unit)配合TrustZone的SAU/IDAU机制,实现安全世界与非安全世界的物理内存隔离。理解这一机制,是进行安全固件设计、安全启动链路构建以及混合关键性任务部署的前提。
4.1 核心原理:TrustZone地址映射与安全属性标记
Cortex-M33的TrustZone技术将传统的单一物理地址空间划分为安全地址空间与非安全地址空间。这种划分并非通过地址位数的扩展实现,而是通过在每个总线事务上附加一个安全属性信号(Secure Attribute)来标记访问的安全性质。具体而言,系统总线上的每一次读写操作都会携带一个额外的信号——HPROT[1](AHB总线)或AxPROT[1](AXI总线),该信号为高电平时表示安全访问,为低电平时表示非安全访问。
地址映射的核心逻辑如下:
- 物理地址不变:安全内存与非安全内存共享相同的物理地址编码空间。例如,片上Flash的地址范围是0x1000_0000~0x1007_FFFF,那么安全世界和非安全世界看到的都是这个地址范围,区别在于访问时携带的安全属性不同。
- 安全属性决定访问权限:当CPU处于安全状态时,发出的访问请求带有安全属性,可以访问所有地址区域(包括安全区域和非安全区域);当CPU处于非安全状态时,发出的访问请求带有非安全属性,只能访问被标记为非安全的区域。
- IDAU与SAU双重过滤:IDAU(Implementation Defined Attribution Unit)是芯片厂商实现的硬连线逻辑,用于定义芯片出厂时的默认安全属性;SAU(Security Attribution Unit)是ARM定义的软件可配置单元,允许固件在运行时动态调整安全属性。两者共同决定每个地址区域的安全性质。
这种设计的关键优势在于:安全世界与非安全世界共享同一物理内存,但通过安全属性实现逻辑隔离。非安全代码无法通过地址猜测或指针篡改来访问安全内存,因为即使它构造出安全地址,总线上的安全属性信号仍然为低电平,硬件会直接拒绝该访问并触发异常。
4.2 判断方法与操作流程:配置SAU与MPU实现隔离
在实际工程中,配置内存隔离需要遵循一套明确的流程。以下以典型的Cortex-M33安全固件开发为例,说明如何配置SAU和MPU。
4.2.1 SAU配置流程
- 确定安全区域划分:根据系统需求,明确哪些Flash、SRAM和外设需要被保护。例如,Bootloader、密钥存储区、安全服务代码放在安全Flash;应用代码、普通数据放在非安全Flash。
- 设置SAU区域寄存器:SAU提供最多8个区域(SAU_REGION),每个区域由SAU_RBAR(基地址寄存器)、SAU_RLAR(限制地址寄存器)和SAU_RNR(区域编号寄存器)定义。区域属性包括:非安全(Non-secure)、非安全可调用(Non-secure Callable,用于函数入口)和安全。
- 启用SAU:设置SAU_CTRL寄存器,使能SAU功能。默认情况下,如果SAU未配置,所有地址都被视为安全。
// 示例:配置SAU将0x10040000~0x1007FFFF设为非安全区域
SAU->RNR = 0; // 选择区域0
SAU->RBAR = (0x10040000 & SAU_RBAR_BADDR_Msk);
SAU->RLAR = (0x1007FFFF & SAU_RLAR_LADDR_Msk) | SAU_RLAR_ENABLE_Msk;
// 注意:区域必须按256字节对齐
4.2.2 MPU配置流程
MPU在TrustZone架构中分为安全MPU(S-MPU)和非安全MPU(NS-MPU),分别由安全软件和非安全软件配置。MPU的作用是定义内存区域的访问权限(读/写/执行)和缓存属性,与TrustZone的安全属性检查是正交的——即一个区域既要通过安全属性检查,也要通过MPU权限检查才能被访问。
- 使能MPU:设置MPU_CTRL寄存器,使能MPU,并决定是否在特权模式下启用。
- 配置区域:使用MPU_RNR、MPU_RBAR、MPU_RLAR寄存器配置区域基地址、大小和访问权限。
- 设置属性:在MPU_MAIR寄存器中定义内存属性索引(如Cache策略、共享属性等)。
4.3 案例拆解:智能门锁的安全内存隔离设计
以一个基于Cortex-M33的智能门锁产品为例,展示内存隔离机制的实际应用。
系统背景与约束:
- 芯片:某厂商Cortex-M33 MCU,内置2MB Flash(0x1000_0000~0x101F_FFFF)和512KB SRAM(0x2000_0000~0x2007_FFFF)。
- 安全需求:指纹模板数据、加密密钥、安全启动代码必须隔离保护;用户应用代码可升级但不可读取密钥。
- 性能约束:安全服务调用频率高(指纹比对每次约50ms),不能因隔离机制引入明显延迟。
初始现象与目标:早期原型使用单地址空间设计,所有代码运行在特权模式,导致一次缓冲区溢出攻击即可读取全部Flash内容。目标是通过TrustZone实现:非安全应用无法读取安全Flash中的密钥和指纹模板,即使被攻破也无法获取敏感数据。
关键输入数据与配置:
| 地址范围 | 大小 | SAU属性 | MPU权限(安全) | MPU权限(非安全) | 用途 |
|---|---|---|---|---|---|
| 0x1000_0000~0x1000_3FFF | 16KB | 安全 | 读/执行 | 禁止 | 安全Bootloader |
| 0x1000_4000~0x1000_7FFF | 16KB | 安全 | 读/写 | 禁止 | 密钥存储区 |
| 0x1000_8000~0x1000_FFFF | 32KB | 非安全可调用 | 读/执行 | 读/执行 | 安全服务入口(SG指令) |
| 0x1001_0000~0x101F_FFFF | ~1.9MB | 非安全 | 读/写/执行 | 读/写/执行 | 用户应用代码 |
| 0x2000_0000~0x2000_3FFF | 16KB | 安全 | 读/写 | 禁止 | 安全数据堆栈 |
| 0x2000_4000~0x2007_FFFF | ~496KB | 非安全 | 读/写 | 读/写 | 应用数据堆栈 |
分析判断过程:
首先,将Bootloader和密钥存储区配置为安全区域,确保系统启动时安全固件先运行,完成硬件初始化和密钥加载。其次,设置一个非安全可调用区域(NSC),该区域存放安全函数的入口跳板代码,非安全代码只能通过SG指令进入该区域,再跳转到真正的安全函数。最后,将用户应用区域全部标记为非安全,即使应用被攻破,也无法访问安全Flash和SRAM。
实际操作步骤:
- 编写安全固件,包含密钥管理、指纹比对等安全服务函数。
- 在NSC区域放置跳板函数,使用
__attribute__((cmse_nonsecure_entry))声明安全入口。 - 配置SAU区域,将上述地址范围按表设置。
- 配置安全MPU,对安全Flash区域设置只读+执行权限,防止安全代码自身被篡改。
- 配置非安全MPU,禁止非安全代码访问安全区域(虽然SAU已阻止,但MPU提供纵深防御)。
- 编译链接时,使用
--cmse选项生成安全和非安全两个镜像。
结果对比:实施TrustZone隔离后,通过渗透测试尝试从非安全应用读取密钥存储区地址,均触发SecureFault异常,无法获取任何数据。安全服务调用耗时增加约3~5μs(主要是SG指令和状态切换开销),对指纹比对50ms的耗时影响可忽略。
风险与代价:安全世界与非安全世界之间的数据传递需要显式拷贝,增加了编程复杂度;NSC区域需要精心设计,避免跳板代码被恶意利用;安全固件和非安全固件需要分别维护和升级,增加了发布流程的复杂度。
复盘经验:内存隔离设计必须在系统架构阶段就确定,而不是在编码后期补充。SAU区域数量有限(通常8个),需要合理规划;NSC区域是安全通信的咽喉,应尽量精简代码量,减少攻击面。
4.4 常见误区与总结复盘
在实际项目中,工程师在配置内存隔离时经常遇到以下误区:
误区二:忽略NSC区域的对齐要求。NSC区域必须满足32字节对齐,且大小必须是32字节的整数倍。如果配置错误,SG指令会触发UsageFault。
误区三:在安全函数中直接访问非安全指针。安全代码访问非安全内存时,必须使用
cmse_check_address_range()等函数进行指针校验,防止TOCTOU(Time-of-Check to Time-of-Use)攻击。
总结复盘要点:
- 纵深防御:TrustZone的SAU/IDAU是第一层隔离,MPU是第二层隔离,两者协同提供更细粒度的控制。不要依赖单一机制。
- 最小权限原则:安全区域应尽量小,只包含必要的代码和数据;非安全区域应有明确的访问边界。
- 安全通信机制:安全世界与非安全世界之间的数据交换应通过NSC区域的API进行,避免直接共享内存(除非经过严格的安全设计)。
- 调试与测试:在开发阶段,可以使用调试器的TrustZone感知功能查看当前CPU状态和内存属性;测试阶段应包含负向测试用例(如非安全代码尝试访问安全地址)。