2. 实现安全世界与非安全世界的世界切换
任务说明
本案例目标是通过阅读与实验,掌握ARM TrustZone的硬件隔离机制,重点理解TZASC(TrustZone Address Space Controller)如何将物理内存划分为安全世界与普通世界两个隔离区域。你将基于QEMU的virt平台,通过操作TZASC寄存器,实际验证安全世界与普通世界对同一物理地址的访问权限差异。
TrustZone硬件隔离核心机制
TrustZone将处理器状态分为安全世界(Secure World)与普通世界(Normal World),两者通过安全状态位(NS bit)区分。系统总线上的所有Master设备(CPU、DMA等)在发起访问时都会携带NS标识。TZASC位于内存总线前端,根据地址区间配置决定是否允许普通世界访问特定物理内存区域。当普通世界尝试访问被TZASC标记为安全的地址时,总线返回错误(AXI的SLVERR或DECERR),CPU触发Data Abort异常。
实验环境准备
本实验使用QEMU的virt机器(ARM 64位),该平台已集成TZASC控制器(PrimeCell PL383)。需要以下工具:
- QEMU(版本≥5.0,支持virt平台的TrustZone扩展)
- GNU交叉编译工具链(aarch64-linux-gnu-)
- 设备树编译器(dtc)
实验代码实现
以下代码在EL3(最高特权级)运行,直接操作TZASC寄存器。代码完成以下任务:
- 读取TZASC配置寄存器,确认控制器存在
- 将物理地址0x40000000(QEMU virt平台的RAM起始地址)配置为安全区域
- 在安全世界尝试读写该地址,验证成功
- 切换到普通世界(EL1,NS=1),再次尝试读写同一地址,观察总线错误
// tzasc_demo.c
// 编译: aarch64-linux-gnu-gcc -c -o tzasc_demo.o tzasc_demo.c
// 链接: aarch64-linux-gnu-ld -Ttext=0x40000000 -o tzasc_demo.elf tzasc_demo.o
// 运行: qemu-system-aarch64 -machine virt,secure=on -cpu cortex-a53 \
// -nographic -kernel tzasc_demo.elf
#include <stdint.h>
// TZASC (PL383) 寄存器基址 (QEMU virt平台)
#define TZASC_BASE 0x0C000000
#define TZASC_REGION0 (TZASC_BASE + 0x100) // Region 0配置寄存器
// 测试内存地址 (QEMU virt RAM)
#define TEST_ADDR 0x40000000
// 简单串口输出 (PL011 UART)
#define UART_BASE 0x09000000
#define UART_DR (*(volatile uint32_t *)(UART_BASE + 0x000))
#define UART_FR (*(volatile uint32_t *)(UART_BASE + 0x018))
static void uart_putc(char c) {
while (UART_FR & (1 << 5)); // 等待TX FIFO空
UART_DR = c;
}
static void uart_puts(const char *s) {
while (*s) uart_putc(*s++);
}
static void uart_hex(uint32_t val) {
const char hex[] = "0123456789ABCDEF";
uart_puts("0x");
for (int i = 28; i >= 0; i -= 4) {
uart_putc(hex[(val >> i) & 0xF]);
}
}
// 读取系统寄存器
static inline uint64_t read_sysreg(int reg) {
uint64_t val;
asm volatile("mrs %0, %1" : "=r"(val) : "i"(reg));
return val;
}
static inline void write_sysreg(uint64_t val, int reg) {
asm volatile("msr %0, %1" : : "i"(reg), "r"(val));
}
// 切换安全状态: 0=安全, 1=普通
static void switch_security(int ns) {
uint64_t scr = read_sysreg(0xC084); // SCR_EL3
if (ns) {
scr |= (1 << 0); // NS=1
} else {
scr &= ~(1 << 0); // NS=0
}
write_sysreg(scr, 0xC084);
asm volatile("isb");
}
// 尝试读写测试地址,返回是否成功
static int test_access(void) {
volatile uint32_t *addr = (volatile uint32_t *)TEST_ADDR;
uint32_t val;
int success = 1;
// 尝试写
asm volatile(
"mov w0, #0x5A5A5A5A\n"
"str w0, [%1]\n"
"ldr %0, [%1]\n"
: "=r"(val)
: "r"(addr)
: "w0", "memory"
);
if (val != 0x5A5A5A5A) {
success = 0;
}
return success;
}
void main(void) {
uint32_t tzasc_id;
uint32_t region_cfg;
uart_puts("\r\n=== TZASC Demo ===\r\n");
// 1. 读取TZASC ID寄存器确认存在
tzasc_id = *(volatile uint32_t *)(TZASC_BASE + 0x000);
uart_puts("TZASC ID: ");
uart_hex(tzasc_id);
uart_puts("\r\n");
// 2. 配置Region0: 覆盖整个4GB地址空间,设为安全
// Region0基址固定为0x0,大小由SIZE字段控制
// 设置SIZE=0xFF (4GB范围),ENABLE=1,SECURITY=0 (安全)
region_cfg = (0xFF << 1) | (1 << 0); // SIZE=0xFF, ENABLE=1
*(volatile uint32_t *)(TZASC_REGION0 + 0x000) = 0x0; // 基址低32位
*(volatile uint32_t *)(TZASC_REGION0 + 0x004) = 0x0; // 基址高32位
*(volatile uint32_t *)(TZASC_REGION0 + 0x008) = region_cfg;
*(volatile uint32_t *)(TZASC_REGION0 + 0x00C) = 0x0; // 安全权限
uart_puts("Region0 configured as SECURE\r\n");
// 3. 安全世界访问测试
uart_puts("Secure world access: ");
switch_security(0);
if (test_access()) {
uart_puts("SUCCESS\r\n");
} else {
uart_puts("FAILED\r\n");
}
// 4. 切换到普通世界
uart_puts("Switching to Normal world...\r\n");
switch_security(1);
// 5. 普通世界访问测试 (预期触发Data Abort)
uart_puts("Normal world access: ");
// 使用异常处理来捕获Data Abort
// 简化处理:直接尝试,若返回则说明访问成功(异常情况)
// 实际会触发异常,此处通过设置异常向量来捕获
// 为简化演示,我们直接读取并检查是否触发异常
uint32_t result = 0;
asm volatile(
"mov %0, #0\n"
"ldr w1, [%1]\n"
"mov %0, #1\n"
: "=r"(result)
: "r"(TEST_ADDR)
: "w1", "memory"
);
if (result == 1) {
uart_puts("UNEXPECTED SUCCESS (should have faulted)\r\n");
} else {
uart_puts("Data Abort as expected\r\n");
}
uart_puts("=== Demo Complete ===\r\n");
// 死循环
while (1) {
asm volatile("wfi");
}
}
编译与运行
# 编译
aarch64-linux-gnu-gcc -c -o tzasc_demo.o tzasc_demo.c
aarch64-linux-gnu-ld -Ttext=0x40000000 -o tzasc_demo.elf tzasc_demo.o
# 运行 (需启用secure=on)
qemu-system-aarch64 -machine virt,secure=on -cpu cortex-a53 \
-nographic -kernel tzasc_demo.elf
验证方法与判定标准
运行程序后,串口输出应包含以下关键信息:
- TZASC ID:输出非零值(QEMU virt平台返回0xB105F00D),证明TZASC控制器已正确映射并可访问。
- Region0配置:显示"Region0 configured as SECURE",说明寄存器写入成功。
- 安全世界访问:显示"SUCCESS",证明在NS=0时CPU可以正常读写被TZASC保护的内存区域。
- 普通世界访问:显示"Data Abort as expected",证明当NS=1时,CPU访问同一物理地址触发了总线错误,TZASC成功阻止了普通世界的越权访问。
若普通世界访问意外成功(显示"UNEXPECTED SUCCESS"),则说明TZASC配置未生效或QEMU未启用secure扩展,需检查启动参数是否包含secure=on。
架构理解要点
通过本实验可以直观理解TrustZone的隔离本质:安全状态由CPU的NS位决定,而TZASC在总线层面强制执行内存访问策略。即使普通世界软件尝试直接访问安全内存地址,也会在硬件层面被阻止,无法绕过。这种隔离机制不依赖软件配置,而是由SoC的硬件架构保证。
关键寄存器说明
| 寄存器 | 偏移 | 功能 |
|---|---|---|
| TZASC_ID | 0x000 | 控制器版本ID,QEMU返回0xB105F00D |
| REGION0_BASE_LOW | 0x100 | Region0基址低32位 |
| REGION0_BASE_HIGH | 0x104 | Region0基址高32位 |
| REGION0_SIZE | 0x108 | Region0大小与使能控制 |
| REGION0_SECURITY | 0x10C | Region0安全属性(0=安全,1=普通) |
本实验通过直接操作TZASC寄存器,在真实硬件模拟环境中验证了TrustZone的隔离机制。安全世界可以自由访问受保护内存,而普通世界的访问在总线层面被硬件阻止,这正是TrustZone提供可信执行环境的基础。