diff --git a/.github/workflows/test-hooks-simulator.yml b/.github/workflows/test-hooks-simulator.yml new file mode 100644 index 0000000000..ae4571361c --- /dev/null +++ b/.github/workflows/test-hooks-simulator.yml @@ -0,0 +1,188 @@ +name: hook simulator tests + +on: + push: + branches: [ 'master', 'main', 'release/**' ] + pull_request: + branches: [ '*' ] + +jobs: + hooks_test: + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - mechanism: flash + config: sim.config + test_script: sim-sunnyday-update.sh + expected_preinit: 2 + expected_postinit: 2 + expected_boot: 2 + expected_panic: 0 + - mechanism: dualbank + config: sim-dualbank.config + test_script: sim-dualbank-swap-update.sh + expected_preinit: 3 + expected_postinit: 3 + expected_boot: 3 + expected_panic: 0 + - mechanism: panic + config: sim.config + test_script: "" + expected_preinit: 1 + expected_postinit: 1 + expected_boot: 0 + expected_panic: 1 + + name: hooks (${{ matrix.mechanism }}) + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Workaround for sources.list + run: | + set -euxo pipefail + + apt-cache policy + grep -RInE '^(deb|Types|URIs)' /etc/apt || true + + shopt -s nullglob + + echo "Replace sources.list (legacy)" + sudo sed -i \ + -e "s|https\?://azure\.archive\.ubuntu\.com/ubuntu/?|http://mirror.arizona.edu/ubuntu/|g" \ + /etc/apt/sources.list || true + + echo "Replace sources.list.d/*.list (legacy)" + for f in /etc/apt/sources.list.d/*.list; do + sudo sed -i \ + -e "s|https\?://azure\.archive\.ubuntu\.com/ubuntu/?|http://mirror.arizona.edu/ubuntu/|g" \ + "$f" + done + + echo "Replace sources.list.d/*.sources (deb822)" + for f in /etc/apt/sources.list.d/*.sources; do + sudo sed -i \ + -e "s|https\?://azure\.archive\.ubuntu\.com/ubuntu/?|http://mirror.arizona.edu/ubuntu/|g" \ + -e "s|https\?://azure\.archive\.ubuntu\.com|http://mirror.arizona.edu|g" \ + "$f" + done + + echo "Fix /etc/apt/apt-mirrors.txt (used by URIs: mirror+file:...)" + if grep -qE '^[[:space:]]*https?://azure\.archive\.ubuntu\.com/ubuntu/?' /etc/apt/apt-mirrors.txt; then + sudo sed -i 's|https\?://azure\.archive\.ubuntu\.com/ubuntu/|http://mirror.arizona.edu/ubuntu/|g' /etc/apt/apt-mirrors.txt + fi + + grep -RIn "azure.archive.ubuntu.com" /etc/apt || true + grep -RInE '^(deb|Types|URIs)' /etc/apt || true + echo "--- apt-mirrors.txt ---" + cat /etc/apt/apt-mirrors.txt || true + + - name: Update repository + run: sudo apt-get update -o Acquire::Retries=3 + + - name: Create test_hooks.c + run: | + cat > test_hooks.c << 'EOF' + #include + #include "hooks.h" + + #define HOOK_LOG_FILE "/tmp/wolfboot_hooks.log" + + static void log_hook(const char *name) + { + FILE *f = fopen(HOOK_LOG_FILE, "a"); + if (f) { + fprintf(f, "%s\n", name); + fclose(f); + } + } + + void wolfBoot_hook_preinit(void) { log_hook("preinit"); } + void wolfBoot_hook_postinit(void) { log_hook("postinit"); } + void wolfBoot_hook_boot(struct wolfBoot_image *boot_img) { (void)boot_img; log_hook("boot"); } + void wolfBoot_hook_panic(void) { log_hook("panic"); } + EOF + + - name: Select config + run: | + cp config/examples/${{ matrix.config }} .config + + - name: Build tools + run: | + make -C tools/keytools && make -C tools/bin-assemble + + - name: Build wolfboot.elf with hooks + run: | + make clean && make test-sim-internal-flash-with-update \ + WOLFBOOT_HOOKS_FILE=test_hooks.c \ + WOLFBOOT_HOOK_LOADER_PREINIT=1 \ + WOLFBOOT_HOOK_LOADER_POSTINIT=1 \ + WOLFBOOT_HOOK_BOOT=1 \ + WOLFBOOT_HOOK_PANIC=1 + + - name: Clear hook log + run: | + rm -f /tmp/wolfboot_hooks.log + + - name: Corrupt partitions and run panic test + if: matrix.mechanism == 'panic' + run: | + # Zero out boot partition header (offset 0x80000) to invalidate image + printf '\x00\x00\x00\x00\x00\x00\x00\x00' | \ + dd of=internal_flash.dd bs=1 seek=$((0x80000)) conv=notrunc + # Zero out update partition header (offset 0x100000) to invalidate image + printf '\x00\x00\x00\x00\x00\x00\x00\x00' | \ + dd of=internal_flash.dd bs=1 seek=$((0x100000)) conv=notrunc + # wolfBoot_panic() calls exit('P') = exit(80) on ARCH_SIM + ./wolfboot.elf get_version 2>&1 || EXIT_CODE=$? + echo "wolfboot.elf exited with code ${EXIT_CODE:-0}" + if [ "${EXIT_CODE:-0}" -ne 80 ]; then + echo "FAIL: expected exit code 80 (panic), got ${EXIT_CODE:-0}" + exit 1 + fi + echo "OK: wolfboot panicked as expected" + + - name: Run ${{ matrix.mechanism }} update test + if: matrix.mechanism != 'panic' + run: | + tools/scripts/${{ matrix.test_script }} + + - name: Display hook log + if: always() + run: | + echo "=== Hook log contents ===" + cat /tmp/wolfboot_hooks.log || echo "(no log file found)" + + - name: Verify hook call counts + run: | + LOG="/tmp/wolfboot_hooks.log" + PASS=true + + check_count() { + local hook_name="$1" + local expected="$2" + local actual + actual=$(grep -c "^${hook_name}$" "$LOG" 2>/dev/null || echo 0) + if [ "$actual" -ne "$expected" ]; then + echo "FAIL: ${hook_name} expected=${expected} actual=${actual}" + PASS=false + else + echo "OK: ${hook_name} expected=${expected} actual=${actual}" + fi + } + + check_count "preinit" ${{ matrix.expected_preinit }} + check_count "postinit" ${{ matrix.expected_postinit }} + check_count "boot" ${{ matrix.expected_boot }} + check_count "panic" ${{ matrix.expected_panic }} + + if [ "$PASS" != "true" ]; then + echo "Hook verification FAILED" + exit 1 + fi + echo "All hook counts verified successfully" diff --git a/docs/README.md b/docs/README.md index 758e556d25..d86720d9c7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -14,6 +14,7 @@ See also: [wolfBoot Product Overview](https://www.wolfssl.com/products/wolfboot/ - [**flash-OTP.md**](./flash-OTP.md) - Using One-Time Programmable (OTP) regions in flash for secure data. - [**flash_partitions.md**](./flash_partitions.md) - Flash partitioning schemes and configuration guidance. - [**HAL.md**](./HAL.md) - Hardware Abstraction Layer notes and porting considerations. +- [**hooks.md**](./hooks.md) - User-defined hooks for injecting custom logic into the wolfBoot boot process. - [**keystore.md**](./keystore.md) - Keystore design, key storage, and access strategies. - [**lib.md**](./lib.md) - Using wolfBoot as a library and linking/integration guidance. - [**Loader.md**](./Loader.md) - Loader/secondary stage behavior and handoff to application. diff --git a/docs/hooks.md b/docs/hooks.md new file mode 100644 index 0000000000..48223eb905 --- /dev/null +++ b/docs/hooks.md @@ -0,0 +1,91 @@ +# Hooks + +wolfBoot provides a hooks framework that allows users to inject custom logic at +well-defined points in the boot process without modifying wolfBoot source code. +Each hook is independently enabled via its own build-time macro and compiled in +from a single user-provided source file. + +Typical use cases include board-specific hardware setup not covered in the +default HAL, debug logging, watchdog management, setting a safe state on boot +failure, etc. + +## Available Hooks + +| Hook | Macro | Signature | When Called | +|------|-------|-----------|------------| +| Preinit | `WOLFBOOT_HOOK_LOADER_PREINIT` | `void wolfBoot_hook_preinit(void)` | Before `hal_init()` in the loader | +| Postinit | `WOLFBOOT_HOOK_LOADER_POSTINIT` | `void wolfBoot_hook_postinit(void)` | After all loader initialization, just before `wolfBoot_start()` | +| Boot | `WOLFBOOT_HOOK_BOOT` | `void wolfBoot_hook_boot(struct wolfBoot_image *boot_img)` | After `hal_prepare_boot()` but before `do_boot()` | +| Panic | `WOLFBOOT_HOOK_PANIC` | `void wolfBoot_hook_panic(void)` | Inside `wolfBoot_panic()`, before halt | + +## Boot Flow + +The following diagram shows where each hook fires in the wolfBoot boot sequence: + +``` +loader main() + | + +-- [HOOK: wolfBoot_hook_preinit()] <-- WOLFBOOT_HOOK_LOADER_PREINIT + | + +-- hal_init() + +-- other initialization... + | + +-- [HOOK: wolfBoot_hook_postinit()] <-- WOLFBOOT_HOOK_LOADER_POSTINIT + | + +-- wolfBoot_start() + | + +-- (image verification, update logic) + | + +-- hal_prepare_boot() + | + +-- [HOOK: wolfBoot_hook_boot()] <-- WOLFBOOT_HOOK_BOOT + | + +-- do_boot() + +wolfBoot_panic() (called on any fatal error) + | + +-- [HOOK: wolfBoot_hook_panic()] <-- WOLFBOOT_HOOK_PANIC + | + +-- halt / infinite loop +``` + +## Build Configuration + +First, enable hooks in your `.config`: + +```makefile +# Path to a single .c file containing your hook implementations +WOLFBOOT_HOOKS_FILE=path/to/my_hooks.c + +# Enable individual hooks (each is independent) +WOLFBOOT_HOOK_LOADER_PREINIT=1 +WOLFBOOT_HOOK_LOADER_POSTINIT=1 +WOLFBOOT_HOOK_BOOT=1 +WOLFBOOT_HOOK_PANIC=1 +``` + +Or pass them on the `make` command line: + +```bash +make WOLFBOOT_HOOKS_FILE=my_hooks.c WOLFBOOT_HOOK_LOADER_PREINIT=1 +``` + +## Notes + +- `WOLFBOOT_HOOKS_FILE` tells the build system to compile and link your hooks + source file. The resulting object file is added to the wolfBoot binary. +- Hook prototypes are declared in `include/hooks.h`. +- Each hook is independently enabled. You only need to implement the hooks you + wish to enable in your build. Additionally, all hooks are optional. If no + `WOLFBOOT_HOOK_*` macros are defined, the boot flow is unchanged. +- The preinit hook runs before any hardware initialization. Be careful to only + use functionality that does not depend on `hal_init()` having been called. +- The boot hook receives a pointer to the verified `wolfBoot_image` struct, + allowing inspection of firmware version, type, and other metadata before boot. +- The panic hook fires inside `wolfBoot_panic()` just before the system halts. + Use it to set the system to a safe state, log errors, toggle GPIOs, notify + external systems, etc. +- **Note (WOLFBOOT_ARMORED):** When armored glitch protection is enabled, the + panic hook is called on a best-effort basis before the glitch-hardened halt + loop. The hook itself is not glitch-protected — a fault during the hook call + could skip it entirely. The halt loop remains hardened regardless. diff --git a/include/hal.h b/include/hal.h index 893b23ef21..5649019b30 100644 --- a/include/hal.h +++ b/include/hal.h @@ -203,7 +203,6 @@ int hal_hsm_server_cleanup(void); #endif /* WOLFBOOT_ENABLE_WOLFHSM_SERVER */ - #if defined(WOLFBOOT_ENABLE_WOLFHSM_CLIENT) || \ defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER) diff --git a/include/hooks.h b/include/hooks.h new file mode 100644 index 0000000000..9e027c9d25 --- /dev/null +++ b/include/hooks.h @@ -0,0 +1,57 @@ +/* hooks.h + * + * wolfBoot hooks API definitions. + * + * Hooks allow users to inject custom logic at well-defined points in the + * wolfBoot boot process. Each hook is independently enabled via its own + * build macro. + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot 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. + * + * wolfBoot 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef WOLFBOOT_HOOKS_H +#define WOLFBOOT_HOOKS_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct wolfBoot_image; + +#ifdef WOLFBOOT_HOOK_LOADER_PREINIT +void wolfBoot_hook_preinit(void); +#endif + +#ifdef WOLFBOOT_HOOK_LOADER_POSTINIT +void wolfBoot_hook_postinit(void); +#endif + +#ifdef WOLFBOOT_HOOK_BOOT +void wolfBoot_hook_boot(struct wolfBoot_image *boot_img); +#endif + +#ifdef WOLFBOOT_HOOK_PANIC +void wolfBoot_hook_panic(void); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFBOOT_HOOKS_H */ diff --git a/include/loader.h b/include/loader.h index e5613a1347..9d2ef37653 100644 --- a/include/loader.h +++ b/include/loader.h @@ -55,21 +55,38 @@ extern "C" { void wolfBoot_start(void); +#include "hooks.h" + #if defined(ARCH_ARM) && defined(WOLFBOOT_ARMORED) /* attempt to jump 5 times to self, causing loop that cannot be glitched past */ -#define wolfBoot_panic() \ +#ifdef WOLFBOOT_HOOK_PANIC +#define wolfBoot_panic() do { \ + wolfBoot_hook_panic(); \ + asm volatile("b ."); \ + asm volatile("b .-2"); \ + asm volatile("b .-4"); \ + asm volatile("b .-6"); \ + asm volatile("b .-8"); \ + } while(0) +#else +#define wolfBoot_panic() do { \ asm volatile("b ."); \ asm volatile("b .-2"); \ asm volatile("b .-4"); \ asm volatile("b .-6"); \ - asm volatile("b .-8"); + asm volatile("b .-8"); \ + } while(0) +#endif #elif defined(ARCH_SIM) #include #include static inline void wolfBoot_panic(void) { +#ifdef WOLFBOOT_HOOK_PANIC + wolfBoot_hook_panic(); +#endif fprintf(stderr, "wolfBoot: PANIC!\n"); exit('P'); } @@ -77,6 +94,9 @@ static inline void wolfBoot_panic(void) static int wolfBoot_panicked = 0; static inline void wolfBoot_panic(void) { +#ifdef WOLFBOOT_HOOK_PANIC + wolfBoot_hook_panic(); +#endif fprintf(stderr, "wolfBoot: PANIC!\n"); wolfBoot_panicked++; } @@ -84,6 +104,9 @@ static inline void wolfBoot_panic(void) #include "printf.h" static inline void wolfBoot_panic(void) { +#ifdef WOLFBOOT_HOOK_PANIC + wolfBoot_hook_panic(); +#endif wolfBoot_printf("wolfBoot: PANIC!\n"); #ifdef WOLFBOOT_FSP extern void panic(void); diff --git a/options.mk b/options.mk index ff57622796..94de9fec5f 100644 --- a/options.mk +++ b/options.mk @@ -1149,6 +1149,33 @@ ifeq ($(WOLFHSM_SERVER),1) $(WOLFBOOT_LIB_WOLFHSM)/src/wh_server_cert.o CFLAGS += -DWOLFHSM_CFG_CERTIFICATE_MANAGER endif + +endif + +# wolfBoot hooks framework +# WOLFBOOT_HOOKS_FILE: path to a single .c file containing hook definitions +WOLFBOOT_HOOKS_ENABLED := +ifeq ($(WOLFBOOT_HOOK_LOADER_PREINIT),1) + CFLAGS += -DWOLFBOOT_HOOK_LOADER_PREINIT + WOLFBOOT_HOOKS_ENABLED := 1 +endif +ifeq ($(WOLFBOOT_HOOK_LOADER_POSTINIT),1) + CFLAGS += -DWOLFBOOT_HOOK_LOADER_POSTINIT + WOLFBOOT_HOOKS_ENABLED := 1 +endif +ifeq ($(WOLFBOOT_HOOK_BOOT),1) + CFLAGS += -DWOLFBOOT_HOOK_BOOT + WOLFBOOT_HOOKS_ENABLED := 1 +endif +ifeq ($(WOLFBOOT_HOOK_PANIC),1) + CFLAGS += -DWOLFBOOT_HOOK_PANIC + WOLFBOOT_HOOKS_ENABLED := 1 +endif +ifneq ($(WOLFBOOT_HOOKS_ENABLED),) + ifeq ($(WOLFBOOT_HOOKS_FILE),) + $(error WOLFBOOT_HOOKS_FILE must be set to a .c file when hooks are enabled) + endif + OBJS += $(patsubst %.c,%.o,$(WOLFBOOT_HOOKS_FILE)) endif # Cert chain verification options diff --git a/src/loader.c b/src/loader.c index 03681d9edd..4cdb8d6b34 100644 --- a/src/loader.c +++ b/src/loader.c @@ -32,6 +32,7 @@ #include "loader.h" #include "image.h" #include "hal.h" +#include "hooks.h" #include "spi_flash.h" #ifdef UART_FLASH #include "uart_flash.h" @@ -101,6 +102,9 @@ int main(void) main_argc = argc; #endif +#ifdef WOLFBOOT_HOOK_LOADER_PREINIT + wolfBoot_hook_preinit(); +#endif hal_init(); #ifdef TEST_FLASH hal_flash_test(); @@ -124,6 +128,9 @@ int main(void) #endif #ifdef WOLFCRYPT_SECURE_MODE wcs_Init(); +#endif +#ifdef WOLFBOOT_HOOK_LOADER_POSTINIT + wolfBoot_hook_postinit(); #endif wolfBoot_start(); diff --git a/src/update_disk.c b/src/update_disk.c index cdf815cd91..7ea38e8e07 100644 --- a/src/update_disk.c +++ b/src/update_disk.c @@ -39,6 +39,7 @@ #include "image.h" #include "loader.h" #include "hal.h" +#include "hooks.h" #include "spi_flash.h" #include "printf.h" #include "wolfboot/wolfboot.h" @@ -502,6 +503,9 @@ void RAMFUNCTION wolfBoot_start(void) #endif hal_prepare_boot(); +#ifdef WOLFBOOT_HOOK_BOOT + wolfBoot_hook_boot(&os_image); +#endif do_boot((uint32_t*)load_address #ifdef MMU ,(uint32_t*)dts_addr diff --git a/src/update_flash.c b/src/update_flash.c index 054837d898..574ea1be1f 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -26,6 +26,7 @@ #include "loader.h" #include "image.h" #include "hal.h" +#include "hooks.h" #include "spi_flash.h" #include "target.h" #include "wolfboot/wolfboot.h" @@ -1419,6 +1420,9 @@ void RAMFUNCTION wolfBoot_start(void) hal_prepare_boot(); +#ifdef WOLFBOOT_HOOK_BOOT + wolfBoot_hook_boot(&boot); +#endif do_boot((void *)boot.fw_base); } diff --git a/src/update_flash_hwswap.c b/src/update_flash_hwswap.c index ec3816e5da..305e919d32 100644 --- a/src/update_flash_hwswap.c +++ b/src/update_flash_hwswap.c @@ -25,6 +25,7 @@ #include "loader.h" #include "image.h" #include "hal.h" +#include "hooks.h" #include "spi_flash.h" #include "wolfboot/wolfboot.h" #ifdef SECURE_PKCS11 @@ -104,5 +105,8 @@ void RAMFUNCTION wolfBoot_start(void) (void)hal_hsm_server_cleanup(); #endif hal_prepare_boot(); +#ifdef WOLFBOOT_HOOK_BOOT + wolfBoot_hook_boot(&fw_image); +#endif do_boot((void *)(WOLFBOOT_PARTITION_BOOT_ADDRESS + IMAGE_HEADER_SIZE)); } diff --git a/src/update_ram.c b/src/update_ram.c index d415fc7602..833a7025ae 100644 --- a/src/update_ram.c +++ b/src/update_ram.c @@ -25,6 +25,7 @@ #include "image.h" #include "loader.h" #include "hal.h" +#include "hooks.h" #include "spi_flash.h" #include "printf.h" #include "wolfboot/wolfboot.h" @@ -389,6 +390,9 @@ void RAMFUNCTION wolfBoot_start(void) hal_prepare_boot(); +#ifdef WOLFBOOT_HOOK_BOOT + wolfBoot_hook_boot(&os_image); +#endif #ifdef MMU do_boot((uint32_t*)load_address, (uint32_t*)dts_addr);