diff --git a/.github/workflows/test-hooks-simulator.yml b/.github/workflows/test-hooks-simulator.yml index 23f385ac70..2db52abe72 100644 --- a/.github/workflows/test-hooks-simulator.yml +++ b/.github/workflows/test-hooks-simulator.yml @@ -88,6 +88,11 @@ jobs: WOLFBOOT_HOOK_BOOT=1 \ WOLFBOOT_HOOK_PANIC=1 + - name: Run dualbank rollback denial simulation + if: matrix.mechanism == 'dualbank' + run: | + tools/scripts/sim-dualbank-rollback-denied.sh + - name: Clear hook log run: | rm -f /tmp/wolfboot_hooks.log diff --git a/.github/workflows/test-sunnyday-simulator.yml b/.github/workflows/test-sunnyday-simulator.yml index f8b3dd4b73..d17eda864e 100644 --- a/.github/workflows/test-sunnyday-simulator.yml +++ b/.github/workflows/test-sunnyday-simulator.yml @@ -54,6 +54,10 @@ jobs: cp config/examples/sim-dualbank.config .config make test-sim-internal-flash-with-update + - name: Run dualbank rollback denial simulation + run: | + tools/scripts/sim-dualbank-rollback-denied.sh + - name: Run dualbank swap simulation run: | tools/scripts/sim-dualbank-swap-update.sh diff --git a/src/libwolfboot.c b/src/libwolfboot.c index f5161446d5..a5610e493b 100644 --- a/src/libwolfboot.c +++ b/src/libwolfboot.c @@ -1395,6 +1395,15 @@ int wolfBoot_dualboot_candidate(void) (wolfBoot_get_partition_state(candidate, &p_state) == 0) && (p_state == IMG_STATE_TESTING)) { +#ifndef ALLOW_DOWNGRADE + uint32_t candidate_v = (candidate == PART_BOOT) ? boot_v : update_v; + uint32_t fallback_v = (candidate == PART_BOOT) ? update_v : boot_v; + + if (fallback_v < candidate_v) { + wolfBoot_printf("Rollback to lower version not allowed\n"); + return candidate; + } +#endif wolfBoot_erase_partition(candidate); candidate ^= 1; /* switch to other partition if available */ } diff --git a/src/update_disk.c b/src/update_disk.c index 244d911582..cf11258733 100644 --- a/src/update_disk.c +++ b/src/update_disk.c @@ -253,12 +253,14 @@ void RAMFUNCTION wolfBoot_start(void) #endif struct wolfBoot_image os_image; int pA_ver = 0, pB_ver = 0; + uint32_t pA_ver_u = 0U, pB_ver_u = 0U; uint32_t cur_part = 0; int ret = -1; int selected; uint32_t *load_address; int failures = 0; uint32_t load_off; + uint32_t max_ver; const uint8_t *hdr_ptr = NULL; #ifdef MMU uint8_t *dts_addr = NULL; @@ -345,10 +347,16 @@ void RAMFUNCTION wolfBoot_start(void) wolfBoot_panic(); } - wolfBoot_printf("Versions, A:%u B:%u\r\n", pA_ver, pB_ver); + if (pA_ver > 0) + pA_ver_u = (uint32_t)pA_ver; + if (pB_ver > 0) + pB_ver_u = (uint32_t)pB_ver; + + wolfBoot_printf("Versions, A:%u B:%u\r\n", pA_ver_u, pB_ver_u); + max_ver = (pB_ver_u > pA_ver_u) ? pB_ver_u : pA_ver_u; /* Choose partition with higher version */ - selected = (pB_ver > pA_ver) ? 1: 0; + selected = (pB_ver_u > pA_ver_u) ? 1 : 0; #ifdef WOLFBOOT_FSP stage2_params = stage2_get_parameters(); @@ -368,6 +376,16 @@ void RAMFUNCTION wolfBoot_start(void) cur_part = BOOT_PART_B; else cur_part = BOOT_PART_A; +#ifndef ALLOW_DOWNGRADE + { + uint32_t cur_ver = selected ? pB_ver_u : pA_ver_u; + if ((max_ver > 0U) && (cur_ver < max_ver)) { + wolfBoot_printf("Rollback to lower version not allowed\r\n"); + wolfBoot_panic(); + return; + } + } +#endif part_name[2] = 'A' + selected; @@ -498,6 +516,7 @@ void RAMFUNCTION wolfBoot_start(void) #endif wolfBoot_printf("Unable to find a valid partition!\r\n"); wolfBoot_panic(); + return; } disk_close(BOOT_DISK); diff --git a/src/update_flash_hwswap.c b/src/update_flash_hwswap.c index 65ce639184..47d66e7d0f 100644 --- a/src/update_flash_hwswap.c +++ b/src/update_flash_hwswap.c @@ -28,6 +28,7 @@ #include "hooks.h" #include "spi_flash.h" #include "wolfboot/wolfboot.h" +#include "printf.h" #ifdef SECURE_PKCS11 int WP11_Library_Init(void); #endif @@ -45,12 +46,33 @@ void RAMFUNCTION wolfBoot_start(void) int active; struct wolfBoot_image fw_image; uint8_t p_state; +#ifndef ALLOW_DOWNGRADE + int boot_v_raw = (int)wolfBoot_current_firmware_version(); + int update_v_raw = (int)wolfBoot_update_firmware_version(); + uint32_t boot_v = 0U; + uint32_t update_v = 0U; + uint32_t max_v = (boot_v > update_v) ? boot_v : update_v; + + if (boot_v_raw >= 0) + boot_v = (uint32_t)boot_v_raw; + if (update_v_raw >= 0) + update_v = (uint32_t)update_v_raw; + max_v = (boot_v > update_v) ? boot_v : update_v; +#endif active = wolfBoot_dualboot_candidate(); if (active < 0) /* panic if no images available */ boot_panic(); for (;;) { +#ifndef ALLOW_DOWNGRADE + uint32_t active_v = (active == PART_UPDATE) ? update_v : boot_v; + if ((max_v > 0U) && (active_v < max_v)) { + wolfBoot_printf("Rollback to lower version not allowed\n"); + boot_panic(); + return; + } +#endif if ((wolfBoot_open_image(&fw_image, active) < 0) #ifndef WOLFBOOT_SKIP_BOOT_VERIFY || (wolfBoot_verify_integrity(&fw_image) < 0) diff --git a/src/update_ram.c b/src/update_ram.c index 247299064c..0ceef8d530 100644 --- a/src/update_ram.c +++ b/src/update_ram.c @@ -138,6 +138,19 @@ void RAMFUNCTION wolfBoot_start(void) uint8_t *dts_addr = NULL; uint32_t dts_size = 0; #endif +#if !defined(ALLOW_DOWNGRADE) && defined(WOLFBOOT_FIXED_PARTITIONS) + int boot_v_raw = (int)wolfBoot_current_firmware_version(); + int update_v_raw = (int)wolfBoot_update_firmware_version(); + uint32_t boot_v = 0U; + uint32_t update_v = 0U; + uint32_t max_v = 0U; + + if (boot_v_raw >= 0) + boot_v = (uint32_t)boot_v_raw; + if (update_v_raw >= 0) + update_v = (uint32_t)update_v_raw; + max_v = (boot_v > update_v) ? boot_v : update_v; +#endif /* !ALLOW_DOWNGRADE && WOLFBOOT_FIXED_PARTITIONS */ memset(&os_image, 0, sizeof(struct wolfBoot_image)); @@ -162,6 +175,16 @@ void RAMFUNCTION wolfBoot_start(void) wolfBoot_panic(); break; } +#if !defined(ALLOW_DOWNGRADE) && defined(WOLFBOOT_FIXED_PARTITIONS) + { + uint32_t active_v = (active == PART_UPDATE) ? update_v : boot_v; + if ((max_v > 0U) && (active_v < max_v)) { + wolfBoot_printf("Rollback to lower version not allowed\n"); + wolfBoot_panic(); + break; + } + } +#endif /* !ALLOW_DOWNGRADE && WOLFBOOT_FIXED_PARTITIONS */ #if defined(WOLFBOOT_DUALBOOT) && defined(WOLFBOOT_FIXED_PARTITIONS) wolfBoot_printf("Trying %s partition at %p\n", diff --git a/tools/keytools/keygen.c b/tools/keytools/keygen.c index 1b99309f79..5a3ed319c7 100644 --- a/tools/keytools/keygen.c +++ b/tools/keytools/keygen.c @@ -948,6 +948,7 @@ static void keygen_lms(const char *priv_fname, uint32_t id_mask) keystore_add(AUTH_KEY_LMS, lms_pub, KEYSTORE_PUBKEY_SIZE_LMS, priv_fname, id_mask); wc_LmsKey_Free(&key); + wc_ForceZero(&key, sizeof(key)); } #include "../xmss/xmss_common.h" diff --git a/tools/scripts/sim-dualbank-rollback-denied.sh b/tools/scripts/sim-dualbank-rollback-denied.sh new file mode 100755 index 0000000000..5cf18001e5 --- /dev/null +++ b/tools/scripts/sim-dualbank-rollback-denied.sh @@ -0,0 +1,65 @@ +#!/bin/bash +set -euo pipefail + +if [ ! -f ".config" ]; then + echo "Missing .config. Run make config first." >&2 + exit 1 +fi + +if ! grep -Eq '^(DUALBANK_SWAP(\?|)=1)' .config; then + echo "DUALBANK_SWAP=1 is required for this simulation." >&2 + exit 1 +fi + +if [ ! -x "./wolfboot.elf" ]; then + echo "wolfboot.elf not found. Build the simulator first." >&2 + exit 1 +fi + +if [ ! -f "./internal_flash.dd" ]; then + echo "internal_flash.dd not found. Build test-sim-internal-flash-with-update first." >&2 + exit 1 +fi + +backup_image="$(mktemp ./internal_flash.rollback.XXXXXX)" +cp ./internal_flash.dd "$backup_image" +trap 'cp "$backup_image" ./internal_flash.dd; rm -f "$backup_image" sim_registers.dd' EXIT + +rm -f sim_registers.dd + +update_addr_hex="$(grep '^WOLFBOOT_PARTITION_UPDATE_ADDRESS=' .config | cut -d= -f2)" +if [ -z "${update_addr_hex}" ]; then + echo "WOLFBOOT_PARTITION_UPDATE_ADDRESS is not set in .config." >&2 + exit 1 +fi + +update_addr=$((update_addr_hex)) + +# Corrupt UPDATE payload bytes so version metadata remains intact but +# image verification fails and boot logic attempts fallback. +printf '\x00\x00\x00\x00\x00\x00\x00\x00' | \ + dd of=./internal_flash.dd bs=1 seek="$((update_addr + 0x120))" conv=notrunc status=none + +set +e +rollback_output="$(timeout 3s ./wolfboot.elf get_version 2>&1)" +rollback_rc=$? +set -e + +if [ "$rollback_rc" -eq 0 ]; then + echo "Expected rollback denial, but boot continued normally." >&2 + exit 1 +fi + +if [ "$rollback_rc" -ne 124 ] && [ "$rollback_rc" -ne 80 ]; then + echo "Unexpected exit code while checking rollback denial: $rollback_rc" >&2 + echo "$rollback_output" >&2 + exit 1 +fi + +if ! printf '%s\n' "$rollback_output" | grep -q "Rollback to lower version not allowed"; then + echo "Rollback denial message not found in output." >&2 + echo "$rollback_output" >&2 + exit 1 +fi + +echo "Dualbank rollback-to-older-version denial verified." diff --git a/tools/tpm/policy_create.c b/tools/tpm/policy_create.c index 15b8555547..b4115d9041 100644 --- a/tools/tpm/policy_create.c +++ b/tools/tpm/policy_create.c @@ -54,7 +54,7 @@ int writeBin(const char* filename, const uint8_t*buf, word32 bufSz) XFILE fp = NULL; size_t fileSz = 0; - fp = XFOPEN(filename, "wt"); + fp = XFOPEN(filename, "wb"); if (fp != XBADFILE) { fileSz = XFWRITE(buf, 1, bufSz, fp); /* sanity check */ diff --git a/tools/tpm/policy_sign.c b/tools/tpm/policy_sign.c index 80ee12d182..e0ee352ef4 100644 --- a/tools/tpm/policy_sign.c +++ b/tools/tpm/policy_sign.c @@ -159,7 +159,10 @@ static int PolicySign(int alg, const char* keyFile, byte* hash, word32 hashSz, rc = BAD_FUNC_ARG; } - XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (buf != NULL) { + wc_ForceZero(buf, bufSz); + XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } wc_FreeRng(&rng); if (rc != 0) { @@ -221,7 +224,7 @@ static int writeBin(const char* filename, const byte *buf, word32 bufSz) FILE *fp = NULL; size_t fileSz = 0; - fp = fopen(filename, "wt"); + fp = fopen(filename, "wb"); if (fp != NULL) { fileSz = fwrite(buf, 1, bufSz, fp); /* sanity check */ diff --git a/tools/unit-tests/unit-update-disk.c b/tools/unit-tests/unit-update-disk.c index 2e7831ded3..a80acafb80 100644 --- a/tools/unit-tests/unit-update-disk.c +++ b/tools/unit-tests/unit-update-disk.c @@ -33,6 +33,7 @@ static int mock_disk_init_ret; static int mock_disk_close_called; static int mock_do_boot_called; static const uint32_t *mock_boot_address; +static int mock_fail_payload_part; ChaCha chacha; @@ -72,6 +73,7 @@ static void reset_mocks(void) mock_disk_close_called = 0; mock_do_boot_called = 0; mock_boot_address = NULL; + mock_fail_payload_part = -1; wolfBoot_panicked = 0; } @@ -140,6 +142,8 @@ int disk_part_read(int drv, int part, uint64_t off, uint64_t sz, uint8_t *buf) (void)drv; image = (part == BOOT_PART_B) ? part_b_image : part_a_image; + if ((mock_fail_payload_part == part) && (off >= IMAGE_HEADER_SIZE)) + return -1; if ((off > max) || (sz > (max - off))) return -1; memcpy(buf, image + off, (size_t)sz); @@ -288,6 +292,20 @@ START_TEST(test_get_decrypted_blob_version_rejects_truncated_version_tlv) } END_TEST +START_TEST(test_update_disk_rejects_rollback_after_higher_image_failure) +{ + reset_mocks(); + build_image(part_a_image, 7, 0xA1); + build_image(part_b_image, 5, 0xB2); + mock_fail_payload_part = BOOT_PART_A; + + 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"); @@ -297,6 +315,7 @@ Suite *wolfboot_suite(void) tcase_add_test(tc, test_update_disk_zeroizes_key_material_before_boot); tcase_add_test(tc, test_update_disk_prefers_primary_partition_when_versions_equal); tcase_add_test(tc, test_get_decrypted_blob_version_rejects_truncated_version_tlv); + tcase_add_test(tc, test_update_disk_rejects_rollback_after_higher_image_failure); suite_add_tcase(s, tc); return s; diff --git a/tools/unit-tests/unit-update-ram-nofixed.c b/tools/unit-tests/unit-update-ram-nofixed.c index 39d047f9ac..3551d93812 100644 --- a/tools/unit-tests/unit-update-ram-nofixed.c +++ b/tools/unit-tests/unit-update-ram-nofixed.c @@ -27,6 +27,7 @@ static __thread unsigned char #define WOLFBOOT_LOAD_ADDRESS (((uintptr_t)wolfboot_ram) + IMAGE_HEADER_SIZE) #define TEST_SIZE_SMALL 5300 #define DIGEST_TLV_OFF_IN_HDR 28 +#define STAGE_ADDR_SENTINEL UINTPTR_MAX #include "user_settings.h" #include "wolfboot/wolfboot.h" @@ -52,7 +53,8 @@ int wolfBoot_dualboot_candidate_addr(void** addr) #include int wolfBoot_staged_ok = 0; -const uint32_t *wolfBoot_stage_address = (uint32_t *)0xFFFFFFFF; +const uint32_t *wolfBoot_stage_address = + (const uint32_t *)(uintptr_t)STAGE_ADDR_SENTINEL; void* hal_get_primary_address(void) { @@ -191,7 +193,7 @@ static int add_payload(uint8_t part, uint32_t version, uint32_t size) return 0; } -START_TEST(test_invalid_update_falls_back_to_boot_without_reselect_loop) +START_TEST(test_invalid_update_falls_back_to_boot) { uint8_t bad_digest[SHA256_DIGEST_SIZE]; @@ -209,7 +211,8 @@ START_TEST(test_invalid_update_falls_back_to_boot_without_reselect_loop) wolfBoot_start(); ck_assert_int_eq(wolfBoot_staged_ok, 1); - ck_assert_ptr_eq(wolfBoot_stage_address, (const uint32_t *)WOLFBOOT_LOAD_ADDRESS); + ck_assert_int_eq(wolfBoot_panicked, 0); + ck_assert_uint_eq((uintptr_t)wolfBoot_stage_address, WOLFBOOT_LOAD_ADDRESS); cleanup_flash(); } END_TEST @@ -219,7 +222,7 @@ static Suite *wolfboot_suite(void) Suite *s = suite_create("wolfboot-update-ram-nofixed"); TCase *tc = tcase_create("fallback"); - tcase_add_test(tc, test_invalid_update_falls_back_to_boot_without_reselect_loop); + tcase_add_test(tc, test_invalid_update_falls_back_to_boot); tcase_set_timeout(tc, 5); suite_add_tcase(s, tc); diff --git a/tools/unit-tests/unit-update-ram.c b/tools/unit-tests/unit-update-ram.c index fecac50540..3f262d5665 100644 --- a/tools/unit-tests/unit-update-ram.c +++ b/tools/unit-tests/unit-update-ram.c @@ -378,8 +378,9 @@ START_TEST (test_invalid_update_type) { ext_flash_lock(); wolfBoot_update_trigger(); wolfBoot_start(); - ck_assert(wolfBoot_staged_ok); - ck_assert(get_version_ramloaded() == 1); + ck_assert(!wolfBoot_staged_ok); + ck_assert_int_eq(wolfBoot_panicked, 1); + ck_assert_int_eq(get_version_ramloaded(), 2); cleanup_flash(); } @@ -396,8 +397,8 @@ START_TEST (test_update_toolarge) { wolfBoot_update_trigger(); wolfBoot_start(); - ck_assert(wolfBoot_staged_ok); - ck_assert(get_version_ramloaded() == 1); + ck_assert(!wolfBoot_staged_ok); + ck_assert_int_eq(wolfBoot_panicked, 1); cleanup_flash(); } @@ -414,12 +415,12 @@ START_TEST (test_invalid_sha) { ext_flash_lock(); wolfBoot_update_trigger(); wolfBoot_start(); - ck_assert(wolfBoot_staged_ok); - ck_assert(get_version_ramloaded() == 1); + ck_assert(!wolfBoot_staged_ok); + ck_assert_int_eq(wolfBoot_panicked, 1); cleanup_flash(); } -START_TEST (test_emergency_rollback) { +START_TEST (test_emergency_rollback_to_older_version_denied) { uint8_t testing_flags[5] = { IMG_STATE_TESTING, 'B', 'O', 'O', 'T' }; reset_mock_stats(); prepare_flash(); @@ -433,7 +434,28 @@ START_TEST (test_emergency_rollback) { wolfBoot_start(); ck_assert(wolfBoot_staged_ok); - ck_assert(get_version_ramloaded() == 1); + ck_assert_int_eq(get_version_ramloaded(), 2); + ck_assert_int_eq(wolfBoot_panicked, 0); + cleanup_flash(); +} + +START_TEST (test_dualboot_candidate_rejects_testing_rollback_to_lower_version) { + uint8_t testing_flags[5] = { IMG_STATE_TESTING, 'B', 'O', 'O', 'T' }; + int candidate; + + reset_mock_stats(); + prepare_flash(); + add_payload(PART_BOOT, 2, TEST_SIZE_SMALL); + add_payload(PART_UPDATE, 1, TEST_SIZE_SMALL); + + ext_flash_unlock(); + ext_flash_write(WOLFBOOT_PARTITION_BOOT_ADDRESS + WOLFBOOT_PARTITION_SIZE - 5, + testing_flags, 5); + ext_flash_lock(); + + candidate = wolfBoot_dualboot_candidate(); + ck_assert_int_eq(candidate, PART_BOOT); + ck_assert_uint_eq(wolfBoot_current_firmware_version(), 2U); cleanup_flash(); } @@ -513,6 +535,8 @@ Suite *wolfboot_suite(void) TCase *update_toolarge = tcase_create("Update too large"); TCase *invalid_sha = tcase_create("Invalid SHA digest"); TCase *emergency_rollback = tcase_create("Emergency rollback"); + TCase *dualboot_candidate_rollback_denied = + tcase_create("Dualboot candidate rollback denied"); 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"); @@ -532,7 +556,9 @@ Suite *wolfboot_suite(void) tcase_add_test(invalid_update_type, test_invalid_update_type); tcase_add_test(update_toolarge, test_update_toolarge); tcase_add_test(invalid_sha, test_invalid_sha); - tcase_add_test(emergency_rollback, test_emergency_rollback); + tcase_add_test(emergency_rollback, test_emergency_rollback_to_older_version_denied); + tcase_add_test(dualboot_candidate_rollback_denied, + test_dualboot_candidate_rejects_testing_rollback_to_lower_version); 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); @@ -553,6 +579,7 @@ Suite *wolfboot_suite(void) suite_add_tcase(s, update_toolarge); suite_add_tcase(s, invalid_sha); suite_add_tcase(s, emergency_rollback); + suite_add_tcase(s, dualboot_candidate_rollback_denied); 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); @@ -573,6 +600,7 @@ Suite *wolfboot_suite(void) tcase_set_timeout(update_toolarge, 5); tcase_set_timeout(invalid_sha, 5); tcase_set_timeout(emergency_rollback, 5); + tcase_set_timeout(dualboot_candidate_rollback_denied, 5); 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);