From c51b3535214de15a021868ad143935e8e25d4915 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 27 Oct 2026 14:43:09 +0100 Subject: [PATCH 1/5] boot: bind image partition claim to the booting core Add wolfBoot_image_part_matches(), which requires the image's claimed partition id to match HDR_IMG_TYPE_APP. --- include/image.h | 1 + src/image.c | 24 ++++++ src/update_disk.c | 12 +++ src/update_flash_hwswap.c | 4 + src/update_ram.c | 11 +++ tools/unit-tests/unit-image.c | 85 +++++++++++++++++++++ tools/unit-tests/unit-update-disk-confirm.c | 7 ++ tools/unit-tests/unit-update-disk-fit.c | 6 ++ tools/unit-tests/unit-update-disk-fsp.c | 6 ++ tools/unit-tests/unit-update-disk-golden.c | 6 ++ tools/unit-tests/unit-update-disk-oob.c | 7 ++ tools/unit-tests/unit-update-disk.c | 7 ++ 12 files changed, 176 insertions(+) diff --git a/include/image.h b/include/image.h index f97ff607c2..5fcdb65ea6 100644 --- a/include/image.h +++ b/include/image.h @@ -1724,6 +1724,7 @@ int wolfBoot_open_image(struct wolfBoot_image *img, uint8_t part); int wolfBoot_open_image_external(struct wolfBoot_image* img, uint8_t part, uint8_t* addr); #endif int wolfBoot_open_image_address(struct wolfBoot_image* img, uint8_t* image); +int wolfBoot_image_part_matches(struct wolfBoot_image *img); #ifdef WOLFBOOT_SELF_HEADER int wolfBoot_open_self(struct wolfBoot_image *img); int wolfBoot_open_self_address(struct wolfBoot_image *img, uint8_t *hdr, diff --git a/src/image.c b/src/image.c index c3ca65d10a..e3b5a3e79c 100644 --- a/src/image.c +++ b/src/image.c @@ -1946,6 +1946,30 @@ int wolfBoot_open_self_address(struct wolfBoot_image* img, uint8_t* hdr, } #endif +/** + * @brief Check that an opened image targets this wolfBoot's partition. + * @param img The opened image (hdr must point to a valid header). + * @return 0 if the image targets this core's partition, -1 otherwise. + */ +int wolfBoot_image_part_matches(struct wolfBoot_image *img) +{ + uint8_t *type_field = NULL; + uint16_t image_type; + + if (img == NULL || img->hdr == NULL) { + return -1; + } + if (wolfBoot_find_header(img->hdr + IMAGE_HEADER_OFFSET, HDR_IMG_TYPE, + &type_field) != WOLFBOOT_HDR_U16_SZ) { + return -1; + } + image_type = (uint16_t)(type_field[0] + (type_field[1] << 8)); + if ((image_type & HDR_IMG_TYPE_PART_MASK) != HDR_IMG_TYPE_APP) { + return -1; + } + return 0; +} + /** * @brief Verify the integrity of the image using the stored SHA hash. * diff --git a/src/update_disk.c b/src/update_disk.c index 9c73b91143..815969ad67 100644 --- a/src/update_disk.c +++ b/src/update_disk.c @@ -1092,6 +1092,18 @@ void RAMFUNCTION wolfBoot_start(void) continue; } + /* The image must target this core's partition: the key mask only + * restricts which partition ids the signing key may claim, not + * which core boots the image. Reject before the payload read so a + * foreign image never drives the disk read into the RAM load + * region. */ + if (wolfBoot_image_part_matches(&os_image) < 0) { + wolfBoot_printf("Image partition mismatch for %s\r\n", + part_name); + selected ^= 1; + continue; + } + /* Bound the UNAUTHENTICATED image length before it drives the disk * read into the RAM load region. An attacker controlling the boot * media could otherwise declare an arbitrary fw_size and overrun the diff --git a/src/update_flash_hwswap.c b/src/update_flash_hwswap.c index cf5ff29276..c10216d28e 100644 --- a/src/update_flash_hwswap.c +++ b/src/update_flash_hwswap.c @@ -81,6 +81,10 @@ void RAMFUNCTION wolfBoot_start(void) } #endif if ((wolfBoot_open_image(&fw_image, active) < 0) + /* The image must target this core's partition; the key mask + * alone does not bind the claimed role to the execution + * context. Short-circuits when the open above failed. */ + || (wolfBoot_image_part_matches(&fw_image) < 0) #ifndef WOLFBOOT_SKIP_BOOT_VERIFY || (wolfBoot_verify_integrity(&fw_image) < 0) || (wolfBoot_verify_authenticity(&fw_image) < 0) diff --git a/src/update_ram.c b/src/update_ram.c index 0e963420c4..d92d2d46bc 100644 --- a/src/update_ram.c +++ b/src/update_ram.c @@ -386,6 +386,17 @@ void RAMFUNCTION wolfBoot_start(void) goto backup_on_failure; } + /* The image must target the partition this wolfBoot instance is + * responsible for booting: the key mask only restricts which + * partition ids the signing key may claim, not which core boots + * the image. Runs even with WOLFBOOT_SKIP_BOOT_VERIFY, as it is a + * header binding check, not a crypto verification. */ + ret = wolfBoot_image_part_matches(&os_image); + if (ret < 0) { + wolfBoot_printf("Image partition mismatch\n"); + goto backup_on_failure; + } + #ifndef WOLFBOOT_SKIP_BOOT_VERIFY /* Verify image integrity (hash check) */ wolfBoot_printf("Checking integrity..."); diff --git a/tools/unit-tests/unit-image.c b/tools/unit-tests/unit-image.c index 0ca0702518..da337203c3 100644 --- a/tools/unit-tests/unit-image.c +++ b/tools/unit-tests/unit-image.c @@ -1029,6 +1029,83 @@ START_TEST(test_verify_authenticity_allows_permitted_key_mask) END_TEST #endif /* WOLFBOOT_FIXED_PARTITIONS */ +#if defined(WOLFBOOT_SIGN_ECC256) && defined(WOLFBOOT_HASH_SHA256) +START_TEST(test_image_part_matches_app) +{ + struct wolfBoot_image test_img; + uint8_t buf[sizeof(test_img_v200000000_signed_bin)]; + int ret; + + memcpy(buf, test_img_v200000000_signed_bin, sizeof(buf)); + + find_header_mocked = 0; + memset(&test_img, 0, sizeof(test_img)); + test_img.hdr = buf; + ret = wolfBoot_image_part_matches(&test_img); + ck_assert_int_eq(ret, 0); +} +END_TEST + +START_TEST(test_image_part_matches_rejects_foreign) +{ + struct wolfBoot_image test_img; + uint8_t buf[sizeof(test_img_v200000000_signed_bin)]; + int ret; + + memcpy(buf, test_img_v200000000_signed_bin, sizeof(buf)); + patch_image_type_part(buf, sizeof(buf), 2); + + find_header_mocked = 0; + memset(&test_img, 0, sizeof(test_img)); + test_img.hdr = buf; + ret = wolfBoot_image_part_matches(&test_img); + ck_assert_int_eq(ret, -1); +} +END_TEST + +START_TEST(test_image_part_matches_rejects_wolfboot) +{ + struct wolfBoot_image test_img; + uint8_t buf[sizeof(test_img_v200000000_signed_bin)]; + int ret; + + memcpy(buf, test_img_v200000000_signed_bin, sizeof(buf)); + patch_image_type_part(buf, sizeof(buf), HDR_IMG_TYPE_WOLFBOOT); + + find_header_mocked = 0; + memset(&test_img, 0, sizeof(test_img)); + test_img.hdr = buf; + ret = wolfBoot_image_part_matches(&test_img); + ck_assert_int_eq(ret, -1); +} +END_TEST + +START_TEST(test_image_part_matches_null) +{ + struct wolfBoot_image test_img; + uint8_t buf[sizeof(test_img_v200000000_signed_bin)]; + int ret; + + ret = wolfBoot_image_part_matches(NULL); + ck_assert_int_eq(ret, -1); + + memcpy(buf, test_img_v200000000_signed_bin, sizeof(buf)); + memset(&test_img, 0, sizeof(test_img)); + ret = wolfBoot_image_part_matches(&test_img); + ck_assert_int_eq(ret, -1); + + /* type field lookup fails (missing HDR_IMG_TYPE) */ + test_img.hdr = buf; + find_header_mocked = 1; + find_header_fail = 1; + ret = wolfBoot_image_part_matches(&test_img); + ck_assert_int_eq(ret, -1); + find_header_fail = 0; + find_header_mocked = 0; +} +END_TEST +#endif /* WOLFBOOT_SIGN_ECC256 && WOLFBOOT_HASH_SHA256 */ + #if defined(UNIT_IMAGE_HYBRID_ONLY) && defined(SIGN_HYBRID) && \ defined(WOLFBOOT_SIGN_SECONDARY_ECC256) START_TEST(test_verify_authenticity_hybrid_direct_call_uses_verified_sha) @@ -1383,6 +1460,14 @@ Suite *wolfboot_suite(void) tcase_add_test(tcase_verify_authenticity, test_verify_authenticity_allows_permitted_key_mask); #endif /* WOLFBOOT_FIXED_PARTITIONS */ +#if defined(WOLFBOOT_HASH_SHA256) + tcase_add_test(tcase_verify_authenticity, test_image_part_matches_app); + tcase_add_test(tcase_verify_authenticity, + test_image_part_matches_rejects_foreign); + tcase_add_test(tcase_verify_authenticity, + test_image_part_matches_rejects_wolfboot); + tcase_add_test(tcase_verify_authenticity, test_image_part_matches_null); +#endif /* WOLFBOOT_HASH_SHA256 */ suite_add_tcase(s, tcase_verify_authenticity); #endif diff --git a/tools/unit-tests/unit-update-disk-confirm.c b/tools/unit-tests/unit-update-disk-confirm.c index 2e7aa02ba0..ab71446099 100644 --- a/tools/unit-tests/unit-update-disk-confirm.c +++ b/tools/unit-tests/unit-update-disk-confirm.c @@ -54,6 +54,7 @@ static int mock_verify_integrity_ret; static int mock_integrity_saw_not_ext; static int mock_authenticity_saw_not_ext; static int mock_verify_authenticity_ret; +static int mock_image_part_matches_ret; static int mock_state_writes; ChaCha chacha; @@ -256,6 +257,12 @@ int wolfBoot_verify_authenticity(struct wolfBoot_image* img) return mock_verify_authenticity_ret; } +int wolfBoot_image_part_matches(struct wolfBoot_image* img) +{ + (void)img; + return mock_image_part_matches_ret; +} + int wolfBoot_get_dts_size(void *dts_addr, uint32_t capacity) { (void)capacity; diff --git a/tools/unit-tests/unit-update-disk-fit.c b/tools/unit-tests/unit-update-disk-fit.c index efad9a967b..86e1f81a29 100644 --- a/tools/unit-tests/unit-update-disk-fit.c +++ b/tools/unit-tests/unit-update-disk-fit.c @@ -262,6 +262,12 @@ int wolfBoot_verify_authenticity(struct wolfBoot_image* img) return 0; } +int wolfBoot_image_part_matches(struct wolfBoot_image* img) +{ + (void)img; + return 0; +} + /* The loaded payload is treated as a FIT container, and the sub-image * returned by fit_load_image() is a flat device tree whose parsed * size is mock_dts_size. */ diff --git a/tools/unit-tests/unit-update-disk-fsp.c b/tools/unit-tests/unit-update-disk-fsp.c index 08694902b3..e38e4a1a8f 100644 --- a/tools/unit-tests/unit-update-disk-fsp.c +++ b/tools/unit-tests/unit-update-disk-fsp.c @@ -164,6 +164,12 @@ int wolfBoot_verify_authenticity(struct wolfBoot_image* img) return 0; } +int wolfBoot_image_part_matches(struct wolfBoot_image* img) +{ + (void)img; + return 0; +} + int wolfBoot_get_dts_size(void *dts_addr, uint32_t capacity) { (void)capacity; diff --git a/tools/unit-tests/unit-update-disk-golden.c b/tools/unit-tests/unit-update-disk-golden.c index fc64173155..321d99d76f 100644 --- a/tools/unit-tests/unit-update-disk-golden.c +++ b/tools/unit-tests/unit-update-disk-golden.c @@ -243,6 +243,12 @@ int wolfBoot_verify_authenticity(struct wolfBoot_image* img) return 0; } +int wolfBoot_image_part_matches(struct wolfBoot_image* img) +{ + (void)img; + return 0; +} + int wolfBoot_get_dts_size(void *dts_addr, uint32_t capacity) { (void)capacity; (void)dts_addr; diff --git a/tools/unit-tests/unit-update-disk-oob.c b/tools/unit-tests/unit-update-disk-oob.c index 793357b58d..13ceee164b 100644 --- a/tools/unit-tests/unit-update-disk-oob.c +++ b/tools/unit-tests/unit-update-disk-oob.c @@ -81,6 +81,7 @@ static int mock_do_boot_called; static const uint32_t *mock_boot_address; static int mock_verify_integrity_ret; static int mock_verify_authenticity_ret; +static int mock_image_part_matches_ret; static int mock_flash_protect_called; static haladdr_t mock_flash_protect_addr; static int mock_flash_protect_len; @@ -215,6 +216,12 @@ int wolfBoot_verify_authenticity(struct wolfBoot_image* img) return mock_verify_authenticity_ret; } +int wolfBoot_image_part_matches(struct wolfBoot_image* img) +{ + (void)img; + return mock_image_part_matches_ret; +} + int wolfBoot_get_dts_size(void *dts_addr, uint32_t capacity) { (void)capacity; diff --git a/tools/unit-tests/unit-update-disk.c b/tools/unit-tests/unit-update-disk.c index 8f45afcb91..112f7cd7b2 100644 --- a/tools/unit-tests/unit-update-disk.c +++ b/tools/unit-tests/unit-update-disk.c @@ -40,6 +40,7 @@ static int mock_verify_integrity_ret; static int mock_integrity_saw_not_ext; static int mock_authenticity_saw_not_ext; static int mock_verify_authenticity_ret; +static int mock_image_part_matches_ret; ChaCha chacha; @@ -201,6 +202,12 @@ int wolfBoot_verify_authenticity(struct wolfBoot_image* img) return mock_verify_authenticity_ret; } +int wolfBoot_image_part_matches(struct wolfBoot_image* img) +{ + (void)img; + return mock_image_part_matches_ret; +} + int wolfBoot_get_dts_size(void *dts_addr, uint32_t capacity) { (void)capacity; From 99e45652d2af30752d7abe7d5e7c677a30b8c6eb Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 27 Oct 2026 15:00:23 +0100 Subject: [PATCH 2/5] Added unit tests to .gitignore --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitignore b/.gitignore index 178e591d48..14f0829dde 100644 --- a/.gitignore +++ b/.gitignore @@ -268,6 +268,14 @@ tools/unit-tests/unit-ubootenv tools/unit-tests/unit-cm4-sdhci tools/unit-tests/unit-imx95-ahab tools/unit-tests/unit-cm4-rauc-slot +tools/unit-tests/stm32h7s_write_extract.h +tools/unit-tests/unit-flash-erase-h7s +tools/unit-tests/unit-flash-erase-mcxw +tools/unit-tests/unit-linux-loader-initrd +tools/unit-tests/unit-stm32h7s-write +tools/unit-tests/unit-update-disk-fit-ramdisk +tools/unit-tests/unit-update-disk-golden +tools/unit-tests/unit-update-ram-fit tools/unit-tests/__pycache__/* From ea9c4c312010b1707e52764f57bae6f160bb7e94 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 27 Oct 2026 15:19:34 +0100 Subject: [PATCH 3/5] unit-tests: stub part check in update-disk-fs, test disk partition mismatch --- tools/unit-tests/unit-update-disk-fs.c | 6 ++++ tools/unit-tests/unit-update-disk.c | 44 ++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/tools/unit-tests/unit-update-disk-fs.c b/tools/unit-tests/unit-update-disk-fs.c index 98f06ac8f5..6bfd942a1d 100644 --- a/tools/unit-tests/unit-update-disk-fs.c +++ b/tools/unit-tests/unit-update-disk-fs.c @@ -669,6 +669,12 @@ int wolfBoot_verify_authenticity(struct wolfBoot_image *img) return mock_verify_authenticity_ret; } +int wolfBoot_image_part_matches(struct wolfBoot_image *img) +{ + (void)img; + return 0; +} + /* Parses the single version TLV that build_image() writes. */ uint32_t wolfBoot_get_blob_version(uint8_t *blob) { diff --git a/tools/unit-tests/unit-update-disk.c b/tools/unit-tests/unit-update-disk.c index 112f7cd7b2..8bc993cf33 100644 --- a/tools/unit-tests/unit-update-disk.c +++ b/tools/unit-tests/unit-update-disk.c @@ -40,7 +40,8 @@ static int mock_verify_integrity_ret; static int mock_integrity_saw_not_ext; static int mock_authenticity_saw_not_ext; static int mock_verify_authenticity_ret; -static int mock_image_part_matches_ret; +static int mock_part_mismatch_part; +static int mock_last_read_part; ChaCha chacha; @@ -87,6 +88,8 @@ static void reset_mocks(void) mock_fail_payload_part = -1; mock_verify_integrity_ret = 0; mock_verify_authenticity_ret = 0; + mock_part_mismatch_part = -2; + mock_last_read_part = -1; mock_integrity_saw_not_ext = -1; mock_authenticity_saw_not_ext = -1; mock_flash_protect_called = 0; @@ -159,6 +162,7 @@ int disk_part_read(int drv, int part, uint64_t off, uint64_t sz, uint8_t *buf) uint64_t max = IMAGE_HEADER_SIZE + TEST_PAYLOAD_SIZE; (void)drv; + mock_last_read_part = part; image = (part == BOOT_PART_B) ? part_b_image : part_a_image; if ((mock_fail_payload_part == part) && (off >= IMAGE_HEADER_SIZE)) return -1; @@ -205,7 +209,11 @@ int wolfBoot_verify_authenticity(struct wolfBoot_image* img) int wolfBoot_image_part_matches(struct wolfBoot_image* img) { (void)img; - return mock_image_part_matches_ret; + /* The header of the slot under test is the last one read. */ + if ((mock_part_mismatch_part == -1) || + (mock_last_read_part == mock_part_mismatch_part)) + return -1; + return 0; } int wolfBoot_get_dts_size(void *dts_addr, uint32_t capacity) @@ -443,6 +451,36 @@ START_TEST(test_update_disk_rejects_invalid_signature) } END_TEST +START_TEST(test_update_disk_skips_partition_mismatch) +{ + reset_mocks(); + build_image(part_a_image, 7, 0xA1); + build_image(part_b_image, 7, 0xB2); + mock_part_mismatch_part = BOOT_PART_A; + + wolfBoot_start(); + + ck_assert_int_eq(wolfBoot_panicked, 0); + ck_assert_int_eq(mock_do_boot_called, 1); + ck_assert_int_eq(memcmp(load_buffer, part_b_image + IMAGE_HEADER_SIZE, + TEST_PAYLOAD_SIZE), 0); +} +END_TEST + +START_TEST(test_update_disk_rejects_partition_mismatch) +{ + reset_mocks(); + build_image(part_a_image, 7, 0xA1); + build_image(part_b_image, 7, 0xB2); + mock_part_mismatch_part = -1; /* both slots */ + + wolfBoot_start(); + + ck_assert_int_gt(wolfBoot_panicked, 0); + ck_assert_int_eq(mock_do_boot_called, 0); +} +END_TEST + Suite *wolfboot_suite(void) { Suite *s = suite_create("wolfBoot"); @@ -459,6 +497,8 @@ Suite *wolfboot_suite(void) tcase_add_test(tc, test_update_disk_rejects_rollback_after_higher_image_failure); tcase_add_test(tc, test_update_disk_rejects_invalid_integrity); tcase_add_test(tc, test_update_disk_rejects_invalid_signature); + tcase_add_test(tc, test_update_disk_skips_partition_mismatch); + tcase_add_test(tc, test_update_disk_rejects_partition_mismatch); suite_add_tcase(s, tc); return s; From f462765545acc6f004c9b6fc2dbd8053f2571e12 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 28 Oct 2026 14:48:44 +0100 Subject: [PATCH 4/5] unit-tests: stub part check in update-disk-failover --- tools/unit-tests/unit-update-disk-failover.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/unit-tests/unit-update-disk-failover.c b/tools/unit-tests/unit-update-disk-failover.c index a44f2d6e54..6ee37ce708 100644 --- a/tools/unit-tests/unit-update-disk-failover.c +++ b/tools/unit-tests/unit-update-disk-failover.c @@ -262,6 +262,12 @@ int wolfBoot_verify_authenticity(struct wolfBoot_image* img) return 0; } +int wolfBoot_image_part_matches(struct wolfBoot_image* img) +{ + (void)img; + return 0; +} + int wolfBoot_get_dts_size(void *dts_addr, uint32_t capacity) { (void)capacity; (void)dts_addr; From 720eaf456425b4bba36be4fd34c06f2c809d1bff Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 28 Oct 2026 14:48:44 +0100 Subject: [PATCH 5/5] sim monolithic self-update: sign the stripped test app --- config/examples/sim-self-update-monolithic.config | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/examples/sim-self-update-monolithic.config b/config/examples/sim-self-update-monolithic.config index aad597cd11..183174c644 100644 --- a/config/examples/sim-self-update-monolithic.config +++ b/config/examples/sim-self-update-monolithic.config @@ -8,6 +8,8 @@ DEBUG=1 RAM_CODE=1 SELF_UPDATE_MONOLITHIC=1 WOLFBOOT_VERSION=1 +# Sign the debug-stripped app so it fits the BOOT share of the install span +STRIP_ELF=1 # Asymmetric layout: BOOT only holds the app; UPDATE stages the monolithic # payload. UPDATE (0x80000) matches the install span (bootloader region