From 8fcc6e5551431458de9af719e7838c175ef8cf02 Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:31:48 -0600 Subject: [PATCH 1/4] monolithic self-updates: force DISABLE_BACKUP=1, eliminate swap, eliminate update code --- Makefile | 2 +- .../sim-self-update-monolithic.config | 1 - include/wolfboot/wolfboot.h | 16 ++++++++++ options.mk | 2 ++ src/update_flash.c | 30 ++++++++++++++++++- tools/test.mk | 8 ++--- 6 files changed, 51 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index a5a81c18b5..6f24b241d3 100644 --- a/Makefile +++ b/Makefile @@ -463,7 +463,7 @@ assemble_internal_flash.dd: FORCE 0 wolfboot.bin \ $$(($(WOLFBOOT_PARTITION_BOOT_ADDRESS) - $(ARCH_FLASH_OFFSET))) test-app/image_v1_signed.bin \ $$(($(WOLFBOOT_PARTITION_UPDATE_ADDRESS)-$(ARCH_FLASH_OFFSET))) /tmp/swap \ - $$(($(WOLFBOOT_PARTITION_SWAP_ADDRESS)-$(ARCH_FLASH_OFFSET))) /tmp/swap + $(if $(DISABLE_BACKUP),,$$(($(WOLFBOOT_PARTITION_SWAP_ADDRESS)-$(ARCH_FLASH_OFFSET))) /tmp/swap) # swap unused with DISABLE_BACKUP internal_flash.dd: $(BINASSEMBLE) wolfboot.bin $(BOOT_IMG) $(PRIVATE_KEY) test-app/image_v1_signed.bin @echo "\t[MERGE] internal_flash.dd" diff --git a/config/examples/sim-self-update-monolithic.config b/config/examples/sim-self-update-monolithic.config index a2d0597c46..c9325844d3 100644 --- a/config/examples/sim-self-update-monolithic.config +++ b/config/examples/sim-self-update-monolithic.config @@ -14,7 +14,6 @@ WOLFBOOT_PARTITION_SIZE=0x40000 WOLFBOOT_SECTOR_SIZE=0x1000 WOLFBOOT_PARTITION_BOOT_ADDRESS=0x20000 WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x60000 -WOLFBOOT_PARTITION_SWAP_ADDRESS=0xA0000 # required for keytools WOLFBOOT_FIXED_PARTITIONS=1 diff --git a/include/wolfboot/wolfboot.h b/include/wolfboot/wolfboot.h index a6e79d708d..d9ee535fa5 100644 --- a/include/wolfboot/wolfboot.h +++ b/include/wolfboot/wolfboot.h @@ -464,6 +464,22 @@ extern "C" { #endif /* defined WOLFBOOT */ +/* Monolithic self-update: imply DISABLE_BACKUP and enforce prerequisites */ +#ifdef WOLFBOOT_SELF_UPDATE_MONOLITHIC + #ifndef DISABLE_BACKUP + #define DISABLE_BACKUP + #endif + #ifdef DELTA_UPDATES + #error "DELTA_UPDATES is not compatible with WOLFBOOT_SELF_UPDATE_MONOLITHIC" + #endif + #ifdef NVM_FLASH_WRITEONCE + #error "NVM_FLASH_WRITEONCE is not compatible with WOLFBOOT_SELF_UPDATE_MONOLITHIC" + #endif + #ifndef RAM_CODE + #error "WOLFBOOT_SELF_UPDATE_MONOLITHIC requires RAM_CODE" + #endif +#endif + #define PART_BOOT 0 #define PART_UPDATE 1 #define PART_SWAP 2 diff --git a/options.mk b/options.mk index ff8df2c4ad..ab4f166d70 100644 --- a/options.mk +++ b/options.mk @@ -92,6 +92,7 @@ endif ## the bootloader region into the contiguous boot partition. ifeq ($(SELF_UPDATE_MONOLITHIC),1) CFLAGS+=-DWOLFBOOT_SELF_UPDATE_MONOLITHIC + DISABLE_BACKUP=1 endif ## Persist wolfBoot self header at fixed address @@ -719,6 +720,7 @@ endif ifeq ($(DISABLE_BACKUP),1) CFLAGS+= -D"DISABLE_BACKUP" + WOLFBOOT_PARTITION_SWAP_ADDRESS?=0 endif DEBUG_SYMBOLS?=0 diff --git a/src/update_flash.c b/src/update_flash.c index 2ead2448be..52d2aaeb37 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -233,6 +233,10 @@ void RAMFUNCTION wolfBoot_check_self_update(void) } #endif /* RAM_CODE for self_update */ +#ifndef WOLFBOOT_SELF_UPDATE_MONOLITHIC +/* The swap-based update machinery (wolfBoot_copy_sector, wolfBoot_update, etc.) + * is not used in monolithic self-update mode. */ + static int RAMFUNCTION wolfBoot_copy_sector(struct wolfBoot_image *src, struct wolfBoot_image *dst, uint32_t sector) { @@ -804,7 +808,10 @@ static int RAMFUNCTION wolfBoot_update(int fallback_allowed) * magic has not been set flag will have an un-determined value when we go * to check it */ uint8_t flag = SECT_FLAG_NEW; - struct wolfBoot_image boot, update, swap; + struct wolfBoot_image boot, update; +#ifndef DISABLE_BACKUP + struct wolfBoot_image swap; +#endif uint16_t update_type; uint32_t fw_size; uint32_t size; @@ -856,7 +863,9 @@ static int RAMFUNCTION wolfBoot_update(int fallback_allowed) return -1; #endif wolfBoot_open_image(&boot, PART_BOOT); +#ifndef DISABLE_BACKUP wolfBoot_open_image(&swap, PART_SWAP); +#endif #if defined(EXT_ENCRYPTED) && defined(DELTA_UPDATES) wolfBoot_printf("Update partition fallback image: %d\n", fallback_image); @@ -1208,6 +1217,7 @@ static int RAMFUNCTION wolfBoot_update(int fallback_allowed) #ifdef __CCRX__ #pragma section #endif +#endif /* !WOLFBOOT_SELF_UPDATE_MONOLITHIC */ #if defined(ARCH_SIM) && defined(WOLFBOOT_TPM) && defined(WOLFBOOT_TPM_SEAL) int wolfBoot_unlock_disk(void) @@ -1319,12 +1329,14 @@ int wolfBoot_unlock_disk(void) void RAMFUNCTION wolfBoot_start(void) { int bootRet; +#ifndef WOLFBOOT_SELF_UPDATE_MONOLITHIC int updateRet; #ifndef DISABLE_BACKUP int resumedFinalErase; #endif uint8_t bootState; uint8_t updateState; +#endif /* !WOLFBOOT_SELF_UPDATE_MONOLITHIC */ struct wolfBoot_image boot; #if defined(ARCH_SIM) && defined(WOLFBOOT_TPM) && defined(WOLFBOOT_TPM_SEAL) @@ -1335,6 +1347,8 @@ void RAMFUNCTION wolfBoot_start(void) wolfBoot_check_self_update(); #endif +#ifndef WOLFBOOT_SELF_UPDATE_MONOLITHIC + #ifdef NVM_FLASH_WRITEONCE /* nvm_select_fresh_sector needs unlocked flash in cases where the unused * sector needs to be erased */ @@ -1393,6 +1407,12 @@ void RAMFUNCTION wolfBoot_start(void) } } +#else /* WOLFBOOT_SELF_UPDATE_MONOLITHIC */ +#ifdef SECURE_PKCS11 + WP11_Library_Init(); +#endif +#endif /* !WOLFBOOT_SELF_UPDATE_MONOLITHIC */ + bootRet = wolfBoot_open_image(&boot, PART_BOOT); wolfBoot_printf("Booting version: 0x%x\n", wolfBoot_get_blob_version(boot.hdr)); @@ -1404,6 +1424,7 @@ void RAMFUNCTION wolfBoot_start(void) ) { wolfBoot_printf("Boot failed: Hdr %d, Hash %d, Sig %d\n", boot.hdr_ok, boot.sha_ok, boot.signature_ok); +#ifndef WOLFBOOT_SELF_UPDATE_MONOLITHIC wolfBoot_printf("Trying emergency update\n"); if (likely(wolfBoot_update(1) < 0)) { /* panic: no boot option available. */ @@ -1427,6 +1448,13 @@ void RAMFUNCTION wolfBoot_start(void) wolfBoot_panic(); } } +#else + /* Monolithic mode: no emergency update path available */ + #ifdef WOLFBOOT_TPM + wolfBoot_tpm2_deinit(); + #endif + wolfBoot_panic(); +#endif /* !WOLFBOOT_SELF_UPDATE_MONOLITHIC */ } PART_SANITY_CHECK(&boot); #else diff --git a/tools/test.mk b/tools/test.mk index 0217ea21d1..591cc75071 100644 --- a/tools/test.mk +++ b/tools/test.mk @@ -282,15 +282,13 @@ test-sim-self-update-monolithic: wolfboot.bin test-app/image_v1_signed.bin FORCE $(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > update_part.dd $(Q)dd if=monolithic_payload_v2_signed.bin of=update_part.dd bs=1 conv=notrunc $(Q)printf "pBOOT" | dd of=update_part.dd bs=1 seek=$$(($(WOLFBOOT_PARTITION_SIZE) - 5)) conv=notrunc - @# Create erased boot and swap partitions + @# Create erased boot partition $(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > boot_part.dd - $(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_SECTOR_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > erased_sec.dd - @# Assemble flash: wolfboot.bin at 0, empty boot partition, update partition, swap + @# 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 \ - $$(($(WOLFBOOT_PARTITION_SWAP_ADDRESS) - $(ARCH_FLASH_OFFSET))) erased_sec.dd + $$(($(WOLFBOOT_PARTITION_UPDATE_ADDRESS) - $(ARCH_FLASH_OFFSET))) update_part.dd @# Run simulator - self-update fires, copies monolithic payload to offset 0 $(Q)./wolfboot.elf get_version || true @# Verify bootloader region contains 0xAA pattern (dummy bootloader was written) From 9e8177fa38750d74ebc8e68272f7ffefe4a1d3b0 Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Wed, 1 Apr 2026 14:26:38 -0600 Subject: [PATCH 2/4] add ci test coverage for monolithic and self-update combined cases --- .github/workflows/test-sim-self-update.yml | 13 ++++++ .../sim-self-update-monolithic.config | 7 +-- tools/test.mk | 43 +++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-sim-self-update.yml b/.github/workflows/test-sim-self-update.yml index 3726de44fb..28c3eea314 100644 --- a/.github/workflows/test-sim-self-update.yml +++ b/.github/workflows/test-sim-self-update.yml @@ -32,6 +32,12 @@ jobs: cp config/examples/sim-self-update-ext.config .config make test-sim-self-update-ext + - name: Run monolithic self-update test + run: | + make clean + cp config/examples/sim-self-update-monolithic.config .config + make test-sim-self-update-monolithic + - name: Run self-header verification test (internal flash) run: | make clean @@ -43,3 +49,10 @@ jobs: make clean cp config/examples/sim-self-header-ext.config .config make test-sim-self-header-ext-verify + + - name: Run combined monolithic self-update + self-header test + run: | + make clean + cp config/examples/sim-self-update-monolithic.config .config + make test-sim-self-update-monolithic-self-header WOLFBOOT_SELF_HEADER=1 WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS=0x1F000 + diff --git a/config/examples/sim-self-update-monolithic.config b/config/examples/sim-self-update-monolithic.config index c9325844d3..0659d80897 100644 --- a/config/examples/sim-self-update-monolithic.config +++ b/config/examples/sim-self-update-monolithic.config @@ -9,11 +9,12 @@ RAM_CODE=1 SELF_UPDATE_MONOLITHIC=1 WOLFBOOT_VERSION=1 -# sizes should be multiple of system page size -WOLFBOOT_PARTITION_SIZE=0x40000 +# Partition size 512KB so the monolithic payload (bootloader region + app) +# fits in the UPDATE partition. +WOLFBOOT_PARTITION_SIZE=0x80000 WOLFBOOT_SECTOR_SIZE=0x1000 WOLFBOOT_PARTITION_BOOT_ADDRESS=0x20000 -WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x60000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS=0xA0000 # required for keytools WOLFBOOT_FIXED_PARTITIONS=1 diff --git a/tools/test.mk b/tools/test.mk index 591cc75071..8e60328767 100644 --- a/tools/test.mk +++ b/tools/test.mk @@ -300,6 +300,49 @@ test-sim-self-update-monolithic: wolfboot.bin test-app/image_v1_signed.bin FORCE @echo " Nested app at boot partition: PASSED" @echo "=== Monolithic Self-Update Test PASSED ===" +# Test monolithic self-update with self-header persistence. +# Same as test-sim-self-update-monolithic, but also verifies that the +# self-header was persisted at WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS and +# matches the signing tool's --header-only output byte-for-byte. +# +test-sim-self-update-monolithic-self-header: wolfboot.bin test-app/image_v1_signed.bin FORCE + @echo "=== Simulator Monolithic Self-Update + Self-Header 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 + @# Concatenate dummy bootloader + signed app image to form monolithic payload + $(Q)cat monolithic_dummy_bl.bin test-app/image_v1_signed.bin > monolithic_payload.bin + @# Sign monolithic payload as wolfBoot self-update v2, and generate header-only + $(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) --wolfboot-update monolithic_payload.bin $(PRIVATE_KEY) 2 + $(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) --wolfboot-update --header-only monolithic_payload.bin $(PRIVATE_KEY) 2 + @# Create update partition with signed monolithic image and "pBOOT" trailer + $(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > update_part.dd + $(Q)dd if=monolithic_payload_v2_signed.bin of=update_part.dd bs=1 conv=notrunc + $(Q)printf "pBOOT" | dd of=update_part.dd bs=1 seek=$$(($(WOLFBOOT_PARTITION_SIZE) - 5)) conv=notrunc + @# Create erased boot partition and self-header sector + $(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > boot_part.dd + $(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_SECTOR_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > self_hdr.dd + @# Assemble flash: wolfboot.bin at 0, erased self-header, empty boot partition, update partition + $(Q)$(BINASSEMBLE) internal_flash.dd \ + 0 wolfboot.bin \ + $$(($(WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS) - $(ARCH_FLASH_OFFSET))) self_hdr.dd \ + $$(($(WOLFBOOT_PARTITION_BOOT_ADDRESS) - $(ARCH_FLASH_OFFSET))) boot_part.dd \ + $$(($(WOLFBOOT_PARTITION_UPDATE_ADDRESS) - $(ARCH_FLASH_OFFSET))) update_part.dd + @# Run simulator - self-update fires, copies monolithic payload to offset 0 and persists header + $(Q)./wolfboot.elf get_version || true + @# Verify bootloader region up to self-header contains 0xAA pattern + @# (the self-header sector itself is overwritten by wolfBoot_update_self_header) + $(Q)cmp -n $$(($(WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS) - $(ARCH_FLASH_OFFSET))) monolithic_dummy_bl.bin internal_flash.dd + @echo " Bootloader region 0xAA pattern: PASSED" + @# Extract nested app from boot partition and verify it matches original signed app + $(Q)dd if=internal_flash.dd bs=1 skip=$$(($(WOLFBOOT_PARTITION_BOOT_ADDRESS) - $(ARCH_FLASH_OFFSET))) count=$$(wc -c < test-app/image_v1_signed.bin | awk '{print $$1}') of=nested_app.dd 2>/dev/null + $(Q)cmp nested_app.dd test-app/image_v1_signed.bin + @echo " Nested app at boot partition: PASSED" + @# Extract persisted self-header and verify it matches --header-only output + $(Q)dd if=internal_flash.dd bs=1 skip=$$(($(WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS) - $(ARCH_FLASH_OFFSET))) count=$$(($(WOLFBOOT_SECTOR_SIZE))) of=persisted_hdr.dd 2>/dev/null + $(Q)cmp -n $$(($(IMAGE_HEADER_SIZE))) persisted_hdr.dd monolithic_payload_v2_header.bin + @echo " Self-header persisted correctly: PASSED" + @echo "=== Monolithic Self-Update + Self-Header Test PASSED ===" + # Test self-header cryptographic verification (hash + signature validation) # # Verifies that an application can cryptographically verify the bootloader using From 11e91f8b7231d3e8c953555f766d90c2a78c7da8 Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Wed, 1 Apr 2026 14:52:16 -0600 Subject: [PATCH 3/4] review: fix Makefile conditional --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6f24b241d3..0118ed8777 100644 --- a/Makefile +++ b/Makefile @@ -463,7 +463,7 @@ assemble_internal_flash.dd: FORCE 0 wolfboot.bin \ $$(($(WOLFBOOT_PARTITION_BOOT_ADDRESS) - $(ARCH_FLASH_OFFSET))) test-app/image_v1_signed.bin \ $$(($(WOLFBOOT_PARTITION_UPDATE_ADDRESS)-$(ARCH_FLASH_OFFSET))) /tmp/swap \ - $(if $(DISABLE_BACKUP),,$$(($(WOLFBOOT_PARTITION_SWAP_ADDRESS)-$(ARCH_FLASH_OFFSET))) /tmp/swap) # swap unused with DISABLE_BACKUP + $(if $(filter 1,$(DISABLE_BACKUP)),,$$(($(WOLFBOOT_PARTITION_SWAP_ADDRESS)-$(ARCH_FLASH_OFFSET))) /tmp/swap) # omit swap partition if DISABLE_BACKUP=1 internal_flash.dd: $(BINASSEMBLE) wolfboot.bin $(BOOT_IMG) $(PRIVATE_KEY) test-app/image_v1_signed.bin @echo "\t[MERGE] internal_flash.dd" From fe043495738f715d9b48038b9fde40b8f0973bff Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:00:46 -0600 Subject: [PATCH 4/4] add additional check for DELTA_UPDATE + DISABLE_BACKUP incompatibility --- include/wolfboot/wolfboot.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/wolfboot/wolfboot.h b/include/wolfboot/wolfboot.h index d9ee535fa5..fec38642a1 100644 --- a/include/wolfboot/wolfboot.h +++ b/include/wolfboot/wolfboot.h @@ -480,6 +480,10 @@ extern "C" { #endif #endif +#if defined(DISABLE_BACKUP) && defined(DELTA_UPDATES) + #error "DELTA_UPDATES requires swap partition (incompatible with DISABLE_BACKUP)" +#endif + #define PART_BOOT 0 #define PART_UPDATE 1 #define PART_SWAP 2