From 018205653a3c0a6b1de18d06e329ca58d4672027 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 2 Oct 2026 13:48:30 -0700 Subject: [PATCH 1/8] Share the Armv8-M isolation level 3 layout, linker fragments and platform hooks across ports --- mk/arch-armv8m.mk | 21 +- mk/common.mk | 6 + mk/target-mimxrt700.mk | 7 +- mk/target-stm32h563.mk | 2 +- port/common/armv8m/l3_layout.h | 101 +++++++ port/common/armv8m/platform_l3.c | 216 +++++++++++++++ port/common/armv8m/secure_l3_bands.ld | 130 +++++++++ port/common/armv8m/secure_l3_memory.ld | 41 +++ port/common/armv8m/secure_l3_symbols.ld | 29 ++ port/common/armv8m/secure_l3_tail.ld | 73 +++++ port/mimxrt700/l3_port.h | 31 +++ port/mimxrt700/memory_map.h | 60 +--- port/mimxrt700/platform_mimxrt700.c | 165 ----------- port/mimxrt700/secure.ld | 219 +-------------- port/stm32h563/l3_port.h | 31 +++ port/stm32h563/memory_map.h | 109 +------- port/stm32h563/platform_stm32h563.c | 182 ------------ port/stm32h563/secure.ld | 137 ++++++++++ src/services/wolfhsm/runner/secure.ld | 350 ------------------------ tools/check-port-only-diff.sh | 11 +- tools/l3_layout_args.py | 75 +++++ tools/secure_owners.txt | 1 + 22 files changed, 908 insertions(+), 1089 deletions(-) create mode 100644 port/common/armv8m/l3_layout.h create mode 100644 port/common/armv8m/platform_l3.c create mode 100644 port/common/armv8m/secure_l3_bands.ld create mode 100644 port/common/armv8m/secure_l3_memory.ld create mode 100644 port/common/armv8m/secure_l3_symbols.ld create mode 100644 port/common/armv8m/secure_l3_tail.ld create mode 100644 port/mimxrt700/l3_port.h create mode 100644 port/stm32h563/l3_port.h create mode 100644 port/stm32h563/secure.ld delete mode 100644 src/services/wolfhsm/runner/secure.ld create mode 100755 tools/l3_layout_args.py diff --git a/mk/arch-armv8m.mk b/mk/arch-armv8m.mk index 3bfe0c29..3ba66f4a 100644 --- a/mk/arch-armv8m.mk +++ b/mk/arch-armv8m.mk @@ -59,6 +59,24 @@ ARCH_SRCS := \ $(ROOT)/src/arch/armv8m/sau_armv8m.c \ $(ROOT)/src/arch/armv8m/start_armv8m.c +# Isolation level 3 layer shared by every Armv8-M port: the band layout, its +# linker fragments and the platform hooks. A port's memory_map.h supplies +# WT_RAM_S_BASE, from which every band is placed. +PORT_COMMON_DIR := $(ROOT)/port/common/armv8m +PORT_HEADERS += $(wildcard $(PORT_COMMON_DIR)/*.h) +TARGET_EXTRA_SRCS += $(PORT_COMMON_DIR)/platform_l3.c +WT_RAM_S_ORIGIN := $(shell sed -n \ + 's/^\#define WT_RAM_S_BASE[[:space:]]*\(0x[0-9A-Fa-f]*\)u.*/\1/p' \ + $(PORT_DIR)/memory_map.h) +ifeq ($(WT_RAM_S_ORIGIN),) +$(error $(PORT_DIR)/memory_map.h has no literal WT_RAM_S_BASE) +endif +TARGET_LDFLAGS += -Wl,-L$(PORT_COMMON_DIR) \ + -Wl,--defsym=WT_RAM_S_ORIGIN=$(WT_RAM_S_ORIGIN) +# Post-link band check inputs, read from the port's memory_map.h at link time. +WT_SECURE_LAYOUT_ARGS = $(shell python3 $(ROOT)/tools/l3_layout_args.py \ + --cc $(TOOLPREFIX)gcc $(PORT_DIR)/memory_map.h) $(WT_SECURE_LAYOUT_EXTRA_ARGS) + # CMSE import library for the Non-secure guests, produced by the secure link. SECURE_CMSE_IMPLIB := $(BUILD_DIR)/secure_cmse_implib.o ARCH_LINK_OUTPUTS := $(SECURE_CMSE_IMPLIB) @@ -66,7 +84,8 @@ ARCH_LDFLAGS := -Wl,--cmse-implib -Wl,--out-implib=$(SECURE_CMSE_IMPLIB) # A changed post-link checker must relink so the image is checked again. $(BUILD_DIR)/wolftrust.elf $(ARCH_LINK_OUTPUTS): \ - $(ROOT)/tools/check_no_fp_insn.py $(ROOT)/tools/check_stack_seal.py + $(ROOT)/tools/check_no_fp_insn.py $(ROOT)/tools/check_stack_seal.py \ + $(ROOT)/tools/l3_layout_args.py $(wildcard $(PORT_COMMON_DIR)/*.ld) # Whitelist of non-secure-callable veneers the linked secure image may # export: exactly the five mediated FF-M gateway entries, pinned by full diff --git a/mk/common.mk b/mk/common.mk index 874c665c..4603eeda 100644 --- a/mk/common.mk +++ b/mk/common.mk @@ -1508,6 +1508,12 @@ $(BUILD_DIR)/wt_sec_%.o: $(WOLFHSM_RUNNER_DIR)/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE $(BUILD_DIR)/wt_sec_%.o: $(PORT_DIR)/%.c $(PORT_HEADERS) $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) $(CC) $(SECURE_CFLAGS) -c -o $@ $< +ifneq ($(PORT_COMMON_DIR),) +$(BUILD_DIR)/wt_sec_%.o: $(PORT_COMMON_DIR)/%.c $(PORT_HEADERS) $(MANIFEST_GEN_H) \ + $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(SECURE_CFLAGS) -c -o $@ $< +endif + $(BUILD_DIR)/wt_sec_%.o: $(WOLFHAL_DIR)/src/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) $(CC) $(SECURE_CFLAGS) -c -o $@ $< diff --git a/mk/target-mimxrt700.mk b/mk/target-mimxrt700.mk index 4ffd1da1..78e2672f 100644 --- a/mk/target-mimxrt700.mk +++ b/mk/target-mimxrt700.mk @@ -56,11 +56,8 @@ TARGET_LDFLAGS := \ -Wl,--defsym=WT_SECURE_FLASH_SIZE=$(WT_SECURE_FLASH_SIZE) \ -Wl,--defsym=WT_SECURE_IMAGE_HEADER_SIZE=$(WT_SECURE_IMAGE_HEADER_SIZE) SECURE_LD := $(PORT_DIR)/secure.ld -# Post-link placement check: the per-partition keystore bands and the -# conformance band from memory_map.h; this port uses no wolfHAL. -WT_SECURE_LAYOUT_ARGS := --band vault=0x301D5000:0x301D7000 \ - --band attest=0x301D7000:0x301D7800 --band hsm=0x301D7800:0x301E9000 \ - --confdata 0x301F3000:0x301F5C00 --no-wolfhal +# This port links no wolfHAL, so the post-link check expects no wolfHAL state. +WT_SECURE_LAYOUT_EXTRA_ARGS := --no-wolfhal TARGET_PLATFORM_SRC := $(PORT_DIR)/platform_mimxrt700.c TARGET_PARTITIONS_SRC := $(PORT_DIR)/partitions.c diff --git a/mk/target-stm32h563.mk b/mk/target-stm32h563.mk index 5b953300..f561b6a0 100644 --- a/mk/target-stm32h563.mk +++ b/mk/target-stm32h563.mk @@ -60,7 +60,7 @@ TARGET_LDFLAGS := \ -Wl,--defsym=WT_SECURE_FLASH_ORIGIN=$(WT_SECURE_FLASH_BASE) \ -Wl,--defsym=WT_SECURE_FLASH_SIZE=$(WT_SECURE_FLASH_SIZE) \ -Wl,--defsym=WT_SECURE_IMAGE_HEADER_SIZE=$(WT_SECURE_IMAGE_HEADER_SIZE) -SECURE_LD := $(WOLFHSM_RUNNER_DIR)/secure.ld +SECURE_LD := $(PORT_DIR)/secure.ld TARGET_PLATFORM_SRC := $(PORT_DIR)/platform_stm32h563.c TARGET_PARTITIONS_SRC := $(PORT_DIR)/partitions.c diff --git a/port/common/armv8m/l3_layout.h b/port/common/armv8m/l3_layout.h new file mode 100644 index 00000000..2e907725 --- /dev/null +++ b/port/common/armv8m/l3_layout.h @@ -0,0 +1,101 @@ +/* l3_layout.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* Isolation level 3 secure RAM layout shared by every Armv8-M port. A port's + * memory_map.h defines WT_RAM_S_BASE and WT_RAM_S_SIZE, then includes this. + * port/common/armv8m/secure_l3_memory.ld places the same bands. */ + +#ifndef WOLFTRUST_PORT_ARMV8M_L3_LAYOUT_H +#define WOLFTRUST_PORT_ARMV8M_L3_LAYOUT_H + +#if !defined(WT_RAM_S_BASE) || !defined(WT_RAM_S_SIZE) +#error "define WT_RAM_S_BASE and WT_RAM_S_SIZE before including l3_layout.h" +#endif + +/* Per-partition secure stacks at the top of the window; slots 2-4 host the + * Arm conformance partitions (SERVER/DRIVER/CLIENT). */ +#define WT_SP_SECURE_STACK_SIZE 0x00002000u +#define WT_SP_SECURE_STACK_COUNT 5u +#define WT_SP_SECURE_RAM_SIZE \ + (WT_SP_SECURE_STACK_SIZE * WT_SP_SECURE_STACK_COUNT) +#define WT_SP_SECURE_RAM_BASE (WT_RAM_S_BASE + 0x0006E000u) +#define WT_SP_SECURE_RAM_END \ + (WT_SP_SECURE_RAM_BASE + WT_SP_SECURE_RAM_SIZE) +#define WT_SP_CRYPTO_STACK_BASE \ + (WT_SP_SECURE_RAM_BASE + 0u * WT_SP_SECURE_STACK_SIZE) +#define WT_SP_ATTEST_STACK_BASE \ + (WT_SP_SECURE_RAM_BASE + 1u * WT_SP_SECURE_STACK_SIZE) +#define WT_SP_FF_SERVER_STACK_BASE \ + (WT_SP_SECURE_RAM_BASE + 2u * WT_SP_SECURE_STACK_SIZE) +#define WT_SP_FF_DRIVER_STACK_BASE \ + (WT_SP_SECURE_RAM_BASE + 3u * WT_SP_SECURE_STACK_SIZE) +#define WT_SP_FF_CLIENT_STACK_BASE \ + (WT_SP_SECURE_RAM_BASE + 4u * WT_SP_SECURE_STACK_SIZE) + +/* Conformance partition .data/.bss window, kept outside SPM RAM. */ +#define WT_CONF_SP_DATA_BASE (WT_RAM_S_BASE + 0x0006B000u) +#define WT_CONF_SP_DATA_SIZE 0x00003000u + +/* 16 KiB: ECC verify's point table overflows an 8 KiB stack (PSPLIM STKOF). */ +#define WT_SP_VAULT_STACK_BASE (WT_RAM_S_BASE + 0x00067000u) +#define WT_SP_VAULT_STACK_SIZE 0x00004000u + +#define WT_SP_ITS_STACK_BASE (WT_RAM_S_BASE + 0x00065000u) +#define WT_SP_ITS_STACK_SIZE WT_SP_SECURE_STACK_SIZE + +#define WT_SP_PS_STACK_BASE (WT_RAM_S_BASE + 0x00063000u) +#define WT_SP_PS_STACK_SIZE WT_SP_SECURE_STACK_SIZE + +#define WT_SP_FWU_STACK_BASE (WT_RAM_S_BASE + 0x00061000u) +#define WT_SP_FWU_STACK_SIZE WT_SP_SECURE_STACK_SIZE + +/* Keystore envelope: the vault, attestation and crypto partitions each own + * one private writable band, so no two partitions share a writable byte. */ +#define WT_KEYSTORE_BASE (WT_RAM_S_BASE + 0x0004D000u) +#define WT_KEYSTORE_SIZE 0x00014000u +#define WT_SP_VAULT_DATA_BASE WT_KEYSTORE_BASE +#define WT_SP_VAULT_DATA_SIZE 0x00002000u +#define WT_SP_ATTEST_DATA_BASE \ + (WT_SP_VAULT_DATA_BASE + WT_SP_VAULT_DATA_SIZE) +#define WT_SP_ATTEST_DATA_SIZE 0x00000800u +#define WT_SP_HSM_DATA_BASE \ + (WT_SP_ATTEST_DATA_BASE + WT_SP_ATTEST_DATA_SIZE) +#define WT_SP_HSM_DATA_SIZE 0x00011800u + +/* CONFIG_VNET and WT_CONFORMANCE are exclusive, so the VNET stack reuses the + * conformance data window. */ +#define WT_SP_VNET_STACK_BASE WT_CONF_SP_DATA_BASE +#define WT_SP_VNET_STACK_SIZE WT_SP_SECURE_STACK_SIZE + +#define WT_VNET_DATA_BASE (WT_RAM_S_BASE + 0x00048000u) +#define WT_VNET_DATA_SIZE 0x00005000u + +/* Per-partition pseudo-MMIO holes at the top of the conformance window; must + * match SERVER/DRIVER_PARTITION_MMIO_0_* in the port's pal_config.h. */ +#define WT_CONF_SERVER_MMIO_BASE (WT_CONF_SP_DATA_BASE + 0x00002C00u) +#define WT_CONF_SERVER_MMIO_SIZE 0x00000100u +#define WT_CONF_DRV_MMIO_BASE (WT_CONF_SP_DATA_BASE + 0x00002E00u) +#define WT_CONF_DRV_MMIO_SIZE 0x00000100u + +#if (WT_SP_SECURE_RAM_END - WT_RAM_S_BASE) > WT_RAM_S_SIZE +#error "the level 3 secure RAM layout does not fit WT_RAM_S_SIZE" +#endif + +#endif diff --git a/port/common/armv8m/platform_l3.c b/port/common/armv8m/platform_l3.c new file mode 100644 index 00000000..9b8db171 --- /dev/null +++ b/port/common/armv8m/platform_l3.c @@ -0,0 +1,216 @@ +/* platform_l3.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* Isolation level 3 platform hooks shared by every Armv8-M port. The layout + * comes from l3_layout.h through the port's memory_map.h; the port's + * l3_port.h supplies the few board inputs. */ + +#include "wolftrust/platform.h" +#include "wolftrust/priv_stack.h" + +#include +#include + +#include "memory_map.h" +#include "l3_port.h" + +#if defined(WT_CONFORMANCE) && (WT_CONFORMANCE == 1) +#include "psa_manifest/pid.h" +#endif + +/* End of executable image code (secure.ld): partitions get RX below it and + * read-only XN above it, so no partition executes constant data. */ +extern char _e_secure_text[]; + +volatile void* wt_platform_boot_handoff_region(size_t* size) +{ + *size = WT_RAM_S_BASE - WT_BOOT_HANDOFF_ADDRESS; + return (volatile void*)WT_BOOT_HANDOFF_ADDRESS; +} + +/* No peripheral is assignable to a Secure Partition yet: the SPM drives every + * Secure peripheral itself, so every partition DEVICE resource is refused. */ +const struct wt_periph* wt_platform_sp_peripherals(size_t* count) +{ + if (count != NULL) { + *count = 0U; + } + return NULL; +} + +size_t wt_platform_sp_shared_regions(wt_memory_region_t* regions, size_t max) +{ + if (max < 2u) { + return 0u; + } + regions[0].base = WT_FLASH_S_BASE; + regions[0].size = (uintptr_t)_e_secure_text - WT_FLASH_S_BASE; + regions[0].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC; + regions[1].base = (uintptr_t)_e_secure_text; + regions[1].size = WT_FLASH_S_BASE + WT_FLASH_S_SIZE - + (uintptr_t)_e_secure_text; + regions[1].attributes = WT_MEM_ATTR_READ; + return 2u; +} + +size_t wt_platform_spm_private_regions(wt_memory_region_t* regions, + size_t max) +{ + uintptr_t first_band = WT_SP_VAULT_DATA_BASE; + size_t count = 1u; + +#if defined(WT_RAMFUNC_BASE) + count = 2u; +#endif + if (max < count) { + return 0u; + } +#if defined(CONFIG_VNET) + first_band = WT_VNET_DATA_BASE; +#endif + regions[0].base = WT_RAM_S_BASE; + regions[0].size = first_band - WT_RAM_S_BASE; + regions[0].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; +#if defined(WT_RAMFUNC_BASE) + /* A port's RAM code band holds the NSC gateway and flash routines. */ + regions[1].base = WT_RAMFUNC_BASE; + regions[1].size = WT_RAMFUNC_SIZE; + regions[1].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC; +#endif + return count; +} + +int wt_platform_priv_stack_ok(const void *stack, size_t size) +{ + /* A privileged coroutine stack must lie wholly in SPM-private RAM, below + * the lowest partition-writable band (WT-FFM-0011). */ +#if defined(CONFIG_VNET) + uintptr_t priv_end = WT_VNET_DATA_BASE; +#else + uintptr_t priv_end = WT_KEYSTORE_BASE; +#endif + + if (stack == NULL) { + return 0; + } + return wt_priv_stack_ok((uintptr_t)stack, size, WT_RAM_S_BASE, priv_end, + NULL, 0u); +} + +#if defined(WT_CONFORMANCE) && (WT_CONFORMANCE == 1) +/* Per-partition private data bands (secure.ld), each denied to other SPs. */ +extern char _s_conf_server_data[]; +extern char _e_conf_server_data[]; +extern char _s_conf_driver_data[]; +extern char _e_conf_driver_data[]; + +/* Append one RW grant segment to an SP's thread table (skips empty segments, + * fails closed by granting nothing when the table is full). */ +static size_t wt_conf_grant(wt_memory_region_t* regions, size_t count, + size_t max, uintptr_t base, uintptr_t end) +{ + if (base < end && count < max) { + regions[count].base = base; + regions[count].size = (uint32_t)(end - base); + regions[count].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; + count++; + } + return count; +} + +/* Grant a hosted Arm partition the conformance window minus every other + * partition's data band and pseudo-MMIO hole, so the suite's cross-partition + * tests (i047/i055/i057/i080/i084) hit a genuine out-of-domain access. */ +size_t wt_platform_conf_sp_grants(int32_t partition_id, + wt_memory_region_t* regions, + size_t count, size_t max) +{ + uintptr_t conf_seg = WT_CONF_SP_DATA_BASE; + + /* The wolfTrust partitions in the image hold none of the suite's data. */ + if (partition_id != SERVER_PARTITION_ID && + partition_id != CLIENT_PARTITION_ID && + partition_id != DRIVER_PARTITION_ID) { + return count; + } + if (partition_id != SERVER_PARTITION_ID) { + count = wt_conf_grant(regions, count, max, conf_seg, + (uintptr_t)_s_conf_server_data); + conf_seg = (uintptr_t)_e_conf_server_data; + } + if (partition_id != DRIVER_PARTITION_ID) { + count = wt_conf_grant(regions, count, max, conf_seg, + (uintptr_t)_s_conf_driver_data); + conf_seg = (uintptr_t)_e_conf_driver_data; + } + if (partition_id != SERVER_PARTITION_ID) { + count = wt_conf_grant(regions, count, max, conf_seg, + WT_CONF_SERVER_MMIO_BASE); + conf_seg = WT_CONF_SERVER_MMIO_BASE + WT_CONF_SERVER_MMIO_SIZE; + } + if (partition_id != DRIVER_PARTITION_ID) { + count = wt_conf_grant(regions, count, max, conf_seg, + WT_CONF_DRV_MMIO_BASE); + conf_seg = WT_CONF_DRV_MMIO_BASE + WT_CONF_DRV_MMIO_SIZE; + } + count = wt_conf_grant(regions, count, max, conf_seg, + WT_CONF_SP_DATA_BASE + WT_CONF_SP_DATA_SIZE); + return count; +} + +size_t wt_platform_conf_shared_regions(wt_memory_region_t* regions, + size_t max) +{ + if (max < 1u) { + return 0u; + } + regions[0].base = WT_CONF_SP_DATA_BASE; + regions[0].size = WT_CONF_SP_DATA_SIZE; + regions[0].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; + return 1u; +} +#endif + +#if (defined(WT_FFM_NEGATIVE_PROBE) && (WT_FFM_NEGATIVE_PROBE == 1)) || \ + (defined(WT_VNET_NEG_PROBE) && (WT_VNET_NEG_PROBE == 1)) || \ + (defined(WT_KEYSTORE_NEG_PROBE) && (WT_KEYSTORE_NEG_PROBE == 1)) || \ + (defined(WT_PERIPH_SP_NEG_PROBE) && (WT_PERIPH_SP_NEG_PROBE == 1)) || \ + (defined(WT_BAND_NEG_PROBE) && (WT_BAND_NEG_PROBE != 0)) || \ + (defined(WT_MANIFEST_NEG_PROBE) && (WT_MANIFEST_NEG_PROBE == 3)) +uintptr_t wt_platform_probe_address(unsigned int target) +{ + switch (target) { + case WT_PROBE_VAULT_DATA_BAND: + return (uintptr_t)WT_SP_VAULT_DATA_BASE; + case WT_PROBE_ATTEST_DATA_BAND: + return (uintptr_t)WT_SP_ATTEST_DATA_BASE; + case WT_PROBE_HSM_DATA_BAND: + return (uintptr_t)WT_SP_HSM_DATA_BASE; + case WT_PROBE_SPM_PERIPHERAL: + return (uintptr_t)WT_L3_SPM_PERIPHERAL_BASE; +#if defined(CONFIG_VNET) + case WT_PROBE_VNET_DATA_BAND: + return (uintptr_t)WT_VNET_DATA_BASE; +#endif + default: + return (uintptr_t)WT_RAM_S_BASE; + } +} +#endif diff --git a/port/common/armv8m/secure_l3_bands.ld b/port/common/armv8m/secure_l3_bands.ld new file mode 100644 index 00000000..2fb324b8 --- /dev/null +++ b/port/common/armv8m/secure_l3_bands.ld @@ -0,0 +1,130 @@ +/* secure_l3_bands.ld + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* Per-partition band sections, INCLUDEd in SECTIONS after the read-only + * image. Objects named here build without LTO so each claim holds, and + * tools/check_secure_layout.py checks every claim against the owner map. */ + + .vault_data : { + . = ALIGN(32); + _s_vault = .; + _s_keystore = .; + *nvm_store.o(.data .data.*) + *wt_hsm_vault.o(.data .data.*) + *wt_hsm_seal.o(.data .data.*) + *wt_hsm_lock.o(.data .data.*) + *vault_service.o(.data .data.*) + *hsm_flash_ctx.o(.data .data.*) + *wh_sec_wh_nvm.o(.data .data.*) + *wh_sec_wh_nvm_flash.o(.data .data.*) + *wh_sec_wh_flash_unit.o(.data .data.*) + *wh_sec_wh_lock.o(.data .data.*) + . = ALIGN(4); + _e_vault_data = .; + } > VAULTDATA AT > FLASH + _si_vault = LOADADDR(.vault_data); + + .vault_bss (NOLOAD) : { + _s_vault_bss = .; + *nvm_store.o(.bss .bss.* COMMON) + *wt_hsm_vault.o(.bss .bss.* COMMON) + *wt_hsm_seal.o(.bss .bss.* COMMON) + *wt_hsm_lock.o(.bss .bss.* COMMON) + *vault_service.o(.bss .bss.* COMMON) + *hsm_flash_ctx.o(.bss .bss.* COMMON) + *wh_sec_wh_nvm.o(.bss .bss.* COMMON) + *wh_sec_wh_nvm_flash.o(.bss .bss.* COMMON) + *wh_sec_wh_flash_unit.o(.bss .bss.* COMMON) + *wh_sec_wh_lock.o(.bss .bss.* COMMON) + . = ALIGN(32); + _e_vault = .; + } > VAULTDATA + + .attest_data : { + . = ALIGN(32); + _s_attest = .; + *attestation_service.o(.data .data.*) + *initial_attestation.o(.data .data.*) + *attestation_cose.o(.data .data.*) + *wolfcose*.o(.data .data.*) + . = ALIGN(4); + _e_attest_data = .; + } > ATTESTDATA AT > FLASH + _si_attest = LOADADDR(.attest_data); + + .attest_bss (NOLOAD) : { + _s_attest_bss = .; + *attestation_service.o(.bss .bss.* COMMON) + *initial_attestation.o(.bss .bss.* COMMON) + *attestation_cose.o(.bss .bss.* COMMON) + *wolfcose*.o(.bss .bss.* COMMON) + . = ALIGN(32); + _e_attest = .; + } > ATTESTDATA + + .hsm_data : { + . = ALIGN(32); + _s_hsm = .; + *wt_hsm.o(.data .data.*) + *hsm_relay_service.o(.data .data.*) + *nvm_client.o(.data .data.*) + *crypto_native.o(.data .data.*) + *keyvault.o(.data .data.*) + *native_wire.o(.data .data.*) + *wh_sec_*.o(.data .data.*) + *wc_sec_cryptocb.o(.data .data.*) + . = ALIGN(4); + _e_hsm_data = .; + } > HSMDATA AT > FLASH + _si_hsm = LOADADDR(.hsm_data); + + .hsm_bss (NOLOAD) : { + _s_hsm_bss = .; + *wt_hsm.o(.bss .bss.* COMMON) + *hsm_relay_service.o(.bss .bss.* COMMON) + *nvm_client.o(.bss .bss.* COMMON) + *crypto_native.o(.bss .bss.* COMMON) + *keyvault.o(.bss .bss.* COMMON) + *native_wire.o(.bss .bss.* COMMON) + *wh_sec_*.o(.bss .bss.* COMMON) + *wc_sec_cryptocb.o(.bss .bss.* COMMON) + . = ALIGN(32); + _e_hsm = .; + _e_keystore = .; + } > HSMDATA + + /* SERVICE_VNET data band: the vnet objects' RAM, claimed out of SPM + * .data/.bss so the partition's MPU grant covers exactly its own state. + * Zero length outside CONFIG_VNET builds. */ + .vnet_data : { + . = ALIGN(32); + _s_vnet = .; + *wt_sec_vnet_*.o(.data .data.*) + . = ALIGN(4); + _e_vnet_data = .; + } > VNETDATA AT > FLASH + _si_vnet = LOADADDR(.vnet_data); + + .vnet_bss (NOLOAD) : { + _s_vnet_bss = .; + *wt_sec_vnet_*.o(.bss .bss.* COMMON) + . = ALIGN(32); + _e_vnet = .; + } > VNETDATA diff --git a/port/common/armv8m/secure_l3_memory.ld b/port/common/armv8m/secure_l3_memory.ld new file mode 100644 index 00000000..444a1554 --- /dev/null +++ b/port/common/armv8m/secure_l3_memory.ld @@ -0,0 +1,41 @@ +/* secure_l3_memory.ld + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* Isolation level 3 secure RAM regions, INCLUDEd inside a port's MEMORY + * block. WT_RAM_S_ORIGIN is WT_RAM_S_BASE from the port's memory_map.h; + * every offset mirrors port/common/armv8m/l3_layout.h. */ + + /* General SPM RAM up to the keystore bands, less the VNET band when built. */ + RAM (rwx): ORIGIN = WT_RAM_S_ORIGIN, LENGTH = 308K - WT_VNET_DATA_LENGTH + /* SERVICE_VNET data band; zero length outside CONFIG_VNET builds. */ + VNETDATA (rw): ORIGIN = WT_RAM_S_ORIGIN + 308K - WT_VNET_DATA_LENGTH, + LENGTH = WT_VNET_DATA_LENGTH + /* Keystore bands: the vault, attestation and crypto partitions each own one. */ + VAULTDATA (rw): ORIGIN = WT_RAM_S_ORIGIN + 0x4D000, LENGTH = 8K + ATTESTDATA (rw): ORIGIN = WT_RAM_S_ORIGIN + 0x4F000, LENGTH = 2K + HSMDATA (rw): ORIGIN = WT_RAM_S_ORIGIN + 0x4F800, LENGTH = 70K + /* Partition execution stacks, each also the partition's MPU RW resource. */ + FWUSTACK (rw): ORIGIN = WT_RAM_S_ORIGIN + 0x61000, LENGTH = 8K + PSSTACK (rw): ORIGIN = WT_RAM_S_ORIGIN + 0x63000, LENGTH = 8K + ITSSTACK (rw): ORIGIN = WT_RAM_S_ORIGIN + 0x65000, LENGTH = 8K + VAULTSTACK (rw): ORIGIN = WT_RAM_S_ORIGIN + 0x67000, LENGTH = 16K + /* Conformance partition data; CONFIG_VNET builds use it as the VNET stack. */ + CONFDATA (rw): ORIGIN = WT_RAM_S_ORIGIN + 0x6B000, LENGTH = 12K + SPSTACKS (rw): ORIGIN = WT_RAM_S_ORIGIN + 0x6E000, LENGTH = 40K diff --git a/port/common/armv8m/secure_l3_symbols.ld b/port/common/armv8m/secure_l3_symbols.ld new file mode 100644 index 00000000..99bf16b8 --- /dev/null +++ b/port/common/armv8m/secure_l3_symbols.ld @@ -0,0 +1,29 @@ +/* secure_l3_symbols.ld + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* Stack symbols of the shared level 3 layout, INCLUDEd after MEMORY. */ + +_estack = ORIGIN(RAM) + LENGTH(RAM); +/* Secure main stack: MSPLIM_S is set to _sstack at reset. */ +_sstack = _estack - WT_SPM_STACK_SIZE; +_wt_part_stacks_base = ORIGIN(FWUSTACK); +_wt_part_stacks_limit = ORIGIN(VAULTSTACK) + LENGTH(VAULTSTACK); +_wt_sp_stacks_base = ORIGIN(SPSTACKS); +_wt_sp_stacks_limit = ORIGIN(SPSTACKS) + LENGTH(SPSTACKS); diff --git a/port/common/armv8m/secure_l3_tail.ld b/port/common/armv8m/secure_l3_tail.ld new file mode 100644 index 00000000..0d761fab --- /dev/null +++ b/port/common/armv8m/secure_l3_tail.ld @@ -0,0 +1,73 @@ +/* secure_l3_tail.ld + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* Secure stacks and the level 3 layout invariants, INCLUDEd inside SECTIONS + * after .bss. */ + + .sp_stacks (NOLOAD) : { + KEEP(*(.sp_stacks*)) + } > SPSTACKS + + ASSERT(ORIGIN(VNETDATA) == (ORIGIN(RAM) + LENGTH(RAM)), + "vnet data band must sit directly above the secure RAM window") + ASSERT(ORIGIN(VAULTDATA) == (ORIGIN(VNETDATA) + LENGTH(VNETDATA)), + "vault data band must sit directly above the vnet data band") + ASSERT(_e_vnet <= (ORIGIN(VNETDATA) + LENGTH(VNETDATA)), + "vnet data band overflow") + ASSERT(ORIGIN(ATTESTDATA) == (ORIGIN(VAULTDATA) + LENGTH(VAULTDATA)), + "attestation data band must sit directly above the vault data band") + ASSERT(ORIGIN(HSMDATA) == (ORIGIN(ATTESTDATA) + LENGTH(ATTESTDATA)), + "crypto data band must sit directly above the attestation data band") + ASSERT(ORIGIN(FWUSTACK) == (ORIGIN(HSMDATA) + LENGTH(HSMDATA)), + "FWU stack must sit directly above the crypto data band") + /* Full band extents: Reset_Handler clears each band end to end before + * loading it, so no retained byte outlives a warm reset or a layout change. */ + _wt_band_vault_base = ORIGIN(VAULTDATA); + _wt_band_vault_limit = ORIGIN(VAULTDATA) + LENGTH(VAULTDATA); + _wt_band_attest_base = ORIGIN(ATTESTDATA); + _wt_band_attest_limit = ORIGIN(ATTESTDATA) + LENGTH(ATTESTDATA); + _wt_band_hsm_base = ORIGIN(HSMDATA); + _wt_band_hsm_limit = ORIGIN(HSMDATA) + LENGTH(HSMDATA); + _wt_band_vnet_base = ORIGIN(VNETDATA); + _wt_band_vnet_limit = ORIGIN(VNETDATA) + LENGTH(VNETDATA); + + ASSERT(_e_vault <= (ORIGIN(VAULTDATA) + LENGTH(VAULTDATA)), + "vault data band overflow") + ASSERT(_e_attest <= (ORIGIN(ATTESTDATA) + LENGTH(ATTESTDATA)), + "attestation data band overflow") + ASSERT(_e_hsm <= (ORIGIN(HSMDATA) + LENGTH(HSMDATA)), + "crypto data band overflow") + ASSERT(ORIGIN(PSSTACK) == (ORIGIN(FWUSTACK) + LENGTH(FWUSTACK)), + "PS stack must sit directly above the FWU stack") + ASSERT(ORIGIN(ITSSTACK) == (ORIGIN(PSSTACK) + LENGTH(PSSTACK)), + "ITS stack must sit directly above the PS stack") + ASSERT(ORIGIN(VAULTSTACK) == (ORIGIN(ITSSTACK) + LENGTH(ITSSTACK)), + "vault stack must sit directly above the ITS stack") + ASSERT(ORIGIN(CONFDATA) == (ORIGIN(VAULTSTACK) + LENGTH(VAULTSTACK)), + "conformance SP data must sit directly above the vault stack") + /* The top 1 KiB of CONFDATA holds the per-partition pseudo-MMIO holes + * (WT_CONF_SERVER/DRV_MMIO in memory_map.h); real data crossing into them + * would be carved out of other partitions' domains and fault at runtime. */ + ASSERT(_econfbss <= (ORIGIN(CONFDATA) + 0x2C00), + "conformance data/bss overflows into the reserved MMIO holes") + ASSERT(ORIGIN(SPSTACKS) == (ORIGIN(CONFDATA) + LENGTH(CONFDATA)), + "SP secure stacks must sit directly above the conformance data window") + ASSERT(_ebss <= _sstack, + "secure .bss reaches the SPM main stack (WT_SPM_STACK_SIZE)") diff --git a/port/mimxrt700/l3_port.h b/port/mimxrt700/l3_port.h new file mode 100644 index 00000000..a2d567de --- /dev/null +++ b/port/mimxrt700/l3_port.h @@ -0,0 +1,31 @@ +/* l3_port.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* MIMXRT700 inputs to the shared Armv8-M isolation level 3 layer. */ + +#ifndef WOLFTRUST_FW_MIMXRT700_L3_PORT_H +#define WOLFTRUST_FW_MIMXRT700_L3_PORT_H + +#include "mimxrt798_regs.h" + +/* A Secure peripheral only the SPM drives, probed by the periphsp negative. */ +#define WT_L3_SPM_PERIPHERAL_BASE WT_TRNG_BASE_S + +#endif diff --git a/port/mimxrt700/memory_map.h b/port/mimxrt700/memory_map.h index d812fccc..b9d0112a 100644 --- a/port/mimxrt700/memory_map.h +++ b/port/mimxrt700/memory_map.h @@ -78,73 +78,17 @@ #define WT_GUEST_RAM_SIZE 0x00040000u /* Secure runtime RAM: the cpu0 application SRAM (sram0) through its Secure - * alias. The band layout below mirrors the H5 port shifted to this base so - * secure.ld and manifest.json stay in lockstep by one constant. */ + * alias; the isolation level 3 bands are offsets from this base. */ #define WT_RAM_S_BASE 0x30188000u #define WT_RAM_S_SIZE 0x00078000u #define WT_BOOT_HANDOFF_ADDRESS 0x30180000u -#define WT_SP_SECURE_STACK_SIZE 0x00002000u -#define WT_SP_SECURE_STACK_COUNT 5u -#define WT_SP_SECURE_RAM_SIZE \ - (WT_SP_SECURE_STACK_SIZE * WT_SP_SECURE_STACK_COUNT) -#define WT_SP_SECURE_RAM_BASE (WT_RAM_S_BASE + 0x0006E000u) /* 0x301F6000 */ -#define WT_SP_SECURE_RAM_END \ - (WT_SP_SECURE_RAM_BASE + WT_SP_SECURE_RAM_SIZE) /* 0x30200000 */ -#define WT_SP_CRYPTO_STACK_BASE \ - (WT_SP_SECURE_RAM_BASE + 0u * WT_SP_SECURE_STACK_SIZE) -#define WT_SP_ATTEST_STACK_BASE \ - (WT_SP_SECURE_RAM_BASE + 1u * WT_SP_SECURE_STACK_SIZE) -#define WT_SP_FF_SERVER_STACK_BASE \ - (WT_SP_SECURE_RAM_BASE + 2u * WT_SP_SECURE_STACK_SIZE) -#define WT_SP_FF_DRIVER_STACK_BASE \ - (WT_SP_SECURE_RAM_BASE + 3u * WT_SP_SECURE_STACK_SIZE) -#define WT_SP_FF_CLIENT_STACK_BASE \ - (WT_SP_SECURE_RAM_BASE + 4u * WT_SP_SECURE_STACK_SIZE) - -#define WT_CONF_SP_DATA_BASE (WT_RAM_S_BASE + 0x0006B000u) /* 0x301F3000 */ -#define WT_CONF_SP_DATA_SIZE 0x00003000u - -#define WT_SP_VAULT_STACK_BASE (WT_RAM_S_BASE + 0x00067000u) /* 0x301EF000 */ -#define WT_SP_VAULT_STACK_SIZE 0x00004000u - -#define WT_SP_ITS_STACK_BASE (WT_RAM_S_BASE + 0x00065000u) /* 0x301ED000 */ -#define WT_SP_ITS_STACK_SIZE WT_SP_SECURE_STACK_SIZE - -#define WT_SP_PS_STACK_BASE (WT_RAM_S_BASE + 0x00063000u) /* 0x301EB000 */ -#define WT_SP_PS_STACK_SIZE WT_SP_SECURE_STACK_SIZE - -#define WT_SP_FWU_STACK_BASE (WT_RAM_S_BASE + 0x00061000u) /* 0x301E9000 */ -#define WT_SP_FWU_STACK_SIZE WT_SP_SECURE_STACK_SIZE - -/* Keystore data bands: the vault, attestation, and crypto partitions each own - * one private writable band inside the KEYSTORE envelope (isolation level 3). */ -#define WT_KEYSTORE_BASE (WT_RAM_S_BASE + 0x0004D000u) /* 0x301D5000 */ -#define WT_KEYSTORE_SIZE 0x00014000u -#define WT_SP_VAULT_DATA_BASE WT_KEYSTORE_BASE /* 0x301D5000 */ -#define WT_SP_VAULT_DATA_SIZE 0x00002000u /* 8 KiB */ -#define WT_SP_ATTEST_DATA_BASE \ - (WT_SP_VAULT_DATA_BASE + WT_SP_VAULT_DATA_SIZE) /* 0x301D7000 */ -#define WT_SP_ATTEST_DATA_SIZE 0x00000800u /* 2 KiB */ -#define WT_SP_HSM_DATA_BASE \ - (WT_SP_ATTEST_DATA_BASE + WT_SP_ATTEST_DATA_SIZE) /* 0x301D7800 */ -#define WT_SP_HSM_DATA_SIZE 0x00011800u /* 70 KiB */ - -#define WT_SP_VNET_STACK_BASE (WT_RAM_S_BASE + 0x0006B000u) /* 0x301F3000 */ -#define WT_SP_VNET_STACK_SIZE WT_SP_SECURE_STACK_SIZE - -#define WT_VNET_DATA_BASE (WT_RAM_S_BASE + 0x00048000u) /* 0x301D0000 */ -#define WT_VNET_DATA_SIZE 0x00005000u +#include "../common/armv8m/l3_layout.h" /* wolfBoot update partition on XSPI0 (Secure alias). */ #define WT_FWU_UPDATE_FLASH_BASE_S 0x38180000u #define WT_FWU_UPDATE_FLASH_SIZE 0x00040000u -#define WT_CONF_SERVER_MMIO_BASE (WT_CONF_SP_DATA_BASE + 0x00002C00u) -#define WT_CONF_SERVER_MMIO_SIZE 0x00000100u -#define WT_CONF_DRV_MMIO_BASE (WT_CONF_SP_DATA_BASE + 0x00002E00u) -#define WT_CONF_DRV_MMIO_SIZE 0x00000100u - #define WT_SHARED_STATUS_ADDR 0x20100000u /* RAM code band: the Non-secure-callable gateway (SG veneers and entry diff --git a/port/mimxrt700/platform_mimxrt700.c b/port/mimxrt700/platform_mimxrt700.c index df93a134..66eb5fb0 100644 --- a/port/mimxrt700/platform_mimxrt700.c +++ b/port/mimxrt700/platform_mimxrt700.c @@ -19,7 +19,6 @@ */ #include "wolftrust/platform.h" -#include "wolftrust/priv_stack.h" #include "wolftrust/arch.h" #include "wolftrust/guest_verify.h" #include "wolftrust/monitor.h" @@ -143,30 +142,6 @@ int wt_platform_guest_flash_wrp_ok(uintptr_t window_base, size_t window_size) return WT_GUEST_VERIFY_ERROR_WRP; } -int wt_platform_priv_stack_ok(const void *stack, size_t size) -{ - /* Only a coroutine that stays privileged reaches here, so require the stack - * wholly inside SPM-private RAM: the secure SRAM below the lowest - * partition-writable band (WT-FFM-0011). */ -#if defined(CONFIG_VNET) - uintptr_t priv_end = WT_VNET_DATA_BASE; -#else - uintptr_t priv_end = WT_KEYSTORE_BASE; -#endif - - if (stack == NULL) { - return 0; - } - return wt_priv_stack_ok((uintptr_t)stack, size, WT_RAM_S_BASE, priv_end, - NULL, 0u); -} - -volatile void* wt_platform_boot_handoff_region(size_t* size) -{ - *size = WT_RAM_S_BASE - WT_BOOT_HANDOFF_ADDRESS; - return (volatile void*)WT_BOOT_HANDOFF_ADDRESS; -} - static int wt_glikey_write_enable(uintptr_t base, uint32_t index) { static const uint32_t codewords[] = { @@ -340,54 +315,6 @@ void wt_platform_program_memory_windows(const wt_memory_window_t* windows, wt_fabric_apply_windows(&fabric, windows, count); } -extern char _e_secure_text[]; - -/* No peripheral is assignable to a Secure Partition yet: the SPM drives every - * Secure peripheral itself, so every partition DEVICE resource is refused. */ -const struct wt_periph* wt_platform_sp_peripherals(size_t* count) -{ - if (count != NULL) { - *count = 0U; - } - return NULL; -} - -size_t wt_platform_sp_shared_regions(wt_memory_region_t* regions, size_t max) -{ - if (max < 2u) { - return 0u; - } - regions[0].base = WT_FLASH_S_BASE; - regions[0].size = (uintptr_t)_e_secure_text - WT_FLASH_S_BASE; - regions[0].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC; - regions[1].base = (uintptr_t)_e_secure_text; - regions[1].size = WT_FLASH_S_BASE + WT_FLASH_S_SIZE - - (uintptr_t)_e_secure_text; - regions[1].attributes = WT_MEM_ATTR_READ; - return 2u; -} - -size_t wt_platform_spm_private_regions(wt_memory_region_t* regions, - size_t max) -{ - uintptr_t first_band = WT_SP_VAULT_DATA_BASE; - - if (max < 2u) { - return 0u; - } -#if defined(CONFIG_VNET) - first_band = WT_VNET_DATA_BASE; -#endif - regions[0].base = WT_RAM_S_BASE; - regions[0].size = first_band - WT_RAM_S_BASE; - regions[0].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; - /* The RAM code band holds the NSC gateway and the XSPI routines. */ - regions[1].base = WT_RAMFUNC_BASE; - regions[1].size = WT_RAMFUNC_SIZE; - regions[1].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC; - return 2u; -} - #if defined(WT_CONFORMANCE) && (WT_CONFORMANCE == 1) /* PAL interrupt source: the unprivileged DRIVER partition asks for its line * to fire, so the privileged side pends it in the NVIC. */ @@ -406,98 +333,6 @@ void WT_CONF_IRQ_HANDLER(void) wt_spm_conf_irq(WT_CONF_IRQ); } -extern char _s_conf_server_data[]; -extern char _e_conf_server_data[]; -extern char _s_conf_driver_data[]; -extern char _e_conf_driver_data[]; - -static size_t wt_conf_grant(wt_memory_region_t* regions, size_t count, - size_t max, uintptr_t base, uintptr_t end) -{ - if (base < end && count < max) { - regions[count].base = base; - regions[count].size = (uint32_t)(end - base); - regions[count].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; - count++; - } - return count; -} - -size_t wt_platform_conf_sp_grants(int32_t partition_id, - wt_memory_region_t* regions, - size_t count, size_t max) -{ - uintptr_t conf_seg = WT_CONF_SP_DATA_BASE; - - /* The wolfTrust partitions in the image hold none of the suite's data. */ - if (partition_id != SERVER_PARTITION_ID && - partition_id != CLIENT_PARTITION_ID && - partition_id != DRIVER_PARTITION_ID) { - return count; - } - if (partition_id != SERVER_PARTITION_ID) { - count = wt_conf_grant(regions, count, max, conf_seg, - (uintptr_t)_s_conf_server_data); - conf_seg = (uintptr_t)_e_conf_server_data; - } - if (partition_id != DRIVER_PARTITION_ID) { - count = wt_conf_grant(regions, count, max, conf_seg, - (uintptr_t)_s_conf_driver_data); - conf_seg = (uintptr_t)_e_conf_driver_data; - } - if (partition_id != SERVER_PARTITION_ID) { - count = wt_conf_grant(regions, count, max, conf_seg, - WT_CONF_SERVER_MMIO_BASE); - conf_seg = WT_CONF_SERVER_MMIO_BASE + WT_CONF_SERVER_MMIO_SIZE; - } - if (partition_id != DRIVER_PARTITION_ID) { - count = wt_conf_grant(regions, count, max, conf_seg, - WT_CONF_DRV_MMIO_BASE); - conf_seg = WT_CONF_DRV_MMIO_BASE + WT_CONF_DRV_MMIO_SIZE; - } - count = wt_conf_grant(regions, count, max, conf_seg, - WT_CONF_SP_DATA_BASE + WT_CONF_SP_DATA_SIZE); - return count; -} - -size_t wt_platform_conf_shared_regions(wt_memory_region_t* regions, - size_t max) -{ - if (max < 1u) { - return 0u; - } - regions[0].base = WT_CONF_SP_DATA_BASE; - regions[0].size = WT_CONF_SP_DATA_SIZE; - regions[0].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; - return 1u; -} -#endif - -#if (defined(WT_FFM_NEGATIVE_PROBE) && (WT_FFM_NEGATIVE_PROBE == 1)) || \ - (defined(WT_VNET_NEG_PROBE) && (WT_VNET_NEG_PROBE == 1)) || \ - (defined(WT_KEYSTORE_NEG_PROBE) && (WT_KEYSTORE_NEG_PROBE == 1)) || \ - (defined(WT_PERIPH_SP_NEG_PROBE) && (WT_PERIPH_SP_NEG_PROBE == 1)) || \ - (defined(WT_BAND_NEG_PROBE) && (WT_BAND_NEG_PROBE != 0)) || \ - (defined(WT_MANIFEST_NEG_PROBE) && (WT_MANIFEST_NEG_PROBE == 3)) -uintptr_t wt_platform_probe_address(unsigned int target) -{ - switch (target) { - case WT_PROBE_VAULT_DATA_BAND: - return (uintptr_t)WT_SP_VAULT_DATA_BASE; - case WT_PROBE_ATTEST_DATA_BAND: - return (uintptr_t)WT_SP_ATTEST_DATA_BASE; - case WT_PROBE_HSM_DATA_BAND: - return (uintptr_t)WT_SP_HSM_DATA_BASE; - case WT_PROBE_SPM_PERIPHERAL: - return (uintptr_t)WT_TRNG_BASE_S; -#if defined(CONFIG_VNET) - case WT_PROBE_VNET_DATA_BAND: - return (uintptr_t)WT_VNET_DATA_BASE; -#endif - default: - return (uintptr_t)WT_RAM_S_BASE; - } -} #endif void wt_platform_log_fault(wt_guest_id_t guest_id, diff --git a/port/mimxrt700/secure.ld b/port/mimxrt700/secure.ld index 8ed53b16..a8409bad 100644 --- a/port/mimxrt700/secure.ld +++ b/port/mimxrt700/secure.ld @@ -24,48 +24,7 @@ ENTRY(Reset_Handler) MEMORY { FLASH (rx) : ORIGIN = (WT_SECURE_FLASH_ORIGIN + WT_SECURE_IMAGE_HEADER_SIZE), LENGTH = (WT_SECURE_FLASH_SIZE - WT_SECURE_IMAGE_HEADER_SIZE) - /* cpu0 application SRAM through its Secure alias (WT_RAM_S_BASE in - * port/mimxrt700/memory_map.h); the guests live below it in their own - * SRAM partitions through the 0x20000000 Non-secure alias. */ - RAM (rwx): ORIGIN = 0x30188000, LENGTH = 308K - WT_VNET_DATA_LENGTH - /* SERVICE_VNET data band (WT-FFM-0011/0056): the switch state and relay - * scratch the confined vnet partition touches in-thread, carved from the - * RAM tail only in CONFIG_VNET builds (WT_VNET_DATA_LENGTH is 0 otherwise). - * Mirrors WT_VNET_DATA_* in port/mimxrt700/memory_map.h. */ - VNETDATA (rw): ORIGIN = 0x30188000 + 308K - WT_VNET_DATA_LENGTH, - LENGTH = WT_VNET_DATA_LENGTH - /* Per-partition keystore data bands (WT-FFM-0011 Level 3): the vault, - * attestation, and crypto partitions each own one private writable band - * (WT_SP_*_DATA_* in memory_map.h). Top of the general window. */ - VAULTDATA (rw): ORIGIN = 0x301D5000, LENGTH = 8K - ATTESTDATA (rw): ORIGIN = 0x301D7000, LENGTH = 2K - HSMDATA (rw): ORIGIN = 0x301D7800, LENGTH = 70K - /* FWU partition stack: the PSA Firmware Update SP's execution stack - * (WT_SP_FWU_STACK_* in memory_map.h). Privileged coroutine that programs - * the wolfBoot update partition flash. Lowest SP band. */ - FWUSTACK (rw): ORIGIN = 0x301E9000, LENGTH = 8K - /* PS partition stack: the sealed-storage SP's execution stack and - * MPU-domain RW resource (WT_SP_PS_STACK_* in memory_map.h). */ - PSSTACK (rw): ORIGIN = 0x301EB000, LENGTH = 8K - /* ITS partition stack: the unprivileged storage SP's execution stack and - * MPU-domain RW resource (WT_SP_ITS_STACK_* in memory_map.h). */ - ITSSTACK (rw): ORIGIN = 0x301ED000, LENGTH = 8K - /* Vault partition stack (WT-FFM-0047): the privileged vault coroutine's - * manifest-declared execution stack (WT_SP_VAULT_STACK_* in memory_map.h). - * 16K: ECC verify's point-table frame overflows 8K (PSPLIM STKOF). */ - VAULTSTACK (rw): ORIGIN = 0x301EF000, LENGTH = 16K - /* Conformance Secure-Partition .data/.bss (P3a). Arm's partition sources hold - * their val_api/psa_api tables in .data; a hosted SP must reach its own data - * but not the SPM's, so it lives in this 8 KiB window (WT_CONF_SP_DATA_* in - * memory_map.h) that the SP MPU domain grants while SPM RAM at 0x30188000 - * stays denied. Empty in non-conformance builds; CONFIG_VNET builds reuse - * the first 8 KiB as the SERVICE_VNET partition stack (WT_SP_VNET_STACK_*, - * manifest-vnet.json domain) - the two builds are mutually exclusive. */ - CONFDATA (rw): ORIGIN = 0x301F3000, LENGTH = 12K - /* Secure per-partition stacks (WT-FFM-0011). The top 40 KiB of the secure - * RAM window, reserved so each Secure Partition runs on its own secure - * stack the MPU can confine. Mirrors WT_SP_SECURE_RAM_* in memory_map.h. */ - SPSTACKS (rw): ORIGIN = 0x301F6000, LENGTH = 40K + INCLUDE secure_l3_memory.ld /* RAM code band through the Secure Code-region alias of SRAM partition 12 * (WT_RAMFUNC_* in memory_map.h): the NSC gateway, because the IDAU honours * NSC only in the Code region, and the XSPI0 NOR program/erase code, which @@ -74,13 +33,7 @@ MEMORY { RAMFUNC (rx): ORIGIN = 0x10200000, LENGTH = 16K } -_estack = ORIGIN(RAM) + LENGTH(RAM); -/* Secure main stack: MSPLIM_S is set to _sstack at reset. */ -_sstack = _estack - WT_SPM_STACK_SIZE; -_wt_part_stacks_base = ORIGIN(FWUSTACK); -_wt_part_stacks_limit = ORIGIN(VAULTSTACK) + LENGTH(VAULTSTACK); -_wt_sp_stacks_base = ORIGIN(SPSTACKS); -_wt_sp_stacks_limit = ORIGIN(SPSTACKS) + LENGTH(SPSTACKS); +INCLUDE secure_l3_symbols.ld _sidata = LOADADDR(.data); SECTIONS { @@ -152,122 +105,7 @@ SECTIONS { _econfbss = .; } > CONFDATA - /* Per-partition keystore state (WT-FFM-0011 Level 3). Every RAM object a - * keystore partition writes in-thread is claimed by its owning band before - * the general .data/.bss rules run: the vault's NVM store, flash context, - * directory, sealer, and RNG; the attestation partition's token state; the - * crypto partition's engine state and the wolfHSM/wolfCrypt library state - * it alone drives at runtime. Every object named here is built without LTO - * so its claim holds, and tools/check_secure_layout.py checks each claim - * against the owner map. Each band spans loaded data and zero-init bss so - * one MPU region covers the whole trust unit. */ - .vault_data : { - . = ALIGN(32); - _s_vault = .; - _s_keystore = .; - *nvm_store.o(.data .data.*) - *wt_hsm_vault.o(.data .data.*) - *wt_hsm_seal.o(.data .data.*) - *wt_hsm_lock.o(.data .data.*) - *vault_service.o(.data .data.*) - *hsm_flash_ctx.o(.data .data.*) - *wh_sec_wh_nvm.o(.data .data.*) - *wh_sec_wh_nvm_flash.o(.data .data.*) - *wh_sec_wh_flash_unit.o(.data .data.*) - *wh_sec_wh_lock.o(.data .data.*) - . = ALIGN(4); - _e_vault_data = .; - } > VAULTDATA AT > FLASH - _si_vault = LOADADDR(.vault_data); - - .vault_bss (NOLOAD) : { - _s_vault_bss = .; - *nvm_store.o(.bss .bss.* COMMON) - *wt_hsm_vault.o(.bss .bss.* COMMON) - *wt_hsm_seal.o(.bss .bss.* COMMON) - *wt_hsm_lock.o(.bss .bss.* COMMON) - *vault_service.o(.bss .bss.* COMMON) - *hsm_flash_ctx.o(.bss .bss.* COMMON) - *wh_sec_wh_nvm.o(.bss .bss.* COMMON) - *wh_sec_wh_nvm_flash.o(.bss .bss.* COMMON) - *wh_sec_wh_flash_unit.o(.bss .bss.* COMMON) - *wh_sec_wh_lock.o(.bss .bss.* COMMON) - . = ALIGN(32); - _e_vault = .; - } > VAULTDATA - - .attest_data : { - . = ALIGN(32); - _s_attest = .; - *attestation_service.o(.data .data.*) - *initial_attestation.o(.data .data.*) - *attestation_cose.o(.data .data.*) - *wolfcose*.o(.data .data.*) - . = ALIGN(4); - _e_attest_data = .; - } > ATTESTDATA AT > FLASH - _si_attest = LOADADDR(.attest_data); - - .attest_bss (NOLOAD) : { - _s_attest_bss = .; - *attestation_service.o(.bss .bss.* COMMON) - *initial_attestation.o(.bss .bss.* COMMON) - *attestation_cose.o(.bss .bss.* COMMON) - *wolfcose*.o(.bss .bss.* COMMON) - . = ALIGN(32); - _e_attest = .; - } > ATTESTDATA - - .hsm_data : { - . = ALIGN(32); - _s_hsm = .; - *wt_hsm.o(.data .data.*) - *hsm_relay_service.o(.data .data.*) - *nvm_client.o(.data .data.*) - *crypto_native.o(.data .data.*) - *keyvault.o(.data .data.*) - *native_wire.o(.data .data.*) - *wh_sec_*.o(.data .data.*) - *wc_sec_cryptocb.o(.data .data.*) - . = ALIGN(4); - _e_hsm_data = .; - } > HSMDATA AT > FLASH - _si_hsm = LOADADDR(.hsm_data); - - .hsm_bss (NOLOAD) : { - _s_hsm_bss = .; - *wt_hsm.o(.bss .bss.* COMMON) - *hsm_relay_service.o(.bss .bss.* COMMON) - *nvm_client.o(.bss .bss.* COMMON) - *crypto_native.o(.bss .bss.* COMMON) - *keyvault.o(.bss .bss.* COMMON) - *native_wire.o(.bss .bss.* COMMON) - *wh_sec_*.o(.bss .bss.* COMMON) - *wc_sec_cryptocb.o(.bss .bss.* COMMON) - . = ALIGN(32); - _e_hsm = .; - _e_keystore = .; - } > HSMDATA - - /* SERVICE_VNET data band: first-claim the vnet dataplane, service, and - * relay objects' RAM out of shared SPM .data/.bss so the unprivileged - * partition's MPU grant covers exactly its own state. Empty (zero-length - * region) outside CONFIG_VNET builds. */ - .vnet_data : { - . = ALIGN(32); - _s_vnet = .; - *wt_sec_vnet_*.o(.data .data.*) - . = ALIGN(4); - _e_vnet_data = .; - } > VNETDATA AT > FLASH - _si_vnet = LOADADDR(.vnet_data); - - .vnet_bss (NOLOAD) : { - _s_vnet_bss = .; - *wt_sec_vnet_*.o(.bss .bss.* COMMON) - . = ALIGN(32); - _e_vnet = .; - } > VNETDATA + INCLUDE secure_l3_bands.ld /* Linker-synthesized SG veneers (--cmse-implib) open the RAM code band: the * first 1 KiB is the SAU NSC window (WT_NSC_* in memory_map.h), padded so @@ -319,54 +157,5 @@ SECTIONS { _ebss = .; } > RAM - .sp_stacks (NOLOAD) : { - KEEP(*(.sp_stacks*)) - } > SPSTACKS - - ASSERT(ORIGIN(VNETDATA) == (ORIGIN(RAM) + LENGTH(RAM)), - "vnet data band must sit directly above the secure RAM window") - ASSERT(ORIGIN(VAULTDATA) == (ORIGIN(VNETDATA) + LENGTH(VNETDATA)), - "vault data band must sit directly above the vnet data band") - ASSERT(_e_vnet <= (ORIGIN(VNETDATA) + LENGTH(VNETDATA)), - "vnet data band overflow") - ASSERT(ORIGIN(ATTESTDATA) == (ORIGIN(VAULTDATA) + LENGTH(VAULTDATA)), - "attestation data band must sit directly above the vault data band") - ASSERT(ORIGIN(HSMDATA) == (ORIGIN(ATTESTDATA) + LENGTH(ATTESTDATA)), - "crypto data band must sit directly above the attestation data band") - ASSERT(ORIGIN(FWUSTACK) == (ORIGIN(HSMDATA) + LENGTH(HSMDATA)), - "FWU stack must sit directly above the crypto data band") - /* Full band extents: Reset_Handler clears each band end to end before - * loading it, so no retained byte outlives a warm reset or a layout change. */ - _wt_band_vault_base = ORIGIN(VAULTDATA); - _wt_band_vault_limit = ORIGIN(VAULTDATA) + LENGTH(VAULTDATA); - _wt_band_attest_base = ORIGIN(ATTESTDATA); - _wt_band_attest_limit = ORIGIN(ATTESTDATA) + LENGTH(ATTESTDATA); - _wt_band_hsm_base = ORIGIN(HSMDATA); - _wt_band_hsm_limit = ORIGIN(HSMDATA) + LENGTH(HSMDATA); - _wt_band_vnet_base = ORIGIN(VNETDATA); - _wt_band_vnet_limit = ORIGIN(VNETDATA) + LENGTH(VNETDATA); - - ASSERT(_e_vault <= (ORIGIN(VAULTDATA) + LENGTH(VAULTDATA)), - "vault data band overflow") - ASSERT(_e_attest <= (ORIGIN(ATTESTDATA) + LENGTH(ATTESTDATA)), - "attestation data band overflow") - ASSERT(_e_hsm <= (ORIGIN(HSMDATA) + LENGTH(HSMDATA)), - "crypto data band overflow") - ASSERT(ORIGIN(PSSTACK) == (ORIGIN(FWUSTACK) + LENGTH(FWUSTACK)), - "PS stack must sit directly above the FWU stack") - ASSERT(ORIGIN(ITSSTACK) == (ORIGIN(PSSTACK) + LENGTH(PSSTACK)), - "ITS stack must sit directly above the PS stack") - ASSERT(ORIGIN(VAULTSTACK) == (ORIGIN(ITSSTACK) + LENGTH(ITSSTACK)), - "vault stack must sit directly above the ITS stack") - ASSERT(ORIGIN(CONFDATA) == (ORIGIN(VAULTSTACK) + LENGTH(VAULTSTACK)), - "conformance SP data must sit directly above the vault stack") - /* The top 1 KiB of CONFDATA holds the per-partition pseudo-MMIO holes - * (WT_CONF_SERVER/DRV_MMIO in memory_map.h); real data crossing into them - * would be carved out of other partitions' domains and fault at runtime. */ - ASSERT(_econfbss <= (ORIGIN(CONFDATA) + 0x2C00), - "conformance data/bss overflows into the reserved MMIO holes") - ASSERT(ORIGIN(SPSTACKS) == (ORIGIN(CONFDATA) + LENGTH(CONFDATA)), - "SP secure stacks must sit directly above the conformance data window") - ASSERT(_ebss <= _sstack, - "secure .bss reaches the SPM main stack (WT_SPM_STACK_SIZE)") + INCLUDE secure_l3_tail.ld } diff --git a/port/stm32h563/l3_port.h b/port/stm32h563/l3_port.h new file mode 100644 index 00000000..8709ef09 --- /dev/null +++ b/port/stm32h563/l3_port.h @@ -0,0 +1,31 @@ +/* l3_port.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* STM32H563 inputs to the shared Armv8-M isolation level 3 layer. */ + +#ifndef WOLFTRUST_FW_STM32H563_L3_PORT_H +#define WOLFTRUST_FW_STM32H563_L3_PORT_H + +#include "stm32h563_regs.h" + +/* A Secure peripheral only the SPM drives, probed by the periphsp negative. */ +#define WT_L3_SPM_PERIPHERAL_BASE WT_RNG_BASE_S + +#endif diff --git a/port/stm32h563/memory_map.h b/port/stm32h563/memory_map.h index edbe2a8b..6a02244d 100644 --- a/port/stm32h563/memory_map.h +++ b/port/stm32h563/memory_map.h @@ -90,102 +90,7 @@ * WT_RAM_S_BASE is cleared once the record is consumed. */ #define WT_BOOT_HANDOFF_ADDRESS 0x30020000u -/* Secure per-partition stacks (WT-FFM-0011 Level 3 isolation). Each Secure - * Partition runs on its own secure stack so the secure MPU can confine it to - * its own domain. Carved from the top of the secure RAM window that the linker - * uses (0x30028000 + 440 KiB .. 0x300A0000, the end of physical SRAM); the - * main stack (_estack) drops to 0x30096000 to make room. Slots 2-4 host the - * PSA-FF conformance partitions (SERVER/DRIVER/CLIENT) in the conformance - * build. These MUST match the SPSTACKS region in - * src/services/wolfhsm/runner/secure.ld. */ -#define WT_SP_SECURE_STACK_SIZE 0x00002000u /* 8 KiB per partition */ -#define WT_SP_SECURE_STACK_COUNT 5u -#define WT_SP_SECURE_RAM_SIZE \ - (WT_SP_SECURE_STACK_SIZE * WT_SP_SECURE_STACK_COUNT) -#define WT_SP_SECURE_RAM_BASE (WT_RAM_S_BASE + 0x0006E000u) /* 0x30096000 */ -#define WT_SP_SECURE_RAM_END \ - (WT_SP_SECURE_RAM_BASE + WT_SP_SECURE_RAM_SIZE) /* 0x300A0000 */ -#define WT_SP_CRYPTO_STACK_BASE \ - (WT_SP_SECURE_RAM_BASE + 0u * WT_SP_SECURE_STACK_SIZE) /* 0x30096000 */ -#define WT_SP_ATTEST_STACK_BASE \ - (WT_SP_SECURE_RAM_BASE + 1u * WT_SP_SECURE_STACK_SIZE) /* 0x30098000 */ -#define WT_SP_FF_SERVER_STACK_BASE \ - (WT_SP_SECURE_RAM_BASE + 2u * WT_SP_SECURE_STACK_SIZE) /* 0x3009A000 */ -#define WT_SP_FF_DRIVER_STACK_BASE \ - (WT_SP_SECURE_RAM_BASE + 3u * WT_SP_SECURE_STACK_SIZE) /* 0x3009C000 */ -#define WT_SP_FF_CLIENT_STACK_BASE \ - (WT_SP_SECURE_RAM_BASE + 4u * WT_SP_SECURE_STACK_SIZE) /* 0x3009E000 */ - -/* Conformance Secure-Partition .data/.bss window (P3a). Arm's partition sources - * keep val_api/psa_api in .data; a hosted SP reaches its own data here while - * SPM RAM at 0x30028000 stays outside its MPU domain. Sits just below the SP - * stacks (the linker's RAM window is shortened to make room); MUST match the - * CONFDATA region in src/services/wolfhsm/runner/secure.ld. */ -#define WT_CONF_SP_DATA_BASE (WT_RAM_S_BASE + 0x0006B000u) /* 0x30093000 */ -#define WT_CONF_SP_DATA_SIZE 0x00003000u /* 12 KiB */ - -/* Vault partition stack (WT-FFM-0047). The vault runs as a scheduled - * UNPRIVILEGED SP, so this band is both its execution stack and its - * MPU-domain RW resource. Sits just below the conformance data window; the - * linker RAM window is shortened to 420 KiB to make room. MUST match the - * VAULTSTACK region in src/services/wolfhsm/runner/secure.ld. */ -/* 16 KiB: ECC verify's arbitrary-point multiply (sp_256_ecc_mulmod_fast_8) - * stacks a point table that overflows an 8 KiB coroutine stack (M33MU - * PSPLIM STKOF proof). */ -#define WT_SP_VAULT_STACK_BASE (WT_RAM_S_BASE + 0x00067000u) /* 0x3008F000 */ -#define WT_SP_VAULT_STACK_SIZE 0x00004000u - -/* ITS partition stack: a normal unprivileged scheduled SP; this band is both - * its execution stack and its MPU-domain RW resource. Sits just below the - * vault stack; the linker RAM window is shortened to 412 KiB to make room. - * MUST match the ITSSTACK region in src/services/wolfhsm/runner/secure.ld. */ -#define WT_SP_ITS_STACK_BASE (WT_RAM_S_BASE + 0x00065000u) /* 0x3008D000 */ -#define WT_SP_ITS_STACK_SIZE WT_SP_SECURE_STACK_SIZE - -#define WT_SP_PS_STACK_BASE (WT_RAM_S_BASE + 0x00063000u) /* 0x3008B000 */ -#define WT_SP_PS_STACK_SIZE WT_SP_SECURE_STACK_SIZE - -/* FWU partition stack: the PSA Firmware Update SP runs as a scheduled - * UNPRIVILEGED SP (flash programming traps to the SVC gate), so this band is - * both its execution stack and its MPU-domain RW resource. Sits just below - * the PS stack; the linker RAM window is shortened to 388 KiB to make room. - * MUST match the FWUSTACK region in src/services/wolfhsm/runner/secure.ld. */ -#define WT_SP_FWU_STACK_BASE (WT_RAM_S_BASE + 0x00061000u) /* 0x30089000 */ -#define WT_SP_FWU_STACK_SIZE WT_SP_SECURE_STACK_SIZE - -/* Keystore data bands: the vault, attestation, and crypto (SERVICE_HSM) - * partitions each own one private writable band; no two partitions share a - * writable byte (isolation level 3). The envelope mirrors the KEYSTORE - * region in secure.ld; the sub-bands mirror its VAULTDATA, ATTESTDATA, and - * HSMDATA regions. */ -#define WT_KEYSTORE_BASE (WT_RAM_S_BASE + 0x0004D000u) /* 0x30075000 */ -#define WT_KEYSTORE_SIZE 0x00014000u /* 80 KiB */ -#define WT_SP_VAULT_DATA_BASE WT_KEYSTORE_BASE /* 0x30075000 */ -#define WT_SP_VAULT_DATA_SIZE 0x00002000u /* 8 KiB */ -#define WT_SP_ATTEST_DATA_BASE \ - (WT_SP_VAULT_DATA_BASE + WT_SP_VAULT_DATA_SIZE) /* 0x30077000 */ -#define WT_SP_ATTEST_DATA_SIZE 0x00000800u /* 2 KiB */ -#define WT_SP_HSM_DATA_BASE \ - (WT_SP_ATTEST_DATA_BASE + WT_SP_ATTEST_DATA_SIZE) /* 0x30077800 */ -#define WT_SP_HSM_DATA_SIZE 0x00011800u /* 70 KiB */ - -/* VNET partition stack (CONFIG_VNET builds): SERVICE_VNET's scheduled - * coroutine stack aliases the conformance data window - VNET and - * WT_CONFORMANCE builds are mutually exclusive, and the window is empty - * outside conformance builds, so the secure RAM chain needs no growth. - * MUST match the CONFDATA origin in src/services/wolfhsm/runner/secure.ld - * and the manifest-vnet.json domain stack. */ -#define WT_SP_VNET_STACK_BASE (WT_RAM_S_BASE + 0x0006B000u) /* 0x30093000 */ -#define WT_SP_VNET_STACK_SIZE WT_SP_SECURE_STACK_SIZE - -/* VNET data band (CONFIG_VNET builds): every RAM object the confined - * SERVICE_VNET partition touches in-thread — the switch, its pools/rings/FDB, - * and the relay's staging scratch — carved from the tail of general secure RAM - * so the unprivileged coroutine reaches only its own state. MUST match the - * VNETDATA region in src/services/wolfhsm/runner/secure.ld and the - * manifest-vnet.json domain resource. */ -#define WT_VNET_DATA_BASE (WT_RAM_S_BASE + 0x00048000u) /* 0x30070000 */ -#define WT_VNET_DATA_SIZE 0x00005000u /* 20 KiB */ +#include "../common/armv8m/l3_layout.h" /* wolfBoot update partition (WOLFBOOT_PARTITION_UPDATE_ADDRESS): the secure * flash window SERVICE_FWU stages a candidate image into (WT-FWU-0002). Secure @@ -193,18 +98,6 @@ #define WT_FWU_UPDATE_FLASH_BASE_S 0x0C100000u #define WT_FWU_UPDATE_FLASH_SIZE 0x00040000u -/* Per-partition pseudo-MMIO holes at the top of the CONFDATA window (P4/K4). - * Each belongs to exactly one Arm conformance partition; the scheduler grants - * every other SP the window WITHOUT its hole, so the L3 MMIO-isolation panic - * tests (i047/i055/i057) see a genuine out-of-domain access. MUST match - * SERVER/DRIVER_PARTITION_MMIO_0_* in conformance/pal_config.h (guarded by an - * #error cross-check in conformance/conf_nvm_sync.c) and stay above _econfbss - * (link-time assert in runner/secure.ld). */ -#define WT_CONF_SERVER_MMIO_BASE (WT_CONF_SP_DATA_BASE + 0x00002C00u) /* 0x30095C00 */ -#define WT_CONF_SERVER_MMIO_SIZE 0x00000100u -#define WT_CONF_DRV_MMIO_BASE (WT_CONF_SP_DATA_BASE + 0x00002E00u) /* 0x30095E00 */ -#define WT_CONF_DRV_MMIO_SIZE 0x00000100u - #define WT_SHARED_STATUS_ADDR 0x20000000u #define WT_PLATFORM_CORE_CLOCK_HZ 240000000u diff --git a/port/stm32h563/platform_stm32h563.c b/port/stm32h563/platform_stm32h563.c index 27643aad..09d1237d 100644 --- a/port/stm32h563/platform_stm32h563.c +++ b/port/stm32h563/platform_stm32h563.c @@ -19,7 +19,6 @@ */ #include "wolftrust/platform.h" -#include "wolftrust/priv_stack.h" #include "wolftrust/arch.h" #include "wolftrust/guest_verify.h" #include "wolftrust/monitor.h" @@ -259,12 +258,6 @@ static int wt_gtzc_init(void) return (wt_gtzc_attribution_ok(nsWords) == 1) ? 0 : -1; } -volatile void* wt_platform_boot_handoff_region(size_t* size) -{ - *size = WT_RAM_S_BASE - WT_BOOT_HANDOFF_ADDRESS; - return (volatile void*)WT_BOOT_HANDOFF_ADDRESS; -} - #if defined(WT_BUSFAULT_NEG_PROBE) && (WT_BUSFAULT_NEG_PROBE == 1) /* The 32 KiB past the end of physical SRAM3 (0x300A0000) is unmapped on the * H563, so an MPU-permitted read there is a precise BusFault on silicon. */ @@ -488,181 +481,6 @@ void wt_platform_program_memory_windows(const wt_memory_window_t* windows, wt_fabric_apply_windows(&fabric, windows, count); } -/* End of executable image code (secure.ld): the SP thread tables grant RX up - * to here (the manifest's 4K code window lies inside it and Armv8-M regions - * must not overlap — task #26 tracks per-partition narrowing) and the rest of - * the image window (constant data and the signed tail) read-only XN - * (WT-FFM-0010). */ -extern char _e_secure_text[]; - -/* No peripheral is assignable to a Secure Partition yet: the SPM drives every - * Secure peripheral itself, so every partition DEVICE resource is refused. */ -const struct wt_periph* wt_platform_sp_peripherals(size_t* count) -{ - if (count != NULL) { - *count = 0U; - } - return NULL; -} - -size_t wt_platform_sp_shared_regions(wt_memory_region_t* regions, size_t max) -{ - if (max < 2u) { - return 0u; - } - regions[0].base = WT_FLASH_S_BASE; - regions[0].size = (uintptr_t)_e_secure_text - WT_FLASH_S_BASE; - regions[0].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC; - regions[1].base = (uintptr_t)_e_secure_text; - regions[1].size = WT_FLASH_S_BASE + WT_FLASH_S_SIZE - - (uintptr_t)_e_secure_text; - regions[1].attributes = WT_MEM_ATTR_READ; - return 2u; -} - -size_t wt_platform_spm_private_regions(wt_memory_region_t* regions, - size_t max) -{ - uintptr_t first_band = WT_SP_VAULT_DATA_BASE; - - if (max < 1u) { - return 0u; - } -#if defined(CONFIG_VNET) - first_band = WT_VNET_DATA_BASE; -#endif - regions[0].base = WT_RAM_S_BASE; - regions[0].size = first_band - WT_RAM_S_BASE; - regions[0].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; - return 1u; -} - -#if defined(WT_CONFORMANCE) && (WT_CONFORMANCE == 1) -/* Per-partition private data bands (secure.ld), each denied to other SPs. */ -extern char _s_conf_server_data[]; -extern char _e_conf_server_data[]; -extern char _s_conf_driver_data[]; -extern char _e_conf_driver_data[]; - -/* Append one RW grant segment to an SP's thread table (skips empty segments, - * fails closed by granting nothing when the table is full). */ -static size_t wt_conf_grant(wt_memory_region_t* regions, size_t count, - size_t max, uintptr_t base, uintptr_t end) -{ - if (base < end && count < max) { - regions[count].base = base; - regions[count].size = (uint32_t)(end - base); - regions[count].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; - count++; - } - return count; -} - -/* Hosted Arm partitions read their val_api/psa_api tables from .data, which - * the linker places in the shared CONFDATA window; grant it so the SP - * reaches its own data while SPM RAM stays denied. The per-partition - * pseudo-MMIO holes at the top of the window (memory_map.h) each belong to - * exactly one partition — every other SP gets the window with that hole - * carved out, so the L3 MMIO-isolation panic tests (i047/i055/i057) hit a - * genuine out-of-domain access and the must-panic reset path fires. Also - * carve the per-partition data bands (i080/i084): a cross-partition read of - * another SP's .data/.bss must fault. Bands are adjacent, so a non-owner's - * empty middle segment is skipped by wt_conf_grant. */ -size_t wt_platform_conf_sp_grants(int32_t partition_id, - wt_memory_region_t* regions, - size_t count, size_t max) -{ - uintptr_t conf_seg = WT_CONF_SP_DATA_BASE; - - /* The wolfTrust partitions in the image hold none of the suite's data. */ - if (partition_id != SERVER_PARTITION_ID && - partition_id != CLIENT_PARTITION_ID && - partition_id != DRIVER_PARTITION_ID) { - return count; - } - if (partition_id != SERVER_PARTITION_ID) { - count = wt_conf_grant(regions, count, max, conf_seg, - (uintptr_t)_s_conf_server_data); - conf_seg = (uintptr_t)_e_conf_server_data; - } - if (partition_id != DRIVER_PARTITION_ID) { - count = wt_conf_grant(regions, count, max, conf_seg, - (uintptr_t)_s_conf_driver_data); - conf_seg = (uintptr_t)_e_conf_driver_data; - } - if (partition_id != SERVER_PARTITION_ID) { - count = wt_conf_grant(regions, count, max, conf_seg, - WT_CONF_SERVER_MMIO_BASE); - conf_seg = WT_CONF_SERVER_MMIO_BASE + WT_CONF_SERVER_MMIO_SIZE; - } - if (partition_id != DRIVER_PARTITION_ID) { - count = wt_conf_grant(regions, count, max, conf_seg, - WT_CONF_DRV_MMIO_BASE); - conf_seg = WT_CONF_DRV_MMIO_BASE + WT_CONF_DRV_MMIO_SIZE; - } - count = wt_conf_grant(regions, count, max, conf_seg, - WT_CONF_SP_DATA_BASE + WT_CONF_SP_DATA_SIZE); - return count; -} - -size_t wt_platform_conf_shared_regions(wt_memory_region_t* regions, - size_t max) -{ - if (max < 1u) { - return 0u; - } - regions[0].base = WT_CONF_SP_DATA_BASE; - regions[0].size = WT_CONF_SP_DATA_SIZE; - regions[0].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; - return 1u; -} -#endif - -#if (defined(WT_FFM_NEGATIVE_PROBE) && (WT_FFM_NEGATIVE_PROBE == 1)) || \ - (defined(WT_VNET_NEG_PROBE) && (WT_VNET_NEG_PROBE == 1)) || \ - (defined(WT_KEYSTORE_NEG_PROBE) && (WT_KEYSTORE_NEG_PROBE == 1)) || \ - (defined(WT_PERIPH_SP_NEG_PROBE) && (WT_PERIPH_SP_NEG_PROBE == 1)) || \ - (defined(WT_BAND_NEG_PROBE) && (WT_BAND_NEG_PROBE != 0)) || \ - (defined(WT_MANIFEST_NEG_PROBE) && (WT_MANIFEST_NEG_PROBE == 3)) -uintptr_t wt_platform_probe_address(unsigned int target) -{ - switch (target) { - case WT_PROBE_VAULT_DATA_BAND: - return (uintptr_t)WT_SP_VAULT_DATA_BASE; - case WT_PROBE_ATTEST_DATA_BAND: - return (uintptr_t)WT_SP_ATTEST_DATA_BASE; - case WT_PROBE_HSM_DATA_BAND: - return (uintptr_t)WT_SP_HSM_DATA_BASE; - case WT_PROBE_SPM_PERIPHERAL: - return (uintptr_t)WT_RNG_BASE_S; -#if defined(CONFIG_VNET) - case WT_PROBE_VNET_DATA_BAND: - return (uintptr_t)WT_VNET_DATA_BASE; -#endif - default: - return (uintptr_t)WT_RAM_S_BASE; - } -} -#endif - -int wt_platform_priv_stack_ok(const void *stack, size_t size) -{ - /* Only a coroutine that stays privileged reaches here, so require the stack - * wholly inside SPM-private RAM: the secure SRAM below the lowest - * partition-writable band (WT-FFM-0011). */ -#if defined(CONFIG_VNET) - uintptr_t priv_end = WT_VNET_DATA_BASE; -#else - uintptr_t priv_end = WT_KEYSTORE_BASE; -#endif - - if (stack == NULL) { - return 0; - } - return wt_priv_stack_ok((uintptr_t)stack, size, WT_RAM_S_BASE, priv_end, - NULL, 0u); -} - void wt_platform_log_fault(wt_guest_id_t guest_id, wt_fault_reason_t reason, uintptr_t fault_address, diff --git a/port/stm32h563/secure.ld b/port/stm32h563/secure.ld new file mode 100644 index 00000000..8e620948 --- /dev/null +++ b/port/stm32h563/secure.ld @@ -0,0 +1,137 @@ +/* secure.ld + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTrust is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +ENTRY(Reset_Handler) + +MEMORY { + FLASH (rx) : ORIGIN = (WT_SECURE_FLASH_ORIGIN + WT_SECURE_IMAGE_HEADER_SIZE), + LENGTH = (WT_SECURE_FLASH_SIZE - WT_SECURE_IMAGE_HEADER_SIZE) + INCLUDE secure_l3_memory.ld +} + +INCLUDE secure_l3_symbols.ld +_sidata = LOADADDR(.data); + +SECTIONS { + .vectors (WT_SECURE_FLASH_ORIGIN + WT_SECURE_IMAGE_HEADER_SIZE) : { + KEEP(*(.vectors)) + } > FLASH + + /* Linker-synthesized SG veneers land here (--cmse-implib); veneer bodies + * live in .text. The dot assignment keeps the section address assigned even + * when no input object carries .gnu.sgstubs content, or ld cannot size the + * synthesized stubs ("no address assigned to the veneers output section"). */ + .gnu.sgstubs (WT_SECURE_FLASH_ORIGIN + WT_SECURE_IMAGE_HEADER_SIZE + 0x400) : { + *(.gnu.sgstubs) + *(.gnu.sgstubs*) + . = ALIGN(32); + } > FLASH + + .nsc ALIGN(32) : { + KEEP(*(.nsc*)) + } > FLASH + + .text (WT_SECURE_FLASH_ORIGIN + WT_SECURE_IMAGE_HEADER_SIZE + 0x800) : { + _s_secure_text = .; + *(.text*) + *(.glue_7) + *(.glue_7t) + . = ALIGN(32); + _e_secure_text = .; + } > FLASH + + /* Constant data is readable, never executable (WT-FFM-0010): the SP thread + * MPU tables grant [flash, _e_secure_text) RX and the rest of the image + * window read-only XN, so no partition can execute from constant data. */ + /* READONLY keeps the output section read-only when the non-LTO objects + * still carry these globals as .data/.bss input sections. */ + .rodata (READONLY) : { + . = ALIGN(4); + KEEP(*(.wt_guest_meas)) + /* wolfCrypt globals that are only ever read (the AES prefetch anchor, the + * DRBG selection flag): read-only and shared, so every partition's crypto + * sees them without any partition owning them (isolation level 3). + * tools/check_secure_layout.py fails the link if a setter of them is + * referenced (wc_Sha256Drbg_Enable/Disable). */ + *(.data.always_prefetch* .bss.always_prefetch*) + *(.data.sha256DrbgDisabled* .bss.sha256DrbgDisabled*) + *(.rodata*) + *(.ARM.extab* .gnu.linkonce.armextab.*) + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + *(.eh_frame) + } > FLASH + + ASSERT(WT_SECURE_IMAGE_HEADER_SIZE < WT_SECURE_FLASH_SIZE, + "secure image header consumes secure flash region") + ASSERT(. <= (WT_SECURE_FLASH_ORIGIN + WT_SECURE_FLASH_SIZE), + "secure image overflows secure flash region") + ASSERT(SIZEOF(.gnu.sgstubs) + SIZEOF(.nsc) <= 0x400, "NSC window overflow") + + /* Claim the conformance partitions' .data/.bss before the general rules so + * they land in CONFDATA, not shared SPM RAM. Loaded from flash and copied by + * Reset_Handler alongside .data; empty (zero size) in non-conformance builds. */ + /* Per-partition private data bands (Arm i080/i084 SP data isolation): the + * server partition's test data and the driver partition's data each get a + * 32-byte-aligned band the SVC gate denies to every other partition. Their + * .bss lands in the loaded section so each band stays one contiguous MPU + * carve; the flash cost is a few zero words. */ + .conf_data : { + . = ALIGN(4); + _sconfdata = .; + EXCLUDE_FILE(*conf_sec_test_supp_*.o *conf_sec_driver_partition.o) *conf_sec_*.o(.data .data.*) + . = ALIGN(32); + _s_conf_server_data = .; + *conf_sec_test_supp_*.o(.data .data.* .bss .bss.* COMMON) + . = ALIGN(32); + _e_conf_server_data = .; + _s_conf_driver_data = .; + *conf_sec_driver_partition.o(.data .data.* .bss .bss.* COMMON) + . = ALIGN(32); + _e_conf_driver_data = .; + _econfdata = .; + } > CONFDATA AT > FLASH + _siconfdata = LOADADDR(.conf_data); + + .conf_bss (NOLOAD) : { + . = ALIGN(4); + _sconfbss = .; + EXCLUDE_FILE(*conf_sec_test_supp_*.o *conf_sec_driver_partition.o) *conf_sec_*.o(.bss .bss.* COMMON) + . = ALIGN(4); + _econfbss = .; + } > CONFDATA + + INCLUDE secure_l3_bands.ld + + .data : { + _sdata = .; + *(.data*) + _edata = .; + } > RAM AT > FLASH + + .bss (NOLOAD) : { + _sbss = .; + *(.bss*) + *(COMMON) + _ebss = .; + } > RAM + + INCLUDE secure_l3_tail.ld +} diff --git a/src/services/wolfhsm/runner/secure.ld b/src/services/wolfhsm/runner/secure.ld deleted file mode 100644 index 197105b8..00000000 --- a/src/services/wolfhsm/runner/secure.ld +++ /dev/null @@ -1,350 +0,0 @@ -/* secure.ld - * - * Copyright (C) 2026 wolfSSL Inc. - * - * This file is part of wolfTrust. - * - * wolfTrust is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * wolfTrust is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA - */ - -ENTRY(Reset_Handler) - -MEMORY { - FLASH (rx) : ORIGIN = (WT_SECURE_FLASH_ORIGIN + WT_SECURE_IMAGE_HEADER_SIZE), - LENGTH = (WT_SECURE_FLASH_SIZE - WT_SECURE_IMAGE_HEADER_SIZE) - /* SRAM1 has Secure and Non-secure aliases to the same physical memory. - * The first 32 KiB are assigned to the two guests through the 0x20000000 - * Non-secure alias, so keep secure .data/.bss above that physical window. */ - RAM (rwx): ORIGIN = 0x30028000, LENGTH = 308K - WT_VNET_DATA_LENGTH - /* SERVICE_VNET data band (WT-FFM-0011/0056): the switch state and relay - * scratch the confined vnet partition touches in-thread, carved from the - * RAM tail only in CONFIG_VNET builds (WT_VNET_DATA_LENGTH is 0 otherwise). - * Mirrors WT_VNET_DATA_* in port/stm32h563/memory_map.h. */ - VNETDATA (rw): ORIGIN = 0x30028000 + 308K - WT_VNET_DATA_LENGTH, - LENGTH = WT_VNET_DATA_LENGTH - /* Keystore data bands (WT-FFM-0011 Level 3): the vault, attestation, and - * crypto (SERVICE_HSM) partitions each own one MPU-alignable band holding - * exactly their writable state, so no two partitions share a writable - * byte. Adjacent, top of the general window; mirror WT_SP_*_DATA_* in - * port/stm32h563/memory_map.h. */ - VAULTDATA (rw): ORIGIN = 0x30075000, LENGTH = 8K - ATTESTDATA (rw): ORIGIN = 0x30077000, LENGTH = 2K - HSMDATA (rw): ORIGIN = 0x30077800, LENGTH = 70K - /* FWU partition stack: the PSA Firmware Update SP's execution stack - * (WT_SP_FWU_STACK_* in memory_map.h). Unprivileged SP whose flash - * programming traps to the SVC gate. Lowest SP band. */ - FWUSTACK (rw): ORIGIN = 0x30089000, LENGTH = 8K - /* PS partition stack: the sealed-storage SP's execution stack and - * MPU-domain RW resource (WT_SP_PS_STACK_* in memory_map.h). */ - PSSTACK (rw): ORIGIN = 0x3008B000, LENGTH = 8K - /* ITS partition stack: the unprivileged storage SP's execution stack and - * MPU-domain RW resource (WT_SP_ITS_STACK_* in memory_map.h). */ - ITSSTACK (rw): ORIGIN = 0x3008D000, LENGTH = 8K - /* Vault partition stack (WT-FFM-0047): the unprivileged vault SP's - * execution stack and MPU-domain RW resource (WT_SP_VAULT_STACK_*). - * 16K: ECC verify's point-table frame overflows 8K (PSPLIM STKOF). */ - VAULTSTACK (rw): ORIGIN = 0x3008F000, LENGTH = 16K - /* Conformance Secure-Partition .data/.bss (P3a). Arm's partition sources hold - * their val_api/psa_api tables in .data; a hosted SP must reach its own data - * but not the SPM's, so it lives in this 8 KiB window (WT_CONF_SP_DATA_* in - * memory_map.h) that the SP MPU domain grants while SPM RAM at 0x30028000 - * stays denied. Empty in non-conformance builds; CONFIG_VNET builds reuse - * the first 8 KiB as the SERVICE_VNET partition stack (WT_SP_VNET_STACK_*, - * manifest-vnet.json domain) - the two builds are mutually exclusive. */ - CONFDATA (rw): ORIGIN = 0x30093000, LENGTH = 12K - /* Secure per-partition stacks (WT-FFM-0011). The top 40 KiB of the secure - * RAM window, reserved so each Secure Partition runs on its own secure - * stack the MPU can confine. Mirrors WT_SP_SECURE_RAM_* in memory_map.h. */ - SPSTACKS (rw): ORIGIN = 0x30096000, LENGTH = 40K -} - -_estack = ORIGIN(RAM) + LENGTH(RAM); -/* Secure main stack: MSPLIM_S is set to _sstack at reset. */ -_sstack = _estack - WT_SPM_STACK_SIZE; -_wt_part_stacks_base = ORIGIN(FWUSTACK); -_wt_part_stacks_limit = ORIGIN(VAULTSTACK) + LENGTH(VAULTSTACK); -_wt_sp_stacks_base = ORIGIN(SPSTACKS); -_wt_sp_stacks_limit = ORIGIN(SPSTACKS) + LENGTH(SPSTACKS); -_sidata = LOADADDR(.data); - -SECTIONS { - .vectors (WT_SECURE_FLASH_ORIGIN + WT_SECURE_IMAGE_HEADER_SIZE) : { - KEEP(*(.vectors)) - } > FLASH - - /* Linker-synthesized SG veneers land here (--cmse-implib); veneer bodies - * live in .text. The dot assignment keeps the section address assigned even - * when no input object carries .gnu.sgstubs content, or ld cannot size the - * synthesized stubs ("no address assigned to the veneers output section"). */ - .gnu.sgstubs (WT_SECURE_FLASH_ORIGIN + WT_SECURE_IMAGE_HEADER_SIZE + 0x400) : { - *(.gnu.sgstubs) - *(.gnu.sgstubs*) - . = ALIGN(32); - } > FLASH - - .nsc ALIGN(32) : { - KEEP(*(.nsc*)) - } > FLASH - - .text (WT_SECURE_FLASH_ORIGIN + WT_SECURE_IMAGE_HEADER_SIZE + 0x800) : { - _s_secure_text = .; - *(.text*) - *(.glue_7) - *(.glue_7t) - . = ALIGN(32); - _e_secure_text = .; - } > FLASH - - /* Constant data is readable, never executable (WT-FFM-0010): the SP thread - * MPU tables grant [flash, _e_secure_text) RX and the rest of the image - * window read-only XN, so no partition can execute from constant data. */ - /* READONLY keeps the output section read-only when the non-LTO objects - * still carry these globals as .data/.bss input sections. */ - .rodata (READONLY) : { - . = ALIGN(4); - KEEP(*(.wt_guest_meas)) - /* wolfCrypt globals that are only ever read (the AES prefetch anchor, the - * DRBG selection flag): read-only and shared, so every partition's crypto - * sees them without any partition owning them (isolation level 3). - * tools/check_secure_layout.py fails the link if a setter of them is - * referenced (wc_Sha256Drbg_Enable/Disable). */ - *(.data.always_prefetch* .bss.always_prefetch*) - *(.data.sha256DrbgDisabled* .bss.sha256DrbgDisabled*) - *(.rodata*) - *(.ARM.extab* .gnu.linkonce.armextab.*) - *(.ARM.exidx* .gnu.linkonce.armexidx.*) - *(.eh_frame) - } > FLASH - - ASSERT(WT_SECURE_IMAGE_HEADER_SIZE < WT_SECURE_FLASH_SIZE, - "secure image header consumes secure flash region") - ASSERT(. <= (WT_SECURE_FLASH_ORIGIN + WT_SECURE_FLASH_SIZE), - "secure image overflows secure flash region") - ASSERT(SIZEOF(.gnu.sgstubs) + SIZEOF(.nsc) <= 0x400, "NSC window overflow") - - /* Claim the conformance partitions' .data/.bss before the general rules so - * they land in CONFDATA, not shared SPM RAM. Loaded from flash and copied by - * Reset_Handler alongside .data; empty (zero size) in non-conformance builds. */ - /* Per-partition private data bands (Arm i080/i084 SP data isolation): the - * server partition's test data and the driver partition's data each get a - * 32-byte-aligned band the SVC gate denies to every other partition. Their - * .bss lands in the loaded section so each band stays one contiguous MPU - * carve; the flash cost is a few zero words. */ - .conf_data : { - . = ALIGN(4); - _sconfdata = .; - EXCLUDE_FILE(*conf_sec_test_supp_*.o *conf_sec_driver_partition.o) *conf_sec_*.o(.data .data.*) - . = ALIGN(32); - _s_conf_server_data = .; - *conf_sec_test_supp_*.o(.data .data.* .bss .bss.* COMMON) - . = ALIGN(32); - _e_conf_server_data = .; - _s_conf_driver_data = .; - *conf_sec_driver_partition.o(.data .data.* .bss .bss.* COMMON) - . = ALIGN(32); - _e_conf_driver_data = .; - _econfdata = .; - } > CONFDATA AT > FLASH - _siconfdata = LOADADDR(.conf_data); - - .conf_bss (NOLOAD) : { - . = ALIGN(4); - _sconfbss = .; - EXCLUDE_FILE(*conf_sec_test_supp_*.o *conf_sec_driver_partition.o) *conf_sec_*.o(.bss .bss.* COMMON) - . = ALIGN(4); - _econfbss = .; - } > CONFDATA - - /* Per-partition keystore state (WT-FFM-0011 Level 3). Every RAM object a - * keystore partition writes in-thread is claimed by its owning band before - * the general .data/.bss rules run: the vault's NVM store, flash context, - * directory, sealer, and RNG; the attestation partition's token state; the - * crypto partition's engine state and the wolfHSM/wolfCrypt library state - * it alone drives at runtime. Every object named here is built without LTO - * so its claim holds, and tools/check_secure_layout.py checks each claim - * against the owner map. Each band spans loaded data and zero-init bss so - * one MPU region covers the whole trust unit. */ - .vault_data : { - . = ALIGN(32); - _s_vault = .; - _s_keystore = .; - *nvm_store.o(.data .data.*) - *wt_hsm_vault.o(.data .data.*) - *wt_hsm_seal.o(.data .data.*) - *wt_hsm_lock.o(.data .data.*) - *vault_service.o(.data .data.*) - *hsm_flash_ctx.o(.data .data.*) - *wh_sec_wh_nvm.o(.data .data.*) - *wh_sec_wh_nvm_flash.o(.data .data.*) - *wh_sec_wh_flash_unit.o(.data .data.*) - *wh_sec_wh_lock.o(.data .data.*) - . = ALIGN(4); - _e_vault_data = .; - } > VAULTDATA AT > FLASH - _si_vault = LOADADDR(.vault_data); - - .vault_bss (NOLOAD) : { - _s_vault_bss = .; - *nvm_store.o(.bss .bss.* COMMON) - *wt_hsm_vault.o(.bss .bss.* COMMON) - *wt_hsm_seal.o(.bss .bss.* COMMON) - *wt_hsm_lock.o(.bss .bss.* COMMON) - *vault_service.o(.bss .bss.* COMMON) - *hsm_flash_ctx.o(.bss .bss.* COMMON) - *wh_sec_wh_nvm.o(.bss .bss.* COMMON) - *wh_sec_wh_nvm_flash.o(.bss .bss.* COMMON) - *wh_sec_wh_flash_unit.o(.bss .bss.* COMMON) - *wh_sec_wh_lock.o(.bss .bss.* COMMON) - . = ALIGN(32); - _e_vault = .; - } > VAULTDATA - - .attest_data : { - . = ALIGN(32); - _s_attest = .; - *attestation_service.o(.data .data.*) - *initial_attestation.o(.data .data.*) - *attestation_cose.o(.data .data.*) - *wolfcose*.o(.data .data.*) - . = ALIGN(4); - _e_attest_data = .; - } > ATTESTDATA AT > FLASH - _si_attest = LOADADDR(.attest_data); - - .attest_bss (NOLOAD) : { - _s_attest_bss = .; - *attestation_service.o(.bss .bss.* COMMON) - *initial_attestation.o(.bss .bss.* COMMON) - *attestation_cose.o(.bss .bss.* COMMON) - *wolfcose*.o(.bss .bss.* COMMON) - . = ALIGN(32); - _e_attest = .; - } > ATTESTDATA - - .hsm_data : { - . = ALIGN(32); - _s_hsm = .; - *wt_hsm.o(.data .data.*) - *hsm_relay_service.o(.data .data.*) - *nvm_client.o(.data .data.*) - *crypto_native.o(.data .data.*) - *keyvault.o(.data .data.*) - *native_wire.o(.data .data.*) - *wh_sec_*.o(.data .data.*) - *wc_sec_cryptocb.o(.data .data.*) - . = ALIGN(4); - _e_hsm_data = .; - } > HSMDATA AT > FLASH - _si_hsm = LOADADDR(.hsm_data); - - .hsm_bss (NOLOAD) : { - _s_hsm_bss = .; - *wt_hsm.o(.bss .bss.* COMMON) - *hsm_relay_service.o(.bss .bss.* COMMON) - *nvm_client.o(.bss .bss.* COMMON) - *crypto_native.o(.bss .bss.* COMMON) - *keyvault.o(.bss .bss.* COMMON) - *native_wire.o(.bss .bss.* COMMON) - *wh_sec_*.o(.bss .bss.* COMMON) - *wc_sec_cryptocb.o(.bss .bss.* COMMON) - . = ALIGN(32); - _e_hsm = .; - _e_keystore = .; - } > HSMDATA - - /* SERVICE_VNET data band: first-claim the vnet dataplane, service, and - * relay objects' RAM out of shared SPM .data/.bss so the unprivileged - * partition's MPU grant covers exactly its own state. Empty (zero-length - * region) outside CONFIG_VNET builds. */ - .vnet_data : { - . = ALIGN(32); - _s_vnet = .; - *wt_sec_vnet_*.o(.data .data.*) - . = ALIGN(4); - _e_vnet_data = .; - } > VNETDATA AT > FLASH - _si_vnet = LOADADDR(.vnet_data); - - .vnet_bss (NOLOAD) : { - _s_vnet_bss = .; - *wt_sec_vnet_*.o(.bss .bss.* COMMON) - . = ALIGN(32); - _e_vnet = .; - } > VNETDATA - - .data : { - _sdata = .; - *(.data*) - _edata = .; - } > RAM AT > FLASH - - .bss (NOLOAD) : { - _sbss = .; - *(.bss*) - *(COMMON) - _ebss = .; - } > RAM - - .sp_stacks (NOLOAD) : { - KEEP(*(.sp_stacks*)) - } > SPSTACKS - - ASSERT(ORIGIN(VNETDATA) == (ORIGIN(RAM) + LENGTH(RAM)), - "vnet data band must sit directly above the secure RAM window") - ASSERT(ORIGIN(VAULTDATA) == (ORIGIN(VNETDATA) + LENGTH(VNETDATA)), - "vault data band must sit directly above the vnet data band") - ASSERT(_e_vnet <= (ORIGIN(VNETDATA) + LENGTH(VNETDATA)), - "vnet data band overflow") - ASSERT(ORIGIN(ATTESTDATA) == (ORIGIN(VAULTDATA) + LENGTH(VAULTDATA)), - "attestation data band must sit directly above the vault data band") - ASSERT(ORIGIN(HSMDATA) == (ORIGIN(ATTESTDATA) + LENGTH(ATTESTDATA)), - "crypto data band must sit directly above the attestation data band") - ASSERT(ORIGIN(FWUSTACK) == (ORIGIN(HSMDATA) + LENGTH(HSMDATA)), - "FWU stack must sit directly above the crypto data band") - /* Full band extents: Reset_Handler clears each band end to end before - * loading it, so no retained byte outlives a warm reset or a layout change. */ - _wt_band_vault_base = ORIGIN(VAULTDATA); - _wt_band_vault_limit = ORIGIN(VAULTDATA) + LENGTH(VAULTDATA); - _wt_band_attest_base = ORIGIN(ATTESTDATA); - _wt_band_attest_limit = ORIGIN(ATTESTDATA) + LENGTH(ATTESTDATA); - _wt_band_hsm_base = ORIGIN(HSMDATA); - _wt_band_hsm_limit = ORIGIN(HSMDATA) + LENGTH(HSMDATA); - _wt_band_vnet_base = ORIGIN(VNETDATA); - _wt_band_vnet_limit = ORIGIN(VNETDATA) + LENGTH(VNETDATA); - - ASSERT(_e_vault <= (ORIGIN(VAULTDATA) + LENGTH(VAULTDATA)), - "vault data band overflow") - ASSERT(_e_attest <= (ORIGIN(ATTESTDATA) + LENGTH(ATTESTDATA)), - "attestation data band overflow") - ASSERT(_e_hsm <= (ORIGIN(HSMDATA) + LENGTH(HSMDATA)), - "crypto data band overflow") - ASSERT(ORIGIN(PSSTACK) == (ORIGIN(FWUSTACK) + LENGTH(FWUSTACK)), - "PS stack must sit directly above the FWU stack") - ASSERT(ORIGIN(ITSSTACK) == (ORIGIN(PSSTACK) + LENGTH(PSSTACK)), - "ITS stack must sit directly above the PS stack") - ASSERT(ORIGIN(VAULTSTACK) == (ORIGIN(ITSSTACK) + LENGTH(ITSSTACK)), - "vault stack must sit directly above the ITS stack") - ASSERT(ORIGIN(CONFDATA) == (ORIGIN(VAULTSTACK) + LENGTH(VAULTSTACK)), - "conformance SP data must sit directly above the vault stack") - /* The top 1 KiB of CONFDATA holds the per-partition pseudo-MMIO holes - * (WT_CONF_SERVER/DRV_MMIO in memory_map.h); real data crossing into them - * would be carved out of other partitions' domains and fault at runtime. */ - ASSERT(_econfbss <= (ORIGIN(CONFDATA) + 0x2C00), - "conformance data/bss overflows into the reserved MMIO holes") - ASSERT(ORIGIN(SPSTACKS) == (ORIGIN(CONFDATA) + LENGTH(CONFDATA)), - "SP secure stacks must sit directly above the conformance data window") - ASSERT(_ebss <= _sstack, - "secure .bss reaches the SPM main stack (WT_SPM_STACK_SIZE)") -} diff --git a/tools/check-port-only-diff.sh b/tools/check-port-only-diff.sh index 96d54e1c..1a2ca609 100755 --- a/tools/check-port-only-diff.sh +++ b/tools/check-port-only-diff.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash # Port-only diff audit. A new architecture or SoC port must be a diff that # touches no architecture-neutral core file: only src/arch/common/, -# src/arch//, include/wolftrust/arch//, port//, the build -# fragments (never mk/common.mk), tests, docs, and workflows. +# src/arch//, include/wolftrust/arch//, port/common//, +# port//, the build fragments (never mk/common.mk), tests, docs, and workflows. # # tools/check-port-only-diff.sh [arch] [soc] # tools/check-port-only-diff.sh --selftest @@ -13,8 +13,8 @@ set -uo pipefail allow_re() { # arch soc local arch="$1" soc="$2" - printf '^(src/arch/(common|%s)/|include/wolftrust/arch/%s/|port/%s/|mk/(arch-%s|target-%s)\\.mk$|tests/|docs/|\\.github/)' \ - "$arch" "$arch" "$soc" "$arch" "$soc" + printf '^(src/arch/(common|%s)/|include/wolftrust/arch/%s/|port/(common/%s|%s)/|mk/(arch-%s|target-%s)\\.mk$|tests/|docs/|\\.github/)' \ + "$arch" "$arch" "$arch" "$soc" "$arch" "$soc" } audit() { # allow-regex, paths on stdin -> prints offenders, returns count @@ -34,6 +34,7 @@ selftest() { re="$(allow_re 'aarch64' 'qemuvirt')" if printf '%s\n' 'src/arch/aarch64/el3/start.S' 'src/arch/common/x.c' \ 'include/wolftrust/arch/aarch64/context.h' 'port/qemuvirt/board.h' \ + 'port/common/aarch64/platform_l3.c' \ 'mk/arch-aarch64.mk' 'mk/target-qemuvirt.mk' 'tests/host/x/main.c' \ 'docs/Porting.md' '.github/workflows/x.yml' | audit "$re" > /dev/null; then :; else echo "SELFTEST FAIL: allowed paths rejected"; fails=$((fails + 1)) @@ -46,6 +47,8 @@ selftest() { echo "SELFTEST FAIL: include/wolftrust/arch.h accepted"; fails=$((fails + 1)); fi if printf '%s\n' 'src/arch/armv8m/spm_svc.c' | audit "$re" > /dev/null; then echo "SELFTEST FAIL: another arch accepted"; fails=$((fails + 1)); fi + if printf '%s\n' 'port/common/armv8m/platform_l3.c' | audit "$re" > /dev/null; then + echo "SELFTEST FAIL: another arch's shared port layer accepted"; fails=$((fails + 1)); fi if "$0" refs/heads/__cpodiff_missing_ref__ > /dev/null 2>&1; then echo "SELFTEST FAIL: invalid base ref approved"; fails=$((fails + 1)); fi if [ "$fails" -ne 0 ]; then echo "SELFTEST: $fails failure(s)"; exit 1; fi diff --git a/tools/l3_layout_args.py b/tools/l3_layout_args.py new file mode 100755 index 00000000..a6d62fbb --- /dev/null +++ b/tools/l3_layout_args.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +"""Print check_secure_layout.py band arguments from a port's memory_map.h. + +The keystore bands and the conformance data window come from the shared +Armv8-M layout, so the post-link check reads the same macros the C code and +the linker fragments are placed by instead of a copied address list.""" + +import argparse +import re +import subprocess +import sys + + +MACROS = ( + "WT_SP_VAULT_DATA_BASE", "WT_SP_VAULT_DATA_SIZE", + "WT_SP_ATTEST_DATA_BASE", "WT_SP_ATTEST_DATA_SIZE", + "WT_SP_HSM_DATA_BASE", "WT_SP_HSM_DATA_SIZE", + "WT_CONF_SP_DATA_BASE", "WT_CONF_SERVER_MMIO_BASE", +) +INTEGER = re.compile(r"\b(0[xX][0-9a-fA-F]+|[0-9]+)[uUlL]*\b") +EXPRESSION = re.compile(r"^[0-9a-fA-FxX+\-*() ]+$") + + +def evaluate(cc, header): + probe = '#include "%s"\n' % header + probe += "".join("%d=%s\n" % (index, name) + for index, name in enumerate(MACROS)) + result = subprocess.run( + [cc, "-E", "-P", "-x", "c", "-"], input=probe, text=True, + capture_output=True, check=False) + if result.returncode != 0: + sys.stderr.write(result.stderr) + raise SystemExit("FAIL: preprocessing %s failed" % header) + values = {} + for line in result.stdout.splitlines(): + index, separator, text = line.partition("=") + if not separator or not index.isdigit() or int(index) >= len(MACROS): + continue + name = MACROS[int(index)] + if name == text.strip(): + continue + text = INTEGER.sub(r"\1", text).strip() + if not EXPRESSION.match(text): + raise SystemExit("FAIL: %s does not reduce to a constant: %s" % + (name, text)) + values[name] = eval(text, {"__builtins__": {}}) + missing = [name for name in MACROS if name not in values] + if missing: + raise SystemExit("FAIL: %s defines no %s" % + (header, ", ".join(missing))) + return values + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--cc", default="arm-none-eabi-gcc") + parser.add_argument("memory_map") + args = parser.parse_args() + + v = evaluate(args.cc, args.memory_map) + args_out = [] + for band, prefix in (("vault", "WT_SP_VAULT_DATA"), + ("attest", "WT_SP_ATTEST_DATA"), + ("hsm", "WT_SP_HSM_DATA")): + base = v[prefix + "_BASE"] + args_out.append("--band %s=0x%08X:0x%08X" % + (band, base, base + v[prefix + "_SIZE"])) + args_out.append("--confdata 0x%08X:0x%08X" % + (v["WT_CONF_SP_DATA_BASE"], v["WT_CONF_SERVER_MMIO_BASE"])) + print(" ".join(args_out)) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/secure_owners.txt b/tools/secure_owners.txt index cb82b99c..0d92066a 100644 --- a/tools/secure_owners.txt +++ b/tools/secure_owners.txt @@ -27,6 +27,7 @@ sec_periph.o spm sec_fabric_windows.o spm sec_platform_stm32h563.o spm sec_platform_mimxrt700.o spm +wt_sec_platform_l3.o spm sec_restart_policy.o spm sec_rollback.o spm sec_runtime.o spm From 4527bfc26b6e2a231e00d6f44e89e6ad1d9ff16b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 2 Oct 2026 13:48:30 -0700 Subject: [PATCH 2/8] Require the isolation level 3 scenarios on every port and document the shared layer --- docs/Porting.md | 55 ++++++++++++++++++++++++++--- tests/target/lib/scenario_matrix.py | 44 ++++++++++++++++++++++- 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/docs/Porting.md b/docs/Porting.md index 789fe0a4..a04d7b0c 100644 --- a/docs/Porting.md +++ b/docs/Porting.md @@ -24,6 +24,7 @@ must not claim security properties until they are tested on that target. | Public and internal contracts | `include/psa/` and `include/wolftrust/` | PSA APIs, SPM types, the two port contracts (`arch.h`, `platform.h`), manifests, and service interfaces | | Architecture-neutral gate | `src/arch/common/` | Secure Partition gate dispatch, fault recovery, scheduler, the SP-side PSA API, and the NS FF-M gateway bodies, written once over the `wolftrust/arch.h` primitives and linked by every architecture | | Architecture | `src/arch//` and `include/wolftrust/arch//` | Every `wt_arch_*` operation: reset entry, guest context save/restore, exception entry and return, the secure tick, interrupt masking and routing, memory-protection programming, the SP trap and its decoder, NS range checks, and the NS entry mechanism (Armv8-M: CMSE veneers) | +| Shared port layer | `port/common//` | The isolation level 3 secure RAM layout, its linker fragments and the level 3 `wt_platform_*` hooks, written once per architecture and linked by every port of it | | SoC and board | `port//` | Every `wt_platform_*` operation plus the SoC facts: clocks, fabric-level TrustZone filter windows, the memory-protection region tables, UART, flash, entropy, reset, the memory map, guest tables, and the manifest | | Build | `mk/common.mk`, `mk/arch-.mk`, `mk/target-.mk` | Shared rules; toolchain and architecture sources; SoC sources, placement, and image checks | | Guest integration | `tests/firmware/` or an application repository | Application-domain linker layout, PSA client shim, architecture-specific client boundary, and OS wiring; Armv8-M uses a CMSE import library | @@ -54,8 +55,10 @@ handlers, the virtual SysTick, NVIC routing, table-driven SAU and MPU programming, the SVC trap decoder, the CMSE checks, and the five NS veneers), `src/arch/common/` supplies the architecture-neutral gate, scheduler, SP-side PSA API and NS gateway bodies on top of them, and -`port/stm32h563/platform_stm32h563.c` implements the `wt_platform_*` -operations together with the SoC's SAU and MPU region tables. +`port/common/armv8m/` supplies the isolation level 3 layout and hooks every +Armv8-M port shares, and `port/stm32h563/platform_stm32h563.c` implements the +remaining `wt_platform_*` operations together with the SoC's SAU and MPU +region tables. A port declares what its hardware can do through the capability bits in `include/wolftrust/partition.h`; the core refuses a manifest that assumes a @@ -80,6 +83,46 @@ guard rejects that. Do not return unconditional success for a missing security mechanism. Report the capability accurately and reject a manifest that requires more. +### Isolation level 3 + +Every port of an architecture gets level 3 from `port/common//` +instead of writing it again. For Armv8-M that layer provides: + +- `l3_layout.h`: the per-partition keystore bands, partition stacks, the + conformance data window and the VNET band, all placed at fixed offsets + from `WT_RAM_S_BASE`; +- `secure_l3_memory.ld`, `secure_l3_symbols.ld`, `secure_l3_bands.ld` and + `secure_l3_tail.ld`: the matching linker regions, stack symbols, band + output sections and layout ASSERTs; and +- `platform_l3.c`: the boot-handoff region, the shared image windows, the + SPM-private RAM, the privileged-stack check, the peripheral table, the + conformance grants and the test-build probe addresses. + +A port supplies: + +- in `memory_map.h`, a literal `WT_RAM_S_BASE` and `WT_RAM_S_SIZE` (at + least the 480 KiB the layout uses), then `#include + "../common/armv8m/l3_layout.h"`; define `WT_RAMFUNC_BASE` and + `WT_RAMFUNC_SIZE` if the SPM runs code from RAM; +- `l3_port.h`, naming a Secure peripheral only the SPM drives as + `WT_L3_SPM_PERIPHERAL_BASE`; +- a `secure.ld` that INCLUDEs the four fragments and keeps only its board + sections (vectors, NSC veneers, any RAM code band, text, read-only data, + the conformance sections, .data and .bss); +- attribution read-back for every Secure peripheral the SPM uses, panicking + at boot on a mismatch; and +- owner lines in `tools/secure_owners.txt` for every object it adds. + +The build reads `WT_RAM_S_BASE` for the linker and derives the post-link band +check from `memory_map.h`, so no band address is written twice. + +Level 3 is claimed for a port only when its full M33MU tier runs every +scenario in `L3_REQUIRED` (`tests/target/lib/scenario_matrix.py`). A scenario +a port cannot run yet goes in that port's `l3_exempt` map with the open issue +that tracks it; `scenario_matrix.py --selftest`, run in CI, fails on a +missing scenario, an exemption without a reason, or an exemption for a +scenario the port already runs. + ### Guest and capability tables Implement the declarations in `include/wolftrust/partition.h`: @@ -180,7 +223,8 @@ worked examples above give a concrete map for each board. the architecture cannot reuse an existing implementation; implement every `wt_arch_*` operation there and leave `src/arch/common/` untouched. 2. Create `port//` with the platform, flash, entropy, board, - memory-map, protection-region-table, partition-table, and manifest files. + memory-map, protection-region-table, partition-table, and manifest files, + building on `port/common//` for isolation level 3. 3. Add `mk/arch-.mk` (if new) and `mk/target-.mk`; the root Makefile selects them from `ARCH` and `TARGET`, and `mk/common.mk` needs no change. @@ -205,7 +249,8 @@ worked examples above give a concrete map for each board. core code, and `wt_arch_*` definitions inside a port). - Run `tools/check-port-only-diff.sh ` on a port change and confirm it touches nothing outside `src/arch/common/`, - `src/arch//`, `include/wolftrust/arch//`, `port//`, the + `src/arch//`, `include/wolftrust/arch//`, + `port/common//`, `port//`, the two build fragments, tests, docs, and workflows. - Run `tools/check-docs-no-internal-links.sh`; `docs/` is published to the wiki and must not reference internal ledgers or developer paths. @@ -226,6 +271,8 @@ worked examples above give a concrete map for each board. guest disables its own Non-secure MPU and stores into another guest's RAM, and the store must not land. Programming the fabric rules is not evidence that they govern those addresses. +- Run `python3 tests/target/lib/scenario_matrix.py --selftest` and run every + `L3_REQUIRED` scenario the port does not exempt. - Test invalid manifests, memory overlap, pointer ranges, stale handles, cross-owner access, and unsupported capabilities. - Run authenticated boot, guest tamper, rollback, restart, Secure Partition diff --git a/tests/target/lib/scenario_matrix.py b/tests/target/lib/scenario_matrix.py index bb4e0616..1e4c95fe 100755 --- a/tests/target/lib/scenario_matrix.py +++ b/tests/target/lib/scenario_matrix.py @@ -42,6 +42,17 @@ # not exist there. HSM_ONLY = frozenset(("hsmattackneg", "hsmpinneg", "hsmfaultneg")) +# The isolation level 3 bar: every port's full tier runs each of these, or +# names it in its "l3_exempt" map with the reason (an open issue) it cannot. +L3_REQUIRED = frozenset(( + "crossdomain", "keystoreneg", "deputyneg", "hsmpinneg", "hsmfaultneg", + "bandneg1", "bandneg2", "bandneg3", "bandneg4", "bandneg5", "bandneg6", + "restartneg1", "restartneg2", "restartneg3", + "manifestneg2", "manifestneg3", "periphspneg", + "fpneg", "sealneg", "sealhaltneg", "sealbootneg", "sealpivotneg", + "mspovfneg", "xnneg", "svcneg", +)) + # smoke: the per-PR set (one job per scenario and engine). groups: the full # tier, packed so each job builds the emulator and wolfBoot once. PORTS = { @@ -100,6 +111,7 @@ ("xnneg", "Privileged execution from SPM RAM denied"), ("svcneg", "Partition guest-return SVC panics only that partition"), ), + "l3_exempt": {}, }, "mimxrt700": { "label": "ci:rt700", @@ -128,6 +140,12 @@ ("devcrypto vaultrecover vaultrecoversec", "RT700 dev_apis crypto conformance and vault recovery"), ), + "l3_exempt": {s: "MIMXRT700 level 3 parity, issue #40" for s in ( + "deputyneg", "hsmpinneg", "hsmfaultneg", + "bandneg1", "bandneg2", "bandneg3", "bandneg4", "bandneg5", + "bandneg6", "restartneg1", "restartneg2", "restartneg3", + "manifestneg2", "manifestneg3", "periphspneg", "sealneg", + "sealhaltneg", "sealpivotneg", "mspovfneg", "xnneg", "svcneg")}, }, } @@ -204,8 +222,29 @@ def ci_plan(event, labels, port_input): ) +def l3_gaps(port): + """Problems with a port's level 3 bar: a required scenario neither run + nor exempted, an exemption with no reason, or a stale exemption.""" + port_def = PORTS[port] + exempt = port_def.get("l3_exempt", {}) + run = set(flat(port, "full", None)) + gaps = [] + for scenario in sorted(L3_REQUIRED): + if scenario not in run and not exempt.get(scenario, "").strip(): + gaps.append("%s does not run level 3 scenario %s" % + (port, scenario)) + for scenario in sorted(exempt): + if scenario not in L3_REQUIRED: + gaps.append("%s exempts %s, which is not a level 3 scenario" % + (port, scenario)) + elif scenario in run: + gaps.append("%s runs %s but still exempts it" % (port, scenario)) + return gaps + + def selftest(): - """The routing table above and the engine rule, run by the select job.""" + """The routing table above, the engine rule and the level 3 bar, run by + the select job.""" for event, labels, port_input, expect in SELFTEST: got = " ".join(sorted(set(e["port"] + ":" + e["tier"] for e in ci_plan(event, labels.split(), port_input)))) @@ -220,6 +259,9 @@ def selftest(): return "native job for the hsm-only %s" % e["key"] if e["engine"] not in ENGINES or e["image"] == "": return "bad entry %r" % e + gaps = l3_gaps(port) + if gaps: + return "; ".join(gaps) return None From b4393a6ff787e0e9e45bbb48a7c12095ae6224e6 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 2 Oct 2026 14:38:37 -0700 Subject: [PATCH 3/8] Share the level 3 target checks and read their addresses from the port layout --- tests/target/lib/scenario.sh | 134 ++++++++++++++++++++++++ tests/target/run_m33mu_scenario.sh | 157 +++-------------------------- tools/l3_layout_args.py | 91 ++++++++++++----- 3 files changed, 217 insertions(+), 165 deletions(-) diff --git a/tests/target/lib/scenario.sh b/tests/target/lib/scenario.sh index 35a4d56a..c5c09a92 100644 --- a/tests/target/lib/scenario.sh +++ b/tests/target/lib/scenario.sh @@ -96,6 +96,14 @@ scenario_assert_verdict() { refute_re "no guest scheduled off the corrupted manifest" \ "$GUEST_STARTED_RE" ;; + sealhaltneg) + expect "damaged seal halted the platform at dispatch" \ + "[BKPT] imm=0x6e" + refute_re "partition with the damaged seal was never resumed" \ + '\[USGFLT\]' + refute_re "run did not reach a clean success exit" \ + '\[BKPT\] imm=0x7f' + ;; sealbootneg) expect "boot halted on the damaged main-stack seal" \ "[BKPT] imm=0x7e" @@ -134,3 +142,129 @@ scenario_assert_verdict() { ;; esac } + +# Isolation level 3 addresses of port/$2 (band, partition stack and SPM +# peripheral), read from its memory_map.h so no runner copies them. +l3_layout_load() { + local vars + vars="$(python3 "$1/tools/l3_layout_args.py" --shell \ + --cc "${L3_CPP:-arm-none-eabi-gcc}" -I "$1/include" \ + -I "$1/lib/wolfhal" "$1/port/$2/memory_map.h")" || + fail "could not read the level 3 layout of port/$2" + eval "$vars" +} + +# The band a bandneg probe touches and the stack its prober runs on. +l3_bandneg_target() { + case "$1" in + 1) L3_BAND=$L3_VAULT_BAND; L3_SP_LO=$L3_CRYPTO_STACK_LO + L3_SP_HI=$L3_CRYPTO_STACK_HI + L3_WHAT="crypto partition denied the vault's band" ;; + 2) L3_BAND=$L3_ATTEST_BAND; L3_SP_LO=$L3_CRYPTO_STACK_LO + L3_SP_HI=$L3_CRYPTO_STACK_HI + L3_WHAT="crypto partition denied the attestation band" ;; + 3) L3_BAND=$L3_VAULT_BAND; L3_SP_LO=$L3_ATTEST_STACK_LO + L3_SP_HI=$L3_ATTEST_STACK_HI + L3_WHAT="attestation partition denied the vault's band" ;; + 4) L3_BAND=$L3_HSM_BAND; L3_SP_LO=$L3_ATTEST_STACK_LO + L3_SP_HI=$L3_ATTEST_STACK_HI + L3_WHAT="attestation partition denied the crypto band" ;; + 5) L3_BAND=$L3_ATTEST_BAND; L3_SP_LO=$L3_VAULT_STACK_LO + L3_SP_HI=$L3_VAULT_STACK_HI + L3_WHAT="vault denied the attestation band" ;; + 6) L3_BAND=$L3_HSM_BAND; L3_SP_LO=$L3_VAULT_STACK_LO + L3_SP_HI=$L3_VAULT_STACK_HI + L3_WHAT="vault denied the crypto band" ;; + *) fail "l3_bandneg_target: no bandneg probe $1" ;; + esac +} + +# MemManage faults at exactly address $1 in $log; with $2/$3, only those whose +# stacked SP (the dump's next line) lies in [$2, $3). +l3_faults_at() { + awk -v want="$(printf '%s' "$1" | tr 'A-F' 'a-f')" -v lo="${2:-}" \ + -v hi="${3:-}" ' + function h(s, i, n) { + s = tolower(s); sub(/^0x/, "", s); n = 0 + for (i = 1; i <= length(s); i++) + n = n * 16 + index("0123456789abcdef", substr(s, i, 1)) - 1 + return n + } + pending && /^\[MEMFAULT\] sp=/ { + split($2, f, "="); sp = h(f[2]) + if (sp >= h(lo) && sp < h(hi)) n++ + pending = 0; next + } + { pending = 0 } + /^\[MEMFAULT\] pc=/ { + for (i = 2; i <= NF; i++) if ($i == "addr=" want) { + if (lo == "") n++; else pending = 1 + } + } + END { print n + 0 }' "$log" +} + +# Port-independent fault checks for the level 3 negatives. The runner sets +# $log and loads the layout first; lifecycle markers stay with the runner. +scenario_assert_l3() { + local n + case "$1" in + crossdomain) + n="$(l3_faults_at "$L3_SPM_RAM")" + check "$([ "$n" -ge 1 ]; echo $?)" \ + "cross-domain read of SPM RAM $L3_SPM_RAM denied (MEMFAULT)" ;; + keystoreneg) + n="$(l3_faults_at "$L3_VAULT_BAND")" + check "$([ "$n" -ge 1 ]; echo $?)" \ + "vault band read $L3_VAULT_BAND denied to a non-keystore SP (MEMFAULT)" ;; + periphspneg) + n="$(l3_faults_at "$L3_SPM_PERIPHERAL")" + check "$([ "$n" -ge 1 ]; echo $?)" \ + "SP read of the SPM's peripheral at $L3_SPM_PERIPHERAL denied (MEMFAULT)" ;; + bandneg[1-6]) + l3_bandneg_target "${1#bandneg}" + n="$(l3_faults_at "$L3_BAND")" + check "$(( n == 2 ? 0 : 1 ))" \ + "$L3_WHAT, read then write (2 MEMFAULTs at $L3_BAND, saw $n)" + n="$(l3_faults_at "$L3_BAND" "$L3_SP_LO" "$L3_SP_HI")" + check "$([ "$n" -eq 2 ]; echo $?)" \ + "both faults taken on the prober's own stack (saw $n)" + refute_re "no access ran past its fault, no keystore pin was open" \ + '^\[USGFLT\]' + refute_re "faults were contained, not escalated" \ + '^(\[HARDFLT\]|HardFault|SecureFault)' ;; + restartneg[1-3]) + # The partition faults once (udf #0) after planting band state; + # a restart on an unreset band traps again on udf #2. + n="$(count_re '^\[USGFLT\] CFSR=0x00010000')" + check "$([ "$n" -eq 1 ]; echo $?)" \ + "partition faulted once and restarted on a reset band (saw $n)" + refute_re "the fault was the planted one" \ + '^\[USGFLT\] mem16\[[^]]*\]=0xde02' + refute_re "fault was contained, not escalated" \ + '^(\[MEMFAULT\]|\[HARDFLT\]|HardFault|SecureFault)' ;; + sealneg|sealpivotneg|svcneg) + # The offending partition is resumed on the panic trap (udf #0x50). + n="$(count_re '^\[USGFLT\] mem16\[0x[0-9a-f]+\]=0xde50')" + check "$([ "$n" -ge 1 ] && [ "$(count_re '\[USGFLT\].*CFSR=0x00010000')" -ge 1 ]; echo $?)" \ + "the partition was panicked on the panic trap (Secure-Thread UNDEFINSTR)" + refute_re "partition fault was contained, not escalated" \ + '(\[HARDFLT\]|HardFault|SecureFault)' + refute_re "platform did not halt on the partition's fault" \ + '\[BKPT\] imm=0x(6e|7e|7d)' ;; + hsmfaultneg) + expect "the guest's HSM tasklet faulted" "[USGFLT]" + refute_re "HSM fault stayed contained" \ + '(\[HARDFLT\]|HardFault|SecureFault|\[BKPT\] imm=0x7e)' ;; + *) + fail "scenario_assert_l3: no level 3 checks for '$1'" ;; + esac +} + +# A probe the scenario relies on must be linked in, or its pass is vacuous. +l3_assert_probe_linked() { + local syms="$1.syms" + "${CROSS_COMPILE:-arm-none-eabi-}nm" "$1" > "$syms" || + fail "nm on $1 failed" + check "$(grep -q " $2\$" "$syms"; echo $?)" "$2 linked into the secure image" +} diff --git a/tests/target/run_m33mu_scenario.sh b/tests/target/run_m33mu_scenario.sh index 9f199bd3..b7db0f14 100755 --- a/tests/target/run_m33mu_scenario.sh +++ b/tests/target/run_m33mu_scenario.sh @@ -66,7 +66,7 @@ esac # Variant scenarios carry their probe number in the name (bandneg3 = probe 3). family="$scenario" case "$scenario" in - bandneg[1-6]) family=bandneg; band_probe="${scenario#bandneg}" ;; + bandneg[1-6]) family=bandneg ;; restartneg[1-3]) family=restartneg ;; esac @@ -156,6 +156,7 @@ cd "$repo" # is what gets signed and flashed. --- # shellcheck source=lib/scenario.sh disable=SC1091 . "$repo/tests/target/lib/scenario.sh" +l3_layout_load "$repo" stm32h563 secure_flags="$(scenario_secure_flags "$scenario")" # shellcheck disable=SC2086 env $secure_flags make build/wolftrust.bin build/secure_cmse_implib.o @@ -414,89 +415,17 @@ case "$scenario" in expect "NS RNG poke and NS DMA copies blocked" \ "wolfTrust periph probe blocked" fi - if [ "$family" = "restartneg" ]; then - # The partition plants state in its own band and faults once (udf #0). - # Its restarted instance traps again (udf #2) if the band did not - # return to its link-time image, so exactly one fault must be seen. - restart_faults=$(grep -cE '^\[USGFLT\] CFSR=0x00010000' "$log" || true) - if [ "$restart_faults" -eq 1 ]; then - check_pass "partition faulted once and restarted on a reset band" - else - check_fail "band reset on restart" \ - "expected 1 UNDEFINSTR UsageFault, saw $restart_faults" - fi - refute_re "the fault was the planted one" \ - '^\[USGFLT\] mem16\[[^]]*\]=0xde02' - refute_re "fault was contained, not escalated" \ - '^(\[MEMFAULT\]|\[HARDFLT\]|HardFault|SecureFault)' - fi - if [ "$family" = "bandneg" ]; then - # The prober reads the other partition's band, is restarted, writes it, - # and is restarted again: both accesses must MemManage-fault on the - # prober's own stack, and neither may run on to the trap behind it. - case "$band_probe" in - 1) band_addr=0x30075000; band_sp='0x3009[67]' - band_what="crypto partition denied the vault's band" ;; - 2) band_addr=0x30077000; band_sp='0x3009[67]' - band_what="crypto partition denied the attestation band" ;; - 3) band_addr=0x30075000; band_sp='0x3009[89]' - band_what="attestation partition denied the vault's band" ;; - 4) band_addr=0x30077800; band_sp='0x3009[89]' - band_what="attestation partition denied the crypto band" ;; - 5) band_addr=0x30077000; band_sp='0x300(8f|9[0-2])' - band_what="vault denied the attestation band" ;; - 6) band_addr=0x30077800; band_sp='0x300(8f|9[0-2])' - band_what="vault denied the crypto band" ;; - esac - band_faults=$(grep -cE "^\[MEMFAULT\].*addr=$band_addr" "$log" || true) - if [ "$band_faults" -eq 2 ]; then - check_pass "$band_what, read then write (2 MEMFAULTs at $band_addr)" - else - check_fail "$band_what" \ - "expected 2 MEMFAULTs at $band_addr, saw $band_faults" - fi - band_stacks=$(grep -cE "^\[MEMFAULT\] sp=$band_sp" "$log" || true) - if [ "$band_stacks" -eq 2 ]; then - check_pass "both faults taken on the prober's own stack" - else - check_fail "fault attribution" \ - "expected 2 faults on a stack matching $band_sp, saw $band_stacks" - fi - refute_re "no access ran past its fault, no keystore pin was open" \ - '^\[USGFLT\]' - refute_re "faults were contained, not escalated" \ - '^(\[HARDFLT\]|HardFault|SecureFault)' + if [ "$family" = "restartneg" ] || [ "$family" = "bandneg" ]; then + scenario_assert_l3 "$scenario" fi + # A clean lifecycle proves these fixes only if their probe was linked in. if [ "$scenario" = "deputyneg" ]; then - # The probe is called unconditionally at vault entry and faults the - # partition on any leak, so a clean positive lifecycle only proves the - # fix if the probe was actually linked in. Guard against a vacuous pass - # from the probe being compiled out (WT_DEPUTY_NEG_PROBE not threaded). - # Dump symbols to a file and grep the file: piping nm into `grep -q` - # would trip pipefail when grep closes the pipe early and nm gets SIGPIPE. - deputy_syms="$repo/build/deputy-syms.txt" - "${CROSS_COMPILE}nm" "$repo/build/wolftrust-signed.elf" > "$deputy_syms" - if grep -q ' wt_platform_deputy_flash_probe$' "$deputy_syms"; then - check_pass "deputy probe linked into the secure image" - else - check_fail "deputy probe presence" \ - "wt_platform_deputy_flash_probe not in the secure image" - fi + l3_assert_probe_linked "$repo/build/wolftrust-signed.elf" \ + wt_platform_deputy_flash_probe fi if [ "$scenario" = "hsmpinneg" ]; then - # Every relay pump forges the server pointers to an SPM-private address - # right before the pin, so the guest crypto/attestation markers below can - # only appear if the unprivileged relay re-pinned them before dereference; - # a neutered pin faults instead. Guard against a vacuous pass with the - # probe compiled out; grep a symbol file, not a pipe, to stay pipefail-safe. - tasklet_syms="$repo/build/hsmpinneg-syms.txt" - "${CROSS_COMPILE}nm" "$repo/build/wolftrust-signed.elf" > "$tasklet_syms" - if grep -q ' wt_platform_hsm_pin_probe$' "$tasklet_syms"; then - check_pass "HSM server pointer pin probe linked into the secure image" - else - check_fail "HSM server pointer pin probe presence" \ - "wt_platform_hsm_pin_probe not in the secure image" - fi + l3_assert_probe_linked "$repo/build/wolftrust-signed.elf" \ + wt_platform_hsm_pin_probe fi if [ "$family" != "bandneg" ] && [ "$family" != "restartneg" ]; then refute_re "no fault markers in boot log" \ @@ -810,35 +739,9 @@ case "$scenario" in fi check_fail "guest restart count" "saw $banners banners, expected $expected" ;; - crossdomain) - if grep -Eq '\[MEMFAULT\].*addr=0x30028000' "$log"; then - check_pass "cross-domain read of 0x30028000 denied by SP domain (MEMFAULT)" - echo "PASS: target/crossdomain" - exit 0 - fi - check_fail "cross-domain isolation" "expected MEMFAULT at 0x30028000, none seen" - ;; - periphspneg) - # WT-FFM-0068: the storage SP reads the SPM's RNG registers at their Secure - # address; no partition domain maps an unassigned peripheral, so the read - # must MemManage-fault at exactly that address. - if grep -Eqi '\[MEMFAULT\].*addr=0x520c0800' "$log"; then - check_pass "SP read of the SPM's RNG at 0x520C0800 denied (MEMFAULT)" - echo "PASS: target/periphspneg" - exit 0 - fi - check_fail "SP peripheral isolation" "expected MEMFAULT at 0x520C0800, none seen" - ;; - keystoreneg) - # A non-keystore partition (ITS) reads the vault's data band; its manifest - # domain grants none of the keystore data bands, so the read must - # MemManage-fault inside the ITS domain (WT-FFM-0062). - if grep -Eq '\[MEMFAULT\].*addr=0x30075000' "$log"; then - check_pass "keystore data band read of 0x30075000 denied to a non-keystore SP (MEMFAULT)" - echo "PASS: target/keystoreneg" - exit 0 - fi - check_fail "keystore-band isolation" "expected MEMFAULT at 0x30075000, none seen" + crossdomain|periphspneg|keystoreneg) + scenario_assert_l3 "$scenario" + echo "PASS: target/$scenario" ;; spfaultneg) # The SERVICE_HSM relay SP faults once on its first entry (udf #0, an @@ -872,8 +775,7 @@ case "$scenario" in hsmfaultneg) # A guest0 HSM tasklet fault must not turn its post-fault stack-canary # check into a platform panic. Guest1 must keep running. - expect "guest0 HSM tasklet faulted" "[USGFLT]" - refute_re "HSM fault stayed contained" '(\[HARDFLT\]|HardFault|SecureFault|\[BKPT\] imm=0x7e)' + scenario_assert_l3 "$scenario" expect "other guest remains functional" "freertos_guest1: ffm sha256 ok" expect "full chain exits cleanly" "[EXPECT BKPT] Success" echo "PASS: target/hsmfaultneg" @@ -1081,16 +983,7 @@ case "$scenario" in # Software check only: the partition that overwrote its own seal is # resumed on the panic trap, so it alone takes a contained UsageFault and # restarts; the platform must not halt (no BKPT 0x6e or 0x7e). - if grep -Eq '\[USGFLT\].*CFSR=0x00010000' "$log" && - grep -Eq '\[USGFLT\] mem16\[0x[0-9a-f]+\]=0xde50' "$log"; then - check_pass "seal violation faulted the offending partition on the panic trap" - else - check_fail "seal violation" "expected the panic-trap UsageFault, none seen" - fi - refute_re "partition fault was contained, not escalated" \ - '(\[HARDFLT\]|HardFault|SecureFault)' - refute_re "platform did not halt on the partition's seal" \ - '\[BKPT\] imm=0x(6e|7e|7d)' + scenario_assert_l3 "$scenario" expect "unrelated guest kept running" "freertos_guest1: alive" expect "run reached the clean scenario end" "[EXPECT BKPT] Success" echo "PASS: target/sealneg" @@ -1099,16 +992,7 @@ case "$scenario" in sealpivotneg) # The partition's blocking wait is stacked on its stack top, over the seal # words; the SPM must still contain the fault to that partition. - if grep -Eq '\[USGFLT\].*CFSR=0x00010000' "$log" && - grep -Eq '\[USGFLT\] mem16\[0x[0-9a-f]+\]=0xde50' "$log"; then - check_pass "seal violation faulted the offending partition on the panic trap" - else - check_fail "seal violation" "expected the panic-trap UsageFault, none seen" - fi - refute_re "partition fault was contained, not escalated" \ - '(\[HARDFLT\]|HardFault|SecureFault)' - refute_re "platform did not halt on the partition's seal" \ - '\[BKPT\] imm=0x(6e|7e|7d)' + scenario_assert_l3 "$scenario" expect "unrelated guest kept running" "freertos_guest1: alive" expect "run reached the clean scenario end" "[EXPECT BKPT] Success" echo "PASS: target/sealpivotneg" @@ -1146,16 +1030,7 @@ case "$scenario" in # UNDEFINSTR UsageFault), the pinned client is unblocked with # COMMUNICATION_FAILURE, and the platform keeps running. A missed reject # would halt the platform (BKPT 0x7E) or run the probe's udf #3. - if grep -Eq '\[USGFLT\].*CFSR=0x00010000' "$log" && - grep -Eq '\[USGFLT\] mem16\[0x[0-9a-f]+\]=0xde50' "$log"; then - check_pass "ITS SP panicked once on the panic trap (Secure-Thread UNDEFINSTR)" - else - check_fail "SP panic" "expected the panic-trap UsageFault, none seen" - fi - refute_re "panic was contained, not escalated" \ - '(\[HARDFLT\]|HardFault|SecureFault)' - refute_re "platform did not halt on the partition's SVC" \ - '\[BKPT\] imm=0x(6e|7e|7d)' + scenario_assert_l3 "$scenario" expect "pinned client unblocked with COMMUNICATION_FAILURE" \ "psa_connect(SERVICE_ITS) failed rc=0 handle=-145" expect "sealed storage path unaffected" \ diff --git a/tools/l3_layout_args.py b/tools/l3_layout_args.py index a6d62fbb..a0781852 100755 --- a/tools/l3_layout_args.py +++ b/tools/l3_layout_args.py @@ -1,42 +1,55 @@ #!/usr/bin/env python3 -"""Print check_secure_layout.py band arguments from a port's memory_map.h. +"""Read the isolation level 3 layout from a port's memory_map.h. -The keystore bands and the conformance data window come from the shared -Armv8-M layout, so the post-link check reads the same macros the C code and -the linker fragments are placed by instead of a copied address list.""" +The keystore bands, partition stacks and conformance data window come from +the shared Armv8-M layout. The default output is the post-link band check's +arguments; --shell prints the addresses the target scenario checks match +fault logs against. Both read the macros the C code and the linker fragments +are placed by instead of a copied address list.""" import argparse +import os import re import subprocess import sys -MACROS = ( +BAND_MACROS = ( "WT_SP_VAULT_DATA_BASE", "WT_SP_VAULT_DATA_SIZE", "WT_SP_ATTEST_DATA_BASE", "WT_SP_ATTEST_DATA_SIZE", "WT_SP_HSM_DATA_BASE", "WT_SP_HSM_DATA_SIZE", "WT_CONF_SP_DATA_BASE", "WT_CONF_SERVER_MMIO_BASE", ) +SHELL_MACROS = BAND_MACROS + ( + "WT_RAM_S_BASE", "WT_SP_SECURE_STACK_SIZE", + "WT_SP_CRYPTO_STACK_BASE", "WT_SP_ATTEST_STACK_BASE", + "WT_SP_VAULT_STACK_BASE", "WT_SP_VAULT_STACK_SIZE", + "WT_L3_SPM_PERIPHERAL_BASE", +) INTEGER = re.compile(r"\b(0[xX][0-9a-fA-F]+|[0-9]+)[uUlL]*\b") EXPRESSION = re.compile(r"^[0-9a-fA-FxX+\-*() ]+$") -def evaluate(cc, header): +def evaluate(cc, header, macros, includes): probe = '#include "%s"\n' % header + port_header = os.path.join(os.path.dirname(header), "l3_port.h") + if "WT_L3_SPM_PERIPHERAL_BASE" in macros: + probe += '#include "%s"\n' % port_header probe += "".join("%d=%s\n" % (index, name) - for index, name in enumerate(MACROS)) - result = subprocess.run( - [cc, "-E", "-P", "-x", "c", "-"], input=probe, text=True, - capture_output=True, check=False) + for index, name in enumerate(macros)) + command = [cc, "-E", "-P", "-x", "c"] + command += ["-I%s" % path for path in includes] + result = subprocess.run(command + ["-"], input=probe, text=True, + capture_output=True, check=False) if result.returncode != 0: sys.stderr.write(result.stderr) raise SystemExit("FAIL: preprocessing %s failed" % header) values = {} for line in result.stdout.splitlines(): index, separator, text = line.partition("=") - if not separator or not index.isdigit() or int(index) >= len(MACROS): + if not separator or not index.isdigit() or int(index) >= len(macros): continue - name = MACROS[int(index)] + name = macros[int(index)] if name == text.strip(): continue text = INTEGER.sub(r"\1", text).strip() @@ -44,30 +57,60 @@ def evaluate(cc, header): raise SystemExit("FAIL: %s does not reduce to a constant: %s" % (name, text)) values[name] = eval(text, {"__builtins__": {}}) - missing = [name for name in MACROS if name not in values] + missing = [name for name in macros if name not in values] if missing: raise SystemExit("FAIL: %s defines no %s" % (header, ", ".join(missing))) return values +def band_args(v): + out = [] + for band, prefix in (("vault", "WT_SP_VAULT_DATA"), + ("attest", "WT_SP_ATTEST_DATA"), + ("hsm", "WT_SP_HSM_DATA")): + base = v[prefix + "_BASE"] + out.append("--band %s=0x%08X:0x%08X" % + (band, base, base + v[prefix + "_SIZE"])) + out.append("--confdata 0x%08X:0x%08X" % + (v["WT_CONF_SP_DATA_BASE"], v["WT_CONF_SERVER_MMIO_BASE"])) + return " ".join(out) + + +def shell_vars(v): + stack = v["WT_SP_SECURE_STACK_SIZE"] + pairs = ( + ("L3_SPM_RAM", v["WT_RAM_S_BASE"]), + ("L3_VAULT_BAND", v["WT_SP_VAULT_DATA_BASE"]), + ("L3_ATTEST_BAND", v["WT_SP_ATTEST_DATA_BASE"]), + ("L3_HSM_BAND", v["WT_SP_HSM_DATA_BASE"]), + ("L3_CRYPTO_STACK_LO", v["WT_SP_CRYPTO_STACK_BASE"]), + ("L3_CRYPTO_STACK_HI", v["WT_SP_CRYPTO_STACK_BASE"] + stack), + ("L3_ATTEST_STACK_LO", v["WT_SP_ATTEST_STACK_BASE"]), + ("L3_ATTEST_STACK_HI", v["WT_SP_ATTEST_STACK_BASE"] + stack), + ("L3_VAULT_STACK_LO", v["WT_SP_VAULT_STACK_BASE"]), + ("L3_VAULT_STACK_HI", + v["WT_SP_VAULT_STACK_BASE"] + v["WT_SP_VAULT_STACK_SIZE"]), + ("L3_SPM_PERIPHERAL", v["WT_L3_SPM_PERIPHERAL_BASE"]), + ) + return "\n".join("%s=0x%08x" % pair for pair in pairs) + + def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--cc", default="arm-none-eabi-gcc") + parser.add_argument("-I", dest="includes", action="append", default=[]) + parser.add_argument("--shell", action="store_true", + help="print the scenario checks' layout variables") parser.add_argument("memory_map") args = parser.parse_args() - v = evaluate(args.cc, args.memory_map) - args_out = [] - for band, prefix in (("vault", "WT_SP_VAULT_DATA"), - ("attest", "WT_SP_ATTEST_DATA"), - ("hsm", "WT_SP_HSM_DATA")): - base = v[prefix + "_BASE"] - args_out.append("--band %s=0x%08X:0x%08X" % - (band, base, base + v[prefix + "_SIZE"])) - args_out.append("--confdata 0x%08X:0x%08X" % - (v["WT_CONF_SP_DATA_BASE"], v["WT_CONF_SERVER_MMIO_BASE"])) - print(" ".join(args_out)) + if args.shell: + print(shell_vars(evaluate(args.cc, args.memory_map, SHELL_MACROS, + args.includes))) + else: + print(band_args(evaluate(args.cc, args.memory_map, BAND_MACROS, + args.includes))) return 0 From 9977bbeaf6ce1aedf68d4a64684512fd50e88c89 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 2 Oct 2026 14:38:37 -0700 Subject: [PATCH 4/8] Run every isolation level 3 negative on the MIMXRT700 emulator --- port/mimxrt700/hsm_flash.c | 78 ++++++++++++++ tests/firmware/mimxrt700-baremetal/Makefile | 6 ++ tests/firmware/mimxrt700-baremetal/guest0.c | 20 ++++ tests/firmware/psa-guest/Makefile | 19 ++-- tests/target/lib/scenario_matrix.py | 21 ++-- tests/target/run_rt700_m33mu.sh | 106 ++++++++++++++++++-- 6 files changed, 231 insertions(+), 19 deletions(-) diff --git a/port/mimxrt700/hsm_flash.c b/port/mimxrt700/hsm_flash.c index f8d8f934..3de78849 100644 --- a/port/mimxrt700/hsm_flash.c +++ b/port/mimxrt700/hsm_flash.c @@ -580,3 +580,81 @@ int wt_conf_nvm_flash_sync(uint8_t *buf, uint32_t len, int store) return (ret == WH_ERROR_OK) ? 0 : -1; } #endif + +#if defined(WT_DEPUTY_NEG_PROBE) && (WT_DEPUTY_NEG_PROBE == 1) +/* A primitive fed a forged offset must reject it with exactly + * WH_ERROR_BADARGS through a delivered gate call. */ +static int wt_deputy_expect_badargs(int32_t sub_op, uint32_t offset, + uint32_t size, void *buf) +{ + wt_spm_call_t call; + + (void)memset(&call, 0, sizeof(call)); + call.op = WT_SPM_OP_KEYSTORE_FLASH; + call.call_type = sub_op; + call.vec_idx = offset; + call.num_bytes = size; + call.buffer = buf; + if (wt_arch_sp_trap(&call) != WT_FFM_SUCCESS) { + return 0; + } + return (call.ret_int == WH_ERROR_BADARGS) ? 1 : 0; +} + +static int wt_deputy_read_ok(int32_t sub_op, uint8_t *buf, uint32_t size) +{ + wt_spm_call_t call; + + (void)memset(&call, 0, sizeof(call)); + call.op = WT_SPM_OP_KEYSTORE_FLASH; + call.call_type = sub_op; + call.vec_idx = 0u; + call.num_bytes = size; + call.buffer = buf; + return (wt_arch_sp_trap(&call) == WT_FFM_SUCCESS && + call.ret_int == WH_ERROR_OK) ? 1 : 0; +} + +/* The vault's writable context holds no geometry here, so a deputy can only + * forge offsets and lengths: each must be refused against the const config, + * and the store must still read back unchanged. */ +__attribute__((used, noinline)) +int wt_platform_deputy_flash_probe(void) +{ + uint8_t ref[16]; + uint8_t leak[16]; + uint32_t oor; + int ok = 1; + + /* Wraps modulo 2^32 so base + offset addresses SPM-private RAM. */ + oor = (uint32_t)((uintptr_t)WT_RAM_S_BASE - g_hsm_flash_cfg.base); + + (void)memset(ref, 0, sizeof(ref)); + if (wt_deputy_read_ok(WT_SPM_KS_FLASH_READ, ref, sizeof(ref)) == 0) { + return 0; + } + ok &= wt_deputy_expect_badargs(WT_SPM_KS_FLASH_READ, oor, 16u, leak); + ok &= wt_deputy_expect_badargs(WT_SPM_KS_FLASH_PROGRAM, oor, 16u, leak); + ok &= wt_deputy_expect_badargs(WT_SPM_KS_FLASH_ERASE, oor, + g_hsm_flash_cfg.sector_size, NULL); + ok &= wt_deputy_expect_badargs(WT_SPM_KS_FLASH_VERIFY, oor, 16u, leak); + ok &= wt_deputy_expect_badargs(WT_SPM_KS_FLASH_BLANKCHECK, oor, 16u, + NULL); + /* A length running past the store from an in-range offset. */ + ok &= wt_deputy_expect_badargs(WT_SPM_KS_FLASH_READ, + g_hsm_flash_cfg.size - 8u, 16u, leak); + /* Misaligned program and sub-sector erase, checked on the const units. */ + ok &= wt_deputy_expect_badargs(WT_SPM_KS_FLASH_PROGRAM, 4u, 16u, leak); + ok &= wt_deputy_expect_badargs(WT_SPM_KS_FLASH_ERASE, 0u, 16u, NULL); + + (void)memset(leak, 0, sizeof(leak)); + if (wt_deputy_read_ok(WT_SPM_KS_FLASH_READ, leak, sizeof(leak)) == 0 || + memcmp(leak, ref, sizeof(ref)) != 0) { + ok = 0; + } + if (wt_deputy_read_ok(WT_SPM_KS_FLASH_VERIFY, ref, sizeof(ref)) == 0) { + ok = 0; + } + return ok; +} +#endif diff --git a/tests/firmware/mimxrt700-baremetal/Makefile b/tests/firmware/mimxrt700-baremetal/Makefile index 9eaeaeea..e9df735f 100644 --- a/tests/firmware/mimxrt700-baremetal/Makefile +++ b/tests/firmware/mimxrt700-baremetal/Makefile @@ -41,6 +41,12 @@ ifeq ($(WT_AHBSC_PROBE),1) GUEST0_CFLAGS += -DWT_AHBSC_PROBE -DGUEST_PROBE_ADDR=0x20170000u \ -DGUEST_PROBE_VALUE=0xDEADBEEFu endif +# WT_PERIPH_NEG_PROBE=1 makes guest0, its MPU off, read the SPM's TRNG through +# the Non-secure alias; the SAU must refuse it on every launch (periphneg). +WT_PERIPH_NEG_PROBE ?= 0 +ifeq ($(WT_PERIPH_NEG_PROBE),1) +GUEST0_CFLAGS += -DWT_PERIPH_NEG_PROBE -DGUEST_PERIPH_ADDR=0x40187000u +endif # WT_GUEST_FAULT_PROBE=1 makes guest0 read Secure RAM on every launch, so the # monitor spends its restart budget and quarantines it (restart). WT_GUEST_FAULT_PROBE ?= 0 diff --git a/tests/firmware/mimxrt700-baremetal/guest0.c b/tests/firmware/mimxrt700-baremetal/guest0.c index 45b8607d..d3f3891b 100644 --- a/tests/firmware/mimxrt700-baremetal/guest0.c +++ b/tests/firmware/mimxrt700-baremetal/guest0.c @@ -71,6 +71,23 @@ typedef struct wt_guest0_mailbox { __attribute__((section(".shared"), used)) volatile wt_guest0_mailbox_t g_guest0_mailbox; +#if defined(WT_PERIPH_NEG_PROBE) +static void guest0_uart_puts(const char* s); + +/* A privileged guest switches off its own MPU and reads the SPM's TRNG through + * the Non-secure alias. The SAU must fault the read; returning at all leaks. */ +static void guest0_periph_probe(volatile wt_guest0_mailbox_t* mb) +{ + *(volatile uint32_t*)0xE000ED94u = 0u; + __asm volatile("dsb\n isb" ::: "memory"); + mb->probe = 1u; + __asm volatile("dsb" ::: "memory"); + mb->probe_read = *(volatile const uint32_t*)GUEST_PERIPH_ADDR; + mb->probe = 3u; + guest0_uart_puts("wolfTrust RT700 " GUEST_NAME ": periph probe LEAKED\r\n"); +} +#endif + #if defined(WT_AHBSC_PROBE) /* Isolation probe: GUEST_PROBE_ADDR is the peer guest's RAM, Secure to the SAU * while this guest runs. A privileged guest can switch off the Non-secure MPU @@ -251,6 +268,9 @@ void Reset_Handler(void) #if defined(WT_AHBSC_PROBE) guest0_fabric_probe(mb); #endif +#if defined(WT_PERIPH_NEG_PROBE) + guest0_periph_probe(mb); +#endif for (;;) { mb->beat++; diff --git a/tests/firmware/psa-guest/Makefile b/tests/firmware/psa-guest/Makefile index 4364985a..abe07cb7 100644 --- a/tests/firmware/psa-guest/Makefile +++ b/tests/firmware/psa-guest/Makefile @@ -55,8 +55,10 @@ WT_EXPECTED_LIFECYCLE ?= 0x3000u WT_RUN_CONFORMANCE ?= 0 WT_CONF_SUITE ?= ipc WT_ATTEST_CBOR ?= wolfcose -# WT_M33MU_EXPECT_BKPT=1 ends the run on bkpt #0x7f once the lifecycle is done. +# WT_M33MU_EXPECT_BKPT=1 ends the run on bkpt #0x7f once guest0's lifecycle +# is done (guest1's with WT_M33MU_BKPT_GUEST=1); the other keeps running. WT_M33MU_EXPECT_BKPT ?= 0 +WT_M33MU_BKPT_GUEST ?= 0 WOLFCRYPT_SRCS := \ $(WOLFSSL_DIR)/wolfcrypt/src/aes.c \ @@ -253,15 +255,19 @@ CFLAGS := -mcpu=cortex-m33 -mthumb -mgeneral-regs-only \ $(ENGINE_DEFS) \ -include user_settings.h -ifeq ($(WT_M33MU_EXPECT_BKPT),1) -CFLAGS += -DWT_M33MU_EXPECT_BKPT -endif - ifneq ($(WT_EXPECTED_MEASUREMENT_HEX),) CFLAGS += -DWT_EXPECTED_MEASUREMENT_HEX=\"$(WT_EXPECTED_MEASUREMENT_HEX)\" endif PROBE_CFLAGS := +GUEST1_BKPT_CFLAGS := +ifeq ($(WT_M33MU_EXPECT_BKPT),1) +ifeq ($(WT_M33MU_BKPT_GUEST),1) +GUEST1_BKPT_CFLAGS += -DWT_M33MU_EXPECT_BKPT +else +PROBE_CFLAGS += -DWT_M33MU_EXPECT_BKPT +endif +endif ifeq ($(WT_GUEST_FAULT_PROBE),1) PROBE_CFLAGS += -DWT_GUEST_FAULT_PROBE endif @@ -285,7 +291,8 @@ endif GUEST0_CFLAGS := -DGUEST_NAME=\"guest0\" -DWT_WOLFHSM_CLIENT_ID=1u \ -DGUEST_PEER_RAM=$(GUEST1_RAM_ORIGIN)u $(PROBE_CFLAGS) $(CONF_DEFS) GUEST1_CFLAGS := -DGUEST_NAME=\"guest1\" -DWT_WOLFHSM_CLIENT_ID=2u \ - -DGUEST_PEER_RAM=$(GUEST0_RAM_ORIGIN)u -DWT_ATTEST_EXPECTED_CLIENT_ID=-2 + -DGUEST_PEER_RAM=$(GUEST0_RAM_ORIGIN)u -DWT_ATTEST_EXPECTED_CLIENT_ID=-2 \ + $(GUEST1_BKPT_CFLAGS) SHARED_OBJS := $(patsubst %.c,$(BUILD)/shared_%.o,$(notdir $(SHARED_SRCS))) GUEST0_OBJS := $(patsubst %.c,$(BUILD)/g0_%.o,$(notdir $(GUEST0_SRCS))) diff --git a/tests/target/lib/scenario_matrix.py b/tests/target/lib/scenario_matrix.py index 1e4c95fe..61dfefde 100755 --- a/tests/target/lib/scenario_matrix.py +++ b/tests/target/lib/scenario_matrix.py @@ -139,13 +139,22 @@ "RT700 dev_apis storage and attestation conformance"), ("devcrypto vaultrecover vaultrecoversec", "RT700 dev_apis crypto conformance and vault recovery"), + ("periphneg", "RT700 NS read of the SPM's TRNG refused by the SAU"), + ("deputyneg", "RT700 keystore-flash privileged deputy refused (L3)"), + ("hsmpinneg hsmfaultneg", + "RT700 wolfHSM pointer pin and tasklet fault containment (L3)"), + ("bandneg1 bandneg2 bandneg3 bandneg4 bandneg5 bandneg6", + "RT700 partition band-to-band isolation negatives (L3)"), + ("restartneg1 restartneg2 restartneg3", + "RT700 partitions restart on a reset band (L3)"), + ("periphspneg manifestneg2 manifestneg3", + "RT700 SPM peripheral and manifest level negatives (L3)"), + ("sealneg sealhaltneg sealpivotneg", + "RT700 stack seal containment and halt (L3)"), + ("mspovfneg xnneg svcneg", + "RT700 SPM stack, execute-never and SVC misuse (L3)"), ), - "l3_exempt": {s: "MIMXRT700 level 3 parity, issue #40" for s in ( - "deputyneg", "hsmpinneg", "hsmfaultneg", - "bandneg1", "bandneg2", "bandneg3", "bandneg4", "bandneg5", - "bandneg6", "restartneg1", "restartneg2", "restartneg3", - "manifestneg2", "manifestneg3", "periphspneg", "sealneg", - "sealhaltneg", "sealpivotneg", "mspovfneg", "xnneg", "svcneg")}, + "l3_exempt": {}, }, } diff --git a/tests/target/run_rt700_m33mu.sh b/tests/target/run_rt700_m33mu.sh index 423fa44a..c7b01a9c 100755 --- a/tests/target/run_rt700_m33mu.sh +++ b/tests/target/run_rt700_m33mu.sh @@ -59,15 +59,18 @@ case " $known " in *" $scenario "*) ;; *) echo "usage: $0 $(echo "$known" | tr ' ' '|')" >&2; exit 2 ;; esac -if [ "$scenario" = "hsmattackneg" ] && [ "${WT_ENGINE:-native}" != "hsm" ]; then - echo "hsmattackneg drives the raw wolfHSM client wire; run it with WT_ENGINE=hsm" >&2 - exit 2 -fi +case "$scenario" in + hsmattackneg|hsmpinneg|hsmfaultneg) + if [ "${WT_ENGINE:-native}" != "hsm" ]; then + echo "$scenario probes the wolfHSM engine; run it with WT_ENGINE=hsm" >&2 + exit 2 + fi ;; +esac here="$(cd "$(dirname "$0")" && pwd)" repo="$(cd "$here/../.." && pwd)" case "$scenario" in - bothpsa|bothiso|attestneg|hsmattackneg|fwustage) + bothpsa|bothiso|attestneg|hsmattackneg|fwustage|deputyneg|hsmpinneg|hsmfaultneg|bandneg[1-6]|restartneg[1-3]|periphspneg|sealneg|sealpivotneg|svcneg) guest_dir="tests/firmware/psa-guest" guest1_dir="$guest_dir" timeout_s="${RT700_M33MU_TIMEOUT:-180}" ;; @@ -99,6 +102,7 @@ fault_addr=0x30188000 # shellcheck source=lib/scenario.sh disable=SC1091 . "$here/lib/scenario.sh" +l3_layout_load "$repo" mimxrt700 # Port markers the shared verdict assertions match against. # shellcheck disable=SC2034 # matched inside lib/scenario.sh @@ -187,6 +191,15 @@ make -s TARGET=mimxrt700 secure-image TOOLPREFIX=arm-none-eabi- guest_flags="" case "$scenario" in ahbscneg) guest_flags="WT_AHBSC_PROBE=1" ;; + periphneg) guest_flags="WT_PERIPH_NEG_PROBE=1" ;; + # The secure probe fires inside a partition; guest0 ends the run on its + # breakpoint once its lifecycle is done, as the H5 level 3 runs do. + deputyneg|hsmpinneg|hsmfaultneg|bandneg[1-6]|restartneg[1-3]|periphspneg|sealneg|sealpivotneg|svcneg) + guest_flags="WT_M33MU_EXPECT_BKPT=1" + # guest0's own tasklet faults, so the peer ends the run. + if [ "$scenario" = "hsmfaultneg" ]; then + guest_flags="$guest_flags WT_M33MU_BKPT_GUEST=1" + fi ;; restart) guest_flags="WT_GUEST_FAULT_PROBE=1" ;; attestneg) guest_flags="WT_ATTEST_NEG_PROBE=1" ;; hsmattackneg) guest_flags="WT_HSM_ATTACK_PROBE=1" ;; @@ -262,9 +275,11 @@ case "$scenario" in # The val guest ends on its own breakpoint; the suite's panic tests reset # the whole chain mid-run and val resumes off its flash boot flag. end="bkpt:0x7f" ;; - fpneg) - # Containment only: M33MU ends the run when it raises NOCP. + fpneg|xnneg|mspovfneg) + # M33MU ends these runs at the fault (NOCP, the fetch denial, STKOF). end="fault" ;; + deputyneg|hsmpinneg|hsmfaultneg|bandneg[1-6]|restartneg[1-3]|periphspneg|sealneg|sealpivotneg|svcneg) + end="bkpt:0x7f" ;; esac stage "boot the chain under M33MU (--cpu imxrt700, ${timeout_s}s budget)" case "$end" in @@ -354,6 +369,61 @@ case "$scenario" in "\[MEMFAULT_CAUSE\] sec=S type=READ addr=$neg_addr reason=mpu-ap" fi ;; + deputyneg|hsmpinneg|hsmfaultneg|bandneg[1-6]|restartneg[1-3]|periphspneg|sealneg|sealpivotneg|svcneg) + expect_n "guest0 launched exactly once" 1 "guest0: alive" + if [ "$scenario" = "hsmfaultneg" ]; then + expect "guest1 ran its whole PSA lifecycle to the end breakpoint" \ + "guest1: done" + else + expect "guest0 ran its whole PSA lifecycle to the end breakpoint" \ + "guest0: done" + expect "guest1 ran beside the partition under test" "guest1: alive" + fi + case "$scenario" in + deputyneg) + l3_assert_probe_linked build/wolftrust.elf \ + wt_platform_deputy_flash_probe ;; + hsmpinneg) + l3_assert_probe_linked build/wolftrust.elf wt_platform_hsm_pin_probe ;; + *) + scenario_assert_l3 "$scenario" ;; + esac + case "$scenario" in + deputyneg|hsmpinneg|bandneg[1-6]|restartneg[1-3]) + # The probed partition is restarted (or never faults) and serves on. + if [ "$scenario" = "deputyneg" ] || [ "$scenario" = "hsmpinneg" ]; then + refute_re "no fault markers in the boot log" \ + '^(\[MEMFAULT\]|\[USGFLT\]|\[HARDFLT\]|HardFault|SecureFault)' + fi + expect "mediated crypto dispatch verified" \ + "guest0: wolfTrust FF-M mediated crypto dispatch verified" + expect "ITS set/get verified" "guest0: wolfTrust ITS set/get verified" + expect "PS sealed set/get verified" \ + "guest0: wolfTrust PS sealed set/get verified" + expect "key-ops sign/verify verified" \ + "guest0: wolfTrust key-ops sign/verify verified" + expect "attestation COSE_Sign1 verified" \ + "guest0: wolfTrust attestation: COSE_Sign1 verified" + ;; + hsmfaultneg) + expect "the other guest kept being served" \ + "guest1: psa_hash_compute(SHA-256) KAT verified" ;; + svcneg) + # The stateless ITS client reports the unblocked call as an error. + expect_re "the pinned ITS client was unblocked with an error" \ + 'guest[01]: psa_its_set failed st=-1(32|45)' + expect "the restarted ITS partition served the other guest" \ + "wolfTrust ITS set/get verified" + expect "sealed storage path unaffected" \ + "guest0: wolfTrust PS sealed set/get verified" ;; + periphspneg) + refute_re "the probed storage SP never served ITS" \ + 'guest[01]: wolfTrust ITS set/get verified' ;; + esac + ;; + xnneg|mspovfneg) + scenario_assert_verdict "$scenario" + ;; fpneg) # The relay's FP instruction must hit a disabled coprocessor: NOCP, not the # fallback undefined-instruction fault. Restart and guest survival are the @@ -514,6 +584,28 @@ case "$scenario" in expect "guest starts under fail-closed attestation" \ "guest0: wolfTrust FF-M conformance: val_entry start" ;; + periphneg) + # The CPU leg of the H5 periphneg; the M33MU RT700 model has no DMA, so + # the NS eDMA copies out of Secure memory are the EVK run's to show. + faults=$((restart_limit + 1)) + expect_n "guest0 relaunched through its restart budget" \ + "$faults" "wolfTrust RT700 guest0: start" + refute_re "no NS read of the SPM's TRNG ever returned" "periph probe LEAKED" + expect_n "guest1 launched once, untouched by guest0's faults" 1 \ + "wolfTrust RT700 guest1: start" + expect_n "guest1 completed the FF-M handshake" 1 \ + "wolfTrust RT700 guest1: FF-M connect ok, done" + stage "boot again with the protection-unit trace, stopping at the first fault" + log="$repo/build/rt700_m33mu_${scenario}_trace.log" + M33MU_PROT_TRACE=1 boot_chain 0 "$log" --quit-on-faults + expect "the traced run stopped at a delivered fault" "Execution stopped" + periph_ns="$(printf '0x%08x' $((L3_SPM_PERIPHERAL - 0x10000000)))" + refused="\[MEMFAULT_CAUSE\] sec=NS type=READ addr=$periph_ns reason=secure-attr" + expect_n_re "the fault was guest0's NS read of the SPM's TRNG, refused as Secure" \ + 1 "$refused" + sau_refusals="$(grep -A1 -E -- "$refused" "$log" | grep -c "src=SAU" || true)" + check "$([ "$sau_refusals" -eq 1 ]; echo $?)" "the refusal came from the SAU" + ;; ahbscneg) faults=$((restart_limit + 1)) expect_n "guest0 relaunched through its restart budget" \ From d966a7e67087c352264057c21653a133dd928e41 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 2 Oct 2026 14:38:37 -0700 Subject: [PATCH 5/8] Document the MIMXRT700 isolation level 3 scenarios --- docs/MIMXRT700-Guide.md | 12 +++++++----- docs/Testing.md | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/docs/MIMXRT700-Guide.md b/docs/MIMXRT700-Guide.md index e507e8c3..e929ac38 100644 --- a/docs/MIMXRT700-Guide.md +++ b/docs/MIMXRT700-Guide.md @@ -65,19 +65,21 @@ with the firmware-update backend. ### Secure runtime (wolfTrust) -The port reuses `src/arch/armv8m/` and `src/arch/common/` unchanged and adds +The port reuses `src/arch/armv8m/`, `src/arch/common/`, and the shared +Armv8-M isolation level 3 layer in `port/common/armv8m/` unchanged, and adds only `port/mimxrt700/` and one build fragment: | File | Responsibility | | --- | --- | -| `memory_map.h` | The bit-28 Secure-alias map: XSPI0 NOR windows, Secure and guest RAM, the boot-handoff address, and the per-partition RAM bands. | +| `memory_map.h` | The bit-28 Secure-alias map: XSPI0 NOR windows, Secure and guest RAM, the boot-handoff address, and the RAM code band; the per-partition bands are offsets from `WT_RAM_S_BASE` in `port/common/armv8m/l3_layout.h`. | +| `l3_port.h` | The level 3 layer's one board input: the TRNG as the Secure peripheral only the SPM drives. | | `mimxrt798_regs.h` | Register bases for CLKCTL, SYSCON, IOPCTL, LPUART0, XSPI0, TRNG, the AHBSC fabric controllers, and their GLIKEY unlock state machines. | | `platform_mimxrt700.c` | Every `wt_platform_*` operation: clocks, the SAU table, the Secure MPU whitelist, enabling AHBSC secure checking behind its GLIKEY unlock, staging of the RAM code band, the boot-handoff region, fault logging, panic, and reset. | | `partitions.c` | The guest and capability tables, the profile capability bitmap (the fabric filter is claimed on the per-dispatch SAU window, not on the AHBSC SRAM rules), and the pinned guest-measurement slot. | | `xspi_nor.c/.h` | The XSPI0 octal-DTR NOR program and erase driver: bounded target-group IP commands on its own LUT sequences, run from the RAM code band with interrupts masked, flushing the XSPI read cache afterwards. | | `hsm_flash.c/.h` | The `port_nvm.h` backend for the wolfHSM store: reads through the Secure XIP alias, program and erase through `xspi_nor.c`. | | `rng_entropy.c` | The `CUSTOM_RAND_GENERATE_BLOCK` entropy source over the on-die TRNG, preserving the unprivileged-to-privileged trap. | -| `secure.ld` | The port's own Secure linker script, including the RAM code band: the SG veneers, the `cmse_nonsecure_entry` bodies, and the NOR driver, loaded from flash and executed from SRAM. | +| `secure.ld` | The port's own Secure linker script, which INCLUDEs the shared level 3 regions, band sections, and layout ASSERTs and adds the RAM code band: the SG veneers, the `cmse_nonsecure_entry` bodies, and the NOR driver, loaded from flash and executed from SRAM. | | `manifest.json` | The service partitions (attestation, HSM, vault, ITS, PS, FWU) and their resources. | | `mk/target-mimxrt700.mk` | `WT_CPU`, the flash and RAM defaults, the linker `--defsym` set, and the source lists. | @@ -205,8 +207,8 @@ under M33MU. The `romsmoke` scenario proves the BootROM XIP path; the `positive` scenario is the wolfTrust chain; `ahbscneg` adds the guest isolation negative. The emulator runner then carries the STM32H563 scenario matrix (restart and launch refusal, SP fault recovery, the Secure-verdict -negatives, the PSA guest's lifecycle and negatives, and Arm's conformance -suites), listed in [Testing](Testing.md). +negatives, every isolation level 3 negative, the PSA guest's lifecycle and +negatives, and Arm's conformance suites), listed in [Testing](Testing.md). The full chain build and flash performs: diff --git a/docs/Testing.md b/docs/Testing.md index c35de758..8264ddb4 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -311,6 +311,23 @@ READY. `hsmattackneg` (hsm engine only, it drives the raw wolfHSM client wire) proves a forged client id cannot reach the IAK and an NVM-group packet never reaches the server. +The isolation level 3 negatives run as they do on the STM32H563, through the +same checks in `tests/target/lib/scenario.sh`; the band, partition stack and +SPM peripheral addresses they match come from `port/mimxrt700/memory_map.h` +through `tools/l3_layout_args.py`. `deputyneg`, `hsmpinneg`, `hsmfaultneg`, +`bandneg1` to `bandneg6`, `restartneg1` to `restartneg3`, `periphspneg`, +`sealneg`, `sealpivotneg`, and `svcneg` run the PSA guest in both windows, +and guest 0 ends the run on its breakpoint once its lifecycle is done. +`manifestneg2`, `manifestneg3`, and `sealhaltneg` end on their Secure verdict +breakpoint, and `mspovfneg` and `xnneg` at the SPM fault. The MIMXRT700 flash +context holds no geometry a partition can write, so `deputyneg` forges +out-of-range offsets and misaligned lengths instead, and each must be refused +against the const flash configuration. `periphneg` is the CPU leg of the +STM32H563 scenario: guest 0 switches off its own MPU and reads the SPM's TRNG +through the Non-secure alias, the SAU refuses the read on every launch, and +guest 0 is quarantined while guest 1 runs on. The M33MU RT700 model has no +DMA, so the Non-secure eDMA copies out of Secure memory are left to the EVK. + The conformance scenarios are the same drop-in proof the STM32H563 gives: `confboot` hosts Arm's unmodified psa-arch-tests FF-M IPC suite in the PSA guest against the conformance Secure image (`WT_CONFORMANCE=1`, the manifest From ac4f0f881f7540520e2225dce3b194be477c7875 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 2 Oct 2026 14:49:00 -0700 Subject: [PATCH 6/8] Stop the secure link when the level 3 band layout cannot be read --- .github/workflows/cross-compile.yml | 9 +++++++++ mk/arch-armv8m.mk | 10 +++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cross-compile.yml b/.github/workflows/cross-compile.yml index a6ac52aa..e6fc62c6 100644 --- a/.github/workflows/cross-compile.yml +++ b/.github/workflows/cross-compile.yml @@ -47,6 +47,15 @@ jobs: fi test "$lto_flash" -lt "$no_lto_flash" echo "LTO flash: $no_lto_flash -> $lto_flash bytes" + - name: Verify an unreadable level 3 layout stops the link + run: | + if make BUILD_DIR=build-no-layout WT_L3_BAND_ARGS= secure-image \ + TOOLPREFIX=arm-none-eabi- 2> no-layout.err; then + echo 'FAIL: the link ran without the level 3 band layout' >&2 + exit 1 + fi + grep -q 'cannot read the level 3 bands' no-layout.err + test ! -e build-no-layout/wolftrust.elf - name: Verify a rejected secure image cannot be reused run: | reject_flag='-Wl,--defsym=malloc=0x0C060800' diff --git a/mk/arch-armv8m.mk b/mk/arch-armv8m.mk index 3ba66f4a..96d205a5 100644 --- a/mk/arch-armv8m.mk +++ b/mk/arch-armv8m.mk @@ -73,9 +73,13 @@ $(error $(PORT_DIR)/memory_map.h has no literal WT_RAM_S_BASE) endif TARGET_LDFLAGS += -Wl,-L$(PORT_COMMON_DIR) \ -Wl,--defsym=WT_RAM_S_ORIGIN=$(WT_RAM_S_ORIGIN) -# Post-link band check inputs, read from the port's memory_map.h at link time. -WT_SECURE_LAYOUT_ARGS = $(shell python3 $(ROOT)/tools/l3_layout_args.py \ - --cc $(TOOLPREFIX)gcc $(PORT_DIR)/memory_map.h) $(WT_SECURE_LAYOUT_EXTRA_ARGS) +# Post-link band check inputs, read from the port's memory_map.h at link time; +# an unreadable layout stops the link rather than checking the default bands. +WT_L3_BAND_ARGS = $(shell python3 $(ROOT)/tools/l3_layout_args.py \ + --cc $(TOOLPREFIX)gcc $(PORT_DIR)/memory_map.h) +WT_SECURE_LAYOUT_ARGS = $(or $(strip $(WT_L3_BAND_ARGS)),$(error \ + cannot read the level 3 bands from $(PORT_DIR)/memory_map.h)) \ + $(WT_SECURE_LAYOUT_EXTRA_ARGS) # CMSE import library for the Non-secure guests, produced by the secure link. SECURE_CMSE_IMPLIB := $(BUILD_DIR)/secure_cmse_implib.o From c2468e944f0873a675623b473db7f5a6d5bc8c77 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 2 Oct 2026 21:04:36 -0700 Subject: [PATCH 7/8] Gate the shared Armv8-M isolation level 3 layer on WT_ISOLATION_LEVEL --- .github/workflows/cross-compile.yml | 14 ++++++++++++++ docs/Porting.md | 5 +++++ mk/arch-armv8m.mk | 10 ++++++++++ port/common/armv8m/l3_layout.h | 4 ++++ 4 files changed, 33 insertions(+) diff --git a/.github/workflows/cross-compile.yml b/.github/workflows/cross-compile.yml index e6fc62c6..d6bf6f18 100644 --- a/.github/workflows/cross-compile.yml +++ b/.github/workflows/cross-compile.yml @@ -47,6 +47,20 @@ jobs: fi test "$lto_flash" -lt "$no_lto_flash" echo "LTO flash: $no_lto_flash -> $lto_flash bytes" + - name: Verify an unimplemented isolation level stops the build + run: | + for target in stm32h563 mimxrt700; do + for level in 1 2; do + if make TARGET=$target WT_ISOLATION_LEVEL=$level \ + BUILD_DIR=build-level-$level secure-image \ + TOOLPREFIX=arm-none-eabi- 2> level.err; then + echo "FAIL: $target built at isolation level $level" >&2 + exit 1 + fi + grep -q 'only isolation level 3 is implemented' level.err + test ! -e build-level-$level/wolftrust.elf + done + done - name: Verify an unreadable level 3 layout stops the link run: | if make BUILD_DIR=build-no-layout WT_L3_BAND_ARGS= secure-image \ diff --git a/docs/Porting.md b/docs/Porting.md index a04d7b0c..cce9a0c2 100644 --- a/docs/Porting.md +++ b/docs/Porting.md @@ -85,6 +85,11 @@ the capability accurately and reject a manifest that requires more. ### Isolation level 3 +A new port must implement isolation level 3 unless it specifically targets +level 1 or 2. `WT_ISOLATION_LEVEL` (default 3) selects the level the secure +image implements and gates the shared level 3 layer; only level 3 exists +today, so any other value stops the build. + Every port of an architecture gets level 3 from `port/common//` instead of writing it again. For Armv8-M that layer provides: diff --git a/mk/arch-armv8m.mk b/mk/arch-armv8m.mk index 96d205a5..1dc6650f 100644 --- a/mk/arch-armv8m.mk +++ b/mk/arch-armv8m.mk @@ -59,6 +59,15 @@ ARCH_SRCS := \ $(ROOT)/src/arch/armv8m/sau_armv8m.c \ $(ROOT)/src/arch/armv8m/start_armv8m.c +# Isolation level the secure image implements. Only level 3 exists, so any +# other value stops the build; the shared level 3 layer is gated on it. +WT_ISOLATION_LEVEL ?= 3 +ifneq ($(WT_ISOLATION_LEVEL),3) +$(error only isolation level 3 is implemented (WT_ISOLATION_LEVEL=$(WT_ISOLATION_LEVEL))) +endif +ARCH_CFLAGS += -DWT_ISOLATION_LEVEL=$(WT_ISOLATION_LEVEL) + +ifeq ($(WT_ISOLATION_LEVEL),3) # Isolation level 3 layer shared by every Armv8-M port: the band layout, its # linker fragments and the platform hooks. A port's memory_map.h supplies # WT_RAM_S_BASE, from which every band is placed. @@ -80,6 +89,7 @@ WT_L3_BAND_ARGS = $(shell python3 $(ROOT)/tools/l3_layout_args.py \ WT_SECURE_LAYOUT_ARGS = $(or $(strip $(WT_L3_BAND_ARGS)),$(error \ cannot read the level 3 bands from $(PORT_DIR)/memory_map.h)) \ $(WT_SECURE_LAYOUT_EXTRA_ARGS) +endif # CMSE import library for the Non-secure guests, produced by the secure link. SECURE_CMSE_IMPLIB := $(BUILD_DIR)/secure_cmse_implib.o diff --git a/port/common/armv8m/l3_layout.h b/port/common/armv8m/l3_layout.h index 2e907725..daab60d2 100644 --- a/port/common/armv8m/l3_layout.h +++ b/port/common/armv8m/l3_layout.h @@ -25,6 +25,10 @@ #ifndef WOLFTRUST_PORT_ARMV8M_L3_LAYOUT_H #define WOLFTRUST_PORT_ARMV8M_L3_LAYOUT_H +#if defined(WT_ISOLATION_LEVEL) && (WT_ISOLATION_LEVEL != 3) +#error "l3_layout.h is the isolation level 3 layout; WT_ISOLATION_LEVEL is not 3" +#endif + #if !defined(WT_RAM_S_BASE) || !defined(WT_RAM_S_SIZE) #error "define WT_RAM_S_BASE and WT_RAM_S_SIZE before including l3_layout.h" #endif From 3b95fd783e1869158749a4a960efeefcaf008a0d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 2 Oct 2026 22:21:07 -0700 Subject: [PATCH 8/8] Match the H5 lifecycle markers across interleaved guest console output --- tests/target/run_m33mu_scenario.sh | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/target/run_m33mu_scenario.sh b/tests/target/run_m33mu_scenario.sh index b7db0f14..bf8197b1 100755 --- a/tests/target/run_m33mu_scenario.sh +++ b/tests/target/run_m33mu_scenario.sh @@ -431,31 +431,31 @@ case "$scenario" in refute_re "no fault markers in boot log" \ '^(\[MEMFAULT\]|\[HARDFLT\]|HardFault|SecureFault)' fi - expect "TEE client initialized" "wolfTrust TEE client initialized" - expect "FF-M psa_framework_version=0x0100" \ + expect_flat "TEE client initialized" "wolfTrust TEE client initialized" + expect_flat "FF-M psa_framework_version=0x0100" \ "wolfTrust FF-M psa_framework_version=0x0100" - expect "mediated crypto dispatch verified" \ + expect_flat "mediated crypto dispatch verified" \ "wolfTrust FF-M mediated crypto dispatch verified" - expect "ITS set/get verified" \ + expect_flat "ITS set/get verified" \ "wolfTrust ITS set/get verified" - expect "PS sealed set/get verified" \ + expect_flat "PS sealed set/get verified" \ "wolfTrust PS sealed set/get verified" - expect "key-ops sign/verify verified" \ + expect_flat "key-ops sign/verify verified" \ "wolfTrust key-ops sign/verify verified" - expect "key negatives verified" \ + expect_flat "key negatives verified" \ "wolfTrust key negatives verified" expect_flat "forged-handle call rejected" \ "wolfTrust FF-M forged-handle call rejected" expect_flat "oversized-vector call rejected" \ "wolfTrust FF-M oversized-vector call rejected" - expect "psa_hash_compute(SHA-256) KAT verified" \ + expect_flat "psa_hash_compute(SHA-256) KAT verified" \ "psa_hash_compute(SHA-256) KAT verified" - expect "psa_initial_attestation st=0" "psa_initial_attestation st=0" - expect "attestation COSE_Sign1 verified" \ + expect_flat "psa_initial_attestation st=0" "psa_initial_attestation st=0" + expect_flat "attestation COSE_Sign1 verified" \ "wolfTrust attestation: COSE_Sign1 verified" - expect "token measurement equals wolfBoot measurement of the signed image" \ + expect_flat "token measurement equals wolfBoot measurement of the signed image" \ "wolfTrust attestation: token measurement=$WT_EXPECTED_MEASUREMENT_HEX" - expect "attestation fields verify=0 lifecycle=0x1000 measurement=ok cose=ES256" \ + expect_flat "attestation fields verify=0 lifecycle=0x1000 measurement=ok cose=ES256" \ "attestation verify=0 challenge=ok identity=ok lifecycle=0x1000 measurement=ok cose=ES256" expect "guest1 FF-M SHA-256 KAT through SERVICE_CRYPTO (P7-S3)" \ "freertos_guest1: ffm sha256 ok"