diff --git a/.github/workflows/test-sim-self-update.yml b/.github/workflows/test-sim-self-update.yml new file mode 100644 index 0000000000..a447f23508 --- /dev/null +++ b/.github/workflows/test-sim-self-update.yml @@ -0,0 +1,73 @@ +name: Simulator self-update test + +on: + push: + branches: [ 'master', 'main', 'release/**' ] + pull_request: + branches: [ '*' ] + +jobs: + self_update_simulator_test: + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Workaround for sources.list + run: | + # Replace sources + + set -euxo pipefail + + # Peek (what repos are active now) + apt-cache policy + grep -RInE '^(deb|Types|URIs)' /etc/apt || true + + # Enable nullglob so *.list/*.sources that don't exist don't break sed + 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 + # Replace azure with our mirror (idempotent) + sudo sed -i 's|https\?://azure\.archive\.ubuntu\.com/ubuntu/|http://mirror.arizona.edu/ubuntu/|g' /etc/apt/apt-mirrors.txt + fi + + # Peek (verify changes) + 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: Run self-update test (internal flash) + run: | + cp config/examples/sim-self-update.config .config + make test-sim-self-update + + - name: Run self-update test (external flash) + run: | + make clean + cp config/examples/sim-self-update-ext.config .config + make test-sim-self-update-ext diff --git a/config/examples/sim-self-update-ext.config b/config/examples/sim-self-update-ext.config new file mode 100644 index 0000000000..11bb1b00b8 --- /dev/null +++ b/config/examples/sim-self-update-ext.config @@ -0,0 +1,21 @@ +ARCH=sim +TARGET=sim +SIGN?=ED25519 +HASH?=SHA256 +WOLFBOOT_SMALL_STACK?=0 +SPI_FLASH=0 +EXT_FLASH=1 +DEBUG=1 +RAM_CODE=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 +# Update and swap on external flash (address 0x00000) +WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x00000 +WOLFBOOT_PARTITION_SWAP_ADDRESS=0x40000 + +# required for keytools +WOLFBOOT_FIXED_PARTITIONS=1 diff --git a/config/examples/sim-self-update.config b/config/examples/sim-self-update.config new file mode 100644 index 0000000000..13a0561848 --- /dev/null +++ b/config/examples/sim-self-update.config @@ -0,0 +1,19 @@ +ARCH=sim +TARGET=sim +SIGN?=ED25519 +HASH?=SHA256 +WOLFBOOT_SMALL_STACK?=0 +SPI_FLASH=0 +DEBUG=1 +RAM_CODE=1 +WOLFBOOT_VERSION=1 + +# sizes should be multiple of system page size +WOLFBOOT_PARTITION_SIZE=0x40000 +WOLFBOOT_SECTOR_SIZE=0x1000 +WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x100000 +WOLFBOOT_PARTITION_SWAP_ADDRESS=0x180000 + +# required for keytools +WOLFBOOT_FIXED_PARTITIONS=1 diff --git a/hal/sim.c b/hal/sim.c index 4df7f8d524..58eb743f6f 100644 --- a/hal/sim.c +++ b/hal/sim.c @@ -596,6 +596,11 @@ int wolfBoot_dualboot_candidate(void) } #endif +void arch_reboot(void) +{ + exit(0); +} + #ifdef WOLFBOOT_ENABLE_WOLFHSM_CLIENT int hal_hsm_init_connect(void) diff --git a/src/update_flash.c b/src/update_flash.c index ba720af7a5..b51f400a97 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -84,7 +84,13 @@ static void RAMFUNCTION wolfBoot_self_update(struct wolfBoot_image *src) while (pos < src->fw_size) { uint8_t buffer[FLASHBUFFER_SIZE]; if (src_offset + pos < (src->fw_size + IMAGE_HEADER_SIZE + FLASHBUFFER_SIZE)) { +#ifdef ARCH_SIM + /* Use ARCH_FLASH_OFFSET for simulator: flash is mmap'd at runtime, + * so the linker symbol _start_text does not point to simulated flash */ + uintptr_t opos = pos + ARCH_FLASH_OFFSET; +#else uintptr_t opos = pos + ((uintptr_t)&_start_text); +#endif ext_flash_check_read((uintptr_t)(src->hdr) + src_offset + pos, (void*)buffer, FLASHBUFFER_SIZE); hal_flash_write(opos, buffer, FLASHBUFFER_SIZE); } @@ -96,7 +102,13 @@ static void RAMFUNCTION wolfBoot_self_update(struct wolfBoot_image *src) while (pos < src->fw_size) { if (src_offset + pos < (src->fw_size + IMAGE_HEADER_SIZE + FLASHBUFFER_SIZE)) { uint8_t *orig = (uint8_t*)(src->hdr + src_offset + pos); +#ifdef ARCH_SIM + /* Use ARCH_FLASH_OFFSET for simulator: flash is mmap'd at runtime, + * so the linker symbol _start_text does not point to simulated flash */ + hal_flash_write(pos + ARCH_FLASH_OFFSET, orig, FLASHBUFFER_SIZE); +#else hal_flash_write(pos + (uintptr_t)&_start_text, orig, FLASHBUFFER_SIZE); +#endif } pos += FLASHBUFFER_SIZE; } diff --git a/tools/test.mk b/tools/test.mk index ac5bb95026..ff91bd80c9 100644 --- a/tools/test.mk +++ b/tools/test.mk @@ -240,6 +240,58 @@ test-sim-rollback-flash: wolfboot.elf test-sim-internal-flash-with-update FORCE $(Q)(test `./wolfboot.elf success get_version` -eq 1) $(Q)(test `./wolfboot.elf get_version` -eq 1) +# Test bootloader self-update mechanism using simulator. Since simulator memmaps runtime addresses +# the best we can do is ensure the self-update copies the intact self-update image to the expected location +test-sim-self-update: wolfboot.bin FORCE + @echo "=== Simulator Self-Update Test ===" + @# Create dummy payload (0xAA pattern) and sign as wolfBoot update v2 + $(Q)dd if=/dev/zero bs=$$(wc -c < wolfboot.bin | awk '{print $$1}') count=1 2>/dev/null | tr '\000' '\252' > dummy_update.bin + $(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) --wolfboot-update dummy_update.bin $(PRIVATE_KEY) 2 + @# Create update partition with signed update and "pBOOT" trailer. Necessary since there is no running + @# app to trigger an initial update + $(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_SIZE))) count=1 2>/dev/null | tr '\000' '\377' > update_part.dd + $(Q)dd if=dummy_update_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 runs before app boot, writes dummy to offset 0, then reboots + $(Q)./wolfboot.elf get_version || true + @# 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 bootloader self-update mechanism with external flash +test-sim-self-update-ext: wolfboot.bin FORCE + @echo "=== Simulator Self-Update Test (External Flash) ===" + @# Create dummy payload (0xAA pattern) and sign as wolfBoot update v2 + $(Q)dd if=/dev/zero bs=$$(wc -c < wolfboot.bin | awk '{print $$1}') count=1 2>/dev/null | tr '\000' '\252' > dummy_update.bin + $(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) --wolfboot-update dummy_update.bin $(PRIVATE_KEY) 2 + @# Create update partition with signed update 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=dummy_update_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 internal flash: wolfboot.bin at 0, empty boot partition + $(Q)$(BINASSEMBLE) internal_flash.dd \ + 0 wolfboot.bin \ + $$(($(WOLFBOOT_PARTITION_BOOT_ADDRESS) - $(ARCH_FLASH_OFFSET))) boot_part.dd + @# Assemble external flash: update partition, swap sector + $(Q)$(BINASSEMBLE) external_flash.dd \ + 0 update_part.dd \ + $(WOLFBOOT_PARTITION_SIZE) erased_sec.dd + @# Run simulator - self-update reads from external, writes to internal at offset 0 + $(Q)./wolfboot.elf get_version || true + @# Verify dummy payload was written to bootloader region + $(Q)cmp -n $$(wc -c < dummy_update.bin | awk '{print $$1}') dummy_update.bin internal_flash.dd && echo "=== Self-update test (External Flash) PASSED ===" + test-self-update: FORCE @mv $(PRIVATE_KEY) private_key.old @make clean factory.bin RAM_CODE=1 WOLFBOOT_VERSION=1 SIGN=$(SIGN)