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
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
ARCH?=AURIX_TC3
TARGET?=aurix_tc3xx
AURIX_TC3_HSM=1
SIGN?=RSA4096
HASH?=SHA256
DEBUG?=0
WOLFBOOT_VERSION?=1
V?=0
SPMATH?=1
RAM_CODE?=1
EXT_FLASH?=1
EXT_BOOT=1
EXT_UPDATE=1
EXT_SWAP=1
FLAGS_INVERT=1
FLASH_MULTI_SECTOR_ERASE=1
DEBUG_UART=1
PRINTF_ENABLED=1

# wolfHSM options
WOLFHSM_SERVER=1

# Cert chain options
CERT_CHAIN_VERIFY=1

# RSA4096 cert chains need the larger header and stack
WOLFBOOT_HUGE_STACK=1
IMAGE_HEADER_SIZE=4096

# self-header feature (persist header in external flash)
WOLFBOOT_SELF_HEADER=1
SELF_HEADER_EXT=1

# Monolithic updates
SELF_UPDATE_MONOLITHIC=1

# Enable wolfBoot hooks
WOLFBOOT_HOOK_LOADER_POSTINIT=1
WOLFBOOT_HOOKS_FILE=../wolfBoot-callbacks/wolfboot_hooks.c

ARCH_FLASH_OFFSET=0x80028000
WOLFBOOT_SECTOR_SIZE=0x4000
WOLFBOOT_PARTITION_SIZE=0x30000
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80038000
WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x80068000
WOLFBOOT_PARTITION_SWAP_ADDRESS=0x80098000
WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS=0x8009C000
20 changes: 20 additions & 0 deletions config/examples/sim-self-update-monolithic.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
ARCH=sim
TARGET=sim
SIGN?=ED25519
HASH?=SHA256
WOLFBOOT_SMALL_STACK?=0
SPI_FLASH=0
DEBUG=1
RAM_CODE=1
SELF_UPDATE_MONOLITHIC=1
WOLFBOOT_VERSION=1

