ESP-IDF 中的 ESP-TEE 高级主题:架构剖析、安全服务调用与自定义扩展实战
2026/9/17 7:32:57 网站建设 项目流程

ESP-IDF 中的 ESP-TEE 高级主题:架构剖析、安全服务调用与自定义扩展实战

【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf

ESP-TEE(Trusted Execution Environment)是 ESP-IDF 在 RISC-V 芯片上提供的可信执行环境框架,它通过 RISC-V 特权级(M/U 模式)、PMP/PMA 以及 Espressif 自研的 APM 外设,把系统划分为高特权 TEE 与低特权 REE 两个隔离的执行域。本文基于 docs/en/security/tee/tee-advanced.rst 展开,深入讲解 ESP-TEE 的详细架构、内存与 Flash 隔离机制、中断处理模型、安全服务(Secure Service)调用约定,并给出新增自定义安全服务的完整实操步骤与单元测试方法,帮助开发者在当前仓库中构建、扩展并验证自己的 TEE 安全方案。


详细架构:M 模式与 U 模式的双世界隔离

ESP-TEE 框架在目标芯片上同时利用了 RISC-V 架构的固有特性与 Espressif 专有硬件外设,共同保证系统隔离并提供安全执行环境:

  • RISC-V CPU 固有特性
    • Machine(M)与 User(U)两种特权级
    • PMP(Physical Memory Protection,物理内存保护)
    • PMA(Physical Memory Attributes,物理内存属性)
  • Espressif 专有硬件
    • APM(Access Permission Management,访问权限管理)外设

通过这些组件的组合,芯片可以将硬件资源(内部内存、外部内存与外设)和软件资源划分为两种模式:高特权的M 模式(TEE)与低特权的U 模式(REE)。CPU 可以在两种模式之间切换,TEE 运行在更高特权的 M 模式,REE 运行在较低特权的 U 模式。

启动流程

启用 ESP-TEE 后,标准应用启动流程会发生轻微变化:复位后,第二阶段引导加载程序(second stage bootloader)从启动设备同时加载TEE 镜像应用镜像,随后将控制权移交给 TEE。TEE 负责配置内存、中断与外设访问权限,然后切换到 U 模式下的 REE 执行应用镜像。

隔离之硬件基础

RISC-V 特权级

在 RISC-V 架构中,每个硬件线程(hart)工作于特定的特权级,该特权级定义在一个或多个控制状态寄存器(CSR)的模式字段中。特权级是软件栈内部不同组件之间的保护屏障——任何试图执行当前特权模式不允许的操作或访问受保护资源的行为都会触发异常。M 模式拥有最高特权,运行在 M 模式的代码通常被视作固有可信代码,因为它拥有对机器实现的底层访问能力。

PMP 与 PMA 检查器

ESP-TEE 利用 RISC-V 的 PMP 与 PMA 检查器管理 TEE/REE 上下文中对系统内存(内部 SRAM 与外部 Flash)的访问:

  • PMP:允许 M 模式软件为指定内存区域配置访问权限(读、写、执行)。该机制按系统预定义策略强制实施 TEE 与 REE 之间的资源隔离,保证安全访问控制。
  • PMA(PMAC):通过提供额外的权限检查增强安全性,包括可配置的内存类型以及为指定内存区域定制属性。
APM 访问权限管理

APM 模块是芯片级硬件外设,用于在系统内存与外设两个层面强制 TEE 与 REE 之间的隔离。它由两个主要组件构成,各自拥有独立寄存器:

  • TEE 控制器:TEE 相关寄存器用于配置每个主设备(如 HP CPU,或作为主设备访问内存的 DMA)的安全模式;
  • APM 控制器:APM 相关寄存器用于指定每种安全模式的访问权限与访问地址范围。

APM 在 PMP/PMA 机制力所不及之处增强了系统隔离:PMP/PMA 的表项数量有限,且无法约束低功耗核或其他主设备(如 GDMA)的访问。需要注意的是,对于目标芯片,APM 与 PMP 共同扮演权限控制(PMS,Permission Control)的角色

隔离之实体划分

内部内存(SRAM)

HP SRAM 顶部的一段区域被预留给 TEE,用于 TEE 代码执行与运行时数据存储(包括栈和堆)。SRAM 内所有内存区域的权限均由 TEE 通过 PMP 配置管理。

