wolfTrust protects Secure services from application domains through the isolation mechanisms supplied by an architecture and target port. This page documents the current STM32H563 reference security profile, which uses Armv8-M TrustZone and STM32 GTZC attribution for its Secure boundary and guest RAM isolation. Its security properties come from the authenticated boot chain, hardware attribution, SPM-owned identity, copied IPC, and service-specific policy.
The reference trusted computing base includes:
- wolfBoot and its verification key
- the wolfTrust Secure image and generated manifest
- wolfCrypt, wolfHAL, wolfCOSE, the shared wolfHSM NVM components, and the full wolfHSM server when the optional hsm engine is selected
- Armv8-M exception, TrustZone, and MPU behavior
- STM32H563 GTZC and flash option-byte configuration
- privileged wolfTrust SVC and fault handlers
- the target entropy and flash implementations
Non-secure guest kernels and applications are not trusted. A Secure Partition is trusted for the resources its manifest domain can access, but it is not trusted to access arbitrary SPM or peer-partition writable state.
| Boundary | Enforcement |
|---|---|
| Non-secure to Secure | SAU/IDAU attribution and exactly five CMSE FF-M gateway veneers |
| Guest RAM to guest RAM | GTZC MPCBB attribution closes the shared guest-RAM extent and opens only the scheduled guest's writable SRAM blocks |
| Guest request memory | CMSE security checks, active-guest range checks, overflow checks, vector-count limits, and copied transfers |
| Guest to service | Manifest service policy, connection ownership, generated handles, and SPM-stamped client identity |
| Secure Partition writable state | Unprivileged Secure threads and a per-partition Secure MPU table |
| Secure Partition to hardware backend | Operation-specific SVC gates pinned to the expected partition identity |
| Persistent objects | Vault ownership tuple, checked NVM operations, engine-specific non-exportable key policy, and key/storage type separation |
The linked Secure image exports exactly these Non-secure-callable functions:
WolfTrust_FFM_FrameworkVersionWolfTrust_FFM_ServiceVersionWolfTrust_FFM_ConnectWolfTrust_FFM_CallWolfTrust_FFM_Close
The link rule records symbols with nm and rejects any extra or
missing __acle_se_* entry. Service-specific operations ride
psa_call rather than adding more veneers.
For a call, the gateway:
- obtains the active guest ID from monitor state;
- converts guest
Nto PSA client ID-(N + 1); - checks and copies the veneer vector structure once;
- validates input and output counts;
- validates each range as Non-secure and within that guest's declared readable or writable memory; and
- copies request bytes into SPM-owned buffers before service dispatch.
A guest cannot choose its PSA identity. A racing write to the original vector descriptor cannot alter the copied descriptor used by the SPM.
Connections and messages carry an owner, type, generation, and state. The SPM rejects stale handles, wrong-owner handles, invalid transitions, and accesses disallowed by the manifest. Secure Partition dependencies are also checked: for example, ITS and Protected Storage may connect to the Secure-only vault, while a Non-secure client may not.
Each copied call is bounded to four vectors total across input and output and to the SPM transfer budget. Individual services impose smaller protocol bounds where needed.
Only the selected guest's Non-secure RAM is attributed Non-secure by the GTZC curtain. The monitor also installs that guest's Non-secure MPU regions and interrupt mask before returning to it. Reference guests currently run privileged Non-secure code and can reprogram their MPU and Non-secure NVIC state, so those controls are scheduling policy rather than adversarial boundaries. Guest RAM windows are non-overlapping and hardware-isolated by GTZC.
Guest flash windows are non-overlapping but share one Non-secure attribution
window and remain mutually readable. WRP plus WT_GUEST_FLASH_WRP=1 protects
their integrity, not confidentiality. A hostile guest can also reach peripherals
left Non-secure by the boot chain; manifest resource lists do not independently
enforce peripheral ownership in the current port.
On a guest fault, wolfTrust captures the reason, masks its interrupts, and applies the bounded restart policy. If restart is allowed, it clears RAM marked for restart and reconstructs the initial context. The image is verified again before the guest runs; failed verification or an exhausted restart budget leaves the guest quarantined.
Every shipped service loop runs as a scheduled unprivileged Secure coroutine. Its Secure MPU view contains:
- shared read/execute Secure image text;
- read-only Secure image constants; and
- its private stack and its own writable data band.
No two Secure Partitions share a writable byte (FF-M isolation level 3). The
vault partition owns the NVM object store, its flash context, the sealer, and
its DRBG; the crypto partition (SERVICE_HSM) owns the engine state and
reaches persistent key objects only through SERVICE_VAULT's keystore object
door, an FF-M service that serves the registered crypto partition alone and
only the ids and labels the keystore owns; the attestation partition owns the
token state and holds no key material, obtaining every signature and the IAK
public key from SERVICE_HSM's attestation door. Each door client is
confined to its door: the crypto partition's other vault requests and any
Secure Partition's call on SERVICE_HSM's ordinary crypto wire return
PSA_ERROR_NOT_PERMITTED. The manifest generator and
the runtime domain validator both refuse a level 3 manifest that shares a
writable resource between partitions, so the split cannot regress silently.
The keystore door hands the crypto partition the key objects it asks for.
That is an IPC contract between two partitions, not shared memory.
The tables the MPU is programmed from are checked again after they are composed. Before any partition runs, the SPM refuses to boot if a composed table grants write access to memory another partition can reach, or any access to SPM-private RAM. A request from a Secure Partition is checked against that partition's own composed table, and the SPM acts on one private copy of the request block, so the memory references it validated are the ones it uses.
A restarted partition keeps nothing its previous instance held. The SPM:
- fails every request the partition was serving and drops every connection to it to the error state;
- releases every connection and request the partition held as a client, so a handle from the old instance is refused;
- clears its asserted signals and masks its interrupt lines until the new instance enables them;
- clears its stack and returns its data band to its link-time image; and
- rebuilds the band with the setup boot ran, from inputs the SPM holds: the boot handoff, the attestation public key, and the contents of the store.
Persistent state is the NVM store alone. Nothing in a partition's RAM survives its restart.
Privileged handlers retain the SPM view. Flash, entropy, NVM lock, and reset operations are available only through narrow SVC operations that check the originating partition: flash and the NVM lock answer only the vault, entropy only the vault and crypto partitions. The state those handlers consume (the NVM lock, the flash driver's state, the lifecycle latch, the rollback floors, and the privileged tasklet stacks) lives in SPM-private RAM, outside every partition band.
This is writable-state isolation inside one linked image. Shared executable text is not per-partition code isolation.
Secure floating point is unsupported. Every Armv8-M port retires any FP
context the loader left active (CONTROL.FPCA, SFPA, FPCCR.LSPACT), then
clears CP10/CP11 access in
CPACR_S, CPACR_NS, and NSACR, clears automatic and lazy FP state
preservation in FPCCR_S, sets LSPENS, CLRONRET, and CLRONRETS, and
halts unless every one of those bits reads back as programmed. The post-link
check rejects FP instructions and soft-float runtime helpers in the Secure
image, so an FP instruction in a partition raises a UsageFault instead of
creating FP state another context could read.
The top of the Secure main stack and of every Secure coroutine stack carries
two seal words (0xFEF5EDA5), written when the stack is created; boot
refuses to continue unless the main-stack seal is in place with MSP below it.
The SPM checks both words of a coroutine stack when the coroutine yields or is
preempted, and again when it is dispatched. A partition that damaged its own
seal, or whose exception frame was stacked over it, is resumed on a trap
instruction below the seal, so it alone faults and restarts under
its recovery policy with a rebuilt stack; a seal found damaged at dispatch,
while its owner was suspended, halts the platform. The post-link check fails
the build if an allocated section reaches the main-stack seal.
The seal words mark the fixed top of each stack. Secure stack pointers are not moved onto a seal while another context runs, so this check does not replace the integrity signature the processor places in, and checks on, the Secure frames it stacks itself.
The Secure main stack has a fixed size (WT_SPM_STACK_SIZE, 16 KiB), reserved
by the linker below _estack; the link fails if .bss reaches it. Reset sets
MSPLIM_S to its bottom right after MSP is placed under the seal words, and
boot refuses to continue unless the limit reads back. An SPM stack overflow
raises UsageFault.STKOF on the main stack; the handler halts the platform on
the production panic without touching the stack, and a real HardFault
handler does the same for any escalated fault, latching CFSR, HFSR,
MMFAR, BFAR, the stacked PC and EXC_RETURN for a debugger instead of
spinning silently.
BusFault is enabled alongside MemManage and UsageFault. All three take one
dispatcher, which attributes the fault by the frame it finds: a Secure Thread
frame on the process stack with a scheduled partition or wolfHSM tasklet
current is that partition's fault (precise bus errors carry BFAR; an
imprecise one is drained by the barrier every context switch issues, so it is
still pending against the partition that issued the write), and the partition
restarts under its manifest policy. A frame from a privileged handler, the
bootstrap thread, or no current coroutine is the SPM's own fault and halts the
platform. A Non-secure bus error targets the Secure BusFault as well
(BFHFNMINS is 0); it is routed to the guest fault path and restarts the
guest. A partition that issues the scheduler's internal guest-return SVC is
resumed on the PROGRAMMER ERROR trap and restarts alone.
The SPM whitelist maps every writable Secure RAM region execute-never. The one
executable Secure RAM window is the MIMXRT700's RAMFUNC band, which holds the
NSC gateway and NOR routines and is mapped read-only. While an
unprivileged partition thread runs, its MPU table keeps PRIVDEFENA so the
privileged SVC gate and its deputies can reach SPM state, and the default map
would let privileged code execute from SRAM; one extra region therefore covers
the SPM's own RAM (the boot-handoff scratch, .data, .bss, the main stack)
privileged-only and execute-never on every partition dispatch, and a domain
region inside that window fails the dispatch closed. A production partition
table must leave the region free; the Arm conformance client partition fills
an 8-region MPU with its window grants, and the conformance image counts those
dispatches in g_wt_xn_denied instead (the STM32H563 implements 12 regions,
so it is covered there).
The Secure image enables GCC link-time optimization by default. LTO can replace the original input-object names with generated objects, so every object that places state in a partition data band is compiled without LTO and claimed by name. CMSE veneers, exception handlers, hand-written assembly, and other assembly-referenced objects are compiled without LTO so their symbols and calling conventions stay stable. State from an optimized link unit can only land in SPM-private RAM.
tools/secure_owners.txt gives every linked
object one owner: a partition, the SPM, or shared for code that runs in more
than one domain. The post-link layout check reads the linker map and rejects:
- a writable input section outside its owner's region;
- an object with no owner;
- a
sharedobject that holds writable state; - a missing exception entry, a linked heap allocator, or a privileged tasklet stack outside SPM-private RAM; and
- in a production image, any conformance object, symbol, or data.
The check runs at every link, with and without LTO, so the optimization cannot
weaken the boundaries described above. WT_LTO=0 disables the optimization
without changing the memory policy.
The SERVICE_HSM door carries the selected crypto engine's wire (WT_ENGINE):
the native engine (default) dispatches wolfCrypt directly, with explicitly
vault-backed keys stored as SENSITIVE and NONEXPORTABLE NVM objects whose
private material never leaves the Secure key-vault domain; the hsm engine
relays wolfHSM server packets. The FF-M surface, SIDs, and isolation bands are
identical in both engines.
In the hsm engine the service receives one copied wolfHSM request packet
through FF-M IPC. The SPM-stamped negative client ID selects guest N, and
the relay forces wolfHSM server client ID N + 1 before processing the
packet. In the native engine the same SPM-stamped identity becomes the vault
key namespace's delegated sub-owner. A client-provided communication ID
therefore cannot select another guest's key namespace in either engine.
The native reference guests normally run wolfPSA and wolfCrypt locally. Their ordinary volatile PSA keys therefore live in Non-secure guest RAM; only DRBG seed requests and explicit native-wire vault-key requests cross the Secure boundary. In the hsm engine, supported guest PSA operations and their private key state are routed to the Secure wolfHSM server.
The hsm engine's guest-facing relay rejects wolfHSM NVM message groups. The native request format exposes no general NVM operation. Guests can use the intended engine protocol but cannot directly reach storage objects, the firmware-version floor, the Protected Storage counter table, or the attestation key. See Crypto Engines for the complete selection and key-model comparison.
ITS and Protected Storage are front-end partitions. They forward every object operation to a vault service that is not available to Non-secure clients. The vault key is:
(front-end partition identity, SPM-stamped end-client identity, 64-bit UID)
ITS supports the core set/get/get-info/remove interface and the write-once
flag. Protected Storage always adds sealing even when a caller supplies
NO_CONFIDENTIALITY or NO_REPLAY_PROTECTION hints.
The current psa_ps_get_info() implementation echoes those requested hint
flags instead of reporting the stronger protection actually applied.
Sealing uses AES-256-GCM with:
- a device-local non-exportable key stored in the shared Secure NVM store;
- the object label as authenticated data;
- a 12-byte nonce derived from a persisted per-write counter; and
- a 16-byte authentication tag.
The counter is stored before ciphertext, preventing nonce reuse after an interrupted write. The live per-object counter also detects replay of a stale ciphertext unless an attacker can coherently roll back the counter store; see Threat Model.
Sealed replacement stages the authenticated prior object until the new
counter mapping commits, then destroys the stage. An interrupted replacement
restores that object
without rolling back the global nonce counter. Replacing sealed data with
unsealed data uses the same transaction and retires the old seal counter on
commit. If the recovery copy is missing or invalid, the live object is kept
only if it authenticates under the committed counter; otherwise that object
is discarded and its counter retired. Other objects remain available.
The vault requires wolfHSM's
wh_NvmFlash backend, including its capacity and compaction callbacks, and
rejects incompatible backends at initialization. The flash HAL may be supplied
by the target port or the host RAM simulator.
Vault storage rejects its reserved key-object type. In the native engine,
explicit key requests use the vault's separate key face. In the hsm engine,
guest cryptographic keys instead use the wolfHSM server keystore behind
SERVICE_HSM. Both paths bind operations to the SPM-stamped caller namespace.
The target image builder hashes the exact guest binary and records its guest ID, version, byte length, and SHA-256 digest in a slot inside wolfTrust. That slot is patched before wolfBoot signs the Secure image.
At boot, after initial guest-image validation and before any guest executes, wolfTrust checks the persistent Secure-image and recorded-guest version floors. Versions that pass the rollback check advance those floors.
During initial validation, immediately before a guest's first dispatch, and again after a restart, wolfTrust:
- locates the guest's signed record;
- rejects an empty or out-of-window image size;
- checks the record version against the manifest minimum;
- hashes exactly the recorded number of bytes and compares the digest in constant work.
When WT_GUEST_FLASH_WRP=1, launch also reads the STM32 WRP register
and refuses the guest unless every flash group covering its full window is
protected. This option is disabled in the generic default build because M33MU
does not model WRP; enable it for the hardened STM32H563 image and provision
the option bytes as described in STM32H5 Guide.
The Secure wolfCrypt settings define both NO_WOLFSSL_MEMORY and
WOLFSSL_NO_MALLOC. Stacks, IPC transfers, service state, selected-engine
contexts, and cryptographic scratch space use fixed storage. Oversized requests
fail instead of allocating. The link also rejects allocator symbols in both
engine images.
- FF-M gateway
- Secure Partition scheduler and SVC gates
- Secure stack sealing and context switch
- Secure fault attribution and SPM halt
- Secure MPU tables and the SPM RAM cover
- Guest verification
- HSM relay binding
- Native crypto dispatch
- Native vault key backend
- Vault storage
See Threat Model for assumptions and residual risks.