diff --git a/.github/workflows/m33mu.yml b/.github/workflows/m33mu.yml index b7c2b182..f2787a5d 100644 --- a/.github/workflows/m33mu.yml +++ b/.github/workflows/m33mu.yml @@ -289,6 +289,7 @@ jobs: - { key: crossdomain, name: "Cross-domain isolation (L3)" } - { key: keystoreneg, name: "Keystore-band isolation (L3)" } - { key: spfaultneg, name: "Graceful SP fault recovery" } + - { key: hsmfaultneg, name: "HSM tasklet fault containment" } - { key: panicneg, name: "Secure-caller misuse panic" } - { key: confboot, name: "FF-M IPC conformance (85/4)" } - { key: devstorage, name: "dev_apis Storage (s001-s017)" } @@ -310,6 +311,8 @@ jobs: - { key: gtzcneg, name: "NS MPU bypass cannot reach peer guest RAM" } - { key: spbudgetneg, name: "SP restart-budget exhaustion escalates" } exclude: + # The native engine has no per-guest HSM tasklet to fault. + - { engine: native, scenario: { key: hsmfaultneg, name: "HSM tasklet fault containment" } } # hsmattackneg drives the raw wolfHSM client wire from the guest; # the native engine does not link that wire, so the attack surface # under test does not exist there. 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 e4943dd0..b1d7f718 100644 --- a/docs/Services.md +++ b/docs/Services.md @@ -152,16 +152,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 a2f87c33..e8a37f91 100644 --- a/docs/TF-M-Compatibility.md +++ b/docs/TF-M-Compatibility.md @@ -114,7 +114,7 @@ where the platform and feature set are held constant. | 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/common/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` | @@ -139,7 +139,7 @@ where the platform and feature set are held constant. | 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()`. | @@ -185,9 +185,9 @@ actual boundary. and manifest. 5. Check data-size assumptions against the copied IPC and service limits. Stream update images in blocks no larger than `PSA_FWU_MAX_WRITE_SIZE`. - For wolfTrust, both the image offset and block size must be aligned to - `1 << PSA_FWU_LOG2_WRITE_ALIGN`, including the final block. This is stricter - than PSA Firmware Update 1.0, which permits padding an unaligned final block. + For wolfTrust, the image offset must be aligned to + `1 << PSA_FWU_LOG2_WRITE_ALIGN`. An unaligned final block is padded by the + service to the backend write alignment. 6. Check optional APIs before use. In particular, treat Protected Storage create/set-extended and Firmware Update accept as unsupported. 7. Express Secure services, dependencies, memory, interrupts, restart policy, diff --git a/include/wolftrust/services/hsm.h b/include/wolftrust/services/hsm.h index f7c3d1d3..8123d40a 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); @@ -167,9 +164,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 +197,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. */ 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..5eed31ac 100644 --- a/include/wolftrust/services/vault_service.h +++ b/include/wolftrust/services/vault_service.h @@ -30,7 +30,8 @@ * never a caller-supplied field (WT-FFM-0044). Non-secure clients are * refused by the manifest (nonsecure_clients = false). */ -/* psa_call request types. REMOVE also destroys keys (psa_destroy_key). */ +/* psa_call request types. REMOVE applies to storage objects; the current + * vault backend does not support key deletion. */ #define WT_VAULT_OP_SET 1 #define WT_VAULT_OP_GET 2 #define WT_VAULT_OP_GET_INFO 3 @@ -56,8 +57,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 +130,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 +157,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/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/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/include/wolftrust/zeroize.h b/include/wolftrust/zeroize.h new file mode 100644 index 00000000..b984cb9d --- /dev/null +++ b/include/wolftrust/zeroize.h @@ -0,0 +1,44 @@ +/* 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 >= 4U) { + bytes[0] = 0U; + bytes[1] = 0U; + bytes[2] = 0U; + bytes[3] = 0U; + bytes += 4; + size -= 4U; + } + while (size > 0U) { + *bytes++ = 0U; + size--; + } +} + +#endif /* WOLFTRUST_ZEROIZE_H */ diff --git a/mk/common.mk b/mk/common.mk index 71b42169..af69c972 100644 --- a/mk/common.mk +++ b/mk/common.mk @@ -60,6 +60,7 @@ WT_KEYSTORE_NEG_PROBE ?= 0 WT_LAUNCH_DEBUG ?= 0 WT_ROLLBACK_PROBE ?= 0 WT_SP_FAULT_PROBE ?= 0 +WT_HSM_FAULT_PROBE ?= 0 WT_SP_FAULT_ALWAYS_PROBE ?= 0 WT_PANIC_NEG_PROBE ?= 0 WT_VNET_NEG_PROBE ?= 0 @@ -147,6 +148,9 @@ endif ifeq ($(WT_SP_FAULT_PROBE),1) SECURE_CFLAGS += -DWT_SP_FAULT_PROBE=1 endif +ifeq ($(WT_HSM_FAULT_PROBE),1) +SECURE_CFLAGS += -DWT_HSM_FAULT_PROBE=1 +endif ifeq ($(WT_SP_FAULT_ALWAYS_PROBE),1) SECURE_CFLAGS += -DWT_SP_FAULT_ALWAYS_PROBE=1 endif @@ -1343,6 +1347,7 @@ $(BUILD_MODE_STAMP): FORCE | $(BUILD_DIR) 'WT_LAUNCH_DEBUG=$(WT_LAUNCH_DEBUG)' \ 'WT_ROLLBACK_PROBE=$(WT_ROLLBACK_PROBE)' \ 'WT_SP_FAULT_PROBE=$(WT_SP_FAULT_PROBE)' \ + 'WT_HSM_FAULT_PROBE=$(WT_HSM_FAULT_PROBE)' \ 'WT_SP_FAULT_ALWAYS_PROBE=$(WT_SP_FAULT_ALWAYS_PROBE)' \ 'WT_PANIC_NEG_PROBE=$(WT_PANIC_NEG_PROBE)' \ 'WT_VNET_NEG_PROBE=$(WT_VNET_NEG_PROBE)' \ diff --git a/port/stm32h563/hsm_flash.c b/port/stm32h563/hsm_flash.c index b3076f86..f1998dea 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; } @@ -575,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; @@ -587,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)) { @@ -596,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, @@ -614,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, @@ -623,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)) { @@ -632,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 = { @@ -677,8 +680,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/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/guest_context_armv8m.c b/src/arch/armv8m/guest_context_armv8m.c index 89f108d7..69598357 100644 --- a/src/arch/armv8m/guest_context_armv8m.c +++ b/src/arch/armv8m/guest_context_armv8m.c @@ -98,8 +98,11 @@ 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; +#define WT_SYST_LATCH_MAX_POLLS 100000U + void wt_arch_init(void) { /* Route MemManage and UsageFault to their own handlers (otherwise @@ -458,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) { @@ -468,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 once the counter has latched it. */ + 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) { @@ -494,10 +509,51 @@ 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; + uint32_t polls; + uint32_t expired = 0U; g_arriving_systick_csr = 0u; - if (csr != 0u) { + g_arriving_systick_rvr = 0u; + if (csr != 0u && reload != 0U) { + /* Keep TICKINT masked until the residual reload is observed. A + * barrier orders writes but does not advance the SysTick clock. */ + WT_SYST_NS_CSR = csr & ~WT_SYST_CSR_TICKINT; + for (polls = 0U; polls < WT_SYST_LATCH_MAX_POLLS; polls++) { + if (WT_SYST_NS_CVR != 0U) { + break; + } + if ((csr & WT_SYST_CSR_TICKINT) != 0U && + (WT_SYST_NS_CSR & WT_SYST_CSR_COUNTFLAG) != 0U) { + expired = 1U; + break; + } + } + /* A guest can select a stopped reference clock. The bounded fallback + * keeps its normal reload without stalling or panicking the monitor. */ + WT_SYST_NS_RVR = reload; WT_SYST_NS_CSR = csr; + if (polls != WT_SYST_LATCH_MAX_POLLS) { + if ((csr & WT_SYST_CSR_TICKINT) != 0U && + (WT_SYST_NS_CSR & WT_SYST_CSR_COUNTFLAG) != 0U) { + expired = 1U; + } + if (expired != 0U) { + /* The short first period elapsed during setup. Restart from + * the normal period and deliver its tick on NS entry. */ + WT_SYST_NS_CSR = 0U; + WT_SYST_NS_CVR = 0U; + WT_SYST_NS_CSR = csr; + g_arriving_systick_inject = 1U; + } + } + } + else if (csr != 0u) { + WT_SYST_NS_CSR = csr; + } + if (csr != 0U) { + wt_dsb(); + wt_isb(); } if (g_arriving_systick_inject != 0u) { g_arriving_systick_inject = 0u; @@ -512,6 +568,13 @@ static void wt_virtual_systick_reset(wt_guest_id_t guest_id) if (guest_id >= WT_MAX_GUESTS) { return; } + if (g_active_guest == guest_id) { + /* The faulted guest's hardware timer is still live. Do not let the + * next dispatch save it back over the cleared virtual state. */ + WT_SYST_NS_CSR = 0U; + WT_SCB_ICSR_NS = WT_SCB_ICSR_PENDSTCLR; + g_active_guest = UINT32_MAX; + } systick = &g_guest_systick[guest_id]; systick->csr = 0u; systick->rvr = 0u; @@ -633,12 +696,19 @@ 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); - size_t i; + volatile uint8_t* ptr = (volatile uint8_t*)base; + size_t offset = 0U; - for (i = 0; i < words; ++i) { - ptr[i] = 0u; + while (offset < size && + ((base + offset) % sizeof(uint32_t)) != 0U) { + ptr[offset++] = 0U; + } + while ((size - offset) >= sizeof(uint32_t)) { + *(volatile uint32_t*)(base + offset) = 0U; + offset += sizeof(uint32_t); + } + while (offset < size) { + ptr[offset++] = 0U; } if (base == WT_GUEST0_RAM_BASE && size >= WT_GUEST_RAM_SIZE) { wt_virtual_systick_reset(0u); diff --git a/src/arch/armv8m/irq_armv8m.c b/src/arch/armv8m/irq_armv8m.c index 016efdc2..4cf876bf 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) { @@ -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"); } diff --git a/src/arch/armv8m/sp_fault_armv8m.c b/src/arch/armv8m/sp_fault_armv8m.c index e7ae92ec..9a6fdf31 100644 --- a/src/arch/armv8m/sp_fault_armv8m.c +++ b/src/arch/armv8m/sp_fault_armv8m.c @@ -59,9 +59,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/arch/common/spm_gate_core.c b/src/arch/common/spm_gate_core.c index 7c864b43..01b3dff1 100644 --- a/src/arch/common/spm_gate_core.c +++ b/src/arch/common/spm_gate_core.c @@ -69,6 +69,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; @@ -543,8 +544,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); @@ -689,6 +694,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. */ @@ -697,7 +705,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; @@ -713,7 +723,7 @@ static uint32_t wt_spm_sched_diag_word(const wt_ffm_runtime_t* runtime, size_t i; if (which == 0) { - /* nibbles: per-slot wait_kind (0..2) then co state (3..5) */ + /* nibbles: per-slot wait_kind (0..3) then coroutine state */ for (i = 0u; i < g_spm_sp_count && i < 3u; i++) { value |= ((uint32_t)g_spm_sp[i].wait_kind & 0xFu) << (4u * i); value |= ((uint32_t)wt_co_state(g_spm_sp[i].co) & 0xFu) << @@ -842,9 +852,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) { 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; } 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; } 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) { diff --git a/src/monitor.c b/src/monitor.c index 8c9c4080..712285bc 100644 --- a/src/monitor.c +++ b/src/monitor.c @@ -334,6 +334,10 @@ static void wt_dispatch_guest(wt_guest_id_t guest_id) } } + /* Stop the departing guest's SysTick before replacing its memory + * protection; a short residual period can otherwise fault in the + * departing guest's handler under the arriving guest's MPU. */ + wt_arch_guest_context_prepare(guest_id, runtime->context); wt_apply_partition(guest_id); runtime->state = WT_GUEST_RUNNING; g_scheduler.current_guest = guest_id; @@ -342,7 +346,6 @@ static void wt_dispatch_guest(wt_guest_id_t guest_id) wt_vnet_service_refresh_irq(guest_id); #endif wt_arch_start_secure_timer(config->timeslice_ms); - wt_arch_guest_context_prepare(guest_id, runtime->context); wt_arch_guest_context_restore(runtime->context); } @@ -413,6 +416,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 +438,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) @@ -639,6 +642,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; 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) diff --git a/src/services/fwu_service.c b/src/services/fwu_service.c index 5221d814..d30b5713 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; + else if (ctx->state != PSA_FWU_WRITING) { + status = 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; - } - 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) @@ -173,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; } @@ -366,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; @@ -378,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, @@ -411,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) diff --git a/src/services/hsm_relay_service.c b/src/services/hsm_relay_service.c index 25ccfb8f..3adefb2e 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 @@ -108,18 +109,6 @@ static int wt_hsm_relay_write_resp(wt_ffm_runtime_t* runtime, return WT_FFM_SUCCESS; } -/* Scrub a relay copy buffer. volatile so the clear is not optimized away; the - * relay unit is port-free and does not link wolfCrypt's ForceZero. */ -static void wt_hsm_relay_zeroize(uint8_t* buf, size_t len) -{ - volatile uint8_t* p = buf; - size_t i; - - for (i = 0U; i < len; i++) { - p[i] = 0U; - } -} - static psa_status_t wt_hsm_relay_call_inner(wt_ffm_runtime_t* runtime, int32_t partition_id, const psa_msg_t* msg) @@ -154,33 +143,27 @@ static psa_status_t wt_hsm_relay_call_inner(wt_ffm_runtime_t* runtime, 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) { + 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) { + g_relay_io.resp, resp_len) != WT_FFM_SUCCESS) { return PSA_ERROR_GENERIC_ERROR; } return PSA_SUCCESS; } -/* Single cleanup path (key hygiene): the relay copy buffers live in the shared - * keystore band and carry imported key material and decrypted plaintext, so - * scrub both after every call, on success and on every error path. */ +/* Relay packets carry key material, so scrub them on every call path. */ static psa_status_t wt_hsm_relay_call(wt_ffm_runtime_t* runtime, int32_t partition_id, const psa_msg_t* msg) { - psa_status_t status; + psa_status_t status = wt_hsm_relay_call_inner(runtime, partition_id, msg); - status = wt_hsm_relay_call_inner(runtime, partition_id, msg); - wt_hsm_relay_zeroize(g_relay_io.req, sizeof(g_relay_io.req)); - wt_hsm_relay_zeroize(g_relay_io.resp, sizeof(g_relay_io.resp)); + wt_forceZero(&g_relay_io, sizeof(g_relay_io)); return status; } 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; } 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; } 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, diff --git a/src/services/wolfhsm/runner/secure.ld b/src/services/wolfhsm/runner/secure.ld index d0ab5cf5..493be1b1 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 90965fdb..1cc6a315 100644 --- a/src/services/wolfhsm/wt_hsm.c +++ b/src/services/wolfhsm/wt_hsm.c @@ -62,6 +62,7 @@ /* wolfTrust headers. */ #include "wolftrust/types.h" +#include "wolftrust/arch.h" #include "wolftrust/guest_verify.h" #include "wolftrust/monitor.h" #include "wolftrust/rollback.h" @@ -148,6 +149,7 @@ static int g_foreign_probe_fired; * 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 @@ -202,10 +204,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; @@ -258,6 +260,9 @@ static void wt_hsm_tasklet_main(void *arg) { wt_guest_id_t gid = (wt_guest_id_t)(uintptr_t)arg; wt_hsm_guest_t *g = &g_guests[gid]; +#if defined(WT_HSM_FAULT_PROBE) && (WT_HSM_FAULT_PROBE == 1) + static int fault_probe_fired; +#endif #if defined(WT_ATTEST_COSE) && (WT_ATTEST_COSE == 1) /* Key provisioning touches the shared persistent store and therefore @@ -273,6 +278,13 @@ static void wt_hsm_tasklet_main(void *arg) } #endif +#if defined(WT_HSM_FAULT_PROBE) && (WT_HSM_FAULT_PROBE == 1) + if (gid == 0U && fault_probe_fired == 0) { + fault_probe_fired = 1; + wt_arch_sp_fault_probe(0U); + } +#endif + for (;;) { int rc = wh_Server_HandleRequestMessage(&g->server); if (rc == WH_ERROR_NOTREADY) { @@ -467,7 +479,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; } @@ -511,6 +525,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; } @@ -546,15 +561,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; } /* ========================================================================= @@ -637,12 +655,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. * ====================================================================== */ @@ -675,6 +691,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); @@ -683,6 +702,12 @@ 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)); + if (g->tasklet != NULL) { + wt_hsm_release_locks(g->tasklet); + wt_tasklet_mark_faulted(g->tasklet); + } g->ready = false; break; } @@ -706,10 +731,19 @@ 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].guard, + sizeof(g_co_stack_slots[guest_id].guard)); + /* Keep the canary at stack[0] for do_switch's post-fault check. */ + wt_hsm_force_zero(g_co_stack_slots[guest_id].stack + sizeof(uint32_t), + sizeof(g_co_stack_slots[guest_id].stack) - + sizeof(uint32_t)); g->ready = false; return WH_ERROR_OK; } 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 9a27b672..c8f88be0 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; @@ -66,7 +68,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]; @@ -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); @@ -330,9 +333,42 @@ static psa_status_t wt_hsm_vault_recover(wt_hsm_vault_table_t* table) whNvmMetadata stage_meta; whNvmId id; uint32_t slot; + uint32_t label_magic; 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) { + (void)memcpy(&label_magic, meta.label, sizeof(label_magic)); + /* Only the key writer bypasses table recovery. Preserve a key + * that reused the slot; reclaim the old sealed object even if + * its ciphertext is corrupt and cannot be authenticated. */ + if (label_magic != WT_HSM_VAULT_LABEL_MAGIC || + (wt_hsm_vault_flags_of(meta.label) & + WT_VAULT_FLAG_KEY) == 0U || + (wt_hsm_vault_flags_of(meta.label) & + WT_VAULT_FLAG_SEALED) != 0U || + (meta.flags & WH_NVM_FLAGS_NONEXPORTABLE) == 0U) { + 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; + } + } + } + else 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(); } @@ -379,7 +415,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). */ @@ -715,6 +751,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) { @@ -728,26 +765,49 @@ static psa_status_t wt_hsm_vault_remove(int32_t owner, int32_t sub, if (status != PSA_SUCCESS) { return status; } - /* Key objects hold WT_VAULT_KEY_USAGE_* bits in the low label bits that - * alias WT_VAULT_FLAG_WRITE_ONCE (USAGE_SIGN == WRITE_ONCE == 0x1); they - * are never write-once stores, so apply the storage gate to non-key - * objects only and let REMOVE destroy a key as psa_destroy_key promises. */ - if ((wt_hsm_vault_flags_of(meta.label) & WT_VAULT_FLAG_KEY) == 0U && - (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) != 0U || + (flags & 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 = { 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: diff --git a/src/spm_partitions.c b/src/spm_partitions.c index c6acfb99..134519c6 100644 --- a/src/spm_partitions.c +++ b/src/spm_partitions.c @@ -234,10 +234,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/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; 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 2ef564fc..46cada77 100644 --- a/tests/firmware/zephyr-stm32h5/apps/guest0_psa/src/main.c +++ b/tests/firmware/zephyr-stm32h5/apps/guest0_psa/src/main.c @@ -308,7 +308,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) { @@ -1094,8 +1094,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). */ diff --git a/tests/host/fwu_service/main.c b/tests/host/fwu_service/main.c index 434092ed..dd3f9638 100644 --- a/tests/host/fwu_service/main.c +++ b/tests/host/fwu_service/main.c @@ -61,8 +61,11 @@ typedef struct mock_backend { uint32_t armed_size; uint32_t armed_version; uint32_t header_version; + uint32_t write_calls; + uint32_t fail_write_call; int fail_write; int fail_arm; + int fail_disarm; int fail_verify; } mock_backend_t; @@ -80,7 +83,8 @@ static int mock_write(void* ctx, uint32_t offset, const uint8_t* data, { mock_backend_t* b = (mock_backend_t*)ctx; - if (b->fail_write) { + b->write_calls++; + if (b->fail_write || b->write_calls == b->fail_write_call) { return -1; } if ((uint64_t)offset + size > sizeof(b->image)) { @@ -107,6 +111,9 @@ static int mock_disarm(void* ctx) { mock_backend_t* b = (mock_backend_t*)ctx; + if (b->fail_disarm) { + return -1; + } b->armed = 0u; return 0; } @@ -305,6 +312,58 @@ static void test_state_machine(void) "WT-FWU-0003 a failed arm leaves the candidate un-armed"); } +static void test_unaligned_write(void) +{ + wt_fwu_backend_t backend; + mock_backend_t mem; + wt_fwu_service_ctx_t ctx; + psa_fwu_component_info_t info; + uint8_t block[17]; + size_t i; + int tail_erased = 1; + + (void)memset(block, 0xA5, sizeof(block)); + mock_backend_init(&backend, &mem); + ctx_init(&ctx, &backend, &mem, 0u); + check(wt_fwu_start(&ctx, WT_FWU_COMPONENT_PRIMARY, 1u) == PSA_SUCCESS, + "WT-FWU-0002 starts a padded candidate"); + check(wt_fwu_write(&ctx, WT_FWU_COMPONENT_PRIMARY, 0u, block, + sizeof(block)) == PSA_SUCCESS && + mem.write_calls == 2u && ctx.write_high == sizeof(block) && + memcmp(mem.image, block, sizeof(block)) == 0, + "WT-FWU-0002 padded write keeps the logical staged size"); + for (i = sizeof(block); i < 2u * MOCK_ALIGN; i++) { + if (mem.image[i] != 0xFFu) { + tail_erased = 0; + } + } + check(tail_erased, "WT-FWU-0002 padded tail stays erased"); + check(wt_fwu_finish(&ctx, WT_FWU_COMPONENT_PRIMARY) == PSA_SUCCESS && + wt_fwu_query(&ctx, WT_FWU_COMPONENT_PRIMARY, &info) == + PSA_SUCCESS && info.impl.staged_size == sizeof(block), + "WT-FWU-0002 candidate excludes physical padding"); + + mock_backend_init(&backend, &mem); + ctx_init(&ctx, &backend, &mem, 0u); + (void)wt_fwu_start(&ctx, WT_FWU_COMPONENT_PRIMARY, 1u); + mem.fail_write_call = 2u; + check(wt_fwu_write(&ctx, WT_FWU_COMPONENT_PRIMARY, 0u, block, + sizeof(block)) == PSA_ERROR_STORAGE_FAILURE && + mem.write_calls == 2u && ctx.state == PSA_FWU_FAILED && + ctx.write_high == 0u, + "WT-FWU-0003 failed padded tail invalidates the candidate"); + + mock_backend_init(&backend, &mem); + backend.capacity = MOCK_CAPACITY - 1u; + ctx_init(&ctx, &backend, &mem, 0u); + (void)wt_fwu_start(&ctx, WT_FWU_COMPONENT_PRIMARY, 1u); + check(wt_fwu_write(&ctx, WT_FWU_COMPONENT_PRIMARY, + MOCK_CAPACITY - MOCK_ALIGN, block, + MOCK_ALIGN - 1u) == PSA_ERROR_INVALID_ARGUMENT && + mem.write_calls == 0u && ctx.state == PSA_FWU_WRITING, + "WT-FWU-0003 physical padding cannot exceed capacity"); +} + /* --- Layer 2: the FF-M IPC round trip. --- */ static const wt_service_descriptor_t g_fwu_services[] = { { @@ -510,6 +569,24 @@ static void test_ipc_round_trip(void) &info, sizeof(info)); check(status == PSA_SUCCESS && info.state == PSA_FWU_STAGED, "WT-FWU-0001 IPC query reports STAGED after install"); + + /* A timed-out owner cannot be reclaimed while disarming the pending + * update fails. A later retry can safely return the service to READY. */ + fwu_ctx.owner = 42; + fwu_ctx.owner_tick = (uint32_t)(0U - WT_FWU_OWNER_IDLE_TIMEOUT_TICKS); + mem.fail_disarm = 1; + status = fwu_call(&runtime, handle, WT_FWU_OP_QUERY, 0u, 0u, 0u, NULL, 0U, + &info, sizeof(info)); + check(status == PSA_ERROR_STORAGE_FAILURE && fwu_ctx.armed == 1u && + mem.armed == 1u && fwu_ctx.owner == 42 && + fwu_ctx.state == PSA_FWU_STAGED, + "WT-FWU-0003 failed disarm preserves the staged update and owner"); + mem.fail_disarm = 0; + status = fwu_call(&runtime, handle, WT_FWU_OP_QUERY, 0u, 0u, 0u, NULL, 0U, + &info, sizeof(info)); + check(status == PSA_SUCCESS && info.state == PSA_FWU_READY && + mem.armed == 0u && fwu_ctx.owner == 0, + "WT-FWU-0003 disarm retry reclaims the staged update"); } /* WT-FWU-0002: the wolfBoot update trigger the FWU backend arms into the @@ -664,6 +741,7 @@ static void test_owner_timeout(void) int main(void) { test_state_machine(); + test_unaligned_write(); test_ipc_round_trip(); test_wolfboot_arm_trailer(); test_staged_header_binding(); diff --git a/tests/host/ps_service/main.c b/tests/host/ps_service/main.c index 2847592b..269880a0 100644 --- a/tests/host/ps_service/main.c +++ b/tests/host/ps_service/main.c @@ -746,6 +746,81 @@ static void test_replacement_stage_cleanup(void) "next operation destroys an orphan recovery stage"); } +static void test_sealed_delete_recovery(void) +{ + static const uint8_t secret[] = "delete recovery"; + static const uint8_t key_data[] = "retained key"; + uint8_t buffer[sizeof(secret)]; + uint8_t corrupt[sizeof(secret) + WT_VAULT_SEAL_TAG_LEN]; + whNvmMetadata meta; + whNvmId id = WH_NVM_ID_INVALID; + size_t got = 0U; + psa_status_t status; + + check(test_nvm_up(0) == 0, "initialized sealed delete recovery test"); + status = wt_hsm_vault_backend.set(TEST_PS_PARTITION, TEST_NS_GUEST0, + 0xC001ULL, WT_VAULT_FLAG_SEALED, secret, sizeof(secret)); + check(status == PSA_SUCCESS, "created sealed delete recovery source"); + if (status != PSA_SUCCESS) { + return; + } + check(test_find_stored(sizeof(secret), &id, &meta) == 0, + "located sealed delete recovery source"); + if (id == WH_NVM_ID_INVALID) { + return; + } + + /* Let the delete marker commit, then fail the table update after the + * object is destroyed. Reboot must finish the pending deletion. */ + g_fail_add_id = WT_HSM_VAULT_TABLE_ID; + g_fail_add_skips = 1U; + status = wt_hsm_vault_backend.remove(TEST_PS_PARTITION, TEST_NS_GUEST0, + 0xC001ULL); + check(status == PSA_ERROR_STORAGE_FAILURE, + "interrupted sealed delete reports storage failure"); + (void)memset(&meta, 0, sizeof(meta)); + meta.id = id; + meta.access = WH_NVM_ACCESS_ANY; + meta.flags = WH_NVM_FLAGS_SENSITIVE | WH_NVM_FLAGS_NONEXPORTABLE; + meta.len = (whNvmSize)sizeof(key_data); + wt_hsm_vault_make_label(meta.label, TEST_PS_PARTITION, TEST_NS_GUEST1, + 0xC002ULL, WT_VAULT_FLAG_KEY); + check(wh_Nvm_AddObject(&g_nvm_ctx, &meta, meta.len, key_data) == + WH_ERROR_OK, "new key reuses deleted sealed slot"); + check(test_nvm_up(1) == 0, "rebooted after interrupted sealed delete"); + status = wt_hsm_vault_backend.get(TEST_PS_PARTITION, TEST_NS_GUEST0, + 0xC001ULL, 0U, buffer, sizeof(buffer), &got); + check(status == PSA_ERROR_DOES_NOT_EXIST, + "recovery finishes sealed delete before serving reads"); + check(wh_Nvm_GetMetadata(&g_nvm_ctx, id, &meta) == WH_ERROR_OK && + wh_Nvm_Read(&g_nvm_ctx, id, 0U, (whNvmSize)sizeof(key_data), + buffer) == WH_ERROR_OK && + memcmp(buffer, key_data, sizeof(key_data)) == 0, + "recovery preserves a key that reused the slot"); + status = wt_hsm_vault_backend.set(TEST_PS_PARTITION, TEST_NS_GUEST0, + 0xC001ULL, WT_VAULT_FLAG_SEALED, secret, sizeof(secret)); + check(status == PSA_SUCCESS, "deleted sealed UID can be reused"); + + check(test_find_stored(sizeof(secret), &id, &meta) == 0, + "located second sealed delete recovery source"); + g_fail_add_id = WT_HSM_VAULT_TABLE_ID; + g_fail_add_skips = 1U; + status = wt_hsm_vault_backend.remove(TEST_PS_PARTITION, TEST_NS_GUEST0, + 0xC001ULL); + check(status == PSA_ERROR_STORAGE_FAILURE, + "second sealed delete is interrupted"); + (void)memset(corrupt, 0xA5, sizeof(corrupt)); + check(wh_Nvm_AddObject(&g_nvm_ctx, &meta, meta.len, corrupt) == + WH_ERROR_OK, "corrupt sealed object occupies deleted slot"); + check(test_nvm_up(1) == 0, "rebooted with corrupt sealed object"); + status = wt_hsm_vault_backend.get(TEST_PS_PARTITION, TEST_NS_GUEST0, + 0xC001ULL, 0U, buffer, sizeof(buffer), &got); + check(status == PSA_ERROR_DOES_NOT_EXIST, + "recovery removes the corrupt sealed object"); + check(wh_Nvm_GetMetadata(&g_nvm_ctx, id, &meta) == WH_ERROR_NOTFOUND, + "corrupt sealed object no longer consumes its slot"); +} + int main(void) { static const uint8_t secret_v1[] = "ps-secret-version-one"; @@ -968,6 +1043,7 @@ int main(void) test_recovery_counter_binding(); test_invalid_sealed_length(); test_replacement_stage_cleanup(); + test_sealed_delete_recovery(); if (g_failures != 0) { return 1; diff --git a/tests/host/sp_recovery/main.c b/tests/host/sp_recovery/main.c index 66115abf..8e465efe 100644 --- a/tests/host/sp_recovery/main.c +++ b/tests/host/sp_recovery/main.c @@ -327,6 +327,9 @@ static void test_mutex_faulted_waiter(void) wt_mutex_init(&m); EXPECT_INT(wt_mutex_acquire_queued(&m, a), 0); EXPECT_INT(wt_mutex_acquire_queued(&m, b), 1); + EXPECT_INT(wt_mutex_acquire_queued(&m, b), 1); + EXPECT_TRUE(m.wait_head == b && m.wait_tail == b && + b->next_wait == NULL); EXPECT_INT(wt_mutex_acquire_queued(&m, c), 1); EXPECT_TRUE(wt_mutex_holder(&m) == a); @@ -354,8 +357,10 @@ static void test_mutex_faulted_waiter(void) wt_mutex_remove_waiter(&m, c); wt_mutex_release_if_holder(&m, a); EXPECT_TRUE(wt_mutex_holder(&m) == b); + EXPECT_TRUE(m.wait_head == NULL && m.wait_tail == NULL); + EXPECT_INT(wt_mutex_acquire_queued(&m, b), 0); - (void)printf("PASS: faulted mutex waiter removed from wait queue\n"); + (void)printf("PASS: mutex waiter handoff and fault cleanup\n"); } int main(void) diff --git a/tests/host/vault_service/main.c b/tests/host/vault_service/main.c index 1a9d90da..81970a7b 100644 --- a/tests/host/vault_service/main.c +++ b/tests/host/vault_service/main.c @@ -309,6 +309,8 @@ int main(void) static const whNvmCb nvm_cb[1] = {WH_NVM_FLASH_CB}; whNvmConfig nvm_cfg; whNvmContext nvm_ctx; + whNvmMetadata key_meta; + whNvmId key_id = WH_NVM_ID_INVALID; wt_ffm_runtime_t runtime; psa_handle_t handle_a; psa_handle_t handle_b; @@ -441,6 +443,27 @@ int main(void) check(status == PSA_ERROR_DOES_NOT_EXIST, "removed uid is gone"); + status = wt_hsm_vault_lookup(TEST_CLIENT_A, 0, 0x44ULL, NULL, NULL, + &key_id); + check(status == PSA_ERROR_DOES_NOT_EXIST && + key_id != WH_NVM_ID_INVALID, "found a slot for a key object"); + if (key_id == WH_NVM_ID_INVALID) { + return 1; + } + (void)memset(&key_meta, 0, sizeof(key_meta)); + key_meta.id = key_id; + key_meta.access = WH_NVM_ACCESS_ANY; + key_meta.flags = WH_NVM_FLAGS_SENSITIVE | WH_NVM_FLAGS_NONEXPORTABLE; + key_meta.len = (whNvmSize)sizeof(data_a); + wt_hsm_vault_make_label(key_meta.label, TEST_CLIENT_A, 0, 0x44ULL, + WT_VAULT_FLAG_KEY | WT_VAULT_KEY_USAGE_VERIFY); + check(wh_Nvm_AddObject(&nvm_ctx, &key_meta, key_meta.len, data_a) == + WH_ERROR_OK, "created key object in shared vault window"); + status = vault_remove(&runtime, TEST_CLIENT_A, handle_a, 0x44ULL); + check(status == PSA_ERROR_NOT_PERMITTED && + wh_Nvm_GetMetadata(&nvm_ctx, key_id, &key_meta) == WH_ERROR_OK, + "storage remove cannot delete a key object"); + /* Malformed request header is refused, not misparsed. */ (void)memset(short_req, 0, sizeof(short_req)); bad_vec.base = short_req; diff --git a/tests/target/run_m33mu_scenario.sh b/tests/target/run_m33mu_scenario.sh index 40b76c36..bc8f8b8a 100755 --- a/tests/target/run_m33mu_scenario.sh +++ b/tests/target/run_m33mu_scenario.sh @@ -13,6 +13,8 @@ # run_m33mu_scenario.sh spfaultneg the crypto SP faults once; wolfTrust # gracefully restarts it in place (no # reset) and it serves again, guests live +# run_m33mu_scenario.sh hsmfaultneg a guest HSM tasklet faults once; +# the other guest survives without panic # run_m33mu_scenario.sh confboot conformance image (Arm server/client SPs # scheduled, WT_CONFORMANCE=1) boots the # full positive lifecycle green @@ -51,8 +53,8 @@ set -o pipefail scenario="${1:-}" case "$scenario" in - positive|bothpsa|bothiso|restart|crossdomain|keystoreneg|spfaultneg|panicneg|confboot|devstorage|devcrypto|devattest|devattestqcbor|attestneg|hsmattackneg|vaultrecover|vaultrecoversec|authneg|rollbackneg|fwustage|remeasureneg|bootupdate|vnet|vnetneg|manifestneg|gtzcneg|spbudgetneg) ;; - *) echo "usage: $0 positive|bothpsa|bothiso|restart|crossdomain|keystoreneg|spfaultneg|panicneg|confboot|devstorage|devcrypto|devattest|devattestqcbor|attestneg|hsmattackneg|vaultrecover|vaultrecoversec|authneg|rollbackneg|fwustage|remeasureneg|bootupdate|vnet|vnetneg|manifestneg|gtzcneg|spbudgetneg" >&2; exit 2 ;; + positive|bothpsa|bothiso|restart|crossdomain|keystoreneg|spfaultneg|hsmfaultneg|panicneg|confboot|devstorage|devcrypto|devattest|devattestqcbor|attestneg|hsmattackneg|vaultrecover|vaultrecoversec|authneg|rollbackneg|fwustage|remeasureneg|bootupdate|vnet|vnetneg|manifestneg|gtzcneg|spbudgetneg) ;; + *) echo "usage: $0 positive|bothpsa|bothiso|restart|crossdomain|keystoreneg|spfaultneg|hsmfaultneg|panicneg|confboot|devstorage|devcrypto|devattest|devattestqcbor|attestneg|hsmattackneg|vaultrecover|vaultrecoversec|authneg|rollbackneg|fwustage|remeasureneg|bootupdate|vnet|vnetneg|manifestneg|gtzcneg|spbudgetneg" >&2; exit 2 ;; esac repo="$(cd "$(dirname "$0")/../.." && pwd)" @@ -149,6 +151,8 @@ elif [ "$scenario" = "keystoreneg" ]; then secure_flags="WT_KEYSTORE_NEG_PROBE=1" elif [ "$scenario" = "spfaultneg" ]; then secure_flags="WT_SP_FAULT_PROBE=1" +elif [ "$scenario" = "hsmfaultneg" ]; then + secure_flags="WT_HSM_FAULT_PROBE=1" elif [ "$scenario" = "panicneg" ]; then secure_flags="WT_PANIC_NEG_PROBE=1" elif [ "$scenario" = "confboot" ] || [ "$scenario" = "devstorage" ] || \ @@ -300,6 +304,9 @@ timeout_s=60 if [ "$scenario" = "restart" ]; then quit_flag="" timeout_s=40 +elif [ "$scenario" = "hsmfaultneg" ]; then + quit_flag="" + timeout_s=40 elif [ "$scenario" = "spbudgetneg" ]; then # The relay faults on every entry until its budget is exhausted; the run # ends on the fail-closed platform recovery marker, never a clean exit. @@ -715,6 +722,16 @@ case "$scenario" in echo "PASS: target/spfaultneg" ;; + hsmfaultneg) + # A guest0 HSM tasklet fault must not turn its post-fault stack-canary + # check into a platform panic. Guest1 must keep running. + expect "guest0 HSM tasklet faulted" "[USGFLT]" + refute_re "HSM fault stayed contained" '(\[HARDFLT\]|HardFault|SecureFault|\[BKPT\] imm=0x7e)' + expect "other guest remains functional" "freertos_guest1: ffm sha256 ok" + expect "full chain exits cleanly" "[EXPECT BKPT] Success" + echo "PASS: target/hsmfaultneg" + ;; + panicneg) # Secure-caller misuse (FF-M PROGRAMMER ERROR, WT-FFM-0063): the ITS SP # closes an error-status handle on its first entry, so the production SPM