REE 内存通过 PMP 划分为 IRAM(代码段:读/执行)与 DRAM(数据段:读/写);而 TEE 内存同样划分为 IRAM 与 DRAM,但其划分由PMA强制执行。

在 Kconfig 中,TEE 的内存区域尺寸可通过以下配置项调整(见 components/esp_tee/Kconfig.projbuild):

配置项说明默认值取值范围
SECURE_TEE_IRAM_SIZETEE 模块 IRAM 区域大小0x80000x5000~0xF000(需 256 字节对齐)
SECURE_TEE_DRAM_SIZETEE 模块 DRAM 区域大小0x50000x3000~0x7000(需 256 字节对齐)
SECURE_TEE_STACK_SIZETEE 栈大小(从 TEE DRAM 区域分配)0x1000(启用 Secure Boot 时)/0xc000x800~0x1000(需 16 字节对齐)
SECURE_TEE_INTR_STACK_SIZETEE 中断栈大小(从 TEE DRAM 区域分配)0x4000x400~0x800(需 16 字节对齐)
SECURE_TEE_IROM_SIZETEE 外部 Flash 指令段大小0x20000需为 MMU 页大小的整数倍
SECURE_TEE_DROM_SIZETEE 外部 Flash 数据段大小0x10000需为 MMU 页大小的整数倍
外部内存(Flash)

外部 Flash 中保留指定分区给 TEE,用途包括:通过 XIP 执行 TEE 代码、安全存储(Secure Storage)以及 OTA 数据。PMS 保护这些分区免受未授权访问:APM 模块保护 MMU 与 SPI1 控制器寄存器,PMP 保护 cache

Flash 保护:虚拟访问与物理访问

Flash 保护的关键接口有两个:连接 SPI0 的cache(提供对 Flash 的虚拟访问)与SPI1 控制器(提供物理访问)。默认情况下,cache 与 MMU 寄存器受 PMS 保护,阻止 REE 对 TEE 相关 Flash 分区进行虚拟访问。

  • 启用Flash 加密后,REE 仍可通过 SPI1 访问 TEE Flash 区域,但读操作返回的是加密数据。由于 REE 与 TEE 都无法直接访问 Flash 加密密钥,攻击者无法通过直接读取推断 TEE 内容。
  • 启用Secure Boot后,对 TEE 固件的任何未授权修改都会在启动时被检测到,导致签名校验失败。因此Flash 加密 + Secure Boot的组合能为大多数应用提供足够强的保护。
  • 需要注意:TEE 固件完整性虽然受保护,但其他 TEE 分区(如 Secure Storage、TEE OTA 数据)仍可能通过直接写入被篡改。

如需更强隔离,可启用CONFIG_SECURE_TEE_EXT_FLASH_MEMPROT_SPI1:该选项完全阻断 REE 通过 SPI1 访问所有 TEE Flash 区域,此时所有 SPI Flash 读、写、擦除操作都通过服务调用(service call)路由到 TEE 执行。该选项带来更高安全性,但会引入一定性能开销。该配置在 Kconfig 中的默认值为n,其帮助文本明确说明:启用后所有经 SPI1 的 SPI Flash 操作都会产生额外的性能开销,并提示"仅在需要完全隔离全部 TEE Flash 区域(即使付出性能代价)时才启用";同时 SPI0(即 MMU)路径对 TEE 分区的访问是无条件阻断的。

下表给出了使用 Partition API 以 256B 分块读写 1MB 分区的大致耗时,展示了 ESP-TEE 与CONFIG_SECURE_TEE_EXT_FLASH_MEMPROT_SPI1配置对性能的影响:

场景读 (ms)读 Δ (ms)读 Δ (%)写 (ms)写 Δ (ms)写 Δ (%)
关闭 ESP-TEE262.01--3394.23--
启用 ESP-TEE279.86+17.85+6.81%3415.64+21.41+0.63%
ESP-TEE + SPI1 保护359.73+97.72+37.33%3778.65+384.42+11.32%
外设

