Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/cross-compile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,49 @@ jobs:
gcc-arm-none-eabi binutils-arm-none-eabi libnewlib-arm-none-eabi
- name: Build STM32H563 secure firmware
run: make -C tests/firmware/stm32h563 all TOOLPREFIX=arm-none-eabi-
- name: Validate the LTO gate, rebuild, and size reduction
run: |
make BUILD_DIR=build-lto-toggle WT_LTO=0 secure-image TOOLPREFIX=arm-none-eabi-
no_lto_flash=$(arm-none-eabi-size build-lto-toggle/wolftrust.elf | awk 'NR == 2 { print $1 + $2 }')
grep -qx 'WT_LTO=0' build-lto-toggle/secure_build_mode.stamp
if arm-none-eabi-readelf -SW build-lto-toggle/sec_spm.o | grep -q '\.gnu\.lto_'; then
echo 'FAIL: WT_LTO=0 produced an LTO object' >&2
exit 1
fi
make BUILD_DIR=build-lto-toggle secure-image TOOLPREFIX=arm-none-eabi-
lto_flash=$(arm-none-eabi-size build-lto-toggle/wolftrust.elf | awk 'NR == 2 { print $1 + $2 }')
grep -qx 'WT_LTO=1' build-lto-toggle/secure_build_mode.stamp
arm-none-eabi-readelf -SW build-lto-toggle/sec_spm.o | grep -q '\.gnu\.lto_'
if arm-none-eabi-readelf -SW build-lto-toggle/sec_runtime.o | grep -q '\.gnu\.lto_'; then
echo 'FAIL: isolation-critical object was compiled with LTO' >&2
exit 1
fi
test "$lto_flash" -lt "$no_lto_flash"
echo "LTO flash: $no_lto_flash -> $lto_flash bytes"
- name: Verify a rejected secure image cannot be reused
run: |
reject_flag='-Wl,--defsym=malloc=0x0C060800'
make BUILD_DIR=build-rejected-layout secure-image TOOLPREFIX=arm-none-eabi-
test -e build-rejected-layout/wolftrust.bin
touch build-rejected-layout/sec_spm.o
if make BUILD_DIR=build-rejected-layout WT_EXTRA_LDFLAGS="$reject_flag" \
secure-image TOOLPREFIX=arm-none-eabi-; then
echo 'FAIL: forbidden heap symbol was accepted' >&2
exit 1
fi
test ! -e build-rejected-layout/wolftrust.elf
test ! -e build-rejected-layout/wolftrust.bin
test ! -e build-rejected-layout/wolftrust.map
test ! -e build-rejected-layout/secure_cmse_implib.o
if make BUILD_DIR=build-rejected-layout WT_EXTRA_LDFLAGS="$reject_flag" \
secure-image TOOLPREFIX=arm-none-eabi-; then
echo 'FAIL: rejected secure image was reused' >&2
exit 1
fi
test ! -e build-rejected-layout/wolftrust.elf
test ! -e build-rejected-layout/wolftrust.bin
test ! -e build-rejected-layout/wolftrust.map
test ! -e build-rejected-layout/secure_cmse_implib.o
- name: Compile-check the no-attestation secure image (WT_ATTEST_COSE=0)
run: make TARGET=stm32h563 WT_ATTEST_COSE=0 secure-image TOOLPREFIX=arm-none-eabi-
- name: Build the hsm-engine secure image (WT_ENGINE=hsm)
Expand Down
24 changes: 24 additions & 0 deletions docs/Building.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ The default target builds:
| `build/wolftrust.elf` | Secure image with symbols |
| `build/wolftrust.bin` | Flat Secure binary |
| `build/secure_cmse_implib.o` | CMSE import library for Non-secure linking |
| `build/wolftrust.map` | Link map used to audit code and isolation-band placement |
| `build/manifest/wolftrust_manifest_generated.c` | Generated manifest source |
| `build/manifest/wolftrust_manifest_generated.h` | Generated partition and service constants |
| `build/nsc-syms.txt` | Symbol list used to enforce the five-veneer gateway |
Expand Down Expand Up @@ -97,6 +98,29 @@ make BUILD_DIR=build-wrp WT_GUEST_FLASH_WRP=1
make CONFIG_VNET=y
```

Link-time optimization is enabled by default for the Secure image. It lets GCC
optimize across source-file boundaries while retaining the assembly, CMSE, and
isolation-band objects that must keep stable linker behavior. Disable it for a
toolchain diagnostic or an explicit non-LTO comparison:

```sh
make WT_LTO=0
```

`WT_LTO` is recorded in `secure_build_mode.stamp`, so changing it rebuilds the
affected objects. Every Secure link also checks the final ELF for required
exception entries, zero-heap policy, and writable-state placement.

Use separate output directories when comparing footprints:

```sh
make BUILD_DIR=build-lto size-report
make BUILD_DIR=build-no-lto WT_LTO=0 size-report
```

Record the LTO setting with published size results. The locally measured TF-M
v2.1.1 comparison builds did not use LTO.

Changing guest count, addresses, or sizes also requires matching manifest,
guest linker, emulator-load, flash, and measurement-record settings. See
[Macros](Macros.md) for the supported values and constraints.
Expand Down
1 change: 1 addition & 0 deletions docs/Macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ selected values into C preprocessor defines. Defaults below come from
| `TARGET` | Target build selector; default `stm32h563`. | Must match an `mk/target-<soc>.mk` fragment; the root Makefile includes it, the architecture fragment, and `mk/common.mk`. |
| `TOOLPREFIX` | Cross-tool prefix; default `arm-none-eabi-`. | The prefixed GCC, objcopy, nm, and size tools must be available. |
| `BUILD_DIR` | Secure build output directory; default `build`. | Must be writable. |
| `WT_LTO` | Enable Secure-image link-time optimization; default `1`. | Set to `0` for diagnostics or a non-LTO size comparison. The GNU Arm compiler must support `-flto=auto`. |
| `WT_ENGINE` | Secure crypto engine: `native` (default) dispatches wolfCrypt directly behind the SERVICE_HSM door with explicitly vault-backed keys stored as `SENSITIVE` and `NONEXPORTABLE` NVM objects; `hsm` links the wolfHSM server as a key-management add-on (server-keystore semantics and an external-HSM offload path). Legacy `WT_ENGINE_HSM=0/1` maps onto the selector. | Both engines share the identical FF-M surface (5 veneers, SIDs, manifest, and L3 bands) and run every applicable CI scenario. Guest builds must use the same engine as the Secure image. See [Crypto Engines](Crypto-Engines.md). |

## Core target configuration
Expand Down
15 changes: 15 additions & 0 deletions docs/Security-Model.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,21 @@ This is writable-state isolation inside one linked image. Shared executable
text is not per-partition code isolation, and the crypto, vault, and attestation
domains share the keystore data band required by their backends.

### Link-time optimization

The Secure image enables GCC link-time optimization by default. LTO can replace
the original input-object names with generated objects, so the linker script
also claims keystore state through its `-fdata-sections` names. CMSE veneers,
exception handlers, hand-written assembly, and other assembly-referenced
objects are compiled without LTO so their symbols and calling conventions stay
stable.

The post-link layout check rejects keystore or VNET writable state outside its
assigned MPU band, missing exception entries, a linked heap allocator, and an
RNG timeout object outside privileged SPM RAM. This keeps the optimization from
weakening the boundaries described above. `WT_LTO=0` disables the optimization
without changing the memory policy.

## Per-guest cryptographic keys

The SERVICE_HSM door carries the selected crypto engine's wire (`WT_ENGINE`):
Expand Down
28 changes: 26 additions & 2 deletions docs/Testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ Current suites cover domain and manifest validation, lifecycle, guest
verification, rollback decisions, IPC and FF-M behavior, SPM policy, gateway
vectors, Secure Partition layout and recovery, crypto-engine relay and key
isolation, vault and storage services, attestation and COSE integration,
firmware update, runtime remeasurement, VNET, public PSA headers, boot-handoff
record consumption, and negative paths.
firmware update, runtime remeasurement, linked Secure layout, VNET, public PSA
headers, boot-handoff record consumption, and negative paths.
The attestation IAK suite runs wolfHSM NVM with both the default 8-byte and
STM32H5 16-byte flash programming units.

Expand All @@ -48,6 +48,30 @@ with the selected `CC` (default `cc`); it does not select multiple compilers.
Run it once per compiler, for example `make test-compilers CC=clang`.
Sanitizer support depends on the local toolchain.

## LTO validation

Every Secure link generates `wolftrust.map` and runs the linked-image layout
check. Compare optimized and diagnostic builds with separate output trees:

```sh
make BUILD_DIR=build-lto size-report
make BUILD_DIR=build-no-lto WT_LTO=0 size-report
```

The default M33MU and hardware commands exercise the LTO image. A release-size
result is valid only when the applicable target scenarios pass without a fault
marker. The cross-compilation workflow rebuilds one output tree from explicit
`WT_LTO=0` to the default `WT_LTO=1`, verifies the build stamp and object
formats change, checks that isolation-critical objects remain non-LTO, and
requires the optimized ELF to be smaller. It also injects a forbidden heap
symbol twice to prove a rejected ELF, flat binary, map, and CMSE import library
are deleted instead of being reused by the next Make invocation.

The per-PR core/port split workflow builds the `CONFIG_VNET=y` Secure image.
The per-PR M33MU matrix builds and runs both that VNET layout and the
`WT_CONFORMANCE=1` `confboot` layout, so the linked-image check covers both
optional isolation-band configurations in CI.

## PSA FF conformance

```sh
Expand Down
2 changes: 2 additions & 0 deletions mk/arch-armv8m.mk
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ define arch_image_checks
echo "FAIL: expected $(NSC_COUNT) FF-M veneers, found $$n (WT-FFM-0057)" >&2; \
exit 1; \
fi
@python3 $(ROOT)/tools/check_secure_layout.py \
--nm $(TOOLPREFIX)nm $(SECURE_ELF)
@if grep -E ' (malloc|free|calloc|realloc|_sbrk|_malloc_r|_free_r)$$' \
$(BUILD_DIR)/nsc-syms.txt; then \
echo "FAIL: heap allocator symbol in the zero-heap secure image" >&2; \
Expand Down
59 changes: 56 additions & 3 deletions mk/common.mk
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ CC := $(TOOLPREFIX)gcc
OBJCOPY := $(TOOLPREFIX)objcopy
SIZE := $(TOOLPREFIX)size

