From 6ba217269553c8603d0c3f35d3dd65718e0e1da6 Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Tue, 18 Aug 2026 09:58:32 -0600 Subject: [PATCH] Add additional test coverage for monolithic self updates with asymmetric partition sizing --- .github/workflows/test-sim-self-update.yml | 25 +++++++++++++ docs/firmware_update.md | 4 ++ tools/test.mk | 43 ++++++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/.github/workflows/test-sim-self-update.yml b/.github/workflows/test-sim-self-update.yml index 28c3eea3..7d636dbc 100644 --- a/.github/workflows/test-sim-self-update.yml +++ b/.github/workflows/test-sim-self-update.yml @@ -38,6 +38,28 @@ jobs: cp config/examples/sim-self-update-monolithic.config .config make test-sim-self-update-monolithic + # Shrink BOOT below the payload size so staging only works because the + # UPDATE partition is sized independently (WOLFBOOT_PARTITION_UPDATE_SIZE) + - name: Run monolithic self-update test (payload exceeds a BOOT-sized slot) + run: | + make clean + cp config/examples/sim-self-update-monolithic.config .config + make test-sim-self-update-monolithic WOLFBOOT_PARTITION_SIZE=0x30000 WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x50000 + + # Unset WOLFBOOT_PARTITION_UPDATE_SIZE so it falls back to + # WOLFBOOT_PARTITION_SIZE, restoring the legacy symmetric layout + - name: Run monolithic self-update test (legacy symmetric layout) + run: | + make clean + cp config/examples/sim-self-update-monolithic.config .config + make test-sim-self-update-monolithic WOLFBOOT_PARTITION_SIZE=0x80000 WOLFBOOT_PARTITION_UPDATE_SIZE= WOLFBOOT_PARTITION_UPDATE_ADDRESS=0xA0000 + + - name: Run monolithic self-update oversize rejection test + run: | + make clean + cp config/examples/sim-self-update-monolithic.config .config + make test-sim-self-update-monolithic-oversize + - name: Run self-header verification test (internal flash) run: | make clean @@ -50,6 +72,9 @@ jobs: cp config/examples/sim-self-header-ext.config .config make test-sim-self-header-ext-verify + # With WOLFBOOT_SELF_HEADER=1 the app grows enough that the payload + # exceeds what a BOOT-sized slot could stage, so this step also covers + # asymmetric staging capacity - name: Run combined monolithic self-update + self-header test run: | make clean diff --git a/docs/firmware_update.md b/docs/firmware_update.md index dceea239..f062b39e 100644 --- a/docs/firmware_update.md +++ b/docs/firmware_update.md @@ -386,6 +386,10 @@ make clean && make make test-sim-self-update-monolithic ``` +A companion negative test, `test-sim-self-update-monolithic-oversize`, stages a +signed payload larger than the install span and verifies that wolfBoot refuses +it without touching the bootloader or the BOOT partition. + #### Skipping boot image verification When wolfBoot is used together with the [self-header](#self-header-persisting-the-bootloader-manifest) diff --git a/tools/test.mk b/tools/test.mk index 13176039..ff055732 100644 --- a/tools/test.mk +++ b/tools/test.mk @@ -343,6 +343,49 @@ test-sim-self-update-monolithic-self-header: wolfboot.bin test-app/image_v1_sign @echo " Self-header persisted correctly: PASSED" @echo "=== Monolithic Self-Update + Self-Header Test PASSED ===" +# Test that an oversized monolithic self-update is rejected. The payload is +# signed and staged normally, but its firmware size exceeds the install span +# (bootloader region + BOOT partition minus the trailer sector), so wolfBoot +# must refuse it and leave the bootloader and BOOT partition untouched. +# Requires a build with wolfBoot_printf output (e.g. DEBUG=1, as in the +# sim-self-update-monolithic example config). +test-sim-self-update-monolithic-oversize: wolfboot.bin test-app/image_v1_signed.bin FORCE + @echo "=== Simulator Monolithic Self-Update Oversize Rejection Test ===" + @# Create dummy bootloader (0xAA pattern, exactly bootloader region size) + $(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_BOOT_ADDRESS) - $(ARCH_FLASH_OFFSET))) count=1 2>/dev/null | tr '\000' '\252' > monolithic_dummy_bl.bin + @# Build a payload 0x100 bytes past the max install span, padded with 0xFF. + @# It still fits the UPDATE partition, so only the install-span check can + @# reject it. + $(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_BOOT_ADDRESS) - $(ARCH_FLASH_OFFSET) + $(WOLFBOOT_PARTITION_SIZE) - $(WOLFBOOT_SECTOR_SIZE) + 0x100)) count=1 2>/dev/null | tr '\000' '\377' > monolithic_oversize.bin + $(Q)cat monolithic_dummy_bl.bin test-app/image_v1_signed.bin | dd of=monolithic_oversize.bin conv=notrunc 2>/dev/null + @# Sign with an inflated update partition size: the keytool refuses + @# oversized images, and the point here is the bootloader's own guard + $(Q)$(SIGN_ENV) WOLFBOOT_PARTITION_UPDATE_SIZE=0x1000000 $(SIGN_TOOL) $(SIGN_OPTIONS) --wolfboot-update monolithic_oversize.bin $(PRIVATE_KEY) 2 + @# Create update partition with signed oversized image and "pBOOT" trailer + $(Q)dd if=/dev/zero bs=$$(($(or $(WOLFBOOT_PARTITION_UPDATE_SIZE),$(WOLFBOOT_PARTITION_SIZE)))) count=1 2>/dev/null | tr '\000' '\377' > update_part.dd + $(Q)dd if=monolithic_oversize_v2_signed.bin of=update_part.dd bs=1 conv=notrunc + $(Q)printf "pBOOT" | dd of=update_part.dd bs=1 seek=$$(($(or $(WOLFBOOT_PARTITION_UPDATE_SIZE),$(WOLFBOOT_PARTITION_SIZE)) - 5)) conv=notrunc + @# Create erased boot partition + $(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > boot_part.dd + @# Assemble flash: wolfboot.bin at 0, empty boot partition, update partition + $(Q)$(BINASSEMBLE) internal_flash.dd \ + 0 wolfboot.bin \ + $$(($(WOLFBOOT_PARTITION_BOOT_ADDRESS) - $(ARCH_FLASH_OFFSET))) boot_part.dd \ + $$(($(WOLFBOOT_PARTITION_UPDATE_ADDRESS) - $(ARCH_FLASH_OFFSET))) update_part.dd + @# Run simulator - the self-update must be refused before any flash write + $(Q)./wolfboot.elf get_version > monolithic_oversize.log 2>&1 || true + $(Q)grep -q "Self update image too large" monolithic_oversize.log || \ + { echo "Rejection message not found; simulator output:"; \ + cat monolithic_oversize.log; false; } + @echo " Oversized self-update rejected: PASSED" + @# Verify the bootloader region still contains the original wolfboot.bin + $(Q)cmp -n $$(wc -c < wolfboot.bin | awk '{print $$1}') wolfboot.bin internal_flash.dd + @echo " Bootloader region untouched: PASSED" + @# Verify the boot partition is still fully erased + $(Q)cmp -n $$(($(WOLFBOOT_PARTITION_SIZE))) boot_part.dd internal_flash.dd 0 $$(($(WOLFBOOT_PARTITION_BOOT_ADDRESS) - $(ARCH_FLASH_OFFSET))) + @echo " Boot partition untouched: PASSED" + @echo "=== Monolithic Self-Update Oversize Rejection Test PASSED ===" + # Test self-header cryptographic verification (hash + signature validation) # # Verifies that an application can cryptographically verify the bootloader using