diff --git a/src/update_flash.c b/src/update_flash.c index c2a27e48..77bc0ed1 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -623,6 +623,13 @@ static int RAMFUNCTION wolfBoot_swap_and_final_erase(int resume) # define DELTA_BLOCK_SIZE 1024 #endif + /* The per-sector fill loop advances in DELTA_BLOCK_SIZE steps, so a + * sector that is not a multiple of the block size would be written + * past the one-sector SWAP partition and misalign the resume path. */ + #if (WOLFBOOT_SECTOR_SIZE % DELTA_BLOCK_SIZE) != 0 + #error "Delta update: WOLFBOOT_SECTOR_SIZE % DELTA_BLOCK_SIZE != 0" + #endif + static inline uint32_t wb_delta_im2n(uint32_t val) { #ifdef BIG_ENDIAN_ORDER diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index c34d235e..234ccf77 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -221,6 +221,7 @@ run: $(TESTS) python3 unit-sign-delta-tlv.py || exit 1 python3 unit-sign-delta-cert-inv-off.py || exit 1 python3 unit-sign-delta-basehash-cleanup.py || exit 1 + python3 unit-delta-sector-align.py || exit 1 python3 unit-sign-custom-tlv-le.py || exit 1 python3 unit-sign-custom-tlv-large.py || exit 1 python3 unit-sign-custom-tlv-pubkey-der.py || exit 1 @@ -264,6 +265,14 @@ unit-update-flash-delta:CFLAGS+=-DMOCK_PARTITIONS -DWOLFBOOT_NO_SIGN -DUNIT_TEST -DWOLFBOOT_HASH_SHA256 -DPRINTF_ENABLED -DEXT_FLASH -DPART_UPDATE_EXT -DPART_SWAP_EXT \ -DDELTA_UPDATES -DDELTA_BLOCK_SIZE=512 -D__WOLFBOOT \ -DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE +# Intentionally misaligned config: the mock WOLFBOOT_SECTOR_SIZE (0x400) is +# not a multiple of DELTA_BLOCK_SIZE (1536). Must fail to build with the +# delta alignment #error. Used by unit-delta-sector-align.py only, so it is +# not part of $(TESTS). +unit-update-flash-delta-misalign:CFLAGS+=-DMOCK_PARTITIONS -DWOLFBOOT_NO_SIGN -DUNIT_TEST_AUTH \ + -DWOLFBOOT_HASH_SHA256 -DPRINTF_ENABLED -DEXT_FLASH -DPART_UPDATE_EXT -DPART_SWAP_EXT \ + -DDELTA_UPDATES -DDELTA_BLOCK_SIZE=1536 -D__WOLFBOOT \ + -DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE unit-update-flash-self-update:CFLAGS+=-DMOCK_PARTITIONS -DWOLFBOOT_NO_SIGN -DUNIT_TEST_AUTH \ -DWOLFBOOT_HASH_SHA256 -DPRINTF_ENABLED -DEXT_FLASH -DPART_UPDATE_EXT -DPART_SWAP_EXT \ -DRAM_CODE -DARCH_SIM -DUNIT_TEST_SELF_UPDATE_ONLY \ @@ -808,6 +817,10 @@ unit-update-flash-delta: ../../include/target.h unit-update-flash.c gcc -o $@ unit-update-flash.c ../../src/image.c ../../src/delta.c \ $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c $(CFLAGS) $(LDFLAGS) +unit-update-flash-delta-misalign: ../../include/target.h unit-update-flash.c + gcc -o $@ unit-update-flash.c ../../src/image.c ../../src/delta.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c $(CFLAGS) $(LDFLAGS) + unit-update-flash-enc:CFLAGS+=-DMOCK_PARTITIONS -DWOLFBOOT_NO_SIGN -DUNIT_TEST_AUTH \ -DWOLFBOOT_HASH_SHA256 -DPRINTF_ENABLED -DEXT_FLASH -DPART_UPDATE_EXT \ -DPART_SWAP_EXT -DEXT_ENCRYPTED -DENCRYPT_WITH_CHACHA -DHAVE_CHACHA \ diff --git a/tools/unit-tests/unit-delta-sector-align.py b/tools/unit-tests/unit-delta-sector-align.py new file mode 100644 index 00000000..644d0439 --- /dev/null +++ b/tools/unit-tests/unit-delta-sector-align.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# unit-delta-sector-align.py +# +# Build-time invariant for DELTA_UPDATES: the per-sector fill loop in +# wolfBoot_delta_update() (src/update_flash.c) calls wb_patch() with +# DELTA_BLOCK_SIZE chunks until WOLFBOOT_SECTOR_SIZE bytes are produced, so +# the reconstructed image is only sector-aligned when WOLFBOOT_SECTOR_SIZE is +# a multiple of DELTA_BLOCK_SIZE. A mismatched config used to compile fine +# and corrupt the delta apply (write past the one-sector SWAP partition, +# misaligned resume); the build now rejects it with an #error. +# +# This test builds the real update_flash.c (via unit-update-flash.c) with a +# deliberately misaligned DELTA_BLOCK_SIZE and asserts the build fails with +# the invariant message. +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfBoot. +# +# wolfBoot is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# wolfBoot is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + +import os +import subprocess +import sys + +MESSAGE = "WOLFBOOT_SECTOR_SIZE % DELTA_BLOCK_SIZE != 0" + + +def main(): + # The misalign target is not in $(TESTS) and its rule does not track + # src/update_flash.c (included via unit-update-flash.c), so drop any + # stale artifact to force a real rebuild. + try: + os.remove("unit-update-flash-delta-misalign") + except FileNotFoundError: + pass + p = subprocess.run(["make", "unit-update-flash-delta-misalign"], + capture_output=True, text=True) + if p.returncode == 0: + print("FAIL: misaligned DELTA_BLOCK_SIZE config built, " + "expected the sector alignment #error") + return 1 + if MESSAGE not in (p.stdout + p.stderr): + print("FAIL: build failed for the wrong reason:\n") + print(p.stderr[-2000:]) + return 1 + print("PASS: misaligned DELTA_BLOCK_SIZE rejected at build time") + return 0 + + +if __name__ == "__main__": + sys.exit(main())