# A linked image that fails any post-link security check must not remain a
# reusable Make target. This also removes every output of the grouped link
# rule, including the CMSE import library and link map.
.DELETE_ON_ERROR:

MANIFEST_DIR := $(BUILD_DIR)/manifest
MANIFEST_STAMP := $(MANIFEST_DIR)/.stamp
MANIFEST_GEN_C := $(MANIFEST_DIR)/wolftrust_manifest_generated.c
MANIFEST_GEN_H := $(MANIFEST_DIR)/wolftrust_manifest_generated.h
SECURE_ELF := $(BUILD_DIR)/wolftrust.elf
SECURE_BIN := $(BUILD_DIR)/wolftrust.bin
SECURE_MAP := $(BUILD_DIR)/wolftrust.map
BUILD_MODE_STAMP := $(BUILD_DIR)/secure_build_mode.stamp
WOLFHSM_CFG_H := $(BUILD_DIR)/wolfhsm_cfg.h

Expand All @@ -22,6 +28,18 @@ WT_MAX_GUESTS ?= 2
# the SP_SMALL math switch; PSPLIM_S faults any real overflow, so this floor
# is measured, not guessed.
WT_CO_STACK_SIZE ?= 10240
WT_LTO ?= 1
ifneq ($(WT_LTO),0)
ifneq ($(WT_LTO),1)
$(error unsupported WT_LTO='$(WT_LTO)' (want 0 or 1))
endif
endif
WT_LTO_CFLAGS :=
WT_LTO_LDFLAGS :=
ifeq ($(WT_LTO),1)
WT_LTO_CFLAGS := -flto=auto
WT_LTO_LDFLAGS := -flto=auto -Wl,-u,wt_platform_panic
endif
# Secure crypto engine. native (the default) calls wolfCrypt directly; hsm
# links the wolfHSM server as a key-management add-on. Legacy WT_ENGINE_HSM
# values map onto the selector.
Expand Down Expand Up @@ -116,7 +134,7 @@ SECURE_CFLAGS := $(CPU_FLAGS) -ffreestanding -fno-builtin -nostdlib -Os -g \
$(TARGET_CFLAGS) \
$(ARCH_CFLAGS) \
$(HSM_INCLUDES_SECURE) $(HSM_DEFS_SECURE) $(SECURE_CFLAGS_COSE) \
-I$(MANIFEST_DIR)
-I$(MANIFEST_DIR) $(WT_LTO_CFLAGS)
SECURE_CFLAGS += $(WT_EXTRA_CFLAGS)

