2、核心硬件架构:安全世界与非安全世界、Monitor 模式、异常向量表重映射

好,我们正式开始啃 TrustZone 最核心的硬件架构。

说实话,很多做安全的朋友看了几年 ARM 文档,还是搞不清「安全世界」和「非安全世界」到底是怎么切来切去的。我当年第一次接触 TrustZone 时也踩过这个坑——以为它就是个软件层面的权限管理。后来被硬件狠狠教育了一顿,才明白这一切的根基,都在芯片的硬件总线设计里。

2.1 两个世界的物理隔离

TrustZone 把整个 SoC 的运行环境切成了两个「世界」:安全世界(Secure World)和非安全世界(Normal World)。注意,这不是虚拟化,不是进程级别的隔离,而是硬件总线级别的物理隔离。

每个世界都有自己的:

  • 物理地址空间——安全世界的内存,非安全世界的 CPU 根本看不到
  • 系统寄存器——比如 CP15 寄存器,两个世界各有一套
  • 异常向量表——这个我们后面细说
  • 外设访问权限——通过 TrustZone Address Space Controller (TZASC) 控制

核心要点:非安全世界的代码,永远无法直接访问安全世界的物理内存。这不是软件能绕过的——总线上的硬件信号(NS bit)会直接截断非法访问。

我曾在某款 IoT 芯片上见过一个案例:工程师在非安全世界写了个驱动,试图通过 DMA 直接读取安全世界的密钥存储区。结果总线直接返回全 F,连个异常都没触发——硬件静默地拒绝了。这就是物理隔离的威力。

2.2 Monitor 模式:两个世界的桥梁

两个世界完全隔离,那怎么切换?

答案就是 Monitor 模式。它是 ARM 架构里一个特殊的 CPU 模式,优先级比所有异常模式都高。你可以把它理解成「两个世界之间的看门人」。

Monitor 模式的核心职责:

  1. 保存上下文——把当前世界的寄存器状态存起来
  2. 切换 NS bit——修改 SCR.NS 位,告诉硬件「我要去另一个世界了」
  3. 恢复上下文——加载目标世界的寄存器状态
  4. 返回目标世界——执行 ERET 指令跳过去

我的习惯:在 Monitor 代码里,我通常会额外保存 SCTLR 和 ACTLR 寄存器。因为有些芯片在切换世界时,MMU 和缓存配置会不一致,不保存这些寄存器,回来时系统可能直接挂掉。

切换流程可以用下面这张图来理解:

安全世界与非安全世界切换流程 安全世界与非安全世界切换流程 非安全世界 Normal World NS bit = 1 运行 Rich OS / 普通应用 安全世界 Secure World NS bit = 0 运行 Trusted OS / 安全服务 Monitor 模式 保存上下文 切换 SCR.NS 恢复上下文 ERET 返回 SMC 指令 ERET SMC 指令 ERET 关键说明: 1. SMC(Secure Monitor Call)是唯一合法的世界切换入口 2. Monitor 代码必须放在安全世界,且不可被非安全世界篡改 3. 切换过程中所有中断必须被屏蔽,否则会导致状态不一致 4. 上下文保存必须完整,包括浮点寄存器(如果启用)

2.3 异常向量表重映射

这里有个非常关键的设计:异常向量表重映射。

在标准 ARM 架构里,异常向量表通常放在 0x00000000 或 0xFFFF0000。但在 TrustZone 里,每个世界都有自己的异常向量表,而且它们可以放在不同的物理地址。

为什么需要重映射?

  • 防止非安全世界劫持安全世界的异常——如果两个世界共用同一个向量表,非安全世界可以通过修改向量表来截获安全世界的中断
  • 支持独立的异常处理逻辑——安全世界需要处理安全中断(FIQ),非安全世界处理普通中断(IRQ),逻辑完全不同
  • 实现 Monitor 模式的异常路由——某些异常(如 SMC)必须路由到 Monitor 模式,而不是直接交给当前世界

我曾经踩过的坑:在某次移植中,我忘了配置 VBAR(Vector Base Address Register)的重映射。结果安全世界的 FIQ 异常直接跳到了非安全世界的向量表里,非安全世界的 OS 以为是自己的中断,随手就处理了——密钥材料直接暴露。从那以后,我每次初始化 TrustZone 都会反复检查 VBAR 的配置。

异常向量表重映射的配置方式:

寄存器 作用 所属世界
VBAR 设置当前世界的异常向量表基地址 安全 / 非安全各一份
MVBAR 设置 Monitor 模式的异常向量表基地址 仅 Monitor 模式
SCR 控制异常路由(FIQ/IRQ/EA 等) 仅安全世界可写

异常路由的决策逻辑是这样的:

  1. CPU 触发异常
  2. 硬件检查当前 SCR 寄存器的配置
  3. 如果异常是 SMC,直接进入 Monitor 模式,查 MVBAR
  4. 如果异常是 FIQ 且 SCR.FIQ=1,进入 Monitor 模式
  5. 否则,根据当前世界的 VBAR 处理异常

我建议:在实际项目中,把安全世界的向量表放在安全 SRAM 里,把非安全世界的向量表放在 DDR 里。这样即使非安全世界被攻破,攻击者也改不了安全世界的向量表。

2.4 硬件隔离的基石:NS bit

说了这么多,其实 TrustZone 硬件隔离的核心就一个东西——NS bit(Non-Secure bit)。

这个 bit 会出现在:

  • CPU 内部——SCR.NS 寄存器位,表示当前 CPU 在哪个世界
  • AMBA 总线——每个总线事务都带一个 NS 信号
  • 内存控制器——TZASC 根据 NS 信号决定是否允许访问
  • 外设——TZPC 根据 NS 信号控制外设的访问权限

你想想看,一个非安全世界的程序,即使它知道安全内存的物理地址,它发出的总线事务 NS bit 是 1。TZASC 一看:NS=1,但你这块内存是安全区域——直接拒绝。就这么简单粗暴。

嗯,这里要注意:NS bit 是硬件自动设置的,软件改不了。CPU 在非安全世界时,所有总线事务的 NS bit 自动为 1。只有通过 Monitor 模式切换到安全世界后,NS bit 才会变成 0。

这就是 TrustZone 最底层的信任根基——不是靠软件检查,而是靠硬件信号。