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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ clears restart-marked RAM and restores a clean context after a bounded delay; if
the budget is exhausted, the guest remains faulted. Runtime remeasurement can
also quarantine a guest without entering it.

When no guest is runnable but recovery is pending, the monitor idles in
Secure Thread mode so Secure SysTick can finish the backoff. It halts only
when every guest is faulted or stopped.

The PSA lifecycle value comes from the authenticated boot handoff. Scheduled
Secure Partitions can read it through `psa_rot_lifecycle_state()`; there is no
Non-secure client adapter or CMSE veneer for that function. The value also controls
Expand Down
4 changes: 4 additions & 0 deletions docs/Security-Model.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ for restart and reconstructs the initial context. The image is verified again
before the guest runs; failed verification or an exhausted restart budget leaves
the guest quarantined.

Pending recovery keeps the Secure timer running even when no peer is runnable.
The monitor leaves the fault handler and waits in Secure Thread mode; a fully
faulted or stopped guest set remains terminal.

## Secure Partition isolation

Every shipped service loop runs as a scheduled unprivileged Secure coroutine.
Expand Down
11 changes: 11 additions & 0 deletions docs/Testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ vectors, Secure Partition layout and recovery, crypto-engine relay and key
isolation, vault and storage services, attestation and COSE integration,
firmware update, runtime remeasurement, linked Secure layout, VNET, public PSA
headers, boot-handoff record consumption, and negative paths.
The monitor suite checks bounded guest recovery, idle backoff, and terminal
lifecycle decisions against the production scheduler with simulated architecture
operations.
The attestation IAK suite runs wolfHSM NVM with both the default 8-byte and
STM32H5 16-byte flash programming units.

Expand Down Expand Up @@ -122,6 +125,14 @@ authenticated-boot failure, rollback, runtime remeasurement, Secure Partition
recovery, key and vault isolation, storage recovery, attestation negatives,
firmware update, manifest rejection, GTZC behavior, and VNET paths.

`restartidle` runs the STM32H563 recovery check with `WT_MAX_GUESTS=1`. It
requires every allowed relaunch and the final terminal verdict. The full tier
runs it with both crypto engines.
It also runs `tests/target/test_systick_idle.sh`, which executes the production
Armv8-M timer code with modeled Non-secure timer registers and real Secure
SysTick interrupts. The check covers timer configuration and pending-tick
preservation across idle, repeated idle, resume, and interrupt ordering.

Five scenarios cover processor-state isolation, and the emulator proves less
than their names suggest:

Expand Down
4 changes: 4 additions & 0 deletions include/wolftrust/arch.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ bool wt_arch_trap_from_guest_thread(void);
bool wt_arch_trap_from_secure_thread(void);
void wt_arch_return_to_secure_thread(
void (*entry)(void) __attribute__((noreturn)));
/* Discard the departing handler stack before entering the idle scheduler. */
void wt_arch_return_to_idle_thread(
void (*entry)(void) __attribute__((noreturn))) __attribute__((noreturn));
void wt_arch_wait_for_interrupt(void);

uintptr_t wt_arch_read_fault_address(void);

Expand Down
24 changes: 24 additions & 0 deletions src/arch/armv8m/guest_context_armv8m.c
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,18 @@ void wt_arch_return_to_secure_thread(
"b wt_platform_panic \n");
}

/* Idle has no suspended Secure caller; reuse the sealed main stack. */
__attribute__((naked, noreturn))
void wt_arch_return_to_idle_thread(
void (*entry)(void) __attribute__((noreturn, unused)))
{
__asm volatile(
"ldr r1, =_estack \n"
"sub r1, r1, #8 \n"
"mov sp, r1 \n"
"b wt_arch_return_to_secure_thread \n");
}

static void wt_exception_return_ns_msp(void) __attribute__((naked, noreturn));

static void wt_exception_return_ns_msp(void)
Expand Down Expand Up @@ -487,6 +499,18 @@ static void wt_virtual_systick_save_departing(void)
WT_SCB_ICSR_NS = WT_SCB_ICSR_PENDSTCLR;
}

void wt_arch_wait_for_interrupt(void)
{
uint32_t primask;

/* Restart ticks must not interleave snapshot and ownership release. */
__asm volatile("mrs %0, primask\n cpsid i" : "=r"(primask) :: "memory");
wt_virtual_systick_save_departing();
g_active_guest = UINT32_MAX;
__asm volatile("dsb 0xF\n wfi\n msr primask, %0\n isb 0xF"
:: "r"(primask) : "memory");
}