ifeq ($(CONFIG_VNET),y)
Expand Down Expand Up @@ -342,6 +360,34 @@ ALL_SECURE_OBJS := $(strip \
$(ARCH_TREE_OBJS) \
$(MANIFEST_OBJ))

# LTO cannot safely rewrite objects whose symbols are consumed by inline
# assembly or synthesized by the CMSE linker. Native engine and VNET objects
# that own filename-selected isolation bands retain their object identity.
ifeq ($(WT_LTO),1)
WT_LTO_WOLFCRYPT_EXCLUDED_OBJS := \
$(BUILD_DIR)/wc_sec_sp_cortexm.o \
$(BUILD_DIR)/wc_sec_thumb2-aes-asm_c.o \
$(BUILD_DIR)/wc_sec_thumb2-sha256-asm_c.o
WT_LTO_SECURE_EXCLUDED_OBJS := \
$(BUILD_DIR)/sec_ivt.o \
$(BUILD_DIR)/sec_runtime.o \
$(BUILD_DIR)/wt_sec_coroutine_armv8m.o \
$(BUILD_DIR)/wt_sec_ffm_nsc.o \
$(BUILD_DIR)/wt_sec_guest_context_armv8m.o \
$(BUILD_DIR)/wt_sec_sp_fault_armv8m.o \
$(BUILD_DIR)/wt_sec_spm_svc.o
WT_LTO_NATIVE_BAND_OBJS := $(filter \
$(BUILD_DIR)/wt_sec_crypto_native.o \
$(BUILD_DIR)/wt_sec_keyvault.o \
$(BUILD_DIR)/wt_sec_native_wire.o \
$(BUILD_DIR)/wt_sec_nvm_store.o,$(ALL_SECURE_OBJS))
WT_LTO_VNET_BAND_OBJS := $(filter \
$(BUILD_DIR)/wt_sec_vnet_%.o,$(ALL_SECURE_OBJS))
$(WT_LTO_WOLFCRYPT_EXCLUDED_OBJS): HSM_LIB_CFLAGS += -fno-lto
$(WT_LTO_SECURE_EXCLUDED_OBJS) $(WT_LTO_NATIVE_BAND_OBJS) \
$(WT_LTO_VNET_BAND_OBJS): SECURE_CFLAGS += -fno-lto
endif

