Merge branch 'refactor/esp_tee_heap_rom' into 'master'

feat(esp_tee): Use the ROM TLSF implementation for the TEE build

See merge request espressif/esp-idf!37277
This commit is contained in:
Laukik Hase
2025-04-09 12:33:15 +08:00
16 changed files with 282 additions and 107 deletions

View File

@@ -133,6 +133,7 @@ endif()
if(ESP_TEE_BUILD)
if(target STREQUAL "esp32c6")
rom_linker_script("spiflash")
rom_linker_script("heap")
endif()
endif()

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -11,6 +11,60 @@
extern "C" {
#endif
/* tlsf_t: a TLSF structure. Can contain 1 to N pools. */
/* pool_t: a block of memory that TLSF can manage. */
typedef void* tlsf_t;
typedef void* pool_t;
/* Create/destroy a memory pool. */
tlsf_t tlsf_create(void* mem);
tlsf_t tlsf_create_with_pool(void* mem, size_t bytes);
pool_t tlsf_get_pool(tlsf_t tlsf);
/* Add/remove memory pools. */
pool_t tlsf_add_pool(tlsf_t tlsf, void* mem, size_t bytes);
void tlsf_remove_pool(tlsf_t tlsf, pool_t pool);
/* malloc/memalign/realloc/free replacements. */
void* tlsf_malloc(tlsf_t tlsf, size_t size);
void* tlsf_memalign(tlsf_t tlsf, size_t align, size_t size);
void* tlsf_memalign_offs(tlsf_t tlsf, size_t align, size_t size, size_t offset);
void* tlsf_realloc(tlsf_t tlsf, void* ptr, size_t size);
void tlsf_free(tlsf_t tlsf, void* ptr);
/* Returns internal block size, not original request size */
size_t tlsf_block_size(void* ptr);
/* Overheads/limits of internal structures. */
size_t tlsf_size(void);
size_t tlsf_pool_overhead(void);
size_t tlsf_alloc_overhead(void);
#if ESP_TEE_BUILD
/* NOTE: These declarations are only needed for the TEE build, since these
* functions are (static inline) defined in tlsf_control_functions.h for
* IDF builds.
*/
size_t tlsf_align_size(void);
size_t tlsf_block_size_min(void);
size_t tlsf_block_size_max(void);
/* NOTE: The consumer of this callback function (tlsf_walk_pool) is patched
* in IDF builds to address issues in the ROM implementation. For TEE build,
* the ROM declarations can be used directly, as heap integrity checking is not
* supported.
*/
typedef void (*tlsf_walker)(void* ptr, size_t size, int used, void* user);
#else
typedef bool (*tlsf_walker)(void* ptr, size_t size, int used, void* user);
#endif
/* Debugging. */
void tlsf_walk_pool(pool_t pool, tlsf_walker walker, void* user);
/* Returns nonzero if any internal consistency check fails. */
int tlsf_check(tlsf_t tlsf);
int tlsf_check_pool(pool_t pool);
/*!
* Defines the function prototypes for multi_heap_internal_poison_fill_region
* and multi_heap_internal_check_block_poisoning, these two function will

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -24,10 +24,6 @@
#include "tlsf_block_functions.h"
#include "tlsf_control_functions.h"
/* Definition of types used in TLSF */
typedef void* tlsf_t;
typedef void* pool_t;
static poison_check_pfunc_t s_poison_check_region = NULL;
void tlsf_poison_check_pfunc_set(poison_check_pfunc_t pfunc)
@@ -43,8 +39,6 @@ typedef struct integrity_t
int status;
} integrity_t;
typedef bool (*tlsf_walker)(void* ptr, size_t size, int used, void* user);
static bool integrity_walker(void* ptr, size_t size, int used, void* user)
{
block_header_t* block = block_from_ptr(ptr);

View File

@@ -40,10 +40,7 @@ list(APPEND include "include"
list(APPEND srcs "common/multi_heap.c")
# TLSF implementation for heap
list(APPEND include "${heap_dir}/tlsf"
"${heap_dir}/tlsf/include")
list(APPEND srcs "${heap_dir}/tlsf/tlsf.c")
list(APPEND include "${heap_dir}/tlsf")
# esp_app_desc_t configuration structure for TEE
list(APPEND srcs "common/esp_app_desc_tee.c")

View File

@@ -1,10 +1,11 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "tlsf.h"
#include <stdbool.h>
#include "esp_rom_tlsf.h"
#include "tlsf_block_functions.h"
#include "multi_heap.h"
@@ -34,36 +35,33 @@ static void assert_valid_block(const heap_t *heap, const block_header_t *block)
(uintptr_t)ptr);
}
int tee_heap_register(void *start_ptr, size_t size)
esp_err_t esp_tee_heap_init(void *start_ptr, size_t size)
{
assert(start_ptr);
if (size < (sizeof(heap_t))) {
//Region too small to be a heap.
return -1;
if (size < (tlsf_size() + tlsf_block_size_min() + sizeof(heap_t))) {
// Region too small to be a heap.
return ESP_ERR_INVALID_SIZE;
}
heap_t *result = (heap_t *)start_ptr;
size -= sizeof(heap_t);
/* Do not specify any maximum size for the allocations so that the default configuration is used */
const size_t max_bytes = 0;
result->heap_data = tlsf_create_with_pool(start_ptr + sizeof(heap_t), size, max_bytes);
result->heap_data = tlsf_create_with_pool(start_ptr + sizeof(heap_t), size);
if (result->heap_data == NULL) {
return -1;
return ESP_FAIL;
}
result->lock = NULL;
result->free_bytes = size - tlsf_size(result->heap_data);
result->free_bytes = size - tlsf_size();
result->pool_size = size;
result->minimum_free_bytes = result->free_bytes;
tee_heap = (multi_heap_handle_t)result;
return 0;
return ESP_OK;
}
void *tee_heap_malloc(size_t size)
void *esp_tee_heap_malloc(size_t size)
{
if (tee_heap == NULL || size == 0) {
return NULL;
@@ -81,17 +79,17 @@ void *tee_heap_malloc(size_t size)
return result;
}
void *tee_heap_calloc(size_t n, size_t size)
void *esp_tee_heap_calloc(size_t n, size_t size)
{
size_t reg_size = n * size;
void *ptr = tee_heap_malloc(reg_size);
void *ptr = esp_tee_heap_malloc(reg_size);
if (ptr != NULL) {
memset(ptr, 0x00, reg_size);
}
return ptr;
}
void *tee_heap_aligned_alloc(size_t size, size_t alignment)
void *esp_tee_heap_aligned_alloc(size_t size, size_t alignment)
{
if (tee_heap == NULL || size == 0) {
return NULL;
@@ -114,7 +112,7 @@ void *tee_heap_aligned_alloc(size_t size, size_t alignment)
return result;
}
void tee_heap_free(void *p)
void esp_tee_heap_free(void *p)
{
if (tee_heap == NULL || p == NULL) {
return;
@@ -129,45 +127,42 @@ void tee_heap_free(void *p)
void *malloc(size_t size)
{
return tee_heap_malloc(size);
return esp_tee_heap_malloc(size);
}
void *calloc(size_t n, size_t size)
{
return tee_heap_calloc(n, size);
return esp_tee_heap_calloc(n, size);
}
void free(void *ptr)
{
tee_heap_free(ptr);
esp_tee_heap_free(ptr);
}
void tee_heap_dump_free_size(void)
size_t esp_tee_heap_get_free_size(void)
{
if (tee_heap == NULL) {
return;
}
printf("Free: %uB | Minimum free: %uB\n", tee_heap->free_bytes, tee_heap->minimum_free_bytes);
return tee_heap->free_bytes;
}
static bool tee_heap_dump_tlsf(void* ptr, size_t size, int used, void* user)
size_t esp_tee_heap_get_min_free_size(void)
{
return tee_heap->minimum_free_bytes;
}
static void heap_dump_tlsf(void* ptr, size_t size, int used, void* user)
{
(void)user;
printf("Block %p data, size: %d bytes, Free: %s\n",
(void *)ptr,
size,
used ? "No" : "Yes");
return true;
}
void tee_heap_dump_info(void)
void esp_tee_heap_dump_info(void)
{
if (tee_heap == NULL) {
return;
}
printf("Showing data for TEE heap: %p\n", (void *)tee_heap);
tee_heap_dump_free_size();
tlsf_walk_pool(tlsf_get_pool(tee_heap->heap_data), tee_heap_dump_tlsf, NULL);
printf("Showing data for TEE heap: %p (%uB)\n", (void *)tee_heap, tee_heap->pool_size);
tlsf_walk_pool(tlsf_get_pool(tee_heap->heap_data), heap_dump_tlsf, NULL);
}
/* Definitions for functions from the heap component, used in files shared with ESP-IDF */
@@ -175,13 +170,13 @@ void tee_heap_dump_info(void)
void *heap_caps_malloc(size_t alignment, size_t size, uint32_t caps)
{
(void) caps;
return tee_heap_malloc(size);
return esp_tee_heap_malloc(size);
}
void *heap_caps_aligned_alloc(size_t alignment, size_t size, uint32_t caps)
{
(void) caps;
return tee_heap_aligned_alloc(size, alignment);
return esp_tee_heap_aligned_alloc(size, alignment);
}
void *heap_caps_aligned_calloc(size_t alignment, size_t n, size_t size, uint32_t caps)
@@ -189,7 +184,7 @@ void *heap_caps_aligned_calloc(size_t alignment, size_t n, size_t size, uint32_t
(void) caps;
uint32_t reg_size = n * size;
void *ptr = tee_heap_aligned_alloc(reg_size, alignment);
void *ptr = esp_tee_heap_aligned_alloc(reg_size, alignment);
if (ptr != NULL) {
memset(ptr, 0x00, reg_size);
}

View File

@@ -132,7 +132,7 @@ void __attribute__((noreturn)) esp_tee_init(uint32_t ree_entry_addr, uint32_t re
tee_init_app_config();
/* TEE Secure World heap initialization. */
assert(tee_heap_register(((void *)&_tee_heap_start), TEE_HEAP_SIZE) == 0);
assert(esp_tee_heap_init(((void *)&_tee_heap_start), TEE_HEAP_SIZE) == ESP_OK);
/* SoC specific secure initialization. */
esp_tee_soc_secure_sys_init();

View File

@@ -1,11 +1,11 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <string.h>
#include "tlsf.h"
#include "esp_err.h"
/* multi_heap is a heap implementation for handling multiple
heterogeneous heaps in a single program.
@@ -29,70 +29,89 @@ typedef struct multi_heap_info heap_t;
/** @brief Opaque handle to a registered heap */
typedef struct multi_heap_info *multi_heap_handle_t;
/** @brief malloc() a buffer in a given heap
/** @brief Initialize the TEE heap
*
* Semantics are the same as standard malloc(), only the returned buffer will be allocated in the TEE heap.
* This function initialises the TEE heap at the specified address, and
* sets up a handle for future heap operations.
*
* @param size Size of desired buffer.
* @param start Start address of the memory to use for the TEE heap.
* @param size Size (in bytes) of the TEE heap.
*
* @return Pointer to new memory, or NULL if allocation fails.
* @return ESP_OK on success, or an `esp_err_t` error code on failure.
*/
void *tee_heap_malloc(size_t size);
/** @brief calloc() a buffer in a given heap
*
* Semantics are the same as standard calloc(), only the returned buffer will be allocated in the TEE heap.
*
* @param size Size of desired buffer.
*
* @return Pointer to new memory, or NULL if allocation fails.
*/
void *tee_heap_calloc(size_t n, size_t size);
esp_err_t esp_tee_heap_init(void *start, size_t size);
/**
* @brief allocate a chunk of memory with specific alignment
* @brief Allocate a buffer (malloc) in the TEE heap
*
* @param heap Handle to a registered heap.
* @param size size in bytes of memory chunk
* @param alignment how the memory must be aligned
* This function allocates a buffer of the specified size in the TEE heap.
* The semantics are the same as the standard malloc().
*
* @return pointer to the memory allocated, NULL on failure
* @param size The size of the desired buffer in bytes.
*
* @return A pointer to the newly allocated memory, or NULL if the allocation fails.
*/
void *tee_heap_aligned_alloc(size_t size, size_t alignment);
/** @brief free() a buffer in a given heap.
*
* Semantics are the same as standard free(), only the argument 'p' must be NULL or have been allocated in the TEE heap.
*
* @param p NULL, or a pointer previously returned from multi_heap_malloc() or multi_heap_realloc() for the same heap.
*/
void tee_heap_free(void *p);
/** @brief Register a new heap for use
*
* This function initialises a heap at the specified address, and returns a handle for future heap operations.
*
* There is no equivalent function for deregistering a heap - if all blocks in the heap are free, you can immediately start using the memory for other purposes.
*
* @param start Start address of the memory to use for a new heap.
* @param size Size (in bytes) of the new heap.
*
* @return Handle of a new heap ready for use, or NULL if the heap region was too small to be initialised.
*/
int tee_heap_register(void *start, size_t size);
void *esp_tee_heap_malloc(size_t size);
/**
* @brief Dump free and minimum free TEE heap information to stdout
* @brief Allocate and zero-initialize (calloc) a buffer in the TEE heap
*
* This function allocates a buffer for an array of 'n' elements, each of 'size' bytes,
* and initializes all bytes in the allocated storage to zero. The semantics are the same
* as the standard calloc().
*
* @param n The number of elements to allocate.
* @param size The size of each element in bytes.
*
* @return A pointer to the newly allocated and zero-initialized memory, or NULL if the allocation fails.
*/
void tee_heap_dump_free_size(void);
void *esp_tee_heap_calloc(size_t n, size_t size);
/** @brief Dump TEE heap information to stdout
/**
* @brief Allocate a memory chunk with specific alignment in the TEE heap
*
* For debugging purposes, this function dumps information about every block in the heap to stdout.
* This function allocates a chunk of memory of the specified size with the specified alignment
* in the TEE heap.
*
* @param size The size in bytes of the memory chunk.
* @param alignment The alignment requirement for the memory chunk.
*
* @return A pointer to the allocated memory, or NULL if the allocation fails.
*/
void tee_heap_dump_info(void);
void *esp_tee_heap_aligned_alloc(size_t size, size_t alignment);
/**
* @brief Free a buffer in the TEE heap
*
* This function frees a buffer that was previously allocated in the TEE heap.
* The semantics are the same as the standard free().
*
* @param p A pointer to the memory to be freed, or NULL. The pointer must have been
* returned from esp_tee_heap_malloc(), esp_tee_heap_calloc(), or esp_tee_heap_aligned_alloc().
*/
void esp_tee_heap_free(void *p);
/**
* @brief Get the size of available TEE heap
*
* @return Available TEE heap size, in bytes
*/
size_t esp_tee_heap_get_free_size(void);
/**
* @brief Get the minimum TEE heap that has ever been available
*
* @return Minimum free TEE heap ever available, in bytes
*/
size_t esp_tee_heap_get_min_free_size(void);
/**
* @brief Dump info about the entire structure of the TEE heap
*
* This function outputs detailed information about every block in the TEE heap to stdout.
* (Intended for debugging purposes)
*/
void esp_tee_heap_dump_info(void);
#ifdef __cplusplus
}

View File

@@ -9,7 +9,8 @@ if(esp_tee_build)
list(APPEND srcs "src/test_dummy_srv.c"
"src/test_interrupt.c"
"src/test_panic.c"
"src/test_sec_srv.c")
"src/test_sec_srv.c"
"src/test_heap.c")
list(APPEND priv_requires main)
else()
list(APPEND srcs "src/test_dummy_srv_wrapper.c")

View File

@@ -38,6 +38,8 @@ void NOINLINE_ATTR dummy_secure_service(int a, int b, int c, int d, int e, int f
uint32_t add_in_loop(uint32_t a, uint32_t b, uint32_t iter);
int _ss_esp_tee_test_heap_malloc_write_free(void);
#ifdef __cplusplus
}
#endif

View File

@@ -69,3 +69,7 @@ secure_services:
type: custom
function: add_in_loop
args: 3
- id: 217
type: custom
function: esp_tee_test_heap_malloc_write_free
args: 0

View File

@@ -1,16 +1,19 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "secure_service_num.h"
#include "esp_tee.h"
#include "esp_err.h"
#include "esp_rom_sys.h"
#include "esp_attr.h"
#include "multi_heap.h"
void NOINLINE_ATTR _ss_dummy_secure_service(int a, int b, int c, int d, int e, int f, int g, int h, int *i)
{
esp_rom_printf("Dummy secure service\n");
*i = a + b + c + d + e + f + g + h;
esp_rom_printf("[TEE] Heap: Free - %uB | Min free - %uB\n", esp_tee_heap_get_free_size(), esp_tee_heap_get_min_free_size());
esp_tee_heap_dump_info();
}

View File

@@ -0,0 +1,99 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_rom_sys.h"
#include "multi_heap.h"
#include "esp_rom_tlsf.h"
typedef struct {
int *ptr;
size_t size;
} alloc_block_t;
static size_t try_malloc_write_free(void)
{
size_t blk_sz = 512;
size_t max_blks = esp_tee_heap_get_free_size() / blk_sz;
size_t total_mem = 0;
alloc_block_t *blocks = malloc(sizeof(alloc_block_t) * (max_blks + 1));
if (!blocks) {
return -1;
}
int n_blks = 0;
// Attempt to allocate memory blocks and write to them
for (int i = 0; i < max_blks; i++) {
blocks[i].ptr = malloc(blk_sz);
if (!blocks[i].ptr) {
break;
}
blocks[i].size = blk_sz;
total_mem += blk_sz;
memset(blocks[i].ptr, 0xEF, blk_sz);
n_blks++;
}
// Check if there is leftover memory that can be allocated
size_t left = esp_tee_heap_get_free_size();
if (left > 24 && n_blks == max_blks) {
size_t rm_sz = left - 24; // Calculate remaining size to allocate
blocks[n_blks].ptr = malloc(rm_sz);
if (blocks[n_blks].ptr) {
blocks[n_blks].size = rm_sz;
total_mem += rm_sz;
memset(blocks[n_blks].ptr, 0xEF, rm_sz);
n_blks++;
} else {
esp_rom_printf("Failed to allocate leftover bytes\n");
free(blocks);
return -1;
}
}
int *ptr = malloc(blk_sz);
if (ptr) {
esp_rom_printf("Illegal allocation\n");
free(blocks);
return -1;
}
// Verify the integrity of allocated memory blocks
for (int i = 0; i < n_blks; i++) {
unsigned char *block_ptr = (unsigned char *)blocks[i].ptr;
for (size_t j = 0; j < blocks[i].size; j++) {
if (block_ptr[j] != 0xEF) {
esp_rom_printf("Corrupt heap\n");
free(blocks);
return -1;
}
}
free(blocks[i].ptr);
}
free(blocks);
return total_mem;
}
int _ss_esp_tee_test_heap_malloc_write_free(void)
{
size_t prev_sz = 0;
for (int i = 0; i < 3; i++) {
size_t cur_sz = try_malloc_write_free();
if (cur_sz == -1) {
return -1;
}
if (i > 0 && cur_sz != prev_sz) {
esp_rom_printf("Processed memory sizes differ between iterations!\n");
return -1;
}
prev_sz = cur_sz;
}
return 0;
}

View File

@@ -95,3 +95,8 @@ TEST_CASE("Task switching during secure service calls", "[basic]")
world = esp_cpu_get_curr_privilege_level();
TEST_ASSERT_MESSAGE((world == ESP_CPU_NS_MODE), "Current world is not NS");
}
TEST_CASE("Test TEE Heap: Malloc-write-free cycles", "[heap]")
{
TEST_ASSERT_EQUAL(0, esp_tee_service_call(1, SS_ESP_TEE_TEST_HEAP_MALLOC_WRITE_FREE));
}

View File

@@ -51,6 +51,7 @@ TEST_PARTITION_LABEL = 'test'
@idf_parametrize('target', ['esp32c6'], indirect=['target'])
def test_esp_tee(dut: IdfDut) -> None:
dut.run_all_single_board_cases(group='basic')
dut.run_all_single_board_cases(group='heap')
@pytest.mark.generic
@@ -104,7 +105,7 @@ def test_esp_tee_apm_violation(dut: IdfDut) -> None:
@idf_parametrize('target', ['esp32c6'], indirect=['target'])
def test_esp_tee_illegal_instruction(dut: IdfDut) -> None:
dut.expect_exact('Press ENTER to see the list of tests')
dut.write(f'"Test TEE-TEE violation: Illegal Instruction"')
dut.write('"Test TEE-TEE violation: Illegal Instruction"')
exc = dut.expect(r'Core ([01]) panic\'ed \(([^)]+)\)', timeout=30).group(2).decode()
if exc != 'Illegal instruction':
raise RuntimeError('Incorrect exception received!')

View File

@@ -4,8 +4,9 @@ CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_tee_ota.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions_tee_ota.csv"
# Increasing Bootloader log verbosity
# Increasing Bootloader and TEE log verbosity
CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG=y
CONFIG_SECURE_TEE_LOG_LEVEL_DEBUG=y
CONFIG_SECURE_TEE_SEC_STG_SUPPORT_SECP192R1_SIGN=y

View File

@@ -5,7 +5,6 @@ CONFIG_ESP_TASK_WDT_INIT=n
# Enabling TEE
CONFIG_SECURE_ENABLE_TEE=y
CONFIG_SECURE_TEE_DEBUG_MODE=y
CONFIG_SECURE_TEE_LOG_LEVEL_DEBUG=y
CONFIG_SECURE_TEE_TEST_MODE=y
# Custom partition table