以下外设通过 APM 模块保护,仅 TEE 可访问;REE 对这些外设的任何直接访问都会触发异常。选择保护这些外设,是因为来自 REE 的访问可能危及系统安全,或干扰安全存储、认证(attestation)等安全服务:

  • APM 外设
  • 中断控制器(Interrupt Controller)
  • eFuse 控制器
  • 欠压检测器(Brownout Detector)
  • 超级看门狗定时器(SWDT)
  • AES 加速器(芯片支持时)
  • SHA 加速器(芯片支持时)
  • ECC 加速器(芯片支持时)
  • HMAC 模块(芯片支持时)
  • 数字签名模块(芯片支持时)

注意:以下外设将在未来版本中纳入保护:MPI 加速器(RSA)、ECDSA 加速器(芯片支持时)。

固件与系统初始化

TEE 固件主要从内部安全 SRAM 加载执行;由于 SRAM 容量有限,部分 TEE 固件存储在专用的外部 Flash 分区中执行(该分区受保护,REE 无法访问)。该固件负责安全配置系统,确保内部/外部内存与外设的隔离与保护。

TEE 负责 SoC 资源的安全初始化,包括:

  • 设置 TEE 向量表、指定的安全中断以及委托给 REE 的中断;
  • 配置 TEE 与 REE 的隔离边界:
    • 为内部内存(I/DRAM)与外部内存(Flash)配置 RISC-V PMP/PMA;
    • 为外设(AES、SHA、eFuse、MMU 等)配置 APM;
  • 注册 TEE 堆。

在 esp_tee_init.c 中可以看到 TEE 初始化时的关键校验逻辑:TEE 会检查esp_tee_app_config结构中的magic_word是否为ESP_TEE_APP_CFG_MAGIC0x3348AAED),并校验 API 主版本号与二进制是否匹配,不匹配即进入ESP_INFINITE_LOOP()(由看门狗复位系统)。该配置结构esp_tee_config_t定义于 esp_tee.h,内嵌在 REE 应用镜像的 IRAM 段中,包含安全中断处理函数指针、REE 入口地址、REE 中断处理函数指针以及 REE 的 IRAM/IROM/DRAM 边界等字段,TEE 在切换到 REE 前读取并更新该结构,随后将其写保护。

中断处理模型

TEE 与 REE 各自拥有独立的向量表,并有一个专用于安全中断的中断引脚。TEE 中断的优先级始终高于 REE 中断。TEE 固件提供了注册安全中断(外设特定中断处理函数)的 API。

根据中断发生时的状态,中断可能在当前执行环境的向量表中被处理,也可能触发特权切换、移交到另一环境的向量表处理。共有四种可能场景:

场景说明
在 TEE 中操作时发生 TEE 中断在 TEE 的 M 模式异常处理程序中捕获
在 REE 中操作时发生 REE 中断在 REE 的 U 模式异常处理程序中捕获
在 REE 中操作时发生 TEE 中断在 TEE 的 M 模式异常处理程序中捕获,处理完毕后返回 REE
在 TEE 中操作时发生 REE 中断在 TEE 的 M 模式异常处理程序中捕获,跳转到 REE 的 U 模式异常处理程序,处理完毕后返回 TEE

当中断在另一执行环境中处理完毕后,执行流程会返回最初触发中断的环境。以"REE 中发生 TEE 中断"为例,其序列为:REE 软件执行 → 触发 TEE 中断(跳转到 M 模式异常处理程序)→ 保存 U 模式上下文、切换到 M 模式中断栈 → 执行服务例程 → 恢复 U 模式上下文(mret指令)→ 返回 REE 继续执行。


安全服务(Secure Services)

执行流程

安全服务调用接口允许 REE 应用请求受信任(TEE)的操作,并触发安全服务分发器(secure service dispatcher)。分发器是 TEE 的入口点,负责解析输入参数、识别要调用的服务,并将请求转发给对应的服务处理函数。安全服务完成后,分发器处理返回过程并启动特权切换,将控制权恢复给 REE 中原始调用的位置——特权级的切换是安全服务分发器前后处理例程的一部分。

在 esp_secure_dispatcher.c 中可以看到分发器esp_tee_service_dispatcher的实现:它首先校验参数个数(上限ESP_TEE_MAX_INPUT_ARG,即 10 个),通过find_service_by_id在内部内存服务表tee_sec_srv_tbl_int_mem与外部内存服务表tee_sec_srv_tbl_ext_mem中查找服务 ID,校验实参个数与表中记录的nargs一致后,通过内联汇编将前 8 个参数装载到a0-a7寄存器(多于 8 个的参数压栈),再jalr调用目标函数。服务表本身在 esp_secure_service_table.c 中通过包含生成的secure_service_int.h/secure_service_ext.h头文件完成初始化。