# Arm PSA-FF conformance partitions (P3a): the unmodified upstream server and
# client partitions plus the i001/i002 test bodies, compiled into the secure
# image and scheduled as SPs. P3c regenerates the test lists over the full run
Expand Down Expand Up @@ -555,6 +601,9 @@ CONF_SEC_OBJS := \
$(BUILD_DIR)/conf_sec_test_i088.o \
$(BUILD_DIR)/conf_sec_test_supp_i088.o
ALL_SECURE_OBJS += $(CONF_SEC_OBJS)
ifeq ($(WT_LTO),1)
$(CONF_SEC_OBJS): SECURE_CFLAGS += -fno-lto
endif

# The upstream sources only exist after the fetch; the empty-recipe rule tells
# make the fetch stamp produces them so the conf_sec pattern rules can fire.
Expand Down Expand Up @@ -1335,6 +1384,7 @@ $(BUILD_MODE_STAMP): FORCE | $(BUILD_DIR)
'WT_GUEST0_FLASH_SIZE=$(WT_GUEST0_FLASH_SIZE)' \
'WT_GUEST1_FLASH_SIZE=$(WT_GUEST1_FLASH_SIZE)' \
'WT_ENGINE_HSM=$(WT_ENGINE_HSM)' \
'WT_LTO=$(WT_LTO)' \
'WT_ATTEST_COSE=$(WT_ATTEST_COSE)' \
'WT_CONFORMANCE=$(WT_CONFORMANCE)' \
'CONFIG_VNET=$(CONFIG_VNET)' \
Expand Down Expand Up @@ -1448,12 +1498,15 @@ $(BUILD_DIR)/sec_%.o: $(PORT_DIR)/%.c $(PORT_HEADERS) $(WOLFHSM_CFG_H) $(BUILD_M
$(BUILD_DIR)/sec_%.o: $(ROOT)/src/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR)
$(CC) $(SECURE_CFLAGS) -c -o $@ $<

$(SECURE_ELF) $(ARCH_LINK_OUTPUTS) &: $(ALL_SECURE_OBJS) $(SECURE_LD) $(BUILD_MODE_STAMP) | $(BUILD_DIR)
$(SECURE_ELF) $(SECURE_MAP) $(ARCH_LINK_OUTPUTS) &: $(ALL_SECURE_OBJS) $(SECURE_LD) \
$(ROOT)/tools/check_secure_layout.py $(BUILD_MODE_STAMP) | $(BUILD_DIR)
$(RM) $(SECURE_BIN)
$(CC) $(SECURE_CFLAGS) \
$(TARGET_LDFLAGS) \
-Wl,--defsym=WT_VNET_DATA_LENGTH=$(WT_VNET_DATA_LENGTH) \
-Wl,-T$(SECURE_LD) \
-Wl,--gc-sections $(WT_EXTRA_LDFLAGS) \
-Wl,--gc-sections -Wl,-Map=$(SECURE_MAP),--cref \
$(WT_LTO_LDFLAGS) $(WT_EXTRA_LDFLAGS) \
$(ARCH_LDFLAGS) \
-o $(SECURE_ELF) $(ALL_SECURE_OBJS) -lgcc
$(arch_image_checks)
Expand Down
25 changes: 25 additions & 0 deletions src/services/wolfhsm/runner/secure.ld
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ SECTIONS {
} > FLASH

