4. TEE启动与安全引导

好,咱们今天聊点硬核的——TEE到底是怎么启动起来的?

很多人做TEE开发,上来就写TA(Trusted Application),结果连TEE是怎么从冷启动到跑起来的都没搞明白。我当年也踩过这个坑,调试一个TA死活加载失败,最后发现是安全引导链断了,TEE根本没进入正常工作状态。嗯,这章咱们就把这事彻底讲清楚。

4.1 TEE的冷启动流程

先问一个问题:手机按电源键,TEE什么时候开始干活?

答案是——比你想的早得多。

TEE的冷启动,说白了就是安全世界从无到有的过程。它不是在Linux启动后才启动的,而是在BootROM阶段就开始介入了。

核心流程:

  1. BootROM(固化在芯片内部)——上电后第一个执行的代码,不可修改
  2. 验证并加载BL1(一级引导)——通常是厂商签名的安全固件
  3. BL1验证并加载BL2——这里开始涉及TEE的初始化
  4. BL2加载TEE内核(如OP-TEE、Trusty)——安全世界正式建立
  5. TEE内核初始化——内存隔离、中断配置、安全存储初始化
  6. 切换到Normal World——启动U-Boot或EDK2,最终加载Linux

我个人习惯把TEE冷启动分成三个阶段:硬件初始化期、安全世界建立期、双世界共存期。

硬件初始化期,说白了就是芯片内部那点事。BootROM检查复位原因,初始化SRAM,然后开始验签。这个阶段TEE的影子都还没有,但安全根基已经种下了。

安全世界建立期,TEE内核开始跑起来。我记得在某个项目里,TEE内核加载到安全DRAM后,第一件事就是配置Monitor模式的异常向量表。这一步要是错了,后面所有SMC调用都会崩。

避坑指南:

我曾经在调试时发现TEE启动后SMC调用返回错误,查了两天才发现是TEE内核加载地址不对。安全DRAM的基地址每个芯片都不一样,一定要看TRM(Technical Reference Manual)确认。

4.2 安全世界与普通世界的切换

TEE启动完成后,系统就进入了双世界共存模式。这两个世界怎么切换?靠的是ARM的Monitor模式和SMC指令。

你想想看,普通世界的Linux想调用TEE里的加密服务,总不能直接跳过去吧?那不乱套了。所以ARM设计了一套机制:

  • SMC(Secure Monitor Call)——从Normal World进入Secure World的唯一合法入口
  • Monitor模式——位于EL3,负责上下文保存和切换
  • 安全中断(FIQ)——安全世界可以抢占普通世界

切换流程大致是这样的:

// Normal World发起SMC调用
asm volatile("smc #0" : : "r"(arg0), "r"(arg1));

// CPU陷入EL3 Monitor
// Monitor保存Normal World上下文(寄存器、SPSR、ELR等)
// 恢复Secure World上下文
// ERET进入Secure World EL1

// Secure World处理请求
// 完成后再次SMC返回Monitor
// Monitor恢复Normal World上下文
// ERET回到Normal World

这里有个细节很多人忽略——上下文切换的开销。每次SMC调用都要保存和恢复几十个寄存器,再加上TLB刷新的代价,频繁切换性能会很难看。我在做支付场景优化时,就把多个小请求合并成一个大请求,减少切换次数,性能提升了30%以上。

注意:

Monitor模式的代码必须放在安全内存中,不能被Normal World访问。我曾经见过一个方案把Monitor代码放在共享内存里,结果被攻击者篡改,整个TEE形同虚设。

4.3 安全引导链(Secure Boot)

安全引导链,说白了就是信任的传递。

从BootROM开始,每一级都验证下一级的签名。只要有一级验证失败,系统就停止启动。这就是所谓的Chain of Trust。

我画了一张图,帮你理解这个链条:

安全引导链(Chain of Trust) BootROM 硬件固化,不可修改 验证签名 BL1 一级引导程序 验证签名 BL2 二级引导程序 验证签名 TEE内核(OP-TEE/Trusty) 安全世界建立 验证签名 U-Boot / EDK2 普通世界引导 验证签名 Linux内核 信任根(Root of Trust) 安全世界(Secure World) 普通世界(Normal World)

这个链条有几个关键点:

  • 信任根(Root of Trust)——BootROM是硬件固化的,没人能改。这是整个安全的基础
  • 逐级验证——每一级只信任上一级,不信任任何外部输入
  • 回滚保护——防止攻击者刷回有漏洞的旧版本固件

说到回滚保护,我踩过一个坑。某次OTA升级后,TEE启动失败,查了半天发现是回滚计数器溢出了。芯片里有个OTP(一次性可编程)区域存着版本号,每次升级版本号递增。结果测试团队刷了太多次,计数器满了,再也升不了级。嗯,从那以后我设计安全引导时,都会留够版本号的位宽。

4.4 Secure Boot在TEE中的实现细节

Secure Boot在TEE里怎么落地?我拿OP-TEE举个例子。

OP-TEE的启动流程中,BL2会加载TEE内核的镜像(tee.bin),然后验证它的签名。签名用的是PKI(公钥基础设施)体系:

组件 作用 存储位置
根公钥(Root Public Key) 验证中间证书 OTP / eFuse
中间证书(Intermediate Cert) 验证镜像签名 固件镜像中
镜像签名(Image Signature) 验证镜像完整性 固件镜像尾部
镜像哈希(Image Hash) 防止篡改 签名结构中

验证流程:

1. BL2从OTP读取根公钥
2. 用根公钥验证中间证书的签名
3. 从中间证书提取镜像签名公钥
4. 用镜像签名公钥验证tee.bin的签名
5. 计算tee.bin的哈希,与签名中的哈希比对
6. 全部通过 → 加载TEE内核
7. 任何一步失败 → 停止启动,进入错误处理

个人经验:

我曾经在调试Secure Boot时,发现TEE内核签名验证总是失败。折腾了两天,最后发现是签名算法不匹配——工具链用的是RSA2048+SHA256,但BootROM只支持RSA1024+SHA1。这种坑在芯片选型阶段就要确认清楚,不然后期改起来成本极高。

4.5 常见问题与避坑指南

最后,我总结几个实战中容易出问题的地方:

  • TEE启动时序问题——有些外设需要在TEE启动前初始化,但TEE启动后又重新配置了这些外设,导致冲突。解决办法是让BL2把外设状态通过共享内存传给TEE
  • 安全中断配置——TEE启动时如果FIQ没配好,Normal World的中断可能会误入Secure World,导致系统卡死
  • 签名密钥管理——开发环境和生产环境的密钥一定要分开。我见过有人把开发密钥烧到量产芯片里,结果安全引导形同虚设
  • 调试接口——JTAG/SWD在量产时必须关闭,否则攻击者可以直接读取安全内存