F-11028: panic when both RAM-boot images fail verification

The RAM boot loop switched to the other partition on a verification
failure (active ^= 1, continue) with a comment claiming the failing
image was invalidated, but nothing was invalidated: wolfBoot_fallback_is_possible()
only sees that both partitions carry nonzero versions. Two present
but invalid images therefore alternated indefinitely, re-verifying
forever. (The flash path does not have this hole because it erases
the failing partition, which zeroes its version and makes the second
fallback check fail.)

Track the candidates attempted this boot: once both partitions have
failed, panic with a clear message. Same-version images are used in
the regression test so the anti-rollback guard cannot mask the
alternation.

unit-update-ram gains test_both_images_corrupted_panics: both
partitions carry valid-version images with corrupted digests; the
boot must panic after exactly two attempts. Pre-fix the loop ran
unbounded (process had to be killed; ~60k partition switches in 30 s).

Verification:
- Built: unit-update-ram, unit-update-ram-enc, unit-update-ram-nofixed,
  unit-update-ram-uboot all compile.
- Tested: unit-update-ram 20/20, unit-update-ram-nofixed 3/3,
  unit-update-ram-uboot 5/5; the new test shows Boot fail, Update
  fail, panic.
- Pitfalls: attempt tracking is per boot session (local), no new
  persistent state; the flash path is untouched.
- Style: cstyle-check.sh flag count on both changed files is
  unchanged from the pre-change versions (pre-existing FMT/R1).
- Message: F-11028: prefix, no co-author trailers.
- Note: unit-update-ram.c defines 21 tests but wires 20 into the
  suite (test_forward_update_samesize_notrigger was never added);
  pre-existing, left as-is.
pull/870/head
Daniele Lacamera 2026-08-24 18:13:26 +02:00
parent e02a734e72
commit e108d4e86c
2 changed files with 50 additions and 5 deletions

View File

@ -269,6 +269,9 @@ static int uboot_legacy_header_valid(const uint8_t *hdr, uint32_t total)
void RAMFUNCTION wolfBoot_start(void)
{
int active = -1, ret = 0;
/* Candidates already tried this boot; a failed RAM image cannot be
* erased to invalidate it like the flash path does. */
int tried_boot = 0, tried_update = 0;
struct wolfBoot_image os_image;
BENCHMARK_DECLARE();
#ifdef WOLFBOOT_UBOOT_LEGACY
@ -425,12 +428,24 @@ backup_on_failure:
wolfBoot_printf("Impossible recovery with fallback.\n");
wolfBoot_panic();
break;
} else {
/* Invalidate failing image and switch to the other partition */
active ^= 1;
wolfBoot_printf("Active is now: %d\n", active);
continue;
}
if (active == PART_BOOT)
tried_boot = 1;
else
tried_update = 1;
if (tried_boot && tried_update) {
/* Both partitions were tried and both failed: the images that
* made fallback look possible are invalid, and nothing left to
* boot. */
wolfBoot_printf(
"Both images failed verification; no valid image to boot.\n");
wolfBoot_panic();
break;
}
/* Switch to the other partition */
active ^= 1;
wolfBoot_printf("Active is now: %d\n", active);
continue;
}
#ifdef UNIT_TEST
if (wolfBoot_panicked != 0) {

View File

@ -476,6 +476,32 @@ START_TEST (test_invalid_sha) {
cleanup_flash();
}
START_TEST (test_both_images_corrupted_panics) {
uint8_t bad_digest[SHA256_DIGEST_SIZE];
reset_mock_stats();
prepare_flash();
/* Same version in both partitions: after a failure the loop must not
* be stopped by the anti-rollback guard, only by noticing that both
* candidates have already failed. */
add_payload(PART_BOOT, 2, TEST_SIZE_SMALL);
add_payload(PART_UPDATE, 2, TEST_SIZE_SMALL);
/* Corrupt both digests: two present images (nonzero versions, so the
* fallback check passes) that both fail verification. The boot loop
* must panic once both have been tried, not alternate forever. */
memset(bad_digest, 0xBA, SHA256_DIGEST_SIZE);
ext_flash_unlock();
ext_flash_write(WOLFBOOT_PARTITION_BOOT_ADDRESS + DIGEST_TLV_OFF_IN_HDR + 4, bad_digest, SHA256_DIGEST_SIZE);
ext_flash_write(WOLFBOOT_PARTITION_UPDATE_ADDRESS + DIGEST_TLV_OFF_IN_HDR + 4, bad_digest, SHA256_DIGEST_SIZE);
ext_flash_lock();
wolfBoot_start();
ck_assert(!wolfBoot_staged_ok);
ck_assert_int_eq(wolfBoot_panicked, 1);
cleanup_flash();
}
END_TEST
START_TEST (test_emergency_rollback_to_older_version_denied) {
uint8_t testing_flags[5] = { IMG_STATE_TESTING, 'B', 'O', 'O', 'T' };
reset_mock_stats();
@ -597,6 +623,7 @@ Suite *wolfboot_suite(void)
TCase *emergency_rollback_failure_due_to_bad_update = tcase_create("Emergency rollback failure due to bad update");
TCase *empty_boot_partition_update = tcase_create("Empty boot partition update");
TCase *empty_boot_but_update_sha_corrupted_denied = tcase_create("Empty boot partition but update SHA corrupted");
TCase *both_images_corrupted = tcase_create("Both images corrupted");
@ -620,6 +647,7 @@ Suite *wolfboot_suite(void)
tcase_add_test(emergency_rollback_failure_due_to_bad_update, test_emergency_rollback_failure_due_to_bad_update);
tcase_add_test(empty_boot_partition_update, test_empty_boot_partition_update);
tcase_add_test(empty_boot_but_update_sha_corrupted_denied, test_empty_boot_but_update_sha_corrupted_denied);
tcase_add_test(both_images_corrupted, test_both_images_corrupted_panics);
@ -642,6 +670,7 @@ Suite *wolfboot_suite(void)
suite_add_tcase(s, emergency_rollback_failure_due_to_bad_update);
suite_add_tcase(s, empty_boot_partition_update);
suite_add_tcase(s, empty_boot_but_update_sha_corrupted_denied);
suite_add_tcase(s, both_images_corrupted);
/* Set timeout for tests */
@ -664,6 +693,7 @@ Suite *wolfboot_suite(void)
tcase_set_timeout(emergency_rollback_failure_due_to_bad_update, 5);
tcase_set_timeout(empty_boot_partition_update, 5);
tcase_set_timeout(empty_boot_but_update_sha_corrupted_denied, 5);
tcase_set_timeout(both_images_corrupted, 5);
return s;