做嵌入式安全也做了好几年,可以说只要摸过 ARM 平台的设备,不管是车机、手机、路由器还是智能门锁,迟早会碰到 TrustZone 这个词。它经常和"安全世界""可信执行环境"一起出现,但很多资料上来就讲寄存器、讲硬件隔离、讲虚拟化,反倒让人越看越迷糊。我自己刚开始接触时也踩了不少坑,后来才慢慢把这套东西的整条链路捋顺:TrustZone 并不是一个额外的安全芯片,而是处理器内核本身的一种硬件能力;它解决的是"同一颗 CPU 上同时跑业务系统和安全逻辑时,如何做到真正的互相隔离"这类问题。这篇文章想用做项目的视角,把 TrustZone 是什么、为什么这么设计、系统里怎么落地,以及开发中常见的坑一次讲清楚。如果你手头的任务是给终端设备做安全启动、做密钥保护,或者刚接手一块带 TrustZone 的 SoC,希望下面的内容能帮你找到能直接参考的思路。
1. 先搞明白 TrustZone 解决的是哪类问题
1.1 为什么软件隔离总是差一口气
以前做嵌入式安全,提到隔离,大多数人第一时间想到的是操作系统权限:root、用户态和内核态的划分、App 沙箱。这些听起来都挺安全,但问题在于,只要攻击者能拿到内核代码执行权限,或者找到一个内核提权漏洞,系统里所有用户态的保护都形同虚设。更麻烦的是,有些攻击是在 CPU 之外进行的:用逻辑分析仪去监听内存总线上的数据,用 DMA 控制器直接搬内存,或者把存储芯片拆下来做离线读取。这些手段根本不在操作系统的沙箱管辖范围内。
TrustZone 的出发点就在这儿:隔离这件事不能只靠软件,得从硬件架构上就把"两个环境"分开。这颗 CPU 在设计上同时支持两种执行状态,每种状态有自己独立的系统寄存器和内存视图。普通世界(Normal World)里跑的 Linux 或 RTOS,不管拥有多高的软件权限,都没法直接读取安全世界(Secure World)的内存,因为内存总线层面已经被硬件挡住了。这就好比住酒店,普通房卡到不了行政楼层,门禁系统在物理上就拦了一刀,而不是光靠前台软件登记。
1.2 所谓的"两个世界"在 CPU 里到底是什么
很多刚接触的朋友会以为 TrustZone 是在芯片里额外塞了一个内核,像手机里的独立协处理器。其实不是。TrustZone 是在现有 CPU 架构上增加的一种执行模式,同一颗物理核分时运行两个世界的代码,只是切换的时候要通过一条特定指令,并且切换过程会有一层固件把关。
CPU 通过一个叫 NS(Non-Secure)的位来区分当前执行的是哪个世界。这看起来就是个 0 和 1 的标记,但关键是,这个标记会被硬件延伸到系统总线上,内存控制器、中断控制器、总线加密模块都会根据这个标记做权限判断。换句话说,普通世界的程序就算伪造一个内存地址想去读安全世界的物理内存,也会在总线那一层被直接拒绝访问。我实战里更愿意把 TrustZone 理解成"带门禁的两套房间":房间 A 是普通世界,房间 B 是安全世界,CPU 是两间房里共用的一个服务员,它同一时刻只能待在一间房里,但两间房的钥匙密码它都知道。从普通房间切到安全房间,必须按铃触发 SMC 中断,让固件出面来开门。
落到具体架构上,ARMv8 里安全世界和普通世界各自还分 EL0/EL1/EL2/EL3 等特权等级,最底层的 EL3 运行的是安全监视器(Secure Monitor)。普通世界的操作系统最多到 EL1,虚拟化层到 EL2,而 EL3 是安全世界和普通世界共同的"法官",处理两个世界之间的切换。搞清楚这个分层,后面看启动代码和调试日志时会顺畅得多。
1.3 隔离不只是 CPU 核的工作
如果只隔离 CPU 核,还有太多路径可以绕过。比如设备有一块共享内存,普通世界的 DMA 控制器可以直接读写它;再比如某个外设中断需要安全世界处理,可中断控制器却被普通世界霸占着。所以 TrustZone 在 SoC 层面还提供了一组配套隔离机制,常见的有:
| 控制单元 | 主要职责 | 通俗理解 |
|---|---|---|
| TZASC(TrustZone Address Space Controller) | 控制 DDR 等内存区域的访问权限,把物理内存标记为安全区或普通区 | 给不同房间分配地盘 |
| TZPC(TrustZone Protection Controller) | 控制外设是否只允许安全世界访问 | 给每个房间的房门装锁 |
| TZMA(TrustZone Memory Adapter) | 控制片内 SRAM 的访问分区 | 给壁橱里的小保险柜设密码 |
拿指纹支付场景举例。指纹特征模板应该放在一片被 TZASC 标记为安全区域的内存里,负责比对的算法也运行在安全世界。普通世界的应用需要验证指纹时,只能通过安全世界提供的接口发起请求,安全世界比对完成后把"结果"返回,而不是把指纹数据本身送出去。这样一来,就算普通世界被攻破,攻击者能拿到的也只是某个验证通过的信号,拿不到底层的生物特征数据。
不过要注意,启用 TZASC/TZPC 不是一个开关就能搞定。它需要在系统启动早期由安全固件在内存控制器上完成地址分区配置,而且区域数量、每段区域的大小粒度在不同 SoC 上差别很大。实际移植时,我习惯先看芯片手册里对内存 map 的定义,再对照设备树里保留的内存段,宁可多画几个分区,也不要图省事把整个 DDR 都设为安全区后慢慢调。
2. 从加电到业务运行:一条完整的 TrustZone 链路
2.1 信任根不是从 Linux 启动那一刻才开始
TrustZone 在系统里的角色,不是"开机之后再打开一个安全房间",而是从芯片复位开始就要构建的一套平行世界。这里有个核心概念叫信任链(Chain of Trust):第一段代码必须固化在芯片内部 ROM 里,并且这段代码只能执行特定的事,比如校验下一级引导镜像的签名。然后一级一级校验下去,每一级都在上一级建立的信任基础上加载更复杂的代码。在 ARM 平台上,这个环节通常由可信固件(ATF,ARM Trusted Firmware)来承担。
以典型的 ARMv8 平台为例,启动顺序大致是:芯片内嵌的 BootROM 先启动,负责从 eMMC 或闪存里加载签名块;校验通过后,ATF 的 BL2 组件继续初始化 DDR,把 BL31(EL3 运行时固件)和 BL33(普通世界引导程序,比如 U-Boot)加载到对应位置;BL31 运行后负责从安全世界切换到普通世界。关键点在于,安全世界的固件在普通世界 Linux 的眼里是不可见的。它不是被当成 boot 阶段的一次性程序跑完就丢,而是让 BL31 常驻在 EL3,随时准备响应运行时的安全请求。
我调试时习惯先断开引导链,看 BootROM 到 BL31 这一步有没有签名校验之类的错误码。常见问题是 BL2 阶段没有正确配置安全内存区域,或者系统控制固件的版本跟 ATF 不匹配,导致后面的 Secure Monitor 起不来。遇到启动卡住,第一反应不要是去查 Linux 内核日志,因为问题往往发生在内核之前。
2.2 安全世界里实际跑了一套怎样的软件栈
如果说普通世界是一套完整的操作系统环境,那安全世界同样有自己的软件体系。最底层是 ATF 里的 BL31,它运行在 EL3,主要负责普通世界与安全世界的切换和安全中断分发。再往上是 TEE(可信执行环境)内核,常见的实现是 OP-TEE。TEE 内核里可以跑一个个叫 TA(Trusted Application)的安全应用,每个 TA 是一个独立上下文的私密代码,比如一个加解密算法、一个密钥管理模块或一个指纹比对模块。
普通世界侧也不是只能被动等结果。Linux 内核里需要有对应的 TEE 驱动,用户态还需要一个叫 tee-supplicant 的守护进程,它负责转发请求、管理共享内存,以及处理一些需要普通世界配合的操作,比如从文件系统读取加密数据块再交给安全世界解析。整体调用路径大致是这样:
- 普通世界 App 调用 TEE 驱动暴露的接口;
- 驱动把请求包装成特定格式,触发 SMC 指令,陷入 EL3;
- BL31 中的 Monitor 接收到 SMC,切换到安全世界;
- 安全世界 TEE 内核解析请求,调用对应的 TA;
- TA 处理完,结果按原路返回,普通世界拿到返回值。
这段流程听起来清晰,但真要在项目里实现一次,最花时间的往往是内存怎么共享。TEE 内核管理的安全内存和普通世界的虚拟地址空间是隔开的;两边要传一段数据时,不能简单地把普通世界的一个指针交给 TA 去用,而是要先通过内存注册机制把普通世界的某段物理内存映射给安全世界,并且要确保这段内存不能同时被普通世界的 DMA 重新映射,否则就会出现数据被接管的情况。
2.3 通信机制:SMC 与共享内存的边界
SMC(Secure Monitor Call)是一条特权指令,普通世界只有通过这条指令才能主动请求切入安全世界。它之所以重要,是因为它是"世界切换的唯一正规通道"。如果不走 SMC,而是直接尝试访问安全内存,硬件会直接拒绝,没有讨价还价的余地。
共享内存的使用边界也很讲究。以 OP-TEE 为例,普通世界可以把自身的一段合法内存注册为共享内存,然后安全世界通过表项映射来读这部分数据。但共享内存是双向可见的,它不等于安全内存。安全世界之所以要做加密、要保护密钥,恰恰是因为共享区域的数据可能被普通世界随意读写。真正不能被碰的密钥和算法代码,应该放在只给安全世界映射的安全内存里。简单说:共享内存是传数据的走廊,安全内存才是放保险柜的房间。很多新手把这两者混为一谈,等安全评审被打回来的时候才明白差距在哪。
3. 实际落地:典型场景的取舍与实现思路
3.1 生物识别与支付:为什么比对一定要在安全世界做
现在很多带指纹识别的终端,比如门锁、笔记本电脑、手机,都会强调指纹特征数据存储在 TrustZone 内。背后的逻辑其实很简单:指纹特征是一段敏感的模板数据,普通世界被攻破后,攻击者只要能读走这些数据,就可以用伪造特征完成认证。把特征模板放在安全内存里,比对算法跑在 TA 中,普通世界拿到的只有"成功/失败"的结果,哪怕整机被完整接管,也导不出指纹模板。
但这套方案在工程上有几个细节要注意。第一,可信 UI 是个难点。如果只是安全世界里比对指纹,屏幕显示和交互仍然在普通世界,中间就存在被插一脚的窗口。完整方案需要安全世界能独占一片显示图层和触屏通道,让用户输入的 PIN 直接进入安全世界、显示结果也由安全世界控制输出。实现这个通常要芯片厂商提供对应的显示安全接口,不然只能在应用层做缓解。第二,指纹传感器驱动本身需要被当作安全资源对待。传感器产生的中断应该走安全中断通道,传感器寄存器区域也应该配置为仅安全世界可访问,否则普通世界可以反复触发传感器,通过 DMA 通路把模板读走。
3.2 密钥管理与安全存储:先想清楚什么能进 TA
TrustZone 另一个被广泛使用的场景是密钥管理和安全存储。设备出厂时在安全世界里生成或者注入一把设备根密钥,后续所有业务密钥都由它派生。常见做法是把根密钥绑定在硬件存储区域(比如 OTP/eFuse),并让 TA 负责用根密钥解密加载业务密钥。这样就算攻击者能完整导出文件系统,拿到的也只是被根密钥加密过的密文,缺了 CPU 里那把解密的钥匙。
这里有一个很多人会忽略的坑:安全世界本身也是软件,它也有漏洞。如果 TA 存在内存安全问题,攻击者一旦在安全世界里执行了任意代码,密钥还是可能被导出。所以设计上一般会把 TA 的权限尽量做小,只暴露最小接口,并对密钥访问加入严格的会话授权机制。相比把密钥放在普通世界的加解密库或独立安全芯片里,TrustZone 的优势在于密钥可以不离开 SoC 上这颗 CPU 的可信上下文,但"放到安全世界"绝不等于万无一失。
3.3 可信启动、防降级与设备锁定
安全启动依赖的信任链,正好可以把 TrustZone 固件和普通世界系统镜像一起纳入签名校验。镜像无法被任意替换,想刷入带后门的固件必须先过签名这一关。与之配套的是防降级机制:安全固件里记录一个版本号,旧版本镜像即使有合法签名,也会因为版本号低于当前记录而被拒绝启动。这个机制经常用在车机、支付终端这类对安全升级有强制要求的设备上。
做防降级方案时我有一个经验:版本号最好由安全固件自己控制,并写进一块只能递增、不能递减的存储,比如普通世界拿不到的 RPMB 分区或 OTP 区域。如果只是把版本号放在普通世界的文件系统里,攻击者用官方镜像配合一个改小过的版本号,就能绕过防降级检查。TrustZone 在这里的价值,就是给"最后一道防线"提供一块普通世界永远无法直接写入的空间。
3.4 一个可参考的 demo 练习
前阵子带同事做一个模拟的"安全保险箱"练习,用来验证 TrustZone 的 TA 开发流程。目标很简单:普通世界写一段消息,通过 TEE 驱动发给安全世界,安全世界对消息做签名,普通世界再读取签名结果。整体分四步:
- 在 TEE 内核里新增一个 TA,分配一个唯一的 UUID,并在清单文件里注册接口;
- 实现 TA 的入口函数,处理消息封装与解析,调用密码库做签名;
- 普通世界写一个小的用户态程序,通过客户端接口注册共享内存,发起签名请求;
- 编译、烧录,观察安全固件启动日志,确认 TA 在安全世界里被加载。
这个练习真正有价值的不是签名算法本身,而是把从 TA 源码到编译、到烧录、到调试的完整工具链摸一遍。第一次跑通时,十个人里有九个会卡在共享内存注册参数上,普通世界里取到的数据明明是对的,进到安全世界就变成乱码,最后查出来是内存对齐和缓存一致性的问题。这块我在下一节展开讲。
4. 开发中的常见误区与问题排查
4.1 三个让我印象深刻的认知误区
误区一:TrustZone 是一个"安全操作系统"。实际上它只是一套硬件隔离能力,安全世界里跑什么代码完全由你决定。如果你在安全世界塞进一个功能庞大、接口复杂的系统,它的攻击面甚至比普通世界还大,因为你还要担心普通世界与安全世界之间那段通信链路被滥用。
误区二:安全世界可以随便跑重负载业务。Secure World 不是高速加速器,它和普通世界共享同一个 CPU 核,内存带宽、中断响应都受整体调度影响,频繁切换世界的开销也不小。我见过有人把加解密批量任务全部丢给安全世界处理,结果整个系统响应明显变差,最后改成批量任务在普通世界做预处理,只把真正敏感的关键操作放进 TA,性能问题才解决。
误区三:开启 TrustZone 就等于设备固若金汤。TrustZone 的隔离做得再好,也只是把问题收敛到一个更小的攻击面里。总线嗅探、侧信道、安全世界的软件漏洞、以及芯片厂商在硬件层面的设计缺陷,都可能绕过 TrustZone。安全设计永远是一层层叠加,而不是装上一个开关就完事。
4.2 从启动日志快速定位问题
排查 TrustZone 问题,我最常做的是先看启动早期日志。这里给一段典型的 ATF 阶段日志示例,方便你对照板子的输出是否正常:
NOTICE: BL31: v2.8(release):v2.8-142-g1234abc NOTICE: BL31: Built : 12:00:00, Jan 1 2025 NOTICE: BL31: Detected device: 某测试平台 INFO: BL31: Initializing runtime services INFO: BL31: Preparing for EL3 exit to normal world NOTICE: OP-TEE: revision 4.1.0 INFO: OP-TEE: Initializing INFO: OP-TEE: Found 3 secure zones如果日志里没有出现 OP-TEE 的初始化信息,我一般按这个顺序排查:
- 检查 BL2 阶段是否把 TEE 镜像正确加载到安全内存地址;
- 检查 BL31 链接脚本里的安全内存基址和大小是否与内存 map 一致;
- 检查 TEE 镜像是混编在 BL31 里还是独立加载,两种方式的加载流程不一样,踩的坑也不同。
运行阶段遇到 SMC 调用失败,最常见的现象是共享内存注册返回错误码,或者打开 TA 会话时超时。这时我会先打开内核 TEE 驱动和 OP-TEE 的调试开关,观察调用链上到底是哪一层出了问题。很多所谓"安全世界崩了"的场景,其实是普通世界传入的消息格式不对,或证书管理没配好,导致 TA 直接拒绝响应。
4.3 一张实战问题速查表
| 症状 | 可能原因 | 排查方式 |
|---|---|---|
| 启动卡在 BL31 之前 | 安全镜像签名校验失败或 BL2 未加载 | 看 BootROM 错误码,检查签名工具与密钥是否匹配 |
| OP-TEE 初始化后立即异常 | 安全内存区域配置冲突 | 对比设备树保留内存与 TZASC 区域配置 |
| SMC 调用返回权限错误 | 请求被安全世界拒绝 | 检查 TA UUID 是否注册、会话权限是否足够 |
| 共享内存读出乱码 | 缓存一致或内存对齐问题 | 共享缓冲区加 cache 维护操作,确认地址按页对齐 |
| 安全世界内 TA 崩溃但普通世界假死 | 缺少安全世界异常处理回调 | 在 TEE 内核里注册异常 hook,把错误码上报普通世界 |
| 开启 TrustZone 后外设中断无响应 | 外设被配置为仅安全访问 | 检查 TZPC 配置,给对应外设添加普通世界访问许可 |
这个表我一般会打印出来贴在工位上。实际项目里的坑多到写一篇长文也写不完,但 80% 都能落到上面几类。
5. 几个值得长期坚持的实操习惯
5.1 把内存边界画成一张图
做熟了之后,我慢慢总结出几件事每次都会做。第一件,把内存 map 和安全边界画成一张图。不要偷懒只用一个设备树文件就完事,设备树里保留的内存段和 TZASC 里配置的安全区域经常对不上。我习惯把物理内存地址、大小、属主、是否安全四个字段做成一张表格,每做一步配置就往里填一步,最后评审时一眼就能看出有没有越界或者漏配。
5.2 安全世界代码保持小而精
第二件,安全世界的代码尽量做得小而精。安全世界不是主业务代码的跑马场,能用普通世界算完的不要拿到安全世界算,但凡是密钥、证书、指纹这类不能暴露的数据,才需要放进 TA。把攻击面控制在最小,是 TrustZone 方案里最容易被忽视、却最重要的设计原则。
5.3 调试时多借助硬件调试器
第三件,调试时不要只打 log,多用硬件调试器。TrustZone 的世界切换、异常向量表、cache 行为,在真实硬件上和模拟器里有细微差别。用调试器在 EL3 处打断点看寄存器现场,往往比对着 log 猜来猜去快得多。另外,把断点同时设在普通世界异常和安全世界异常里,两边都能抓到现场,定位问题时省掉大量重复实验。
5.4 评估方案前先摸清周边能力
最后说一个经验:TrustZone 方案评估阶段,不要光看芯片手册写"支持 TrustZone"就认为万事俱备。你得确认采购的 SoC 是否已经开放了安全启动、可信固件源码、TEE 调试接口这些周边能力。有些芯片只开放普通世界的 SDK,安全世界相关的东西要么闭源要么只给二进制,遇到这种平台,后面做定制开发会非常难受。先花时间把供应链和安全工具的成熟度摸清楚,再定技术方案,比什么都重要。
这大概是我做 TrustZone 相关项目以来最想分享的内容。技术细节还有很多,文章里难免有没说透的地方,但如果你正准备把一个安全功能从"普通世界代码里硬搞"换成"TrustZone 里正经做",上面这些结构和坑位应该能帮你少走不少弯路。