diff --git a/components/esp_tee/test_apps/tee_cli_app/sdkconfig.defaults b/components/esp_tee/test_apps/tee_cli_app/sdkconfig.defaults index f6cf613bd1..6235cf4cf7 100644 --- a/components/esp_tee/test_apps/tee_cli_app/sdkconfig.defaults +++ b/components/esp_tee/test_apps/tee_cli_app/sdkconfig.defaults @@ -2,6 +2,9 @@ CONFIG_SECURE_ENABLE_TEE=y CONFIG_SECURE_TEE_IRAM_SIZE=0x9000 +# Enabling flash protection over SPI1 +CONFIG_SECURE_TEE_EXT_FLASH_MEMPROT_SPI1=y + # Custom partition table CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y CONFIG_PARTITION_TABLE_TWO_OTA_TEE=y diff --git a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_flash_prot.c b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_flash_prot.c index 3949878a36..ffd979db81 100644 --- a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_flash_prot.c +++ b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_flash_prot.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -21,8 +21,11 @@ #include "secure_service_num.h" #include "unity.h" +#include "ccomp_timer.h" #define BOOT_COUNT_NAMESPACE "boot_count" +#define TEST_PART_LABEL "custom" +#define TEST_BUF_SZ 256 static const char *TAG = "test_esp_tee_flash_prot"; @@ -106,6 +109,66 @@ TEST_CASE_MULTIPLE_STAGES("Test REE-TEE isolation: Flash - SPI0 (esp_partition_m test_initial_boot, test_esp_partition_mmap_api, test_esp_partition_mmap_api, test_esp_partition_mmap_api, test_esp_partition_mmap_api); +#if CONFIG_SECURE_TEE_EXT_FLASH_MEMPROT_SPI1 +static void test_esp_partition_api_r(const esp_partition_t *part) +{ + TEST_ASSERT_NOT_NULL(part); + uint8_t buf_r[128]; + memset(buf_r, 0x00, sizeof(buf_r)); + TEST_ESP_ERR(ESP_FAIL, esp_partition_read(part, 0x00, buf_r, sizeof(buf_r))); +} + +static void test_esp_partition_api_w(const esp_partition_t *part) +{ + TEST_ASSERT_NOT_NULL(part); + uint8_t buf_w[128]; + memset(buf_w, 0xA5, sizeof(buf_w)); + TEST_ESP_OK(esp_partition_write(part, 0x00, buf_w, sizeof(buf_w))); +} + +static void test_esp_partition_api_e(const esp_partition_t *part) +{ + TEST_ASSERT_NOT_NULL(part); + TEST_ESP_OK(esp_partition_erase_range(part, 0x00, SPI_FLASH_SEC_SIZE)); +} + +static void test_esp_partition_api(void) +{ + uint8_t boot_count = get_boot_count_from_nvs(); + boot_count++; + set_boot_count_in_nvs(boot_count); + + const esp_partition_t *part = NULL; + switch (boot_count) { + case 2: + part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_TEE_0, NULL); + test_esp_partition_api_r(part); + break; + case 3: + part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_TEE_1, NULL); + test_esp_partition_api_w(part); + break; + case 4: + part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_TEE_SEC_STORAGE, NULL); + test_esp_partition_api_w(part); + break; + case 5: + part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_TEE_OTA, NULL); + test_esp_partition_api_e(part); + break; + default: + TEST_FAIL_MESSAGE("Unexpected stage"); + break; + } + + esp_restart(); +} + +TEST_CASE_MULTIPLE_STAGES("Test REE-TEE isolation: Flash - SPI1 (esp_partition)", "[flash_prot][timeout=60]", + test_initial_boot, test_esp_partition_api, test_esp_partition_api, + test_esp_partition_api, test_esp_partition_api); +#endif + /* ---------------------------------------------- API family 2: spi_flash ------------------------------------------------- */ static void test_spi_flash_mmap_api(void) @@ -149,3 +212,172 @@ static void test_spi_flash_mmap_api(void) TEST_CASE_MULTIPLE_STAGES("Test REE-TEE isolation: Flash - SPI0 (spi_flash_mmap)", "[flash_prot][timeout=60]", test_initial_boot, test_spi_flash_mmap_api, test_spi_flash_mmap_api, test_spi_flash_mmap_api); + +/* ---------------------------------------------- API family 3: esp_flash ------------------------------------------------- */ + +#if CONFIG_SECURE_TEE_EXT_FLASH_MEMPROT_SPI1 +static void test_esp_flash_api_r(uint32_t paddr) +{ + uint8_t buf_r[128]; + memset(buf_r, 0x00, sizeof(buf_r)); + TEST_ESP_ERR(ESP_FAIL, esp_flash_read(NULL, buf_r, paddr, sizeof(buf_r))); +} + +static void test_esp_flash_api_w(uint32_t paddr) +{ + uint8_t buf_w[128]; + memset(buf_w, 0xA5, sizeof(buf_w)); + TEST_ESP_OK(esp_flash_write(NULL, buf_w, paddr, sizeof(buf_w))); +} + +static void test_esp_flash_api_e(uint32_t paddr) +{ + TEST_ESP_OK(esp_flash_erase_region(NULL, paddr, SPI_FLASH_SEC_SIZE)); +} + +static void test_esp_flash_api(void) +{ + uint8_t boot_count = get_boot_count_from_nvs(); + boot_count++; + set_boot_count_in_nvs(boot_count); + + const esp_partition_t *part = NULL; + + switch (boot_count) { + case 2: + part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_TEE_0, NULL); + TEST_ASSERT_NOT_NULL(part); + test_esp_flash_api_w(part->address); + break; + case 3: + part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_TEE_1, NULL); + TEST_ASSERT_NOT_NULL(part); + test_esp_flash_api_r(part->address); + break; + case 4: + part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_TEE_SEC_STORAGE, NULL); + TEST_ASSERT_NOT_NULL(part); + test_esp_flash_api_e(part->address); + break; + case 5: + part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_TEE_OTA, NULL); + TEST_ASSERT_NOT_NULL(part); + test_esp_flash_api_w(part->address); + break; + default: + TEST_FAIL_MESSAGE("Unexpected stage"); + break; + } + + esp_restart(); +} + +TEST_CASE_MULTIPLE_STAGES("Test REE-TEE isolation: Flash - SPI1 (esp_flash)", "[flash_prot][timeout=60]", + test_initial_boot, test_esp_flash_api, test_esp_flash_api, test_esp_flash_api, + test_esp_flash_api); + +/* ---------------------------------------------- API family 4: esp_rom ------------------------------------------------- */ + +static IRAM_ATTR void test_esp_rom_spiflash_api_r(uint32_t paddr) +{ + uint32_t buf_r[32]; + memset(buf_r, 0x00, sizeof(buf_r)); + esp_rom_spiflash_result_t rc = esp_rom_spiflash_read(paddr, buf_r, sizeof(buf_r)); + TEST_ASSERT_EQUAL_HEX(ESP_ROM_SPIFLASH_RESULT_OK, rc); + ESP_LOG_BUFFER_HEXDUMP(TAG, buf_r, sizeof(buf_r), ESP_LOG_INFO); +} + +static IRAM_ATTR void test_esp_rom_spiflash_api_w(uint32_t paddr) +{ + uint32_t buf_w[32]; + memset(buf_w, 0xA5, sizeof(buf_w)); + spi_flash_disable_interrupts_caches_and_other_cpu(); + esp_rom_spiflash_result_t rc = esp_rom_spiflash_write(paddr, buf_w, sizeof(buf_w)); + spi_flash_enable_interrupts_caches_and_other_cpu(); + TEST_ASSERT_EQUAL_HEX(ESP_ROM_SPIFLASH_RESULT_OK, rc); +} + +static IRAM_ATTR void test_esp_rom_spiflash_api_e(uint32_t paddr) +{ + spi_flash_disable_interrupts_caches_and_other_cpu(); + esp_rom_spiflash_result_t rc = esp_rom_spiflash_erase_area(paddr, SPI_FLASH_SEC_SIZE); + spi_flash_enable_interrupts_caches_and_other_cpu(); + TEST_ASSERT_EQUAL_HEX(ESP_ROM_SPIFLASH_RESULT_OK, rc); +} + +static void test_esp_rom_spiflash_api(void) +{ + uint8_t boot_count = get_boot_count_from_nvs(); + boot_count++; + set_boot_count_in_nvs(boot_count); + + const esp_partition_t *part = NULL; + + switch (boot_count) { + case 2: + part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_TEE_0, NULL); + TEST_ASSERT_NOT_NULL(part); + test_esp_rom_spiflash_api_r(part->address); + TEST_FAIL_MESSAGE("System fault should have been generated"); + break; + case 3: + part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_TEE_1, NULL); + TEST_ASSERT_NOT_NULL(part); + test_esp_rom_spiflash_api_w(part->address); + TEST_FAIL_MESSAGE("System fault should have been generated"); + break; + case 4: + part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_TEE_SEC_STORAGE, NULL); + TEST_ASSERT_NOT_NULL(part); + test_esp_rom_spiflash_api_e(part->address); + TEST_FAIL_MESSAGE("System fault should have been generated"); + break; + case 5: + part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_TEE_OTA, NULL); + TEST_ASSERT_NOT_NULL(part); + test_esp_rom_spiflash_api_w(part->address); + TEST_FAIL_MESSAGE("System fault should have been generated"); + break; + default: + TEST_FAIL_MESSAGE("Unexpected stage"); + break; + } +} + +TEST_CASE_MULTIPLE_STAGES("Test REE-TEE isolation: Flash - SPI1 (esp_rom_spiflash)", "[flash_prot][timeout=60]", + test_initial_boot, test_esp_rom_spiflash_api, test_esp_rom_spiflash_api, + test_esp_rom_spiflash_api, test_esp_rom_spiflash_api); +#endif + +TEST_CASE("Test TEE flash read/write performance", "[flash_prot]") +{ + const esp_partition_t *part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, TEST_PART_LABEL); + TEST_ASSERT_NOT_NULL(part); + + TEST_ESP_OK(esp_partition_erase_range(part, 0x00, part->size)); + TEST_ASSERT_TRUE((part->size % TEST_BUF_SZ) == 0); + + ESP_LOGI(TAG, "R/W operations over a %luKB partition in %luB chunks...", part->size / 1024, TEST_BUF_SZ); + + uint8_t buf_w[TEST_BUF_SZ]; + memset(buf_w, 0xA5, sizeof(buf_w)); + + float write_usec, read_usec; + ccomp_timer_start(); + for (size_t offs = 0; offs < part->size; offs += TEST_BUF_SZ) { + TEST_ESP_OK(esp_partition_write(part, offs, buf_w, TEST_BUF_SZ)); + } + write_usec = ccomp_timer_stop(); + ESP_LOGI(TAG, "[Time taken] Write: %.2fus", write_usec); + + uint8_t buf_r[TEST_BUF_SZ] = {}; + + ccomp_timer_start(); + for (size_t offs = 0; offs < part->size; offs += TEST_BUF_SZ) { + TEST_ESP_OK(esp_partition_read(part, offs, buf_r, TEST_BUF_SZ)); + } + read_usec = ccomp_timer_stop(); + + TEST_ASSERT_EQUAL_HEX8_ARRAY(buf_w, buf_r, TEST_BUF_SZ); + ESP_LOGI(TAG, "[Time taken] Read: %.2fus", read_usec); +} diff --git a/components/esp_tee/test_apps/tee_test_fw/partitions.csv b/components/esp_tee/test_apps/tee_test_fw/partitions.csv index e5f7bb21fd..d79624198b 100644 --- a/components/esp_tee/test_apps/tee_test_fw/partitions.csv +++ b/components/esp_tee/test_apps/tee_test_fw/partitions.csv @@ -4,3 +4,4 @@ tee, app, tee_0, , 192K, secure_storage, data, tee_sec_stg, , 64K, factory, app, factory, , 512K, nvs, data, nvs, , 24K, +custom, data, , , 1M diff --git a/components/esp_tee/test_apps/tee_test_fw/partitions_tee_ota.csv b/components/esp_tee/test_apps/tee_test_fw/partitions_tee_ota.csv index 7856d6bb34..6708e6401e 100644 --- a/components/esp_tee/test_apps/tee_test_fw/partitions_tee_ota.csv +++ b/components/esp_tee/test_apps/tee_test_fw/partitions_tee_ota.csv @@ -8,3 +8,4 @@ ota_0, app, ota_0, , 512K, ota_1, app, ota_1, , 512K, otadata, data, ota, , 8K, nvs, data, nvs, , 24K, +custom, data, , , 1M diff --git a/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py b/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py index 0d41fef279..6bbed77791 100644 --- a/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py +++ b/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 from enum import Enum from typing import Any @@ -15,6 +15,11 @@ CONFIGS_OTA = [ pytest.param('ota', marks=[pytest.mark.esp32c6]) ] +CONFIGS_ALL = [ + pytest.param('default', marks=[pytest.mark.esp32c6]), + pytest.param('ota', marks=[pytest.mark.esp32c6]) +] + TEE_VIOLATION_TEST_EXC_RSN: Dict[str, Any] = { 'esp32c6': { ('Reserved', 'W1'): 'Store access fault', @@ -42,6 +47,8 @@ REE_ISOLATION_TEST_EXC_RSN: Dict[str, Any] = { TEE_APM_VIOLATION_EXC_CHK = ['AES', 'eFuse', 'MMU'] +TEST_PARTITION_LABEL = 'test' + # ---------------- TEE default tests ---------------- @@ -52,14 +59,14 @@ def test_esp_tee(dut: IdfDut) -> None: @pytest.mark.generic -@pytest.mark.parametrize('config', CONFIGS_DEFAULT, indirect=True) +@pytest.mark.parametrize('config', CONFIGS_ALL, indirect=True) def test_esp_tee_crypto_aes(dut: IdfDut) -> None: dut.run_all_single_board_cases(group='aes') dut.run_all_single_board_cases(group='aes-gcm') @pytest.mark.generic -@pytest.mark.parametrize('config', CONFIGS_DEFAULT, indirect=True) +@pytest.mark.parametrize('config', CONFIGS_ALL, indirect=True) def test_esp_tee_crypto_sha(dut: IdfDut) -> None: dut.run_all_single_board_cases(group='mbedtls') dut.run_all_single_board_cases(group='hw_crypto') @@ -67,7 +74,7 @@ def test_esp_tee_crypto_sha(dut: IdfDut) -> None: # NOTE: Stress testing the AES performance case for interrupt-related edge-cases @pytest.mark.generic -@pytest.mark.parametrize('config', CONFIGS_DEFAULT, indirect=True) +@pytest.mark.parametrize('config', CONFIGS_ALL, indirect=True) def test_esp_tee_aes_perf(dut: IdfDut) -> None: # start test for i in range(24): @@ -134,17 +141,56 @@ def test_esp_tee_isolation_checks(dut: IdfDut) -> None: raise RuntimeError('Incorrect exception received!') dut.expect('Exception origin: U-mode') +# ---------------- TEE Flash Protection Tests ---------------- + + +class TeeFlashAccessApi(Enum): + ESP_PARTITION_MMAP = 1 + SPI_FLASH_MMAP = 2 + ESP_PARTITION = 3 + ESP_FLASH = 4 + ESP_ROM_SPIFLASH = 5 + + +def check_panic_or_reset(dut: IdfDut) -> None: + try: + exc = dut.expect(r"Core ([01]) panic\'ed \(([^)]+)\)", timeout=5).group(2).decode() + if exc not in {'Cache error', 'Authority exception'}: + raise RuntimeError('Flash operation incorrect exception') + except Exception: + rst_rsn = dut.expect(r'rst:(0x[0-9A-Fa-f]+) \(([^)]+)\)', timeout=5).group(2).decode() + # Fault assert check produces this reset reason + if rst_rsn != 'LP_SW_HPSYS': + raise RuntimeError('Flash operation incorrect reset reason') + + +def run_multiple_stages(dut: IdfDut, test_case_num: int, stages: int, api: TeeFlashAccessApi) -> None: + exp_seq = { + TeeFlashAccessApi.ESP_PARTITION: ['read', 'program_page', 'program_page', 'erase_sector'], + TeeFlashAccessApi.ESP_FLASH: ['program_page', 'read', 'erase_sector', 'program_page'] + } -def run_multiple_stages(dut: IdfDut, test_case_num: int, stages: int) -> None: for stage in range(1, stages + 1): dut.write(str(test_case_num)) dut.expect(r'\s+\((\d+)\)\s+"([^"]+)"\r?\n', timeout=30) dut.write(str(stage)) if 1 < stage <= stages: - rst_rsn = dut.expect(r"Core ([01]) panic\'ed \(([^)]+)\)", timeout=30).group(2).decode() - if rst_rsn != 'Cache error': - raise RuntimeError('Incorrect reset reason observed after TEE image failure!') + if api in exp_seq: + try: + match = dut.expect(r'\[_ss_spi_flash_hal_(\w+)\] Illegal flash access at \s*(0x[0-9a-fA-F]+)', timeout=5) + fault_api = match.group(1).decode() + if fault_api != exp_seq[api][stage - 2]: + raise RuntimeError('Flash operation address check failed') + except Exception: + # NOTE: The esp_partition_read API handles both decrypted + # and plaintext reads. When flash encryption is enabled, + # it uses the MMU HAL instead of the SPI flash HAL. + exc = dut.expect(r"Core ([01]) panic\'ed \(([^)]+)\)", timeout=5).group(2).decode() + if exc != 'Cache error': + raise RuntimeError('Flash operation incorrect exception') + else: + check_panic_or_reset(dut) if stage != stages: dut.expect_exact('Press ENTER to see the list of tests.') @@ -160,8 +206,8 @@ def test_esp_tee_flash_prot_esp_partition_mmap(dut: IdfDut) -> None: # start test extra_data = dut.parse_test_menu() for test_case in extra_data: - if test_case.name == 'Test REE-TEE isolation: Flash - SPI1 (esp_partition_mmap)': - run_multiple_stages(dut, test_case.index, len(test_case.subcases)) + if test_case.name == 'Test REE-TEE isolation: Flash - SPI0 (esp_partition_mmap)': + run_multiple_stages(dut, test_case.index, len(test_case.subcases), TeeFlashAccessApi.ESP_PARTITION_MMAP) else: continue @@ -177,7 +223,55 @@ def test_esp_tee_flash_prot_spi_flash_mmap(dut: IdfDut) -> None: extra_data = dut.parse_test_menu() for test_case in extra_data: if test_case.name == 'Test REE-TEE isolation: Flash - SPI0 (spi_flash_mmap)': - run_multiple_stages(dut, test_case.index, len(test_case.subcases)) + run_multiple_stages(dut, test_case.index, len(test_case.subcases), TeeFlashAccessApi.SPI_FLASH_MMAP) + else: + continue + + +@pytest.mark.generic +@pytest.mark.parametrize('config', CONFIGS_OTA, indirect=True) +@pytest.mark.parametrize('skip_autoflash', ['y'], indirect=True) +def test_esp_tee_flash_prot_esp_rom_spiflash(dut: IdfDut) -> None: + # Flash the bootloader, TEE and REE firmware + dut.serial.custom_flash() + + # start test + extra_data = dut.parse_test_menu() + for test_case in extra_data: + if test_case.name == 'Test REE-TEE isolation: Flash - SPI1 (esp_rom_spiflash)': + run_multiple_stages(dut, test_case.index, len(test_case.subcases), TeeFlashAccessApi.ESP_ROM_SPIFLASH) + else: + continue + + +@pytest.mark.generic +@pytest.mark.parametrize('config', CONFIGS_OTA, indirect=True) +@pytest.mark.parametrize('skip_autoflash', ['y'], indirect=True) +def test_esp_tee_flash_prot_esp_partition(dut: IdfDut) -> None: + # Flash the bootloader, TEE and REE firmware + dut.serial.custom_flash() + + # start test + extra_data = dut.parse_test_menu() + for test_case in extra_data: + if test_case.name == 'Test REE-TEE isolation: Flash - SPI1 (esp_partition)': + run_multiple_stages(dut, test_case.index, len(test_case.subcases), TeeFlashAccessApi.ESP_PARTITION) + else: + continue + + +@pytest.mark.generic +@pytest.mark.parametrize('config', CONFIGS_OTA, indirect=True) +@pytest.mark.parametrize('skip_autoflash', ['y'], indirect=True) +def test_esp_tee_flash_prot_esp_flash(dut: IdfDut) -> None: + # Flash the bootloader, TEE and REE firmware + dut.serial.custom_flash() + + # start test + extra_data = dut.parse_test_menu() + for test_case in extra_data: + if test_case.name == 'Test REE-TEE isolation: Flash - SPI1 (esp_flash)': + run_multiple_stages(dut, test_case.index, len(test_case.subcases), TeeFlashAccessApi.ESP_FLASH) else: continue diff --git a/components/esp_tee/test_apps/tee_test_fw/sdkconfig.ci.ota b/components/esp_tee/test_apps/tee_test_fw/sdkconfig.ci.ota index f6984d573c..ba0a656949 100644 --- a/components/esp_tee/test_apps/tee_test_fw/sdkconfig.ci.ota +++ b/components/esp_tee/test_apps/tee_test_fw/sdkconfig.ci.ota @@ -9,3 +9,6 @@ CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG=y # secure storage key slot for attestation CONFIG_SECURE_TEE_ATT_KEY_SLOT_ID=14 + +# Enabling flash protection over SPI1 +CONFIG_SECURE_TEE_EXT_FLASH_MEMPROT_SPI1=y