完整的安全服务调用序列为:REE 软件执行 → 发起安全服务调用(ecall指令,跳转到 M 模式异常处理程序)→ 保存 U 模式上下文、切换到 M 模式栈、禁用 U 模式中断委托→ 执行安全服务 → 恢复 U 模式上下文、重新启用 U 模式中断委托mret指令)→ 返回 REE 继续执行。

调用约定

安全服务调用接口 APIesp_tee_service_call的调用约定如下:

参数类型说明
arg0uint8_t输入:传给安全服务的输入参数个数
arg1tee_secure_service_t输入:安全服务 ID
arg2-arg9uint32_t输入:传给安全服务的参数
valuint32_t输出:安全服务的返回值

IRAM 安全变体esp_tee_service_call_with_noniram_intr_disabled会在切换执行环境之前挂起调度器并禁用所有非 IRAM 驻留中断。这在通过 SPI1 总线访问 Flash(此时 Flash cache 可能被禁用)等操作中至关重要,可防止多个实体并发访问 Flash。该 API 在 esp_tee.h 中声明(uint32_t esp_tee_service_call(int argc, ...)),仅对非 TEE 构建(!ESP_TEE_BUILD)暴露。

默认服务表

默认安全服务表按"家族"(family)分组,定义在各芯片目录下的 YAML 文件中,例如 components/esp_tee/scripts/esp32c6/sec_srv_tbl_default.yml。以 ESP32-C6 为例,主要包括:

  • misc:ID 0(invalid_secure_service
  • flash_protection_spi0(ID 1-4):MMU 映射/解映射、虚实地址转换(mmu_hal_map_region等)
  • flash_protection_spi1(ID 5-21):SPI Flash HAL 层的状态查询、通用命令、擦除、编程、读写、挂起/恢复、写保护等
  • interrupt_handling(ID 30-37):中断矩阵路由、使能/禁用、优先级/类型/阈值设置等
  • hal(ID 54-55):看门狗 HAL 初始化/反初始化
  • crypto(ID 86-111):AES 各模式加解密、SHA、HMAC、数字签名(DS)、ECC 点乘与点验证、时钟使能等
  • attestation(ID 170-171):PSA 初始认证 token 获取
  • secure_storage(ID 175-181):密钥生成/清除、ECDSA 签名与公钥获取、AEAD 加解密等
  • ota(ID 195-197):TEE OTA 的 begin/write/end
  • ID 200+:用户自定义区域

所有默认服务调用函数定义在 esp_secure_services.c 中。REE 侧则通过 esp_secure_service_wrapper.c 中的__wrap_*包装函数完成对服务 ID 的封装调用——例如__wrap_esp_aes_crypt_ecb内部调用esp_tee_service_call(5, SS_ESP_AES_CRYPT_ECB, ...),部分加密操作还会在调用前后获取/释放对应的加密锁(如esp_crypto_sha_aes_lock_acquire/release)。注意,SPI1 Flash 保护相关包装函数仅在CONFIG_SECURE_TEE_EXT_FLASH_MEMPROT_SPI1使能时编译,而认证相关包装函数在CONFIG_SECURE_TEE_ATTESTATION使能时编译。


添加自定义安全服务

扩展 ESP-TEE 框架以支持自定义服务调用,需要完成以下三个步骤。

第一步:创建自定义服务调用表

定义一个用于声明自定义服务调用的组件,并在组件内创建.yml文件:

touch <path/to/yml/file>/custom_srvcall.yml

按以下格式向.yml文件添加自定义服务调用条目:

secure_services: - family: <api_family> entries: - id: <service_call_number> type: custom function: <function_name> args: <arguments_count>

示例条目

secure_services: - family: example entries: - id: 300 type: custom function: example_sec_serv_aes_op args: 5

字段含义:

  • 300:唯一的服务调用编号;
  • custom:自定义服务调用类型;
  • example_sec_serv_aes_op:函数名;
  • 5:参数个数。

必须确保自定义服务调用编号与默认服务调用表不冲突(默认表已为ID 200+预留用户自定义区域)。ESP-TEE 框架会同时解析自定义服务调用表与默认表,生成应用所需的头文件。

第二步:定义服务调用实现

在 TEE 中定义与自定义服务调用对应的函数,该函数在 REE 以对应编号发起服务调用时执行。

示例实现

int _ss_custom_sec_srv_op(void *arg) { // Perform the intended task return 0; }

函数名必须以_ss_前缀开头,并且必须与.yml文件中指定的名称一致。

第三步:构建系统改动

自定义组件级:在定义自定义服务调用的组件中创建 CMake 文件(例如custom_sec_srv.cmake),并添加以下配置。

  1. 将服务调用表追加到默认表:
idf_build_set_property(CUSTOM_SECURE_SERVICE_YAML ${CMAKE_CURRENT_LIST_DIR}/custom_srvcall.yml APPEND)
  1. 设置自定义组件目录与名称,使esp_tee子项目可以使用它:
get_filename_component(directory "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE DIRECTORY) idf_build_set_property(CUSTOM_SECURE_SERVICE_COMPONENT_DIR ${directory} APPEND) get_filename_component(name ${CMAKE_CURRENT_LIST_DIR} NAME) idf_build_set_property(CUSTOM_SECURE_SERVICE_COMPONENT ${name} APPEND)
  1. 定义自定义组件的CMakeLists.txt
idf_build_get_property(esp_tee_build ESP_TEE_BUILD) if(esp_tee_build) ## Headers, sources and dependent components for the TEE-build else() ## Headers, sources and dependent components for the REE-build endif() idf_component_register(...)

项目级:修改项目顶层CMakeLists.txt,在调用project()命令之前包含custom_sec_srv.cmake文件:

include(<path/to/component>/custom_sec_srv.cmake) project(your_project_name)

更多细节可参考tee_basic示例(examples/security/tee/tee_basic)。该示例组件 example_secure_service 中即包含了完整的参考实现:其 sec_srv_tbl_example.yml 定义了两个自定义服务(ID 200/201 的 AES-GCM 加解密),tee_project.cmake 展示了上述三条构建属性的实际写法(并注释说明该文件必须在项目顶层CMakeLists.txt中、project()之前手动 include,以确保变量在 TEE 开始构建前设置完毕)。


单元测试

ESP-TEE 框架使用 ESP-IDF 的 pytest 框架在目标芯片上执行专用单元测试。测试应用覆盖以下模块,位于components目录下的esp_tee/test_apps(具体为 components/esp_tee/test_apps):

  • 安全服务调用接口
  • 中断与异常处理
  • 特权违例
  • 加密操作
  • TEE OTA 更新
  • 安全存储
  • 认证(Attestation)

要运行 TEE 测试套件(假设已配置好 ESP-IDF 环境),例如为某目标芯片执行 TEE 测试套件并使用所有可用的sdkconfig文件,可执行以下步骤:

$ cd components/esp_tee/test_apps/tee_test_fw $ idf.py build $ pytest --target <target>

测试应用目录下提供了多种 CI 配置(如sdkconfig.ci.tee_defaultsdkconfig.ci.tee_otasdkconfig.ci.tee_fuzzing),以及pytest_esp_tee_ut.py等 pytest 入口脚本,便于针对不同安全特性组合执行验证。


小结

ESP-TEE 通过 RISC-V M/U 特权级、PMP/PMA 与 APM 外设的协同,在单芯片上构建了可验证的隔离边界:内部 SRAM 顶部预留 TEE 区域、外部 Flash 保留 TEE 分区并由 PMS 保护、关键外设仅对 TEE 开放。安全服务分发器以ecall为入口完成 REE→TEE→REE 的特权切换,配合默认服务表与__wrap_*包装机制,使 REE 可以安全调用 Flash、中断、密码学等受保护能力。开发者只需遵循"YAML 声明 →_ss_前缀实现 → CMake 注册"三步流程即可扩展自定义安全服务,并可通过tee_test_fw测试框架对新增服务进行完整验证。对于追求更强 Flash 隔离的场景,CONFIG_SECURE_TEE_EXT_FLASH_MEMPROT_SPI1提供了以性能换取完全隔离的取舍方案(读操作 +37.33%、写操作 +11.32% 的实测开销可供工程评估参考)。

【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询