.text (WT_SECURE_FLASH_ORIGIN + WT_SECURE_IMAGE_HEADER_SIZE + 0x800) : {
_s_secure_text = .;
*(.text*)
*(.glue_7)
*(.glue_7t)
Expand Down Expand Up @@ -158,6 +159,19 @@ SECTIONS {
.keystore_data : {
. = ALIGN(32);
_s_keystore = .;
/* LTO emits anonymous ltrans objects, so preserve the writable state by
* its -fdata-sections names before the non-LTO object selectors below. */
*(.data.g_attest_* .data.g_vault_* .data.g_relay_* .data.g_nvm_*)
*(.data.g_seal_* .data.g_flash_ecc_* .data.g_fwu_*)
*(.data.g_wt_flash_* .data.g_hsm_flash_ctx*)
*(.data.g_conf_nvm_*)
*(.data.g_hsm_fault_notify* .data.g_active_image_version*)
Comment thread
aidangarske marked this conversation as resolved.
*(.data.g_foreign_probe_fired*)
*(.data.g_boot_lifecycle* .data.g_boot_seed* .data.g_boot_handoff*)
*(.data.g_handoff_ready* .data.g_implementation_id*)
*(.data.g_signer_id* .data.g_ueid*)
*(.data.g_guests* .data.g_co_stack_slots*)
*(.data.gCryptoDev* .data.sha256DrbgDisabled* .data.initRefCount*)
*wt_hsm.o(.data .data.*)
*nvm_store.o(.data .data.*)
*crypto_native.o(.data .data.*)
Expand All @@ -182,6 +196,17 @@ SECTIONS {

.keystore_bss (NOLOAD) : {
_s_keystore_bss = .;
*(.bss.g_attest_* .bss.g_vault_* .bss.g_relay_* .bss.g_nvm_*)
*(.bss.g_seal_* .bss.g_flash_ecc_* .bss.g_fwu_*)
*(.bss.g_wt_flash_* .bss.g_hsm_flash_ctx*)
*(.bss.g_conf_nvm_*)
*(.bss.g_hsm_fault_notify* .bss.g_active_image_version*)
*(.bss.g_foreign_probe_fired*)
*(.bss.g_boot_lifecycle* .bss.g_boot_seed* .bss.g_boot_handoff*)
*(.bss.g_handoff_ready* .bss.g_implementation_id*)
*(.bss.g_signer_id* .bss.g_ueid*)
*(.bss.g_guests* .bss.g_co_stack_slots*)
*(.bss.gCryptoDev* .bss.sha256DrbgDisabled* .bss.initRefCount*)
*wt_hsm.o(.bss .bss.* COMMON)
*nvm_store.o(.bss .bss.* COMMON)
*crypto_native.o(.bss .bss.* COMMON)
Expand Down
2 changes: 1 addition & 1 deletion tests/host/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ UNIT_SUITES := domain manifest lifecycle guest_verify rollback sp_recovery \
attestation_service attestation attestation_token \
attestation_golden attestation_negatives attestation_replay \
attestation_iak ffm_domain sp_layout port_binding boot_handoff spm_gate \
flash_nvm ffm_veneer \
flash_nvm ffm_veneer secure_layout \
vault_service storage_service ps_service negatives qcbor_shim \
fwu_service runtime_verify psa_ffm_client psa_storage_client vnet_relay \
psa_headers
Expand Down
40 changes: 40 additions & 0 deletions tests/host/secure_layout/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Makefile
#
# 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 <https://www.gnu.org/licenses/>.

.PHONY: all run clean sanitize valgrind

all: run

run:
@python3 ../../../tools/check_secure_layout.py --self-test
@out="$$( $(MAKE) --no-print-directory -C ../../.. \
WT_LTO=invalid -n secure-image 2>&1 )"; status=$$?; \
if [ $$status -eq 0 ]; then \
echo "FAIL: invalid WT_LTO value was accepted" >&2; \
exit 1; \
fi; \
printf '%s\n' "$$out" | \
grep -Fq "unsupported WT_LTO='invalid'"

sanitize: run

valgrind: run

clean:
@:
Loading
Loading