static void wt_virtual_systick_account_elapsed(wt_virtual_systick_t* systick)
{
uint64_t elapsed;
Expand Down
23 changes: 22 additions & 1 deletion src/monitor.c
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,19 @@ static void wt_restart_guest(wt_guest_id_t guest_id, wt_fault_reason_t reason)

}

static bool wt_all_guests_terminal(void)
{
size_t i;

for (i = 0U; i < g_scheduler.guest_count; ++i) {
if (g_scheduler.runtime[i].state != WT_GUEST_FAULTED &&
g_scheduler.runtime[i].state != WT_GUEST_STOPPED) {
return false;
}
}
return true;
}

static void wt_schedule_next_guest(void)
{
wt_guest_id_t next_guest;
Expand All @@ -456,7 +469,15 @@ static void wt_schedule_next_guest(void)
&rep);

if (next_guest >= g_scheduler.guest_count) {
wt_platform_all_guests_faulted();
if (wt_all_guests_terminal()) {
wt_platform_all_guests_faulted();
}
/* Backoff must run after the fault handler has unwound. */
if (wt_arch_in_handler_mode()) {
wt_arch_return_to_idle_thread(wt_schedule_next_guest);
}
wt_arch_wait_for_interrupt();
continue;
}

wt_arch_quarantine_pending_irqs(&g_scheduler.configs[next_guest].irq_mask);
Expand Down
182 changes: 182 additions & 0 deletions tests/firmware/systick-idle/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/* main.c
*
* Copyright (C) 2026 wolfSSL Inc.
*
* This file is part of wolfTrust.
*
* wolfTrust is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfTrust is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/

#include "wolftrust/arch/armv8m/core_regs.h"

static volatile uint32_t g_test_csr;
static volatile uint32_t g_test_rvr;
static volatile uint32_t g_test_cvr;
static volatile uint32_t g_test_icsr;
static volatile uint32_t g_test_ticks;
static volatile uint32_t g_test_inject;
static volatile uint32_t g_test_handoff;
static volatile uint32_t g_test_mask_seen;
static volatile uint32_t g_test_bad_handoff;
static volatile uint32_t g_test_reset_owner;

static volatile uint32_t* test_reload_register(void);

#undef WT_SYST_NS_CSR
#undef WT_SYST_NS_RVR
#undef WT_SYST_NS_CVR
#undef WT_SCB_ICSR_NS
#define WT_SYST_NS_CSR g_test_csr
#define WT_SYST_NS_RVR (*test_reload_register())
#define WT_SYST_NS_CVR g_test_cvr
#define WT_SCB_ICSR_NS g_test_icsr

/* Stable NS register model; Secure SysTick and WFI execute on the Cortex-M. */
#include "../../../src/arch/armv8m/guest_context_armv8m.c"

extern uint32_t _estack;
extern uint32_t _sbss;
extern uint32_t _ebss;
void Reset_Handler(void);

__attribute__((section(".vectors")))
const uintptr_t g_test_vectors[16] = {
[0] = (uintptr_t)&_estack,
[1] = (uintptr_t)Reset_Handler,
[3] = (uintptr_t)wt_platform_panic,
[15] = (uintptr_t)SysTick_Handler
};

static volatile uint32_t* test_reload_register(void)
{
if (g_test_inject != 0U) {
g_test_inject = 0U;
__asm volatile("mrs %0, primask" : "=r"(g_test_mask_seen));
g_test_handoff = 1U;
WT_SCB_ICSR_S = WT_SCB_ICSR_PENDSTSET;
__asm volatile("dsb 0xF\n isb 0xF" ::: "memory");
}
return &g_test_rvr;
}

void wt_monitor_on_secure_timer(const wt_trap_frame_t* frame)
{
(void)frame;
g_test_ticks++;
if (g_test_reset_owner != 0U) {
g_test_reset_owner = 0U;
wt_virtual_systick_reset(0U);
}
if (g_test_handoff != 0U && g_active_guest != UINT32_MAX) {
g_test_bad_handoff++;
}
}

void wt_platform_panic(void)
{
__asm volatile("bkpt #0x7e");
for (;;) {}
}

static void check(int ok)
{
if (!ok) {
wt_platform_panic();
}
}

