From 42a42ec1c16327ebfd4bd30946e03f89e0cad105 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 26 Aug 2026 20:10:47 +0200 Subject: [PATCH] unit-tests: pin the update-trigger scrub payload and ordering The hal_flash_write stub discarded the written data, so the test only checked call counts and the final NVM_CACHE state. An implementation that scrubbed before the write - programming an all-zero flags sector and destroying the firmware key that persists in the trailer - would have satisfied every assertion. Capture the written sector in the stub at call time and assert the staged payload: the sector fill and key pattern intact, the fresh IMG_STATE_UPDATING byte and magic in place, with NVM_CACHE still required to be zero afterwards. Mutation-checked: a scrub-before- write variant that programs the zeroed sector fails the payload assertion. --- tools/unit-tests/unit-update-trigger-scrub.c | 31 +++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/tools/unit-tests/unit-update-trigger-scrub.c b/tools/unit-tests/unit-update-trigger-scrub.c index b69d5585..bbda0071 100644 --- a/tools/unit-tests/unit-update-trigger-scrub.c +++ b/tools/unit-tests/unit-update-trigger-scrub.c @@ -12,8 +12,12 @@ * * The real function is extracted by the Makefile (together with * nvm_cache_scrub()) and run with a test-owned NVM_CACHE, a staged - * sector carrying a key pattern and stubbed flash calls; the buffer - * must be zero after the call. + * sector carrying a key pattern and stubbed flash calls. The stub + * captures the written sector: it must carry the staged payload + * (key pattern plus the fresh flags), which pins the scrub-after- + * write ordering - a scrub that ran first would program an + * all-zero sector and destroy the key that persists in the trailer + * - and NVM_CACHE must be zero after the call. * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfBoot. @@ -66,14 +70,18 @@ static uint8_t g_sector[NVM_CACHE_SIZE] * g_sector (the alignment above makes that true by construction). */ #define PART_UPDATE_ENDFLAGS ((uintptr_t)(g_sector + WOLFBOOT_SECTOR_SIZE)) -/* Stubbed flash layer: records calls. */ +/* Stubbed flash layer: records calls and captures the written + * sector at call time, before the function under test can scrub it. */ static int g_flash_writes; static int g_flash_erases; +static uint8_t g_written[WOLFBOOT_SECTOR_SIZE]; int hal_flash_write(uint32_t address, const uint8_t *data, int len) { - (void)address; (void)data; (void)len; + ck_assert_int_eq(len, WOLFBOOT_SECTOR_SIZE); + memcpy(g_written, data, WOLFBOOT_SECTOR_SIZE); g_flash_writes++; + (void)address; return 0; } @@ -151,6 +159,8 @@ static void teardown(void) * before rewriting the flags. Pre-fix the staged pattern remained. */ START_TEST(test_update_trigger_scrubs_cache) { + uint32_t magic = WOLFBOOT_MAGIC_TRAIL; + ck_assert_int_eq(cache_scrubbed(), 1); wolfBoot_update_trigger(); @@ -159,6 +169,19 @@ START_TEST(test_update_trigger_scrubs_cache) * both candidate sectors */ ck_assert_int_eq(g_flash_writes, 1); ck_assert_int_eq(g_flash_erases, 2); + + /* The written sector carries the staged payload: the sector fill + * and key pattern intact, the fresh state and magic in place. + * A scrub before the write would have programmed all zeros and + * destroyed the key that persists in the trailer. */ + ck_assert_int_eq(g_written[0], 0x11); + ck_assert_int_eq(g_written[KEY_OFF], 0xA5); + ck_assert_int_eq(g_written[KEY_OFF + KEY_LEN - 1], 0xA5); + ck_assert_int_eq(g_written[SECTOR_FLAGS_SIZE], IMG_STATE_UPDATING); + ck_assert_mem_eq(g_written + SECTOR_FLAGS_SIZE + 1, &magic, + sizeof(magic)); + + /* and the RAM copy of that sector is gone */ ck_assert_int_eq(cache_scrubbed(), 1); } END_TEST