From 4a3f9f041c04e4632a80dcd42bae00a710f49635 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:31:48 -0700 Subject: [PATCH 01/27] F-14097 - Correct boot pointer-validation comment --- src/ffm_boot.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/ffm_boot.c b/src/ffm_boot.c index 69b762c4..d1f1ad7b 100644 --- a/src/ffm_boot.c +++ b/src/ffm_boot.c @@ -34,12 +34,11 @@ static wt_ffm_runtime_t g_ffm_runtime; -/* WT-FFM-0012: the SPM validates every external memory reference before - * an API transfer. Every registered service today declares - * nonsecure_clients, so the only caller identity this port validates is a - * Non-secure guest (caller < 0). Secure-Partition callers (caller > 0) - * have no memory-envelope check yet and stay fail-closed until item 5 - * gives each SP its own L3 domain. */ +/* WT-FFM-0012: NS callers are validated by the installed NS-window checks. + * Positive Secure-Partition callers are accepted here because wt_spm_gate + * validates each dereferenced SP pointer against that caller's resolved + * protection domain. Service nonsecure_clients policy is independent of + * these pointer checks. */ static int wt_ffm_boot_caller_guest(psa_client_id_t caller, wt_guest_id_t* guest_id) { From 2ad72b04d3a7d6a16e3c49f1fa95d6f9b82fd809 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:31:51 -0700 Subject: [PATCH 02/27] F-14098 - Clarify VNET RX token exposure --- include/wolftrust/services/vnet_relay.h | 4 ++-- src/services/vnet/vnet_relay_service.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/wolftrust/services/vnet_relay.h b/include/wolftrust/services/vnet_relay.h index 3866e6f6..004ab64f 100644 --- a/include/wolftrust/services/vnet_relay.h +++ b/include/wolftrust/services/vnet_relay.h @@ -29,8 +29,8 @@ * (WT-FFM-0056). A non-secure guest reaches its switch port only through * psa_connect/psa_call; the operation rides the FF-M call type and the SPM * stamps the caller identity, which selects the port. Frame bytes cross as - * copied FF-M vectors, so pool slot/generation tokens never leave the - * secure side and cannot be forged or replayed by a guest. */ + * copied FF-M vectors. RX_FETCH returns the slot/generation metadata only + * after the relay has consumed and released the corresponding token. */ /* The WT_VNET_OP_* operation codes and SID live in vnet_abi.h, shared with * the non-secure client transport. diff --git a/src/services/vnet/vnet_relay_service.c b/src/services/vnet/vnet_relay_service.c index c0862aee..21e81b83 100644 --- a/src/services/vnet/vnet_relay_service.c +++ b/src/services/vnet/vnet_relay_service.c @@ -219,8 +219,8 @@ static psa_status_t wt_vnet_relay_tx(wt_ffm_runtime_t* runtime, return PSA_SUCCESS; } -/* Dequeue one frame: poll, copy the payload out, release the slot. The - * slot/generation cookie stays secure-side for its whole lifetime. */ +/* Dequeue one frame: poll, copy the payload out, release the slot, then return + * metadata including the now-consumed slot/generation token to the caller. */ static psa_status_t wt_vnet_relay_rx_fetch(wt_ffm_runtime_t* runtime, int32_t partition_id, vnet_switch_t* sw, uint32_t vm, From 2183259044508428c24d7edc64c5f1f7c31591a2 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:32:48 -0700 Subject: [PATCH 03/27] F-14096 - Correct secure partition privilege comments --- include/wolftrust/services/hsm.h | 15 ++++++------ include/wolftrust/services/storage_service.h | 2 +- include/wolftrust/services/vault_service.h | 14 +++++------ include/wolftrust/spm_sched.h | 23 ++++++++----------- port/stm32h563/hsm_flash.c | 4 ++-- port/stm32h563/memory_map.h | 16 ++++++------- src/services/wolfhsm/runner/secure.ld | 10 ++++---- src/services/wolfhsm/wt_hsm.c | 8 +++---- src/services/wolfhsm/wt_hsm_seal.c | 5 ++-- src/services/wolfhsm/wt_hsm_vault.c | 4 ++-- src/spm_partitions.c | 8 +++---- .../zephyr-stm32h5/apps/guest0_psa/src/main.c | 6 ++--- 12 files changed, 54 insertions(+), 61 deletions(-) diff --git a/include/wolftrust/services/hsm.h b/include/wolftrust/services/hsm.h index d7728b4b..c3a33707 100644 --- a/include/wolftrust/services/hsm.h +++ b/include/wolftrust/services/hsm.h @@ -167,9 +167,9 @@ int wt_hsm_vault_init(struct whNvmContext_t* nvm); struct wt_vault_backend; extern const struct wt_vault_backend wt_hsm_vault_backend; -/* Vault sealer (WT-FFM-0048): AES-GCM confidentiality + rollback binding for - * WT_VAULT_FLAG_SEALED objects, running entirely inside the privileged vault - * domain — the device-unique key never reaches any Secure Partition. seal +/* Vault sealer (WT-FFM-0048): AES-GCM confidentiality and rollback binding + * for WT_VAULT_FLAG_SEALED objects. Operations run in the confined vault SP, + * with the device-unique key kept in the shared keystore trust band. seal * writes pt_len + WT_VAULT_SEAL_TAG_LEN bytes ([ciphertext][tag]); unseal * takes ct_len >= tag length and writes ct_len - tag plaintext bytes. The * monotonic rollback counter is the GCM nonce, so a replayed (rolled-back) @@ -200,7 +200,7 @@ void wt_hsm_vault_set_sealer(const wt_vault_sealer_t* sealer); int wt_hsm_seal_init(struct whNvmContext_t* nvm); extern const wt_vault_sealer_t wt_hsm_sealer; -/* Shared vault directory helpers (wt_hsm_vault.c) for privileged backends: +/* Shared vault directory helpers (wt_hsm_vault.c) for confined backends: * label-addressed lookup over the vault NVM id window, and the label * make/flags codec. whNvmMetadata is an untagged typedef, so wh_common.h * must be included for the real type. */ @@ -211,10 +211,9 @@ void wt_hsm_vault_make_label(uint8_t* label, int32_t owner, int32_t sub, uint64_t uid, uint32_t flags); uint32_t wt_hsm_vault_flags_of(const uint8_t* label); -/* Vault-domain RNG (WT-FFM-0054): entropy for SERVICE_VAULT's RANDOM face, - * produced by a wolfCrypt DRBG owned by the privileged vault domain. Installed - * via wt_vault_service_set_rng at boot. Only linked into builds that carry - * wolfCrypt. */ +/* Vault RNG (WT-FFM-0054): entropy for SERVICE_VAULT's RANDOM face, produced + * by a wolfCrypt DRBG in the shared keystore trust band. Installed via + * wt_vault_service_set_rng at boot. Only linked into wolfCrypt builds. */ psa_status_t wt_hsm_vault_random(uint8_t* out, size_t len); #endif /* WOLFTRUST_SERVICES_HSM_H */ diff --git a/include/wolftrust/services/storage_service.h b/include/wolftrust/services/storage_service.h index 0eb4453c..8324bf1b 100644 --- a/include/wolftrust/services/storage_service.h +++ b/include/wolftrust/services/storage_service.h @@ -33,7 +33,7 @@ * (partition, client id, uid) via the delegated sub_owner (WT-FFM-0044). * The PS instance additionally ORs WT_VAULT_FLAG_SEALED into every request: * AES-GCM under the device-unique wolfHSM key plus rollback binding, applied - * entirely inside the privileged vault domain (WT-FFM-0048). */ + * inside the confined vault partition (WT-FFM-0048). */ /* psa_call request types (client face). Ops 1-4 are the shared ITS/PS core; * 5-7 exist only on the PS face. */ diff --git a/include/wolftrust/services/vault_service.h b/include/wolftrust/services/vault_service.h index a8560a0a..34855fda 100644 --- a/include/wolftrust/services/vault_service.h +++ b/include/wolftrust/services/vault_service.h @@ -56,8 +56,8 @@ /* Internal flag a storage frontend ORs in (never a PSA create flag): the * object is AES-GCM sealed under the device-unique wolfHSM key with the - * monotonic rollback counter as nonce (WT-FFM-0048). Sealing runs entirely - * inside the privileged vault domain. */ + * monotonic rollback counter as nonce (WT-FFM-0048). Sealing runs inside the + * confined vault partition. */ #define WT_VAULT_FLAG_SEALED 0x10000U /* Label marker for key objects (WT-FFM-0046). Never accepted from a storage @@ -129,9 +129,9 @@ typedef struct wt_vault_backend { psa_status_t (*remove)(int32_t owner, int32_t sub, uint64_t uid); } wt_vault_backend_t; -/* Key-operation vtable (WT-FFM-0046): every operation executes INSIDE the - * privileged vault domain against material that never leaves it. There is - * deliberately no private-export entry point. sign/verify operate on a +/* Key-operation vtable (WT-FFM-0046): every operation executes inside the + * confined vault partition against material in its keystore trust band. There + * is deliberately no private-export entry point. sign/verify operate on a * caller-supplied digest; encrypt frames its output [nonce][ct][tag] and * decrypt consumes the same framing. */ typedef struct wt_vault_key_backend { @@ -156,8 +156,8 @@ typedef struct wt_vault_key_backend { uint8_t* out, size_t cap, size_t* out_len); } wt_vault_key_backend_t; -/* Vault-domain randomness (WT-FFM-0054): fill out[0..len) from an RNG owned - * by the privileged vault domain, never a frontend partition. This is entropy +/* Vault randomness (WT-FFM-0054): fill out[0..len) from an RNG owned by the + * confined vault partition, never a frontend partition. This is entropy * plumbing, kept separate from the key backend so retiring the key backend * does not disturb the RANDOM face. */ typedef psa_status_t (*wt_vault_rng_fn)(uint8_t* out, size_t len); diff --git a/include/wolftrust/spm_sched.h b/include/wolftrust/spm_sched.h index 05d08e79..ab93c3f4 100644 --- a/include/wolftrust/spm_sched.h +++ b/include/wolftrust/spm_sched.h @@ -39,22 +39,18 @@ typedef void (*wt_spm_sp_entry_fn)(void* arg); int wt_spm_sched_add(wt_ffm_runtime_t* runtime, int32_t partition_id, wt_spm_sp_entry_fn entry, void* arg); -/* Start the SERVICE_HSM relay partition (WT-FFM-0054) as a scheduled - * PRIVILEGED coroutine: its loop hands each mediated wolfHSM packet to the - * monitor's inline server pump, which reads secure state and may block on - * the shared NVM mutex — the same privilege rationale as the vault. */ +/* Start SERVICE_HSM (WT-FFM-0054) as a confined, unprivileged scheduled SP. + * Keystore NVM, flash, and entropy operations cross the privileged SVC gate. */ int wt_spm_hsm_start(wt_ffm_runtime_t* runtime, int32_t partition_id); -/* Start the SERVICE_ATTEST partition as a scheduled PRIVILEGED coroutine: its - * dispatch loop runs on its own stack and reaches the secure attestation - * server state, so it runs privileged like the HSM relay for now. Defined only +/* Start SERVICE_ATTEST as a confined, unprivileged scheduled SP. Keystore NVM, + * flash, and entropy operations cross the privileged SVC gate. Defined only * in attestation-enabled builds. */ int wt_spm_attest_start(wt_ffm_runtime_t* runtime, int32_t partition_id); -/* Start the vault partition (WT-FFM-0047) as a scheduled PRIVILEGED coroutine: - * same slot machinery and SVC gate, but wt_co_set_domain is never called, so - * the loop may reach the wolfHSM NVM state and block on its mutex. Clients - * still cross the gate; the manifest's dependencies[] authorizes them. */ +/* Start SERVICE_VAULT (WT-FFM-0047) as a confined, unprivileged scheduled SP. + * Its thread domain is installed with wt_co_set_domain; NVM and flash access + * cross the privileged SVC gate. */ int wt_spm_vault_start(wt_ffm_runtime_t* runtime, int32_t partition_id); /* Start the ITS partition as a normal UNPRIVILEGED scheduled SP whose service @@ -64,9 +60,8 @@ int wt_spm_its_start(wt_ffm_runtime_t* runtime, int32_t partition_id); /* Schedule the PS partition: the storage loop with sealing forced on. */ int wt_spm_ps_start(wt_ffm_runtime_t* runtime, int32_t partition_id); -/* Start the Firmware Update partition (WT-FWU-0001) as a scheduled PRIVILEGED - * coroutine: it programs the wolfBoot update partition flash to stage a - * candidate, so it runs privileged like the vault. */ +/* Start SERVICE_FWU (WT-FWU-0001) as a confined, unprivileged scheduled SP. + * Update-partition flash operations cross the privileged SVC gate. */ int wt_spm_fwu_start(wt_ffm_runtime_t* runtime, int32_t partition_id); int wt_spm_vnet_start(wt_ffm_runtime_t* runtime, int32_t partition_id); diff --git a/port/stm32h563/hsm_flash.c b/port/stm32h563/hsm_flash.c index b3076f86..11f0d09e 100644 --- a/port/stm32h563/hsm_flash.c +++ b/port/stm32h563/hsm_flash.c @@ -677,8 +677,8 @@ int wt_hsm_flash_format(void) } /* SERVICE_FWU staging into the wolfBoot update partition (WT-FWU-0002). The - * privileged FWU coroutine erases each target sector lazily, programs the - * candidate, and verifies every block against the memory-mapped secure flash. + * unprivileged FWU SP crosses the privileged SVC gate to erase each target + * sector lazily, program the candidate, and verify each block in secure flash. * install() arms wolfBoot's real WRITEONCE update trigger in the UPDATE * partition trailer (wt_fwu_wolfboot_arm_trailer), so the next boot swaps the * staged image; the swapped image is still gated by authenticated launch and diff --git a/port/stm32h563/memory_map.h b/port/stm32h563/memory_map.h index 58d15544..0c4bbae0 100644 --- a/port/stm32h563/memory_map.h +++ b/port/stm32h563/memory_map.h @@ -124,9 +124,9 @@ #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 - * PRIVILEGED coroutine (its ops block on the shared wolfHSM NVM mutex), so - * this band is its manifest-declared execution stack, not an MPU domain. +/* Vault partition stack (WT-FFM-0047). The vault runs as a confined, + * unprivileged scheduled SP; this band is its execution stack and MPU-domain + * RW resource. NVM and flash operations cross the privileged SVC gate. * 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. */ @@ -146,11 +146,11 @@ #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 - * PRIVILEGED coroutine (it programs the wolfBoot update partition flash), so - * this band is its manifest-declared execution stack. 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. */ +/* FWU partition stack: the PSA Firmware Update service runs as a confined, + * unprivileged scheduled SP. This band is its execution stack and MPU-domain + * RW resource; flash operations cross the privileged SVC gate. Sits just + * below the PS stack; the linker RAM window is shortened to 388 KiB to make + * room. MUST match the FWUSTACK region in 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 diff --git a/src/services/wolfhsm/runner/secure.ld b/src/services/wolfhsm/runner/secure.ld index d0d02774..b1d0f299 100644 --- a/src/services/wolfhsm/runner/secure.ld +++ b/src/services/wolfhsm/runner/secure.ld @@ -39,9 +39,9 @@ MEMORY { * SPM RAM into one MPU-alignable region so the keystore partitions can be * granted exactly it while running unprivileged. Top of the general window. */ KEYSTORE (rw): ORIGIN = 0x30075000, LENGTH = 80K - /* 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. */ + /* FWU partition stack: the confined, unprivileged PSA Firmware Update SP's + * execution stack and MPU-domain RW resource. Flash programming crosses + * the privileged 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). */ @@ -49,8 +49,8 @@ MEMORY { /* 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 privileged vault coroutine's - * manifest-declared execution stack (WT_SP_VAULT_STACK_* in memory_map.h). + /* Vault partition stack (WT-FFM-0047): the confined, 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 diff --git a/src/services/wolfhsm/wt_hsm.c b/src/services/wolfhsm/wt_hsm.c index 3808a294..ae60482a 100644 --- a/src/services/wolfhsm/wt_hsm.c +++ b/src/services/wolfhsm/wt_hsm.c @@ -407,10 +407,10 @@ static int wt_hsm_vault_format(void) return rc; } -/* Vault-domain RNG (WT-FFM-0054): a wolfCrypt DRBG owned by the privileged - * vault domain, installed on SERVICE_VAULT's RANDOM face at boot. Kept - * separate from the wolfHSM server keystore — the single crypto backend for - * keys — because this is entropy plumbing, not key storage. */ +/* Vault RNG (WT-FFM-0054): a wolfCrypt DRBG in the shared keystore trust + * band, installed on SERVICE_VAULT's RANDOM face at boot. It remains separate + * from the wolfHSM server keystore because this is entropy plumbing, not key + * storage. */ static WC_RNG g_vault_rng; static int g_vault_rng_ready; diff --git a/src/services/wolfhsm/wt_hsm_seal.c b/src/services/wolfhsm/wt_hsm_seal.c index f4a24537..c4cef14d 100644 --- a/src/services/wolfhsm/wt_hsm_seal.c +++ b/src/services/wolfhsm/wt_hsm_seal.c @@ -20,9 +20,8 @@ /* wolfCrypt AES-256-GCM vault sealer (WT-FFM-0048). The device-unique key is * generated on first boot, stored at WT_HSM_SEAL_KEY_ID as NONEXPORTABLE and - * immutable, and cached only in privileged vault-domain RAM — it never - * reaches a Secure Partition, which is the property that beats TF-M's - * Crypto-partition-RAM key storage. The GCM nonce is the caller-supplied + * immutable, and cached in the shared keystore trust band granted only to the + * confined keystore partitions. The GCM nonce is the caller-supplied * monotonic rollback counter, unique per sealed write by construction, so a * rolled-back ciphertext fails tag authentication on unseal. */ diff --git a/src/services/wolfhsm/wt_hsm_vault.c b/src/services/wolfhsm/wt_hsm_vault.c index d8d7d648..62246b2c 100644 --- a/src/services/wolfhsm/wt_hsm_vault.c +++ b/src/services/wolfhsm/wt_hsm_vault.c @@ -66,7 +66,7 @@ typedef struct wt_hsm_vault_table { static whNvmContext* g_vault_nvm; static const wt_vault_sealer_t* g_vault_sealer; -/* Sealed-object staging, privileged vault domain only. */ +/* Sealed-object staging in the shared keystore trust band. */ static uint8_t g_vault_ct[WT_VAULT_OBJECT_MAX + WT_VAULT_SEAL_TAG_LEN]; static uint8_t g_vault_pt[WT_VAULT_OBJECT_MAX]; @@ -368,7 +368,7 @@ static psa_status_t wt_hsm_vault_recover(wt_hsm_vault_table_t* table) return wt_hsm_vault_destroy_stage(); } -/* Shared directory lookup for privileged vault backends: find the +/* Shared directory lookup for confined vault backends: find the * (owner, sub, uid) object in the vault id window. Returns PSA_SUCCESS * with the id + metadata, or PSA_ERROR_DOES_NOT_EXIST. out_free_id receives * the lowest unused id in the window (WH_NVM_ID_INVALID when full). */ diff --git a/src/spm_partitions.c b/src/spm_partitions.c index a01f10c7..a442cd9c 100644 --- a/src/spm_partitions.c +++ b/src/spm_partitions.c @@ -225,10 +225,10 @@ int wt_spm_its_start(wt_ffm_runtime_t* runtime, int32_t partition_id) (void*)(intptr_t)partition_id); } -/* The PS partition: the same unprivileged storage loop, but every request is - * forwarded SEALED — AES-GCM under the device-unique wolfHSM key plus - * rollback binding, applied inside the privileged vault domain - * (WT-FFM-0048). The NO_* client hints are accepted and recorded, never +/* The PS partition uses the same unprivileged storage loop, but every request + * is forwarded SEALED. AES-GCM and rollback binding run in the confined vault + * partition using the device-unique wolfHSM key (WT-FFM-0048). The NO_* + * client hints are accepted and recorded, never * honoured downward: wolfTrust always stores at full strength. */ static void wt_spm_ps_entry(void* arg) { diff --git a/tests/firmware/zephyr-stm32h5/apps/guest0_psa/src/main.c b/tests/firmware/zephyr-stm32h5/apps/guest0_psa/src/main.c index 0440d2ad..10de3fa6 100644 --- a/tests/firmware/zephyr-stm32h5/apps/guest0_psa/src/main.c +++ b/tests/firmware/zephyr-stm32h5/apps/guest0_psa/src/main.c @@ -304,7 +304,7 @@ static void exercise_ffm_its(void) } /* P4-S3: the sealed-storage round trip. Same wire as ITS, but SERVICE_PS - * AES-GCM-seals every object inside the privileged vault domain, so a clean + * routes each object through the confined vault partition, so a clean * set/get proves seal + rollback-counter + unseal end to end on target. */ static void exercise_ffm_ps(void) { @@ -1090,8 +1090,8 @@ static void wt_guest_fault_probe(void) #endif #if defined(WT_FWU_PROBE) -/* P6-S4: drive SERVICE_FWU from a Non-secure guest. The privileged FWU SP - * stages the candidate into the real wolfBoot update partition flash and +/* P6-S4: drive SERVICE_FWU from a Non-secure guest. The unprivileged FWU SP + * stages the candidate through its privileged SVC flash gate and * verifies each block by read-back, so a clean start/write/finish/install * proves the isolated update service end to end on target. The wolfBoot swap * of the armed image rides the full boot-and-update gate (P6-S6). */ From cd95ff8ec0fbd0f02ce1c66822bc33905efec9d3 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:33:22 -0700 Subject: [PATCH 04/27] F-14111 - Make queued mutex acquisition idempotent --- src/sync/mutex.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/sync/mutex.c b/src/sync/mutex.c index 462ea5cc..5c81731f 100644 --- a/src/sync/mutex.c +++ b/src/sync/mutex.c @@ -136,6 +136,8 @@ struct wt_co *wt_mutex_holder(const wt_mutex_t *m) int wt_mutex_acquire_queued(wt_mutex_t *m, struct wt_co *self) { + wt_co_t *waiter; + if (m == NULL || self == NULL) { return -1; } @@ -148,6 +150,12 @@ int wt_mutex_acquire_queued(wt_mutex_t *m, struct wt_co *self) if (m->holder == self) { return 0; } + for (waiter = m->wait_head; waiter != NULL; + waiter = waiter->next_wait) { + if (waiter == self) { + return 1; + } + } m->contend_count++; self->next_wait = NULL; From f6fbbacdb9deba5099be6621346c5e83a91a90e9 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:33:25 -0700 Subject: [PATCH 05/27] F-14114 - Check active coroutine stack canaries --- src/sched/coroutine.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sched/coroutine.c b/src/sched/coroutine.c index 3b63b705..15f59ab3 100644 --- a/src/sched/coroutine.c +++ b/src/sched/coroutine.c @@ -112,7 +112,7 @@ static struct wt_co *runqueue_dequeue(void) static void check_canary(struct wt_co *co) { - if (co->stack_base == (uint8_t *)0) { + if (co == (struct wt_co *)0 || co->stack_base == (uint8_t *)0) { return; /* bootstrap has no stack buffer */ } if (*(volatile uint32_t *)(void *)co->stack_base != WT_CO_STACK_CANARY) { @@ -130,11 +130,13 @@ static void do_switch(struct wt_co *to) to = &g_wt_co_bootstrap; } - check_canary(&g_wt_co_bootstrap); + check_canary(g_wt_co_current); + check_canary(to); g_wt_co_current = to; to->state = WT_CO_RUNNING; wt_co_arch_enter(to); /* Execution resumes here when `to` blocks or faults back to bootstrap. */ + check_canary(to); } static void runqueue_unlink(struct wt_co *co) From 99af9abffa8a1b4d37e0866b001dec2aee224357 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:33:29 -0700 Subject: [PATCH 06/27] F-14104 - Panic on invalid wait output pointers --- src/spm_gate.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/spm_gate.c b/src/spm_gate.c index 9bdebd45..46c429c2 100644 --- a/src/spm_gate.c +++ b/src/spm_gate.c @@ -106,6 +106,9 @@ int wt_spm_gate(wt_ffm_runtime_t* runtime, call->must_panic = 1U; } } + else { + call->must_panic = 1U; + } call->ret_int = ret; break; case WT_SPM_OP_GET: From e4ad972f85faac7f93996a323a7c07bf66f98f39 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:33:53 -0700 Subject: [PATCH 07/27] F-14112 - Dispatch runnable lock waiters --- src/arch/common/spm_gate_core.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/arch/common/spm_gate_core.c b/src/arch/common/spm_gate_core.c index 89c2ea7e..0abb2233 100644 --- a/src/arch/common/spm_gate_core.c +++ b/src/arch/common/spm_gate_core.c @@ -66,6 +66,7 @@ #define WT_SPM_WAIT_NONE 0U #define WT_SPM_WAIT_SIG 1U #define WT_SPM_WAIT_MSG 2U +#define WT_SPM_WAIT_LOCK 3U typedef struct wt_spm_sp { wt_co_t* co; @@ -534,8 +535,12 @@ int wt_spm_dispatch_call(wt_spm_call_t* call, wt_trap_frame_t* frame) /* Enqueued behind the holder: block on exception return * and report retry; the release hands the mutex over * before waking, so the re-issue observes ownership. */ + slot->wait_kind = WT_SPM_WAIT_LOCK; wt_co_block(); } + else if (ks_ret == 0) { + slot->wait_kind = WT_SPM_WAIT_NONE; + } } else if (call->call_type == WT_SPM_KS_LOCK_RELEASE) { ks_ret = wt_mutex_release(nvm_mutex); @@ -680,6 +685,9 @@ static int wt_spm_slot_ready(const wt_ffm_runtime_t* runtime, if (slot->wait_kind == WT_SPM_WAIT_MSG) { return wt_ffm_msg_complete(runtime, slot->wait_msg); } + if (slot->wait_kind == WT_SPM_WAIT_LOCK) { + return 0; + } /* No recorded wait (the partition has not run since boot): pending * signals mean queued work; a spurious wake lands in its psa_wait and * re-blocks harmlessly. */ @@ -688,7 +696,9 @@ static int wt_spm_slot_ready(const wt_ffm_runtime_t* runtime, static int wt_spm_run_co(wt_co_t* co) { - wt_co_wake(co); + if (wt_co_state(co) == WT_CO_BLOCKED) { + wt_co_wake(co); + } while (wt_co_state(co) == WT_CO_RUNNABLE) { if (wt_co_run(co) == 0u) { return WT_FFM_ERROR_STATE; @@ -833,9 +843,12 @@ static int wt_spm_sched_dispatch(void* context, wt_ffm_runtime_t* runtime, for (i = 0u; i < g_spm_sp_count; i++) { wt_spm_sp_t* slot = &g_spm_sp[i]; + wt_co_state_t state = wt_co_state(slot->co); + if (slot->in_use == 0u || - wt_co_state(slot->co) != WT_CO_BLOCKED || - wt_spm_slot_ready(runtime, slot) == 0) { + (state != WT_CO_RUNNABLE && + (state != WT_CO_BLOCKED || + wt_spm_slot_ready(runtime, slot) == 0))) { continue; } if (wt_spm_run_co(slot->co) != WT_FFM_SUCCESS) { From 6a5e4854be383c3080fcd88215b5b494f5087773 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:34:18 -0700 Subject: [PATCH 08/27] F-14118 - Erase shared HSM relay buffers --- include/wolftrust/zeroize.h | 36 +++++++++++++++ src/services/hsm_relay_service.c | 75 +++++++++++++++++--------------- 2 files changed, 75 insertions(+), 36 deletions(-) create mode 100644 include/wolftrust/zeroize.h diff --git a/include/wolftrust/zeroize.h b/include/wolftrust/zeroize.h new file mode 100644 index 00000000..17c45001 --- /dev/null +++ b/include/wolftrust/zeroize.h @@ -0,0 +1,36 @@ +/* zeroize.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 . + */ + +#ifndef WOLFTRUST_ZEROIZE_H +#define WOLFTRUST_ZEROIZE_H + +#include + +static inline void wt_forceZero(void* memory, size_t size) +{ + volatile unsigned char* bytes = (volatile unsigned char*)memory; + + while (size > 0U) { + *bytes++ = 0U; + size--; + } +} + +#endif /* WOLFTRUST_ZEROIZE_H */ diff --git a/src/services/hsm_relay_service.c b/src/services/hsm_relay_service.c index b3d54edd..eae11dc7 100644 --- a/src/services/hsm_relay_service.c +++ b/src/services/hsm_relay_service.c @@ -19,6 +19,7 @@ */ #include "wolftrust/services/hsm_relay.h" +#include "wolftrust/zeroize.h" #include @@ -115,46 +116,48 @@ static psa_status_t wt_hsm_relay_call(wt_ffm_runtime_t* runtime, size_t req_len = 0U; size_t resp_len = 0U; size_t resp_cap; + psa_status_t status; wt_hsm_relay_submit_fn submit = g_relay_submit; - if (msg->in_size[0] == 0U || - msg->in_size[0] > sizeof(g_relay_io.req)) { - return PSA_ERROR_INVALID_ARGUMENT; - } + status = PSA_ERROR_INVALID_ARGUMENT; resp_cap = msg->out_size[0]; - if (resp_cap == 0U) { - return PSA_ERROR_INVALID_ARGUMENT; - } - if (resp_cap > sizeof(g_relay_io.resp)) { - resp_cap = sizeof(g_relay_io.resp); - } - if (submit == NULL) { - /* Fail closed: no platform submit hook, no path to the server. */ - submit = wt_hsm_relay_default_submit; - } - if (wt_hsm_relay_read_req(runtime, partition_id, msg->handle, - g_relay_io.req, sizeof(g_relay_io.req), - &req_len) != WT_FFM_SUCCESS || - req_len != msg->in_size[0]) { - return PSA_ERROR_INVALID_ARGUMENT; - } - if (submit == wt_hsm_relay_default_submit) { - return PSA_ERROR_NOT_SUPPORTED; - } - if (submit(g_relay_submit_ctx, msg->client_id, g_relay_io.req, req_len, - g_relay_io.resp, - resp_cap, &resp_len) != 0) { - return PSA_ERROR_GENERIC_ERROR; - } - if (resp_len == 0U || resp_len > resp_cap) { - return PSA_ERROR_GENERIC_ERROR; - } - if (wt_hsm_relay_write_resp(runtime, partition_id, msg->handle, - g_relay_io.resp, resp_len) != - WT_FFM_SUCCESS) { - return PSA_ERROR_GENERIC_ERROR; + if (msg->in_size[0] != 0U && + msg->in_size[0] <= sizeof(g_relay_io.req) && resp_cap != 0U) { + if (resp_cap > sizeof(g_relay_io.resp)) { + resp_cap = sizeof(g_relay_io.resp); + } + if (submit == NULL) { + /* Fail closed: no platform submit hook, no path to the server. */ + submit = wt_hsm_relay_default_submit; + } + if (wt_hsm_relay_read_req(runtime, partition_id, msg->handle, + g_relay_io.req, sizeof(g_relay_io.req), + &req_len) != WT_FFM_SUCCESS || + req_len != msg->in_size[0]) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + else if (submit == wt_hsm_relay_default_submit) { + status = PSA_ERROR_NOT_SUPPORTED; + } + else if (submit(g_relay_submit_ctx, msg->client_id, + g_relay_io.req, req_len, g_relay_io.resp, + resp_cap, &resp_len) != 0) { + status = PSA_ERROR_GENERIC_ERROR; + } + else if (resp_len == 0U || resp_len > resp_cap) { + status = PSA_ERROR_GENERIC_ERROR; + } + else if (wt_hsm_relay_write_resp(runtime, partition_id, msg->handle, + g_relay_io.resp, resp_len) != + WT_FFM_SUCCESS) { + status = PSA_ERROR_GENERIC_ERROR; + } + else { + status = PSA_SUCCESS; + } } - return PSA_SUCCESS; + wt_forceZero(&g_relay_io, sizeof(g_relay_io)); + return status; } int wt_hsm_relay_dispatch(void* context, wt_ffm_runtime_t* runtime, From 6c85f87cbab1984d95707a44ad4ce43a2edcb542 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:34:28 -0700 Subject: [PATCH 09/27] F-14124 - Erase HSM client response buffers --- src/client/hsm_psa_transport.c | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/client/hsm_psa_transport.c b/src/client/hsm_psa_transport.c index 5bc8bdf8..9285f253 100644 --- a/src/client/hsm_psa_transport.c +++ b/src/client/hsm_psa_transport.c @@ -33,6 +33,14 @@ #include "psa/client.h" #include "wolftrust/services/hsm_relay.h" #include "wolftrust/hsm_psa_transport.h" +#include "wolftrust/zeroize.h" + +static void wt_hsm_psa_clear_response(wt_hsm_psa_transport_ctx_t* ctx) +{ + wt_forceZero(ctx->resp, sizeof(ctx->resp)); + ctx->resp_len = 0U; + ctx->has_resp = 0U; +} static int wt_hsm_psa_init(void* ctx_v, const void* cfg_v, whCommSetConnectedCb connectcb, @@ -42,7 +50,12 @@ static int wt_hsm_psa_init(void* ctx_v, const void* cfg_v, const wt_hsm_psa_transport_cfg_t* cfg = (const wt_hsm_psa_transport_cfg_t*)cfg_v; - if (ctx == NULL || cfg == NULL) { + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + wt_hsm_psa_clear_response(ctx); + ctx->handle = 0; + if (cfg == NULL) { return WH_ERROR_BADARGS; } ctx->handle = (int32_t)psa_connect(cfg->sid, cfg->version); @@ -53,8 +66,6 @@ static int wt_hsm_psa_init(void* ctx_v, const void* cfg_v, if (ctx->handle <= 0) { return WH_ERROR_ABORTED; } - ctx->resp_len = 0U; - ctx->has_resp = 0U; if (connectcb != NULL) { connectcb(connectcb_arg, WH_COMM_CONNECTED); } @@ -70,9 +81,9 @@ static int wt_hsm_psa_cleanup(void* ctx_v) } if (ctx->handle > 0) { psa_close((psa_handle_t)ctx->handle); - ctx->handle = 0; } - ctx->has_resp = 0U; + ctx->handle = 0; + wt_hsm_psa_clear_response(ctx); return WH_ERROR_OK; } @@ -87,12 +98,15 @@ static int wt_hsm_psa_send(void* ctx_v, uint16_t data_size, const void* data) psa_outvec out_vec; psa_status_t status; - if (ctx == NULL || data == NULL || ctx->handle <= 0) { + if (ctx == NULL) { return WH_ERROR_BADARGS; } - if (data_size == 0U || data_size > WT_HSM_RELAY_MSG_MAX) { + if (data == NULL || ctx->handle <= 0 || data_size == 0U || + data_size > WT_HSM_RELAY_MSG_MAX) { + wt_hsm_psa_clear_response(ctx); return WH_ERROR_BADARGS; } + wt_hsm_psa_clear_response(ctx); in_vec.base = data; in_vec.len = data_size; out_vec.base = ctx->resp; @@ -101,7 +115,7 @@ static int wt_hsm_psa_send(void* ctx_v, uint16_t data_size, const void* data) &out_vec, 1U); if (status != PSA_SUCCESS || out_vec.len == 0U || out_vec.len > sizeof(ctx->resp)) { - ctx->has_resp = 0U; + wt_hsm_psa_clear_response(ctx); return WH_ERROR_ABORTED; } ctx->resp_len = (uint16_t)out_vec.len; @@ -121,7 +135,7 @@ static int wt_hsm_psa_recv(void* ctx_v, uint16_t* out_size, void* data) } (void)memcpy(data, ctx->resp, ctx->resp_len); *out_size = ctx->resp_len; - ctx->has_resp = 0U; + wt_hsm_psa_clear_response(ctx); return WH_ERROR_OK; } From cd2f3677a901578b4a2f5a7a724edd033e98ba13 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:34:31 -0700 Subject: [PATCH 10/27] F-14125 - Erase storage client request copies --- src/client/psa_storage_client.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/client/psa_storage_client.c b/src/client/psa_storage_client.c index 30c18214..2b274f96 100644 --- a/src/client/psa_storage_client.c +++ b/src/client/psa_storage_client.c @@ -38,6 +38,7 @@ #include "psa/storage_common.h" #include "psa/internal_trusted_storage.h" #include "psa/protected_storage.h" +#include "wolftrust/zeroize.h" /* Wire header + ops, kept in lockstep with storage_service.h (the SPM-side * definition pulls in Secure-only headers and is not includable here). */ @@ -90,12 +91,15 @@ static psa_status_t wt_storage_ns_write(uint32_t sid, int32_t op, uint64_t uid, } handle = psa_connect(sid, 1U); if (handle <= 0) { - return PSA_ERROR_GENERIC_ERROR; + status = PSA_ERROR_GENERIC_ERROR; } - in_vec[0].base = buffer; - in_vec[0].len = sizeof(hdr) + len; - status = psa_call(handle, op, in_vec, 1U, NULL, 0U); - psa_close(handle); + else { + in_vec[0].base = buffer; + in_vec[0].len = sizeof(hdr) + len; + status = psa_call(handle, op, in_vec, 1U, NULL, 0U); + psa_close(handle); + } + wt_forceZero(buffer, sizeof(buffer)); return status; } From da417995f1f2e861e57dcb84c6577b4e41a8d3b0 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:34:52 -0700 Subject: [PATCH 11/27] F-14119 - Erase per-guest HSM relay buffers --- src/services/wolfhsm/wt_hsm.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/services/wolfhsm/wt_hsm.c b/src/services/wolfhsm/wt_hsm.c index ae60482a..325aefd7 100644 --- a/src/services/wolfhsm/wt_hsm.c +++ b/src/services/wolfhsm/wt_hsm.c @@ -334,6 +334,7 @@ int wt_hsm_rollback_image_floor(uint32_t* floor) * Forward declaration — tasklet body defined below. * ---------------------------------------------------------------------- */ static void wt_hsm_tasklet_main(void *arg); +static void wt_hsm_force_zero(void* memory, size_t size); /* ========================================================================= * wt_hsm_init @@ -672,7 +673,9 @@ static int wt_hsm_relay_srv_send(void* context, uint16_t size, static int wt_hsm_relay_srv_cleanup(void* context) { - (void)context; + if (context != NULL) { + wt_hsm_force_zero(context, sizeof(wt_hsm_relay_buf_t)); + } return WH_ERROR_OK; } @@ -716,6 +719,7 @@ int wt_hsm_relay_submit(void* submit_ctx, int32_t client_id, } g = &g_guests[gid]; buf = &g_relay_bufs[gid]; + *resp_len = 0U; if (!g->ready || g->transport_ctx != buf) { return WH_ERROR_NOTREADY; } @@ -751,15 +755,18 @@ int wt_hsm_relay_submit(void* submit_ctx, int32_t client_id, } } if (buf->resp_ready == 0u) { - buf->req_pending = 0u; - return (rc != WH_ERROR_OK) ? rc : WH_ERROR_ABORTED; + rc = (rc != WH_ERROR_OK) ? rc : WH_ERROR_ABORTED; } - if (buf->resp_len > resp_cap) { - return WH_ERROR_ABORTED; + else if (buf->resp_len > resp_cap) { + rc = WH_ERROR_ABORTED; } - (void)memcpy(resp, buf->resp, buf->resp_len); - *resp_len = buf->resp_len; - return WH_ERROR_OK; + else { + (void)memcpy(resp, buf->resp, buf->resp_len); + *resp_len = buf->resp_len; + rc = WH_ERROR_OK; + } + wt_hsm_force_zero(buf, sizeof(*buf)); + return rc; } /* ========================================================================= From 986ff51f572f436858211ef58320214da5d84521 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:35:18 -0700 Subject: [PATCH 12/27] F-14122 - Erase HSM state during relay recovery --- src/services/wolfhsm/wt_hsm.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/services/wolfhsm/wt_hsm.c b/src/services/wolfhsm/wt_hsm.c index 325aefd7..221097f5 100644 --- a/src/services/wolfhsm/wt_hsm.c +++ b/src/services/wolfhsm/wt_hsm.c @@ -905,6 +905,9 @@ int wt_hsm_relay_reinit_servers(void) * and tasklet stored in g persist — only the live contexts reset. */ (void)wh_Server_Cleanup(&g->server); (void)wc_FreeRng(g->crypto.rng); + wt_hsm_force_zero(&g_relay_bufs[gid], sizeof(g_relay_bufs[gid])); + wt_hsm_force_zero(&g->server, sizeof(g->server)); + wt_hsm_force_zero(&g->crypto, sizeof(g->crypto)); rc = wc_InitRng_ex(g->crypto.rng, NULL, INVALID_DEVID); if (rc == 0) { rc = wh_Server_Init(&g->server, &g->server_cfg); @@ -913,6 +916,8 @@ int wt_hsm_relay_reinit_servers(void) rc = wh_Server_SetConnected(&g->server, WH_COMM_CONNECTED); } if (rc != 0) { + wt_hsm_force_zero(&g->server, sizeof(g->server)); + wt_hsm_force_zero(&g->crypto, sizeof(g->crypto)); g->ready = false; break; } From 6a1f08311bb807d3d22a2c8a76729d8e63e4eb3c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:35:39 -0700 Subject: [PATCH 13/27] F-14123 - Erase HSM tasklet state on fault --- src/services/wolfhsm/wt_hsm.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/services/wolfhsm/wt_hsm.c b/src/services/wolfhsm/wt_hsm.c index 221097f5..e7c86ae6 100644 --- a/src/services/wolfhsm/wt_hsm.c +++ b/src/services/wolfhsm/wt_hsm.c @@ -941,10 +941,15 @@ int wt_hsm_signal_fault(wt_guest_id_t guest_id) wt_hsm_release_locks(g->tasklet); } - /* Tell the NS client. Failure here just means the transport was - * never wired (guest_id outside transport range) — still safe. */ + /* Notify through the optional port hook before erasing transport state. */ (void)g_hsm_fault_notify(guest_id); + wt_hsm_force_zero(&g_relay_bufs[guest_id], + sizeof(g_relay_bufs[guest_id])); + wt_hsm_force_zero(&g->server, sizeof(g->server)); + wt_hsm_force_zero(&g->crypto, sizeof(g->crypto)); + wt_hsm_force_zero(&g_co_stack_slots[guest_id], + sizeof(g_co_stack_slots[guest_id])); g->ready = false; return WH_ERROR_OK; } From c0901f9dc34c82b82168cbb7be5ed3f1bd7cc4b9 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:35:57 -0700 Subject: [PATCH 14/27] F-14095 - Correct HSM fault notification comments --- include/wolftrust/services/hsm.h | 13 +++++-------- port/stm32h563/platform_stm32h563.c | 4 ++-- src/arch/armv8m/sp_fault_armv8m.c | 6 +++--- src/services/wolfhsm/wt_hsm.c | 10 ++++------ 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/include/wolftrust/services/hsm.h b/include/wolftrust/services/hsm.h index c3a33707..2b5f576b 100644 --- a/include/wolftrust/services/hsm.h +++ b/include/wolftrust/services/hsm.h @@ -111,11 +111,9 @@ struct wt_co *wt_hsm_guest_tasklet(wt_guest_id_t guest_id); * dispatcher to map a faulted tasklet back to its NS client. */ wt_guest_id_t wt_hsm_guest_for_tasklet(const struct wt_co *tasklet); -/* Signal a terminal Secure-side fault for guest_id: drops the NVM lock - * if the dying tasklet was holding it, writes a WH_ERROR_ABORTED - * fatal-response into the guest's transport, and clears the ready bit - * so subsequent NSC veneers reject HSM calls from this guest. Safe to - * call from handler mode. Returns WH_ERROR_OK on success. */ +/* Signal a terminal Secure-side fault for guest_id: drop held locks, invoke + * the optional fault-notification hook, erase retained tasklet state, and + * clear the ready bit. Safe from handler mode. */ int wt_hsm_signal_fault(wt_guest_id_t guest_id); /* Drop every secure-side wolfHSM lock held by a faulted coroutine. Used by the @@ -134,9 +132,8 @@ struct wt_mutex *wt_hsm_nvm_lock_mutex(void); * state unusable. Fails closed — a guest whose re-init fails stays down. */ int wt_hsm_relay_reinit_servers(void); -/* Terminal-fault NS-client notifier. wt_hsm_signal_fault calls the installed - * callback; the arch transport installs its concrete notifier at boot. The - * default is a no-op so engine-less/host builds link. */ +/* Terminal-fault NS-client notifier. The default is a no-op, and no current + * port installs a replacement, so this path does not notify NS clients. */ typedef int (*wt_hsm_fault_notify_fn)(wt_guest_id_t guest_id); void wt_hsm_set_fault_notify(wt_hsm_fault_notify_fn fn); diff --git a/port/stm32h563/platform_stm32h563.c b/port/stm32h563/platform_stm32h563.c index a1f21af4..f84d7d2c 100644 --- a/port/stm32h563/platform_stm32h563.c +++ b/port/stm32h563/platform_stm32h563.c @@ -97,8 +97,8 @@ static const wt_armv8m_mpu_region_t g_mpu_s_whitelist[] = { WT_MPU_RLAR_ATTRIDX_NORMAL }, /* Region 3: NS RAM RW-NX. Secure code touches this through the - * 0x20000000 alias to exchange HSM transport buffers with guests - * and to write fault-response CSRs. */ + * 0x20000000 alias to validate and copy mediated service vectors. HSM + * tasklet faults do not write a response through this mapping. */ { WT_RAM_NS_BASE, WT_RAM_NS_BASE + 0x0001FFFFu, WT_MPU_RBAR_XN | WT_MPU_RBAR_AP_RW | WT_MPU_RBAR_SH_INNER, WT_MPU_RLAR_ATTRIDX_NORMAL }, diff --git a/src/arch/armv8m/sp_fault_armv8m.c b/src/arch/armv8m/sp_fault_armv8m.c index 1ca3a996..7cfa56f7 100644 --- a/src/arch/armv8m/sp_fault_armv8m.c +++ b/src/arch/armv8m/sp_fault_armv8m.c @@ -60,9 +60,9 @@ static volatile uint32_t g_tasklet_fault_co_sp; * Recovery model: * 1. C dispatcher logs the fault, identifies the running tasklet * (g_tasklet_current via wt_tasklet_current), maps it back to a guest_id, - * hands the NS client a WH_ERROR_ABORTED via wt_hsm_signal_fault, - * drops any mutex held by the dying tasklet, and marks the - * tasklet WT_TASKLET_FAULTED. + * invokes wt_hsm_signal_fault's optional notification hook (a no-op in + * current ports), drops any mutex held by the dying tasklet, erases its + * retained state, and marks the tasklet WT_TASKLET_FAULTED. * 2. The naked handler asm restores MSP_S to the bootstrap SP that * PendSV saved (r4-r11 push + preserved exception frame), pops * r4-r11, and EXC_RETURNs through the preserved bootstrap frame — diff --git a/src/services/wolfhsm/wt_hsm.c b/src/services/wolfhsm/wt_hsm.c index e7c86ae6..89ac8143 100644 --- a/src/services/wolfhsm/wt_hsm.c +++ b/src/services/wolfhsm/wt_hsm.c @@ -849,12 +849,10 @@ wt_guest_id_t wt_hsm_guest_for_tasklet(const struct wt_co *tasklet) /* ========================================================================= * wt_hsm_signal_fault * - * Called from the Secure fault dispatcher after wt_tasklet_mark_faulted has - * removed the tasklet from the scheduler. Drops any NVM lock the dying - * tasklet still held, writes a WH_ERROR_ABORTED fatal-response into - * the guest's transport so the NS client unblocks with a clean error, - * and clears the ready bit so future NSC veneers reject HSM calls from - * this guest. + * Called from the Secure fault dispatcher for a terminal tasklet fault. Drops + * any NVM lock the tasklet held, invokes the optional transport notification + * hook, erases retained tasklet state, and clears the ready bit. The default + * notification hook is a no-op, and no current port replaces it. * * Idempotent: calling on an already-faulted guest is harmless. * ====================================================================== */ From 8e4b5ab9122ee614ab9d8a533ffb355d178db8cb Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:36:35 -0700 Subject: [PATCH 15/27] F-14109 - Restore residual virtual SysTick period --- src/arch/armv8m/guest_context_armv8m.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/arch/armv8m/guest_context_armv8m.c b/src/arch/armv8m/guest_context_armv8m.c index fff85819..527d552e 100644 --- a/src/arch/armv8m/guest_context_armv8m.c +++ b/src/arch/armv8m/guest_context_armv8m.c @@ -98,6 +98,7 @@ static wt_virtual_systick_t g_guest_systick[WT_MAX_GUESTS]; * NS bank is restored lets the tick preempt the dispatch window and stack * through the new VTOR_NS onto the departing guest's MSP_NS. */ static volatile uint32_t g_arriving_systick_csr; +static volatile uint32_t g_arriving_systick_rvr; static volatile uint32_t g_arriving_systick_inject; void wt_arch_init(void) @@ -460,6 +461,7 @@ static void wt_virtual_systick_account_elapsed(wt_virtual_systick_t* systick) static void wt_virtual_systick_restore_arriving(wt_guest_id_t guest_id) { wt_virtual_systick_t* systick; + uint32_t first_reload; WT_SCB_ICSR_NS = WT_SCB_ICSR_PENDSTCLR; if (guest_id >= WT_MAX_GUESTS) { @@ -470,13 +472,24 @@ static void wt_virtual_systick_restore_arriving(wt_guest_id_t guest_id) wt_virtual_systick_account_elapsed(systick); WT_SYST_NS_CSR = 0u; - WT_SYST_NS_RVR = systick->rvr; - WT_SYST_NS_CVR = 0u; g_arriving_systick_csr = 0u; + g_arriving_systick_rvr = systick->rvr & 0x00FFFFFFu; g_arriving_systick_inject = 0u; if (wt_virtual_systick_active(systick)) { + first_reload = systick->cvr & 0x00FFFFFFu; + if (first_reload == 0u || first_reload > g_arriving_systick_rvr) { + first_reload = g_arriving_systick_rvr; + } + /* Load the accounted residual for the first resumed period. The + * normal reload is restored immediately after the counter is armed. */ + WT_SYST_NS_RVR = first_reload; + WT_SYST_NS_CVR = 0u; g_arriving_systick_csr = systick->csr & ~WT_SYST_CSR_COUNTFLAG; } + else { + WT_SYST_NS_RVR = g_arriving_systick_rvr; + WT_SYST_NS_CVR = 0u; + } if (systick->owed_ticks > 0u && wt_virtual_systick_irq_enabled(systick)) { if (!systick->pending) { @@ -496,10 +509,14 @@ static void wt_virtual_systick_arm_arriving(void) __attribute__((used)); static void wt_virtual_systick_arm_arriving(void) { uint32_t csr = g_arriving_systick_csr; + uint32_t reload = g_arriving_systick_rvr; g_arriving_systick_csr = 0u; + g_arriving_systick_rvr = 0u; if (csr != 0u) { WT_SYST_NS_CSR = csr; + __asm volatile("dsb\nisb" ::: "memory"); + WT_SYST_NS_RVR = reload; } if (g_arriving_systick_inject != 0u) { g_arriving_systick_inject = 0u; From e25079b69057c214a820ce087cada0e9f7f35819 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:37:00 -0700 Subject: [PATCH 16/27] F-14126 - Scrub restart-clear RAM before quarantine --- src/monitor.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/monitor.c b/src/monitor.c index 8c9c4080..a7dcac64 100644 --- a/src/monitor.c +++ b/src/monitor.c @@ -413,6 +413,10 @@ static void wt_restart_guest(wt_guest_id_t guest_id, wt_fault_reason_t reason) /* Quarantined or restarted, the guest will never close its handles. */ (void)wt_ffm_fail_client_connections(wt_ffm_boot_runtime_mut(), -(psa_client_id_t)(guest_id + 1U)); + restart_window = wt_find_restart_clear_window(config); + if (restart_window != NULL) { + wt_arch_zero_guest_memory(restart_window->base, restart_window->size); + } if (wt_restart_policy_evaluate(config->restart_policy.restart_limit, config->restart_policy.restart_window_ticks, g_scheduler.monotonic_ticks, @@ -431,10 +435,6 @@ static void wt_restart_guest(wt_guest_id_t guest_id, wt_fault_reason_t reason) runtime->remaining_delay_ticks = config->restart_policy.initial_delay_ticks; - restart_window = wt_find_restart_clear_window(config); - if (restart_window != NULL) { - wt_arch_zero_guest_memory(restart_window->base, restart_window->size); - } } static void wt_schedule_next_guest(void) From 0428c38e6d4ec24f402f4e9dc9528011ee84d622 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:37:03 -0700 Subject: [PATCH 17/27] F-14127 - Erase non-word-aligned guest RAM tails --- src/arch/armv8m/guest_context_armv8m.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/arch/armv8m/guest_context_armv8m.c b/src/arch/armv8m/guest_context_armv8m.c index 527d552e..377bf980 100644 --- a/src/arch/armv8m/guest_context_armv8m.c +++ b/src/arch/armv8m/guest_context_armv8m.c @@ -652,11 +652,10 @@ void wt_arch_guest_context_restore(wt_guest_context_t* context) void wt_arch_zero_guest_memory(uintptr_t base, size_t size) { - volatile uint32_t* ptr = (volatile uint32_t*)base; - size_t words = size / sizeof(uint32_t); + volatile uint8_t* ptr = (volatile uint8_t*)base; size_t i; - for (i = 0; i < words; ++i) { + for (i = 0U; i < size; ++i) { ptr[i] = 0u; } if (base == WT_GUEST0_RAM_BASE && size >= WT_GUEST_RAM_SIZE) { From 61dd3921570d1ecbe1dcbb5eda4fef333752d0ba Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:37:06 -0700 Subject: [PATCH 18/27] F-14108 - Release IPC resources on quarantine --- src/monitor.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/monitor.c b/src/monitor.c index a7dcac64..08035656 100644 --- a/src/monitor.c +++ b/src/monitor.c @@ -639,6 +639,8 @@ void wt_monitor_quarantine_guest(wt_guest_id_t guest_id) return; } + (void)wt_ffm_fail_client_connections(wt_ffm_boot_runtime_mut(), + -(psa_client_id_t)(guest_id + 1U)); runtime->state = WT_GUEST_FAULTED; g_wt_quarantine_events++; g_wt_launch_refused_mask |= (uint32_t)1U << guest_id; From b03ac65cb6d5e4f9f9c56840138e5932951d4211 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:37:29 -0700 Subject: [PATCH 19/27] F-14101 - Address the Non-secure NVIC banks --- src/arch/armv8m/irq_armv8m.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/arch/armv8m/irq_armv8m.c b/src/arch/armv8m/irq_armv8m.c index 016efdc2..64c2cea7 100644 --- a/src/arch/armv8m/irq_armv8m.c +++ b/src/arch/armv8m/irq_armv8m.c @@ -31,7 +31,7 @@ void wt_arch_mask_all_guest_irqs(void) { - volatile uint32_t* icer = (volatile uint32_t*)0xE000E180u; + volatile uint32_t* icer = (volatile uint32_t*)0xE002E180u; size_t i; for (i = 0; i < WT_MAX_IRQ_WORDS; ++i) { @@ -41,7 +41,7 @@ void wt_arch_mask_all_guest_irqs(void) void wt_arch_apply_irq_mask(const wt_irq_mask_t* mask) { - volatile uint32_t* iser = (volatile uint32_t*)0xE000E100u; + volatile uint32_t* iser = (volatile uint32_t*)0xE002E100u; size_t i; if (mask == NULL) { @@ -55,7 +55,7 @@ void wt_arch_apply_irq_mask(const wt_irq_mask_t* mask) void wt_arch_quarantine_pending_irqs(const wt_irq_mask_t* allowed_mask) { - volatile uint32_t* icpr = (volatile uint32_t*)0xE000E280u; + volatile uint32_t* icpr = (volatile uint32_t*)0xE002E280u; size_t i; if (allowed_mask == NULL) { From af64aaee12ff8d2934be83f8e3c729cdf6803cf6 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:37:32 -0700 Subject: [PATCH 20/27] F-14102 - Support the full Secure IRQ range --- src/arch/armv8m/irq_armv8m.c | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/arch/armv8m/irq_armv8m.c b/src/arch/armv8m/irq_armv8m.c index 64c2cea7..4cf876bf 100644 --- a/src/arch/armv8m/irq_armv8m.c +++ b/src/arch/armv8m/irq_armv8m.c @@ -69,42 +69,36 @@ void wt_arch_quarantine_pending_irqs(const wt_irq_mask_t* allowed_mask) void wt_arch_secure_irq_enable(uint32_t irq) { + volatile uint32_t* iser = (volatile uint32_t*)0xE000E100u; + volatile uint32_t* icpr = (volatile uint32_t*)0xE000E280u; + volatile uint32_t* itns = (volatile uint32_t*)0xE000E380u; uint32_t word = irq >> 5; uint32_t bit = irq & 31u; - if (word > 1u) + if (word >= WT_MAX_IRQ_WORDS) { return; + } /* Route to Secure, drop any stale pending, lowest priority so the line * never preempts the active SVC gate, then unmask. */ - if (word == 0u) { - WT_NVIC_ITNS0 &= ~(1u << bit); - WT_NVIC_ICPR0 = (1u << bit); - } else { - WT_NVIC_ITNS1 &= ~(1u << bit); - WT_NVIC_ICPR1 = (1u << bit); - } + itns[word] &= ~(1u << bit); + icpr[word] = (1u << bit); WT_NVIC_IPR_BASE[irq] = 0xFFu; __asm volatile("dsb\nisb" ::: "memory"); - if (word == 0u) - WT_NVIC_ISER0 = (1u << bit); - else - WT_NVIC_ISER1 = (1u << bit); + iser[word] = (1u << bit); } void wt_arch_secure_irq_disable(uint32_t irq) { + volatile uint32_t* icer = (volatile uint32_t*)0xE000E180u; + volatile uint32_t* icpr = (volatile uint32_t*)0xE000E280u; uint32_t word = irq >> 5; uint32_t bit = irq & 31u; - if (word > 1u) + if (word >= WT_MAX_IRQ_WORDS) { return; - if (word == 0u) { - WT_NVIC_ICER0 = (1u << bit); - WT_NVIC_ICPR0 = (1u << bit); - } else { - WT_NVIC_ICER1 = (1u << bit); - WT_NVIC_ICPR1 = (1u << bit); } + icer[word] = (1u << bit); + icpr[word] = (1u << bit); __asm volatile("dsb\nisb" ::: "memory"); } From ad5ebac0b908299b33467fea1d8bbef4c74658a8 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:38:08 -0700 Subject: [PATCH 21/27] F-14120 - Erase flash programming words --- port/stm32h563/hsm_flash.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/port/stm32h563/hsm_flash.c b/port/stm32h563/hsm_flash.c index 11f0d09e..638f9a02 100644 --- a/port/stm32h563/hsm_flash.c +++ b/port/stm32h563/hsm_flash.c @@ -28,6 +28,7 @@ #include "wolftrust/services/fwu_service.h" #include "wolftrust/spm_gate.h" #include "wolftrust/arch.h" +#include "wolftrust/zeroize.h" #include #include @@ -475,6 +476,7 @@ static int wt_hsm_flash_program(void *context, uint32_t offset, uint32_t size, g_wt_flash_first_err_sr = WT_FLASH_SR; } ret = wt_flash_check_errors(); + wt_forceZero(word, sizeof(word)); if (ret != WH_ERROR_OK) { break; } From b5e2219d38ec98b5a70de4561de8374ec79b6cfc Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:38:13 -0700 Subject: [PATCH 22/27] F-14121 - Erase flash read-back buffers --- port/stm32h563/hsm_flash.c | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/port/stm32h563/hsm_flash.c b/port/stm32h563/hsm_flash.c index 638f9a02..f1998dea 100644 --- a/port/stm32h563/hsm_flash.c +++ b/port/stm32h563/hsm_flash.c @@ -577,7 +577,7 @@ static int wt_hsm_flash_verify(void *context, uint32_t offset, uint32_t size, wt_hsm_flash_context_t *ctx = (wt_hsm_flash_context_t *)context; uint8_t flash_data[16]; uint32_t checked = 0u; - int ret; + int ret = WH_ERROR_OK; if (data == NULL && size != 0u) { return WH_ERROR_BADARGS; @@ -589,7 +589,7 @@ static int wt_hsm_flash_verify(void *context, uint32_t offset, uint32_t size, if (!wt_flash_range_ok(ctx, offset, size)) { return WH_ERROR_BADARGS; } - while (checked < size) { + while (checked < size && ret == WH_ERROR_OK) { uint32_t chunk = size - checked; if (chunk > sizeof(flash_data)) { @@ -598,15 +598,16 @@ static int wt_hsm_flash_verify(void *context, uint32_t offset, uint32_t size, ret = wt_flash_read_checked( (const uint8_t *)(ctx->base + offset + checked), flash_data, chunk); - if (ret != WH_ERROR_OK) { - return ret; + if (ret == WH_ERROR_OK && + memcmp(flash_data, data + checked, chunk) != 0) { + ret = WH_ERROR_NOTVERIFIED; } - if (memcmp(flash_data, data + checked, chunk) != 0) { - return WH_ERROR_NOTVERIFIED; + if (ret == WH_ERROR_OK) { + checked += chunk; } - checked += chunk; } - return WH_ERROR_OK; + wt_forceZero(flash_data, sizeof(flash_data)); + return ret; } static int wt_hsm_flash_blank_check(void *context, uint32_t offset, @@ -616,7 +617,7 @@ static int wt_hsm_flash_blank_check(void *context, uint32_t offset, uint8_t flash_data[16]; uint32_t checked = 0u; uint32_t i; - int ret; + int ret = WH_ERROR_OK; if (wt_arch_thread_unprivileged()) { return wt_hsm_flash_gate(context, WT_SPM_KS_FLASH_BLANKCHECK, offset, @@ -625,7 +626,7 @@ static int wt_hsm_flash_blank_check(void *context, uint32_t offset, if (!wt_flash_range_ok(ctx, offset, size)) { return WH_ERROR_BADARGS; } - while (checked < size) { + while (checked < size && ret == WH_ERROR_OK) { uint32_t chunk = size - checked; if (chunk > sizeof(flash_data)) { @@ -634,17 +635,17 @@ static int wt_hsm_flash_blank_check(void *context, uint32_t offset, ret = wt_flash_read_checked( (const uint8_t *)(ctx->base + offset + checked), flash_data, chunk); - if (ret != WH_ERROR_OK) { - return ret; - } - for (i = 0u; i < chunk; i++) { + for (i = 0u; i < chunk && ret == WH_ERROR_OK; i++) { if (flash_data[i] != 0xFFu) { - return WH_ERROR_NOTBLANK; + ret = WH_ERROR_NOTBLANK; } } - checked += chunk; + if (ret == WH_ERROR_OK) { + checked += chunk; + } } - return WH_ERROR_OK; + wt_forceZero(flash_data, sizeof(flash_data)); + return ret; } const whFlashCb g_wt_hsm_flash_cb = { From 73978315a79ec1006cc41a0dcb6c0c1c12830a4d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:38:37 -0700 Subject: [PATCH 23/27] F-14117 - Pad unaligned firmware update writes --- docs/API-Reference.md | 6 +-- docs/Services.md | 10 ++--- docs/TF-M-Compatibility.md | 4 +- src/services/fwu_service.c | 84 +++++++++++++++++++++++++++----------- 4 files changed, 71 insertions(+), 33 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index f00c4ee4..66cda8b0 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -476,9 +476,9 @@ monotonic version. The current service supports one primary component and commits installation at authenticated reboot. It does not offer a persistent trial state, so `psa_fwu_accept` returns `PSA_ERROR_NOT_SUPPORTED`. -Unlike PSA Firmware Update 1.0, the service rejects unaligned block sizes rather -than padding them and returns `PSA_ERROR_INVALID_ARGUMENT` for unknown -component IDs instead of `PSA_ERROR_DOES_NOT_EXIST`. +The service pads an unaligned block size to the backend write alignment. It +returns `PSA_ERROR_INVALID_ARGUMENT` for unknown component IDs instead of +`PSA_ERROR_DOES_NOT_EXIST`. ## Initial Attestation diff --git a/docs/Services.md b/docs/Services.md index a5d93b9b..5221ffbc 100644 --- a/docs/Services.md +++ b/docs/Services.md @@ -142,16 +142,16 @@ exported. ## Firmware Update `SERVICE_FWU` exposes a single-component subset of PSA Firmware Update 1.0. -Unlike the specification, it rejects unaligned block sizes instead of padding -them and returns `PSA_ERROR_INVALID_ARGUMENT` rather than -`PSA_ERROR_DOES_NOT_EXIST` for unknown component IDs. The normal flow is: +It pads an unaligned block size to the backend write alignment and returns +`PSA_ERROR_INVALID_ARGUMENT` rather than `PSA_ERROR_DOES_NOT_EXIST` for an +unknown component ID. The normal flow is: ```text READY -> WRITING -> CANDIDATE -> STAGED -> authenticated reboot ``` -Writes are copied, bounded, aligned to the target flash granularity, and staged -in the wolfBoot update partition. Finish validates the complete wolfBoot image +Writes are copied, bounded, offset-aligned, padded with erased-flash bytes when +needed, and staged in the wolfBoot update partition. Finish validates the image header and binds the candidate version. Install checks the candidate again against the persistent version floor loaded when the Firmware Update partition started, then writes the wolfBoot update trigger. A reboot request is allowed diff --git a/docs/TF-M-Compatibility.md b/docs/TF-M-Compatibility.md index a40099e5..b224c756 100644 --- a/docs/TF-M-Compatibility.md +++ b/docs/TF-M-Compatibility.md @@ -22,7 +22,7 @@ algorithm or feature is enabled in every build. | Internal Trusted Storage | 1.0 | Core set/get/get-info/remove subset with the `WRITE_ONCE` lifecycle deviation below | `include/psa/internal_trusted_storage.h` and `src/services/wolfhsm/wt_hsm_vault.c` | | Protected Storage | 1.0 | Core set/get/get-info/remove subset; optional create/set-extended absent and the `WRITE_ONCE` lifecycle deviation below applies | `include/psa/protected_storage.h` and `src/services/storage_service.c` | | Initial Attestation | 1.0 API subset with a nonconformant RFC 9783-derived token | Token and exact-size operations are supported, but the advertised TF-M profile has the claim-semantic deviations below | `lib/wolfPSA/wolfpsa/psa/initial_attestation.h` and `src/services/initial_attestation.c` | -| Firmware Update | 1.0 subset | Single-component staging and authenticated reboot supported, with the alignment and status deviations below | `include/psa/update.h` and `src/services/fwu_service.c` | +| Firmware Update | 1.0 subset | Single-component staging and authenticated reboot supported, with the status deviation below | `include/psa/update.h` and `src/services/fwu_service.c` | | RoT lifecycle query | FF-M 1.0 | Secure Partition only; there is no Non-secure adapter or veneer | `include/psa/lifecycle.h` and `src/arch/armv8m/spm_sp_api.c` | | Secure Partition signals and IRQ APIs | FF-M 1.0 plus one wolfTrust-specific beta-extension backport | The 1.0 signal APIs and `psa_eoi` are supported; only `psa_irq_enable()` is backported from the FF-M 1.1 Extension Beta, Issue 0, while `psa_irq_status_t`, `psa_irq_is_enabled`, `psa_irq_disable`, and `psa_irq_restore` are absent | `include/psa/service.h` and the Armv8-M SVC implementation | | Guest identity | FF-M convention | Non-secure guest `N` is client `-(N + 1)` | `src/arch/armv8m/ffm_nsc.c` | @@ -47,7 +47,7 @@ algorithm or feature is enabled in every build. | The attestation token advertises `tag:psacertified.org,2023:psa#tfm` but does not implement that profile's claim semantics. | Known token-profile deviation | The boot seed is deterministic across equivalent boots; software-component measurement type and description values are reversed; signer ID hashes the literal name `wolfBoot` rather than identifying the signing key; and implementation ID hashes a software label rather than identifying the immutable PSA RoT hardware assembly. A distinct derived profile identifier is required until these claims conform to [RFC 9783](https://www.rfc-editor.org/rfc/rfc9783.html). | | Firmware Update has no persistent trial-accept flow. | Scoped | Installation commits only after wolfBoot authenticates the swapped image at reboot; `psa_fwu_accept()` returns `PSA_ERROR_NOT_SUPPORTED`. | | The firmware-update detached manifest is a 32-bit version word. | Scoped integration | Passing `NULL, 0` instead binds the version from the staged wolfBoot header. Other manifest encodings require an adapter. | -| Firmware Update rejects unaligned block sizes and reports unknown component IDs as `PSA_ERROR_INVALID_ARGUMENT`. | Known API deviations | PSA Firmware Update 1.0 pads unaligned final block sizes and specifies `PSA_ERROR_DOES_NOT_EXIST` for unknown component IDs. | +| Firmware Update reports unknown component IDs as `PSA_ERROR_INVALID_ARGUMENT`. | Known API deviation | PSA Firmware Update 1.0 specifies `PSA_ERROR_DOES_NOT_EXIST` for unknown component IDs. Unaligned block sizes are padded to the backend write alignment. | | Secure memory uses no dynamic allocation. | Stronger resource policy | Fixed pools and buffers can reject excess work rather than expanding at runtime. | | Manifests use wolfTrust JSON and generated C. | Integration difference | Existing TF-M manifests are not consumed directly. Security resources and services must be represented in the wolfTrust schema. | | Secure Partition entry functions are bound at build time instead of being selected by each manifest's `entry_point` field. | Integration difference | The numeric field validates an executable window, but adding a service also requires a compiled entry wrapper and an explicit start call in `wt_ffm_boot_start_sched()`. | diff --git a/src/services/fwu_service.c b/src/services/fwu_service.c index 5221d814..7113aa4a 100644 --- a/src/services/fwu_service.c +++ b/src/services/fwu_service.c @@ -19,6 +19,7 @@ */ #include "wolftrust/services/fwu_service.h" +#include "wolftrust/zeroize.h" #include @@ -81,41 +82,78 @@ psa_status_t wt_fwu_start(wt_fwu_service_ctx_t* ctx, uint32_t component, psa_status_t wt_fwu_write(wt_fwu_service_ctx_t* ctx, uint32_t component, uint32_t offset, const uint8_t* data, uint32_t size) { - uint32_t end; + uint8_t tail[WT_FWU_BLOCK_MAX]; + uint32_t align = 1u; + uint32_t end = 0u; + uint32_t prefix = 0u; + uint32_t remainder = 0u; + uint32_t padding = 0u; + psa_status_t status = PSA_SUCCESS; if (ctx == NULL || ctx->backend == NULL) { - return PSA_ERROR_BAD_STATE; + status = PSA_ERROR_BAD_STATE; } - if (!wt_fwu_component_ok(component)) { - return PSA_ERROR_INVALID_ARGUMENT; + else if (!wt_fwu_component_ok(component) || data == NULL || size == 0u) { + status = PSA_ERROR_INVALID_ARGUMENT; } - if (ctx->state != PSA_FWU_WRITING) { - return PSA_ERROR_BAD_STATE; - } - if (data == NULL || size == 0u) { - return PSA_ERROR_INVALID_ARGUMENT; - } - /* Reject an oversize or wrapping block before it is programmed. */ - end = offset + size; - if (end < offset || end > ctx->backend->capacity) { - return PSA_ERROR_INVALID_ARGUMENT; + else if (ctx->state != PSA_FWU_WRITING) { + status = PSA_ERROR_BAD_STATE; } - if (ctx->backend->align > 1u && - (((offset % ctx->backend->align) != 0u) || - ((size % ctx->backend->align) != 0u))) { - return PSA_ERROR_INVALID_ARGUMENT; + else { + align = ctx->backend->align; + if (align == 0u) { + align = 1u; + } + end = offset + size; + if (end < offset || end > ctx->backend->capacity || + (offset % align) != 0u) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + else { + remainder = size % align; + if (remainder != 0u) { + padding = align - remainder; + prefix = size - remainder; + if (align > sizeof(tail) || end > UINT32_MAX - padding || + end + padding > ctx->backend->capacity) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + else { + if (prefix != 0u && ctx->backend->write != NULL && + ctx->backend->write(ctx->backend_ctx, offset, + data, prefix) != 0) { + status = PSA_ERROR_STORAGE_FAILURE; + } + if (status == PSA_SUCCESS) { + (void)memset(tail, 0xFF, align); + (void)memcpy(tail, data + prefix, remainder); + if (ctx->backend->write != NULL && + ctx->backend->write(ctx->backend_ctx, + offset + prefix, tail, + align) != 0) { + status = PSA_ERROR_STORAGE_FAILURE; + } + } + } + } + else if (ctx->backend->write != NULL && + ctx->backend->write(ctx->backend_ctx, offset, data, + size) != 0) { + status = PSA_ERROR_STORAGE_FAILURE; + } + } } - if (ctx->backend->write != NULL && - ctx->backend->write(ctx->backend_ctx, offset, data, size) != 0) { + if (status == PSA_ERROR_STORAGE_FAILURE) { /* A partial program invalidates the candidate: never arm it. */ ctx->state = PSA_FWU_FAILED; ctx->error = PSA_ERROR_STORAGE_FAILURE; - return PSA_ERROR_STORAGE_FAILURE; } - if (end > ctx->write_high) { + else if (status == PSA_SUCCESS && end > ctx->write_high) { + /* Logical image size excludes backend padding bytes. */ ctx->write_high = end; } - return PSA_SUCCESS; + wt_forceZero(tail, sizeof(tail)); + return status; } psa_status_t wt_fwu_finish(wt_fwu_service_ctx_t* ctx, uint32_t component) From fda8cbd297699e9f80cbd491af8ac71e97a5f382 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:38:45 -0700 Subject: [PATCH 24/27] F-14107 - Preserve FWU state when disarm fails --- src/services/fwu_service.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/services/fwu_service.c b/src/services/fwu_service.c index 7113aa4a..d30b5713 100644 --- a/src/services/fwu_service.c +++ b/src/services/fwu_service.c @@ -211,15 +211,17 @@ psa_status_t wt_fwu_install(wt_fwu_service_ctx_t* ctx) if (ctx->state != PSA_FWU_CANDIDATE) { return PSA_ERROR_BAD_STATE; } + if (ctx->backend->arm == NULL || ctx->backend->disarm == NULL) { + return PSA_ERROR_BAD_STATE; + } /* Final anti-rollback guard immediately before the swap is armed. */ if (ctx->candidate_version < ctx->version_floor) { ctx->state = PSA_FWU_FAILED; ctx->error = PSA_ERROR_NOT_PERMITTED; return PSA_ERROR_NOT_PERMITTED; } - if (ctx->backend->arm != NULL && - ctx->backend->arm(ctx->backend_ctx, ctx->write_high, - ctx->candidate_version) != 0) { + if (ctx->backend->arm(ctx->backend_ctx, ctx->write_high, + ctx->candidate_version) != 0) { /* Arming failed: stay a candidate, no swap pending. */ return PSA_ERROR_STORAGE_FAILURE; } @@ -404,11 +406,12 @@ int wt_fwu_owner_expired(psa_client_id_t owner, uint32_t owner_tick, /* Reclaim an abandoned session: disarm any pending swap and return to READY so * a new client may start. Never advances the version floor or arms a swap. */ -static void wt_fwu_force_reset(wt_fwu_service_ctx_t* ctx) +static psa_status_t wt_fwu_force_reset(wt_fwu_service_ctx_t* ctx) { - if (ctx->armed != 0u && ctx->backend != NULL && - ctx->backend->disarm != NULL) { - (void)ctx->backend->disarm(ctx->backend_ctx); + if (ctx->armed != 0u && + (ctx->backend == NULL || ctx->backend->disarm == NULL || + ctx->backend->disarm(ctx->backend_ctx) != 0)) { + return PSA_ERROR_STORAGE_FAILURE; } ctx->state = PSA_FWU_READY; ctx->write_high = 0u; @@ -416,6 +419,7 @@ static void wt_fwu_force_reset(wt_fwu_service_ctx_t* ctx) ctx->armed = 0u; ctx->error = PSA_SUCCESS; ctx->owner = 0; + return PSA_SUCCESS; } static psa_status_t wt_fwu_service_call(wt_fwu_service_ctx_t* ctx, @@ -449,7 +453,10 @@ static psa_status_t wt_fwu_service_call(wt_fwu_service_ctx_t* ctx, * (DoS). An active owner refreshes its clock on every op below. */ if (wt_fwu_owner_expired(ctx->owner, ctx->owner_tick, now_tick, msg->client_id)) { - wt_fwu_force_reset(ctx); + status = wt_fwu_force_reset(ctx); + if (status != PSA_SUCCESS) { + return status; + } } /* Per-transaction owner: only the client that opened the update (START) From 68447afb98c146c36542d05a266f0da35c34195a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:39:08 -0700 Subject: [PATCH 25/27] F-14099 - Erase vault request and plaintext buffers --- src/services/vault_service.c | 352 ++++++++++++++++++----------------- 1 file changed, 185 insertions(+), 167 deletions(-) diff --git a/src/services/vault_service.c b/src/services/vault_service.c index 1c8a38ae..f144032a 100644 --- a/src/services/vault_service.c +++ b/src/services/vault_service.c @@ -19,6 +19,7 @@ */ #include "wolftrust/services/vault_service.h" +#include "wolftrust/zeroize.h" #include @@ -252,175 +253,192 @@ static psa_status_t wt_vault_service_call(wt_ffm_runtime_t* runtime, size_t cap; psa_status_t status; - if (msg->in_size[0] != sizeof(req)) { - return PSA_ERROR_INVALID_ARGUMENT; - } - if (wt_vault_read_vec(runtime, partition_id, msg->handle, 0U, - (uint8_t*)&req, sizeof(req), &req_len) != - WT_FFM_SUCCESS || req_len != sizeof(req)) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - switch (msg->type) { - case WT_VAULT_OP_SET: - if (msg->in_size[1] > sizeof(data)) { - return PSA_ERROR_INSUFFICIENT_STORAGE; - } - if (wt_vault_read_vec(runtime, partition_id, msg->handle, 1U, data, - sizeof(data), &data_len) != WT_FFM_SUCCESS) { - return PSA_ERROR_INVALID_ARGUMENT; - } - status = g_vault_backend->set(msg->client_id, req.sub_owner, req.uid, - req.flags, data, data_len); - break; - case WT_VAULT_OP_GET: - /* A caller buffer larger than the object bound is legal PSA usage; - * clamp to the copied-transfer buffer — no object exceeds it. */ - data_len = msg->out_size[0]; - if (data_len > sizeof(data)) { - data_len = sizeof(data); - } - status = g_vault_backend->get(msg->client_id, req.sub_owner, req.uid, - req.offset, data, data_len, &out_len); - if (status == PSA_SUCCESS && - wt_vault_write_vec(runtime, partition_id, msg->handle, 0U, - data, out_len) != WT_FFM_SUCCESS) { - status = PSA_ERROR_GENERIC_ERROR; - } - break; - case WT_VAULT_OP_GET_INFO: - if (msg->out_size[0] < sizeof(info)) { - return PSA_ERROR_INVALID_ARGUMENT; - } - status = g_vault_backend->get_info(msg->client_id, req.sub_owner, - req.uid, &info); - if (status == PSA_SUCCESS && - wt_vault_write_vec(runtime, partition_id, msg->handle, 0U, - &info, sizeof(info)) != WT_FFM_SUCCESS) { - status = PSA_ERROR_GENERIC_ERROR; - } - break; - case WT_VAULT_OP_REMOVE: - status = g_vault_backend->remove(msg->client_id, req.sub_owner, - req.uid); - break; - case WT_VAULT_OP_KEY_GENERATE: - /* Key ops carry type in reserved and usage in flags. */ - status = g_vault_key_backend->generate(msg->client_id, req.sub_owner, - req.uid, req.reserved, - req.flags); - break; - case WT_VAULT_OP_KEY_IMPORT: - if (msg->in_size[1] > sizeof(data)) { - return PSA_ERROR_INVALID_ARGUMENT; - } - if (wt_vault_read_vec(runtime, partition_id, msg->handle, 1U, data, - sizeof(data), &data_len) != WT_FFM_SUCCESS) { - return PSA_ERROR_INVALID_ARGUMENT; - } - status = g_vault_key_backend->import(msg->client_id, req.sub_owner, - req.uid, req.reserved, req.flags, - data, data_len); - break; - case WT_VAULT_OP_KEY_EXPORT_PUBLIC: - cap = msg->out_size[0]; - if (cap > sizeof(out)) { - cap = sizeof(out); - } - status = g_vault_key_backend->export_public(msg->client_id, - req.sub_owner, req.uid, - out, cap, &out_len); - if (status == PSA_SUCCESS && - wt_vault_write_vec(runtime, partition_id, msg->handle, 0U, - out, out_len) != WT_FFM_SUCCESS) { - status = PSA_ERROR_GENERIC_ERROR; - } - break; - case WT_VAULT_OP_KEY_SIGN: - if (msg->in_size[1] > sizeof(data)) { - return PSA_ERROR_INVALID_ARGUMENT; - } - if (wt_vault_read_vec(runtime, partition_id, msg->handle, 1U, data, - sizeof(data), &data_len) != WT_FFM_SUCCESS) { - return PSA_ERROR_INVALID_ARGUMENT; - } - cap = msg->out_size[0]; - if (cap > sizeof(out)) { - cap = sizeof(out); - } - status = g_vault_key_backend->sign(msg->client_id, req.sub_owner, - req.uid, data, data_len, out, cap, - &out_len); - if (status == PSA_SUCCESS && - wt_vault_write_vec(runtime, partition_id, msg->handle, 0U, - out, out_len) != WT_FFM_SUCCESS) { - status = PSA_ERROR_GENERIC_ERROR; - } - break; - case WT_VAULT_OP_KEY_VERIFY: - /* invec[1] = [digest][raw r||s signature]. */ - if (msg->in_size[1] > sizeof(data)) { - return PSA_ERROR_INVALID_ARGUMENT; - } - if (wt_vault_read_vec(runtime, partition_id, msg->handle, 1U, data, - sizeof(data), &data_len) != WT_FFM_SUCCESS || - data_len <= WT_VAULT_KEY_SIG_LEN) { - return PSA_ERROR_INVALID_ARGUMENT; - } - status = g_vault_key_backend->verify(msg->client_id, req.sub_owner, - req.uid, data, - data_len - WT_VAULT_KEY_SIG_LEN, - data + data_len - - WT_VAULT_KEY_SIG_LEN, - WT_VAULT_KEY_SIG_LEN); - break; - case WT_VAULT_OP_KEY_ENCRYPT: - case WT_VAULT_OP_KEY_DECRYPT: - if (msg->in_size[1] > sizeof(data)) { - return PSA_ERROR_INVALID_ARGUMENT; - } - if (wt_vault_read_vec(runtime, partition_id, msg->handle, 1U, data, - sizeof(data), &data_len) != WT_FFM_SUCCESS) { - return PSA_ERROR_INVALID_ARGUMENT; - } - cap = msg->out_size[0]; - if (cap > sizeof(out)) { - cap = sizeof(out); - } - if (msg->type == WT_VAULT_OP_KEY_ENCRYPT) { - status = g_vault_key_backend->encrypt(msg->client_id, - req.sub_owner, req.uid, - data, data_len, out, cap, - &out_len); - } - else { - status = g_vault_key_backend->decrypt(msg->client_id, - req.sub_owner, req.uid, - data, data_len, out, cap, - &out_len); - } - if (status == PSA_SUCCESS && - wt_vault_write_vec(runtime, partition_id, msg->handle, 0U, - out, out_len) != WT_FFM_SUCCESS) { - status = PSA_ERROR_GENERIC_ERROR; - } - break; - case WT_VAULT_OP_RANDOM: - cap = msg->out_size[0]; - if (cap == 0U || cap > WT_VAULT_RANDOM_MAX) { - return PSA_ERROR_INVALID_ARGUMENT; - } - status = g_vault_rng(out, cap); - if (status == PSA_SUCCESS && - wt_vault_write_vec(runtime, partition_id, msg->handle, 0U, - out, cap) != WT_FFM_SUCCESS) { - status = PSA_ERROR_GENERIC_ERROR; + status = PSA_ERROR_INVALID_ARGUMENT; + if (msg->in_size[0] == sizeof(req) && + wt_vault_read_vec(runtime, partition_id, msg->handle, 0U, + (uint8_t*)&req, sizeof(req), &req_len) == + WT_FFM_SUCCESS && req_len == sizeof(req)) { + switch (msg->type) { + case WT_VAULT_OP_SET: + if (msg->in_size[1] > sizeof(data)) { + status = PSA_ERROR_INSUFFICIENT_STORAGE; + } + else if (wt_vault_read_vec(runtime, partition_id, msg->handle, 1U, + data, sizeof(data), &data_len) != + WT_FFM_SUCCESS) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + else { + status = g_vault_backend->set(msg->client_id, req.sub_owner, + req.uid, req.flags, data, data_len); + } + break; + case WT_VAULT_OP_GET: + /* A caller buffer larger than the object bound is legal PSA usage. + * Clamp it because no object exceeds the transfer buffer. */ + data_len = msg->out_size[0]; + if (data_len > sizeof(data)) { + data_len = sizeof(data); + } + status = g_vault_backend->get(msg->client_id, req.sub_owner, req.uid, + req.offset, data, data_len, &out_len); + if (status == PSA_SUCCESS && + wt_vault_write_vec(runtime, partition_id, msg->handle, 0U, + data, out_len) != WT_FFM_SUCCESS) { + status = PSA_ERROR_GENERIC_ERROR; + } + break; + case WT_VAULT_OP_GET_INFO: + if (msg->out_size[0] < sizeof(info)) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + else { + status = g_vault_backend->get_info(msg->client_id, req.sub_owner, + req.uid, &info); + if (status == PSA_SUCCESS && + wt_vault_write_vec(runtime, partition_id, msg->handle, 0U, + &info, sizeof(info)) != + WT_FFM_SUCCESS) { + status = PSA_ERROR_GENERIC_ERROR; + } + } + break; + case WT_VAULT_OP_REMOVE: + status = g_vault_backend->remove(msg->client_id, req.sub_owner, + req.uid); + break; + case WT_VAULT_OP_KEY_GENERATE: + /* Key ops carry type in reserved and usage in flags. */ + status = g_vault_key_backend->generate(msg->client_id, req.sub_owner, + req.uid, req.reserved, + req.flags); + break; + case WT_VAULT_OP_KEY_IMPORT: + if (msg->in_size[1] > sizeof(data)) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + else if (wt_vault_read_vec(runtime, partition_id, msg->handle, 1U, + data, sizeof(data), &data_len) != + WT_FFM_SUCCESS) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + else { + status = g_vault_key_backend->import(msg->client_id, + req.sub_owner, req.uid, + req.reserved, req.flags, + data, data_len); + } + break; + case WT_VAULT_OP_KEY_EXPORT_PUBLIC: + cap = msg->out_size[0]; + if (cap > sizeof(out)) { + cap = sizeof(out); + } + status = g_vault_key_backend->export_public(msg->client_id, + req.sub_owner, req.uid, + out, cap, &out_len); + if (status == PSA_SUCCESS && + wt_vault_write_vec(runtime, partition_id, msg->handle, 0U, + out, out_len) != WT_FFM_SUCCESS) { + status = PSA_ERROR_GENERIC_ERROR; + } + break; + case WT_VAULT_OP_KEY_SIGN: + if (msg->in_size[1] > sizeof(data)) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + else if (wt_vault_read_vec(runtime, partition_id, msg->handle, 1U, + data, sizeof(data), &data_len) != + WT_FFM_SUCCESS) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + else { + cap = msg->out_size[0]; + if (cap > sizeof(out)) { + cap = sizeof(out); + } + status = g_vault_key_backend->sign(msg->client_id, req.sub_owner, + req.uid, data, data_len, out, + cap, &out_len); + if (status == PSA_SUCCESS && + wt_vault_write_vec(runtime, partition_id, msg->handle, 0U, + out, out_len) != WT_FFM_SUCCESS) { + status = PSA_ERROR_GENERIC_ERROR; + } + } + break; + case WT_VAULT_OP_KEY_VERIFY: + /* invec[1] = [digest][raw r||s signature]. */ + if (msg->in_size[1] > sizeof(data)) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + else if (wt_vault_read_vec(runtime, partition_id, msg->handle, 1U, + data, sizeof(data), &data_len) != + WT_FFM_SUCCESS || + data_len <= WT_VAULT_KEY_SIG_LEN) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + else { + status = g_vault_key_backend->verify( + msg->client_id, req.sub_owner, req.uid, data, + data_len - WT_VAULT_KEY_SIG_LEN, + data + data_len - WT_VAULT_KEY_SIG_LEN, + WT_VAULT_KEY_SIG_LEN); + } + break; + case WT_VAULT_OP_KEY_ENCRYPT: + case WT_VAULT_OP_KEY_DECRYPT: + if (msg->in_size[1] > sizeof(data)) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + else if (wt_vault_read_vec(runtime, partition_id, msg->handle, 1U, + data, sizeof(data), &data_len) != + WT_FFM_SUCCESS) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + else { + cap = msg->out_size[0]; + if (cap > sizeof(out)) { + cap = sizeof(out); + } + if (msg->type == WT_VAULT_OP_KEY_ENCRYPT) { + status = g_vault_key_backend->encrypt( + msg->client_id, req.sub_owner, req.uid, data, data_len, + out, cap, &out_len); + } + else { + status = g_vault_key_backend->decrypt( + msg->client_id, req.sub_owner, req.uid, data, data_len, + out, cap, &out_len); + } + if (status == PSA_SUCCESS && + wt_vault_write_vec(runtime, partition_id, msg->handle, 0U, + out, out_len) != WT_FFM_SUCCESS) { + status = PSA_ERROR_GENERIC_ERROR; + } + } + break; + case WT_VAULT_OP_RANDOM: + cap = msg->out_size[0]; + if (cap == 0U || cap > WT_VAULT_RANDOM_MAX) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + else { + status = g_vault_rng(out, cap); + if (status == PSA_SUCCESS && + wt_vault_write_vec(runtime, partition_id, msg->handle, 0U, + out, cap) != WT_FFM_SUCCESS) { + status = PSA_ERROR_GENERIC_ERROR; + } + } + break; + default: + status = PSA_ERROR_NOT_SUPPORTED; + break; } - break; - default: - status = PSA_ERROR_NOT_SUPPORTED; - break; } + wt_forceZero(data, sizeof(data)); + wt_forceZero(out, sizeof(out)); return status; } From 2e998438223bcd5a039530cbf6f1b428265e138b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:39:12 -0700 Subject: [PATCH 26/27] F-14100 - Erase storage request and plaintext buffers --- src/services/storage_service.c | 177 +++++++++++++++++---------------- 1 file changed, 92 insertions(+), 85 deletions(-) diff --git a/src/services/storage_service.c b/src/services/storage_service.c index 51dabf89..c4c9138b 100644 --- a/src/services/storage_service.c +++ b/src/services/storage_service.c @@ -19,6 +19,7 @@ */ #include "wolftrust/services/storage_service.h" +#include "wolftrust/zeroize.h" #include @@ -169,98 +170,104 @@ static psa_status_t wt_storage_service_call(wt_storage_service_ctx_t* ctx, size_t cap; psa_status_t status; - if (msg->in_size[0] < sizeof(req)) { - return PSA_ERROR_INVALID_ARGUMENT; - } - if (msg->in_size[0] > sizeof(buffer)) { - return PSA_ERROR_INSUFFICIENT_STORAGE; - } - if (wt_storage_read_req(ctx, runtime, partition_id, msg->handle, buffer, - sizeof(buffer), &in_len) != WT_FFM_SUCCESS || - in_len < sizeof(req)) { - return PSA_ERROR_INVALID_ARGUMENT; - } - (void)memcpy(&req, buffer, sizeof(req)); - /* PSA Storage: uid 0 is invalid for every operation. */ - if (req.uid == 0U) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - /* Optional PS features (create/set_extended): psa_ps_get_support - * advertises none (ctx->caps stays 0), so these are refused honestly — - * a build that raises caps must implement them first. */ - if (msg->type == WT_PS_OP_CREATE || msg->type == WT_PS_OP_SET_EXTENDED) { - return PSA_ERROR_NOT_SUPPORTED; - } - - status = wt_storage_vault_handle(ctx, runtime, partition_id); - if (status != PSA_SUCCESS) { - return status; + status = PSA_ERROR_INVALID_ARGUMENT; + if (msg->in_size[0] >= sizeof(req)) { + if (msg->in_size[0] > sizeof(buffer)) { + status = PSA_ERROR_INSUFFICIENT_STORAGE; + } + else if (wt_storage_read_req(ctx, runtime, partition_id, msg->handle, + buffer, sizeof(buffer), &in_len) != + WT_FFM_SUCCESS || in_len < sizeof(req)) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + else { + (void)memcpy(&req, buffer, sizeof(req)); + /* PSA Storage: uid 0 is invalid for every operation. */ + if (req.uid == 0U) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + /* Optional PS features (create/set_extended): psa_ps_get_support + * advertises none, so refuse them until implemented. */ + else if (msg->type == WT_PS_OP_CREATE || + msg->type == WT_PS_OP_SET_EXTENDED) { + status = PSA_ERROR_NOT_SUPPORTED; + } + else { + status = wt_storage_vault_handle(ctx, runtime, partition_id); + } + } } - /* The vault namespaces by (this partition, end client, uid): the - * SPM-stamped message client id is the delegated sub_owner. */ - (void)memset(&vreq, 0, sizeof(vreq)); - vreq.uid = req.uid; - vreq.flags = req.flags; - vreq.offset = req.offset; - vreq.sub_owner = msg->client_id; + if (status == PSA_SUCCESS) { + /* The vault namespaces by (this partition, end client, uid): the + * SPM-stamped message client id is the delegated sub_owner. */ + (void)memset(&vreq, 0, sizeof(vreq)); + vreq.uid = req.uid; + vreq.flags = req.flags; + vreq.offset = req.offset; + vreq.sub_owner = msg->client_id; - switch (msg->type) { - case WT_ITS_OP_SET: - if ((req.flags & ~ctx->client_flags_mask) != 0U) { + switch (msg->type) { + case WT_ITS_OP_SET: + if ((req.flags & ~ctx->client_flags_mask) != 0U) { + status = PSA_ERROR_NOT_SUPPORTED; + break; + } + vreq.flags = req.flags | ctx->vault_flags; + status = wt_storage_vault_call(ctx, runtime, partition_id, + WT_VAULT_OP_SET, &vreq, + buffer + sizeof(req), + in_len - sizeof(req), NULL, 0U, + NULL); + break; + case WT_ITS_OP_GET: + cap = msg->out_size[0]; + if (cap > WT_VAULT_OBJECT_MAX) { + cap = WT_VAULT_OBJECT_MAX; + } + status = wt_storage_vault_call(ctx, runtime, partition_id, + WT_VAULT_OP_GET, &vreq, NULL, 0U, + buffer, cap, &out_len); + if (status == PSA_SUCCESS && + wt_storage_write_reply(ctx, runtime, partition_id, + msg->handle, buffer, out_len) != + WT_FFM_SUCCESS) { + status = PSA_ERROR_GENERIC_ERROR; + } + break; + case WT_ITS_OP_GET_INFO: + if (msg->out_size[0] < sizeof(info)) { + status = PSA_ERROR_INVALID_ARGUMENT; + } + else { + status = wt_storage_vault_call(ctx, runtime, partition_id, + WT_VAULT_OP_GET_INFO, &vreq, + NULL, 0U, &info, sizeof(info), + NULL); + if (status == PSA_SUCCESS) { + /* Hide the frontend's internal sealing flag. */ + info.flags &= ~ctx->vault_flags; + } + if (status == PSA_SUCCESS && + wt_storage_write_reply(ctx, runtime, partition_id, + msg->handle, &info, + sizeof(info)) != + WT_FFM_SUCCESS) { + status = PSA_ERROR_GENERIC_ERROR; + } + } + break; + case WT_ITS_OP_REMOVE: + status = wt_storage_vault_call(ctx, runtime, partition_id, + WT_VAULT_OP_REMOVE, &vreq, NULL, + 0U, NULL, 0U, NULL); + break; + default: status = PSA_ERROR_NOT_SUPPORTED; break; } - vreq.flags = req.flags | ctx->vault_flags; - status = wt_storage_vault_call(ctx, runtime, partition_id, - WT_VAULT_OP_SET, &vreq, - buffer + sizeof(req), - in_len - sizeof(req), NULL, 0U, NULL); - break; - case WT_ITS_OP_GET: - cap = msg->out_size[0]; - if (cap > WT_VAULT_OBJECT_MAX) { - cap = WT_VAULT_OBJECT_MAX; - } - status = wt_storage_vault_call(ctx, runtime, partition_id, - WT_VAULT_OP_GET, &vreq, NULL, 0U, - buffer, cap, &out_len); - if (status == PSA_SUCCESS && - wt_storage_write_reply(ctx, runtime, partition_id, - msg->handle, buffer, out_len) != - WT_FFM_SUCCESS) { - status = PSA_ERROR_GENERIC_ERROR; - } - break; - case WT_ITS_OP_GET_INFO: - if (msg->out_size[0] < sizeof(info)) { - return PSA_ERROR_INVALID_ARGUMENT; - } - status = wt_storage_vault_call(ctx, runtime, partition_id, - WT_VAULT_OP_GET_INFO, &vreq, NULL, 0U, - &info, sizeof(info), NULL); - if (status == PSA_SUCCESS) { - /* Clients see the create flags they passed, not the frontend's - * internal sealing flag. */ - info.flags &= ~ctx->vault_flags; - } - if (status == PSA_SUCCESS && - wt_storage_write_reply(ctx, runtime, partition_id, - msg->handle, &info, sizeof(info)) != - WT_FFM_SUCCESS) { - status = PSA_ERROR_GENERIC_ERROR; - } - break; - case WT_ITS_OP_REMOVE: - status = wt_storage_vault_call(ctx, runtime, partition_id, - WT_VAULT_OP_REMOVE, &vreq, NULL, 0U, - NULL, 0U, NULL); - break; - default: - status = PSA_ERROR_NOT_SUPPORTED; - break; } + wt_forceZero(buffer, sizeof(buffer)); return status; } From 12cabafa9e5346de434621fab0a9d1833cbf79a4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 22 Sep 2026 15:39:58 -0700 Subject: [PATCH 27/27] F-14106 - Preserve sealed removal and reject key deletion --- src/services/wolfhsm/wt_hsm_vault.c | 65 +++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/src/services/wolfhsm/wt_hsm_vault.c b/src/services/wolfhsm/wt_hsm_vault.c index 62246b2c..6e6c2183 100644 --- a/src/services/wolfhsm/wt_hsm_vault.c +++ b/src/services/wolfhsm/wt_hsm_vault.c @@ -45,6 +45,7 @@ #define WT_HSM_VAULT_LABEL_MAGIC 0x31565457UL /* "WTV1" little-endian */ #define WT_HSM_VAULT_TABLE_MAGIC 0x43565457UL /* "WTVC" little-endian */ #define WT_HSM_VAULT_STAGE_ID 0x0123U +#define WT_HSM_VAULT_DELETE_MARKER_BASE WT_HSM_VAULT_ID_COUNT #define WT_HSM_VAULT_FLAG_MASK \ (WT_VAULT_FLAG_WRITE_ONCE | WT_VAULT_FLAG_NO_CONFIDENTIALITY | \ @@ -55,7 +56,8 @@ * object, so a power loss can never make a GCM nonce repeat; slot[] holds the * counter each in-window object was sealed under, so a replayed (rolled-back) * ciphertext fails tag authentication on the next read. reserved holds the - * slot plus one while its prior object is staged for rollback. */ + * slot plus one while its prior object is staged for rollback, or the + * delete marker base plus slot plus one while deletion is in progress. */ typedef struct wt_hsm_vault_table { uint32_t magic; uint32_t reserved; @@ -118,7 +120,8 @@ static psa_status_t wt_hsm_vault_table_load(wt_hsm_vault_table_t* table) rc = wh_Nvm_Read(g_vault_nvm, WT_HSM_VAULT_TABLE_ID, 0U, (whNvmSize)sizeof(*table), (uint8_t*)table); if (rc != WH_ERROR_OK || table->magic != WT_HSM_VAULT_TABLE_MAGIC || - table->reserved > WT_HSM_VAULT_ID_COUNT) { + table->reserved > + WT_HSM_VAULT_DELETE_MARKER_BASE + WT_HSM_VAULT_ID_COUNT) { return PSA_ERROR_STORAGE_FAILURE; } return wt_hsm_vault_recover(table); @@ -322,6 +325,22 @@ static psa_status_t wt_hsm_vault_recover(wt_hsm_vault_table_t* table) size_t pt_len; psa_status_t status; + if (table->reserved > WT_HSM_VAULT_DELETE_MARKER_BASE) { + slot = table->reserved - WT_HSM_VAULT_DELETE_MARKER_BASE - 1U; + id = (whNvmId)(WT_HSM_VAULT_ID_BASE + slot); + status = wt_hsm_vault_map_err( + wh_Nvm_GetMetadata(g_vault_nvm, id, &meta)); + if (status == PSA_SUCCESS) { + status = wt_hsm_vault_map_err( + wh_Nvm_DestroyObjectsChecked(g_vault_nvm, 1U, &id)); + } + if (status != PSA_SUCCESS && status != PSA_ERROR_DOES_NOT_EXIST) { + return status; + } + table->slot[slot] = 0U; + table->reserved = 0U; + return wt_hsm_vault_table_store(table); + } if (table->reserved == 0U) { return wt_hsm_vault_destroy_stage(); } @@ -704,6 +723,7 @@ static psa_status_t wt_hsm_vault_remove(int32_t owner, int32_t sub, whNvmMetadata meta; wt_hsm_vault_table_t table; whNvmId id = WH_NVM_ID_INVALID; + uint32_t flags; psa_status_t status; if (g_vault_nvm == NULL) { @@ -717,21 +737,48 @@ static psa_status_t wt_hsm_vault_remove(int32_t owner, int32_t sub, if (status != PSA_SUCCESS) { return status; } - if ((wt_hsm_vault_flags_of(meta.label) & - WT_VAULT_FLAG_WRITE_ONCE) != 0U) { + flags = wt_hsm_vault_flags_of(meta.label); + if ((flags & (WT_VAULT_FLAG_KEY | WT_VAULT_FLAG_WRITE_ONCE)) != 0U) { return PSA_ERROR_NOT_PERMITTED; } - if ((wt_hsm_vault_flags_of(meta.label) & WT_VAULT_FLAG_SEALED) != 0U) { - /* Retire the counter first: a later flash-level resurrection of the - * destroyed ciphertext then fails authentication (WT-FFM-0048). */ - table.slot[id - WT_HSM_VAULT_ID_BASE] = 0U; + if ((flags & WT_VAULT_FLAG_SEALED) != 0U) { + status = wt_hsm_vault_reserve( + 2U * wt_hsm_vault_storage_size(sizeof(table)), 2U); + if (status != PSA_SUCCESS) { + return status; + } + table.reserved = WT_HSM_VAULT_DELETE_MARKER_BASE + + (id - WT_HSM_VAULT_ID_BASE) + 1U; status = wt_hsm_vault_table_store(&table); if (status != PSA_SUCCESS) { return status; } } - return wt_hsm_vault_map_err( + status = wt_hsm_vault_map_err( wh_Nvm_DestroyObjectsChecked(g_vault_nvm, 1U, &id)); + if (status != PSA_SUCCESS) { + if ((flags & WT_VAULT_FLAG_SEALED) != 0U && + wh_Nvm_GetMetadata(g_vault_nvm, id, &meta) == WH_ERROR_OK && + wt_hsm_vault_label_match(meta.label, owner, sub, uid) != 0 && + wt_hsm_vault_read_sealed( + id, &meta, table.slot[id - WT_HSM_VAULT_ID_BASE]) == + PSA_SUCCESS) { + /* The original object survived intact, so cancel the deletion. */ + wt_hsm_vault_zeroize( + g_vault_pt, (size_t)meta.len - WT_VAULT_SEAL_TAG_LEN); + table.reserved = 0U; + if (wt_hsm_vault_table_store(&table) != PSA_SUCCESS) { + return PSA_ERROR_STORAGE_FAILURE; + } + } + return status; + } + if ((flags & WT_VAULT_FLAG_SEALED) != 0U) { + table.slot[id - WT_HSM_VAULT_ID_BASE] = 0U; + table.reserved = 0U; + return wt_hsm_vault_table_store(&table); + } + return PSA_SUCCESS; } const wt_vault_backend_t wt_hsm_vault_backend = {