# sizes should be multiple of system page size
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
114 changes: 114 additions & 0 deletions docs/firmware_update.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,120 @@ regular signed image at the bootloader origin.
See [Signing.md](Signing.md#header-only-output-wolfboot-self-header) for
more detail on the `--header-only` sign tool option.

#### Monolithic updates

The self-update mechanism can be used to update both the bootloader
**and** the application firmware in a single operation. Because
`wolfBoot_self_update()` copies `fw_size` bytes from the update image to
`ARCH_FLASH_OFFSET`, a payload that is larger than the bootloader region
will spill into the contiguous BOOT partition, overwriting whatever
application image was there previously.

To enable this behavior, set `SELF_UPDATE_MONOLITHIC=1` in your build
configuration. The payload should be constructed by concatenating the
new bootloader binary with the new signed application image and signing
the result as a wolfBoot self-update. Note that the user must ensure that padding
is supplied such that the header of the new signed app image will be located
at an offset of `WOLFBOOT_PARTITION_BOOT_ADDRESS` from the base of the binary.


The following pseudo-shell-script demonstrates how to use standard CLI tools to
build this padded image, where `$PRIVATE_KEY`, `$ARCH_FLASH_OFFSET` and
`$WOLFBOOT_PARTITION_BOOT_ADDRESS` are the wolfBoot config variables that
correspond to your platform

```sh
# Sign your app image as v2 for inclusion in the monolithic payload. This generates test-app/image_v2_signed.bin
tools/keytools/sign test-app/image.bin $(PRIVATE_KEY) 2

# Create padded wolfboot v2 binary file (0xFF fill to exact bootloader region size)
# Bootloader region = $WOLFBOOT_PARTITION_BOOT_ADDRESS - $ARCH_FLASH_OFFSET
dd if=/dev/zero bs=$$(($WOLFBOOT_PARTITION_BOOT_ADDRESS - $ARCH_FLASH_OFFSET)) count=1 2>/dev/null | tr '\000' '\377' > wolfboot_v2_padded.bin
dd if=wolfboot.bin of=wolfboot_v2_padded.bin conv=notrunc 2>/dev/null

# Concatenate padded bootloader v2 + signed app v2 to form the monolithic payload
cat wolfboot_v2_padded.bin test-app/image_v2_signed.bin > monolithic_payload.bin
# Sign the monolithic payload as a wolfBoot self-update v2
tools/keytools/sign --wolfboot-update monolithic_payload.bin $PRIVATE_KEY 2
```

After the self-update completes, flash looks like:

```
ARCH_FLASH_OFFSET WOLFBOOT_PARTITION_BOOT_ADDRESS
| |
v v
[ new bootloader bytes | padding | new signed app image ]
<-------- fw_size ------------------------------------------->
```

##### Self-header interaction

When `WOLFBOOT_SELF_HEADER` is enabled, the persisted header retains the
`fw_size`, hash and signature exactly as the signing tool produced them.
The hash covers the **entire** monolithic payload — both the bootloader
bytes and the nested application image. Later calls to
`wolfBoot_open_self()` / `wolfBoot_verify_integrity()` will re-hash
`fw_size` bytes starting at `ARCH_FLASH_OFFSET`, spanning into the BOOT
partition.

##### Restrictions

- **Not power-fail safe.** Like all self-updates, a monolithic update
erases the bootloader region and writes in-place. An interruption
during the write leaves the device unbootable.

- **Not revertible.** There is no swap or rollback mechanism. The old
bootloader and application are destroyed during the update.

- **Locks bootloader verification to a specific application version.**
Because the self-header hash covers the full monolithic image, any
independent application update will invalidate the persisted
self-header. To maintain a valid self-header, both components must
always be updated together as a single monolithic payload.

- **Payload must fit in the UPDATE partition.** The signed monolithic
image (header + bootloader + signed application) plus the 5-byte
`pBOOT` trailer must not exceed `WOLFBOOT_PARTITION_SIZE`.

##### Simulator test

A simulator test is provided in `tools/test.mk` to exercise this use case:

```
cp config/examples/sim-self-update-monolithic.config .config
make clean && make
make test-sim-self-update-monolithic
```

#### Skipping boot image verification

When wolfBoot is used together with the [self-header](#self-header-persisting-the-bootloader-manifest)
and [monolithic updates](#monolithic-updates) features, an external verifier such as
[wolfHSM](wolfHSM.md) can verify the combined bootloader+application payload before wolfBoot runs.
In this scenario, wolfBoot's own boot-time verification is redundant and can be skipped as a
performance optimization.

Setting `WOLFBOOT_SKIP_BOOT_VERIFY=1` in the build configuration disables both the integrity (hash)
and authenticity (signature) checks that wolfBoot normally performs on the boot image at startup.

**WARNING: This option completely disables boot-time firmware verification. It is only safe to use
when ALL of the following conditions are met:**

- The self-header feature is enabled, so the bootloader manifest is persisted alongside the
application image
- Monolithic updates are enabled, so the bootloader and application are always updated together as a
single payload
- An external entity (e.g. an HSM running wolfHSM) is guaranteed to verify the full monolithic
payload before wolfBoot boots

**Using this option outside of this specific scenario removes all boot-time authenticity and integrity
guarantees and is not secure.**

Note that this option only affects verification of the boot image at startup. Firmware updates
staged in the update partition are still fully verified (signature and integrity) before being
installed, regardless of this setting.

### Incremental updates (aka: 'delta' updates)

wolfBoot supports incremental updates, based on a specific older version. The sign tool
Expand Down
10 changes: 10 additions & 0 deletions options.mk
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ ifeq ($(WOLFBOOT_TPM_SEAL),1)
endif
endif

## Monolithic self-update: erase covers fw_size so the payload can span
## the bootloader region into the contiguous boot partition.
ifeq ($(SELF_UPDATE_MONOLITHIC),1)
CFLAGS+=-DWOLFBOOT_SELF_UPDATE_MONOLITHIC
endif

## Persist wolfBoot self header at fixed address
## Invariants and defaults are enforced in wolfboot.h
ifeq ($(WOLFBOOT_SELF_HEADER),1)
Expand Down Expand Up @@ -681,6 +687,10 @@ ifeq ($(ALLOW_DOWNGRADE),1)
CFLAGS+= -D"ALLOW_DOWNGRADE"
endif

ifeq ($(WOLFBOOT_SKIP_BOOT_VERIFY),1)
CFLAGS+=-D"WOLFBOOT_SKIP_BOOT_VERIFY"
endif

ifeq ($(NVM_FLASH_WRITEONCE),1)
CFLAGS+= -D"NVM_FLASH_WRITEONCE"
endif
Expand Down
5 changes: 5 additions & 0 deletions src/update_disk.c
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ void RAMFUNCTION wolfBoot_start(void)
}
os_image.fw_base = (uint8_t*)load_address;

#ifndef WOLFBOOT_SKIP_BOOT_VERIFY
wolfBoot_printf("Checking image integrity...");
BENCHMARK_START();
if (wolfBoot_verify_integrity(&os_image) != 0) {
Expand All @@ -445,6 +446,10 @@ void RAMFUNCTION wolfBoot_start(void)
failures = 0;
break; /* Success case */
}
#else
failures = 0;
break; /* Skip verification, boot directly */
#endif
} while (failures < MAX_FAILURES);

if (failures) {
Expand Down
22 changes: 18 additions & 4 deletions src/update_flash.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,19 @@ static uint8_t buffer[FLASHBUFFER_SIZE] XALIGNED(4);
#endif


static void RAMFUNCTION wolfBoot_erase_bootloader(void)
static void RAMFUNCTION wolfBoot_erase_bootloader(uint32_t len)
{
uint32_t len = WOLFBOOT_PARTITION_BOOT_ADDRESS - ARCH_FLASH_OFFSET;
#ifdef WOLFBOOT_SELF_UPDATE_MONOLITHIC
/* Erase the full write range (rounded up to sector boundary) so that
* a monolithic payload that spills past the bootloader region into the
* contiguous boot partition lands on erased flash. */
len = ((len + WOLFBOOT_SECTOR_SIZE - 1) /
WOLFBOOT_SECTOR_SIZE) * WOLFBOOT_SECTOR_SIZE;
#else
(void)len;
len = WOLFBOOT_PARTITION_BOOT_ADDRESS - ARCH_FLASH_OFFSET;
#endif
hal_flash_erase(ARCH_FLASH_OFFSET, len);

}

#include <string.h>
Expand Down Expand Up @@ -154,7 +162,7 @@ static void RAMFUNCTION wolfBoot_self_update(struct wolfBoot_image *src)
#endif

hal_flash_unlock();
wolfBoot_erase_bootloader();
wolfBoot_erase_bootloader(src->fw_size);
#ifdef EXT_FLASH
if (PART_IS_EXT(src)) {
while (pos < src->fw_size) {
Expand Down Expand Up @@ -1345,6 +1353,7 @@ void RAMFUNCTION wolfBoot_start(void)
wolfBoot_printf("Booting version: 0x%x\n",
wolfBoot_get_blob_version(boot.hdr));

#ifndef WOLFBOOT_SKIP_BOOT_VERIFY
if (bootRet < 0
|| (wolfBoot_verify_integrity(&boot) < 0)
|| (wolfBoot_verify_authenticity(&boot) < 0)
Expand Down Expand Up @@ -1376,6 +1385,11 @@ void RAMFUNCTION wolfBoot_start(void)
}
}
PART_SANITY_CHECK(&boot);
#else
if (bootRet < 0) {
wolfBoot_panic();
}
#endif

#ifdef WOLFBOOT_ELF_FLASH_SCATTER
unsigned long entry;
Expand Down
10 changes: 6 additions & 4 deletions src/update_flash_hwswap.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ void RAMFUNCTION wolfBoot_start(void)
boot_panic();

for (;;) {
if ((wolfBoot_open_image(&fw_image, active) < 0) ||
(wolfBoot_verify_integrity(&fw_image) < 0) ||
(wolfBoot_verify_authenticity(&fw_image) < 0)) {

if ((wolfBoot_open_image(&fw_image, active) < 0)
#ifndef WOLFBOOT_SKIP_BOOT_VERIFY
|| (wolfBoot_verify_integrity(&fw_image) < 0)
|| (wolfBoot_verify_authenticity(&fw_image) < 0)
#endif
) {
/* panic if authentication fails and no backup */
if (!wolfBoot_fallback_is_possible())
boot_panic();
Expand Down
2 changes: 2 additions & 0 deletions src/update_ram.c
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ void RAMFUNCTION wolfBoot_start(void)
goto backup_on_failure;
}

#ifndef WOLFBOOT_SKIP_BOOT_VERIFY
/* Verify image integrity (hash check) */
wolfBoot_printf("Checking integrity...");
BENCHMARK_START();
Expand All @@ -210,6 +211,7 @@ void RAMFUNCTION wolfBoot_start(void)
goto backup_on_failure;
}
BENCHMARK_END("done");
#endif

{
/* Success - integrity and signature valid */
Expand Down
36 changes: 36 additions & 0 deletions tools/test.mk
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,42 @@ test-sim-self-update: wolfboot.bin FORCE
@# Verify dummy payload was written to bootloader region, indicating the self update swapped images as expected
$(Q)cmp -n $$(wc -c < dummy_update.bin | awk '{print $$1}') dummy_update.bin internal_flash.dd && echo "=== Self-update test PASSED ==="

# Test monolithic self-update mechanism using simulator. A monolithic self-update
# updates both the bootloader AND the boot partition application image in a single
# operation by crafting a payload that spans the bootloader region and spills into
# the contiguous boot partition.
test-sim-self-update-monolithic: wolfboot.bin test-app/image_v1_signed.bin FORCE
@echo "=== Simulator Monolithic Self-Update Test ==="
@# Create dummy bootloader (0xAA pattern, exactly bootloader region size: WOLFBOOT_PARTITION_BOOT_ADDRESS - ARCH_FLASH_OFFSET)
$(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
$(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) --wolfboot-update 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 and swap partitions
$(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
$(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
@# 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)
$(Q)cmp -n $$(($(WOLFBOOT_PARTITION_BOOT_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"
@echo "=== Monolithic Self-Update Test PASSED ==="

# Test self-header cryptographic verification (hash + signature validation)
#
# Verifies that an application can cryptographically verify the bootloader using
Expand Down