Skip to content

Commit 999a450

Browse files
bigbrettdanielinux
authored andcommitted
add monoloithic self-update test case and docs
1 parent 3f18028 commit 999a450

3 files changed

Lines changed: 127 additions & 0 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
ARCH=sim
2+
TARGET=sim
3+
SIGN?=ED25519
4+
HASH?=SHA256
5+
WOLFBOOT_SMALL_STACK?=0
6+
SPI_FLASH=0
7+
DEBUG=1
8+
RAM_CODE=1
9+
WOLFBOOT_VERSION=1
10+
11+
# sizes should be multiple of system page size
12+
WOLFBOOT_PARTITION_SIZE=0x40000
13+
WOLFBOOT_SECTOR_SIZE=0x1000
14+
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x20000
15+
WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x60000
16+
WOLFBOOT_PARTITION_SWAP_ADDRESS=0xA0000
17+
18+
# required for keytools
19+
WOLFBOOT_FIXED_PARTITIONS=1

docs/firmware_update.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,78 @@ regular signed image at the bootloader origin.
200200
See [Signing.md](Signing.md#header-only-output-wolfboot-self-header) for
201201
more detail on the `--header-only` sign tool option.
202202

203+
#### Monolithic updates
204+
205+
The self-update mechanism can be used to update both the bootloader
206+
**and** the application firmware in a single atomic operation. Because
207+
`wolfBoot_self_update()` copies `fw_size` bytes from the update image to
208+
`ARCH_FLASH_OFFSET`, a payload that is larger than the bootloader region
209+
will spill into the contiguous BOOT partition, overwriting whatever
210+
application image was there previously.
211+
212+
No wolfBoot code changes are required — only the update payload needs to
213+
be assembled differently. The payload is constructed by concatenating the
214+
new bootloader binary with the new signed application image and signing
215+
the result as a wolfBoot self-update:
216+
217+
```
218+
cat wolfboot.bin image_v1_signed.bin > monolithic_payload.bin
219+
sign --wolfboot-update monolithic_payload.bin key.der 2
220+
```
221+
222+
After the self-update completes, flash looks like:
223+
224+
```
225+
ARCH_FLASH_OFFSET BOOT_ADDRESS
226+
| |
227+
v v
228+
[ new bootloader bytes | padding | new signed app image ]
229+
<-------- fw_size ------------------------------------------->
230+
```
231+
232+
##### Self-header interaction
233+
234+
When `WOLFBOOT_SELF_HEADER` is enabled, the persisted header retains the
235+
`fw_size`, hash and signature exactly as the signing tool produced them.
236+
The hash covers the **entire** monolithic payload — both the bootloader
237+
bytes and the nested application image. Later calls to
238+
`wolfBoot_open_self()` / `wolfBoot_verify_integrity()` will re-hash
239+
`fw_size` bytes starting at `ARCH_FLASH_OFFSET`, spanning into the BOOT
240+
partition.
241+
242+
##### Restrictions
243+
244+
- **Not power-fail safe.** Like all self-updates, a monolithic update
245+
erases the bootloader region and writes in-place. An interruption
246+
during the write leaves the device unbootable. Additionally, the BOOT
247+
partition is written without a prior erase — this relies on the
248+
partition being in an erased (0xFF) state, which is only guaranteed
249+
when the device has no prior application installed or the partition has
250+
been explicitly erased beforehand.
251+
252+
- **Not revertable.** There is no swap or rollback mechanism. The old
253+
bootloader and application are destroyed during the update.
254+
255+
- **Locks bootloader verification to a specific application version.**
256+
Because the self-header hash covers the full monolithic image, any
257+
independent application update will invalidate the persisted
258+
self-header. To maintain a valid self-header, both components must
259+
always be updated together as a single monolithic payload.
260+
261+
- **Payload must fit in the UPDATE partition.** The signed monolithic
262+
image (header + bootloader + signed application) plus the 5-byte
263+
`pBOOT` trailer must not exceed `WOLFBOOT_PARTITION_SIZE`.
264+
265+
##### Simulator test
266+
267+
A simulator test is provided in `tools/test.mk` to exercise this use case:
268+
269+
```
270+
cp config/examples/sim-self-update-monolithic.config .config
271+
make clean && make
272+
make test-sim-self-update-monolithic
273+
```
274+
203275
### Incremental updates (aka: 'delta' updates)
204276

205277
wolfBoot supports incremental updates, based on a specific older version. The sign tool

tools/test.mk

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,42 @@ test-sim-self-update: wolfboot.bin FORCE
266266
@# Verify dummy payload was written to bootloader region, indicating the self update swapped images as expected
267267
$(Q)cmp -n $$(wc -c < dummy_update.bin | awk '{print $$1}') dummy_update.bin internal_flash.dd && echo "=== Self-update test PASSED ==="
268268

269+
# Test monolithic self-update mechanism using simulator. A monolithic self-update
270+
# updates both the bootloader AND the boot partition application image in a single
271+
# operation by crafting a payload that spans the bootloader region and spills into
272+
# the contiguous boot partition.
273+
test-sim-self-update-monolithic: wolfboot.bin test-app/image_v1_signed.bin FORCE
274+
@echo "=== Simulator Monolithic Self-Update Test ==="
275+
@# Create dummy bootloader (0xAA pattern, exactly BOOTLOADER_PARTITION_SIZE)
276+
$(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
277+
@# Concatenate dummy bootloader + signed app image to form monolithic payload
278+
$(Q)cat monolithic_dummy_bl.bin test-app/image_v1_signed.bin > monolithic_payload.bin
279+
@# Sign monolithic payload as wolfBoot self-update v2
280+
$(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) --wolfboot-update monolithic_payload.bin $(PRIVATE_KEY) 2
281+
@# Create update partition with signed monolithic image and "pBOOT" trailer
282+
$(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > update_part.dd
283+
$(Q)dd if=monolithic_payload_v2_signed.bin of=update_part.dd bs=1 conv=notrunc
284+
$(Q)printf "pBOOT" | dd of=update_part.dd bs=1 seek=$$(($(WOLFBOOT_PARTITION_SIZE) - 5)) conv=notrunc
285+
@# Create erased boot and swap partitions
286+
$(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > boot_part.dd
287+
$(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_SECTOR_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > erased_sec.dd
288+
@# Assemble flash: wolfboot.bin at 0, empty boot partition, update partition, swap
289+
$(Q)$(BINASSEMBLE) internal_flash.dd \
290+
0 wolfboot.bin \
291+
$$(($(WOLFBOOT_PARTITION_BOOT_ADDRESS) - $(ARCH_FLASH_OFFSET))) boot_part.dd \
292+
$$(($(WOLFBOOT_PARTITION_UPDATE_ADDRESS) - $(ARCH_FLASH_OFFSET))) update_part.dd \
293+
$$(($(WOLFBOOT_PARTITION_SWAP_ADDRESS) - $(ARCH_FLASH_OFFSET))) erased_sec.dd
294+
@# Run simulator - self-update fires, copies monolithic payload to offset 0
295+
$(Q)./wolfboot.elf get_version || true
296+
@# Verify bootloader region contains 0xAA pattern (dummy bootloader was written)
297+
$(Q)cmp -n $$(($(WOLFBOOT_PARTITION_BOOT_ADDRESS) - $(ARCH_FLASH_OFFSET))) monolithic_dummy_bl.bin internal_flash.dd
298+
@echo " Bootloader region 0xAA pattern: PASSED"
299+
@# Extract nested app from boot partition and verify it matches original signed app
300+
$(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
301+
$(Q)cmp nested_app.dd test-app/image_v1_signed.bin
302+
@echo " Nested app at boot partition: PASSED"
303+
@echo "=== Monolithic Self-Update Test PASSED ==="
304+
269305
# Test self-header cryptographic verification (hash + signature validation)
270306
#
271307
# Verifies that an application can cryptographically verify the bootloader using

0 commit comments

Comments
 (0)