From 53d6c28d3ff42d1a5489750eb6203cee38e6f463 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Mon, 6 Jul 2026 16:33:26 +0800 Subject: [PATCH 1/8] fix(esp_tee): fixes IV length check for TEE AEAD operations --- .../include/esp_tee_sec_storage.h | 12 ++++++++++-- .../tee_sec_storage/tee_sec_storage.c | 18 ++++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/components/esp_tee/subproject/components/tee_sec_storage/include/esp_tee_sec_storage.h b/components/esp_tee/subproject/components/tee_sec_storage/include/esp_tee_sec_storage.h index a889a865ca6..d8c0d36a2f5 100644 --- a/components/esp_tee/subproject/components/tee_sec_storage/include/esp_tee_sec_storage.h +++ b/components/esp_tee/subproject/components/tee_sec_storage/include/esp_tee_sec_storage.h @@ -141,10 +141,14 @@ esp_err_t esp_tee_sec_storage_ecdsa_get_pubkey(const esp_tee_sec_storage_key_cfg * @brief Perform encryption using AES256-GCM with the key from secure storage * * @param[in] ctx Pointer to the AEAD operation context + * @param[out] iv Pointer to the output buffer for the generated initialization vector + * @param[in] iv_len Length of the initialization vector buffer; must be exactly 12 bytes (96-bit IV, per NIST SP 800-38D) * @param[out] tag Pointer to the authentication tag buffer - * @param[in] tag_len Length of the authentication tag + * @param[in] tag_len Length of the authentication tag; must be 12 to 16 bytes (96- to 128-bit tag, per NIST SP 800-38D) * @param[out] output Pointer to the output data buffer * + * @note Non-standard @p iv_len / @p tag_len values are rejected with ESP_ERR_INVALID_SIZE. + * * @return esp_err_t ESP_OK on success, appropriate error code otherwise. */ esp_err_t esp_tee_sec_storage_aead_encrypt(esp_tee_sec_storage_aead_ctx_t *ctx, uint8_t *tag, size_t tag_len, uint8_t *output); @@ -153,10 +157,14 @@ esp_err_t esp_tee_sec_storage_aead_encrypt(esp_tee_sec_storage_aead_ctx_t *ctx, * @brief Perform decryption using AES256-GCM with the key from secure storage * * @param[in] ctx Pointer to the AEAD operation context + * @param[in] iv Pointer to the initialization vector used during encryption + * @param[in] iv_len Length of the initialization vector; must be exactly 12 bytes (96-bit IV, per NIST SP 800-38D) * @param[in] tag Pointer to the authentication tag buffer - * @param[in] tag_len Length of the authentication tag + * @param[in] tag_len Length of the authentication tag; must be 12 to 16 bytes (96- to 128-bit tag, per NIST SP 800-38D) * @param[out] output Pointer to the output data buffer * + * @note Non-standard @p iv_len / @p tag_len values are rejected with ESP_ERR_INVALID_SIZE. + * * @return esp_err_t ESP_OK on success, appropriate error code otherwise. */ esp_err_t esp_tee_sec_storage_aead_decrypt(const esp_tee_sec_storage_aead_ctx_t *ctx, const uint8_t *tag, size_t tag_len, uint8_t *output); diff --git a/components/esp_tee/subproject/components/tee_sec_storage/tee_sec_storage.c b/components/esp_tee/subproject/components/tee_sec_storage/tee_sec_storage.c index 7704ad1663d..f2720957921 100644 --- a/components/esp_tee/subproject/components/tee_sec_storage/tee_sec_storage.c +++ b/components/esp_tee/subproject/components/tee_sec_storage/tee_sec_storage.c @@ -35,8 +35,10 @@ #define AES256_KEY_LEN 32 #define AES256_KEY_BITS (AES256_KEY_LEN * 8) -#define AES256_DEFAULT_IV_LEN 16 -#define AES256_GCM_IV_LEN (AES_GCM_SUPPORTED_IV_LEN) +#define AES256_GCM_IV_LEN 12 +#define AES256_GCM_TAG_LEN_MIN 12 /* NIST SP800-38D general-use minimum (96-bit tag) */ +#define AES256_GCM_TAG_LEN_MAX 16 /* full GCM tag (128-bit) */ +#define ECDSA_SECP384R1_KEY_LEN 48 #define ECDSA_SECP256R1_KEY_LEN 32 #define ECDSA_SECP192R1_KEY_LEN 24 @@ -558,8 +560,16 @@ static esp_err_t tee_sec_storage_crypt_common(const char *key_id, const uint8_t return ESP_ERR_INVALID_ARG; } - if (len == 0 || tag_len == 0 || iv_len != AES256_GCM_IV_LEN) { - ESP_LOGE(TAG, "Invalid input/tag/iv length"); + if (len == 0) { + ESP_LOGE(TAG, "Invalid input length"); + return ESP_ERR_INVALID_SIZE; + } + + /* Enforce standard AES-GCM parameters */ + if (iv_len != AES256_GCM_IV_LEN || + tag_len < AES256_GCM_TAG_LEN_MIN || tag_len > AES256_GCM_TAG_LEN_MAX) { + ESP_LOGE(TAG, "Non-standard GCM iv_len(%u)/tag_len(%u) rejected", + (unsigned)iv_len, (unsigned)tag_len); return ESP_ERR_INVALID_SIZE; } From 5091e7c8745fd5da3af9f25c92b23f03e2d004b8 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Tue, 7 Jul 2026 11:00:40 +0800 Subject: [PATCH 2/8] fix(esp_tee): enforce MMU-map vaddr validity at the REE->TEE boundary --- .../main/core/esp_secure_services_iram.c | 48 ++++++++++++++++--- components/hal/include/hal/mmu_hal.h | 12 +++++ components/hal/mmu_hal.c | 5 ++ 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/components/esp_tee/subproject/main/core/esp_secure_services_iram.c b/components/esp_tee/subproject/main/core/esp_secure_services_iram.c index cb0e7b04d7d..e2dae3d090e 100644 --- a/components/esp_tee/subproject/main/core/esp_secure_services_iram.c +++ b/components/esp_tee/subproject/main/core/esp_secure_services_iram.c @@ -8,6 +8,7 @@ #include "esp_err.h" #include "esp_log.h" +#include "esp_macros.h" #include "esp_fault.h" #include "hal/mmu_types.h" @@ -36,6 +37,9 @@ static __attribute__((unused)) const char *TAG = "esp_tee_sec_srv_iram"; +#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) +#define ALIGN_DOWN(num, align) ((num) & ~((align) - 1)) + /* ---------------------------------------------- Interrupts ------------------------------------------------- */ #if SOC_INT_CLIC_SUPPORTED @@ -272,11 +276,37 @@ esp_err_t _ss_esp_tee_sec_storage_ecdsa_sign_pbkdf2(const esp_tee_sec_storage_pb /* ---------------------------------------------- MMU HAL ------------------------------------------------- */ +/* Gates for REE-supplied external-flash address ranges. The HAL maps whole pages, + * so validate the page-rounded span it will actually touch (not the raw byte len) + * and require page-aligned addresses, so a sub-page or misaligned request cannot + * smuggle in an adjacent TEE page. mmu_hal_check_valid_ext_vaddr_region() must be + * enforced HERE, at the boundary: it rejects out-of-window (aliased) vaddrs. */ +static bool tee_ree_ext_vaddr_ok(uint32_t mmu_id, uint32_t vaddr, uint32_t len) +{ + uint32_t page = mmu_hal_pages_to_bytes(mmu_id, 1); + uint32_t map_len = ALIGN_UP(len, page); + + return (len != 0 && map_len >= len && (vaddr % page == 0) && + mmu_hal_check_valid_ext_vaddr_region(mmu_id, vaddr, map_len, + MMU_VADDR_DATA | MMU_VADDR_INSTRUCTION) && + !esp_tee_flash_check_vrange_in_tee_region(vaddr, map_len)); +} + +static bool tee_ree_ext_paddr_ok(uint32_t mmu_id, uint32_t paddr, uint32_t len) +{ + uint32_t page = mmu_hal_pages_to_bytes(mmu_id, 1); + uint32_t map_len = ALIGN_UP(len, page); + + return (len != 0 && map_len >= len && (paddr % page == 0) && + mmu_hal_check_valid_paddr_region(mmu_id, paddr, map_len) && + !esp_tee_flash_check_prange_in_tee_region(paddr, map_len)); +} + void _ss_mmu_hal_map_region(uint32_t mmu_id, mmu_target_t mem_type, uint32_t vaddr, uint32_t paddr, uint32_t len, uint32_t *out_len) { - bool valid_addr = (!esp_tee_flash_check_vrange_in_tee_region(vaddr, len) && - !esp_tee_flash_check_prange_in_tee_region(paddr, len) && + bool valid_addr = (tee_ree_ext_vaddr_ok(mmu_id, vaddr, len) && + tee_ree_ext_paddr_ok(mmu_id, paddr, len) && esp_tee_buf_in_ree(out_len, sizeof(uint32_t))); if (!valid_addr) { @@ -290,20 +320,23 @@ void _ss_mmu_hal_map_region(uint32_t mmu_id, mmu_target_t mem_type, uint32_t vad void _ss_mmu_hal_unmap_region(uint32_t mmu_id, uint32_t vaddr, uint32_t len) { - bool vaddr_chk = esp_tee_flash_check_vrange_in_tee_region(vaddr, len); + bool valid_addr = tee_ree_ext_vaddr_ok(mmu_id, vaddr, len); - if (vaddr_chk) { + if (!valid_addr) { ESP_LOGD(TAG, "[%s] Illegal flash access at 0x%08x", __func__, vaddr); return; } - ESP_FAULT_ASSERT(!vaddr_chk); + ESP_FAULT_ASSERT(valid_addr); mmu_hal_unmap_region(mmu_id, vaddr, len); } bool _ss_mmu_hal_vaddr_to_paddr(uint32_t mmu_id, uint32_t vaddr, uint32_t *out_paddr, mmu_target_t *out_target) { - bool valid_addr = (!esp_tee_flash_check_vaddr_in_tee_region(vaddr) && + /* Same aliasing gate as map/unmap; translation itself is page-granular and + * vaddr need not be aligned, so validate the page containing it. */ + uint32_t page = mmu_hal_pages_to_bytes(mmu_id, 1); + bool valid_addr = (tee_ree_ext_vaddr_ok(mmu_id, ALIGN_DOWN(vaddr, page), 1) && esp_tee_buf_in_ree(out_paddr, sizeof(uint32_t)) && esp_tee_buf_in_ree(out_target, sizeof(mmu_target_t))); @@ -317,7 +350,8 @@ bool _ss_mmu_hal_vaddr_to_paddr(uint32_t mmu_id, uint32_t vaddr, uint32_t *out_p bool _ss_mmu_hal_paddr_to_vaddr(uint32_t mmu_id, uint32_t paddr, mmu_target_t target, mmu_vaddr_t type, uint32_t *out_vaddr) { - bool valid_addr = (!esp_tee_flash_check_paddr_in_tee_region(paddr) && + bool valid_addr = (mmu_hal_check_valid_paddr_region(mmu_id, paddr, 1) && + !esp_tee_flash_check_paddr_in_tee_region(paddr) && esp_tee_buf_in_ree(out_vaddr, sizeof(uint32_t))); if (!valid_addr) { diff --git a/components/hal/include/hal/mmu_hal.h b/components/hal/include/hal/mmu_hal.h index 9e8d208d1a0..0dd9d038eb3 100644 --- a/components/hal/include/hal/mmu_hal.h +++ b/components/hal/include/hal/mmu_hal.h @@ -132,6 +132,18 @@ bool mmu_hal_paddr_to_vaddr(uint32_t mmu_id, uint32_t paddr, mmu_target_t target */ bool mmu_hal_check_valid_ext_vaddr_region(uint32_t mmu_id, uint32_t vaddr_start, uint32_t len, mmu_vaddr_t type); +/** + * Check if the paddr region is valid + * + * @param mmu_id MMU ID + * @param paddr_start start of the physical address + * @param len length, in bytes + * + * @return + * True for valid + */ +bool mmu_hal_check_valid_paddr_region(uint32_t mmu_id, uint32_t paddr_start, uint32_t len); + #if SOC_MMU_PER_EXT_MEM_TARGET /** * Get MMU ID from MMU target diff --git a/components/hal/mmu_hal.c b/components/hal/mmu_hal.c index e102c7759b3..1d7cb4b44d1 100644 --- a/components/hal/mmu_hal.c +++ b/components/hal/mmu_hal.c @@ -186,6 +186,11 @@ bool mmu_hal_check_valid_ext_vaddr_region(uint32_t mmu_id, uint32_t vaddr_start, return mmu_ll_check_valid_ext_vaddr_region(mmu_id, vaddr_start, len, type); } +bool mmu_hal_check_valid_paddr_region(uint32_t mmu_id, uint32_t paddr_start, uint32_t len) +{ + return mmu_ll_check_valid_paddr_region(mmu_id, paddr_start, len); +} + #if SOC_MMU_PER_EXT_MEM_TARGET uint32_t mmu_hal_get_id_from_target(mmu_target_t target) { From b23211a2872e6c95438fce3ec44281f6c857851a Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Fri, 10 Jul 2026 17:26:07 +0800 Subject: [PATCH 3/8] fix(esp_tee): ensure hal assert is enabled for tee builds --- .../esp_tee/include/private/esp_tee_binary.h | 16 ++++++++++++++++ .../main/core/esp_secure_services_iram.c | 7 ------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/components/esp_tee/include/private/esp_tee_binary.h b/components/esp_tee/include/private/esp_tee_binary.h index 8845462e6a5..8fa52fed464 100644 --- a/components/esp_tee/include/private/esp_tee_binary.h +++ b/components/esp_tee/include/private/esp_tee_binary.h @@ -48,6 +48,22 @@ extern "C" { #error "CONFIG_SECURE_TEE_INTR_STACK_SIZE must be 16-byte (0x10) aligned" #endif +#if ((CONFIG_SECURE_TEE_IROM_SIZE) % SOC_MMU_PAGE_SIZE) +#error "CONFIG_SECURE_TEE_IROM_SIZE must be a multiple of SOC_MMU_PAGE_SIZE" +#endif + +#if ((CONFIG_SECURE_TEE_DROM_SIZE) % SOC_MMU_PAGE_SIZE) +#error "CONFIG_SECURE_TEE_DROM_SIZE must be a multiple of SOC_MMU_PAGE_SIZE" +#endif + +/* With HAL assertions disabled (level 0), a failed HAL_ASSERT() expands to + * __builtin_unreachable(): the compiler then optimizes assuming the asserted + * preconditions always hold, turning any unvalidated HAL input into undefined + * behavior. The TEE must never be built this way. */ +#if CONFIG_SECURE_ENABLE_TEE && (CONFIG_HAL_DEFAULT_ASSERTION_LEVEL < 1) +#error "ESP-TEE requires HAL assertions (CONFIG_HAL_DEFAULT_ASSERTION_LEVEL >= 1)" +#endif + /* TEE Secure Storage partition label and NVS namespace */ #define ESP_TEE_SEC_STG_PART_LABEL "secure_storage" #define ESP_TEE_SEC_STG_NVS_NAMESPACE "tee_sec_stg_ns" diff --git a/components/esp_tee/subproject/main/core/esp_secure_services_iram.c b/components/esp_tee/subproject/main/core/esp_secure_services_iram.c index e2dae3d090e..c0e46323c99 100644 --- a/components/esp_tee/subproject/main/core/esp_secure_services_iram.c +++ b/components/esp_tee/subproject/main/core/esp_secure_services_iram.c @@ -276,11 +276,6 @@ esp_err_t _ss_esp_tee_sec_storage_ecdsa_sign_pbkdf2(const esp_tee_sec_storage_pb /* ---------------------------------------------- MMU HAL ------------------------------------------------- */ -/* Gates for REE-supplied external-flash address ranges. The HAL maps whole pages, - * so validate the page-rounded span it will actually touch (not the raw byte len) - * and require page-aligned addresses, so a sub-page or misaligned request cannot - * smuggle in an adjacent TEE page. mmu_hal_check_valid_ext_vaddr_region() must be - * enforced HERE, at the boundary: it rejects out-of-window (aliased) vaddrs. */ static bool tee_ree_ext_vaddr_ok(uint32_t mmu_id, uint32_t vaddr, uint32_t len) { uint32_t page = mmu_hal_pages_to_bytes(mmu_id, 1); @@ -333,8 +328,6 @@ void _ss_mmu_hal_unmap_region(uint32_t mmu_id, uint32_t vaddr, uint32_t len) bool _ss_mmu_hal_vaddr_to_paddr(uint32_t mmu_id, uint32_t vaddr, uint32_t *out_paddr, mmu_target_t *out_target) { - /* Same aliasing gate as map/unmap; translation itself is page-granular and - * vaddr need not be aligned, so validate the page containing it. */ uint32_t page = mmu_hal_pages_to_bytes(mmu_id, 1); bool valid_addr = (tee_ree_ext_vaddr_ok(mmu_id, ALIGN_DOWN(vaddr, page), 1) && esp_tee_buf_in_ree(out_paddr, sizeof(uint32_t)) && From 6a605263e80403425a6f12144a13408001f58512 Mon Sep 17 00:00:00 2001 From: Laukik Hase Date: Mon, 6 Jul 2026 13:48:12 +0530 Subject: [PATCH 4/8] fix(esp_tee): Miscellaneous optimizations and fixes Closes https://github.com/espressif/esp-idf/issues/18591 --- .../bootloader_flash/src/bootloader_flash.c | 2 +- components/esp_tee/CMakeLists.txt | 2 +- components/esp_tee/subproject/main/common/multi_heap.c | 6 +++++- components/esp_tee/subproject/main/core/esp_tee_init.c | 8 +++++++- .../esp_tee/test_apps/tee_cli_app/sdkconfig.ci.sb_fe | 4 ++-- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/components/bootloader_support/bootloader_flash/src/bootloader_flash.c b/components/bootloader_support/bootloader_flash/src/bootloader_flash.c index b21b9d4f04d..0a4a9edf3de 100644 --- a/components/bootloader_support/bootloader_flash/src/bootloader_flash.c +++ b/components/bootloader_support/bootloader_flash/src/bootloader_flash.c @@ -426,8 +426,8 @@ void bootloader_munmap(const void *mapping) mmu_hal_unmap_all(); #else cache_hal_suspend(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_ALL); - mmu_hal_unmap_region(0, FLASH_MMAP_VADDR, current_mapped_size); cache_hal_invalidate_addr(FLASH_MMAP_VADDR, current_mapped_size); + mmu_hal_unmap_region(0, FLASH_MMAP_VADDR, current_mapped_size); cache_hal_resume(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_ALL); #endif #endif diff --git a/components/esp_tee/CMakeLists.txt b/components/esp_tee/CMakeLists.txt index fc58bbed717..777187b9a0e 100644 --- a/components/esp_tee/CMakeLists.txt +++ b/components/esp_tee/CMakeLists.txt @@ -9,7 +9,7 @@ idf_build_get_property(target IDF_TARGET) # ESP-TEE is currently supported only on the ESP32-C6, H2 and C5 SoCs set(SUPPORTED_TARGETS "esp32c6" "esp32h2" "esp32c5") if(NOT target IN_LIST SUPPORTED_TARGETS) - message(STATUS "ESP-TEE is currently supported only on the ${SUPPORTED_TARGETS} SoCs") + # ESP-TEE Kconfig is gated on the supported targets; nothing to register elsewhere. return() endif() diff --git a/components/esp_tee/subproject/main/common/multi_heap.c b/components/esp_tee/subproject/main/common/multi_heap.c index ade9f35f759..97d613d6fe3 100644 --- a/components/esp_tee/subproject/main/common/multi_heap.c +++ b/components/esp_tee/subproject/main/common/multi_heap.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "esp_rom_tlsf.h" #include "esp_rom_sys.h" #include "tlsf_block_functions.h" @@ -63,6 +64,9 @@ esp_err_t esp_tee_heap_init(void *start_ptr, size_t size) return ESP_ERR_INVALID_SIZE; } + /* Zeroize the entire region before registering it as the TEE heap*/ + memset(start_ptr, 0, size); + #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2 void *heap = tlsf_create_with_pool(start_ptr + sizeof(heap_t), usable_size); size_t overhead = tlsf_size(); @@ -229,7 +233,7 @@ void esp_tee_heap_dump_info(void) /* Definitions for functions from the heap component, used in files shared with ESP-IDF */ -void *heap_caps_malloc(size_t alignment, size_t size, uint32_t caps) +void *heap_caps_malloc(size_t size, uint32_t caps) { (void) caps; return esp_tee_heap_malloc(size); diff --git a/components/esp_tee/subproject/main/core/esp_tee_init.c b/components/esp_tee/subproject/main/core/esp_tee_init.c index d1e6ca662cb..d427f71d6c9 100644 --- a/components/esp_tee/subproject/main/core/esp_tee_init.c +++ b/components/esp_tee/subproject/main/core/esp_tee_init.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -24,6 +24,9 @@ /* TEE symbols */ extern uint32_t _tee_stack; +extern uint32_t _tee_stack_bottom; +extern uint32_t _tee_intr_stack; +extern uint32_t _tee_intr_stack_bottom; extern uint32_t _tee_bss_start; extern uint32_t _tee_bss_end; extern uint32_t _tee_s_intr_handler; @@ -117,6 +120,9 @@ void __attribute__((noreturn)) esp_tee_init(uint32_t ree_entry_addr, uint32_t re { /* Clear BSS */ memset(&_tee_bss_start, 0, (&_tee_bss_end - &_tee_bss_start) * sizeof(_tee_bss_start)); + /* Clear the TEE stack and interrupt stack */ + memset(&_tee_stack_bottom, 0, (&_tee_stack - &_tee_stack_bottom) * sizeof(_tee_stack_bottom)); + memset(&_tee_intr_stack_bottom, 0, (&_tee_intr_stack - &_tee_intr_stack_bottom) * sizeof(_tee_intr_stack_bottom)); static uint32_t btld_sp; diff --git a/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.sb_fe b/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.sb_fe index ab5093cb612..70b543c2f95 100644 --- a/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.sb_fe +++ b/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.sb_fe @@ -4,8 +4,8 @@ # Increasing TEE I/DRAM sizes # 38KB CONFIG_SECURE_TEE_IRAM_SIZE=0x9800 -# 18KB -CONFIG_SECURE_TEE_DRAM_SIZE=0x4800 +# 18.5KB +CONFIG_SECURE_TEE_DRAM_SIZE=0x4A00 # Security features - build-only configuration CONFIG_PARTITION_TABLE_OFFSET=0xf000 From a1bc64d14ef1bc710b77f869a4263ab8e53f902c Mon Sep 17 00:00:00 2001 From: Laukik Hase Date: Fri, 3 Jul 2026 19:20:14 +0530 Subject: [PATCH 5/8] feat(esp_tee): Restrict REE access to TEE-owned secure storage keys --- .../scripts/esp_tee_sec_stg_keygen/README.md | 4 +- .../esp_tee_sec_stg_keygen.py | 10 +++ .../attestation/esp_att_utils_crypto.c | 2 + .../include/esp_tee_sec_storage.h | 24 ++++++- .../tee_sec_storage/tee_sec_storage.c | 23 +++++++ .../main/core/esp_secure_services.c | 15 +++-- .../main/core/esp_secure_services_iram.c | 54 +++++++-------- .../tee_cli_app/main/tee_srv_sec_str.c | 2 +- .../test_apps/tee_cli_app/pytest_tee_cli.py | 6 +- .../esp_tee/test_apps/tee_test_fw/conftest.py | 2 + .../tee_test_fw/main/test_esp_tee_sec_stg.c | 65 +++++++++++++++++-- docs/en/security/tee/tee-attestation.rst | 4 ++ .../tee/tee_secure_storage/main/tee_main.c | 4 +- 13 files changed, 170 insertions(+), 45 deletions(-) diff --git a/components/esp_tee/scripts/esp_tee_sec_stg_keygen/README.md b/components/esp_tee/scripts/esp_tee_sec_stg_keygen/README.md index d32d46ebcbc..41615118f35 100644 --- a/components/esp_tee/scripts/esp_tee_sec_stg_keygen/README.md +++ b/components/esp_tee/scripts/esp_tee_sec_stg_keygen/README.md @@ -18,6 +18,8 @@ options: -o, --output OUTPUT output binary file name -i, --input INPUT input key file (.pem for ecdsa, .bin for aes) --write-once make key persistent - cannot be modified or deleted once written + --tee-only mark key as owned exclusively by the TEE - the REE cannot use, generate or clear it + -h, --help Show this message and exit ``` ### ECDSA Keys @@ -31,7 +33,7 @@ python esp_tee_sec_stg_keygen.py -k ecdsa_p192 -o ecdsa_p192_k0.bin ```bash openssl ecparam -name prime256v1 -genkey -noout -out ecdsa_p256.pem -python esp_tee_sec_stg_keygen.py -k ecdsa_p256 -o ecdsa_p256_k1.bin -i ecdsa_p256.pem --write-once +python esp_tee_sec_stg_keygen.py -k ecdsa_p256 -o ecdsa_p256_k1.bin -i ecdsa_p256.pem --write-once --tee-only ``` ### AES-256 Key diff --git a/components/esp_tee/scripts/esp_tee_sec_stg_keygen/esp_tee_sec_stg_keygen.py b/components/esp_tee/scripts/esp_tee_sec_stg_keygen/esp_tee_sec_stg_keygen.py index 7c1f4818d3d..80b57e9e9a8 100644 --- a/components/esp_tee/scripts/esp_tee_sec_stg_keygen/esp_tee_sec_stg_keygen.py +++ b/components/esp_tee/scripts/esp_tee_sec_stg_keygen/esp_tee_sec_stg_keygen.py @@ -32,6 +32,7 @@ class KeyType(Enum): class Flags(IntFlag): NONE = 0x00000000 WRITE_ONCE = 0x00000001 + TEE_ONLY = 0x00000002 # === Key Generators === @@ -113,6 +114,11 @@ def parse_args() -> argparse.Namespace: action='store_true', help='make key persistent - cannot be modified or deleted once written', ) + parser.add_argument( + '--tee-only', + action='store_true', + help='mark key as owned exclusively by the TEE - the REE cannot use, generate or clear it', + ) return parser.parse_args() @@ -123,12 +129,16 @@ def main() -> None: flags = Flags.NONE if args.write_once: flags |= Flags.WRITE_ONCE + if args.tee_only: + flags |= Flags.TEE_ONLY print(f'[+] Generating key of type: {key_type.name} (value: {key_type.value})') if args.input: print(f'[+] Using user-provided key file: {args.input}') if args.write_once: print('[+] WRITE_ONCE flag is set') + if args.tee_only: + print('[+] TEE_ONLY flag is set') key_data = generate_key_data(key_type, flags, args.input) diff --git a/components/esp_tee/subproject/components/attestation/esp_att_utils_crypto.c b/components/esp_tee/subproject/components/attestation/esp_att_utils_crypto.c index ad0a9a9c589..2827aacd3b6 100644 --- a/components/esp_tee/subproject/components/attestation/esp_att_utils_crypto.c +++ b/components/esp_tee/subproject/components/attestation/esp_att_utils_crypto.c @@ -46,6 +46,8 @@ static esp_err_t gen_ecdsa_keypair_secp256r1(esp_att_ecdsa_keypair_t *keypair) esp_tee_sec_storage_key_cfg_t key_cfg = { .id = (const char *)(ESP_ATT_TK_KEY_ID), .type = ESP_SEC_STG_KEY_ECDSA_SECP256R1, + /* The attestation key must never be usable from the REE */ + .flags = SEC_STORAGE_FLAG_TEE_ONLY, }; esp_err_t err = esp_tee_sec_storage_gen_key(&key_cfg); diff --git a/components/esp_tee/subproject/components/tee_sec_storage/include/esp_tee_sec_storage.h b/components/esp_tee/subproject/components/tee_sec_storage/include/esp_tee_sec_storage.h index d8c0d36a2f5..ad12969df6f 100644 --- a/components/esp_tee/subproject/components/tee_sec_storage/include/esp_tee_sec_storage.h +++ b/components/esp_tee/subproject/components/tee_sec_storage/include/esp_tee_sec_storage.h @@ -16,12 +16,19 @@ extern "C" { #include "esp_err.h" #include "esp_bit_defs.h" -#define MAX_ECDSA_SUPPORTED_KEY_LEN 32 /*!< Maximum supported size for the ECDSA key */ +#include "sdkconfig.h" + +#if CONFIG_SECURE_TEE_SEC_STG_SUPPORT_SECP384R1_SIGN +#define MAX_ECDSA_SUPPORTED_KEY_LEN 48 /*!< Maximum supported size for the ECDSA key (SECP384R1) */ +#else +#define MAX_ECDSA_SUPPORTED_KEY_LEN 32 /*!< Maximum supported size for the ECDSA key (SECP256R1) */ +#endif /* CONFIG_SECURE_TEE_SEC_STG_SUPPORT_SECP384R1_SIGN */ #define MAX_AES_SUPPORTED_KEY_LEN 32 /*!< Maximum supported size for the AES key */ #define AES_GCM_SUPPORTED_IV_LEN 12 /*!< Supported IV length for AES-GCM operations */ #define SEC_STORAGE_FLAG_NONE 0 /*!< No flags */ #define SEC_STORAGE_FLAG_WRITE_ONCE BIT(0) /*!< Data can only be written once */ +#define SEC_STORAGE_FLAG_TEE_ONLY BIT(1) /*!< Key is owned exclusively by the TEE */ /** * @brief Enum to represent the type of key stored in the secure storage @@ -94,6 +101,21 @@ typedef struct { * @return esp_err_t ESP_OK on success, appropriate error code otherwise. */ esp_err_t esp_tee_sec_storage_init(void); + +/** + * @brief Check whether a key ID is owned exclusively by the TEE + * + * A key is TEE-owned if either: + * - it refers to the reserved TEE attestation key + * (`CONFIG_SECURE_TEE_ATT_KEY_STR_ID`); this also blocks the REE from + * "squatting" the ID before the TEE creates the key, or + * - the stored key carries the ::SEC_STORAGE_FLAG_TEE_ONLY flag. + * + * @param key_id NULL-terminated key identifier string (may be NULL) + * + * @return true if the key is TEE-owned (REE access must be denied), false otherwise + */ +bool esp_tee_sec_storage_is_key_tee_owned(const char *key_id); #endif /** diff --git a/components/esp_tee/subproject/components/tee_sec_storage/tee_sec_storage.c b/components/esp_tee/subproject/components/tee_sec_storage/tee_sec_storage.c index f2720957921..7ff6655a628 100644 --- a/components/esp_tee/subproject/components/tee_sec_storage/tee_sec_storage.c +++ b/components/esp_tee/subproject/components/tee_sec_storage/tee_sec_storage.c @@ -211,6 +211,29 @@ static esp_err_t secure_storage_read(const char *key_id, void *data, size_t *len return nvs_get_blob(tee_nvs_hdl, key_id, data, len); } +bool esp_tee_sec_storage_is_key_tee_owned(const char *key_id) +{ + if (key_id == NULL) { + return false; + } + + bool is_att_key = false, is_tee_only = false; + esp_err_t err = ESP_FAIL; + +#if CONFIG_SECURE_TEE_ATTESTATION + is_att_key = (strncmp(key_id, CONFIG_SECURE_TEE_ATT_KEY_STR_ID, NVS_KEY_NAME_MAX_SIZE) == 0); +#endif + + sec_stg_key_t keyctx = {}; + size_t keyctx_len = sizeof(keyctx); + + err = secure_storage_read(key_id, (void *)&keyctx, &keyctx_len); + is_tee_only = (err == ESP_OK) && ((keyctx.flags & SEC_STORAGE_FLAG_TEE_ONLY) != 0); + mbedtls_platform_zeroize(&keyctx, sizeof(keyctx)); + + return (is_att_key || is_tee_only); +} + /* ---------------------------------------------- Interface APIs ------------------------------------------------- */ esp_err_t esp_tee_sec_storage_init(void) diff --git a/components/esp_tee/subproject/main/core/esp_secure_services.c b/components/esp_tee/subproject/main/core/esp_secure_services.c index b9efb282ce2..9e163b48af7 100644 --- a/components/esp_tee/subproject/main/core/esp_secure_services.c +++ b/components/esp_tee/subproject/main/core/esp_secure_services.c @@ -545,17 +545,24 @@ int _ss_esp_tee_ota_end(void) */ esp_err_t _ss_esp_tee_sec_storage_clear_key(const char *key_id) { + bool valid_arg = !esp_tee_sec_storage_is_key_tee_owned(key_id); + if (!valid_arg) { + return ESP_ERR_INVALID_ARG; + } + ESP_FAULT_ASSERT(valid_arg); + return esp_tee_sec_storage_clear_key(key_id); } esp_err_t _ss_esp_tee_sec_storage_gen_key(const esp_tee_sec_storage_key_cfg_t *cfg) { - bool valid_addr = esp_tee_buf_in_ree(cfg, sizeof(esp_tee_sec_storage_key_cfg_t)); - - if (!valid_addr) { + bool valid_arg = esp_tee_buf_in_ree(cfg, sizeof(esp_tee_sec_storage_key_cfg_t)) && + !(cfg->flags & SEC_STORAGE_FLAG_TEE_ONLY) && + !esp_tee_sec_storage_is_key_tee_owned(cfg->id); + if (!valid_arg) { return ESP_ERR_INVALID_ARG; } - ESP_FAULT_ASSERT(valid_addr); + ESP_FAULT_ASSERT(valid_arg); return esp_tee_sec_storage_gen_key(cfg); } diff --git a/components/esp_tee/subproject/main/core/esp_secure_services_iram.c b/components/esp_tee/subproject/main/core/esp_secure_services_iram.c index c0e46323c99..a102b2b2112 100644 --- a/components/esp_tee/subproject/main/core/esp_secure_services_iram.c +++ b/components/esp_tee/subproject/main/core/esp_secure_services_iram.c @@ -193,67 +193,67 @@ void _ss_wdt_hal_deinit(wdt_hal_context_t *hal) */ esp_err_t _ss_esp_tee_sec_storage_ecdsa_sign(const esp_tee_sec_storage_key_cfg_t *cfg, const uint8_t *hash, size_t hlen, esp_tee_sec_storage_ecdsa_sign_t *out_sign) { - bool valid_addr = (esp_tee_buf_in_ree(cfg, sizeof(esp_tee_sec_storage_key_cfg_t)) && - esp_tee_buf_in_ree(hash, hlen) && - esp_tee_buf_in_ree(out_sign, sizeof(esp_tee_sec_storage_ecdsa_sign_t))); - - if (!valid_addr) { + bool valid_arg = (esp_tee_buf_in_ree(cfg, sizeof(esp_tee_sec_storage_key_cfg_t)) && + esp_tee_buf_in_ree(hash, hlen) && + esp_tee_buf_in_ree(out_sign, sizeof(esp_tee_sec_storage_ecdsa_sign_t)) && + !esp_tee_sec_storage_is_key_tee_owned(cfg->id)); + if (!valid_arg) { return ESP_ERR_INVALID_ARG; } - ESP_FAULT_ASSERT(valid_addr); + ESP_FAULT_ASSERT(valid_arg); return esp_tee_sec_storage_ecdsa_sign(cfg, hash, hlen, out_sign); } esp_err_t _ss_esp_tee_sec_storage_ecdsa_get_pubkey(const esp_tee_sec_storage_key_cfg_t *cfg, esp_tee_sec_storage_ecdsa_pubkey_t *out_pubkey) { - bool valid_addr = (esp_tee_buf_in_ree(cfg, sizeof(esp_tee_sec_storage_key_cfg_t)) && - esp_tee_buf_in_ree(out_pubkey, sizeof(esp_tee_sec_storage_ecdsa_pubkey_t))); - - if (!valid_addr) { + bool valid_arg = (esp_tee_buf_in_ree(cfg, sizeof(esp_tee_sec_storage_key_cfg_t)) && + esp_tee_buf_in_ree(out_pubkey, sizeof(esp_tee_sec_storage_ecdsa_pubkey_t)) && + !esp_tee_sec_storage_is_key_tee_owned(cfg->id)); + if (!valid_arg) { return ESP_ERR_INVALID_ARG; } - ESP_FAULT_ASSERT(valid_addr); + ESP_FAULT_ASSERT(valid_arg); return esp_tee_sec_storage_ecdsa_get_pubkey(cfg, out_pubkey); } esp_err_t _ss_esp_tee_sec_storage_aead_encrypt(esp_tee_sec_storage_aead_ctx_t *ctx, uint8_t *tag, size_t tag_len, uint8_t *output) { - bool valid_addr = (esp_tee_buf_in_ree(ctx, sizeof(esp_tee_sec_storage_aead_ctx_t)) && - esp_tee_buf_in_ree(ctx->input, ctx->input_len) && - esp_tee_buf_in_ree(ctx->iv, AES_GCM_SUPPORTED_IV_LEN) && - esp_tee_buf_in_ree(tag, tag_len) && - esp_tee_buf_in_ree(output, ctx->input_len)); + bool valid_arg = (esp_tee_buf_in_ree(ctx, sizeof(esp_tee_sec_storage_aead_ctx_t)) && + esp_tee_buf_in_ree(ctx->input, ctx->input_len) && + esp_tee_buf_in_ree(tag, tag_len) && + esp_tee_buf_in_ree(output, ctx->input_len) && + !esp_tee_sec_storage_is_key_tee_owned(ctx->key_id)); if (ctx->aad_len != 0) { - valid_addr &= esp_tee_buf_in_ree(ctx->aad, ctx->aad_len); + valid_arg &= esp_tee_buf_in_ree(ctx->aad, ctx->aad_len); } - if (!valid_addr) { + if (!valid_arg) { return ESP_ERR_INVALID_ARG; } - ESP_FAULT_ASSERT(valid_addr); + ESP_FAULT_ASSERT(valid_arg); return esp_tee_sec_storage_aead_encrypt(ctx, tag, tag_len, output); } esp_err_t _ss_esp_tee_sec_storage_aead_decrypt(const esp_tee_sec_storage_aead_ctx_t *ctx, const uint8_t *tag, size_t tag_len, uint8_t *output) { - bool valid_addr = (esp_tee_buf_in_ree(ctx, sizeof(esp_tee_sec_storage_aead_ctx_t)) && - esp_tee_buf_in_ree(ctx->input, ctx->input_len) && - esp_tee_buf_in_ree(ctx->iv, AES_GCM_SUPPORTED_IV_LEN) && - esp_tee_buf_in_ree(tag, tag_len) && - esp_tee_buf_in_ree(output, ctx->input_len)); + bool valid_arg = (esp_tee_buf_in_ree(ctx, sizeof(esp_tee_sec_storage_aead_ctx_t)) && + esp_tee_buf_in_ree(ctx->input, ctx->input_len) && + esp_tee_buf_in_ree(tag, tag_len) && + esp_tee_buf_in_ree(output, ctx->input_len) && + !esp_tee_sec_storage_is_key_tee_owned(ctx->key_id)); if (ctx->aad_len != 0) { - valid_addr &= esp_tee_buf_in_ree(ctx->aad, ctx->aad_len); + valid_arg &= esp_tee_buf_in_ree(ctx->aad, ctx->aad_len); } - if (!valid_addr) { + if (!valid_arg) { return ESP_ERR_INVALID_ARG; } - ESP_FAULT_ASSERT(valid_addr); + ESP_FAULT_ASSERT(valid_arg); return esp_tee_sec_storage_aead_decrypt(ctx, tag, tag_len, output); } diff --git a/components/esp_tee/test_apps/tee_cli_app/main/tee_srv_sec_str.c b/components/esp_tee/test_apps/tee_cli_app/main/tee_srv_sec_str.c index 8a093ff0009..f1d88926671 100644 --- a/components/esp_tee/test_apps/tee_cli_app/main/tee_srv_sec_str.c +++ b/components/esp_tee/test_apps/tee_cli_app/main/tee_srv_sec_str.c @@ -219,7 +219,7 @@ static int tee_sec_stg_gen_key(int argc, char **argv) err = esp_tee_sec_storage_clear_key(cfg.id); if (err != ESP_OK && err != ESP_ERR_NOT_FOUND) { - ESP_LOGE(TAG, "Failed to clear key %d!", cfg.id); + ESP_LOGE(TAG, "Failed to clear key %s!", cfg.id); goto exit; } diff --git a/components/esp_tee/test_apps/tee_cli_app/pytest_tee_cli.py b/components/esp_tee/test_apps/tee_cli_app/pytest_tee_cli.py index 0f5d32cd204..36ae373d3bc 100644 --- a/components/esp_tee/test_apps/tee_cli_app/pytest_tee_cli.py +++ b/components/esp_tee/test_apps/tee_cli_app/pytest_tee_cli.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import hashlib import http.server @@ -134,10 +134,6 @@ def test_tee_cli_attestation(dut: Dut) -> None: dut.expect('ESP-TEE: Secure services demonstration', timeout=30) time.sleep(1) - att_key_id = dut.app.sdkconfig.get('SECURE_TEE_ATT_KEY_STR_ID') - dut.write(f'tee_sec_stg_gen_key {att_key_id} 1') - dut.expect(r'Generated ECDSA_SECP256R1 key with ID (\S+)', timeout=30) - # Get the Entity Attestation token from TEE and verify its signature dut.write('tee_att_info') dut.expect(r'Attestation token - Length: (\d+)', timeout=30) diff --git a/components/esp_tee/test_apps/tee_test_fw/conftest.py b/components/esp_tee/test_apps/tee_test_fw/conftest.py index 45fc2b20091..e576f2e7273 100644 --- a/components/esp_tee/test_apps/tee_test_fw/conftest.py +++ b/components/esp_tee/test_apps/tee_test_fw/conftest.py @@ -289,6 +289,7 @@ class TEESerial(IdfSerial): 'type': 'ecdsa_p256', 'input': 'ecdsa_p256_key.pem', 'write_once': True, + 'tee_only': True, 'b64': ( 'LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUlNU1VpUktHaVZjSTIvbUZFekI3eXRIOVJj' 'd0wyUThkNDhONHNFUHFYc0RvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFSkYxYXRZQUxrdnB4cCt4N3c1dmVPQ1Vj' @@ -349,6 +350,7 @@ class TEESerial(IdfSerial): [sys.executable, ESP_TEE_SEC_STG_KEYGEN, '-k', entry['type'], '-o', str(tmp_dir / f'{entry["key"]}.bin')] + (['-i', entry['input']] if entry['input'] else []) + (['--write-once'] if entry['write_once'] else []) + + (['--tee-only'] if entry.get('tee_only') else []) for entry in self.KEY_DEFS ] diff --git a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c index 78aa3e814b2..285665636f2 100644 --- a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c +++ b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -327,6 +327,38 @@ TEST_CASE("Test TEE Secure Storage - Null Pointer and Zero Length", "[sec_storag TEST_ESP_OK(esp_tee_sec_storage_clear_key(key_cfg.id)); } +#if CONFIG_SECURE_TEE_ATTESTATION +TEST_CASE("Test TEE Secure Storage - Attestation key is not REE-accessible", "[sec_storage]") +{ + const char *att_key_id = CONFIG_SECURE_TEE_ATT_KEY_STR_ID; + + esp_tee_sec_storage_key_cfg_t key_cfg = { + .id = att_key_id, + .type = ESP_SEC_STG_KEY_ECDSA_SECP256R1 + }; + + uint8_t digest[SHA256_DIGEST_SZ]; + esp_fill_random(digest, sizeof(digest)); + + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_tee_sec_storage_gen_key(&key_cfg)); + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_tee_sec_storage_clear_key(att_key_id)); + + esp_tee_sec_storage_ecdsa_sign_t sign = {}; + esp_tee_sec_storage_ecdsa_pubkey_t pubkey = {}; + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_tee_sec_storage_ecdsa_sign(&key_cfg, digest, sizeof(digest), &sign)); + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_tee_sec_storage_ecdsa_get_pubkey(&key_cfg, &pubkey)); + + uint8_t data[31], tag[12]; + esp_tee_sec_storage_aead_ctx_t aead_ctx = { + .key_id = att_key_id, + .input = data, + .input_len = sizeof(data), + }; + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_tee_sec_storage_aead_encrypt(&aead_ctx, tag, sizeof(tag), data)); + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_tee_sec_storage_aead_decrypt(&aead_ctx, tag, sizeof(tag), data)); +} +#endif + TEST_CASE("Test TEE Secure Storage - WRITE_ONCE keys", "[sec_storage]") { const char *key_id = "key_id_test_wo"; @@ -346,6 +378,22 @@ TEST_CASE("Test TEE Secure Storage - WRITE_ONCE keys", "[sec_storage]") TEST_ESP_ERR(ESP_ERR_INVALID_STATE, esp_tee_sec_storage_clear_key(key_cfg.id)); } +TEST_CASE("Test TEE Secure Storage - TEE_ONLY keys", "[sec_storage]") +{ + const char *key_id = "key_id_tee_only"; + esp_tee_sec_storage_key_cfg_t key_cfg = { + .id = key_id, + .type = ESP_SEC_STG_KEY_ECDSA_SECP256R1, + .flags = SEC_STORAGE_FLAG_TEE_ONLY, + }; + + esp_err_t err = esp_tee_sec_storage_clear_key(key_cfg.id); + TEST_ASSERT_TRUE(err == ESP_OK || err == ESP_ERR_NOT_FOUND); + + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_tee_sec_storage_gen_key(&key_cfg)); + TEST_ESP_ERR(ESP_ERR_NOT_FOUND, esp_tee_sec_storage_clear_key(key_cfg.id)); +} + static void test_aead_encrypt_decrypt(const char *key_id, const uint8_t *input, size_t len) { uint8_t *ciphertext = heap_caps_malloc(len, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); @@ -425,9 +473,19 @@ TEST_CASE("Test TEE Secure Storage - Host-generated keys", "[sec_storage_host_ke uint32_t token_len = 0; TEST_ESP_OK(esp_tee_att_generate_token(0xA1B2C3D4, 0x0FACADE0, (const char *)ESP_ATT_TK_PSA_CERT_REF, token_buf, ESP_ATT_TK_BUF_SIZE, &token_len)); - free(token_buf); -#endif +#endif /* CONFIG_SECURE_TEE_ATTESTATION */ + + esp_tee_sec_storage_key_cfg_t attest_cfg = { + .id = attest_key_id, + .type = ESP_SEC_STG_KEY_ECDSA_SECP256R1, + }; + esp_tee_sec_storage_ecdsa_sign_t attest_sign = {0}; + esp_tee_sec_storage_ecdsa_pubkey_t attest_pubkey = {0}; + + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_tee_sec_storage_ecdsa_sign(&attest_cfg, msg_digest, SHA256_DIGEST_SZ, &attest_sign)); + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_tee_sec_storage_ecdsa_get_pubkey(&attest_cfg, &attest_pubkey)); + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_tee_sec_storage_clear_key(attest_key_id)); } #if CONFIG_MBEDTLS_TEE_SEC_STG_ECDSA_SIGN @@ -470,7 +528,6 @@ static void test_ecdsa_sign(mbedtls_ecp_group_id gid) mbedtls_mpi_init(&s); mbedtls_ecdsa_context ecdsa_context; - mbedtls_ecdsa_init(&ecdsa_context); TEST_ASSERT_MBEDTLS_OK(mbedtls_ecp_group_load(&ecdsa_context.MBEDTLS_PRIVATE(grp), gid)); diff --git a/docs/en/security/tee/tee-attestation.rst b/docs/en/security/tee/tee-attestation.rst index 07284bfb6f1..c5985031c1c 100644 --- a/docs/en/security/tee/tee-attestation.rst +++ b/docs/en/security/tee/tee-attestation.rst @@ -14,6 +14,10 @@ To ensure security, the EAT is cryptographically protected. The remote relying p - Support for Attestation can be toggled using the option :ref:`CONFIG_SECURE_TEE_ATTESTATION` (enabled by default). + - The attestation signing key (identified by :ref:`CONFIG_SECURE_TEE_ATT_KEY_STR_ID`) is owned exclusively by the TEE. When the TEE generates this key, it is marked with the ``SEC_STORAGE_FLAG_TEE_ONLY`` flag, and the REE is denied any access to it through the secure service interface - it cannot use the key for signing, regenerate it, or clear it. This ensures that the attestation evidence can only ever be signed from within the TEE. + + - In addition, the reserved key ID is treated as TEE-owned even before the key exists, which prevents the REE from "squatting" the ID with a key of its own before the TEE provisions it. If the key is pre-provisioned as part of an NVS image (see :doc:`Secure Storage `), it **must** be generated with the ``--tee-only`` flag of the :component_file:`esp_tee_sec_stg_keygen.py` tool. + Attestation Flow ---------------- diff --git a/examples/security/tee/tee_secure_storage/main/tee_main.c b/examples/security/tee/tee_secure_storage/main/tee_main.c index a704da822a3..779f05d9bb6 100644 --- a/examples/security/tee/tee_secure_storage/main/tee_main.c +++ b/examples/security/tee/tee_secure_storage/main/tee_main.c @@ -120,7 +120,7 @@ static void example_tee_sec_stg_sign_verify(void *pvParameter) esp_err_t err = esp_tee_sec_storage_clear_key(cfg.id); if (err != ESP_OK && err != ESP_ERR_NOT_FOUND) { - ESP_LOGE(TAG, "Failed to clear key %d!", cfg.id); + ESP_LOGE(TAG, "Failed to clear key %s!", cfg.id); goto exit; } @@ -188,7 +188,7 @@ static void example_tee_sec_stg_encrypt_decrypt(void *pvParameter) err = esp_tee_sec_storage_clear_key(cfg.id); if (err != ESP_OK && err != ESP_ERR_NOT_FOUND) { - ESP_LOGE(TAG, "Failed to clear key %d!", cfg.id); + ESP_LOGE(TAG, "Failed to clear key %s!", cfg.id); goto exit; } From dabd37fae8d2a7b1cc71f48fce824b0b9d909a07 Mon Sep 17 00:00:00 2001 From: Laukik Hase Date: Thu, 9 Jul 2026 11:50:42 +0530 Subject: [PATCH 6/8] fix(esp_tee): Validate the stack pointer at the privilege switch boundary --- .../main/arch/riscv/esp_tee_asm_utils.inc | 32 +++++++++ .../main/arch/riscv/esp_tee_vectors_clic.S | 72 ++++++++++++------- .../main/arch/riscv/esp_tee_vectors_plic.S | 70 +++++++++++------- .../test_apps/tee_test_fw/main/CMakeLists.txt | 2 +- .../tee_test_fw/main/test_esp_tee_panic.c | 20 ++++++ .../tee_test_fw/tee_exception_test_map.py | 1 + 6 files changed, 142 insertions(+), 55 deletions(-) diff --git a/components/esp_tee/subproject/main/arch/riscv/esp_tee_asm_utils.inc b/components/esp_tee/subproject/main/arch/riscv/esp_tee_asm_utils.inc index 6478f02e13a..7e638cf55af 100644 --- a/components/esp_tee/subproject/main/arch/riscv/esp_tee_asm_utils.inc +++ b/components/esp_tee/subproject/main/arch/riscv/esp_tee_asm_utils.inc @@ -221,3 +221,35 @@ 1: #endif .endm + +/** + * VALIDATE_REE_SP + * Validate an REE-supplied sp before the TEE stores through it. The TEE region is + * at the bottom of SRAM, so a valid REE frame [sp - framesz, sp) must lie in the + * band above it and below the peripheral window: [SOC_S_DRAM_END, SOC_PERIPHERAL_LOW]. + * An out-of-bound sp will lead to a fault. + * + * With chk_priv (default) the check is skipped unless the trap came from U-mode; + * pass chk_priv=0 where the U-mode origin is already guaranteed (ecall-from-U). + * + * TODO: Revisit these bounds for high-performance RISC-V SoCs (e.g. ESP32-P4, + * ESP32-S31) with different memory maps than current ESP-TEE targets. + * + * Clobbers: \tx + */ +.macro VALIDATE_REE_SP framesz, tx, chk_priv=1 +.if \chk_priv + /* Skip validation unless the previous privilege (mstatus.MPP) was U-mode */ + csrr \tx, mstatus + srli \tx, \tx, MSTATUS_MPP_SHIFT + andi \tx, \tx, (MSTATUS_MPP >> MSTATUS_MPP_SHIFT) + bnez \tx, 1f +.endif + li \tx, (SOC_S_DRAM_END + \framesz) + bltu sp, \tx, _tee_sp_reject /* frame would dip into the TEE (or sub-TEE) region */ + li \tx, SOC_PERIPHERAL_LOW + bltu \tx, sp, _tee_sp_reject /* sp at/above the peripheral window */ +.if \chk_priv +1: +.endif +.endm diff --git a/components/esp_tee/subproject/main/arch/riscv/esp_tee_vectors_clic.S b/components/esp_tee/subproject/main/arch/riscv/esp_tee_vectors_clic.S index 7f470bd4ef0..781ac2f6ff4 100644 --- a/components/esp_tee/subproject/main/arch/riscv/esp_tee_vectors_clic.S +++ b/components/esp_tee/subproject/main/arch/riscv/esp_tee_vectors_clic.S @@ -25,6 +25,8 @@ .equ ECALL_M_MODE, 0xb .equ CSR_UINTTHRESH, 0x047 .equ CSR_MINTTHRESH, 0x347 + .equ MCAUSE_EXCCODE_SHIFT, 20 + .equ MSTATUS_MPP_SHIFT, 11 .global esp_tee_global_interrupt_handler .global esp_tee_service_dispatcher @@ -55,34 +57,33 @@ _ns_sp_min: _ns_sp_max: .word 0 + .global _ns_int_rtn +_ns_int_rtn: + .word 0 + .section .exception_vectors.text, "ax" /* Exception handler. */ .global _tee_panic_handler .type _tee_panic_handler, @function _tee_panic_handler: - /* Backup t0, t1 on the stack before using it */ - addi sp, sp, -16 - sw t0, 0(sp) - sw t1, 4(sp) + /* Backup t0 before using it */ + csrw mscratch, t0 - /* Read mcause */ + /* Check whether the exception is an M-mode/U-mode ecall */ csrr t0, mcause - li t1, VECTORS_MCAUSE_REASON_MASK - and t0, t0, t1 + slli t0, t0, MCAUSE_EXCCODE_SHIFT + srli t0, t0, MCAUSE_EXCCODE_SHIFT + addi t0, t0, -ECALL_M_MODE + beqz t0, _machine_ecall /* M-mode ecall */ + addi t0, t0, (ECALL_M_MODE - ECALL_U_MODE) + beqz t0, _user_ecall /* U-mode ecall */ - /* Check whether the exception is an M-mode ecall */ - li t1, ECALL_M_MODE - beq t0, t1, _machine_ecall + /* Validate a U-mode-origin sp before the handler stores/dumps through it */ + VALIDATE_REE_SP RV_STK_FRMSZ, t0 - /* Check whether the exception is an U-mode ecall */ - li t1, ECALL_U_MODE - beq t0, t1, _user_ecall - - /* Restore t0, t1 from the stack */ - lw t0, 0(sp) - lw t1, 4(sp) - addi sp, sp, 16 + /* Restore t0 */ + csrr t0, mscratch _actual_panic: /* Not an ecall, proceed to the panic handler */ @@ -146,6 +147,12 @@ _return_from_exception: restore_general_regs RV_STK_FRMSZ mret + /* Fault if the sp given by the REE is found to be out-of-bounds */ +_tee_sp_reject: + csrr t0, mscratch + la sp, _tee_stack + j _actual_panic + .size _tee_panic_handler, .-_tee_panic_handler /* ECALL handler. */ @@ -204,14 +211,15 @@ _skip_ctx_restore: /* U-mode ecall handler */ _user_ecall: /* Check whether we are returning after servicing an U-mode interrupt */ - lui t0, RTNVAL - csrrw t1, mscratch, zero - beq t0, t1, _rtn_from_ns_int + la t0, _ns_int_rtn + lw t0, 0(t0) + bnez t0, _rtn_from_ns_int - /* Restore t0, t1 from the stack */ - lw t0, 0(sp) - lw t1, 4(sp) - addi sp, sp, 16 + /* Reject an sp whose frame would be out-of-bounds */ + VALIDATE_REE_SP CONTEXT_SIZE, t0, 0 + + /* Restore t0 */ + csrr t0, mscratch /* This point is reached when a secure service call is issued from the REE */ /* Save register context and mepc */ @@ -259,6 +267,10 @@ _2: /* This point is reached after servicing a U-mode interrupt occurred * while executing a secure service */ _rtn_from_ns_int: + /* Consume the U-mode-interrupt-return sentinel (checked in _user_ecall). */ + la t0, _ns_int_rtn + sw zero, 0(t0) + /* Disable the U-mode interrupt delegation */ li t0, INTMTX_SIG_IDX_ASSERT_IN_SEC_REG li t1, TEE_PASS_INUM + CLIC_EXT_INTR_NUM_OFFSET @@ -392,8 +404,9 @@ _4: lw sp, 0(t1) /* Set a flag to identify the next U2M switch would be after handling a U-mode interrupt */ - lui t0, RTNVAL - csrw mscratch, t0 + la t0, _ns_int_rtn + li t1, RTNVAL + sw t1, 0(t0) /* Place magic bytes in all the general registers */ store_magic_general_regs @@ -408,6 +421,11 @@ _4: .global _tee_s_intr_handler .type _tee_s_intr_handler, @function _tee_s_intr_handler: + /* Check sp if trapped from U-mode */ + csrw mscratch, t0 + VALIDATE_REE_SP RV_STK_FRMSZ, t0 + csrr t0, mscratch /* restore the preempted t0 */ + /* Start by saving the general purpose registers and the PC value before * the interrupt happened. */ save_general_regs RV_STK_FRMSZ diff --git a/components/esp_tee/subproject/main/arch/riscv/esp_tee_vectors_plic.S b/components/esp_tee/subproject/main/arch/riscv/esp_tee_vectors_plic.S index a4dd850f018..be7e042e242 100644 --- a/components/esp_tee/subproject/main/arch/riscv/esp_tee_vectors_plic.S +++ b/components/esp_tee/subproject/main/arch/riscv/esp_tee_vectors_plic.S @@ -23,6 +23,7 @@ .equ RTNVAL, 0xc0de .equ ECALL_U_MODE, 0x8 .equ ECALL_M_MODE, 0xb + .equ MSTATUS_MPP_SHIFT, 11 /* NOTE: INTWDT timeout and Cache error interrupts trigger the panic * handler before reset, so they don’t need to be delegated. */ .equ TEE_INTR_DELEG_MASK, ~((1U << TEE_SECURE_INUM) | (1U << ETS_INT_WDT_INUM) | (1U << ETS_CACHEERR_INUM)) @@ -56,34 +57,32 @@ _ns_sp_min: _ns_sp_max: .word 0 + .global _ns_int_rtn +_ns_int_rtn: + .word 0 + .section .exception_vectors.text, "ax" /* Exception handler. */ .global _tee_panic_handler .type _tee_panic_handler, @function _tee_panic_handler: - /* Backup t0, t1 on the stack before using it */ - addi sp, sp, -16 - sw t0, 0(sp) - sw t1, 4(sp) + /* Backup t0 before using it */ + csrw mscratch, t0 - /* Read mcause */ + /* Check whether the exception is an M-mode/U-mode ecall */ csrr t0, mcause - li t1, VECTORS_MCAUSE_REASON_MASK - and t0, t0, t1 + andi t0, t0, VECTORS_MCAUSE_REASON_MASK + addi t0, t0, -ECALL_M_MODE + beqz t0, _machine_ecall /* M-mode ecall */ + addi t0, t0, (ECALL_M_MODE - ECALL_U_MODE) + beqz t0, _user_ecall /* U-mode ecall */ - /* Check whether the exception is an M-mode ecall */ - li t1, ECALL_M_MODE - beq t0, t1, _machine_ecall + /* Validate a U-mode-origin sp before the handler stores/dumps through it */ + VALIDATE_REE_SP RV_STK_FRMSZ, t0 - /* Check whether the exception is an U-mode ecall */ - li t1, ECALL_U_MODE - beq t0, t1, _user_ecall - - /* Restore t0, t1 from the stack */ - lw t0, 0(sp) - lw t1, 4(sp) - addi sp, sp, 16 + /* Restore t0 */ + csrr t0, mscratch _actual_panic: /* Not an ecall, proceed to the panic handler */ @@ -138,6 +137,12 @@ _return_from_exception: restore_general_regs RV_STK_FRMSZ mret + /* Fault if the sp given by the REE is found to be out-of-bounds */ +_tee_sp_reject: + csrr t0, mscratch + la sp, _tee_stack + j _actual_panic + .size _tee_panic_handler, .-_tee_panic_handler /* ECALL handler. */ @@ -192,14 +197,15 @@ _skip_ctx_restore: /* U-mode ecall handler */ _user_ecall: /* Check whether we are returning after servicing an U-mode interrupt */ - lui t0, RTNVAL - csrrw t1, mscratch, zero - beq t0, t1, _rtn_from_ns_int + la t0, _ns_int_rtn + lw t0, 0(t0) + bnez t0, _rtn_from_ns_int - /* Restore t0, t1 from the stack */ - lw t0, 0(sp) - lw t1, 4(sp) - addi sp, sp, 16 + /* Reject an sp whose frame would be out-of-bounds */ + VALIDATE_REE_SP CONTEXT_SIZE, t0, 0 + + /* Restore t0 */ + csrr t0, mscratch /* This point is reached when a secure service call is issued from the REE */ /* Save register context and mepc */ @@ -244,6 +250,10 @@ _process_ecall: /* This point is reached after servicing a U-mode interrupt occurred * while executing a secure service */ _rtn_from_ns_int: + /* Consume the U-mode-interrupt-return sentinel (checked in _user_ecall). */ + la t0, _ns_int_rtn + sw zero, 0(t0) + /* Disable the U-mode interrupt delegation */ csrwi mideleg, 0 @@ -315,8 +325,9 @@ _tee_ns_intr_handler: lw sp, 0(t1) /* Set a flag to identify the next U2M switch would be after handling a U-mode interrupt */ - lui t0, RTNVAL - csrw mscratch, t0 + la t0, _ns_int_rtn + li t1, RTNVAL + sw t1, 0(t0) /* Enable the U-mode interrupt delegation (except for the TEE secure interrupt) */ li t0, TEE_INTR_DELEG_MASK @@ -335,6 +346,11 @@ _tee_ns_intr_handler: .global _tee_s_intr_handler .type _tee_s_intr_handler, @function _tee_s_intr_handler: + /* Check sp if trapped from U-mode */ + csrw mscratch, t0 + VALIDATE_REE_SP RV_STK_FRMSZ, t0 + csrr t0, mscratch /* restore the preempted t0 */ + /* Start by saving the general purpose registers and the PC value before * the interrupt happened. */ save_general_regs RV_STK_FRMSZ diff --git a/components/esp_tee/test_apps/tee_test_fw/main/CMakeLists.txt b/components/esp_tee/test_apps/tee_test_fw/main/CMakeLists.txt index fa10a28e267..5aa23099823 100644 --- a/components/esp_tee/test_apps/tee_test_fw/main/CMakeLists.txt +++ b/components/esp_tee/test_apps/tee_test_fw/main/CMakeLists.txt @@ -1,6 +1,6 @@ idf_build_get_property(idf_path IDF_PATH) -set(priv_requires bootloader_support esp_driver_gptimer esp_tee esp_timer mbedtls spi_flash) +set(priv_requires bootloader_support esp_driver_gptimer esp_system esp_tee esp_timer mbedtls spi_flash) # Test FW related list(APPEND priv_requires json nvs_flash test_utils unity) # TEE related diff --git a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_panic.c b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_panic.c index 612c0d475bd..4c7a666b7f1 100644 --- a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_panic.c +++ b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_panic.c @@ -19,6 +19,7 @@ #include "unity.h" #include "esp_tee.h" +#include "esp_private/hw_stack_guard.h" #include "secure_service_num.h" #define ALIGN_DOWN_TO_MMU_PAGE_SIZE(addr) ((addr) & ~((SOC_MMU_PAGE_SIZE) - 1)) @@ -212,3 +213,22 @@ TEST_CASE("Test REE-TEE isolation: DROM-W1", "[exception]") *(uint32_t *)(test_addr - 0x04) = 0xbadc0de; TEST_FAIL_MESSAGE("Exception should have been generated"); } + +TEST_CASE("Test REE-TEE isolation: Corrupted SP", "[exception]") +{ + uintptr_t atk_sp = (uintptr_t)&_iram_start - 0x100; + + /* Disable U-mode interrupts so the tick cannot preempt before the ecall */ + __asm__ volatile("csrci ustatus, 0x1\n\t" : : : "memory"); + + /* Stop the REE-owned HW stack guard, as a malicious REE could */ +#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD + esp_hw_stack_guard_monitor_stop(); +#endif + + /* Cross into the TEE with the doctored sp; the handler rejects it and panics */ + __asm__ volatile("mv sp, %0\n\t" + "ecall\n\t" : : "r"(atk_sp) : "memory"); + + TEST_FAIL_MESSAGE("Exception should have been generated"); +} diff --git a/components/esp_tee/test_apps/tee_test_fw/tee_exception_test_map.py b/components/esp_tee/test_apps/tee_test_fw/tee_exception_test_map.py index db2d00c3b05..9eccea39adb 100644 --- a/components/esp_tee/test_apps/tee_test_fw/tee_exception_test_map.py +++ b/components/esp_tee/test_apps/tee_test_fw/tee_exception_test_map.py @@ -24,6 +24,7 @@ _BASE_CONFIG = { 'DROM-R1': 'Load access fault', 'DROM-W1': 'Store access fault', 'MMU-spillover': 'Illegal instruction', + 'Corrupted SP': 'Environment call from U-mode', }, 'apm_violation': { 'eFuse': 'APM - Space exception', From 12fe8f0122177caec1c0114c61e5851463871b96 Mon Sep 17 00:00:00 2001 From: Laukik Hase Date: Wed, 15 Jul 2026 13:12:23 +0530 Subject: [PATCH 7/8] fix(esp_tee): Validate REE-supplied memory bounds (`esp_tee_app_config`) before use --- .../main/include/esp_tee_memory_utils.h | 23 ++++++-------- .../soc/esp32c5/esp_tee_pmp_pma_prot_cfg.c | 29 +++++++++++------- .../soc/esp32c6/esp_tee_pmp_pma_prot_cfg.c | 30 ++++++++++++------- .../soc/esp32h2/esp_tee_pmp_pma_prot_cfg.c | 30 ++++++++++++------- 4 files changed, 66 insertions(+), 46 deletions(-) diff --git a/components/esp_tee/subproject/main/include/esp_tee_memory_utils.h b/components/esp_tee/subproject/main/include/esp_tee_memory_utils.h index e3ec2f50dfa..0d52b8fc636 100644 --- a/components/esp_tee/subproject/main/include/esp_tee_memory_utils.h +++ b/components/esp_tee/subproject/main/include/esp_tee_memory_utils.h @@ -16,19 +16,9 @@ extern "C" { #endif -FORCE_INLINE_ATTR bool esp_tee_ptr_in_ree(const void *p) -{ - uintptr_t addr = (uintptr_t)p; - return ( - (addr >= SOC_NS_IDRAM_START && addr < SOC_NS_IDRAM_END) || - (addr >= (uintptr_t)esp_tee_app_config.ns_drom_start && - addr < SOC_S_MMU_MMAP_RESV_START_VADDR) -#if SOC_RTC_MEM_SUPPORTED - || (addr >= SOC_RTC_DATA_LOW && addr < SOC_RTC_DATA_HIGH) -#endif - ); -} - +/* TODO: Revisit these bounds for high-performance RISC-V SoCs (e.g. ESP32-P4, + * ESP32-S31) with different memory maps than current ESP-TEE targets. + */ FORCE_INLINE_ATTR bool esp_tee_buf_in_ree(const void *p, size_t len) { uintptr_t start = (uintptr_t)p; @@ -40,13 +30,18 @@ FORCE_INLINE_ATTR bool esp_tee_buf_in_ree(const void *p, size_t len) uintptr_t end = start + len; return ((start >= SOC_NS_IDRAM_START && end <= SOC_NS_IDRAM_END) || - (start >= (uintptr_t)esp_tee_app_config.ns_drom_start && end <= SOC_S_MMU_MMAP_RESV_START_VADDR) + (start >= SOC_S_DROM_HIGH && end <= SOC_S_MMU_MMAP_RESV_START_VADDR) #if SOC_RTC_MEM_SUPPORTED || (start >= SOC_RTC_DATA_LOW && end <= SOC_RTC_DATA_HIGH) #endif ); } +FORCE_INLINE_ATTR bool esp_tee_ptr_in_ree(const void *p) +{ + return esp_tee_buf_in_ree(p, 4); +} + #ifdef __cplusplus } #endif diff --git a/components/esp_tee/subproject/main/soc/esp32c5/esp_tee_pmp_pma_prot_cfg.c b/components/esp_tee/subproject/main/soc/esp32c5/esp_tee_pmp_pma_prot_cfg.c index f7d47af6365..3947a6e72f2 100644 --- a/components/esp_tee/subproject/main/soc/esp32c5/esp_tee_pmp_pma_prot_cfg.c +++ b/components/esp_tee/subproject/main/soc/esp32c5/esp_tee_pmp_pma_prot_cfg.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -55,9 +55,9 @@ static void esp_tee_configure_invalid_regions(void) // 7. Using PMA to configure the TEE text and data section access attribute. */ PMA_ENTRY_CFG_RESET(12); - assert(IS_PMA_ENTRY_UNLOCKED(13)); - assert(IS_PMA_ENTRY_UNLOCKED(14)); - assert(IS_PMA_ENTRY_UNLOCKED(15)); + ESP_FAULT_ASSERT(IS_PMA_ENTRY_UNLOCKED(13) && + IS_PMA_ENTRY_UNLOCKED(14) && + IS_PMA_ENTRY_UNLOCKED(15)); extern int _tee_iram_end; PMA_RESET_AND_ENTRY_SET_TOR(13, SOC_S_IRAM_START, PMA_NONE); @@ -122,6 +122,20 @@ void esp_tee_configure_region_protection(void) PMP_ENTRY_CFG_RESET(4); PMP_ENTRY_CFG_RESET(5); PMP_ENTRY_CFG_RESET(6); + /* Validate the REE-supplied (esp_tee_app_config) bounds before programming + * the TOR-chained PMP entries below */ + const uint32_t ns_iram_end = (uint32_t)esp_tee_app_config.ns_iram_end; + const uint32_t s_irom_resv_end = SOC_IROM_LOW + CONFIG_SECURE_TEE_IROM_SIZE + CONFIG_SECURE_TEE_DROM_SIZE; + const uint32_t ns_irom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)esp_tee_app_config.ns_irom_end); + const uint32_t ns_drom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)esp_tee_app_config.ns_drom_end); + const uint32_t ns_drom_mmap_end = (uint32_t)(SOC_S_MMU_MMAP_RESV_START_VADDR); + + ESP_FAULT_ASSERT(ns_iram_end >= SOC_NS_IRAM_START && + ns_iram_end <= SOC_DRAM_HIGH && + s_irom_resv_end <= ns_irom_resv_end && + ns_irom_resv_end <= ns_drom_resv_end && + ns_drom_resv_end <= ns_drom_mmap_end); + if (esp_cpu_dbgr_is_attached()) { // Anti-FI check that cpu is really in ocd mode ESP_FAULT_ASSERT(esp_cpu_dbgr_is_attached()); @@ -131,15 +145,10 @@ void esp_tee_configure_region_protection(void) } else { // REE SRAM (D/IRAM) PMP_ENTRY_SET(4, (int)SOC_NS_IRAM_START, NONE); - PMP_ENTRY_SET(5, (int)esp_tee_app_config.ns_iram_end, PMP_TOR | RX); + PMP_ENTRY_SET(5, (int)ns_iram_end, PMP_TOR | RX); PMP_ENTRY_SET(6, SOC_DRAM_HIGH, PMP_TOR | RW); } - const uint32_t s_irom_resv_end = SOC_IROM_LOW + CONFIG_SECURE_TEE_IROM_SIZE + CONFIG_SECURE_TEE_DROM_SIZE; - const uint32_t ns_irom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)esp_tee_app_config.ns_irom_end); - const uint32_t ns_drom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)esp_tee_app_config.ns_drom_end); - const uint32_t ns_drom_mmap_end = (uint32_t)(SOC_S_MMU_MMAP_RESV_START_VADDR); - // 4. I_Cache / D_Cache (flash) - REE PMP_ENTRY_CFG_RESET(7); PMP_ENTRY_CFG_RESET(8); diff --git a/components/esp_tee/subproject/main/soc/esp32c6/esp_tee_pmp_pma_prot_cfg.c b/components/esp_tee/subproject/main/soc/esp32c6/esp_tee_pmp_pma_prot_cfg.c index bea234af031..4f977647f59 100644 --- a/components/esp_tee/subproject/main/soc/esp32c6/esp_tee_pmp_pma_prot_cfg.c +++ b/components/esp_tee/subproject/main/soc/esp32c6/esp_tee_pmp_pma_prot_cfg.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -63,9 +63,9 @@ static void esp_tee_configure_invalid_regions(void) PMA_RESET_AND_ENTRY_SET_TOR(12, UINT32_MAX, PMA_TOR | PMA_NONE); // 8. Using PMA to configure the TEE text and data section access attribute. */ - assert(IS_PMA_ENTRY_UNLOCKED(13)); - assert(IS_PMA_ENTRY_UNLOCKED(14)); - assert(IS_PMA_ENTRY_UNLOCKED(15)); + ESP_FAULT_ASSERT(IS_PMA_ENTRY_UNLOCKED(13) && + IS_PMA_ENTRY_UNLOCKED(14) && + IS_PMA_ENTRY_UNLOCKED(15)); extern int _tee_iram_end; PMA_RESET_AND_ENTRY_SET_TOR(13, SOC_S_IRAM_START, PMA_NONE); @@ -112,7 +112,20 @@ void esp_tee_configure_region_protection(void) PMP_ENTRY_SET(1, SOC_IROM_MASK_HIGH, PMP_TOR | RX); _Static_assert(SOC_IROM_MASK_LOW < SOC_IROM_MASK_HIGH, "Invalid I/D-ROM region"); - /* TODO: Check whether changes are required here */ + /* Validate the REE-supplied (esp_tee_app_config) bounds before programming + * the TOR-chained PMP entries below */ + const uint32_t ns_iram_end = (uint32_t)esp_tee_app_config.ns_iram_end; + const uint32_t s_irom_resv_end = SOC_IROM_LOW + CONFIG_SECURE_TEE_IROM_SIZE + CONFIG_SECURE_TEE_DROM_SIZE; + const uint32_t ns_irom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)esp_tee_app_config.ns_irom_end); + const uint32_t ns_drom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)esp_tee_app_config.ns_drom_end); + const uint32_t ns_drom_mmap_end = (uint32_t)(SOC_S_MMU_MMAP_RESV_START_VADDR); + + ESP_FAULT_ASSERT(ns_iram_end >= SOC_NS_IRAM_START && + ns_iram_end <= SOC_DRAM_HIGH && + s_irom_resv_end <= ns_irom_resv_end && + ns_irom_resv_end <= ns_drom_resv_end && + ns_drom_resv_end <= ns_drom_mmap_end); + if (esp_cpu_dbgr_is_attached()) { // Anti-FI check that cpu is really in ocd mode ESP_FAULT_ASSERT(esp_cpu_dbgr_is_attached()); @@ -125,15 +138,10 @@ void esp_tee_configure_region_protection(void) // 2. IRAM and DRAM // Splitting the REE SRAM region into IRAM and DRAM PMP_ENTRY_SET(2, (int)SOC_NS_IRAM_START, NONE); - PMP_ENTRY_SET(3, (int)esp_tee_app_config.ns_iram_end, PMP_TOR | RX); + PMP_ENTRY_SET(3, (int)ns_iram_end, PMP_TOR | RX); PMP_ENTRY_SET(4, SOC_DRAM_HIGH, PMP_TOR | RW); } - const uint32_t s_irom_resv_end = SOC_IROM_LOW + CONFIG_SECURE_TEE_IROM_SIZE + CONFIG_SECURE_TEE_DROM_SIZE; - const uint32_t ns_irom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)esp_tee_app_config.ns_irom_end); - const uint32_t ns_drom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)esp_tee_app_config.ns_drom_end); - const uint32_t ns_drom_mmap_end = (uint32_t)(SOC_S_MMU_MMAP_RESV_START_VADDR); - // 4. I_Cache / D_Cache (flash) - REE PMP_ENTRY_CFG_RESET(5); PMP_ENTRY_CFG_RESET(6); diff --git a/components/esp_tee/subproject/main/soc/esp32h2/esp_tee_pmp_pma_prot_cfg.c b/components/esp_tee/subproject/main/soc/esp32h2/esp_tee_pmp_pma_prot_cfg.c index 8de916768ce..5c7d3acd54b 100644 --- a/components/esp_tee/subproject/main/soc/esp32h2/esp_tee_pmp_pma_prot_cfg.c +++ b/components/esp_tee/subproject/main/soc/esp32h2/esp_tee_pmp_pma_prot_cfg.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -65,9 +65,9 @@ static void esp_tee_configure_invalid_regions(void) PMA_ENTRY_SET_TOR(12, UINT32_MAX, PMA_TOR | PMA_NONE); /* 8. Using PMA to configure the TEE text and data section access attribute. */ - assert(IS_PMA_ENTRY_UNLOCKED(13)); - assert(IS_PMA_ENTRY_UNLOCKED(14)); - assert(IS_PMA_ENTRY_UNLOCKED(15)); + ESP_FAULT_ASSERT(IS_PMA_ENTRY_UNLOCKED(13) && + IS_PMA_ENTRY_UNLOCKED(14) && + IS_PMA_ENTRY_UNLOCKED(15)); extern int _tee_iram_end; PMA_RESET_AND_ENTRY_SET_TOR(13, SOC_S_IRAM_START, PMA_NONE); @@ -108,7 +108,20 @@ void esp_tee_configure_region_protection(void) PMP_ENTRY_SET(0, pmpaddr0, PMP_NAPOT | RX); _Static_assert(SOC_IROM_MASK_LOW < SOC_IROM_MASK_HIGH, "Invalid I/D-ROM region"); - /* TODO: Check whether changes are required here */ + /* Validate the REE-supplied (esp_tee_app_config) bounds before programming + * the TOR-chained PMP entries below */ + const uint32_t ns_iram_end = (uint32_t)esp_tee_app_config.ns_iram_end; + const uint32_t s_irom_resv_end = SOC_IROM_LOW + CONFIG_SECURE_TEE_IROM_SIZE + CONFIG_SECURE_TEE_DROM_SIZE; + const uint32_t ns_irom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)esp_tee_app_config.ns_irom_end); + const uint32_t ns_drom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)esp_tee_app_config.ns_drom_end); + const uint32_t ns_drom_mmap_end = (uint32_t)(SOC_S_MMU_MMAP_RESV_START_VADDR); + + ESP_FAULT_ASSERT(ns_iram_end >= SOC_NS_IRAM_START && + ns_iram_end <= SOC_DRAM_HIGH && + s_irom_resv_end <= ns_irom_resv_end && + ns_irom_resv_end <= ns_drom_resv_end && + ns_drom_resv_end <= ns_drom_mmap_end); + if (esp_cpu_dbgr_is_attached()) { // Anti-FI check that cpu is really in ocd mode ESP_FAULT_ASSERT(esp_cpu_dbgr_is_attached()); @@ -121,15 +134,10 @@ void esp_tee_configure_region_protection(void) // 2. IRAM and DRAM // Splitting the REE SRAM region into IRAM and DRAM PMP_ENTRY_SET(1, (int)SOC_NS_IRAM_START, NONE); - PMP_ENTRY_SET(2, (int)esp_tee_app_config.ns_iram_end, PMP_TOR | RX); + PMP_ENTRY_SET(2, (int)ns_iram_end, PMP_TOR | RX); PMP_ENTRY_SET(3, SOC_DRAM_HIGH, PMP_TOR | RW); } - const uint32_t s_irom_resv_end = SOC_IROM_LOW + CONFIG_SECURE_TEE_IROM_SIZE + CONFIG_SECURE_TEE_DROM_SIZE; - const uint32_t ns_irom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)esp_tee_app_config.ns_irom_end); - const uint32_t ns_drom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)esp_tee_app_config.ns_drom_end); - const uint32_t ns_drom_mmap_end = (uint32_t)(SOC_S_MMU_MMAP_RESV_START_VADDR); - // 4. I_Cache / D_Cache (flash) - REE PMP_ENTRY_CFG_RESET(5); PMP_ENTRY_CFG_RESET(6); From 4692bfd2757bebb2a1b88b757608d10952c90a25 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Mon, 24 Aug 2026 18:10:34 +0800 Subject: [PATCH 8/8] docs(esp_tee): fix AEAD doxygen params after IV moved into the context --- .../include/esp_tee_sec_storage.h | 27 ++++++++++--------- .../tee_sec_storage/tee_sec_storage.c | 1 - .../tee_test_fw/main/test_esp_tee_sec_stg.c | 3 ++- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/components/esp_tee/subproject/components/tee_sec_storage/include/esp_tee_sec_storage.h b/components/esp_tee/subproject/components/tee_sec_storage/include/esp_tee_sec_storage.h index ad12969df6f..594c61c8972 100644 --- a/components/esp_tee/subproject/components/tee_sec_storage/include/esp_tee_sec_storage.h +++ b/components/esp_tee/subproject/components/tee_sec_storage/include/esp_tee_sec_storage.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -162,14 +162,16 @@ esp_err_t esp_tee_sec_storage_ecdsa_get_pubkey(const esp_tee_sec_storage_key_cfg /** * @brief Perform encryption using AES256-GCM with the key from secure storage * - * @param[in] ctx Pointer to the AEAD operation context - * @param[out] iv Pointer to the output buffer for the generated initialization vector - * @param[in] iv_len Length of the initialization vector buffer; must be exactly 12 bytes (96-bit IV, per NIST SP 800-38D) - * @param[out] tag Pointer to the authentication tag buffer - * @param[in] tag_len Length of the authentication tag; must be 12 to 16 bytes (96- to 128-bit tag, per NIST SP 800-38D) - * @param[out] output Pointer to the output data buffer + * @param[in,out] ctx Pointer to the AEAD operation context; the generated + * initialization vector is written to @p ctx->iv + * @param[out] tag Pointer to the authentication tag buffer + * @param[in] tag_len Length of the authentication tag; must be 12 to 16 bytes (96- to 128-bit tag, per NIST SP 800-38D) + * @param[out] output Pointer to the output data buffer * - * @note Non-standard @p iv_len / @p tag_len values are rejected with ESP_ERR_INVALID_SIZE. + * @note The initialization vector is generated internally and is always + * ::AES_GCM_SUPPORTED_IV_LEN bytes long (96-bit IV, per NIST SP 800-38D). + * Read it from @p ctx->iv after the call and store it with the ciphertext. + * @note Non-standard @p tag_len values are rejected with ESP_ERR_INVALID_SIZE. * * @return esp_err_t ESP_OK on success, appropriate error code otherwise. */ @@ -178,14 +180,15 @@ esp_err_t esp_tee_sec_storage_aead_encrypt(esp_tee_sec_storage_aead_ctx_t *ctx, /** * @brief Perform decryption using AES256-GCM with the key from secure storage * - * @param[in] ctx Pointer to the AEAD operation context - * @param[in] iv Pointer to the initialization vector used during encryption - * @param[in] iv_len Length of the initialization vector; must be exactly 12 bytes (96-bit IV, per NIST SP 800-38D) + * @param[in] ctx Pointer to the AEAD operation context; @p ctx->iv must hold + * the initialization vector used during encryption * @param[in] tag Pointer to the authentication tag buffer * @param[in] tag_len Length of the authentication tag; must be 12 to 16 bytes (96- to 128-bit tag, per NIST SP 800-38D) * @param[out] output Pointer to the output data buffer * - * @note Non-standard @p iv_len / @p tag_len values are rejected with ESP_ERR_INVALID_SIZE. + * @note The initialization vector is always ::AES_GCM_SUPPORTED_IV_LEN bytes long + * (96-bit IV, per NIST SP 800-38D). Write it to @p ctx->iv before the call. + * @note Non-standard @p tag_len values are rejected with ESP_ERR_INVALID_SIZE. * * @return esp_err_t ESP_OK on success, appropriate error code otherwise. */ diff --git a/components/esp_tee/subproject/components/tee_sec_storage/tee_sec_storage.c b/components/esp_tee/subproject/components/tee_sec_storage/tee_sec_storage.c index 7ff6655a628..1ff06c5004d 100644 --- a/components/esp_tee/subproject/components/tee_sec_storage/tee_sec_storage.c +++ b/components/esp_tee/subproject/components/tee_sec_storage/tee_sec_storage.c @@ -38,7 +38,6 @@ #define AES256_GCM_IV_LEN 12 #define AES256_GCM_TAG_LEN_MIN 12 /* NIST SP800-38D general-use minimum (96-bit tag) */ #define AES256_GCM_TAG_LEN_MAX 16 /* full GCM tag (128-bit) */ -#define ECDSA_SECP384R1_KEY_LEN 48 #define ECDSA_SECP256R1_KEY_LEN 32 #define ECDSA_SECP192R1_KEY_LEN 24 diff --git a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c index 285665636f2..01f1c660d0f 100644 --- a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c +++ b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c @@ -464,7 +464,7 @@ TEST_CASE("Test TEE Secure Storage - Host-generated keys", "[sec_storage_host_ke TEST_ESP_OK(esp_tee_sec_storage_clear_key(ecdsa_key_id0)); - TEST_ESP_ERR(ESP_ERR_INVALID_STATE, esp_tee_sec_storage_clear_key(attest_key_id)); + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_tee_sec_storage_clear_key(attest_key_id)); #if CONFIG_SECURE_TEE_ATTESTATION uint8_t *token_buf = heap_caps_calloc(ESP_ATT_TK_BUF_SIZE, sizeof(uint8_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); @@ -528,6 +528,7 @@ static void test_ecdsa_sign(mbedtls_ecp_group_id gid) mbedtls_mpi_init(&s); mbedtls_ecdsa_context ecdsa_context; + mbedtls_ecdsa_init(&ecdsa_context); TEST_ASSERT_MBEDTLS_OK(mbedtls_ecp_group_load(&ecdsa_context.MBEDTLS_PRIVATE(grp), gid));