Skip to content
Open
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
73 changes: 73 additions & 0 deletions .github/workflows/test-sim-self-update.yml
Original file line number Diff line number Diff line change
@@ -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
21 changes: 21 additions & 0 deletions config/examples/sim-self-update-ext.config
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions config/examples/sim-self-update.config
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions hal/sim.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions src/update_flash.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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;
}
Expand Down
52 changes: 52 additions & 0 deletions tools/test.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down