void Reset_Handler(void)
{
uint32_t* p;
uint32_t primask;
wt_virtual_systick_t saved;

for (p = &_sbss; p < &_ebss; ++p) {
*p = 0U;
}
wt_arch_init();
g_systick_reload = 100000U;
g_secure_wall_cycles = 1000000U;
WT_SYST_RVR = g_systick_reload - 1U;
WT_SYST_CVR = 0U;
WT_SYST_CSR = WT_SYST_CSR_ENABLE | WT_SYST_CSR_TICKINT |
WT_SYST_CSR_CLKSOURCE;

g_active_guest = 0U;
g_test_csr = WT_SYST_CSR_ENABLE | WT_SYST_CSR_TICKINT |
WT_SYST_CSR_CLKSOURCE;
g_test_rvr = 10000U;
g_test_cvr = 4321U;
g_test_icsr = WT_SCB_ICSR_PENDSTSET;
g_test_inject = 1U;
wt_arch_wait_for_interrupt();
g_test_handoff = 0U;
check(g_guest_systick[0].csr == 7U &&
g_guest_systick[0].rvr == 10000U &&
g_guest_systick[0].cvr == 4321U &&
g_guest_systick[0].pending == 1U &&
g_guest_systick[0].owed_ticks == 1U);
check(g_active_guest == UINT32_MAX && g_test_csr == 0U &&
g_test_icsr == WT_SCB_ICSR_PENDSTCLR);
__asm volatile("mrs %0, primask" : "=r"(primask));
check(g_test_mask_seen == 1U && primask == 0U &&
g_test_bad_handoff == 0U && g_test_ticks == 1U);

saved = g_guest_systick[0];
wt_arch_wait_for_interrupt();
check(g_guest_systick[0].csr == saved.csr &&
g_guest_systick[0].rvr == saved.rvr &&
g_guest_systick[0].cvr == saved.cvr &&
g_guest_systick[0].pending == saved.pending &&
g_guest_systick[0].owed_ticks == saved.owed_ticks &&
g_guest_systick[0].last_accounted_cycles ==
saved.last_accounted_cycles);

g_secure_wall_cycles = saved.last_accounted_cycles;
wt_virtual_systick_restore_arriving(0U);
check(g_arriving_systick_csr == saved.csr &&
g_test_rvr == saved.cvr && g_arriving_systick_inject == 1U);
wt_virtual_systick_arm_arriving();
check(g_test_csr == saved.csr && g_test_rvr == saved.rvr &&
g_test_icsr == WT_SCB_ICSR_PENDSTSET);

g_active_guest = 1U;
g_test_csr = WT_SYST_CSR_CLKSOURCE;
g_test_rvr = 321U;
g_test_cvr = 123U;
g_test_icsr = 0U;
wt_arch_wait_for_interrupt();
check(g_guest_systick[1].csr == WT_SYST_CSR_CLKSOURCE &&
g_guest_systick[1].rvr == 321U &&
g_guest_systick[1].cvr == 123U &&
g_guest_systick[1].pending == 0U &&
g_guest_systick[1].owed_ticks == 0U);

/* A tick before the critical section completes before ownership is read. */
g_active_guest = 0U;
g_test_csr = 7U;
g_test_reset_owner = 1U;
WT_SCB_ICSR_S = WT_SCB_ICSR_PENDSTSET;
__asm volatile("dsb 0xF\n isb 0xF" ::: "memory");
check(g_test_reset_owner == 0U);
wt_arch_wait_for_interrupt();
check(g_active_guest == UINT32_MAX &&
g_guest_systick[0].csr == 0U &&
g_guest_systick[0].rvr == 0U &&
g_guest_systick[0].cvr == 0U &&
g_guest_systick[0].pending == 0U &&
g_guest_systick[0].owed_ticks == 0U);
__asm volatile("bkpt #0x7f");
for (;;) {}
}
42 changes: 42 additions & 0 deletions tests/firmware/systick-idle/test.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* test.ld
*
* Copyright (C) 2026 wolfSSL Inc.
*
* This file is part of wolfTrust.
*
* wolfTrust is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfTrust is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/

ENTRY(Reset_Handler)
MEMORY
{
FLASH (rx) : ORIGIN = 0x0C000000, LENGTH = 128K
RAM (rwx) : ORIGIN = 0x30000000, LENGTH = 64K
}
_estack = ORIGIN(RAM) + LENGTH(RAM);
SECTIONS
{
.text : {
KEEP(*(.vectors))
*(.text*)
*(.rodata*)
} > FLASH
.data : { *(.data*) } > RAM AT > FLASH
.bss (NOLOAD) : {
_sbss = .;
*(.bss*)
*(COMMON)
_ebss = .;
} > RAM
}
2 changes: 1 addition & 1 deletion tests/host/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ else
SUBMAKE_FLAGS := --no-print-directory -s
endif

UNIT_SUITES := domain manifest lifecycle guest_verify rollback sp_recovery \
UNIT_SUITES := domain manifest lifecycle monitor guest_verify rollback sp_recovery \
fabric_windows periph irq_claim rt700_trng rt700_xspi \
ipc ffm spm \
vnet wolfhsm_loopback wolfhsm_relay native_wire native_wire_client \
Expand Down
Loading
Loading