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
188 changes: 188 additions & 0 deletions .github/workflows/test-hooks-simulator.yml
Original file line number Diff line number Diff line change
@@ -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 <stdio.h>
#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"
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
91 changes: 91 additions & 0 deletions docs/hooks.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 0 additions & 1 deletion include/hal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
57 changes: 57 additions & 0 deletions include/hooks.h
Original file line number Diff line number Diff line change
@@ -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 */
Loading
Loading