From 6bd173b80600f8d42503151a75bd03a61c714dd5 Mon Sep 17 00:00:00 2001 From: Laukik Hase Date: Fri, 27 Mar 2026 17:04:10 +0530 Subject: [PATCH] ci(esp_tee): Fix TEE test-suite failures with Secure Boot enabled --- .../test_apps/tee_cli_app/sdkconfig.defaults | 3 + ...m => secure_boot_signing_key_rsa_3072.pem} | 0 .../esp_tee/test_apps/tee_test_fw/conftest.py | 59 +++++++-------- .../tee_test_fw/main/test_esp_tee_ota.c | 4 +- .../tee_test_fw/main/test_esp_tee_sec_stg.c | 4 + .../tee_test_fw/pytest_esp_tee_ut.py | 71 ++++++++++++------ .../test_apps/tee_test_fw/sdkconfig.defaults | 3 + .../tee_test_fw/tee_exception_test_map.py | 6 ++ .../test_keys/tee_sec_stg_hmac_key.bin | Bin 0 -> 32 bytes 9 files changed, 90 insertions(+), 60 deletions(-) rename components/esp_tee/test_apps/tee_cli_app/test_keys/{secure_boot_signing_key.pem => secure_boot_signing_key_rsa_3072.pem} (100%) create mode 100644 components/esp_tee/test_apps/tee_test_fw/test_keys/tee_sec_stg_hmac_key.bin 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 c35d9943e4b..a0ae715a92c 100644 --- a/components/esp_tee/test_apps/tee_cli_app/sdkconfig.defaults +++ b/components/esp_tee/test_apps/tee_cli_app/sdkconfig.defaults @@ -20,3 +20,6 @@ CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=30000 CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN=y CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE=y CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH="test_certs/server_cert.pem" + +# Takes effect only when Secure boot is enabled +CONFIG_SECURE_BOOT_FLASH_BOOTLOADER_DEFAULT=y diff --git a/components/esp_tee/test_apps/tee_cli_app/test_keys/secure_boot_signing_key.pem b/components/esp_tee/test_apps/tee_cli_app/test_keys/secure_boot_signing_key_rsa_3072.pem similarity index 100% rename from components/esp_tee/test_apps/tee_cli_app/test_keys/secure_boot_signing_key.pem rename to components/esp_tee/test_apps/tee_cli_app/test_keys/secure_boot_signing_key_rsa_3072.pem 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 66cdddb7d61..2a2de9e4f3c 100644 --- a/components/esp_tee/test_apps/tee_test_fw/conftest.py +++ b/components/esp_tee/test_apps/tee_test_fw/conftest.py @@ -202,25 +202,6 @@ class TEESerial(IdfSerial): def _get_flash_size(self) -> Any: return self.app.sdkconfig.get('ESPTOOLPY_FLASHSIZE', '') - @EspSerial.use_esptool() - def bootloader_force_flash_if_req(self) -> None: - # Forcefully flash the bootloader only if security features are enabled - if any( - ( - self.app.sdkconfig.get('SECURE_BOOT', True), - self.app.sdkconfig.get('SECURE_FLASH_ENC_ENABLED', True), - ) - ): - offs = int(self.app.sdkconfig.get('BOOTLOADER_OFFSET_IN_FLASH', 0)) - bootloader_path = os.path.join(self.app.binary_path, 'bootloader', 'bootloader.bin') - encrypt = '--encrypt' if self.app.sdkconfig.get('SECURE_FLASH_ENC_ENABLED') else '' - flash_size = self._get_flash_size() - - esptool.main( - f'--no-stub write-flash {offs} {bootloader_path} --force {encrypt} --flash-size {flash_size}'.split(), - esp=self.esp, - ) - @EspSerial.use_esptool() def custom_erase_partition(self, partition: str) -> None: if self.app.sdkconfig.get('SECURE_ENABLE_SECURE_ROM_DL_MODE'): @@ -294,26 +275,18 @@ class TEESerial(IdfSerial): if os.path.exists(file): os.remove(file) - @EspSerial.use_esptool() - def custom_flash(self) -> None: - self.bootloader_force_flash_if_req() - self.flash() - @EspSerial.use_esptool() def custom_flash_w_test_tee_img_gen(self) -> None: - self.bootloader_force_flash_if_req() self.flash() self.copy_test_tee_img('ota_1', False) @EspSerial.use_esptool() def custom_flash_w_test_tee_img_rb(self) -> None: - self.bootloader_force_flash_if_req() self.flash() self.copy_test_tee_img('ota_1', True) @EspSerial.use_esptool() def custom_flash_with_empty_sec_stg(self) -> None: - self.bootloader_force_flash_if_req() self.flash() self.custom_erase_partition('secure_storage') @@ -354,12 +327,11 @@ class TEESerial(IdfSerial): }, ] - NVS_KEYS_B64 = 'MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzPMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzA==' - + TEST_KEYS_DIR = Path(__file__).parent / 'test_keys' TMP_DIR = Path('tmp') - NVS_KEYS_PATH = TMP_DIR / 'nvs_keys.bin' NVS_CSV_PATH = TMP_DIR / 'tee_sec_stg_val.csv' NVS_BIN_PATH = TMP_DIR / 'tee_sec_stg_nvs.bin' + NVS_KEYS_FILE = 'tee_sec_stg_nvs_keys.bin' def run_command(self, command: list[str]) -> None: try: @@ -391,7 +363,6 @@ class TEESerial(IdfSerial): input_path = tmp_dir / entry['input'] self.write_keys_to_file(entry['b64'], input_path) entry['input'] = str(input_path) - self.write_keys_to_file(self.NVS_KEYS_B64, self.NVS_KEYS_PATH) idf_path = Path(os.environ['IDF_PATH']) ESP_TEE_SEC_STG_KEYGEN = os.path.join( @@ -401,6 +372,30 @@ class TEESerial(IdfSerial): idf_path, 'components', 'nvs_flash', 'nvs_partition_generator', 'nvs_partition_gen.py' ) + nvs_keys = tmp_dir / self.NVS_KEYS_FILE + if self.app.sdkconfig.get('SECURE_TEE_SEC_STG_MODE_RELEASE'): + hmac_key_src = self.TEST_KEYS_DIR / 'tee_sec_stg_hmac_key.bin' + self.run_command( + [ + sys.executable, + NVS_PARTITION_GEN, + 'generate-key', + '--key_protect_hmac', + '--kp_hmac_inputkey', + str(hmac_key_src), + '--keyfile', + self.NVS_KEYS_FILE, + '--outdir', + str(tmp_dir), + ] + ) + nvs_keys = tmp_dir / 'keys' / self.NVS_KEYS_FILE + else: + NVS_KEYS_DEV_B64 = ( + 'MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzPMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzA==' + ) + self.write_keys_to_file(NVS_KEYS_DEV_B64, nvs_keys) + cmds = [ [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 []) @@ -410,7 +405,6 @@ class TEESerial(IdfSerial): csv_path = self.create_tee_sec_stg_csv(tmp_dir) nvs_bin = self.NVS_BIN_PATH - nvs_keys = self.NVS_KEYS_PATH size = self.app.partition_table['secure_storage']['size'] cmds.append( @@ -430,7 +424,6 @@ class TEESerial(IdfSerial): for cmd in cmds: self.run_command(cmd) - self.bootloader_force_flash_if_req() self.flash() self.custom_erase_partition('secure_storage') self.custom_write_partition('secure_storage', nvs_bin) diff --git a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_ota.c b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_ota.c index 6096e49ac3e..5e305af3839 100644 --- a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_ota.c +++ b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_ota.c @@ -127,8 +127,8 @@ TEST_CASE("Test TEE OTA - Corrupted image", "[ota_neg_2]") /* Corrupting the image */ ESP_LOGI(TAG, "Corrupting the image at some offset..."); uint32_t corrupt[8] = {[0 ... 7] = 0x0BADC0DE}; - curr_write_offset -= (2 * FLASH_SECTOR_SIZE + sizeof(corrupt)); - TEST_ESP_OK(esp_tee_ota_write(curr_write_offset, (const void *)corrupt, sizeof(corrupt))); + uint32_t offs = SOC_MMU_PAGE_SIZE + 0x200; + TEST_ESP_OK(esp_tee_ota_write(offs, (const void *)corrupt, sizeof(corrupt))); TEST_ESP_ERR(ESP_ERR_IMAGE_INVALID, esp_tee_ota_end()); } 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 06860307328..8c43a8f316b 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 @@ -425,6 +425,10 @@ static void do_ecdsa_sign_and_verify(const esp_tee_sec_storage_key_cfg_t *cfg, c TEST_ESP_OK(verify_ecdsa_sign(cfg->type, digest, digest_len, &pubkey, &sign)); } +/* NOTE: In release mode (CONFIG_SECURE_TEE_SEC_STG_MODE_RELEASE), the test expects + * the eFuse-burned HMAC key used for TEE secure storage to be available at + * the path "test_keys/tee_sec_stg_hmac_key.bin" + */ TEST_CASE("Test TEE Secure Storage - Host-generated keys", "[sec_storage_host_keygen]") { const char *aes_key_ids[] = { "aes256_key0", "aes256_key1" }; 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 73590e48d2a..9e841d68bbd 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 @@ -6,7 +6,7 @@ from enum import Enum import pytest from pytest_embedded_idf import IdfDut from pytest_embedded_idf.utils import idf_parametrize -from tee_exception_cfg import TEE_EXCEPTION_TEST_MAP +from tee_exception_test_map import TEE_EXCEPTION_TEST_MAP # ---------------- Pytest build parameters ---------------- @@ -20,6 +20,12 @@ CONFIG_DEFAULT = [ ] CONFIG_OTA = [ + # 'config, target, markers', + ('tee_ota', target, (pytest.mark.generic,)) + for target in TESTING_TARGETS +] + +CONFIG_OTA_NO_AUTOFLASH = [ # 'config, target, skip_autoflash, markers', ('tee_ota', target, 'y', (pytest.mark.generic,)) for target in TESTING_TARGETS @@ -50,8 +56,10 @@ def test_esp_tee(dut: IdfDut) -> None: CONFIG_ALL, indirect=['config', 'target'], ) -@pytest.mark.skipif(targets=['esp32c61'], reason='Not supported') def test_esp_tee_crypto_aes(dut: IdfDut) -> None: + if dut.target == 'esp32c61': + pytest.skip(f'AES not supported on {dut.target}') + dut.run_all_single_board_cases(group='aes') dut.run_all_single_board_cases(group='aes-gcm') @@ -72,8 +80,10 @@ def test_esp_tee_crypto_sha(dut: IdfDut) -> None: CONFIG_ALL, indirect=['config', 'target'], ) -@pytest.mark.skipif(targets=['esp32c61'], reason='Not supported') def test_esp_tee_aes_perf(dut: IdfDut) -> None: + if dut.target == 'esp32c61': + pytest.skip(f'AES not supported on {dut.target}') + for i in range(10): dut.run_all_single_board_cases(name=['mbedtls AES performance']) @@ -121,6 +131,13 @@ def test_esp_tee_isolation_checks(dut: IdfDut) -> None: for test_name, expected in cfg.items(): run_exception_case(dut, 'Test REE-TEE isolation', test_name, expected, check_origin=True) + # ESP32-C61: MMU-spillover gracefully reboots instead of panicking + if dut.target == 'esp32c61': + dut.skip_decode_panic = True + dut.expect_exact('Press ENTER to see the list of tests') + dut.write('"Test REE-TEE isolation: MMU-spillover"') + dut.expect_exact('Failed MMU operation, rebooting!', timeout=10) + @idf_parametrize( 'config, target, markers', @@ -238,8 +255,6 @@ def run_flash_access_test(dut: IdfDut, api: TeeFlashAccessApi, test_name: str) - # Panics are expected during these tests dut.skip_decode_panic = True - dut.serial.custom_flash() - extra_data = dut._parse_test_menu() test_case = next((tc for tc in extra_data if tc.name == test_name), None) @@ -250,9 +265,9 @@ def run_flash_access_test(dut: IdfDut, api: TeeFlashAccessApi, test_name: str) - @idf_parametrize( - 'config, target, skip_autoflash, markers', + 'config, target, markers', CONFIG_OTA, - indirect=['config', 'target', 'skip_autoflash'], + indirect=['config', 'target'], ) def test_esp_tee_flash_prot_esp_partition_mmap(dut: IdfDut) -> None: run_flash_access_test( @@ -261,9 +276,9 @@ def test_esp_tee_flash_prot_esp_partition_mmap(dut: IdfDut) -> None: @idf_parametrize( - 'config, target, skip_autoflash, markers', + 'config, target, markers', CONFIG_OTA, - indirect=['config', 'target', 'skip_autoflash'], + indirect=['config', 'target'], ) def test_esp_tee_flash_prot_spi_flash_mmap(dut: IdfDut) -> None: run_flash_access_test( @@ -272,9 +287,9 @@ def test_esp_tee_flash_prot_spi_flash_mmap(dut: IdfDut) -> None: @idf_parametrize( - 'config, target, skip_autoflash, markers', + 'config, target, markers', CONFIG_OTA, - indirect=['config', 'target', 'skip_autoflash'], + indirect=['config', 'target'], ) def test_esp_tee_flash_prot_esp_rom_spiflash(dut: IdfDut) -> None: run_flash_access_test( @@ -283,18 +298,18 @@ def test_esp_tee_flash_prot_esp_rom_spiflash(dut: IdfDut) -> None: @idf_parametrize( - 'config, target, skip_autoflash, markers', + 'config, target, markers', CONFIG_OTA, - indirect=['config', 'target', 'skip_autoflash'], + indirect=['config', 'target'], ) def test_esp_tee_flash_prot_esp_partition(dut: IdfDut) -> None: run_flash_access_test(dut, TeeFlashAccessApi.ESP_PARTITION, 'Test REE-TEE isolation: Flash - SPI1 (esp_partition)') @idf_parametrize( - 'config, target, skip_autoflash, markers', + 'config, target, markers', CONFIG_OTA, - indirect=['config', 'target', 'skip_autoflash'], + indirect=['config', 'target'], ) def test_esp_tee_flash_prot_esp_flash(dut: IdfDut) -> None: run_flash_access_test(dut, TeeFlashAccessApi.ESP_FLASH, 'Test REE-TEE isolation: Flash - SPI1 (esp_flash)') @@ -303,9 +318,11 @@ def test_esp_tee_flash_prot_esp_flash(dut: IdfDut) -> None: # ---------------- TEE Local OTA tests ---------------- -@pytest.mark.generic -@idf_parametrize('config', ['tee_ota'], indirect=['config']) -@idf_parametrize('target', TESTING_TARGETS, indirect=['target']) +@idf_parametrize( + 'config, target, markers', + CONFIG_OTA, + indirect=['config', 'target'], +) def test_esp_tee_ota_negative(dut: IdfDut) -> None: # start test dut.run_all_single_board_cases(group='ota_neg_1', timeout=10) @@ -313,7 +330,7 @@ def test_esp_tee_ota_negative(dut: IdfDut) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_ota_corrupted_img(dut: IdfDut) -> None: @@ -347,7 +364,7 @@ def tee_ota_stage_checks(dut: IdfDut, stage: TeeOtaStage, offset: str) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_ota_reboot_without_ota_end(dut: IdfDut) -> None: @@ -370,7 +387,7 @@ def test_esp_tee_ota_reboot_without_ota_end(dut: IdfDut) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_ota_valid_img(dut: IdfDut) -> None: @@ -401,7 +418,7 @@ def test_esp_tee_ota_valid_img(dut: IdfDut) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_ota_rollback(dut: IdfDut) -> None: @@ -440,7 +457,7 @@ def test_esp_tee_ota_rollback(dut: IdfDut) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_secure_storage(dut: IdfDut) -> None: @@ -452,11 +469,15 @@ def test_esp_tee_secure_storage(dut: IdfDut) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_secure_storage_with_host_img(dut: IdfDut) -> None: # Flash image and write the secure_storage partition with host-generated keys + + # NOTE: In release mode (CONFIG_SECURE_TEE_SEC_STG_MODE_RELEASE), the test + # expects the eFuse-burned HMAC key used for TEE secure storage to be available + # at the path "test_keys/tee_sec_stg_hmac_key.bin" dut.serial.custom_flash_with_host_gen_sec_stg_img() dut.run_all_single_board_cases(group='sec_storage_host_keygen') @@ -467,7 +488,7 @@ def test_esp_tee_secure_storage_with_host_img(dut: IdfDut) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_attestation(dut: IdfDut) -> None: diff --git a/components/esp_tee/test_apps/tee_test_fw/sdkconfig.defaults b/components/esp_tee/test_apps/tee_test_fw/sdkconfig.defaults index 9abd3154f4c..ad47c1b6908 100644 --- a/components/esp_tee/test_apps/tee_test_fw/sdkconfig.defaults +++ b/components/esp_tee/test_apps/tee_test_fw/sdkconfig.defaults @@ -15,3 +15,6 @@ CONFIG_PARTITION_TABLE_OFFSET=0xF000 # Increasing TEE I/DRAM size CONFIG_SECURE_TEE_IRAM_SIZE=0x8800 CONFIG_SECURE_TEE_DRAM_SIZE=0x5800 + +# Takes effect only when Secure boot is enabled +CONFIG_SECURE_BOOT_FLASH_BOOTLOADER_DEFAULT=y 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 2527d4df93f..c25fce29110 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 @@ -78,6 +78,12 @@ _TARGET_OVERRIDES: dict[str, dict[str, Any]] = { }, }, 'esp32c61': { + # NOTE: On ESP32-C61, MMU-spillover does not raise a CPU panic — the TEE + # test fills the bad mapping with a poison pattern and calls esp_restart(). + # Verified separately in the pytest, so drop it from the panic-driven map. + 'ree_isolation': { + '_remove': ['MMU-spillover'], + }, # NOTE: ESP32-C61 does not support the following peripherals 'apm_violation': { '_remove': ['AES', 'HMAC', 'DS'], diff --git a/components/esp_tee/test_apps/tee_test_fw/test_keys/tee_sec_stg_hmac_key.bin b/components/esp_tee/test_apps/tee_test_fw/test_keys/tee_sec_stg_hmac_key.bin new file mode 100644 index 0000000000000000000000000000000000000000..9868f801a9a3230283e0936b52fe70a9f68a224c GIT binary patch literal 32 qcmV+*0N?*XQ|)j$@tuKmvmZRW+ywyCJN^EZ^%Yu2yrZ}J%95%Oo)NeJ literal 0 HcmV?d00001