mirror of
https://github.com/espressif/esp-idf.git
synced 2026-05-28 16:46:31 +03:00
ci(esp_tee): Add fuzzing tests for verifying service call behaviour
This commit is contained in:
@@ -2,7 +2,7 @@ idf_build_get_property(idf_path IDF_PATH)
|
||||
|
||||
set(priv_requires bootloader_support esp_driver_gptimer esp_tee esp_timer mbedtls spi_flash)
|
||||
# Test FW related
|
||||
list(APPEND priv_requires nvs_flash test_utils unity)
|
||||
list(APPEND priv_requires console nvs_flash test_utils unity)
|
||||
# TEE related
|
||||
list(APPEND priv_requires tee_sec_storage tee_ota_ops test_sec_srv)
|
||||
|
||||
@@ -13,7 +13,8 @@ list(APPEND srcs "test_esp_tee_ctx_switch.c"
|
||||
"test_esp_tee_panic.c"
|
||||
"test_esp_tee_sec_stg.c"
|
||||
"test_esp_tee_ota.c"
|
||||
"test_esp_tee_flash_prot.c")
|
||||
"test_esp_tee_flash_prot.c"
|
||||
"test_esp_tee_fuzzer.c")
|
||||
|
||||
if(CONFIG_SECURE_TEE_ATTESTATION)
|
||||
list(APPEND srcs "test_esp_tee_att.c")
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_tee.h"
|
||||
#include "esp_console.h"
|
||||
#include "argtable3/argtable3.h"
|
||||
#include "unity.h"
|
||||
|
||||
#define MAX_ARGS 9
|
||||
#define MAX_CMD_FUZZ_LEN 384
|
||||
|
||||
static const char *TAG = "test_esp_tee_fuzzer";
|
||||
|
||||
static struct {
|
||||
struct arg_int *arg_count;
|
||||
struct arg_int *srv_id;
|
||||
struct arg_str *arg_list;
|
||||
struct arg_end *end;
|
||||
} fuzz_args;
|
||||
|
||||
static int fuzz(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **) &fuzz_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, fuzz_args.end, argv[0]);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
const int arg_count = fuzz_args.arg_count->ival[0];
|
||||
const int srv_id = fuzz_args.srv_id->ival[0];
|
||||
|
||||
if (arg_count < 0 || arg_count > MAX_ARGS) {
|
||||
ESP_LOGE(TAG, "arg_count %d out of range [0, %d]", arg_count, MAX_ARGS);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
uint32_t arg_val[MAX_ARGS] = {0};
|
||||
|
||||
if (arg_count > 0 && fuzz_args.arg_list->count > 0) {
|
||||
char arg_list_buf[MAX_CMD_FUZZ_LEN + 1];
|
||||
strlcpy(arg_list_buf, fuzz_args.arg_list->sval[0], sizeof(arg_list_buf));
|
||||
|
||||
char *saveptr = NULL;
|
||||
char *token = strtok_r(arg_list_buf, ";", &saveptr);
|
||||
for (int i = 0; i < arg_count && token != NULL; i++) {
|
||||
arg_val[i] = (uint32_t)strtoul(token, NULL, 0);
|
||||
token = strtok_r(NULL, ";", &saveptr);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* All service arguments are passed as uint32_t through the variadic interface.
|
||||
* The TEE dispatcher reads only (arg_count + 1) values; extra trailing args
|
||||
* are harmless since they remain untouched in registers / on the stack.
|
||||
* Skipping per-service type casts is intentional: for fuzzing we want the
|
||||
* full 32-bit garbage to reach the service, not a truncated subset.
|
||||
*/
|
||||
uint32_t ret = esp_tee_service_call(arg_count + 1, srv_id,
|
||||
arg_val[0], arg_val[1], arg_val[2],
|
||||
arg_val[3], arg_val[4], arg_val[5],
|
||||
arg_val[6]);
|
||||
ESP_LOGI(TAG, "Service call returned 0x%08lx", ret);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
TEST_CASE("Test TEE service call Fuzzer", "[fuzzing]")
|
||||
{
|
||||
esp_console_repl_t *repl = NULL;
|
||||
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
|
||||
repl_config.max_cmdline_length = MAX_CMD_FUZZ_LEN;
|
||||
|
||||
esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
|
||||
TEST_ESP_OK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
|
||||
|
||||
fuzz_args.arg_count = arg_int1("c", "count", "<count>", "Argument count");
|
||||
fuzz_args.srv_id = arg_int1("i", "id", "<srv_id>", "Service ID");
|
||||
fuzz_args.arg_list = arg_str0(NULL, NULL, "<arg_list>", "Argument list");
|
||||
fuzz_args.end = arg_end(2);
|
||||
|
||||
const esp_console_cmd_t fuzz_cmd = {
|
||||
.command = "fuzz",
|
||||
.help = "Fuzz TEE service",
|
||||
.hint = NULL,
|
||||
.func = &fuzz,
|
||||
.argtable = &fuzz_args
|
||||
};
|
||||
|
||||
TEST_ESP_OK(esp_console_cmd_register(&fuzz_cmd));
|
||||
TEST_ESP_OK(esp_console_register_help_command());
|
||||
TEST_ESP_OK(esp_console_start_repl(repl));
|
||||
|
||||
vTaskSuspend(NULL);
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import logging
|
||||
import random
|
||||
import re
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
|
||||
import pexpect
|
||||
import pytest
|
||||
import yaml
|
||||
from pytest_embedded_idf import IdfDut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
from tee_exception_test_map import TEE_EXCEPTION_TEST_MAP
|
||||
@@ -506,3 +511,62 @@ def test_esp_tee_attestation(dut: IdfDut) -> None:
|
||||
|
||||
# start test
|
||||
dut.run_all_single_board_cases(group='attestation')
|
||||
|
||||
|
||||
# ---------------- TEE Fuzzing tests ----------------
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize('config', ['tee_fuzzing'], indirect=['config'])
|
||||
@idf_parametrize('target', TESTING_TARGETS, indirect=['target'])
|
||||
def test_esp_tee_fuzzer(dut: IdfDut) -> None:
|
||||
# Panics are expected during fuzzing; skip GDB decode on teardown
|
||||
dut.skip_decode_panic = True
|
||||
|
||||
seed = random.randrange(2**32)
|
||||
logging.info('Fuzzer seed: 0x%08x', seed)
|
||||
rng = random.Random(seed)
|
||||
|
||||
# Number of random inputs to send per service
|
||||
FUZZ_ITERATIONS_PER_SERVICE = 4
|
||||
|
||||
# Build {service_id: arg_count} map from the target's YAML service table
|
||||
yml_path = Path(__file__).parent.joinpath('..', '..', 'scripts', dut.target, 'sec_srv_tbl_default.yml').resolve()
|
||||
with open(yml_path) as f:
|
||||
services = {
|
||||
entry['id']: entry['args'] for family in yaml.safe_load(f)['secure_services'] for entry in family['entries']
|
||||
}
|
||||
|
||||
pat_ret = re.compile(r'Service call returned (0x[0-9A-Fa-f]+)')
|
||||
pat_rst = re.compile(r'rst:(0x[0-9A-Fa-f]+) \(([^)]+)\)')
|
||||
device_rebooted = True # True on first entry since device boots fresh
|
||||
|
||||
for srv_id, num_args in sorted(services.items()):
|
||||
for iter_idx in range(FUZZ_ITERATIONS_PER_SERVICE):
|
||||
# Re-enter the console menu only after a reboot
|
||||
if device_rebooted:
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.write('[fuzzing]')
|
||||
dut.expect('esp>')
|
||||
device_rebooted = False
|
||||
|
||||
# Always pass at least 1 arg; extra trailing args are ignored by the dispatcher
|
||||
n = max(num_args, 1)
|
||||
arg_str = ';'.join(f'0x{rng.getrandbits(32):08x}' for _ in range(n))
|
||||
dut.write(f'fuzz -c {num_args} -i {srv_id} {arg_str}')
|
||||
|
||||
try:
|
||||
# Service call either returns gracefully or crashes the device
|
||||
match = dut.expect([pat_ret, pat_rst], timeout=3)
|
||||
except pexpect.TIMEOUT:
|
||||
logging.warning('Device hung, hard-resetting (srv_id: %d, iter: %d)', srv_id, iter_idx)
|
||||
dut.serial.hard_reset()
|
||||
device_rebooted = True
|
||||
continue
|
||||
|
||||
if pat_rst.match(match.group(0).decode()):
|
||||
# Device crashed and rebooted — verify it was a software reset
|
||||
device_rebooted = True
|
||||
rst_rsn = match.group(2).decode()
|
||||
if rst_rsn not in ('LP_SW_HPSYS', 'RTC_SW_HPSYS'):
|
||||
logging.warning('Unexpected reset reason: "%s" (srv_id: %d, iter: %d)', rst_rsn, srv_id, iter_idx)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Custom partition table
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_tee_ota.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions_tee_ota.csv"
|
||||
|
||||
# Decreasing Bootloader and TEE log verbosity
|
||||
CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
|
||||
CONFIG_SECURE_TEE_LOG_LEVEL_WARN=y
|
||||
|
||||
# Takes effect only for supported targets
|
||||
CONFIG_SECURE_TEE_SEC_STG_SUPPORT_SECP384R1_SIGN=y
|
||||
|
||||
# Enabling flash protection over SPI1
|
||||
CONFIG_SECURE_TEE_EXT_FLASH_MEMPROT_SPI1=y
|
||||
|
||||
# Increasing TEE DRAM size
|
||||
# 20KB
|
||||
CONFIG_SECURE_TEE_DRAM_SIZE=0x5000
|
||||
Reference in New Issue
Block a user