From 1c824539228f9e8ea5642b352e0c582c57ad725c Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 15:38:18 +0200 Subject: [PATCH 01/75] sign: scrub key buffers before free F/2273 --- tools/keytools/sign.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index 5109c9d9a2..be4e8e1f78 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -296,6 +296,23 @@ static struct cmd_options CMD = { .hybrid = 0 }; +static void zero_and_free(uint8_t *buf, uint32_t len) +{ + volatile uint8_t *p; + uint32_t i; + + if (buf == NULL) { + return; + } + + p = buf; + for (i = 0; i < len; i++) { + p[i] = 0; + } + + free(buf); +} + static uint16_t sign_tool_find_header(uint8_t *haystack, uint16_t type, uint8_t **ptr) { uint8_t *p = haystack; @@ -942,7 +959,7 @@ static uint8_t *load_key(uint8_t **key_buffer, uint32_t *key_buffer_sz, failure: if (*key_buffer != NULL) { - free(*key_buffer); + zero_and_free(*key_buffer, *key_buffer_sz); *key_buffer = NULL; } return NULL; @@ -3009,7 +3026,7 @@ int main(int argc, char** argv) DEBUG_PRINT("Secondary signature size: %u\n", CMD.secondary_signature_sz); DEBUG_PRINT("Header size: %u\n", CMD.header_sz); if (kbuf2) - free(kbuf2); + zero_and_free(kbuf2, key_buffer_sz2); if (pubkey2) free(pubkey2); } else { @@ -3029,7 +3046,7 @@ int main(int argc, char** argv) free(pubkey); if (kbuf) - free(kbuf); + zero_and_free(kbuf, key_buffer_sz); if (CMD.sign == SIGN_ED25519) { wc_ed25519_free(&key.ed); } From 87d5a7fc9a49d356815a7f3fd624e5954d60c35f Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 15:39:47 +0200 Subject: [PATCH 02/75] Scrub RSA keygen private material F/2274 --- tools/keytools/keygen.c | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/tools/keytools/keygen.c b/tools/keytools/keygen.c index 96104fa962..cd6a8c9284 100644 --- a/tools/keytools/keygen.c +++ b/tools/keytools/keygen.c @@ -539,31 +539,41 @@ static void keygen_rsa(const char *keyfile, int kbits, uint32_t id_mask) uint8_t priv_der[4096], pub_der[2048]; int privlen, publen; FILE *fpriv; + int ret = 0; + int exit_code = 0; + int rsa_init = 0; - if (wc_InitRsaKey(&k, NULL) != 0) { + ret = wc_InitRsaKey(&k, NULL); + if (ret != 0) { fprintf(stderr, "Unable to initialize RSA%d key\n", kbits); exit(1); } + rsa_init = 1; - if (wc_MakeRsaKey(&k, kbits, 65537, &rng) != 0) { + ret = wc_MakeRsaKey(&k, kbits, 65537, &rng); + if (ret != 0) { fprintf(stderr, "Unable to create RSA%d key\n", kbits); - exit(1); + exit_code = 1; + goto cleanup; } privlen = wc_RsaKeyToDer(&k, priv_der, kbits); if (privlen <= 0) { fprintf(stderr, "Unable to export private key to DER\n"); - exit(2); + exit_code = 2; + goto cleanup; } publen = wc_RsaKeyToPublicDer(&k, pub_der, kbits); if (publen <= 0) { fprintf(stderr, "Unable to export public key\n"); - exit(3); + exit_code = 3; + goto cleanup; } printf("RSA public key len: %d bytes\n", publen); fpriv = fopen(keyfile, "wb"); if (fpriv == NULL) { fprintf(stderr, "Unable to open file '%s' for writing: %s", keyfile, strerror(errno)); - exit(4); + exit_code = 4; + goto cleanup; } fwrite(priv_der, privlen, 1, fpriv); fclose(fpriv); @@ -571,7 +581,8 @@ static void keygen_rsa(const char *keyfile, int kbits, uint32_t id_mask) if (exportPubKey) { if (export_pubkey_file(keyfile, pub_der, publen) != 0) { fprintf(stderr, "Unable to export public key to file\n"); - exit(5); + exit_code = 5; + goto cleanup; } } @@ -581,6 +592,13 @@ static void keygen_rsa(const char *keyfile, int kbits, uint32_t id_mask) keystore_add(AUTH_KEY_RSA3072, pub_der, publen, keyfile, id_mask); else if (kbits == 4096) keystore_add(AUTH_KEY_RSA4096, pub_der, publen, keyfile, id_mask); + +cleanup: + wc_ForceZero(priv_der, sizeof(priv_der)); + if (rsa_init) + wc_FreeRsaKey(&k); + if (exit_code != 0) + exit(exit_code); } #define MAX_ECC_KEY_SIZE 66 From 114140c232e819f13a8d1dbd639d00d94f402fcb Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 15:40:48 +0200 Subject: [PATCH 03/75] Zero ECC keygen private buffers F/2275 --- tools/keytools/keygen.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/keytools/keygen.c b/tools/keytools/keygen.c index cd6a8c9284..ae111570d9 100644 --- a/tools/keytools/keygen.c +++ b/tools/keytools/keygen.c @@ -666,7 +666,7 @@ static void keygen_ecc(const char *priv_fname, uint16_t ecc_key_size, if (exportPubKey) { int pubOutLen; /* Reuse priv_der buffer for public key */ - memset(priv_der, 0, sizeof(priv_der)); + wc_ForceZero(priv_der, sizeof(priv_der)); if (saveAsDer) { /* If you want public key also exported as a DER file and not as RAW @@ -693,6 +693,8 @@ static void keygen_ecc(const char *priv_fname, uint16_t ecc_key_size, } wc_ecc_free(&k); + wc_ForceZero(d, sizeof(d)); + wc_ForceZero(priv_der, sizeof(priv_der)); memcpy(k_buffer, Qx, ecc_key_size); memcpy(k_buffer + ecc_key_size, Qy, ecc_key_size); From 6f40afaedfabcab37cebcbca448829e182f0534d Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 15:42:14 +0200 Subject: [PATCH 04/75] Zero EdDSA keygen private data F/2276 --- tools/keytools/keygen.c | 66 ++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/tools/keytools/keygen.c b/tools/keytools/keygen.c index ae111570d9..e1a8be3105 100644 --- a/tools/keytools/keygen.c +++ b/tools/keytools/keygen.c @@ -714,22 +714,37 @@ static void keygen_ed25519(const char *privkey, uint32_t id_mask) uint8_t priv[32], pub[32]; FILE *fpriv; uint32_t outlen = ED25519_KEY_SIZE; + int exit_code = 0; + int key_init = 0; + + key_init = wc_ed25519_init(&k); + if (key_init != 0) { + fprintf(stderr, "Unable to initialize ed25519 key\n"); + exit_code = 1; + goto cleanup; + } + if (wc_ed25519_make_key(&rng, ED25519_KEY_SIZE, &k) != 0) { fprintf(stderr, "Unable to create ed25519 key\n"); - exit(1); + exit_code = 1; + goto cleanup; } if (wc_ed25519_export_private_only(&k, priv, &outlen) != 0) { fprintf(stderr, "Unable to export ed25519 private key\n"); - exit(2); + exit_code = 2; + goto cleanup; } + outlen = ED25519_PUB_KEY_SIZE; if (wc_ed25519_export_public(&k, pub, &outlen) != 0) { fprintf(stderr, "Unable to export ed25519 public key\n"); - exit(2); + exit_code = 2; + goto cleanup; } fpriv = fopen(privkey, "wb"); if (fpriv == NULL) { fprintf(stderr, "Unable to open file '%s' for writing: %s", privkey, strerror(errno)); - exit(3); + exit_code = 3; + goto cleanup; } fwrite(priv, 32, 1, fpriv); fwrite(pub, 32, 1, fpriv); @@ -738,11 +753,19 @@ static void keygen_ed25519(const char *privkey, uint32_t id_mask) if (exportPubKey) { if (export_pubkey_file(privkey, pub, ED25519_PUB_KEY_SIZE) != 0) { fprintf(stderr, "Unable to export public key to file\n"); - exit(4); + exit_code = 4; + goto cleanup; } } keystore_add(AUTH_KEY_ED25519, pub, ED25519_PUB_KEY_SIZE, privkey, id_mask); + +cleanup: + wc_ForceZero(priv, sizeof(priv)); + if (key_init == 0) + wc_ed25519_free(&k); + if (exit_code != 0) + exit(exit_code); } static void keygen_ed448(const char *privkey, uint32_t id_mask) @@ -751,22 +774,37 @@ static void keygen_ed448(const char *privkey, uint32_t id_mask) uint8_t priv[ED448_KEY_SIZE], pub[ED448_PUB_KEY_SIZE]; FILE *fpriv; uint32_t outlen = ED448_KEY_SIZE; + int exit_code = 0; + int key_init = 0; + + key_init = wc_ed448_init(&k); + if (key_init != 0) { + fprintf(stderr, "Unable to initialize ed448 key\n"); + exit_code = 1; + goto cleanup; + } + if (wc_ed448_make_key(&rng, ED448_KEY_SIZE, &k) != 0) { fprintf(stderr, "Unable to create ed448 key\n"); - exit(1); + exit_code = 1; + goto cleanup; } if (wc_ed448_export_private_only(&k, priv, &outlen) != 0) { fprintf(stderr, "Unable to export ed448 private key\n"); - exit(2); + exit_code = 2; + goto cleanup; } + outlen = ED448_PUB_KEY_SIZE; if (wc_ed448_export_public(&k, pub, &outlen) != 0) { fprintf(stderr, "Unable to export ed448 public key\n"); - exit(2); + exit_code = 2; + goto cleanup; } fpriv = fopen(privkey, "wb"); if (fpriv == NULL) { fprintf(stderr, "Unable to open file 'ed448.der' for writing: %s", strerror(errno)); - exit(3); + exit_code = 3; + goto cleanup; } fwrite(priv, ED448_KEY_SIZE, 1, fpriv); fwrite(pub, ED448_PUB_KEY_SIZE, 1, fpriv); @@ -775,11 +813,19 @@ static void keygen_ed448(const char *privkey, uint32_t id_mask) if (exportPubKey) { if (export_pubkey_file(privkey, pub, ED448_PUB_KEY_SIZE) != 0) { fprintf(stderr, "Unable to export public key to file\n"); - exit(4); + exit_code = 4; + goto cleanup; } } keystore_add(AUTH_KEY_ED448, pub, ED448_PUB_KEY_SIZE, privkey, id_mask); + +cleanup: + wc_ForceZero(priv, sizeof(priv)); + if (key_init == 0) + wc_ed448_free(&k); + if (exit_code != 0) + exit(exit_code); } #include "../lms/lms_common.h" From 32783034ab7bb20cc278fa8de58ee20864a10756 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 15:43:26 +0200 Subject: [PATCH 05/75] zero ML-DSA private key buffer F/2277 --- tools/keytools/keygen.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/keytools/keygen.c b/tools/keytools/keygen.c index e1a8be3105..818ac7bf3d 100644 --- a/tools/keytools/keygen.c +++ b/tools/keytools/keygen.c @@ -1205,6 +1205,7 @@ static void keygen_ml_dsa(const char *priv_fname, uint32_t id_mask) keystore_add(AUTH_KEY_ML_DSA, pub, pub_len, priv_fname, id_mask); wc_MlDsaKey_Free(&key); + wc_ForceZero(priv, priv_len); free(priv); priv = NULL; } From 8399e3ed44f823204dee93dcce49f587d2b78759 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 15:47:47 +0200 Subject: [PATCH 06/75] Add restricted key mask authenticity tests F/2258 --- tools/unit-tests/unit-image.c | 80 +++++++++++++++++++++++++++++++- tools/unit-tests/unit-keystore.c | 2 +- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/tools/unit-tests/unit-image.c b/tools/unit-tests/unit-image.c index b20611182c..fd0f48819a 100644 --- a/tools/unit-tests/unit-image.c +++ b/tools/unit-tests/unit-image.c @@ -186,7 +186,7 @@ static const unsigned char test_img_v200000000_wrong_pubkey_bin[] = { static uint16_t _find_header(uint8_t *haystack, uint16_t type, uint8_t **ptr); -static void patch_pubkey_hint(uint8_t *img, uint32_t img_len) +static void patch_pubkey_hint_slot(uint8_t *img, uint32_t img_len, uint8_t slot) { uint8_t *ptr = NULL; uint16_t len; @@ -195,10 +195,15 @@ static void patch_pubkey_hint(uint8_t *img, uint32_t img_len) (void)img_len; len = _find_header(img + IMAGE_HEADER_OFFSET, HDR_PUBKEY, &ptr); ck_assert_int_eq(len, WOLFBOOT_SHA_DIGEST_SIZE); - key_hash(0, hash); + key_hash(slot, hash); memcpy(ptr, hash, WOLFBOOT_SHA_DIGEST_SIZE); } +static void patch_pubkey_hint(uint8_t *img, uint32_t img_len) +{ + patch_pubkey_hint_slot(img, img_len, 0); +} + static void patch_signature_len(uint8_t *img, uint32_t img_len, uint16_t new_len) { uint8_t *ptr = NULL; @@ -225,6 +230,22 @@ static void patch_image_type_auth(uint8_t *img, uint32_t img_len) ptr[0] = (uint8_t)(type & 0xFF); ptr[1] = (uint8_t)(type >> 8); } + +static void patch_image_type_part(uint8_t *img, uint32_t img_len, uint16_t part) +{ + uint8_t *ptr = NULL; + uint16_t len; + uint16_t type; + + (void)img_len; + len = _find_header(img + IMAGE_HEADER_OFFSET, HDR_IMG_TYPE, &ptr); + ck_assert_int_eq(len, sizeof(uint16_t)); + type = (uint16_t)(ptr[0] | (ptr[1] << 8)); + type = (uint16_t)((type & ~HDR_IMG_TYPE_PART_MASK) | + (part & HDR_IMG_TYPE_PART_MASK)); + ptr[0] = (uint8_t)(type & 0xFF); + ptr[1] = (uint8_t)(type >> 8); +} static const unsigned int test_img_len = 275; @@ -672,6 +693,57 @@ START_TEST(test_verify_authenticity_bad_siglen) ck_assert_int_eq(ret, -1); } END_TEST + +START_TEST(test_verify_authenticity_rejects_disallowed_key_mask) +{ + 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_auth(buf, sizeof(buf)); + patch_pubkey_hint_slot(buf, sizeof(buf), 1); + patch_image_type_part(buf, sizeof(buf), HDR_IMG_TYPE_WOLFBOOT); + + find_header_mocked = 0; + find_header_fail = 0; + hdr_cpy_done = 0; + ext_flash_write(0, buf, sizeof(buf)); + + memset(&test_img, 0, sizeof(struct wolfBoot_image)); + test_img.part = PART_UPDATE; + test_img.signature_ok = 1; + ret = wolfBoot_verify_authenticity(&test_img); + ck_assert_int_eq(ret, -1); +} +END_TEST + +START_TEST(test_verify_authenticity_allows_permitted_key_mask) +{ + 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_auth(buf, sizeof(buf)); + patch_pubkey_hint_slot(buf, sizeof(buf), 1); + patch_image_type_part(buf, sizeof(buf), HDR_IMG_TYPE_APP); + + find_header_mocked = 0; + find_header_fail = 0; + hdr_cpy_done = 0; + ecc_import_fail = 0; + ecc_init_fail = 0; + ext_flash_erase(0, 2 * WOLFBOOT_SECTOR_SIZE); + ext_flash_write(0, buf, sizeof(buf)); + + memset(&test_img, 0, sizeof(struct wolfBoot_image)); + test_img.part = PART_UPDATE; + test_img.signature_ok = 1; + ret = wolfBoot_verify_authenticity(&test_img); + ck_assert_int_eq(ret, 0); +} +END_TEST #endif #ifdef WOLFBOOT_FIXED_PARTITIONS @@ -826,6 +898,10 @@ Suite *wolfboot_suite(void) tcase_set_timeout(tcase_verify_authenticity, 20); tcase_add_test(tcase_verify_authenticity, test_verify_authenticity); tcase_add_test(tcase_verify_authenticity, test_verify_authenticity_bad_siglen); + tcase_add_test(tcase_verify_authenticity, + test_verify_authenticity_rejects_disallowed_key_mask); + tcase_add_test(tcase_verify_authenticity, + test_verify_authenticity_allows_permitted_key_mask); suite_add_tcase(s, tcase_verify_authenticity); #endif diff --git a/tools/unit-tests/unit-keystore.c b/tools/unit-tests/unit-keystore.c index 27856dc6a9..7b9371eb6d 100644 --- a/tools/unit-tests/unit-keystore.c +++ b/tools/unit-tests/unit-keystore.c @@ -117,7 +117,7 @@ const KEYSTORE_SECTION struct keystore_slot PubKeys[NUM_PUBKEYS] = { { .slot_id = 1, .key_type = UNIT_KEY_TYPE, - .part_id_mask = 0xFFFFFFFF, + .part_id_mask = KEY_VERIFY_APP_ONLY, .pubkey_size = UNIT_PUBKEY_SIZE, .pubkey = { 0x00 }, }, From 1442e1c8d0c74fab5af43ad1b52c1ac70a17369e Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 15:54:26 +0200 Subject: [PATCH 07/75] Propagate encrypt key flash errors F/1892 --- src/libwolfboot.c | 10 +++++----- tools/unit-tests/unit-enc-nvm.c | 30 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/libwolfboot.c b/src/libwolfboot.c index 658cf6aeea..00164e94d9 100644 --- a/src/libwolfboot.c +++ b/src/libwolfboot.c @@ -1549,7 +1549,7 @@ static int RAMFUNCTION hal_set_key(const uint8_t *k, const uint8_t *nonce) /* erase the old key */ ret = hal_flash_erase(addr_align, WOLFBOOT_SECTOR_SIZE); if (ret != 0) - return ret; + goto exit_lock; #endif /* Populate key + nonce in the cache */ @@ -1574,7 +1574,7 @@ static int RAMFUNCTION hal_set_key(const uint8_t *k, const uint8_t *nonce) ret = hal_flash_write(addr_align, ENCRYPT_CACHE, WOLFBOOT_SECTOR_SIZE); #ifdef NVM_FLASH_WRITEONCE if (ret != 0) - return ret; + goto exit_lock; /* Erasing original sector "sel_sec", * same one returned from by nvm_select. */ @@ -1582,6 +1582,7 @@ static int RAMFUNCTION hal_set_key(const uint8_t *k, const uint8_t *nonce) addr_align -= (sel_sec * WOLFBOOT_SECTOR_SIZE); ret = hal_flash_erase(addr_align, WOLFBOOT_SECTOR_SIZE); #endif +exit_lock: hal_flash_lock(); return ret; #endif @@ -1596,14 +1597,13 @@ static int RAMFUNCTION hal_set_key(const uint8_t *k, const uint8_t *nonce) * @param key Pointer to the encryption key. * @param nonce Pointer to the encryption nonce. * - * @return 0 if successful. + * @return 0 on success, or the underlying flash error code on failure. * */ int RAMFUNCTION wolfBoot_set_encrypt_key(const uint8_t *key, const uint8_t *nonce) { - hal_set_key(key, nonce); - return 0; + return hal_set_key(key, nonce); } #ifndef UNIT_TEST diff --git a/tools/unit-tests/unit-enc-nvm.c b/tools/unit-tests/unit-enc-nvm.c index ce98c88ae6..81a0ceb711 100644 --- a/tools/unit-tests/unit-enc-nvm.c +++ b/tools/unit-tests/unit-enc-nvm.c @@ -291,6 +291,34 @@ START_TEST (test_nvm_update_with_encryption) } END_TEST +START_TEST(test_set_encrypt_key_propagates_flash_write_error) +{ + int ret; + static const uint8_t key[ENCRYPT_KEY_SIZE] = { 0x11 }; + static const uint8_t nonce[ENCRYPT_NONCE_SIZE] = { 0x22 }; + + ret = mmap_file("/tmp/wolfboot-unit-enc-key.bin", (void *)MOCK_ADDRESS, + WOLFBOOT_PARTITION_SIZE, NULL); + ck_assert(ret >= 0); + ret = mmap_file("/tmp/wolfboot-unit-enc-key-int.bin", + (void *)MOCK_ADDRESS_BOOT, WOLFBOOT_PARTITION_SIZE, NULL); + ck_assert(ret >= 0); + ret = mmap_file("/tmp/wolfboot-unit-enc-key-swap.bin", (void *)MOCK_ADDRESS_SWAP, + WOLFBOOT_SECTOR_SIZE, NULL); + ck_assert(ret >= 0); + + hal_flash_unlock(); + wolfBoot_erase_partition(PART_BOOT); + hal_flash_lock(); + + hal_flash_write_fail = 1; + ret = wolfBoot_set_encrypt_key(key, nonce); + + ck_assert_int_eq(ret, -1); + ck_assert_msg(locked, "The FLASH was left unlocked.\n"); +} +END_TEST + Suite *wolfboot_suite(void) { @@ -300,6 +328,8 @@ Suite *wolfboot_suite(void) /* Test cases */ TCase *nvm_update_with_encryption = tcase_create("NVM update with encryption"); tcase_add_test(nvm_update_with_encryption, test_nvm_update_with_encryption); + tcase_add_test(nvm_update_with_encryption, + test_set_encrypt_key_propagates_flash_write_error); suite_add_tcase(s, nvm_update_with_encryption); return s; From de4c33ee442af6ba3d8d3ce7f49646c3c7f49214 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 15:56:21 +0200 Subject: [PATCH 08/75] Propagate erase encrypt key write failures F/1893 --- src/libwolfboot.c | 4 +++- tools/unit-tests/unit-enc-nvm.c | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/libwolfboot.c b/src/libwolfboot.c index 00164e94d9..ee479e2120 100644 --- a/src/libwolfboot.c +++ b/src/libwolfboot.c @@ -1659,6 +1659,7 @@ int RAMFUNCTION wolfBoot_erase_encrypt_key(void) #elif defined(MMU) ForceZero(ENCRYPT_KEY, ENCRYPT_KEY_SIZE + ENCRYPT_NONCE_SIZE); #else + int ret = 0; uint8_t ff[ENCRYPT_KEY_SIZE + ENCRYPT_NONCE_SIZE]; uint8_t *mem = (uint8_t *)ENCRYPT_TMP_SECRET_OFFSET + WOLFBOOT_PARTITION_BOOT_ADDRESS; @@ -1668,7 +1669,8 @@ int RAMFUNCTION wolfBoot_erase_encrypt_key(void) #endif XMEMSET(ff, FLASH_BYTE_ERASED, ENCRYPT_KEY_SIZE + ENCRYPT_NONCE_SIZE); if (XMEMCMP(mem, ff, ENCRYPT_KEY_SIZE + ENCRYPT_NONCE_SIZE) != 0) - hal_set_key(ff, ff + ENCRYPT_KEY_SIZE); + ret = hal_set_key(ff, ff + ENCRYPT_KEY_SIZE); + return ret; #endif return 0; } diff --git a/tools/unit-tests/unit-enc-nvm.c b/tools/unit-tests/unit-enc-nvm.c index 81a0ceb711..a4811c3ff4 100644 --- a/tools/unit-tests/unit-enc-nvm.c +++ b/tools/unit-tests/unit-enc-nvm.c @@ -319,6 +319,37 @@ START_TEST(test_set_encrypt_key_propagates_flash_write_error) } END_TEST +START_TEST(test_erase_encrypt_key_propagates_flash_write_error) +{ + int ret; + static const uint8_t key[ENCRYPT_KEY_SIZE] = { 0x33 }; + static const uint8_t nonce[ENCRYPT_NONCE_SIZE] = { 0x44 }; + + ret = mmap_file("/tmp/wolfboot-unit-enc-erase.bin", (void *)MOCK_ADDRESS, + WOLFBOOT_PARTITION_SIZE, NULL); + ck_assert(ret >= 0); + ret = mmap_file("/tmp/wolfboot-unit-enc-erase-int.bin", + (void *)MOCK_ADDRESS_BOOT, WOLFBOOT_PARTITION_SIZE, NULL); + ck_assert(ret >= 0); + ret = mmap_file("/tmp/wolfboot-unit-enc-erase-swap.bin", + (void *)MOCK_ADDRESS_SWAP, WOLFBOOT_SECTOR_SIZE, NULL); + ck_assert(ret >= 0); + + hal_flash_unlock(); + wolfBoot_erase_partition(PART_BOOT); + hal_flash_lock(); + + ret = wolfBoot_set_encrypt_key(key, nonce); + ck_assert_int_eq(ret, 0); + + hal_flash_write_fail = 1; + ret = wolfBoot_erase_encrypt_key(); + + ck_assert_int_eq(ret, -1); + ck_assert_msg(locked, "The FLASH was left unlocked.\n"); +} +END_TEST + Suite *wolfboot_suite(void) { @@ -330,6 +361,8 @@ Suite *wolfboot_suite(void) tcase_add_test(nvm_update_with_encryption, test_nvm_update_with_encryption); tcase_add_test(nvm_update_with_encryption, test_set_encrypt_key_propagates_flash_write_error); + tcase_add_test(nvm_update_with_encryption, + test_erase_encrypt_key_propagates_flash_write_error); suite_add_tcase(s, nvm_update_with_encryption); return s; From 364b9d099bb9634c05827df3ee6ebf1afd255e02 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 15:59:07 +0200 Subject: [PATCH 09/75] Fix policy_create PCR digest validation F/1894 --- tools/tpm/policy_create.c | 2 +- tools/unit-tests/Makefile | 9 +- tools/unit-tests/unit-policy-create.c | 169 ++++++++++++++++++++++++++ 3 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 tools/unit-tests/unit-policy-create.c diff --git a/tools/tpm/policy_create.c b/tools/tpm/policy_create.c index 0ea6ce3663..15b8555547 100644 --- a/tools/tpm/policy_create.c +++ b/tools/tpm/policy_create.c @@ -241,7 +241,7 @@ int main(int argc, char *argv[]) pcrDigestSz = -1; else pcrDigestSz = hexToByte(hashHexStr, pcrDigest, hashHexStrLen); - if (pcrDigestSz <= 0) { + if ((int)pcrDigestSz <= 0) { fprintf(stderr, "Invalid PCR hash length\n"); usage(); return -1; diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 4edf38319e..cbcfbc707f 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -49,7 +49,8 @@ TESTS:=unit-parser unit-fdt unit-extflash unit-string unit-spi-flash unit-aes128 unit-update-flash-enc unit-update-ram unit-pkcs11_store unit-psa_store unit-disk \ unit-update-disk unit-multiboot unit-boot-x86-fsp unit-qspi-flash unit-tpm-rsa-exp \ unit-image-nopart unit-image-sha384 unit-image-sha3-384 unit-store-sbrk \ - unit-tpm-blob unit-policy-sign unit-rot-auth unit-sdhci-response-bits + unit-tpm-blob unit-policy-create unit-policy-sign unit-rot-auth \ + unit-sdhci-response-bits TESTS+=unit-tpm-check-rot-auth all: $(TESTS) @@ -144,6 +145,12 @@ unit-tpm-blob: ../../include/target.h unit-tpm-blob.c -DWOLFBOOT_HASH_SHA256 \ -ffunction-sections -fdata-sections $(LDFLAGS) -Wl,--gc-sections +unit-policy-create: ../../include/target.h unit-policy-create.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/memory.c + gcc -o $@ $^ -I../tpm $(CFLAGS) -I$(WOLFBOOT_LIB_WOLFTPM) -DWOLFBOOT_TPM \ + -DWOLFTPM_USER_SETTINGS -DWOLFBOOT_SIGN_ECC256 -DWOLFBOOT_HASH_SHA256 \ + -ffunction-sections -fdata-sections $(LDFLAGS) -Wl,--gc-sections + unit-policy-sign: ../../include/target.h unit-policy-sign.c \ $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/memory.c gcc -o $@ $^ -I../tpm $(CFLAGS) -I$(WOLFBOOT_LIB_WOLFTPM) -DWOLFBOOT_TPM \ diff --git a/tools/unit-tests/unit-policy-create.c b/tools/unit-tests/unit-policy-create.c new file mode 100644 index 0000000000..d00638cd0d --- /dev/null +++ b/tools/unit-tests/unit-policy-create.c @@ -0,0 +1,169 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include "tpm.h" + +static int policy_pcr_make_calls; +static int policy_ref_make_calls; +static int hash_digest_size_calls; + +#define TPM2_IoCb NULL +#define XSTRTOL strtol + +int wolfTPM2_PolicyPCRMake(TPM_ALG_ID pcrAlg, byte* pcrArray, word32 pcrArraySz, + const byte* pcrDigest, word32 pcrDigestSz, byte* digest, word32* digestSz) +{ + (void)pcrAlg; + (void)pcrArray; + (void)pcrArraySz; + (void)pcrDigest; + (void)pcrDigestSz; + (void)digest; + (void)digestSz; + policy_pcr_make_calls++; + return -200; +} + +int wolfTPM2_PolicyRefMake(TPM_ALG_ID pcrAlg, byte* digest, word32* digestSz, + const byte* policyRef, word32 policyRefSz) +{ + (void)pcrAlg; + (void)digest; + (void)digestSz; + (void)policyRef; + (void)policyRefSz; + policy_ref_make_calls++; + return -201; +} + +int wolfTPM2_Init(WOLFTPM2_DEV* dev, TPM2HalIoCb ioCb, void* userCtx) +{ + (void)dev; + (void)ioCb; + (void)userCtx; + return 0; +} + +int wolfTPM2_PCRGetDigest(WOLFTPM2_DEV* dev, TPM_ALG_ID pcrAlg, byte* pcrArray, + word32 pcrArraySz, byte* pcrDigest, word32* pcrDigestSz) +{ + (void)dev; + (void)pcrAlg; + (void)pcrArray; + (void)pcrArraySz; + (void)pcrDigest; + (void)pcrDigestSz; + return -202; +} + +int wolfTPM2_Cleanup(WOLFTPM2_DEV* dev) +{ + (void)dev; + return 0; +} + +int TPM2_GetHashDigestSize(TPMI_ALG_HASH hashAlg) +{ + (void)hashAlg; + hash_digest_size_calls++; + return 32; +} + +const char* TPM2_GetAlgName(TPM_ALG_ID alg) +{ + (void)alg; + return "SHA256"; +} + +const char* wolfTPM2_GetRCString(int rc) +{ + (void)rc; + return "stub"; +} + +#define main policy_create_tool_main +#include "../tpm/policy_create.c" +#undef main + +static void setup(void) +{ + policy_pcr_make_calls = 0; + policy_ref_make_calls = 0; + hash_digest_size_calls = 0; +} + +static void make_oversized_hex_arg(char* dst, size_t dst_sz, const char* prefix) +{ + size_t prefix_len = strlen(prefix); + size_t hex_len = (WC_MAX_DIGEST_SIZE * 2U) + 2U; + + ck_assert_uint_gt(dst_sz, prefix_len + hex_len); + memcpy(dst, prefix, prefix_len); + memset(dst + prefix_len, 'a', hex_len); + dst[prefix_len + hex_len] = '\0'; +} + +START_TEST(test_policy_create_rejects_oversized_pcr_digest) +{ + char arg[sizeof("-pcrdigest=") + (WC_MAX_DIGEST_SIZE * 2) + 3]; + char* argv[] = { (char*)"policy_create", arg }; + int rc; + + make_oversized_hex_arg(arg, sizeof(arg), "-pcrdigest="); + rc = policy_create_tool_main(2, argv); + + ck_assert_int_eq(rc, -1); + ck_assert_int_eq(hash_digest_size_calls, 0); + ck_assert_int_eq(policy_pcr_make_calls, 0); + ck_assert_int_eq(policy_ref_make_calls, 0); +} +END_TEST + +START_TEST(test_policy_create_rejects_invalid_pcr_digest_hex) +{ + char arg[] = "-pcrdigest=zz"; + char* argv[] = { (char*)"policy_create", arg }; + int rc; + + rc = policy_create_tool_main(2, argv); + + ck_assert_int_eq(rc, -1); + ck_assert_int_eq(hash_digest_size_calls, 0); + ck_assert_int_eq(policy_pcr_make_calls, 0); + ck_assert_int_eq(policy_ref_make_calls, 0); +} +END_TEST + +static Suite* policy_create_suite(void) +{ + Suite* s; + TCase* tc; + + s = suite_create("policy_create"); + tc = tcase_create("argument_validation"); + tcase_add_checked_fixture(tc, setup, NULL); + tcase_add_test(tc, test_policy_create_rejects_oversized_pcr_digest); + tcase_add_test(tc, test_policy_create_rejects_invalid_pcr_digest_hex); + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + Suite* s; + SRunner* sr; + int failed; + + s = policy_create_suite(); + sr = srunner_create(s); + srunner_run_all(sr, CK_NORMAL); + failed = srunner_ntests_failed(sr); + srunner_free(sr); + return failed == 0 ? 0 : 1; +} From c6e7f7966b57076e904bf96802c7e144bf78519c Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 16:04:28 +0200 Subject: [PATCH 10/75] Fix sign encrypted output open failure F/1895 --- tools/keytools/sign.c | 3 +- tools/unit-tests/Makefile | 17 +- tools/unit-tests/unit-sign-encrypted-output.c | 181 ++++++++++++++++++ .../unit-sign-encrypted-output.mkfrag | 30 +++ 4 files changed, 228 insertions(+), 3 deletions(-) create mode 100644 tools/unit-tests/unit-sign-encrypted-output.c create mode 100644 tools/unit-tests/unit-sign-encrypted-output.mkfrag diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index be4e8e1f78..e9481269f6 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -1899,7 +1899,8 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, fef = fopen(CMD.output_encrypted_image_file, "wb"); if (!fef) { fprintf(stderr, "Open encrypted output file %s: %s\n", - CMD.encrypt_key_file, strerror(errno)); + CMD.output_encrypted_image_file, strerror(errno)); + goto failure; } fsize = ftell(f); fseek(f, 0, SEEK_SET); /* restart the _signed file from 0 */ diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index cbcfbc707f..2a4ac55487 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -49,10 +49,12 @@ TESTS:=unit-parser unit-fdt unit-extflash unit-string unit-spi-flash unit-aes128 unit-update-flash-enc unit-update-ram unit-pkcs11_store unit-psa_store unit-disk \ unit-update-disk unit-multiboot unit-boot-x86-fsp unit-qspi-flash unit-tpm-rsa-exp \ unit-image-nopart unit-image-sha384 unit-image-sha3-384 unit-store-sbrk \ - unit-tpm-blob unit-policy-create unit-policy-sign unit-rot-auth \ - unit-sdhci-response-bits + unit-tpm-blob unit-policy-create unit-policy-sign unit-rot-auth unit-sdhci-response-bits \ + unit-sign-encrypted-output TESTS+=unit-tpm-check-rot-auth +include unit-sign-encrypted-output.mkfrag + all: $(TESTS) cov: @@ -158,6 +160,17 @@ unit-policy-sign: ../../include/target.h unit-policy-sign.c \ -DHAVE_ECC_KEY_IMPORT \ -ffunction-sections -fdata-sections $(LDFLAGS) -Wl,--gc-sections +<<<<<<< HEAD +======= +unit-sign-encrypted-output: ../../include/target.h unit-sign-encrypted-output.c \ + $(KEYTOOLS_SIGN_SRCS) + gcc -o $@ $^ -I../keytools $(CFLAGS) -DML_DSA_LEVEL=2 \ + -D"LMS_LEVELS=1" -D"LMS_HEIGHT=10" -D"LMS_WINTERNITZ=8" \ + -DWOLFBOOT_XMSS_PARAMS=\"XMSS-SHA2_10_256\" \ + -ffunction-sections -fdata-sections \ + $(LDFLAGS) -Wl,--gc-sections + +>>>>>>> 34438999 (Fix sign encrypted output open failure) unit-rot-auth: ../../include/target.h unit-rot-auth.c \ $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/memory.c gcc -o $@ $^ -I../tpm $(CFLAGS) -I$(WOLFBOOT_LIB_WOLFTPM) -DWOLFBOOT_TPM \ diff --git a/tools/unit-tests/unit-sign-encrypted-output.c b/tools/unit-tests/unit-sign-encrypted-output.c new file mode 100644 index 0000000000..24d1175573 --- /dev/null +++ b/tools/unit-tests/unit-sign-encrypted-output.c @@ -0,0 +1,181 @@ +/* unit-sign-encrypted-output.c + * + * Unit test for sign tool encrypted output error handling. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *mock_fail_open_path; +static int mock_null_fwrite_calls; +static int mock_null_fclose_calls; +static char mock_last_error[512]; + +static FILE *mock_fopen(const char *path, const char *mode); +static size_t mock_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); +static int mock_fclose(FILE *stream); +static int mock_fprintf(FILE *stream, const char *format, ...); + +#define main wolfboot_sign_main +#define fopen mock_fopen +#define fwrite mock_fwrite +#define fclose mock_fclose +#define fprintf mock_fprintf +#include "../keytools/sign.c" +#undef fprintf +#undef fclose +#undef fwrite +#undef fopen +#undef main + +static void reset_mocks(const char *fail_open_path) +{ + mock_fail_open_path = fail_open_path; + mock_null_fwrite_calls = 0; + mock_null_fclose_calls = 0; + mock_last_error[0] = '\0'; +} + +static FILE *mock_fopen(const char *path, const char *mode) +{ + if (mock_fail_open_path != NULL && strcmp(path, mock_fail_open_path) == 0) { + errno = EACCES; + return NULL; + } + + return fopen(path, mode); +} + +static size_t mock_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) +{ + if (stream == NULL) { + mock_null_fwrite_calls++; + return 0; + } + + return fwrite(ptr, size, nmemb, stream); +} + +static int mock_fclose(FILE *stream) +{ + if (stream == NULL) { + mock_null_fclose_calls++; + return EOF; + } + + return fclose(stream); +} + +static int mock_fprintf(FILE *stream, const char *format, ...) +{ + int ret; + va_list args; + + va_start(args, format); + ret = vsnprintf(mock_last_error, sizeof(mock_last_error), format, args); + va_end(args); + + va_start(args, format); + ret = vfprintf(stream, format, args); + va_end(args); + + return ret; +} + +static int write_file(const char *path, const void *buf, size_t len) +{ + FILE *f = fopen(path, "wb"); + size_t written; + + if (f == NULL) { + return -1; + } + + written = fwrite(buf, 1, len, f); + fclose(f); + + return written == len ? 0 : -1; +} + +START_TEST(test_make_header_ex_fails_when_encrypted_output_open_fails) +{ + char tempdir[] = "/tmp/wolfboot-sign-XXXXXX"; + char image_path[PATH_MAX]; + char key_path[PATH_MAX]; + char output_path[PATH_MAX]; + char encrypted_output_path[PATH_MAX]; + uint8_t image_buf[] = { 0x01, 0x02, 0x03, 0x04 }; + uint8_t key_buf[ENCRYPT_KEY_SIZE_AES128 + ENCRYPT_NONCE_SIZE_AES]; + uint8_t pubkey[] = { 0xA5 }; + int ret; + + ck_assert_ptr_nonnull(mkdtemp(tempdir)); + + snprintf(image_path, sizeof(image_path), "%s/image.bin", tempdir); + snprintf(key_path, sizeof(key_path), "%s/encrypt.key", tempdir); + snprintf(output_path, sizeof(output_path), "%s/output.bin", tempdir); + snprintf(encrypted_output_path, sizeof(encrypted_output_path), + "%s/encrypted-output.bin", tempdir); + + memset(key_buf, 0x5A, sizeof(key_buf)); + ck_assert_int_eq(write_file(image_path, image_buf, sizeof(image_buf)), 0); + ck_assert_int_eq(write_file(key_path, key_buf, sizeof(key_buf)), 0); + + memset(&CMD, 0, sizeof(CMD)); + CMD.sign = NO_SIGN; + CMD.encrypt = ENC_AES128; + CMD.hash_algo = HASH_SHA256; + CMD.partition_id = HDR_IMG_TYPE_APP; + CMD.header_sz = 256; + CMD.fw_version = "7"; + CMD.no_ts = 1; + CMD.encrypt_key_file = key_path; + snprintf(CMD.output_encrypted_image_file, + sizeof(CMD.output_encrypted_image_file), "%s", encrypted_output_path); + + reset_mocks(encrypted_output_path); + ret = make_header_ex(0, pubkey, sizeof(pubkey), image_path, output_path, + 0, 0, 0, 0, NULL, 0, NULL, 0); + + ck_assert_int_ne(ret, 0); + ck_assert_int_eq(mock_null_fwrite_calls, 0); + ck_assert_int_eq(mock_null_fclose_calls, 0); + ck_assert_ptr_nonnull(strstr(mock_last_error, encrypted_output_path)); + ck_assert_ptr_null(strstr(mock_last_error, key_path)); + + unlink(output_path); + unlink(key_path); + unlink(image_path); + rmdir(tempdir); +} +END_TEST + +Suite *wolfboot_suite(void) +{ + Suite *s = suite_create("sign-encrypted-output"); + TCase *tcase = tcase_create("make-header-ex"); + + tcase_add_test(tcase, test_make_header_ex_fails_when_encrypted_output_open_fails); + suite_add_tcase(s, tcase); + + return s; +} + +int main(void) +{ + int failed; + Suite *s = wolfboot_suite(); + SRunner *runner = srunner_create(s); + + srunner_run_all(runner, CK_NORMAL); + failed = srunner_ntests_failed(runner); + srunner_free(runner); + + return failed == 0 ? 0 : 1; +} diff --git a/tools/unit-tests/unit-sign-encrypted-output.mkfrag b/tools/unit-tests/unit-sign-encrypted-output.mkfrag new file mode 100644 index 0000000000..69da996def --- /dev/null +++ b/tools/unit-tests/unit-sign-encrypted-output.mkfrag @@ -0,0 +1,30 @@ +KEYTOOLS_SIGN_SRCS=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/asn.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/aes.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/ecc.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/coding.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/chacha.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/ed25519.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/ed448.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/fe_operations.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/ge_operations.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/fe_448.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/ge_448.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/hash.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/memory.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/random.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/rsa.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sp_int.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sp_c32.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sp_c64.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha3.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha512.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/tfm.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/wc_port.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/wolfmath.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/wc_lms.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/wc_lms_impl.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/wc_xmss.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/wc_xmss_impl.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/dilithium.c \ + ../../src/delta.c From 2ab04ed382eac9ab785dd55464b124e2642010fa Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 16:06:11 +0200 Subject: [PATCH 11/75] Check image reopen failures in sign tool F/1898 --- tools/keytools/sign.c | 18 +++++ tools/unit-tests/unit-sign-encrypted-output.c | 69 ++++++++++++++++++- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index e9481269f6..b6416f3e6a 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -1417,6 +1417,12 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, /* Hash image file */ f = fopen(image_file, "rb"); + if (f == NULL) { + printf("Open image file %s failed\n", image_file); + ret = -1; + wc_Sha256Free(&sha); + goto failure; + } pos = 0; while (ret == 0 && pos < image_sz) { read_sz = image_sz - pos; @@ -1486,6 +1492,12 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, /* Hash image file */ f = fopen(image_file, "rb"); + if (f == NULL) { + printf("Open image file %s failed\n", image_file); + ret = -1; + wc_Sha384Free(&sha); + goto failure; + } pos = 0; while (ret == 0 && pos < image_sz) { read_sz = image_sz - pos; @@ -1553,6 +1565,12 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, /* Hash image file */ f = fopen(image_file, "rb"); + if (f == NULL) { + printf("Open image file %s failed\n", image_file); + ret = -1; + wc_Sha3_384_Free(&sha); + goto failure; + } pos = 0; while (ret == 0 && pos < image_sz) { read_sz = image_sz - pos; diff --git a/tools/unit-tests/unit-sign-encrypted-output.c b/tools/unit-tests/unit-sign-encrypted-output.c index 24d1175573..4fb1777956 100644 --- a/tools/unit-tests/unit-sign-encrypted-output.c +++ b/tools/unit-tests/unit-sign-encrypted-output.c @@ -13,17 +13,22 @@ #include static const char *mock_fail_open_path; +static int mock_fail_open_on_call; +static int mock_open_call_count; static int mock_null_fwrite_calls; +static int mock_null_fread_calls; static int mock_null_fclose_calls; static char mock_last_error[512]; static FILE *mock_fopen(const char *path, const char *mode); +static size_t mock_fread(void *ptr, size_t size, size_t nmemb, FILE *stream); static size_t mock_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); static int mock_fclose(FILE *stream); static int mock_fprintf(FILE *stream, const char *format, ...); #define main wolfboot_sign_main #define fopen mock_fopen +#define fread mock_fread #define fwrite mock_fwrite #define fclose mock_fclose #define fprintf mock_fprintf @@ -31,13 +36,17 @@ static int mock_fprintf(FILE *stream, const char *format, ...); #undef fprintf #undef fclose #undef fwrite +#undef fread #undef fopen #undef main -static void reset_mocks(const char *fail_open_path) +static void reset_mocks(const char *fail_open_path, int fail_open_on_call) { mock_fail_open_path = fail_open_path; + mock_fail_open_on_call = fail_open_on_call; + mock_open_call_count = 0; mock_null_fwrite_calls = 0; + mock_null_fread_calls = 0; mock_null_fclose_calls = 0; mock_last_error[0] = '\0'; } @@ -45,6 +54,11 @@ static void reset_mocks(const char *fail_open_path) static FILE *mock_fopen(const char *path, const char *mode) { if (mock_fail_open_path != NULL && strcmp(path, mock_fail_open_path) == 0) { + mock_open_call_count++; + } + + if (mock_fail_open_path != NULL && strcmp(path, mock_fail_open_path) == 0 && + mock_open_call_count == mock_fail_open_on_call) { errno = EACCES; return NULL; } @@ -52,6 +66,16 @@ static FILE *mock_fopen(const char *path, const char *mode) return fopen(path, mode); } +static size_t mock_fread(void *ptr, size_t size, size_t nmemb, FILE *stream) +{ + if (stream == NULL) { + mock_null_fread_calls++; + return 0; + } + + return fread(ptr, size, nmemb, stream); +} + static size_t mock_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) { if (stream == NULL) { @@ -139,12 +163,13 @@ START_TEST(test_make_header_ex_fails_when_encrypted_output_open_fails) snprintf(CMD.output_encrypted_image_file, sizeof(CMD.output_encrypted_image_file), "%s", encrypted_output_path); - reset_mocks(encrypted_output_path); + reset_mocks(encrypted_output_path, 1); ret = make_header_ex(0, pubkey, sizeof(pubkey), image_path, output_path, 0, 0, 0, 0, NULL, 0, NULL, 0); ck_assert_int_ne(ret, 0); ck_assert_int_eq(mock_null_fwrite_calls, 0); + ck_assert_int_eq(mock_null_fread_calls, 0); ck_assert_int_eq(mock_null_fclose_calls, 0); ck_assert_ptr_nonnull(strstr(mock_last_error, encrypted_output_path)); ck_assert_ptr_null(strstr(mock_last_error, key_path)); @@ -156,12 +181,52 @@ START_TEST(test_make_header_ex_fails_when_encrypted_output_open_fails) } END_TEST +START_TEST(test_make_header_ex_fails_when_image_reopen_fails) +{ + char tempdir[] = "/tmp/wolfboot-sign-XXXXXX"; + char image_path[PATH_MAX]; + char output_path[PATH_MAX]; + uint8_t image_buf[] = { 0x01, 0x02, 0x03, 0x04 }; + uint8_t pubkey[] = { 0xA5 }; + int ret; + + ck_assert_ptr_nonnull(mkdtemp(tempdir)); + + snprintf(image_path, sizeof(image_path), "%s/image.bin", tempdir); + snprintf(output_path, sizeof(output_path), "%s/output.bin", tempdir); + + ck_assert_int_eq(write_file(image_path, image_buf, sizeof(image_buf)), 0); + + memset(&CMD, 0, sizeof(CMD)); + CMD.sign = NO_SIGN; + CMD.hash_algo = HASH_SHA256; + CMD.partition_id = HDR_IMG_TYPE_APP; + CMD.header_sz = 256; + CMD.fw_version = "7"; + CMD.no_ts = 1; + + reset_mocks(image_path, 2); + ret = make_header_ex(0, pubkey, sizeof(pubkey), image_path, output_path, + 0, 0, 0, 0, NULL, 0, NULL, 0); + + ck_assert_int_ne(ret, 0); + ck_assert_int_eq(mock_null_fwrite_calls, 0); + ck_assert_int_eq(mock_null_fread_calls, 0); + ck_assert_int_eq(mock_null_fclose_calls, 0); + + unlink(output_path); + unlink(image_path); + rmdir(tempdir); +} +END_TEST + Suite *wolfboot_suite(void) { Suite *s = suite_create("sign-encrypted-output"); TCase *tcase = tcase_create("make-header-ex"); tcase_add_test(tcase, test_make_header_ex_fails_when_encrypted_output_open_fails); + tcase_add_test(tcase, test_make_header_ex_fails_when_image_reopen_fails); suite_add_tcase(s, tcase); return s; From 9715b4df26af07a743e5c326871a960c61a2cd7a Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 16:07:33 +0200 Subject: [PATCH 12/75] Use constant-time RSA hash comparison F/2247 --- include/image.h | 10 ++++++---- src/image.c | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/include/image.h b/include/image.h index c9e7fcba10..c9441a3d69 100644 --- a/include/image.h +++ b/include/image.h @@ -424,7 +424,8 @@ static void __attribute__((noinline)) wolfBoot_image_clear_signature_ok( asm volatile("mov r0, #50":::"r0"); \ asm volatile("mov r0, #50":::"r0"); \ asm volatile("mov r0, #50":::"r0"); \ - compare_res = XMEMCMP(digest, img->sha_hash, WOLFBOOT_SHA_DIGEST_SIZE); \ + compare_res = !image_CT_compare(digest, img->sha_hash, \ + WOLFBOOT_SHA_DIGEST_SIZE); \ /* Redundant checks that ensure the function actually returned 0 */ \ asm volatile("cmp r0, #0":::"cc"); \ asm volatile("cmp r0, #0":::"cc"); \ @@ -442,8 +443,9 @@ static void __attribute__((noinline)) wolfBoot_image_clear_signature_ok( asm volatile("cmp r0, #0":::"cc"); \ asm volatile("cmp r0, #0":::"cc"); \ asm volatile("bne hnope"); \ - /* Repeat memcmp call */ \ - compare_res = XMEMCMP(digest, img->sha_hash, WOLFBOOT_SHA_DIGEST_SIZE); \ + /* Repeat comparison call */ \ + compare_res = !image_CT_compare(digest, img->sha_hash, \ + WOLFBOOT_SHA_DIGEST_SIZE); \ compare_res; \ /* Redundant checks that ensure the function actually returned 0 */ \ asm volatile("cmp r0, #0":::"cc"); \ @@ -1234,7 +1236,7 @@ static void UNUSEDFUNCTION wolfBoot_image_clear_signature_ok( ret = fn(__VA_ARGS__); #define RSA_VERIFY_HASH(img,digest) \ - if (XMEMCMP(img->sha_hash, digest, WOLFBOOT_SHA_DIGEST_SIZE) == 0) \ + if (image_CT_compare(img->sha_hash, digest, WOLFBOOT_SHA_DIGEST_SIZE)) \ wolfBoot_image_confirm_signature_ok(img); #define PART_SANITY_CHECK(p) \ diff --git a/src/image.c b/src/image.c index d5ff5f3e1a..324f883aee 100644 --- a/src/image.c +++ b/src/image.c @@ -58,8 +58,8 @@ /* Globals */ static uint8_t digest[WOLFBOOT_SHA_DIGEST_SIZE] XALIGNED(4); -static int image_CT_compare(const uint8_t *expected, const uint8_t *actual, - uint32_t len) +static int __attribute__((noinline)) image_CT_compare( + const uint8_t *expected, const uint8_t *actual, uint32_t len) { uint8_t diff = 0; uint32_t i; From 44c5b4ab0a56c0b5f9bb1a10734ae19fdfb3cca8 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 16:13:59 +0200 Subject: [PATCH 13/75] Protect bootloader before application boot F/2255 --- hal/hal.c | 7 +++++++ hal/nrf5340.c | 8 -------- hal/skeleton.c | 5 ++++- include/hal.h | 1 + src/update_disk.c | 3 +++ src/update_flash.c | 3 +++ src/update_flash_hwswap.c | 3 +++ src/update_ram.c | 3 +++ 8 files changed, 24 insertions(+), 9 deletions(-) diff --git a/hal/hal.c b/hal/hal.c index 4fb243fe51..963ad544cc 100644 --- a/hal/hal.c +++ b/hal/hal.c @@ -281,6 +281,13 @@ int hal_flash_test_dualbank(void) #endif /* TEST_FLASH */ +WEAKFUNCTION int hal_flash_protect(haladdr_t address, int len) +{ + (void)address; + (void)len; + return 0; +} + WEAKFUNCTION int hal_uds_derive_key(uint8_t *out, size_t out_len) { (void)out; diff --git a/hal/nrf5340.c b/hal/nrf5340.c index f1772a4b03..5f7d7786a2 100644 --- a/hal/nrf5340.c +++ b/hal/nrf5340.c @@ -884,14 +884,6 @@ static void periph_unsecure() void hal_prepare_boot(void) { - /* Write protect bootloader region of flash. - * Not needed in TrustZone configs because the application - * runs in non-secure mode and the bootloader partition is marked as - * secure. */ -#ifndef TZEN - hal_flash_protect(WOLFBOOT_ORIGIN, BOOTLOADER_PARTITION_SIZE); -#endif - if (enableShm) { #ifdef TARGET_nrf5340_net if (doUpdateNet) { diff --git a/hal/skeleton.c b/hal/skeleton.c index bdd3597e05..6a35a3593f 100644 --- a/hal/skeleton.c +++ b/hal/skeleton.c @@ -34,6 +34,10 @@ void hal_init(void) void hal_prepare_boot(void) { + /* wolfBoot calls hal_flash_protect() before this hook. + * Override hal_flash_protect() to lock the bootloader region on + * targets that support runtime write protection, and use this hook + * only for any remaining platform-specific handoff work. */ } #endif @@ -55,4 +59,3 @@ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) { return 0; /* on success. */ } - diff --git a/include/hal.h b/include/hal.h index 5649019b30..979702e3a8 100644 --- a/include/hal.h +++ b/include/hal.h @@ -87,6 +87,7 @@ uint64_t hal_get_timer_us(void); #endif void hal_flash_unlock(void); void hal_flash_lock(void); +int hal_flash_protect(haladdr_t address, int len); void hal_prepare_boot(void); #ifdef DUALBANK_SWAP diff --git a/src/update_disk.c b/src/update_disk.c index 6d1b7d0514..11381c91be 100644 --- a/src/update_disk.c +++ b/src/update_disk.c @@ -547,6 +547,9 @@ void RAMFUNCTION wolfBoot_start(void) (void)hal_hsm_disconnect(); #elif defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER) (void)hal_hsm_server_cleanup(); +#endif +#ifndef TZEN + (void)hal_flash_protect(WOLFBOOT_ORIGIN, BOOTLOADER_PARTITION_SIZE); #endif hal_prepare_boot(); diff --git a/src/update_flash.c b/src/update_flash.c index 2ead2448be..5a6920cdbb 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -1475,6 +1475,9 @@ void RAMFUNCTION wolfBoot_start(void) (void)hal_hsm_server_cleanup(); #endif +#ifndef TZEN + (void)hal_flash_protect(WOLFBOOT_ORIGIN, BOOTLOADER_PARTITION_SIZE); +#endif hal_prepare_boot(); #ifdef WOLFBOOT_HOOK_BOOT diff --git a/src/update_flash_hwswap.c b/src/update_flash_hwswap.c index ef1db99514..53efb4943a 100644 --- a/src/update_flash_hwswap.c +++ b/src/update_flash_hwswap.c @@ -105,6 +105,9 @@ void RAMFUNCTION wolfBoot_start(void) (void)hal_hsm_disconnect(); #elif defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER) (void)hal_hsm_server_cleanup(); +#endif +#ifndef TZEN + (void)hal_flash_protect(WOLFBOOT_ORIGIN, BOOTLOADER_PARTITION_SIZE); #endif hal_prepare_boot(); #ifdef WOLFBOOT_HOOK_BOOT diff --git a/src/update_ram.c b/src/update_ram.c index 7f2beb5a73..c76063e00e 100644 --- a/src/update_ram.c +++ b/src/update_ram.c @@ -390,6 +390,9 @@ void RAMFUNCTION wolfBoot_start(void) (void)hal_hsm_server_cleanup(); #endif +#ifndef TZEN + (void)hal_flash_protect(WOLFBOOT_ORIGIN, BOOTLOADER_PARTITION_SIZE); +#endif hal_prepare_boot(); #ifdef WOLFBOOT_HOOK_BOOT From 12dd6becc681e68f980d988c377421d8d903ad24 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 16:15:51 +0200 Subject: [PATCH 14/75] Add auth type coverage for unit-image F/2259 --- tools/unit-tests/unit-image.c | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tools/unit-tests/unit-image.c b/tools/unit-tests/unit-image.c index fd0f48819a..fef59d2c95 100644 --- a/tools/unit-tests/unit-image.c +++ b/tools/unit-tests/unit-image.c @@ -231,6 +231,23 @@ static void patch_image_type_auth(uint8_t *img, uint32_t img_len) ptr[1] = (uint8_t)(type >> 8); } +static void patch_image_type_auth_value(uint8_t *img, uint32_t img_len, + uint16_t auth) +{ + uint8_t *ptr = NULL; + uint16_t len; + uint16_t type; + + (void)img_len; + len = _find_header(img + IMAGE_HEADER_OFFSET, HDR_IMG_TYPE, &ptr); + ck_assert_int_eq(len, sizeof(uint16_t)); + type = (uint16_t)(ptr[0] | (ptr[1] << 8)); + type = (uint16_t)((type & ~HDR_IMG_TYPE_AUTH_MASK) | + (auth & HDR_IMG_TYPE_AUTH_MASK)); + ptr[0] = (uint8_t)(type & 0xFF); + ptr[1] = (uint8_t)(type >> 8); +} + static void patch_image_type_part(uint8_t *img, uint32_t img_len, uint16_t part) { uint8_t *ptr = NULL; @@ -694,6 +711,29 @@ START_TEST(test_verify_authenticity_bad_siglen) } END_TEST +START_TEST(test_verify_authenticity_rejects_mismatched_auth_type) +{ + 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_auth_value(buf, sizeof(buf), HDR_IMG_TYPE_AUTH_RSA2048); + patch_pubkey_hint(buf, sizeof(buf)); + + find_header_mocked = 0; + find_header_fail = 0; + hdr_cpy_done = 0; + ext_flash_write(0, buf, sizeof(buf)); + + memset(&test_img, 0, sizeof(struct wolfBoot_image)); + test_img.part = PART_UPDATE; + test_img.signature_ok = 1; + ret = wolfBoot_verify_authenticity(&test_img); + ck_assert_int_eq(ret, -1); +} +END_TEST + START_TEST(test_verify_authenticity_rejects_disallowed_key_mask) { struct wolfBoot_image test_img; @@ -898,6 +938,8 @@ Suite *wolfboot_suite(void) tcase_set_timeout(tcase_verify_authenticity, 20); tcase_add_test(tcase_verify_authenticity, test_verify_authenticity); tcase_add_test(tcase_verify_authenticity, test_verify_authenticity_bad_siglen); + tcase_add_test(tcase_verify_authenticity, + test_verify_authenticity_rejects_mismatched_auth_type); tcase_add_test(tcase_verify_authenticity, test_verify_authenticity_rejects_disallowed_key_mask); tcase_add_test(tcase_verify_authenticity, From 30fb32f89ff9c6a3f175700a871bc7abdfdfcedb Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 16:18:52 +0200 Subject: [PATCH 15/75] Add auth-only invalid update test F/2260 --- tools/unit-tests/unit-update-flash.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tools/unit-tests/unit-update-flash.c b/tools/unit-tests/unit-update-flash.c index 4272fe005d..6b93b6f3a6 100644 --- a/tools/unit-tests/unit-update-flash.c +++ b/tools/unit-tests/unit-update-flash.c @@ -495,6 +495,23 @@ START_TEST (test_invalid_update_type) { cleanup_flash(); } +START_TEST (test_invalid_update_auth_type) { + reset_mock_stats(); + prepare_flash(); + uint16_t word16 = HDR_IMG_TYPE_AUTH_ECC256 | HDR_IMG_TYPE_APP; + add_payload(PART_BOOT, 1, TEST_SIZE_SMALL); + add_payload(PART_UPDATE, 2, TEST_SIZE_SMALL); + ext_flash_unlock(); + ext_flash_write(WOLFBOOT_PARTITION_UPDATE_ADDRESS + 20, (void *)&word16, 2); + ext_flash_lock(); + wolfBoot_update_trigger(); + wolfBoot_start(); + ck_assert(!wolfBoot_panicked); + ck_assert(wolfBoot_staged_ok); + ck_assert(wolfBoot_current_firmware_version() == 1); + cleanup_flash(); +} + START_TEST (test_update_toolarge) { uint32_t very_large = WOLFBOOT_PARTITION_SIZE; reset_mock_stats(); @@ -684,6 +701,8 @@ Suite *wolfboot_suite(void) tcase_create("Update to older version denied"); TCase *invalid_update_type = tcase_create("Invalid update type"); + TCase *invalid_update_auth_type = + tcase_create("Invalid update auth type"); TCase *update_toolarge = tcase_create("Update too large"); TCase *invalid_sha = tcase_create("Invalid SHA digest"); TCase *emergency_rollback = tcase_create("Emergency rollback"); @@ -714,6 +733,7 @@ Suite *wolfboot_suite(void) tcase_add_test(forward_update_sameversion_denied, test_forward_update_sameversion_denied); tcase_add_test(update_oldversion_denied, test_update_oldversion_denied); tcase_add_test(invalid_update_type, test_invalid_update_type); + tcase_add_test(invalid_update_auth_type, test_invalid_update_auth_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); @@ -735,6 +755,7 @@ Suite *wolfboot_suite(void) suite_add_tcase(s, forward_update_sameversion_denied); suite_add_tcase(s, update_oldversion_denied); suite_add_tcase(s, invalid_update_type); + suite_add_tcase(s, invalid_update_auth_type); suite_add_tcase(s, update_toolarge); suite_add_tcase(s, invalid_sha); suite_add_tcase(s, emergency_rollback); From 47b61ba2eb31c26a62a069042975cbedc7f7085e Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 16:26:05 +0200 Subject: [PATCH 16/75] Add RAM_CODE self-update unit coverage F/2261 --- tools/unit-tests/Makefile | 17 ++- tools/unit-tests/unit-update-flash.c | 188 ++++++++++++++++++++++++++- 2 files changed, 199 insertions(+), 6 deletions(-) diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 2a4ac55487..1e3c99af9f 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -46,6 +46,7 @@ TESTS:=unit-parser unit-fdt unit-extflash unit-string unit-spi-flash unit-aes128 unit-aes256 unit-chacha20 unit-pci unit-mock-state unit-sectorflags \ unit-image unit-image-rsa unit-nvm unit-nvm-flagshome unit-enc-nvm \ unit-enc-nvm-flagshome unit-delta unit-update-flash \ + unit-update-flash-self-update \ unit-update-flash-enc unit-update-ram unit-pkcs11_store unit-psa_store unit-disk \ unit-update-disk unit-multiboot unit-boot-x86-fsp unit-qspi-flash unit-tpm-rsa-exp \ unit-image-nopart unit-image-sha384 unit-image-sha3-384 unit-store-sbrk \ @@ -95,7 +96,13 @@ unit-delta:CFLAGS+=-DNVM_FLASH_WRITEONCE -DMOCK_PARTITIONS -DDELTA_UPDATES -DDEL unit-pkcs11_store:CFLAGS+=-I$(WOLFBOOT_LIB_WOLFPKCS11) -DMOCK_PARTITIONS -DMOCK_KEYVAULT -DSECURE_PKCS11 -DWOLFPKCS11_USER_SETTINGS unit-psa_store:CFLAGS+=-I$(WOLFBOOT_LIB_WOLFPSA) -DMOCK_PARTITIONS -DMOCK_KEYVAULT -DWOLFCRYPT_TZ_PSA unit-update-flash:CFLAGS+=-DMOCK_PARTITIONS -DWOLFBOOT_NO_SIGN -DUNIT_TEST_AUTH \ - -DWOLFBOOT_HASH_SHA256 -DPRINTF_ENABLED -DEXT_FLASH -DPART_UPDATE_EXT -DPART_SWAP_EXT + -DWOLFBOOT_HASH_SHA256 -DPRINTF_ENABLED -DEXT_FLASH -DPART_UPDATE_EXT -DPART_SWAP_EXT \ + -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 \ + -DARCH_FLASH_OFFSET=MOCK_ADDRESS_BOOT -DWOLFBOOT_VERSION=7 \ + -DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE unit-update-ram:CFLAGS+=-DMOCK_PARTITIONS -DWOLFBOOT_NO_SIGN -DUNIT_TEST_AUTH \ -DWOLFBOOT_HASH_SHA256 -DPRINTF_ENABLED -DEXT_FLASH -DPART_UPDATE_EXT \ -DPART_SWAP_EXT -DPART_BOOT_EXT -DWOLFBOOT_DUALBOOT -DNO_XIP @@ -183,6 +190,11 @@ unit-store-sbrk: unit-store-sbrk.c ../../src/store_sbrk.c unit-string: ../../include/target.h unit-string.c gcc -o $@ $^ $(CFLAGS) -DDEBUG_UART -DPRINTF_ENABLED $(LDFLAGS) +unit-update-flash-self-update: ../../include/target.h unit-update-flash.c + gcc -o $@ unit-update-flash.c ../../src/image.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c \ + $(CFLAGS) $(LDFLAGS) + unit-sdhci-response-bits: ../../include/target.h unit-sdhci-response-bits.c gcc -o $@ $^ $(CFLAGS) -ffunction-sections -fdata-sections $(LDFLAGS) \ -Wl,--gc-sections @@ -256,7 +268,8 @@ unit-update-flash: ../../include/target.h unit-update-flash.c 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 \ - -DCUSTOM_ENCRYPT_KEY -DUNIT_TEST_FALLBACK_ONLY + -DCUSTOM_ENCRYPT_KEY -DUNIT_TEST_FALLBACK_ONLY \ + -DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE unit-update-flash-enc: ../../include/target.h unit-update-flash.c gcc -o $@ unit-update-flash.c ../../src/image.c \ $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c \ diff --git a/tools/unit-tests/unit-update-flash.c b/tools/unit-tests/unit-update-flash.c index 6b93b6f3a6..11799a3ffe 100644 --- a/tools/unit-tests/unit-update-flash.c +++ b/tools/unit-tests/unit-update-flash.c @@ -51,6 +51,8 @@ const char *argv0; static void reset_mock_stats(void); static void prepare_flash(void); static void cleanup_flash(void); +static int add_payload_type(uint8_t part, uint32_t version, uint32_t size, + uint16_t img_type); #ifdef CUSTOM_ENCRYPT_KEY int wolfBoot_get_encrypt_key(uint8_t *k, uint8_t *nonce) @@ -78,6 +80,7 @@ int wolfBoot_erase_encrypt_key(void) } #endif +#ifndef UNIT_TEST_SELF_UPDATE_ONLY START_TEST (test_boot_success_sets_state) { uint8_t state = 0; @@ -96,27 +99,62 @@ START_TEST (test_boot_success_sets_state) cleanup_flash(); } END_TEST +#endif Suite *wolfboot_suite(void); int wolfBoot_staged_ok = 0; const uint32_t *wolfBoot_stage_address = (uint32_t *) 0xFFFFFFFF; +#ifdef RAM_CODE +static int arch_reboot_called = 0; +unsigned int _start_text = MOCK_ADDRESS_BOOT; +#endif void do_boot(const uint32_t *address) { /* Mock of do_boot */ +#ifndef ARCH_SIM if (wolfBoot_panicked) return; +#endif wolfBoot_staged_ok++; wolfBoot_stage_address = address; printf("Called do_boot with address %p\n", address); } +int hal_flash_protect(haladdr_t address, int len) +{ + (void)address; + (void)len; + return 0; +} + static void reset_mock_stats(void) { wolfBoot_staged_ok = 0; +#ifndef ARCH_SIM wolfBoot_panicked = 0; +#endif + erased_boot = 0; + erased_update = 0; + erased_swap = 0; + erased_nvm_bank0 = 0; + erased_nvm_bank1 = 0; + erased_vault = 0; ext_flash_reset_lock(); +#ifdef RAM_CODE + arch_reboot_called = 0; +#endif +} + +static void clear_erase_stats(void) +{ + erased_boot = 0; + erased_update = 0; + erased_swap = 0; + erased_nvm_bank0 = 0; + erased_nvm_bank1 = 0; + erased_vault = 0; } @@ -148,9 +186,15 @@ static void cleanup_flash(void) #define DIGEST_TLV_OFF_IN_HDR 28 static int add_payload(uint8_t part, uint32_t version, uint32_t size) +{ + return add_payload_type(part, version, size, + HDR_IMG_TYPE_AUTH_NONE | HDR_IMG_TYPE_APP); +} + +static int add_payload_type(uint8_t part, uint32_t version, uint32_t size, + uint16_t img_type) { uint32_t word; - uint16_t word16; int i; uint8_t *base = (uint8_t *)(uintptr_t)WOLFBOOT_PARTITION_BOOT_ADDRESS; int ret; @@ -182,9 +226,8 @@ static int add_payload(uint8_t part, uint32_t version, uint32_t size) word = 2 << 16 | HDR_IMG_TYPE; hal_flash_write((uintptr_t)base + 16, (void *)&word, 4); - word16 = HDR_IMG_TYPE_AUTH_NONE | HDR_IMG_TYPE_APP; - hal_flash_write((uintptr_t)base + 20, (void *)&word16, 2); - printf("Written img_type: %04X\n", word16); + hal_flash_write((uintptr_t)base + 20, (void *)&img_type, 2); + printf("Written img_type: %04X\n", img_type); /* Add 28B header to sha calculation */ ret = wc_Sha256Update(&sha, base, DIGEST_TLV_OFF_IN_HDR); @@ -225,6 +268,104 @@ static int add_payload(uint8_t part, uint32_t version, uint32_t size) } +#ifdef RAM_CODE +void arch_reboot(void) +{ + arch_reboot_called++; +} + +START_TEST (test_self_update_sameversion_erased) +{ + reset_mock_stats(); + prepare_flash(); + clear_erase_stats(); + add_payload_type(PART_UPDATE, WOLFBOOT_VERSION, TEST_SIZE_SMALL, + HDR_IMG_TYPE_WOLFBOOT | HDR_IMG_TYPE_AUTH); + ext_flash_unlock(); + wolfBoot_set_partition_state(PART_UPDATE, IMG_STATE_UPDATING); + ext_flash_lock(); + + wolfBoot_check_self_update(); + + ck_assert_int_eq(arch_reboot_called, 0); + ck_assert_int_ge(erased_update, 1); + ck_assert_uint_eq(*(uint32_t *)(uintptr_t)WOLFBOOT_PARTITION_UPDATE_ADDRESS, + 0xFFFFFFFFu); + cleanup_flash(); +} +END_TEST + +START_TEST (test_self_update_oldversion_erased) +{ + reset_mock_stats(); + prepare_flash(); + clear_erase_stats(); + add_payload_type(PART_UPDATE, WOLFBOOT_VERSION - 1, TEST_SIZE_SMALL, + HDR_IMG_TYPE_WOLFBOOT | HDR_IMG_TYPE_AUTH); + ext_flash_unlock(); + wolfBoot_set_partition_state(PART_UPDATE, IMG_STATE_UPDATING); + ext_flash_lock(); + + wolfBoot_check_self_update(); + + ck_assert_int_eq(arch_reboot_called, 0); + ck_assert_int_ge(erased_update, 1); + ck_assert_uint_eq(*(uint32_t *)(uintptr_t)WOLFBOOT_PARTITION_UPDATE_ADDRESS, + 0xFFFFFFFFu); + cleanup_flash(); +} +END_TEST + +START_TEST (test_self_update_newversion_invalid_integrity_denied) +{ + uint8_t bad_digest[SHA256_DIGEST_SIZE]; + + reset_mock_stats(); + prepare_flash(); + clear_erase_stats(); + add_payload_type(PART_UPDATE, WOLFBOOT_VERSION + 1, TEST_SIZE_SMALL, + HDR_IMG_TYPE_WOLFBOOT | HDR_IMG_TYPE_AUTH); + memset(bad_digest, 0xBA, sizeof(bad_digest)); + ext_flash_unlock(); + ext_flash_write(WOLFBOOT_PARTITION_UPDATE_ADDRESS + DIGEST_TLV_OFF_IN_HDR + 4, + bad_digest, sizeof(bad_digest)); + wolfBoot_set_partition_state(PART_UPDATE, IMG_STATE_UPDATING); + ext_flash_lock(); + + wolfBoot_check_self_update(); + + ck_assert_int_eq(arch_reboot_called, 0); + ck_assert_int_eq(erased_update, 0); + ck_assert_uint_eq(*(uint32_t *)(uintptr_t)WOLFBOOT_PARTITION_BOOT_ADDRESS, + 0xFFFFFFFFu); + cleanup_flash(); +} +END_TEST + +START_TEST (test_self_update_newversion_copies_and_reboots) +{ + reset_mock_stats(); + prepare_flash(); + clear_erase_stats(); + add_payload_type(PART_UPDATE, WOLFBOOT_VERSION + 1, TEST_SIZE_SMALL, + HDR_IMG_TYPE_WOLFBOOT | HDR_IMG_TYPE_AUTH); + ext_flash_unlock(); + wolfBoot_set_partition_state(PART_UPDATE, IMG_STATE_UPDATING); + ext_flash_lock(); + + wolfBoot_check_self_update(); + + ck_assert_int_eq(arch_reboot_called, 1); + ck_assert_int_ge(erased_boot, 1); + ck_assert_mem_eq((const void *)(uintptr_t)WOLFBOOT_PARTITION_BOOT_ADDRESS, + (const void *)(uintptr_t)(WOLFBOOT_PARTITION_UPDATE_ADDRESS + IMAGE_HEADER_SIZE), + FLASHBUFFER_SIZE); + cleanup_flash(); +} +END_TEST +#endif + +#ifndef UNIT_TEST_SELF_UPDATE_ONLY #ifdef EXT_ENCRYPTED static int build_image_buffer(uint8_t part, uint32_t version, uint32_t size, uint8_t *buf, uint32_t buf_sz) @@ -676,6 +817,7 @@ START_TEST (test_diffbase_version_reads) cleanup_flash(); } END_TEST +#endif Suite *wolfboot_suite(void) @@ -684,6 +826,25 @@ Suite *wolfboot_suite(void) Suite *s = suite_create("wolfboot"); /* Test cases */ +#ifdef UNIT_TEST_SELF_UPDATE_ONLY +#ifdef RAM_CODE + TCase *self_update_sameversion = tcase_create("Self update same version erased"); + TCase *self_update_oldversion = tcase_create("Self update older version erased"); + TCase *self_update_invalid_integrity = tcase_create("Self update invalid integrity denied"); + TCase *self_update_success = tcase_create("Self update success"); + + tcase_add_test(self_update_sameversion, test_self_update_sameversion_erased); + tcase_add_test(self_update_oldversion, test_self_update_oldversion_erased); + tcase_add_test(self_update_invalid_integrity, test_self_update_newversion_invalid_integrity_denied); + tcase_add_test(self_update_success, test_self_update_newversion_copies_and_reboots); + + suite_add_tcase(s, self_update_sameversion); + suite_add_tcase(s, self_update_oldversion); + suite_add_tcase(s, self_update_invalid_integrity); + suite_add_tcase(s, self_update_success); +#endif + return s; +#else #ifdef UNIT_TEST_FALLBACK_ONLY TCase *fallback_verify = tcase_create("Fallback verify"); #else @@ -712,6 +873,12 @@ Suite *wolfboot_suite(void) TCase *swap_resume = tcase_create("Swap resume noop"); TCase *diffbase_version = tcase_create("Diffbase version lookup"); TCase *boot_success = tcase_create("Boot success state"); +#ifdef RAM_CODE + TCase *self_update_sameversion = tcase_create("Self update same version erased"); + TCase *self_update_oldversion = tcase_create("Self update older version erased"); + TCase *self_update_invalid_integrity = tcase_create("Self update invalid integrity denied"); + TCase *self_update_success = tcase_create("Self update success"); +#endif #ifdef EXT_ENCRYPTED TCase *fallback_verify = tcase_create("Fallback verify"); #endif @@ -743,6 +910,12 @@ Suite *wolfboot_suite(void) tcase_add_test(swap_resume, test_swap_resume_noop); tcase_add_test(diffbase_version, test_diffbase_version_reads); tcase_add_test(boot_success, test_boot_success_sets_state); +#ifdef RAM_CODE + tcase_add_test(self_update_sameversion, test_self_update_sameversion_erased); + tcase_add_test(self_update_oldversion, test_self_update_oldversion_erased); + tcase_add_test(self_update_invalid_integrity, test_self_update_newversion_invalid_integrity_denied); + tcase_add_test(self_update_success, test_self_update_newversion_copies_and_reboots); +#endif #ifdef EXT_ENCRYPTED tcase_add_test(fallback_verify, test_fallback_image_verification_rejects_corruption); #endif @@ -765,6 +938,12 @@ Suite *wolfboot_suite(void) suite_add_tcase(s, swap_resume); suite_add_tcase(s, diffbase_version); suite_add_tcase(s, boot_success); +#ifdef RAM_CODE + suite_add_tcase(s, self_update_sameversion); + suite_add_tcase(s, self_update_oldversion); + suite_add_tcase(s, self_update_invalid_integrity); + suite_add_tcase(s, self_update_success); +#endif #ifdef EXT_ENCRYPTED suite_add_tcase(s, fallback_verify); #endif @@ -773,6 +952,7 @@ Suite *wolfboot_suite(void) return s; +#endif } From 0bcc49ab3c5dd9e7fd0d9613a6a550dcc368ed00 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 16:30:14 +0200 Subject: [PATCH 17/75] Strengthen same-version RAM update test F/2262 --- tools/unit-tests/unit-update-ram.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/unit-tests/unit-update-ram.c b/tools/unit-tests/unit-update-ram.c index 4550f59419..6399d01c73 100644 --- a/tools/unit-tests/unit-update-ram.c +++ b/tools/unit-tests/unit-update-ram.c @@ -341,6 +341,7 @@ START_TEST (test_forward_update_sameversion_denied) { wolfBoot_start(); ck_assert(wolfBoot_staged_ok); ck_assert(get_version_ramloaded() == 1); + ck_assert_uint_eq(*(uint32_t *)(wolfboot_ram + 4), TEST_SIZE_SMALL); ck_assert(*(uint32_t *)(WOLFBOOT_PARTITION_BOOT_ADDRESS + 4) == TEST_SIZE_SMALL); cleanup_flash(); } From 153ad2b0af186bdab85aec43eed561f9da7d77e8 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 16:35:20 +0200 Subject: [PATCH 18/75] Fix sign header TLV overflow sizing F/2266 --- tools/keytools/sign.c | 139 ++++++++++++++++-- tools/unit-tests/unit-sign-encrypted-output.c | 54 +++++++ 2 files changed, 184 insertions(+), 9 deletions(-) diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index b6416f3e6a..f8502491cf 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -221,9 +221,23 @@ static void header_append_u16(uint8_t* header, uint32_t* idx, uint16_t tmp16) memcpy(&header[*idx], &tmp16, sizeof(tmp16)); *idx += sizeof(tmp16); } + +static uint32_t header_append_limit(void); + static void header_append_tag(uint8_t* header, uint32_t* idx, uint16_t tag, - uint16_t len, void* data) + uint16_t len, const void* data) { + const uint32_t append_sz = (uint32_t)(sizeof(tag) + sizeof(len)) + len; + const uint32_t header_sz = header_append_limit(); + + if ((*idx > header_sz) || (append_sz > (header_sz - *idx))) { + fprintf(stderr, + "Header overflow while appending tag 0x%04x " + "(offset=%u, size=%u, header=%u)\n", + tag, *idx, append_sz, header_sz); + exit(1); + } + header_append_u16(header, idx, tag); header_append_u16(header, idx, len); memcpy(&header[*idx], data, len); @@ -296,6 +310,11 @@ static struct cmd_options CMD = { .hybrid = 0 }; +static uint32_t header_append_limit(void) +{ + return CMD.header_sz; +} + static void zero_and_free(uint8_t *buf, uint32_t len) { volatile uint8_t *p; @@ -1124,6 +1143,114 @@ static int sign_digest(int sign, int hash_algo, #define ALIGN_8(x) while ((x % 8) != 4) { x++; } #define ALIGN_4(x) while ((x % 4) != 0) { x++; } +static void header_size_align_8(uint32_t *idx) +{ + while ((*idx % 8U) != 4U) { + (*idx)++; + } +} + +static void header_size_align_4(uint32_t *idx) +{ + while ((*idx % 4U) != 0U) { + (*idx)++; + } +} + +static void header_size_append_tag(uint32_t *idx, uint32_t len) +{ + *idx += 4U + len; +} + +static uint32_t header_digest_size(int hash_algo) +{ + switch (hash_algo) { + case HASH_SHA256: + return HDR_SHA256_LEN; + case HASH_SHA384: + return HDR_SHA384_LEN; + case HASH_SHA3: + return HDR_SHA3_384_LEN; + default: + return 0; + } +} + +static uint32_t header_required_size(int is_diff, uint32_t cert_chain_sz, + uint32_t secondary_key_sz) +{ + uint32_t idx = 0; + uint32_t digest_sz = header_digest_size(CMD.hash_algo); + uint32_t i; + + idx += 2U * sizeof(uint32_t); + header_size_append_tag(&idx, HDR_VERSION_LEN); + header_size_align_8(&idx); + + if (!CMD.no_ts) { + header_size_append_tag(&idx, HDR_TIMESTAMP_LEN); + } + + header_size_append_tag(&idx, HDR_IMG_TYPE_LEN); + + if (is_diff) { + header_size_align_4(&idx); + header_size_append_tag(&idx, 4); + header_size_append_tag(&idx, 4); + header_size_align_4(&idx); + header_size_append_tag(&idx, 4); + header_size_append_tag(&idx, 4); + + if (!CMD.no_base_sha && digest_sz > 0U) { + header_size_align_8(&idx); + header_size_append_tag(&idx, digest_sz); + } + } + + for (i = 0; i < CMD.custom_tlvs; i++) { + header_size_align_8(&idx); + header_size_append_tag(&idx, CMD.custom_tlv[i].len); + } + + if (cert_chain_sz > 0U) { + header_size_align_8(&idx); + header_size_append_tag(&idx, cert_chain_sz); + } + + if (digest_sz > 0U) { + header_size_align_8(&idx); + header_size_append_tag(&idx, digest_sz); + header_size_align_8(&idx); + + if (CMD.hybrid && secondary_key_sz > 0U) { + header_size_append_tag(&idx, 2); + header_size_align_8(&idx); + header_size_append_tag(&idx, digest_sz); + header_size_align_8(&idx); + } + + header_size_append_tag(&idx, digest_sz); + } + + if (CMD.sign != NO_SIGN) { + header_size_align_8(&idx); + header_size_append_tag(&idx, CMD.signature_sz); + + if (CMD.hybrid) { + header_size_align_8(&idx); + header_size_append_tag(&idx, CMD.secondary_signature_sz); + } + + if (CMD.policy_sign) { + header_size_align_8(&idx); + header_size_append_tag(&idx, + CMD.policy_sz + (uint32_t)sizeof(uint32_t)); + } + } + + return idx; +} + static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, const char *image_file, const char *outfile, uint32_t delta_base_version, uint32_t patch_len, uint32_t patch_inv_off, @@ -1157,14 +1284,8 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, /* Get the file size */ if (stat(CMD.cert_chain_file, &file_stat) == 0) { - /* 2 bytes for tag + 2 bytes for length field */ - const uint32_t tag_len_size = 4; - /* Maximum alignment padding that might be needed */ - const uint32_t max_alignment = 8; - /* Required space = tag(2) + length(2) + data + potential alignment - * * padding */ - const uint32_t required_space = - tag_len_size + file_stat.st_size + max_alignment; + const uint32_t required_space = header_required_size(is_diff, + (uint32_t)file_stat.st_size, secondary_key_sz); /* If the current header size is too small, increase it */ if (CMD.header_sz < required_space) { diff --git a/tools/unit-tests/unit-sign-encrypted-output.c b/tools/unit-tests/unit-sign-encrypted-output.c index 4fb1777956..25360383ed 100644 --- a/tools/unit-tests/unit-sign-encrypted-output.c +++ b/tools/unit-tests/unit-sign-encrypted-output.c @@ -220,6 +220,58 @@ START_TEST(test_make_header_ex_fails_when_image_reopen_fails) } END_TEST +START_TEST(test_make_header_ex_grows_header_for_cert_chain_and_digest_tlvs) +{ + char tempdir[] = "/tmp/wolfboot-sign-XXXXXX"; + char image_path[PATH_MAX]; + char output_path[PATH_MAX]; + char cert_chain_path[PATH_MAX]; + uint8_t image_buf[] = { 0x01, 0x02, 0x03, 0x04 }; + uint8_t cert_chain_buf[200]; + uint8_t pubkey[] = { 0xA5 }; + struct stat st; + int ret; + + ck_assert_ptr_nonnull(mkdtemp(tempdir)); + + snprintf(image_path, sizeof(image_path), "%s/image.bin", tempdir); + snprintf(output_path, sizeof(output_path), "%s/output.bin", tempdir); + snprintf(cert_chain_path, sizeof(cert_chain_path), "%s/cert-chain.bin", + tempdir); + + memset(cert_chain_buf, 0xC3, sizeof(cert_chain_buf)); + ck_assert_int_eq(write_file(image_path, image_buf, sizeof(image_buf)), 0); + ck_assert_int_eq(write_file(cert_chain_path, cert_chain_buf, + sizeof(cert_chain_buf)), 0); + + memset(&CMD, 0, sizeof(CMD)); + CMD.sign = NO_SIGN; + CMD.hash_algo = HASH_SHA256; + CMD.partition_id = HDR_IMG_TYPE_APP; + CMD.header_sz = 256; + CMD.fw_version = "7"; + CMD.no_ts = 1; + CMD.cert_chain_file = cert_chain_path; + + reset_mocks(NULL, 0); + ret = make_header_ex(0, pubkey, sizeof(pubkey), image_path, output_path, + 0, 0, 0, 0, NULL, 0, NULL, 0); + + ck_assert_int_eq(ret, 0); + ck_assert_uint_eq(CMD.header_sz, 512); + ck_assert_int_eq(stat(output_path, &st), 0); + ck_assert_uint_eq((uint32_t)st.st_size, CMD.header_sz + sizeof(image_buf)); + ck_assert_int_eq(mock_null_fwrite_calls, 0); + ck_assert_int_eq(mock_null_fread_calls, 0); + ck_assert_int_eq(mock_null_fclose_calls, 0); + + unlink(output_path); + unlink(cert_chain_path); + unlink(image_path); + rmdir(tempdir); +} +END_TEST + Suite *wolfboot_suite(void) { Suite *s = suite_create("sign-encrypted-output"); @@ -227,6 +279,8 @@ Suite *wolfboot_suite(void) tcase_add_test(tcase, test_make_header_ex_fails_when_encrypted_output_open_fails); tcase_add_test(tcase, test_make_header_ex_fails_when_image_reopen_fails); + tcase_add_test(tcase, + test_make_header_ex_grows_header_for_cert_chain_and_digest_tlvs); suite_add_tcase(s, tcase); return s; From a5ea3ff8297c46780c26efa0834d714a416a5b63 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 16:39:43 +0200 Subject: [PATCH 19/75] Reject oversized delta source offsets F/2267 --- src/delta.c | 5 +++++ tools/delta/bmdiff.c | 4 ++++ tools/keytools/sign.c | 8 ++++++++ tools/unit-tests/unit-delta.c | 27 +++++++++++++++++++++++++++ 4 files changed, 44 insertions(+) diff --git a/src/delta.c b/src/delta.c index c1f2e3be12..fd7674ee3e 100644 --- a/src/delta.c +++ b/src/delta.c @@ -40,6 +40,7 @@ struct BLOCK_HDR_PACKED block_hdr { }; #define BLOCK_HDR_SIZE (sizeof (struct block_hdr)) +#define BLOCK_OFF_MAX 0xFFFFFFU #if defined(EXT_ENCRYPTED) && defined(__WOLFBOOT) #include "image.h" @@ -300,6 +301,8 @@ int wb_diff(WB_DIFF_CTX *ctx, uint8_t *patch, uint32_t len) */ match_len = BLOCK_HDR_SIZE; blk_start = pa - ctx->src_a; + if (blk_start > BLOCK_OFF_MAX) + return -1; b_start = ctx->off_b; pa+= BLOCK_HDR_SIZE; ctx->off_b += BLOCK_HDR_SIZE; @@ -362,6 +365,8 @@ int wb_diff(WB_DIFF_CTX *ctx, uint8_t *patch, uint32_t len) */ match_len = BLOCK_HDR_SIZE; blk_start = pb - ctx->src_b; + if (blk_start > BLOCK_OFF_MAX) + return -1; pb+= BLOCK_HDR_SIZE; ctx->off_b += BLOCK_HDR_SIZE; while ((pb < pb_limit) && diff --git a/tools/delta/bmdiff.c b/tools/delta/bmdiff.c index 4ae336da5a..adeafb3d9b 100644 --- a/tools/delta/bmdiff.c +++ b/tools/delta/bmdiff.c @@ -97,6 +97,10 @@ int main(int argc, char *argv[]) exit(3); } len2 = st.st_size; + if (len2 > MAX_SRC_SIZE) { + printf("%s: file too large\n", argv[2]); + exit(3); + } buffer = mmap(NULL, len2, PROT_READ, MAP_SHARED, fd2, 0); if (buffer == (void *)(-1)) { perror("mmap"); diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index f8502491cf..93143a1436 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -2240,6 +2240,10 @@ static int base_diff(const char *f_base, uint8_t *pubkey, uint32_t pubkey_sz, in goto cleanup; } len2 = st.st_size; + if (len2 > MAX_SRC_SIZE) { + printf("%s: file too large\n", CMD.output_image_file); + goto cleanup; + } buffer = mmap(NULL, len2, PROT_READ, MAP_SHARED, fd2, 0); if (buffer == (void *)(-1)) { perror("mmap"); @@ -2275,6 +2279,10 @@ static int base_diff(const char *f_base, uint8_t *pubkey, uint32_t pubkey_sz, in fseek(f2, 0L, SEEK_END); len2 = ftell(f2); fseek(f2, 0L, SEEK_SET); + if (len2 > MAX_SRC_SIZE) { + printf("%s: file too large\n", CMD.output_image_file); + goto cleanup; + } buffer = malloc(len2); if (buffer == NULL) { fprintf(stderr, "Error malloc for buffer %d\n", len2); diff --git a/tools/unit-tests/unit-delta.c b/tools/unit-tests/unit-delta.c index d648e39841..8c240d3359 100644 --- a/tools/unit-tests/unit-delta.c +++ b/tools/unit-tests/unit-delta.c @@ -34,6 +34,7 @@ #define PATCH_SIZE 8192 #define DST_SIZE 4096 #define DIFF_SIZE 8192 +#define DELTA_OFFSET_LIMIT (1U << 24) START_TEST(test_wb_patch_init_invalid) @@ -238,6 +239,31 @@ START_TEST(test_wb_diff_preserves_main_loop_header_margin_for_escape) } END_TEST +START_TEST(test_wb_diff_rejects_match_offsets_beyond_24_bits) +{ + WB_DIFF_CTX diff_ctx; + uint8_t *src_a; + uint8_t src_b[BLOCK_HDR_SIZE + 1] = {0}; + uint8_t patch[DELTA_BLOCK_SIZE] = {0}; + size_t src_a_size = DELTA_OFFSET_LIMIT + BLOCK_HDR_SIZE; + int ret; + + src_a = calloc(1, src_a_size); + ck_assert_ptr_nonnull(src_a); + + memset(src_a + DELTA_OFFSET_LIMIT, 0x5a, BLOCK_HDR_SIZE); + memset(src_b, 0x5a, BLOCK_HDR_SIZE); + + ret = wb_diff_init(&diff_ctx, src_a, src_a_size, src_b, sizeof(src_b)); + ck_assert_int_eq(ret, 0); + + ret = wb_diff(&diff_ctx, patch, sizeof(patch)); + ck_assert_int_eq(ret, -1); + + free(src_a); +} +END_TEST + static void initialize_buffers(uint8_t *src_a, uint8_t *src_b, size_t size) { uint32_t pseudo_rand = 0; @@ -348,6 +374,7 @@ Suite *patch_diff_suite(void) tcase_add_test(tc_wolfboot_delta, test_wb_diff_self_match_extends_to_src_b_end); tcase_add_test(tc_wolfboot_delta, test_wb_diff_preserves_trailing_header_margin_for_escape); tcase_add_test(tc_wolfboot_delta, test_wb_diff_preserves_main_loop_header_margin_for_escape); + tcase_add_test(tc_wolfboot_delta, test_wb_diff_rejects_match_offsets_beyond_24_bits); tcase_add_test(tc_wolfboot_delta, test_wb_patch_and_diff); suite_add_tcase(s, tc_wolfboot_delta); From e22eade623cfe1df954ce334afd9dce2baba7fa2 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 16:44:45 +0200 Subject: [PATCH 20/75] fix memmove large-length backward copy F/1897 --- src/string.c | 6 +++--- tools/unit-tests/unit-string.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/string.c b/src/string.c index 7f65ebda18..880f0878ae 100644 --- a/src/string.c +++ b/src/string.c @@ -288,14 +288,14 @@ void RAMFUNCTION *memcpy(void *dst, const void *src, size_t n) !defined(__CCRX__) void *memmove(void *dst, const void *src, size_t n) { - int i; + size_t i; if (dst == src) return dst; if (src < dst) { const char *s = (const char *)src; char *d = (char *)dst; - for (i = n - 1; i >= 0; i--) { - d[i] = s[i]; + for (i = n; i > 0; i--) { + d[i - 1] = s[i - 1]; } return dst; } else { diff --git a/tools/unit-tests/unit-string.c b/tools/unit-tests/unit-string.c index 604a6eccfe..a91e8ff9bf 100644 --- a/tools/unit-tests/unit-string.c +++ b/tools/unit-tests/unit-string.c @@ -25,8 +25,12 @@ #define FAST_MEMCPY #include +#include #include #include +#ifdef __linux__ +#include +#endif #include "string.c" @@ -325,6 +329,31 @@ START_TEST(test_memcpy_memmove) } END_TEST +#if defined(__linux__) && (SIZE_MAX > INT_MAX) +START_TEST(test_memmove_large_overlap_length) +{ + size_t n = (size_t)INT_MAX + 2U; + size_t len = n + 1U; + unsigned char *region = mmap(NULL, len, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + ck_assert_ptr_ne(region, MAP_FAILED); + + region[0] = 0x11; + region[1] = 0x22; + region[n - 1] = 0x33; + region[n] = 0x44; + + memmove(region + 1, region, n); + + ck_assert_uint_eq(region[1], 0x11); + ck_assert_uint_eq(region[n], 0x33); + + ck_assert_int_eq(munmap(region, len), 0); +} +END_TEST +#endif + START_TEST(test_memcpy_aligned_buffers) { union { @@ -443,6 +472,9 @@ Suite *string_suite(void) tcase_add_test(tcase_misc, test_strcpy_strncpy_strcat_strncat); tcase_add_test(tcase_misc, test_strncmp); tcase_add_test(tcase_misc, test_memcpy_memmove); +#if defined(__linux__) && (SIZE_MAX > INT_MAX) + tcase_add_test(tcase_misc, test_memmove_large_overlap_length); +#endif tcase_add_test(tcase_misc, test_memcpy_aligned_buffers); tcase_add_test(tcase_misc, test_uart_writenum_basic); tcase_add_test(tcase_misc, test_uart_printf_formats); From 1528bc8d992aba3d856223eca412c19edd3ef064 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 16:46:26 +0200 Subject: [PATCH 21/75] Use constant-time TPM secret checks F/2248 --- include/tpm.h | 4 ++++ src/tpm.c | 2 +- src/update_flash.c | 3 ++- src/x86/ahci.c | 3 ++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/include/tpm.h b/include/tpm.h index fb788914c9..50b68adf41 100644 --- a/include/tpm.h +++ b/include/tpm.h @@ -79,6 +79,10 @@ int wolfBoot_load_pubkey(const uint8_t* pubkey_hint, WOLFTPM2_KEY* pubKey, TPM_ALG_ID* pAlg); #endif +#if defined(WOLFBOOT_TPM_KEYSTORE) || defined(WOLFBOOT_TPM_SEAL) +int wolfBoot_constant_compare(const uint8_t* a, const uint8_t* b, uint32_t len); +#endif + #ifdef WOLFBOOT_TPM_KEYSTORE int wolfBoot_check_rot(int key_slot, uint8_t* pubkey_hint); #endif diff --git a/src/tpm.c b/src/tpm.c index 682ab51c60..23b3e05dd3 100644 --- a/src/tpm.c +++ b/src/tpm.c @@ -44,7 +44,7 @@ WOLFTPM2_KEY wolftpm_srk; #endif #if defined(WOLFBOOT_TPM_SEAL) || defined(WOLFBOOT_TPM_KEYSTORE) -static int wolfBoot_constant_compare(const uint8_t* a, const uint8_t* b, +int wolfBoot_constant_compare(const uint8_t* a, const uint8_t* b, uint32_t len) { uint32_t i; diff --git a/src/update_flash.c b/src/update_flash.c index 5a6920cdbb..4b2c383c4e 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -1265,7 +1265,8 @@ int wolfBoot_unlock_disk(void) secretCheck, &secretCheckSz); if (ret == 0) { if (secretSz != secretCheckSz || - memcmp(secret, secretCheck, secretSz) != 0) + wolfBoot_constant_compare(secret, secretCheck, + (uint32_t)secretSz) != 0) { wolfBoot_printf("secret check mismatch!\n"); ret = -1; diff --git a/src/x86/ahci.c b/src/x86/ahci.c index ff5a7a42a2..0c691797cb 100644 --- a/src/x86/ahci.c +++ b/src/x86/ahci.c @@ -296,7 +296,8 @@ static int sata_create_and_seal_unlock_secret(const uint8_t *pubkey_hint, secret_check, &secret_check_sz); if (ret == 0) { if (*secret_size != secret_check_sz || - memcmp(secret, secret_check, secret_check_sz) != 0) + wolfBoot_constant_compare(secret, secret_check, + (uint32_t)secret_check_sz) != 0) { wolfBoot_printf("secret check mismatch!\n"); ret = -1; From dfc73ca7e18e2ae38dad77553bed0838b9f57f98 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 16:47:59 +0200 Subject: [PATCH 22/75] Use constant-time encryption key validation F/2249 --- src/libwolfboot.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/libwolfboot.c b/src/libwolfboot.c index ee479e2120..2d50bd7091 100644 --- a/src/libwolfboot.c +++ b/src/libwolfboot.c @@ -70,6 +70,20 @@ static uint8_t encrypt_iv_nonce[ENCRYPT_NONCE_SIZE] XALIGNED(4); static uint32_t encrypt_iv_offset = 0; static int fallback_iv_forced = 0; +static int encrypt_key_is_valid(const uint8_t *key, uint32_t len) +{ + uint8_t has_one = 0; + uint8_t has_zero = 0; + uint32_t i; + + for (i = 0; i < len; i++) { + has_one |= key[i]; + has_zero |= (uint8_t)~key[i]; + } + + return (has_one != 0) && (has_zero != 0); +} + #define FALLBACK_IV_OFFSET 0x00100000U #if !defined(XMEMSET) #include @@ -1692,8 +1706,6 @@ int RAMFUNCTION chacha_init(void) const uint8_t* stored_nonce; uint8_t *key; #endif - uint8_t ff[ENCRYPT_KEY_SIZE]; - #ifdef CUSTOM_ENCRYPT_KEY int ret = wolfBoot_get_encrypt_key(key, stored_nonce); if (ret != 0) @@ -1713,12 +1725,7 @@ int RAMFUNCTION chacha_init(void) XMEMSET(&chacha, 0, sizeof(chacha)); - /* Check against 'all 0xff' or 'all zero' cases */ - XMEMSET(ff, 0xFF, ENCRYPT_KEY_SIZE); - if (XMEMCMP(key, ff, ENCRYPT_KEY_SIZE) == 0) - return -1; - XMEMSET(ff, 0x00, ENCRYPT_KEY_SIZE); - if (XMEMCMP(key, ff, ENCRYPT_KEY_SIZE) == 0) + if (!encrypt_key_is_valid(key, ENCRYPT_KEY_SIZE)) return -1; XMEMCPY(encrypt_iv_nonce, stored_nonce, ENCRYPT_NONCE_SIZE); @@ -1751,8 +1758,6 @@ int aes_init(void) uint8_t *stored_nonce; uint8_t *key; #endif - uint8_t ff[ENCRYPT_KEY_SIZE]; - #ifdef WOLFBOOT_RENESAS_TSIP int ret; wrap_enc_key_t* enc_key; @@ -1781,12 +1786,7 @@ int aes_init(void) wc_AesInit(&aes_enc, NULL, devId); wc_AesInit(&aes_dec, NULL, devId); - /* Check against 'all 0xff' or 'all zero' cases */ - XMEMSET(ff, 0xFF, ENCRYPT_KEY_SIZE); - if (XMEMCMP(key, ff, ENCRYPT_KEY_SIZE) == 0) - return -1; - XMEMSET(ff, 0x00, ENCRYPT_KEY_SIZE); - if (XMEMCMP(key, ff, ENCRYPT_KEY_SIZE) == 0) + if (!encrypt_key_is_valid(key, ENCRYPT_KEY_SIZE)) return -1; #ifdef WOLFBOOT_RENESAS_TSIP From 8e2f8b34da4a08997ca7b1588db7ef85e06e5f89 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 16:50:15 +0200 Subject: [PATCH 23/75] Use fixed-length erased-key check F/2252 --- src/libwolfboot.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/libwolfboot.c b/src/libwolfboot.c index 2d50bd7091..30f864a76a 100644 --- a/src/libwolfboot.c +++ b/src/libwolfboot.c @@ -84,6 +84,17 @@ static int encrypt_key_is_valid(const uint8_t *key, uint32_t len) return (has_one != 0) && (has_zero != 0); } +static int encrypt_key_is_erased(const uint8_t *key, uint32_t len) +{ + uint8_t diff = 0; + uint32_t i; + + for (i = 0; i < len; i++) + diff |= key[i] ^ FLASH_BYTE_ERASED; + + return diff == 0; +} + #define FALLBACK_IV_OFFSET 0x00100000U #if !defined(XMEMSET) #include @@ -1682,7 +1693,7 @@ int RAMFUNCTION wolfBoot_erase_encrypt_key(void) mem -= (sel_sec * WOLFBOOT_SECTOR_SIZE); #endif XMEMSET(ff, FLASH_BYTE_ERASED, ENCRYPT_KEY_SIZE + ENCRYPT_NONCE_SIZE); - if (XMEMCMP(mem, ff, ENCRYPT_KEY_SIZE + ENCRYPT_NONCE_SIZE) != 0) + if (!encrypt_key_is_erased(mem, ENCRYPT_KEY_SIZE + ENCRYPT_NONCE_SIZE)) ret = hal_set_key(ff, ff + ENCRYPT_KEY_SIZE); return ret; #endif From f5d50d4257759e74d2e4e8cdac85e7f320ceac60 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 16:52:25 +0200 Subject: [PATCH 24/75] enforce skip-verify prerequisites F/2256 --- include/wolfboot/wolfboot.h | 9 +++++++++ options.mk | 6 ++++++ tools/unit-tests/unit-update-disk.c | 2 ++ 3 files changed, 17 insertions(+) diff --git a/include/wolfboot/wolfboot.h b/include/wolfboot/wolfboot.h index a6e79d708d..73f45e2ff2 100644 --- a/include/wolfboot/wolfboot.h +++ b/include/wolfboot/wolfboot.h @@ -185,6 +185,15 @@ extern "C" { #endif #endif /* WOLFBOOT_SELF_HEADER */ +#if defined(WOLFBOOT_SKIP_BOOT_VERIFY) && !defined(WOLFBOOT_SELF_HEADER) +#error "WOLFBOOT_SKIP_BOOT_VERIFY requires WOLFBOOT_SELF_HEADER" +#endif + +#if defined(WOLFBOOT_SKIP_BOOT_VERIFY) && \ + !defined(WOLFBOOT_SELF_UPDATE_MONOLITHIC) +#error "WOLFBOOT_SKIP_BOOT_VERIFY requires WOLFBOOT_SELF_UPDATE_MONOLITHIC" +#endif + #ifdef BIG_ENDIAN_ORDER # define WOLFBOOT_MAGIC 0x574F4C46 /* WOLF */ # define WOLFBOOT_MAGIC_TRAIL 0x424F4F54 /* BOOT */ diff --git a/options.mk b/options.mk index ff8df2c4ad..517c758874 100644 --- a/options.mk +++ b/options.mk @@ -710,6 +710,12 @@ ifeq ($(ALLOW_DOWNGRADE),1) endif ifeq ($(WOLFBOOT_SKIP_BOOT_VERIFY),1) + ifneq ($(WOLFBOOT_SELF_HEADER),1) + $(error WOLFBOOT_SKIP_BOOT_VERIFY=1 requires WOLFBOOT_SELF_HEADER=1) + endif + ifneq ($(SELF_UPDATE_MONOLITHIC),1) + $(error WOLFBOOT_SKIP_BOOT_VERIFY=1 requires SELF_UPDATE_MONOLITHIC=1) + endif CFLAGS+=-D"WOLFBOOT_SKIP_BOOT_VERIFY" endif diff --git a/tools/unit-tests/unit-update-disk.c b/tools/unit-tests/unit-update-disk.c index 6c051be11f..43633f4dc0 100644 --- a/tools/unit-tests/unit-update-disk.c +++ b/tools/unit-tests/unit-update-disk.c @@ -1,5 +1,7 @@ #define WOLFBOOT_UPDATE_DISK #define WOLFBOOT_SKIP_BOOT_VERIFY +#define WOLFBOOT_SELF_UPDATE_MONOLITHIC +#define WOLFBOOT_SELF_HEADER #define EXT_ENCRYPTED #define ENCRYPT_WITH_CHACHA #define HAVE_CHACHA From 0c4be70a29cf9e7bd9e96fc2eea3ca921300c7e5 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 16:56:53 +0200 Subject: [PATCH 25/75] Add equal-version update-disk regression test F/2264 --- tools/unit-tests/Makefile | 2 ++ tools/unit-tests/unit-update-disk.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 1e3c99af9f..5c1669abd0 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -106,6 +106,8 @@ unit-update-flash-self-update:CFLAGS+=-DMOCK_PARTITIONS -DWOLFBOOT_NO_SIGN -DUNI unit-update-ram:CFLAGS+=-DMOCK_PARTITIONS -DWOLFBOOT_NO_SIGN -DUNIT_TEST_AUTH \ -DWOLFBOOT_HASH_SHA256 -DPRINTF_ENABLED -DEXT_FLASH -DPART_UPDATE_EXT \ -DPART_SWAP_EXT -DPART_BOOT_EXT -DWOLFBOOT_DUALBOOT -DNO_XIP +unit-update-disk:CFLAGS+=-DMOCK_PARTITIONS -DPRINTF_ENABLED \ + -DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE unit-string:CFLAGS+=-fno-builtin diff --git a/tools/unit-tests/unit-update-disk.c b/tools/unit-tests/unit-update-disk.c index 43633f4dc0..2e7831ded3 100644 --- a/tools/unit-tests/unit-update-disk.c +++ b/tools/unit-tests/unit-update-disk.c @@ -8,12 +8,14 @@ #define IMAGE_HEADER_SIZE 256 #define BOOT_PART_A 0 #define BOOT_PART_B 1 +#define MOCK_ADDRESS_BOOT 0xCD000000 #include #include #include #include +#include "hal.h" #include "target.h" #include "wolfboot/wolfboot.h" #include "image.h" @@ -190,6 +192,13 @@ void do_boot(const uint32_t *address) mock_boot_address = address; } +int hal_flash_protect(haladdr_t address, int len) +{ + (void)address; + (void)len; + return 0; +} + #include "update_disk.c" START_TEST(test_update_disk_zeroizes_key_material_on_panic) @@ -232,6 +241,24 @@ START_TEST(test_update_disk_zeroizes_key_material_before_boot) } END_TEST +START_TEST(test_update_disk_prefers_primary_partition_when_versions_equal) +{ + reset_mocks(); + build_image(part_a_image, 7, 0xA1); + build_image(part_b_image, 7, 0xB2); + + wolfBoot_start(); + + ck_assert_int_eq(wolfBoot_panicked, 0); + ck_assert_int_eq(mock_do_boot_called, 1); + ck_assert_ptr_eq(mock_boot_address, (const uint32_t *)WOLFBOOT_LOAD_ADDRESS); + ck_assert_int_eq(memcmp(load_buffer, part_a_image + IMAGE_HEADER_SIZE, + TEST_PAYLOAD_SIZE), 0); + ck_assert_int_ne(memcmp(load_buffer, part_b_image + IMAGE_HEADER_SIZE, + TEST_PAYLOAD_SIZE), 0); +} +END_TEST + START_TEST(test_get_decrypted_blob_version_rejects_truncated_version_tlv) { uint8_t hdr[IMAGE_HEADER_SIZE + 2]; @@ -268,6 +295,7 @@ Suite *wolfboot_suite(void) tcase_add_test(tc, test_update_disk_zeroizes_key_material_on_panic); 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); suite_add_tcase(s, tc); From e4e96ad2fadeb246a70f6d90bef461de829ab338 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 17:01:25 +0200 Subject: [PATCH 26/75] Reject valid zero-size delta images F/2268 --- src/update_flash.c | 9 +++- tools/unit-tests/Makefile | 10 +++- tools/unit-tests/unit-update-flash.c | 73 ++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 3 deletions(-) diff --git a/src/update_flash.c b/src/update_flash.c index 4b2c383c4e..81a33f2e95 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -562,8 +562,13 @@ static int wolfBoot_delta_update(struct wolfBoot_image *boot, uint8_t *base_hash; if (boot->fw_size == 0) { - /* Resume after powerfail can leave boot header erased; bound by partition size. */ - boot->fw_size = WOLFBOOT_PARTITION_SIZE - IMAGE_HEADER_SIZE; + if ((boot->hdr != NULL) && + (*((uint32_t *)boot->hdr) != WOLFBOOT_MAGIC)) { + /* Resume after powerfail can leave the boot header erased. */ + boot->fw_size = WOLFBOOT_PARTITION_SIZE - IMAGE_HEADER_SIZE; + } else { + return -1; + } } /* Use biggest size for the swap */ diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 5c1669abd0..54cd3f0813 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -45,7 +45,7 @@ endif TESTS:=unit-parser unit-fdt unit-extflash unit-string unit-spi-flash unit-aes128 \ unit-aes256 unit-chacha20 unit-pci unit-mock-state unit-sectorflags \ unit-image unit-image-rsa unit-nvm unit-nvm-flagshome unit-enc-nvm \ - unit-enc-nvm-flagshome unit-delta unit-update-flash \ + unit-enc-nvm-flagshome unit-delta unit-update-flash unit-update-flash-delta \ unit-update-flash-self-update \ unit-update-flash-enc unit-update-ram unit-pkcs11_store unit-psa_store unit-disk \ unit-update-disk unit-multiboot unit-boot-x86-fsp unit-qspi-flash unit-tpm-rsa-exp \ @@ -98,6 +98,10 @@ unit-psa_store:CFLAGS+=-I$(WOLFBOOT_LIB_WOLFPSA) -DMOCK_PARTITIONS -DMOCK_KEYVAU unit-update-flash:CFLAGS+=-DMOCK_PARTITIONS -DWOLFBOOT_NO_SIGN -DUNIT_TEST_AUTH \ -DWOLFBOOT_HASH_SHA256 -DPRINTF_ENABLED -DEXT_FLASH -DPART_UPDATE_EXT -DPART_SWAP_EXT \ -DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE +unit-update-flash-delta: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=512 -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 \ @@ -267,6 +271,10 @@ unit-delta: ../../include/target.h unit-delta.c unit-update-flash: ../../include/target.h unit-update-flash.c gcc -o $@ unit-update-flash.c ../../src/image.c $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c $(CFLAGS) $(LDFLAGS) +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-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-update-flash.c b/tools/unit-tests/unit-update-flash.c index 11799a3ffe..23d449ba75 100644 --- a/tools/unit-tests/unit-update-flash.c +++ b/tools/unit-tests/unit-update-flash.c @@ -672,6 +672,22 @@ START_TEST (test_update_toolarge) { cleanup_flash(); } +START_TEST (test_zero_size_update_rejected) +{ + int ret; + + reset_mock_stats(); + prepare_flash(); + add_payload(PART_BOOT, 1, 0); + add_payload(PART_UPDATE, 2, 0); + + ret = wolfBoot_update(1); + ck_assert_int_eq(ret, -1); + + cleanup_flash(); +} +END_TEST + START_TEST (test_invalid_sha) { uint8_t bad_digest[SHA256_DIGEST_SIZE]; reset_mock_stats(); @@ -817,6 +833,50 @@ START_TEST (test_diffbase_version_reads) cleanup_flash(); } END_TEST + +#ifdef DELTA_UPDATES +START_TEST (test_delta_zero_size_valid_header_rejected_without_recovery_heuristic) +{ + struct wolfBoot_image boot, update, swap; + int ret; + + reset_mock_stats(); + prepare_flash(); + add_payload(PART_BOOT, 1, 0); + + ck_assert_int_eq(wolfBoot_open_image(&boot, PART_BOOT), 0); + memset(&update, 0, sizeof(update)); + memset(&swap, 0, sizeof(swap)); + + ret = wolfBoot_delta_update(&boot, &update, &swap, 0, 0); + ck_assert_int_eq(ret, -1); + ck_assert_uint_eq(boot.fw_size, 0); + + cleanup_flash(); +} +END_TEST + +START_TEST (test_delta_zero_size_erased_header_uses_recovery_heuristic) +{ + struct wolfBoot_image boot, update, swap; + int ret; + + reset_mock_stats(); + prepare_flash(); + + ck_assert_int_eq(wolfBoot_open_image(&boot, PART_BOOT), -1); + memset(&update, 0, sizeof(update)); + memset(&swap, 0, sizeof(swap)); + + ret = wolfBoot_delta_update(&boot, &update, &swap, 0, 0); + ck_assert_int_eq(ret, -1); + ck_assert_uint_eq(boot.fw_size, + WOLFBOOT_PARTITION_SIZE - IMAGE_HEADER_SIZE); + + cleanup_flash(); +} +END_TEST +#endif #endif @@ -865,6 +925,7 @@ Suite *wolfboot_suite(void) TCase *invalid_update_auth_type = tcase_create("Invalid update auth type"); TCase *update_toolarge = tcase_create("Update too large"); + TCase *zero_size_update = tcase_create("Zero size update"); TCase *invalid_sha = tcase_create("Invalid SHA digest"); TCase *emergency_rollback = tcase_create("Emergency rollback"); TCase *emergency_rollback_failure_due_to_bad_update = tcase_create("Emergency rollback failure due to bad update"); @@ -873,6 +934,9 @@ Suite *wolfboot_suite(void) TCase *swap_resume = tcase_create("Swap resume noop"); TCase *diffbase_version = tcase_create("Diffbase version lookup"); TCase *boot_success = tcase_create("Boot success state"); +#ifdef DELTA_UPDATES + TCase *delta_zero_size = tcase_create("Delta zero size"); +#endif #ifdef RAM_CODE TCase *self_update_sameversion = tcase_create("Self update same version erased"); TCase *self_update_oldversion = tcase_create("Self update older version erased"); @@ -902,6 +966,7 @@ Suite *wolfboot_suite(void) tcase_add_test(invalid_update_type, test_invalid_update_type); tcase_add_test(invalid_update_auth_type, test_invalid_update_auth_type); tcase_add_test(update_toolarge, test_update_toolarge); + tcase_add_test(zero_size_update, test_zero_size_update_rejected); tcase_add_test(invalid_sha, test_invalid_sha); tcase_add_test(emergency_rollback, test_emergency_rollback); tcase_add_test(emergency_rollback_failure_due_to_bad_update, test_emergency_rollback_failure_due_to_bad_update); @@ -910,6 +975,10 @@ Suite *wolfboot_suite(void) tcase_add_test(swap_resume, test_swap_resume_noop); tcase_add_test(diffbase_version, test_diffbase_version_reads); tcase_add_test(boot_success, test_boot_success_sets_state); +#ifdef DELTA_UPDATES + tcase_add_test(delta_zero_size, test_delta_zero_size_valid_header_rejected_without_recovery_heuristic); + tcase_add_test(delta_zero_size, test_delta_zero_size_erased_header_uses_recovery_heuristic); +#endif #ifdef RAM_CODE tcase_add_test(self_update_sameversion, test_self_update_sameversion_erased); tcase_add_test(self_update_oldversion, test_self_update_oldversion_erased); @@ -930,6 +999,7 @@ Suite *wolfboot_suite(void) suite_add_tcase(s, invalid_update_type); suite_add_tcase(s, invalid_update_auth_type); suite_add_tcase(s, update_toolarge); + suite_add_tcase(s, zero_size_update); suite_add_tcase(s, invalid_sha); suite_add_tcase(s, emergency_rollback); suite_add_tcase(s, emergency_rollback_failure_due_to_bad_update); @@ -938,6 +1008,9 @@ Suite *wolfboot_suite(void) suite_add_tcase(s, swap_resume); suite_add_tcase(s, diffbase_version); suite_add_tcase(s, boot_success); +#ifdef DELTA_UPDATES + suite_add_tcase(s, delta_zero_size); +#endif #ifdef RAM_CODE suite_add_tcase(s, self_update_sameversion); suite_add_tcase(s, self_update_oldversion); From 35142e6945b3e7c73e1f646a076a60a56e2b279d Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 17:03:42 +0200 Subject: [PATCH 27/75] Fix total size type in update flash F/2269 --- src/update_flash.c | 2 +- tools/unit-tests/unit-update-flash.c | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/update_flash.c b/src/update_flash.c index 81a33f2e95..5c79488698 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -786,7 +786,7 @@ static int wolfBoot_delta_update(struct wolfBoot_image *boot, #ifdef __CCRX__ #pragma section FRAM #endif -static int wolfBoot_get_total_size(struct wolfBoot_image* boot, +static uint32_t wolfBoot_get_total_size(struct wolfBoot_image* boot, struct wolfBoot_image* update) { uint32_t total_size = 0; diff --git a/tools/unit-tests/unit-update-flash.c b/tools/unit-tests/unit-update-flash.c index 23d449ba75..b9f3547c1d 100644 --- a/tools/unit-tests/unit-update-flash.c +++ b/tools/unit-tests/unit-update-flash.c @@ -34,6 +34,7 @@ #include #include +#include #include "user_settings.h" #include "wolfboot/wolfboot.h" #include "libwolfboot.c" @@ -834,6 +835,25 @@ START_TEST (test_diffbase_version_reads) } END_TEST +START_TEST (test_get_total_size_preserves_uint32_range) +{ + struct wolfBoot_image boot; + struct wolfBoot_image update; + uint32_t total_size; + + memset(&boot, 0, sizeof(boot)); + memset(&update, 0, sizeof(update)); + + boot.fw_size = (uint32_t)INT_MAX - IMAGE_HEADER_SIZE + 1u; + update.fw_size = boot.fw_size + 7u; + + total_size = wolfBoot_get_total_size(&boot, &update); + + ck_assert_uint_eq(total_size, update.fw_size + IMAGE_HEADER_SIZE); + ck_assert(total_size > (uint32_t)INT_MAX); +} +END_TEST + #ifdef DELTA_UPDATES START_TEST (test_delta_zero_size_valid_header_rejected_without_recovery_heuristic) { @@ -933,6 +953,7 @@ Suite *wolfboot_suite(void) TCase *empty_boot_but_update_sha_corrupted_denied = tcase_create("Empty boot partition but update SHA corrupted"); TCase *swap_resume = tcase_create("Swap resume noop"); TCase *diffbase_version = tcase_create("Diffbase version lookup"); + TCase *get_total_size = tcase_create("Total size range"); TCase *boot_success = tcase_create("Boot success state"); #ifdef DELTA_UPDATES TCase *delta_zero_size = tcase_create("Delta zero size"); @@ -974,6 +995,7 @@ Suite *wolfboot_suite(void) tcase_add_test(empty_boot_but_update_sha_corrupted_denied, test_empty_boot_but_update_sha_corrupted_denied); tcase_add_test(swap_resume, test_swap_resume_noop); tcase_add_test(diffbase_version, test_diffbase_version_reads); + tcase_add_test(get_total_size, test_get_total_size_preserves_uint32_range); tcase_add_test(boot_success, test_boot_success_sets_state); #ifdef DELTA_UPDATES tcase_add_test(delta_zero_size, test_delta_zero_size_valid_header_rejected_without_recovery_heuristic); @@ -1007,6 +1029,7 @@ Suite *wolfboot_suite(void) suite_add_tcase(s, empty_boot_but_update_sha_corrupted_denied); suite_add_tcase(s, swap_resume); suite_add_tcase(s, diffbase_version); + suite_add_tcase(s, get_total_size); suite_add_tcase(s, boot_success); #ifdef DELTA_UPDATES suite_add_tcase(s, delta_zero_size); From 04cc957d6f96441c2b93e2b3fecd91496587d8f2 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 17:04:37 +0200 Subject: [PATCH 28/75] zeroize update key material F/1888 --- src/update_flash.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/update_flash.c b/src/update_flash.c index 5c79488698..434cf1e353 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -1202,6 +1202,8 @@ static int RAMFUNCTION wolfBoot_update(int fallback_allowed) /* Save the encryption key after swapping */ #ifdef EXT_ENCRYPTED wolfBoot_set_encrypt_key(key, nonce); + wolfBoot_zeroize(key, sizeof(key)); + wolfBoot_zeroize(nonce, sizeof(nonce)); #endif #endif /* DISABLE_BACKUP */ #ifdef EXT_ENCRYPTED From 2f75363a19234a4db7f774456cb33495c234008e Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 17:05:56 +0200 Subject: [PATCH 29/75] zero custom encrypt stack buffers F/1889 --- src/libwolfboot.c | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/libwolfboot.c b/src/libwolfboot.c index 30f864a76a..33008fff82 100644 --- a/src/libwolfboot.c +++ b/src/libwolfboot.c @@ -1710,6 +1710,7 @@ ChaCha chacha; int RAMFUNCTION chacha_init(void) { + int ret = 0; #ifdef CUSTOM_ENCRYPT_KEY uint8_t stored_nonce[ENCRYPT_NONCE_SIZE]; uint8_t key[ENCRYPT_KEY_SIZE]; @@ -1718,9 +1719,9 @@ int RAMFUNCTION chacha_init(void) uint8_t *key; #endif #ifdef CUSTOM_ENCRYPT_KEY - int ret = wolfBoot_get_encrypt_key(key, stored_nonce); + ret = wolfBoot_get_encrypt_key(key, stored_nonce); if (ret != 0) - return ret; + goto exit; #else #if defined(MMU) || defined(UNIT_TEST) key = ENCRYPT_KEY; @@ -1736,14 +1737,21 @@ int RAMFUNCTION chacha_init(void) XMEMSET(&chacha, 0, sizeof(chacha)); - if (!encrypt_key_is_valid(key, ENCRYPT_KEY_SIZE)) - return -1; + if (!encrypt_key_is_valid(key, ENCRYPT_KEY_SIZE)) { + ret = -1; + goto exit; + } XMEMCPY(encrypt_iv_nonce, stored_nonce, ENCRYPT_NONCE_SIZE); wc_Chacha_SetKey(&chacha, key, ENCRYPT_KEY_SIZE); encrypt_initialized = 1; - return 0; +exit: +#ifdef CUSTOM_ENCRYPT_KEY + ForceZero(key, sizeof(key)); + ForceZero(stored_nonce, sizeof(stored_nonce)); +#endif + return ret; } #elif defined(ENCRYPT_WITH_AES128) || defined(ENCRYPT_WITH_AES256) @@ -1762,6 +1770,7 @@ Aes aes_dec, aes_enc; int aes_init(void) { int devId = INVALID_DEVID; + int ret = 0; #if defined(CUSTOM_ENCRYPT_KEY) && !defined(WOLFBOOT_RENESAS_TSIP) uint8_t stored_nonce[ENCRYPT_NONCE_SIZE]; uint8_t key[ENCRYPT_KEY_SIZE]; @@ -1770,7 +1779,6 @@ int aes_init(void) uint8_t *key; #endif #ifdef WOLFBOOT_RENESAS_TSIP - int ret; wrap_enc_key_t* enc_key; devId = RENESAS_DEVID + 1; enc_key =(wrap_enc_key_t*)RENESAS_TSIP_INSTALLEDENCKEY_ADDR; @@ -1797,8 +1805,10 @@ int aes_init(void) wc_AesInit(&aes_enc, NULL, devId); wc_AesInit(&aes_dec, NULL, devId); - if (!encrypt_key_is_valid(key, ENCRYPT_KEY_SIZE)) - return -1; + if (!encrypt_key_is_valid(key, ENCRYPT_KEY_SIZE)) { + ret = -1; + goto exit; + } #ifdef WOLFBOOT_RENESAS_TSIP /* Unwrap key and get key index */ @@ -1810,7 +1820,8 @@ int aes_init(void) enc_key->encrypted_user_key, &aes_enc.ctx.tsip_keyIdx); #endif if (ret != TSIP_SUCCESS) { - return -1; + ret = -1; + goto exit; } /* set encryption key size */ aes_enc.ctx.keySize = ENCRYPT_KEY_SIZE; @@ -1831,7 +1842,12 @@ int aes_init(void) XMEMCPY(encrypt_iv_nonce, stored_nonce, ENCRYPT_NONCE_SIZE); encrypt_initialized = 1; - return 0; +exit: +#if defined(CUSTOM_ENCRYPT_KEY) && !defined(WOLFBOOT_RENESAS_TSIP) + ForceZero(key, sizeof(key)); + ForceZero(stored_nonce, sizeof(stored_nonce)); +#endif + return ret; } /** From e855c591db212456936d41cb2f8e109a7147effa Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 17:07:23 +0200 Subject: [PATCH 30/75] zeroize swap trailer key buffer F/1890 --- src/update_flash.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/update_flash.c b/src/update_flash.c index 434cf1e353..53263ebaa9 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -525,6 +525,10 @@ static int RAMFUNCTION wolfBoot_swap_and_final_erase(int resume) #endif hal_flash_lock(); +#ifdef EXT_ENCRYPTED + wolfBoot_zeroize(tmpBuffer, sizeof(tmpBuffer)); +#endif + return 0; } #ifdef __CCRX__ From 435e8d4c92ce253f44e1765d0c672fd79e6f4a0f Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 17:09:52 +0200 Subject: [PATCH 31/75] Scrub sign-tool encryption material F/1891 --- tools/keytools/sign.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index 93143a1436..fa01e7c1d3 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -1269,6 +1269,8 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, int ret = -1; uint8_t buf[4096]; uint8_t second_buf[4096]; + uint8_t key[ENC_MAX_KEY_SZ]; + uint8_t iv[ENC_MAX_IV_SZ]; uint32_t read_sz, pos; uint8_t digest[48]; /* max digest */ uint32_t digest_sz = 0; @@ -1277,6 +1279,9 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, uint8_t* cert_chain = NULL; uint32_t cert_chain_sz = 0; + XMEMSET(key, 0, sizeof(key)); + XMEMSET(iv, 0, sizeof(iv)); + /* Check certificate chain file size before allocating header, and adjust * header size if needed */ if (CMD.cert_chain_file != NULL) { @@ -1992,7 +1997,6 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, } if (!CMD.header_only && (CMD.encrypt != ENC_OFF) && CMD.encrypt_key_file) { - uint8_t key[ENC_MAX_KEY_SZ], iv[ENC_MAX_IV_SZ]; uint8_t enc_buf[ENC_MAX_BLOCK_SZ]; int ivSz, keySz, encBlockSz; uint32_t fsize = 0; @@ -2021,19 +2025,20 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, if (fek == NULL) { fprintf(stderr, "Open encryption key file %s: %s\n", CMD.encrypt_key_file, strerror(errno)); - exit(1); + goto failure; } ret = (int)fread(key, 1, keySz, fek); if (ret != keySz) { fprintf(stderr, "Error reading key from %s\n", CMD.encrypt_key_file); - exit(1); + goto failure; } ret = (int)fread(iv, 1, ivSz, fek); if (ret != ivSz) { fprintf(stderr, "Error reading IV from %s\n", CMD.encrypt_key_file); - exit(1); + goto failure; } fclose(fek); + fek = NULL; fef = fopen(CMD.output_encrypted_image_file, "wb"); if (!fef) { @@ -2051,7 +2056,8 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, #ifndef HAVE_CHACHA fprintf(stderr, "Encryption not supported: chacha support not found" "in wolfssl configuration.\n"); - exit(100); + ret = 100; + goto failure; #endif wc_Chacha_SetKey(&cha, key, sizeof(key)); wc_Chacha_SetIV(&cha, iv, 0); @@ -2083,6 +2089,7 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, } } fclose(fef); + fef = NULL; printf("Encryption complete.\n"); } printf("Output image(s) successfully created.\n"); @@ -2094,6 +2101,12 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, fclose(f); } failure: + wc_ForceZero(key, sizeof(key)); + wc_ForceZero(iv, sizeof(iv)); + if (fek) + fclose(fek); + if (fef) + fclose(fef); if (cert_chain) free(cert_chain); if (policy) From fc9e7a3e8a9098769111dde15aa4fa5699875771 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 17:12:35 +0200 Subject: [PATCH 32/75] Use constant-time delta base hash compare F/2253 --- include/image.h | 2 ++ src/image.c | 2 +- src/update_flash.c | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/include/image.h b/include/image.h index c9441a3d69..d7abd029f3 100644 --- a/include/image.h +++ b/include/image.h @@ -1252,6 +1252,8 @@ static void UNUSEDFUNCTION wolfBoot_image_clear_signature_ok( #endif /* Defined in image.c */ +int image_CT_compare(const uint8_t *expected, const uint8_t *actual, + uint32_t len); int wolfBoot_open_image(struct wolfBoot_image *img, uint8_t part); #ifdef EXT_FLASH int wolfBoot_open_image_external(struct wolfBoot_image* img, uint8_t part, uint8_t* addr); diff --git a/src/image.c b/src/image.c index 324f883aee..67ea21a331 100644 --- a/src/image.c +++ b/src/image.c @@ -58,7 +58,7 @@ /* Globals */ static uint8_t digest[WOLFBOOT_SHA_DIGEST_SIZE] XALIGNED(4); -static int __attribute__((noinline)) image_CT_compare( +int __attribute__((noinline)) image_CT_compare( const uint8_t *expected, const uint8_t *actual, uint32_t len) { uint8_t diff = 0; diff --git a/src/update_flash.c b/src/update_flash.c index 53263ebaa9..a066e81d67 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -642,7 +642,7 @@ static int wolfBoot_delta_update(struct wolfBoot_image *boot, cur_v, delta_base_v); ret = -1; } else if (!resume && delta_base_hash && - memcmp(base_hash, delta_base_hash, base_hash_sz) != 0) { + !image_CT_compare(base_hash, delta_base_hash, base_hash_sz)) { /* Wrong base image digest, cannot apply delta patch */ wolfBoot_printf("Delta Base hash mismatch\n"); ret = -1; From b705ca74ae599bec8978a76b063226b405ca4880 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 17:14:00 +0200 Subject: [PATCH 33/75] Warn when DISABLE_BACKUP is enabled F/2257 --- docs/compile.md | 4 +++- options.mk | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/compile.md b/docs/compile.md index cf0d566ac0..8da2b093c5 100644 --- a/docs/compile.md +++ b/docs/compile.md @@ -193,7 +193,9 @@ To circumvent the compile-time checks on the maximum allowed stack size, use `WO Optionally, it is possible to disable the backup copy of the current running firmware upon the installation of the update. This implies that no fall-back mechanism is protecting the target from a faulty firmware installation, but may be useful -in some cases where it is not possible to write on the update partition from the bootloader. +in some cases where it is not possible to write on the update partition from the bootloader. This also removes the +power-fail-safe swap behavior: if power is lost while the update is being copied into the BOOT partition, the original +firmware may already be partially overwritten and the device can be left unrecoverable. The associated compile-time option is `DISABLE_BACKUP=1` diff --git a/options.mk b/options.mk index 517c758874..ade1b50320 100644 --- a/options.mk +++ b/options.mk @@ -724,6 +724,7 @@ ifeq ($(NVM_FLASH_WRITEONCE),1) endif ifeq ($(DISABLE_BACKUP),1) + $(warning DISABLE_BACKUP=1 disables power-fail-safe updates; losing power during an update can leave BOOT partially written and unrecoverable) CFLAGS+= -D"DISABLE_BACKUP" endif From 4d6f1d584b1f07e72e95dc0c9c7eb98a4c5ef2b3 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 17:19:38 +0200 Subject: [PATCH 34/75] Add sign/parser roundtrip tests F/2270 --- tools/unit-tests/unit-sign-encrypted-output.c | 296 ++++++++++++++++-- 1 file changed, 277 insertions(+), 19 deletions(-) diff --git a/tools/unit-tests/unit-sign-encrypted-output.c b/tools/unit-tests/unit-sign-encrypted-output.c index 25360383ed..02cf4cfa44 100644 --- a/tools/unit-tests/unit-sign-encrypted-output.c +++ b/tools/unit-tests/unit-sign-encrypted-output.c @@ -12,6 +12,9 @@ #include #include +#define WOLFBOOT_HASH_SHA256 +#define IMAGE_HEADER_SIZE 512 + static const char *mock_fail_open_path; static int mock_fail_open_on_call; static int mock_open_call_count; @@ -40,6 +43,45 @@ static int mock_fprintf(FILE *stream, const char *format, ...); #undef fopen #undef main +#include "../../src/libwolfboot.c" + +static int locked; + +void hal_init(void) +{ +} + +int hal_flash_write(haladdr_t address, const uint8_t *data, int len) +{ + (void)address; + (void)data; + (void)len; + return 0; +} + +int hal_flash_erase(haladdr_t address, int len) +{ + (void)address; + (void)len; + return 0; +} + +void hal_flash_unlock(void) +{ + ck_assert_msg(locked, "Double unlock detected\n"); + locked--; +} + +void hal_flash_lock(void) +{ + ck_assert_msg(!locked, "Double lock detected\n"); + locked++; +} + +void hal_prepare_boot(void) +{ +} + static void reset_mocks(const char *fail_open_path, int fail_open_on_call) { mock_fail_open_path = fail_open_path; @@ -127,6 +169,89 @@ static int write_file(const char *path, const void *buf, size_t len) return written == len ? 0 : -1; } +static int read_file(const char *path, uint8_t **buf, size_t *len) +{ + FILE *f; + struct stat st; + size_t read_len; + + if (stat(path, &st) != 0) { + return -1; + } + + *buf = malloc((size_t)st.st_size); + if (*buf == NULL) { + return -1; + } + + f = fopen(path, "rb"); + if (f == NULL) { + free(*buf); + *buf = NULL; + return -1; + } + + read_len = fread(*buf, 1, (size_t)st.st_size, f); + fclose(f); + if (read_len != (size_t)st.st_size) { + free(*buf); + *buf = NULL; + return -1; + } + + *len = read_len; + return 0; +} + +static void reset_cmd_defaults(void) +{ + memset(&CMD, 0, sizeof(CMD)); + CMD.sign = NO_SIGN; + CMD.hash_algo = HASH_SHA256; + CMD.partition_id = HDR_IMG_TYPE_APP; + CMD.header_sz = IMAGE_HEADER_SIZE; + CMD.fw_version = "7"; + CMD.no_ts = 1; +} + +static void free_custom_tlv_buffers(void) +{ + uint32_t i; + + for (i = 0; i < MAX_CUSTOM_TLVS; i++) { + free(CMD.custom_tlv[i].buffer); + CMD.custom_tlv[i].buffer = NULL; + } +} + +static void assert_header_bytes(const uint8_t *image, uint16_t tag, + const uint8_t *expected, uint16_t expected_len) +{ + uint8_t *value = NULL; + uint16_t len = wolfBoot_find_header((uint8_t *)image + IMAGE_HEADER_OFFSET, + tag, &value); + + ck_assert_uint_eq(len, expected_len); + ck_assert_ptr_nonnull(value); + ck_assert_msg(memcmp(value, expected, expected_len) == 0, + "Tag 0x%04x mismatch", tag); +} + +static uint16_t find_exact_fill_custom_len(void) +{ + uint16_t len; + + for (len = 1; len < IMAGE_HEADER_SIZE; len++) { + CMD.custom_tlvs = 1; + CMD.custom_tlv[0].len = len; + if (header_required_size(0, 0, 0) == CMD.header_sz) { + return len; + } + } + + return 0; +} + START_TEST(test_make_header_ex_fails_when_encrypted_output_open_fails) { char tempdir[] = "/tmp/wolfboot-sign-XXXXXX"; @@ -151,14 +276,9 @@ START_TEST(test_make_header_ex_fails_when_encrypted_output_open_fails) ck_assert_int_eq(write_file(image_path, image_buf, sizeof(image_buf)), 0); ck_assert_int_eq(write_file(key_path, key_buf, sizeof(key_buf)), 0); - memset(&CMD, 0, sizeof(CMD)); - CMD.sign = NO_SIGN; + reset_cmd_defaults(); CMD.encrypt = ENC_AES128; - CMD.hash_algo = HASH_SHA256; - CMD.partition_id = HDR_IMG_TYPE_APP; CMD.header_sz = 256; - CMD.fw_version = "7"; - CMD.no_ts = 1; CMD.encrypt_key_file = key_path; snprintf(CMD.output_encrypted_image_file, sizeof(CMD.output_encrypted_image_file), "%s", encrypted_output_path); @@ -197,13 +317,8 @@ START_TEST(test_make_header_ex_fails_when_image_reopen_fails) ck_assert_int_eq(write_file(image_path, image_buf, sizeof(image_buf)), 0); - memset(&CMD, 0, sizeof(CMD)); - CMD.sign = NO_SIGN; - CMD.hash_algo = HASH_SHA256; - CMD.partition_id = HDR_IMG_TYPE_APP; + reset_cmd_defaults(); CMD.header_sz = 256; - CMD.fw_version = "7"; - CMD.no_ts = 1; reset_mocks(image_path, 2); ret = make_header_ex(0, pubkey, sizeof(pubkey), image_path, output_path, @@ -226,10 +341,13 @@ START_TEST(test_make_header_ex_grows_header_for_cert_chain_and_digest_tlvs) char image_path[PATH_MAX]; char output_path[PATH_MAX]; char cert_chain_path[PATH_MAX]; + uint8_t *output_buf = NULL; + uint8_t *value = NULL; uint8_t image_buf[] = { 0x01, 0x02, 0x03, 0x04 }; uint8_t cert_chain_buf[200]; uint8_t pubkey[] = { 0xA5 }; struct stat st; + size_t output_len; int ret; ck_assert_ptr_nonnull(mkdtemp(tempdir)); @@ -244,13 +362,8 @@ START_TEST(test_make_header_ex_grows_header_for_cert_chain_and_digest_tlvs) ck_assert_int_eq(write_file(cert_chain_path, cert_chain_buf, sizeof(cert_chain_buf)), 0); - memset(&CMD, 0, sizeof(CMD)); - CMD.sign = NO_SIGN; - CMD.hash_algo = HASH_SHA256; - CMD.partition_id = HDR_IMG_TYPE_APP; + reset_cmd_defaults(); CMD.header_sz = 256; - CMD.fw_version = "7"; - CMD.no_ts = 1; CMD.cert_chain_file = cert_chain_path; reset_mocks(NULL, 0); @@ -264,7 +377,16 @@ START_TEST(test_make_header_ex_grows_header_for_cert_chain_and_digest_tlvs) ck_assert_int_eq(mock_null_fwrite_calls, 0); ck_assert_int_eq(mock_null_fread_calls, 0); ck_assert_int_eq(mock_null_fclose_calls, 0); - + ck_assert_int_eq(read_file(output_path, &output_buf, &output_len), 0); + ck_assert_uint_eq(output_len, CMD.header_sz + sizeof(image_buf)); + assert_header_bytes(output_buf, HDR_CERT_CHAIN, cert_chain_buf, + sizeof(cert_chain_buf)); + ck_assert_uint_eq(wolfBoot_find_header(output_buf + IMAGE_HEADER_OFFSET, + HDR_PUBKEY, &value), HDR_SHA256_LEN); + ck_assert_uint_eq(wolfBoot_find_header(output_buf + IMAGE_HEADER_OFFSET, + HDR_SHA256, &value), HDR_SHA256_LEN); + + free(output_buf); unlink(output_path); unlink(cert_chain_path); unlink(image_path); @@ -272,6 +394,138 @@ START_TEST(test_make_header_ex_grows_header_for_cert_chain_and_digest_tlvs) } END_TEST +START_TEST(test_make_header_ex_roundtrip_custom_tlvs_via_wolfboot_parser) +{ + char tempdir[] = "/tmp/wolfboot-sign-XXXXXX"; + char image_path[PATH_MAX]; + char output_path[PATH_MAX]; + uint8_t *output_buf = NULL; + uint8_t *value = NULL; + uint8_t image_buf[] = { 0x10, 0x20, 0x30, 0x40, 0x50 }; + uint8_t pubkey[] = { 0xA5, 0x5A, 0x33, 0xCC }; + uint8_t tlv_one[] = { 0xAB }; + uint8_t tlv_two[] = { 0x10, 0x11, 0x12, 0x13, 0x14 }; + uint8_t tlv_three[] = { 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28 }; + uint16_t image_type; + uint32_t version = 7; + size_t output_len; + int ret; + + ck_assert_ptr_nonnull(mkdtemp(tempdir)); + + snprintf(image_path, sizeof(image_path), "%s/image.bin", tempdir); + snprintf(output_path, sizeof(output_path), "%s/output.bin", tempdir); + ck_assert_int_eq(write_file(image_path, image_buf, sizeof(image_buf)), 0); + + reset_cmd_defaults(); + image_type = (uint16_t)((CMD.sign & HDR_IMG_TYPE_AUTH_MASK) | + CMD.partition_id); + CMD.custom_tlvs = 3; + CMD.custom_tlv[0].tag = 0x30; + CMD.custom_tlv[0].len = sizeof(tlv_one); + CMD.custom_tlv[0].buffer = malloc(sizeof(tlv_one)); + memcpy(CMD.custom_tlv[0].buffer, tlv_one, sizeof(tlv_one)); + CMD.custom_tlv[1].tag = 0x31; + CMD.custom_tlv[1].len = sizeof(tlv_two); + CMD.custom_tlv[1].buffer = malloc(sizeof(tlv_two)); + memcpy(CMD.custom_tlv[1].buffer, tlv_two, sizeof(tlv_two)); + CMD.custom_tlv[2].tag = 0x32; + CMD.custom_tlv[2].len = sizeof(tlv_three); + CMD.custom_tlv[2].buffer = malloc(sizeof(tlv_three)); + memcpy(CMD.custom_tlv[2].buffer, tlv_three, sizeof(tlv_three)); + + reset_mocks(NULL, 0); + ret = make_header_ex(0, pubkey, sizeof(pubkey), image_path, output_path, + 0, 0, 0, 0, NULL, 0, NULL, 0); + + ck_assert_int_eq(ret, 0); + ck_assert_int_eq(read_file(output_path, &output_buf, &output_len), 0); + ck_assert_uint_eq(output_len, CMD.header_sz + sizeof(image_buf)); + assert_header_bytes(output_buf, HDR_VERSION, (uint8_t *)&version, + sizeof(version)); + assert_header_bytes(output_buf, HDR_IMG_TYPE, (uint8_t *)&image_type, + sizeof(image_type)); + assert_header_bytes(output_buf, 0x30, tlv_one, sizeof(tlv_one)); + assert_header_bytes(output_buf, 0x31, tlv_two, sizeof(tlv_two)); + assert_header_bytes(output_buf, 0x32, tlv_three, sizeof(tlv_three)); + ck_assert_uint_eq(wolfBoot_find_header(output_buf + IMAGE_HEADER_OFFSET, + HDR_PUBKEY, &value), HDR_SHA256_LEN); + ck_assert_ptr_nonnull(value); + ck_assert_uint_eq((uintptr_t)value % 8U, 0); + ck_assert_uint_eq(wolfBoot_find_header(output_buf + IMAGE_HEADER_OFFSET, + 0x30, &value), sizeof(tlv_one)); + ck_assert_uint_eq((uintptr_t)value % 8U, 0); + ck_assert_uint_eq(wolfBoot_find_header(output_buf + IMAGE_HEADER_OFFSET, + 0x31, &value), sizeof(tlv_two)); + ck_assert_uint_eq((uintptr_t)value % 8U, 0); + ck_assert_uint_eq(wolfBoot_find_header(output_buf + IMAGE_HEADER_OFFSET, + 0x32, &value), sizeof(tlv_three)); + ck_assert_uint_eq((uintptr_t)value % 8U, 0); + ck_assert_uint_eq(wolfBoot_find_header(output_buf + IMAGE_HEADER_OFFSET, + HDR_SHA256, &value), HDR_SHA256_LEN); + + free(output_buf); + free_custom_tlv_buffers(); + unlink(output_path); + unlink(image_path); + rmdir(tempdir); +} +END_TEST + +START_TEST(test_make_header_ex_roundtrip_finds_tlv_that_exactly_fills_header) +{ + char tempdir[] = "/tmp/wolfboot-sign-XXXXXX"; + char image_path[PATH_MAX]; + char output_path[PATH_MAX]; + uint8_t *output_buf = NULL; + uint8_t image_buf[] = { 0x61, 0x62, 0x63, 0x64 }; + uint8_t pubkey[] = { 0x01, 0x02 }; + uint8_t *custom_buf = NULL; + uint8_t *value = NULL; + size_t output_len; + uint16_t exact_len; + int ret; + + ck_assert_ptr_nonnull(mkdtemp(tempdir)); + + snprintf(image_path, sizeof(image_path), "%s/image.bin", tempdir); + snprintf(output_path, sizeof(output_path), "%s/output.bin", tempdir); + ck_assert_int_eq(write_file(image_path, image_buf, sizeof(image_buf)), 0); + + reset_cmd_defaults(); + exact_len = find_exact_fill_custom_len(); + ck_assert_uint_ne(exact_len, 0); + custom_buf = malloc(exact_len); + ck_assert_ptr_nonnull(custom_buf); + memset(custom_buf, 0x6C, exact_len); + + CMD.custom_tlvs = 1; + CMD.custom_tlv[0].tag = 0x40; + CMD.custom_tlv[0].len = exact_len; + CMD.custom_tlv[0].buffer = custom_buf; + ck_assert_uint_eq(header_required_size(0, 0, 0), CMD.header_sz); + + reset_mocks(NULL, 0); + ret = make_header_ex(0, pubkey, sizeof(pubkey), image_path, output_path, + 0, 0, 0, 0, NULL, 0, NULL, 0); + + ck_assert_int_eq(ret, 0); + ck_assert_int_eq(read_file(output_path, &output_buf, &output_len), 0); + ck_assert_uint_eq(output_len, CMD.header_sz + sizeof(image_buf)); + ck_assert_uint_eq(wolfBoot_find_header(output_buf + IMAGE_HEADER_OFFSET, + 0x40, &value), exact_len); + ck_assert_ptr_nonnull(value); + ck_assert_msg(memcmp(value, custom_buf, exact_len) == 0, + "Exact-fit TLV did not roundtrip"); + + free(output_buf); + free_custom_tlv_buffers(); + unlink(output_path); + unlink(image_path); + rmdir(tempdir); +} +END_TEST + Suite *wolfboot_suite(void) { Suite *s = suite_create("sign-encrypted-output"); @@ -281,6 +535,10 @@ Suite *wolfboot_suite(void) tcase_add_test(tcase, test_make_header_ex_fails_when_image_reopen_fails); tcase_add_test(tcase, test_make_header_ex_grows_header_for_cert_chain_and_digest_tlvs); + tcase_add_test(tcase, + test_make_header_ex_roundtrip_custom_tlvs_via_wolfboot_parser); + tcase_add_test(tcase, + test_make_header_ex_roundtrip_finds_tlv_that_exactly_fills_header); suite_add_tcase(s, tcase); return s; From 6ffae1a4dd6be19b5d136c2acc733d8a52bedce1 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 17:26:42 +0200 Subject: [PATCH 35/75] Add delta roundtrip edge-case coverage F/2271 --- tools/unit-tests/unit-delta.c | 227 +++++++++++++++++++++++++++++----- 1 file changed, 196 insertions(+), 31 deletions(-) diff --git a/tools/unit-tests/unit-delta.c b/tools/unit-tests/unit-delta.c index 8c240d3359..060f77bc97 100644 --- a/tools/unit-tests/unit-delta.c +++ b/tools/unit-tests/unit-delta.c @@ -302,54 +302,213 @@ static void initialize_buffers(uint8_t *src_a, uint8_t *src_b, size_t size) } -START_TEST(test_wb_patch_and_diff) +static uint8_t pattern_byte(uint32_t seed, size_t index) +{ + uint32_t value = seed ^ (uint32_t)index; + + value *= 1664525U; + value += 1013904223U; + value ^= (uint32_t)(index >> 8); + value ^= (uint32_t)(index >> 16); + + if ((uint8_t)value == ESC) { + value ^= 0x55U; + } + return (uint8_t)value; +} + +static void fill_pattern(uint8_t *dst, size_t size, uint32_t seed) +{ + size_t i; + + for (i = 0; i < size; ++i) { + dst[i] = pattern_byte(seed, i); + } +} + +static uint32_t run_roundtrip_case(const uint8_t *src_a, uint32_t size_a, + const uint8_t *src_b, uint32_t size_b, uint32_t patch_capacity) { WB_DIFF_CTX diff_ctx; WB_PATCH_CTX patch_ctx; - uint8_t src_a[SRC_SIZE]; - uint8_t src_b[SRC_SIZE]; - uint8_t patch[PATCH_SIZE]; - uint8_t patched_dst[DST_SIZE]; - int ret; - int i; + uint8_t *src_a_copy; + uint8_t *patch; + uint8_t *patched_dst; + uint8_t *new_patch; + uint8_t block[DELTA_BLOCK_SIZE] = {0}; uint32_t p_written = 0; + uint32_t dst_written = 0; + int ret; + src_a_copy = malloc(size_a); + patch = malloc(patch_capacity); + patched_dst = malloc(size_b); + ck_assert_ptr_nonnull(src_a_copy); + ck_assert_ptr_nonnull(patch); + ck_assert_ptr_nonnull(patched_dst); - initialize_buffers(src_a, src_b, SRC_SIZE); + memcpy(src_a_copy, src_a, size_a); - ret = wb_diff_init(&diff_ctx, src_a, SRC_SIZE, src_b, SRC_SIZE); + ret = wb_diff_init(&diff_ctx, src_a_copy, size_a, (uint8_t *)src_b, size_b); ck_assert_int_eq(ret, 0); - /* Create the patch */ - for (i = 0; i < SRC_SIZE; i += DELTA_BLOCK_SIZE) { - ret = wb_diff(&diff_ctx, patch + p_written, DELTA_BLOCK_SIZE); - ck_assert_int_ge(ret, 0); /* Should not be 0 until patch is over*/ - if (ret == 0) + for (;;) { + uint32_t remaining = patch_capacity - p_written; + + ck_assert_uint_ge(remaining, BLOCK_HDR_SIZE); + ret = wb_diff(&diff_ctx, patch + p_written, + remaining > DELTA_BLOCK_SIZE ? DELTA_BLOCK_SIZE : remaining); + ck_assert_int_ge(ret, 0); + if (ret == 0) { + if (diff_ctx.off_b < size_b) { + patch_capacity += DELTA_BLOCK_SIZE; + new_patch = realloc(patch, patch_capacity); + ck_assert_ptr_nonnull(new_patch); + patch = new_patch; + continue; + } break; - p_written += ret; + } + p_written += (uint32_t)ret; + ck_assert_uint_le(p_written, patch_capacity); } - ck_assert_int_gt(p_written, 0); /* Should not be 0 */ - printf("patch size: %u\n", p_written); - ret = wb_patch_init(&patch_ctx, src_a, SRC_SIZE, patch, p_written); + ck_assert_uint_eq(diff_ctx.off_b, size_b); + ck_assert_uint_gt(p_written, 0); + + ret = wb_patch_init(&patch_ctx, src_a_copy, size_a, patch, p_written); ck_assert_int_eq(ret, 0); - /* Apply the patch */ - for (i = 0; i < SRC_SIZE;) - { - ret = wb_patch(&patch_ctx, patched_dst + i, DELTA_BLOCK_SIZE); - ck_assert_int_ge(ret, 0); /* Should not be 0 until patch is over*/ - if (ret == 0) + for (;;) { + ret = wb_patch(&patch_ctx, block, sizeof(block)); + ck_assert_int_ge(ret, 0); + if (ret == 0) { break; - i += ret; + } + ck_assert_uint_le(dst_written + (uint32_t)ret, size_b); + memcpy(patched_dst + dst_written, block, (uint32_t)ret); + dst_written += (uint32_t)ret; } - ck_assert_int_gt(i, 0); /* Should not be 0 */ - ck_assert_int_eq(i, SRC_SIZE); // The patched length should match the buffer size - /* Verify that the patched destination matches src_b */ - for (i = 0; i < SRC_SIZE; ++i) { - ck_assert_uint_eq(patched_dst[i], src_b[i]); - } + ck_assert_uint_eq(dst_written, size_b); + ck_assert_int_eq(memcmp(patched_dst, src_b, size_b), 0); + + free(patched_dst); + free(patch); + free(src_a_copy); + return p_written; +} + +START_TEST(test_wb_patch_and_diff) +{ + uint8_t src_a[SRC_SIZE]; + uint8_t src_b[SRC_SIZE]; + uint32_t p_written = 0; + + initialize_buffers(src_a, src_b, SRC_SIZE); + + p_written = run_roundtrip_case(src_a, SRC_SIZE, src_b, SRC_SIZE, PATCH_SIZE); + printf("patch size: %u\n", p_written); +} +END_TEST + +START_TEST(test_wb_patch_and_diff_identical_images) +{ + uint8_t src_a[SRC_SIZE]; + uint32_t p_written; + + fill_pattern(src_a, sizeof(src_a), 0x12345678U); + + p_written = run_roundtrip_case(src_a, sizeof(src_a), src_a, sizeof(src_a), + PATCH_SIZE); + ck_assert_uint_lt(p_written, 64); +} +END_TEST + +START_TEST(test_wb_patch_and_diff_completely_different_images) +{ + uint8_t src_a[SRC_SIZE]; + uint8_t src_b[SRC_SIZE]; + + fill_pattern(src_a, sizeof(src_a), 0x11111111U); + fill_pattern(src_b, sizeof(src_b), 0x22222222U); + + (void)run_roundtrip_case(src_a, sizeof(src_a), src_b, sizeof(src_b), + sizeof(src_b) + DELTA_BLOCK_SIZE); +} +END_TEST + +START_TEST(test_wb_patch_and_diff_all_escape_images) +{ + uint8_t src_a[SRC_SIZE]; + uint8_t src_b[SRC_SIZE]; + + memset(src_a, ESC, sizeof(src_a)); + memset(src_b, ESC, sizeof(src_b)); + + (void)run_roundtrip_case(src_a, sizeof(src_a), src_b, sizeof(src_b), + PATCH_SIZE); +} +END_TEST + +START_TEST(test_wb_patch_and_diff_multi_sector_images) +{ + int sector_size_ret; + uint32_t size; + uint8_t *src_a; + uint8_t *src_b; + + sector_size_ret = wb_diff_get_sector_size(); + ck_assert_int_gt(sector_size_ret, BLOCK_HDR_SIZE); + size = (uint32_t)(3 * sector_size_ret + 37); + + src_a = malloc(size); + src_b = malloc(size); + ck_assert_ptr_nonnull(src_a); + ck_assert_ptr_nonnull(src_b); + + fill_pattern(src_a, size, 0xA5A5A5A5U); + memcpy(src_b, src_a, size); + src_b[sector_size_ret - 1] ^= 0x11; + src_b[sector_size_ret] ^= 0x22; + src_b[(2 * sector_size_ret) + 5] = ESC; + + (void)run_roundtrip_case(src_a, size, src_b, size, size + DELTA_BLOCK_SIZE); + + free(src_b); + free(src_a); +} +END_TEST + +START_TEST(test_wb_patch_and_diff_size_changing_update) +{ + uint8_t src_a[2048]; + uint8_t src_b[3077]; + + fill_pattern(src_a, sizeof(src_a), 0x31415926U); + fill_pattern(src_b, sizeof(src_b), 0x27182818U); + memcpy(src_b + 512, src_a, sizeof(src_a)); + src_b[0] = ESC; + src_b[sizeof(src_b) - 1] ^= 0x5A; + + (void)run_roundtrip_case(src_a, sizeof(src_a), src_b, sizeof(src_b), + sizeof(src_b) + DELTA_BLOCK_SIZE); +} +END_TEST + +START_TEST(test_wb_patch_and_diff_single_byte_difference) +{ + uint8_t src_a[SRC_SIZE]; + uint8_t src_b[SRC_SIZE]; + uint32_t p_written; + + fill_pattern(src_a, sizeof(src_a), 0x0BADB002U); + memcpy(src_b, src_a, sizeof(src_a)); + src_b[1537] ^= 0x01; + + p_written = run_roundtrip_case(src_a, sizeof(src_a), src_b, sizeof(src_b), + PATCH_SIZE); + ck_assert_uint_lt(p_written, 64); } END_TEST @@ -376,6 +535,12 @@ Suite *patch_diff_suite(void) tcase_add_test(tc_wolfboot_delta, test_wb_diff_preserves_main_loop_header_margin_for_escape); tcase_add_test(tc_wolfboot_delta, test_wb_diff_rejects_match_offsets_beyond_24_bits); tcase_add_test(tc_wolfboot_delta, test_wb_patch_and_diff); + tcase_add_test(tc_wolfboot_delta, test_wb_patch_and_diff_identical_images); + tcase_add_test(tc_wolfboot_delta, test_wb_patch_and_diff_completely_different_images); + tcase_add_test(tc_wolfboot_delta, test_wb_patch_and_diff_all_escape_images); + tcase_add_test(tc_wolfboot_delta, test_wb_patch_and_diff_multi_sector_images); + tcase_add_test(tc_wolfboot_delta, test_wb_patch_and_diff_size_changing_update); + tcase_add_test(tc_wolfboot_delta, test_wb_patch_and_diff_single_byte_difference); suite_add_tcase(s, tc_wolfboot_delta); return s; From 1c07a99c27beda60deadda6c3099e15f22348a55 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 19:28:42 +0200 Subject: [PATCH 36/75] libwolfboot: fix encrypted test-app builds --- src/libwolfboot.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/libwolfboot.c b/src/libwolfboot.c index 33008fff82..8c06bc7f94 100644 --- a/src/libwolfboot.c +++ b/src/libwolfboot.c @@ -62,6 +62,19 @@ #include /* for size_t */ +#ifdef EXT_ENCRYPTED +static int encrypt_key_is_erased(const uint8_t *key, uint32_t len) +{ + uint8_t diff = 0; + uint32_t i; + + for (i = 0; i < len; i++) + diff |= key[i] ^ FLASH_BYTE_ERASED; + + return diff == 0; +} +#endif + #if defined(EXT_ENCRYPTED) && (defined(__WOLFBOOT) || defined(UNIT_TEST) || defined(MMU)) #include "encrypt.h" static int encrypt_initialized = 0; @@ -84,17 +97,6 @@ static int encrypt_key_is_valid(const uint8_t *key, uint32_t len) return (has_one != 0) && (has_zero != 0); } -static int encrypt_key_is_erased(const uint8_t *key, uint32_t len) -{ - uint8_t diff = 0; - uint32_t i; - - for (i = 0; i < len; i++) - diff |= key[i] ^ FLASH_BYTE_ERASED; - - return diff == 0; -} - #define FALLBACK_IV_OFFSET 0x00100000U #if !defined(XMEMSET) #include From 0ab76ea4afddb995346c4f61ba65166bd532877c Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 19:54:57 +0200 Subject: [PATCH 37/75] tools/unit-tests: fix rebased sign test target Remove leftover conflict markers after dropping 98d68c90 so unit-sign-encrypted-output builds again. --- tools/unit-tests/Makefile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 54cd3f0813..3b15e21ea6 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -173,8 +173,6 @@ unit-policy-sign: ../../include/target.h unit-policy-sign.c \ -DHAVE_ECC_KEY_IMPORT \ -ffunction-sections -fdata-sections $(LDFLAGS) -Wl,--gc-sections -<<<<<<< HEAD -======= unit-sign-encrypted-output: ../../include/target.h unit-sign-encrypted-output.c \ $(KEYTOOLS_SIGN_SRCS) gcc -o $@ $^ -I../keytools $(CFLAGS) -DML_DSA_LEVEL=2 \ @@ -182,8 +180,6 @@ unit-sign-encrypted-output: ../../include/target.h unit-sign-encrypted-output.c -DWOLFBOOT_XMSS_PARAMS=\"XMSS-SHA2_10_256\" \ -ffunction-sections -fdata-sections \ $(LDFLAGS) -Wl,--gc-sections - ->>>>>>> 34438999 (Fix sign encrypted output open failure) unit-rot-auth: ../../include/target.h unit-rot-auth.c \ $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/memory.c gcc -o $@ $^ -I../tpm $(CFLAGS) -I$(WOLFBOOT_LIB_WOLFTPM) -DWOLFBOOT_TPM \ From b8131a1fa1a039dfa111b4e48355ec56c5de2d23 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 20:14:59 +0200 Subject: [PATCH 38/75] cmake: define bootloader protect macros Export WOLFBOOT_ORIGIN and BOOTLOADER_PARTITION_SIZE through WOLFBOOT_DEFS so update_flash/update_ram/update_disk protection code builds under CMake presets. --- CMakeLists.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 65f0e6c70e..c3b62ac723 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -747,7 +747,20 @@ if(ARCH STREQUAL "AARCH64") endif() +if(NOT DEFINED WOLFBOOT_ORIGIN) + set(WOLFBOOT_ORIGIN ${ARCH_FLASH_OFFSET}) +endif() + +if(NOT DEFINED BOOTLOADER_PARTITION_SIZE) + math(EXPR BOOTLOADER_PARTITION_SIZE + "${WOLFBOOT_PARTITION_BOOT_ADDRESS} - ${ARCH_FLASH_OFFSET}" + OUTPUT_FORMAT HEXADECIMAL) +endif() + list(APPEND WOLFBOOT_DEFS ARCH_FLASH_OFFSET=${ARCH_FLASH_OFFSET}) +list(APPEND WOLFBOOT_DEFS + WOLFBOOT_ORIGIN=${WOLFBOOT_ORIGIN} + BOOTLOADER_PARTITION_SIZE=${BOOTLOADER_PARTITION_SIZE}) if(${WOLFBOOT_TARGET} STREQUAL "x86_64_efi") if(NOT DEFINED GNU_EFI_LIB_PATH) From e60db57bba7be36abba3091907ddfe52ce632a4b Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 21:47:07 +0200 Subject: [PATCH 39/75] cmake: link default flash-protect hook Include hal/hal.c in wolfboothal and mark hal_flash_protect RAMFUNCTION so CMake RAM_CODE targets link and call the default hook correctly. --- CMakeLists.txt | 2 +- hal/hal.c | 2 +- hal/nrf5340.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c3b62ac723..88756f5754 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1152,7 +1152,7 @@ if(TZEN) endif() endif() -target_sources(wolfboothal PRIVATE include/hal.h hal/${WOLFBOOT_TARGET}.c ${WOLFBOOT_FLASH_SOURCES} +target_sources(wolfboothal PRIVATE include/hal.h hal/hal.c hal/${WOLFBOOT_TARGET}.c ${WOLFBOOT_FLASH_SOURCES} ${PARTITION_SOURCE} ${WOLFBOOT_TZ_HAL_SOURCES}) diff --git a/hal/hal.c b/hal/hal.c index 963ad544cc..6c14c63ed9 100644 --- a/hal/hal.c +++ b/hal/hal.c @@ -281,7 +281,7 @@ int hal_flash_test_dualbank(void) #endif /* TEST_FLASH */ -WEAKFUNCTION int hal_flash_protect(haladdr_t address, int len) +WEAKFUNCTION int RAMFUNCTION hal_flash_protect(haladdr_t address, int len) { (void)address; (void)len; diff --git a/hal/nrf5340.c b/hal/nrf5340.c index 5f7d7786a2..7111f13506 100644 --- a/hal/nrf5340.c +++ b/hal/nrf5340.c @@ -827,7 +827,7 @@ void hal_init(void) #ifdef __WOLFBOOT /* enable write protection for the region of flash specified */ -int hal_flash_protect(uint32_t start, uint32_t len) +int RAMFUNCTION hal_flash_protect(uint32_t start, uint32_t len) { /* only application core supports SPU */ #ifdef TARGET_nrf5340_app From 399e87508a971f9de5f7bdeb685d172aa8fa647a Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 21:50:39 +0200 Subject: [PATCH 40/75] unit-tests: fix update_ram protect context Pass WOLFBOOT_ORIGIN and BOOTLOADER_PARTITION_SIZE to unit-update-ram and provide a hal_flash_protect stub so the update_ram harness still builds after bootloader protection was added. --- tools/unit-tests/Makefile | 3 ++- tools/unit-tests/unit-update-ram.c | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 3b15e21ea6..3b1fd62900 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -109,7 +109,8 @@ unit-update-flash-self-update:CFLAGS+=-DMOCK_PARTITIONS -DWOLFBOOT_NO_SIGN -DUNI -DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE unit-update-ram:CFLAGS+=-DMOCK_PARTITIONS -DWOLFBOOT_NO_SIGN -DUNIT_TEST_AUTH \ -DWOLFBOOT_HASH_SHA256 -DPRINTF_ENABLED -DEXT_FLASH -DPART_UPDATE_EXT \ - -DPART_SWAP_EXT -DPART_BOOT_EXT -DWOLFBOOT_DUALBOOT -DNO_XIP + -DPART_SWAP_EXT -DPART_BOOT_EXT -DWOLFBOOT_DUALBOOT -DNO_XIP \ + -DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE unit-update-disk:CFLAGS+=-DMOCK_PARTITIONS -DPRINTF_ENABLED \ -DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE unit-string:CFLAGS+=-fno-builtin diff --git a/tools/unit-tests/unit-update-ram.c b/tools/unit-tests/unit-update-ram.c index 6399d01c73..fecac50540 100644 --- a/tools/unit-tests/unit-update-ram.c +++ b/tools/unit-tests/unit-update-ram.c @@ -84,6 +84,13 @@ static void reset_mock_stats(void) wolfBoot_staged_ok = 0; } +int hal_flash_protect(haladdr_t address, int len) +{ + (void)address; + (void)len; + return 0; +} + uint32_t get_version_ramloaded(void) { return wolfBoot_get_blob_version(wolfboot_ram); From c51bb6a7f5f36e613a85aac542928484b5df3eeb Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 21:54:30 +0200 Subject: [PATCH 41/75] unit-tests: exclude keytools from coverage --- tools/unit-tests/Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 3b1fd62900..61895d333f 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -59,7 +59,11 @@ include unit-sign-encrypted-output.mkfrag all: $(TESTS) cov: + rm -f unit-sign-encrypted-output-* gcovr -f "^\.\.\/\.\.\/src.*\.c" -r ../.. --verbose \ + --exclude-directories 'tools/keytools$$' \ + --gcov-exclude '.*tools/keytools/.*' \ + --gcov-exclude '.*unit-sign-encrypted-output-.*' \ --merge-mode-functions merge-use-line-0 \ --html-medium-threshold 60 \ --html-high-threshold 80 \ @@ -316,6 +320,7 @@ unit-multiboot: unit-multiboot.c covclean: rm -f *.gcov *.gcno *.gcda coverage.* + rm -f unit-sign-encrypted-output-* clean: covclean rm -f $(TESTS) *.o *.gcno *.gcda coverage.* From e5a42e369a4ffe57a465ff3d9c45acdf6b170723 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 22:20:17 +0200 Subject: [PATCH 42/75] nrf5340: match hal_flash_protect signature --- hal/nrf5340.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hal/nrf5340.c b/hal/nrf5340.c index 7111f13506..2c0a3f6fbc 100644 --- a/hal/nrf5340.c +++ b/hal/nrf5340.c @@ -827,7 +827,7 @@ void hal_init(void) #ifdef __WOLFBOOT /* enable write protection for the region of flash specified */ -int RAMFUNCTION hal_flash_protect(uint32_t start, uint32_t len) +int RAMFUNCTION hal_flash_protect(haladdr_t start, int len) { /* only application core supports SPU */ #ifdef TARGET_nrf5340_app From 62f6eca694173f17908819f094a5683d5bb9058b Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 8 Apr 2026 22:22:19 +0200 Subject: [PATCH 43/75] unit-tests: avoid gcov memmove timeout --- src/string.c | 8 ++++++++ tools/unit-tests/unit-string.c | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/string.c b/src/string.c index 880f0878ae..1812be7c6b 100644 --- a/src/string.c +++ b/src/string.c @@ -294,6 +294,14 @@ void *memmove(void *dst, const void *src, size_t n) if (src < dst) { const char *s = (const char *)src; char *d = (char *)dst; + if (((size_t)dst & (sizeof(unsigned long)-1)) == 0 && + ((size_t)src & (sizeof(unsigned long)-1)) == 0) + { + while (n >= sizeof(unsigned long)) { + n -= sizeof(unsigned long); + *(unsigned long*)(d + n) = *(const unsigned long*)(s + n); + } + } for (i = n; i > 0; i--) { d[i - 1] = s[i - 1]; } diff --git a/tools/unit-tests/unit-string.c b/tools/unit-tests/unit-string.c index a91e8ff9bf..a5c76c2a7a 100644 --- a/tools/unit-tests/unit-string.c +++ b/tools/unit-tests/unit-string.c @@ -329,7 +329,7 @@ START_TEST(test_memcpy_memmove) } END_TEST -#if defined(__linux__) && (SIZE_MAX > INT_MAX) +#if defined(__linux__) && (SIZE_MAX > INT_MAX) && !defined(__GCOV__) START_TEST(test_memmove_large_overlap_length) { size_t n = (size_t)INT_MAX + 2U; From 040bcdb00d67ee137f1e3b3db1095f7e988e6b66 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 9 Apr 2026 06:23:40 +0200 Subject: [PATCH 44/75] unit-tests: skip large memmove under coverage --- tools/unit-tests/Makefile | 1 + tools/unit-tests/unit-string.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 61895d333f..6ced0dbca0 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -29,6 +29,7 @@ CFLAGS+=-g -ggdb CFLAGS+=-fprofile-arcs CFLAGS+=-ftest-coverage CFLAGS+=--coverage +CFLAGS+=-DUNIT_TEST_COVERAGE CFLAGS+=-DUNIT_TEST -DWOLFSSL_USER_SETTINGS LDFLAGS+=-fprofile-arcs LDFLAGS+=-ftest-coverage diff --git a/tools/unit-tests/unit-string.c b/tools/unit-tests/unit-string.c index a5c76c2a7a..0d8a85cedd 100644 --- a/tools/unit-tests/unit-string.c +++ b/tools/unit-tests/unit-string.c @@ -329,7 +329,7 @@ START_TEST(test_memcpy_memmove) } END_TEST -#if defined(__linux__) && (SIZE_MAX > INT_MAX) && !defined(__GCOV__) +#if defined(__linux__) && (SIZE_MAX > INT_MAX) && !defined(UNIT_TEST_COVERAGE) START_TEST(test_memmove_large_overlap_length) { size_t n = (size_t)INT_MAX + 2U; @@ -473,7 +473,9 @@ Suite *string_suite(void) tcase_add_test(tcase_misc, test_strncmp); tcase_add_test(tcase_misc, test_memcpy_memmove); #if defined(__linux__) && (SIZE_MAX > INT_MAX) +#if !defined(UNIT_TEST_COVERAGE) tcase_add_test(tcase_misc, test_memmove_large_overlap_length); +#endif #endif tcase_add_test(tcase_misc, test_memcpy_aligned_buffers); tcase_add_test(tcase_misc, test_uart_writenum_basic); From fab609df64e2758776b1bb60cde297fefdcff373 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 9 Apr 2026 14:53:35 +0200 Subject: [PATCH 45/75] options: normalize self-update monolithic flag --- options.mk | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/options.mk b/options.mk index ade1b50320..f9307e5b29 100644 --- a/options.mk +++ b/options.mk @@ -90,6 +90,9 @@ endif ## Monolithic self-update: erase covers fw_size so the payload can span ## the bootloader region into the contiguous boot partition. +ifeq ($(WOLFBOOT_SELF_UPDATE_MONOLITHIC),1) + SELF_UPDATE_MONOLITHIC:=1 +endif ifeq ($(SELF_UPDATE_MONOLITHIC),1) CFLAGS+=-DWOLFBOOT_SELF_UPDATE_MONOLITHIC endif @@ -714,7 +717,7 @@ ifeq ($(WOLFBOOT_SKIP_BOOT_VERIFY),1) $(error WOLFBOOT_SKIP_BOOT_VERIFY=1 requires WOLFBOOT_SELF_HEADER=1) endif ifneq ($(SELF_UPDATE_MONOLITHIC),1) - $(error WOLFBOOT_SKIP_BOOT_VERIFY=1 requires SELF_UPDATE_MONOLITHIC=1) + $(error WOLFBOOT_SKIP_BOOT_VERIFY=1 requires WOLFBOOT_SELF_UPDATE_MONOLITHIC (set SELF_UPDATE_MONOLITHIC=1)) endif CFLAGS+=-D"WOLFBOOT_SKIP_BOOT_VERIFY" endif From fff5222e2d6733c11556ba6d6dcfebc0db8749db Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 9 Apr 2026 14:54:14 +0200 Subject: [PATCH 46/75] hal: document flash protect API contract --- hal/skeleton.c | 8 +++++--- include/hal.h | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/hal/skeleton.c b/hal/skeleton.c index 6a35a3593f..e6858a1641 100644 --- a/hal/skeleton.c +++ b/hal/skeleton.c @@ -35,9 +35,11 @@ void hal_init(void) void hal_prepare_boot(void) { /* wolfBoot calls hal_flash_protect() before this hook. - * Override hal_flash_protect() to lock the bootloader region on - * targets that support runtime write protection, and use this hook - * only for any remaining platform-specific handoff work. */ + * Override int hal_flash_protect(haladdr_t address, int len) to lock + * the bootloader region on targets that support runtime write + * protection. Return 0 on success or a negative value on failure, and + * use this hook only for any remaining platform-specific handoff work. + */ } #endif diff --git a/include/hal.h b/include/hal.h index 979702e3a8..fac45ef034 100644 --- a/include/hal.h +++ b/include/hal.h @@ -87,6 +87,10 @@ uint64_t hal_get_timer_us(void); #endif void hal_flash_unlock(void); void hal_flash_lock(void); +/* + * Lock the flash region [address, address + len) against writes. + * Return 0 on success, or a negative value on failure. + */ int hal_flash_protect(haladdr_t address, int len); void hal_prepare_boot(void); From d78c625a87631d9d536318a6496c03137e38d991 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 9 Apr 2026 14:58:39 +0200 Subject: [PATCH 47/75] update: propagate encrypt key persist errors --- src/update_flash.c | 25 +++++++++++++---- tools/unit-tests/unit-update-flash.c | 42 +++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/src/update_flash.c b/src/update_flash.c index a066e81d67..f8c3339cac 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -426,6 +426,7 @@ static int RAMFUNCTION wolfBoot_swap_and_final_erase(int resume) struct wolfBoot_image update[1]; struct wolfBoot_image swap[1]; uint8_t updateState = IMG_STATE_NEW; + int ret = 0; int eraseLen = (WOLFBOOT_SECTOR_SIZE #ifdef NVM_FLASH_WRITEONCE /* need to erase the redundant sector too */ * 2 @@ -499,8 +500,16 @@ static int RAMFUNCTION wolfBoot_swap_and_final_erase(int resume) #ifdef EXT_ENCRYPTED /* Initialize encryption with the saved key */ - wolfBoot_set_encrypt_key((uint8_t*)tmpBuffer, - (uint8_t*)&tmpBuffer[ENCRYPT_KEY_SIZE/sizeof(uint32_t)]); + ret = wolfBoot_set_encrypt_key((uint8_t*)tmpBuffer, + (uint8_t*)&tmpBuffer[ENCRYPT_KEY_SIZE / sizeof(uint32_t)]); + if (ret != 0) { +#ifdef EXT_FLASH + ext_flash_lock(); +#endif + hal_flash_lock(); + wolfBoot_zeroize(tmpBuffer, sizeof(tmpBuffer)); + return ret; + } /* wolfBoot_set_encrypt_key calls hal_flash_unlock, need to unlock again */ hal_flash_unlock(); #endif @@ -808,6 +817,7 @@ static int RAMFUNCTION wolfBoot_update(int fallback_allowed) uint32_t total_size = 0; const uint32_t sector_size = WOLFBOOT_SECTOR_SIZE; uint32_t sector = 0; + int ret = 0; /* we need to pre-set flag to SECT_FLAG_NEW in case magic hasn't been set * on the update partition as part of the delta update direction check. if * magic has not been set flag will have an un-determined value when we go @@ -1128,7 +1138,9 @@ static int RAMFUNCTION wolfBoot_update(int fallback_allowed) #if !defined(CUSTOM_PARTITION_TRAILER) /* start re-entrant final erase, return code is only for resumption in * wolfBoot_start */ - wolfBoot_swap_and_final_erase(0); + ret = wolfBoot_swap_and_final_erase(0); + if (ret != 0) + return ret; #ifndef DISABLE_BACKUP if (rollback_needed) { hal_flash_unlock(); @@ -1205,16 +1217,19 @@ static int RAMFUNCTION wolfBoot_update(int fallback_allowed) /* Save the encryption key after swapping */ #ifdef EXT_ENCRYPTED - wolfBoot_set_encrypt_key(key, nonce); + ret = wolfBoot_set_encrypt_key(key, nonce); wolfBoot_zeroize(key, sizeof(key)); wolfBoot_zeroize(nonce, sizeof(nonce)); + if (ret != 0) + goto out; #endif #endif /* DISABLE_BACKUP */ +out: #ifdef EXT_ENCRYPTED /* Make sure we leave the global IV offset in its normal state. */ wolfBoot_enable_fallback_iv(0); #endif - return 0; + return ret; } #ifdef __CCRX__ #pragma section diff --git a/tools/unit-tests/unit-update-flash.c b/tools/unit-tests/unit-update-flash.c index b9f3547c1d..b564b3b2c9 100644 --- a/tools/unit-tests/unit-update-flash.c +++ b/tools/unit-tests/unit-update-flash.c @@ -56,6 +56,9 @@ static int add_payload_type(uint8_t part, uint32_t version, uint32_t size, uint16_t img_type); #ifdef CUSTOM_ENCRYPT_KEY +static int mock_set_encrypt_key_ret = 0; +static int mock_set_encrypt_key_calls = 0; + int wolfBoot_get_encrypt_key(uint8_t *k, uint8_t *nonce) { int i; @@ -72,7 +75,8 @@ int wolfBoot_set_encrypt_key(const uint8_t *key, const uint8_t *nonce) { (void)key; (void)nonce; - return 0; + mock_set_encrypt_key_calls++; + return mock_set_encrypt_key_ret; } int wolfBoot_erase_encrypt_key(void) @@ -133,6 +137,8 @@ int hal_flash_protect(haladdr_t address, int len) static void reset_mock_stats(void) { wolfBoot_staged_ok = 0; + mock_set_encrypt_key_ret = 0; + mock_set_encrypt_key_calls = 0; #ifndef ARCH_SIM wolfBoot_panicked = 0; #endif @@ -520,6 +526,38 @@ START_TEST (test_fallback_image_verification_rejects_corruption) cleanup_flash(); } END_TEST + +START_TEST (test_final_swap_propagates_encrypt_key_persist_failure) +{ + int ret; + int erase_len = WOLFBOOT_SECTOR_SIZE; + uintptr_t tmp_boot_pos = WOLFBOOT_PARTITION_SIZE - erase_len - + WOLFBOOT_SECTOR_SIZE; + uint32_t tmp_buffer[TRAILER_OFFSET_WORDS + 1]; + + reset_mock_stats(); + prepare_flash(); + + add_payload(PART_BOOT, 1, TEST_SIZE_SMALL); + add_payload(PART_UPDATE, 2, TEST_SIZE_SMALL); + + memset(tmp_buffer, 0, sizeof(tmp_buffer)); + tmp_buffer[TRAILER_OFFSET_WORDS] = WOLFBOOT_MAGIC_TRAIL; + + hal_flash_unlock(); + hal_flash_write(WOLFBOOT_PARTITION_BOOT_ADDRESS + tmp_boot_pos, + (const uint8_t *)tmp_buffer, sizeof(tmp_buffer)); + hal_flash_lock(); + + mock_set_encrypt_key_ret = -5; + ret = wolfBoot_swap_and_final_erase(1); + + ck_assert_int_eq(ret, -5); + ck_assert_int_eq(mock_set_encrypt_key_calls, 1); + + cleanup_flash(); +} +END_TEST #endif START_TEST (test_sunnyday_noupdate) @@ -973,6 +1011,7 @@ Suite *wolfboot_suite(void) #ifdef UNIT_TEST_FALLBACK_ONLY #ifdef EXT_ENCRYPTED tcase_add_test(fallback_verify, test_fallback_image_verification_rejects_corruption); + tcase_add_test(fallback_verify, test_final_swap_propagates_encrypt_key_persist_failure); suite_add_tcase(s, fallback_verify); #endif return s; @@ -1009,6 +1048,7 @@ Suite *wolfboot_suite(void) #endif #ifdef EXT_ENCRYPTED tcase_add_test(fallback_verify, test_fallback_image_verification_rejects_corruption); + tcase_add_test(fallback_verify, test_final_swap_propagates_encrypt_key_persist_failure); #endif suite_add_tcase(s, empty_panic); From cdf84024faaee5a182e576a3f893aba134a4814a Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 9 Apr 2026 15:00:09 +0200 Subject: [PATCH 48/75] docs: fix encrypt key erase return contract --- src/libwolfboot.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libwolfboot.c b/src/libwolfboot.c index 8c06bc7f94..4bd7cb4a55 100644 --- a/src/libwolfboot.c +++ b/src/libwolfboot.c @@ -1676,7 +1676,7 @@ int RAMFUNCTION wolfBoot_get_encrypt_key(uint8_t *k, uint8_t *nonce) * This function erases the encryption key and nonce, resetting them to all 0xFF * bytes.It ensures that the key and nonce cannot be accessed after erasure. * - * @return 0 if successful. + * @return 0 on success, or the underlying flash error code on failure. * */ int RAMFUNCTION wolfBoot_erase_encrypt_key(void) From 4c704a9440ce445c85beabaa2fd9cfffc67668bc Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 9 Apr 2026 16:36:38 +0200 Subject: [PATCH 49/75] boot: fail closed on flash protect errors --- src/update_disk.c | 5 ++++- src/update_flash.c | 5 ++++- src/update_flash_hwswap.c | 3 ++- src/update_ram.c | 5 ++++- tools/unit-tests/unit-update-flash.c | 2 ++ 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/update_disk.c b/src/update_disk.c index 11381c91be..7e52feadd7 100644 --- a/src/update_disk.c +++ b/src/update_disk.c @@ -549,7 +549,10 @@ void RAMFUNCTION wolfBoot_start(void) (void)hal_hsm_server_cleanup(); #endif #ifndef TZEN - (void)hal_flash_protect(WOLFBOOT_ORIGIN, BOOTLOADER_PARTITION_SIZE); + if (hal_flash_protect(WOLFBOOT_ORIGIN, BOOTLOADER_PARTITION_SIZE) < 0) { + wolfBoot_printf("Error protecting bootloader flash region\r\n"); + wolfBoot_panic(); + } #endif hal_prepare_boot(); diff --git a/src/update_flash.c b/src/update_flash.c index f8c3339cac..382c20bbee 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -1503,7 +1503,10 @@ void RAMFUNCTION wolfBoot_start(void) #endif #ifndef TZEN - (void)hal_flash_protect(WOLFBOOT_ORIGIN, BOOTLOADER_PARTITION_SIZE); + if (hal_flash_protect(WOLFBOOT_ORIGIN, BOOTLOADER_PARTITION_SIZE) < 0) { + wolfBoot_printf("Error protecting bootloader flash region\n"); + wolfBoot_panic(); + } #endif hal_prepare_boot(); diff --git a/src/update_flash_hwswap.c b/src/update_flash_hwswap.c index 53efb4943a..07c95862b7 100644 --- a/src/update_flash_hwswap.c +++ b/src/update_flash_hwswap.c @@ -107,7 +107,8 @@ void RAMFUNCTION wolfBoot_start(void) (void)hal_hsm_server_cleanup(); #endif #ifndef TZEN - (void)hal_flash_protect(WOLFBOOT_ORIGIN, BOOTLOADER_PARTITION_SIZE); + if (hal_flash_protect(WOLFBOOT_ORIGIN, BOOTLOADER_PARTITION_SIZE) < 0) + boot_panic(); #endif hal_prepare_boot(); #ifdef WOLFBOOT_HOOK_BOOT diff --git a/src/update_ram.c b/src/update_ram.c index c76063e00e..b2f0d77212 100644 --- a/src/update_ram.c +++ b/src/update_ram.c @@ -391,7 +391,10 @@ void RAMFUNCTION wolfBoot_start(void) #endif #ifndef TZEN - (void)hal_flash_protect(WOLFBOOT_ORIGIN, BOOTLOADER_PARTITION_SIZE); + if (hal_flash_protect(WOLFBOOT_ORIGIN, BOOTLOADER_PARTITION_SIZE) < 0) { + wolfBoot_printf("Error protecting bootloader flash region\n"); + wolfBoot_panic(); + } #endif hal_prepare_boot(); diff --git a/tools/unit-tests/unit-update-flash.c b/tools/unit-tests/unit-update-flash.c index b564b3b2c9..aeddb337c5 100644 --- a/tools/unit-tests/unit-update-flash.c +++ b/tools/unit-tests/unit-update-flash.c @@ -137,8 +137,10 @@ int hal_flash_protect(haladdr_t address, int len) static void reset_mock_stats(void) { wolfBoot_staged_ok = 0; +#ifdef CUSTOM_ENCRYPT_KEY mock_set_encrypt_key_ret = 0; mock_set_encrypt_key_calls = 0; +#endif #ifndef ARCH_SIM wolfBoot_panicked = 0; #endif From a5ef464731af180c0a1d2ce65b29e9f31186f358 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 9 Apr 2026 16:45:07 +0200 Subject: [PATCH 50/75] update: fix warning-only build regressions --- src/update_flash.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/update_flash.c b/src/update_flash.c index 382c20bbee..3b981a185c 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -426,7 +426,6 @@ static int RAMFUNCTION wolfBoot_swap_and_final_erase(int resume) struct wolfBoot_image update[1]; struct wolfBoot_image swap[1]; uint8_t updateState = IMG_STATE_NEW; - int ret = 0; int eraseLen = (WOLFBOOT_SECTOR_SIZE #ifdef NVM_FLASH_WRITEONCE /* need to erase the redundant sector too */ * 2 @@ -499,6 +498,7 @@ static int RAMFUNCTION wolfBoot_swap_and_final_erase(int resume) wb_flash_erase(boot, WOLFBOOT_PARTITION_SIZE - eraseLen, eraseLen); #ifdef EXT_ENCRYPTED + int ret; /* Initialize encryption with the saved key */ ret = wolfBoot_set_encrypt_key((uint8_t*)tmpBuffer, (uint8_t*)&tmpBuffer[ENCRYPT_KEY_SIZE / sizeof(uint32_t)]); @@ -1221,10 +1221,9 @@ static int RAMFUNCTION wolfBoot_update(int fallback_allowed) wolfBoot_zeroize(key, sizeof(key)); wolfBoot_zeroize(nonce, sizeof(nonce)); if (ret != 0) - goto out; + return ret; #endif #endif /* DISABLE_BACKUP */ -out: #ifdef EXT_ENCRYPTED /* Make sure we leave the global IV offset in its normal state. */ wolfBoot_enable_fallback_iv(0); From eca76efcc52912c22f9bf5340d4a7c47a7686d77 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 9 Apr 2026 17:44:26 +0200 Subject: [PATCH 51/75] string: fix unaligned backward word copies --- src/string.c | 13 ++++++++----- tools/unit-tests/unit-string.c | 8 ++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/string.c b/src/string.c index 1812be7c6b..af333b97fa 100644 --- a/src/string.c +++ b/src/string.c @@ -294,17 +294,20 @@ void *memmove(void *dst, const void *src, size_t n) if (src < dst) { const char *s = (const char *)src; char *d = (char *)dst; + size_t aligned_n = 0; if (((size_t)dst & (sizeof(unsigned long)-1)) == 0 && ((size_t)src & (sizeof(unsigned long)-1)) == 0) { - while (n >= sizeof(unsigned long)) { - n -= sizeof(unsigned long); - *(unsigned long*)(d + n) = *(const unsigned long*)(s + n); - } + aligned_n = n & ~(sizeof(unsigned long) - 1); } - for (i = n; i > 0; i--) { + for (i = n; i > aligned_n; i--) { d[i - 1] = s[i - 1]; } + while (aligned_n > 0) { + aligned_n -= sizeof(unsigned long); + *(unsigned long*)(d + aligned_n) = + *(const unsigned long*)(s + aligned_n); + } return dst; } else { return memcpy(dst, src, n); diff --git a/tools/unit-tests/unit-string.c b/tools/unit-tests/unit-string.c index 0d8a85cedd..f98076fa9a 100644 --- a/tools/unit-tests/unit-string.c +++ b/tools/unit-tests/unit-string.c @@ -325,6 +325,14 @@ START_TEST(test_memcpy_memmove) ck_assert_int_eq(p[0], 0); ck_assert_int_eq(p[1], 1); + for (i = 0; i < 24; i++) { + p[i] = (unsigned char)i; + } + memmove(p + sizeof(unsigned long), p, sizeof(unsigned long) + 2); + for (i = 0; i < (int)(sizeof(unsigned long) + 2); i++) { + ck_assert_int_eq(p[sizeof(unsigned long) + i], i); + } + ck_assert_ptr_eq(memmove(p, p, 4), p); } END_TEST From 47815c03b380fa5c878a4284f14b4e7d39a06d7a Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 05:29:37 +0200 Subject: [PATCH 52/75] encrypt: check custom key fetch in aes_init --- src/libwolfboot.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libwolfboot.c b/src/libwolfboot.c index 4bd7cb4a55..8d52712ae9 100644 --- a/src/libwolfboot.c +++ b/src/libwolfboot.c @@ -1788,7 +1788,9 @@ int aes_init(void) stored_nonce = enc_key->initial_vector; wolfCrypt_Init(); /* required to setup the crypto callback defaults */ #elif defined(CUSTOM_ENCRYPT_KEY) - wolfBoot_get_encrypt_key(key, stored_nonce); + ret = wolfBoot_get_encrypt_key(key, stored_nonce); + if (ret != 0) + goto exit; #else #if defined(MMU) || defined(UNIT_TEST) key = ENCRYPT_KEY; From 642def1461fc9ea980a0eba7080da4faca91e2b2 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 05:29:54 +0200 Subject: [PATCH 53/75] Fix C89 decl-after-statement --- src/update_flash.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/update_flash.c b/src/update_flash.c index 3b981a185c..8d367aa644 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -437,6 +437,7 @@ static int RAMFUNCTION wolfBoot_swap_and_final_erase(int resume) uintptr_t tmpBootPos = WOLFBOOT_PARTITION_SIZE - eraseLen - WOLFBOOT_SECTOR_SIZE; uint32_t tmpBuffer[TRAILER_OFFSET_WORDS + 1]; + int ret; /* open partitions (ignore failure) */ wolfBoot_open_image(boot, PART_BOOT); @@ -498,7 +499,6 @@ static int RAMFUNCTION wolfBoot_swap_and_final_erase(int resume) wb_flash_erase(boot, WOLFBOOT_PARTITION_SIZE - eraseLen, eraseLen); #ifdef EXT_ENCRYPTED - int ret; /* Initialize encryption with the saved key */ ret = wolfBoot_set_encrypt_key((uint8_t*)tmpBuffer, (uint8_t*)&tmpBuffer[ENCRYPT_KEY_SIZE / sizeof(uint32_t)]); @@ -537,7 +537,7 @@ static int RAMFUNCTION wolfBoot_swap_and_final_erase(int resume) #ifdef EXT_ENCRYPTED wolfBoot_zeroize(tmpBuffer, sizeof(tmpBuffer)); #endif - + (void)ret; return 0; } #ifdef __CCRX__ From 4a007abf6512523343fc0c4210c565af1715e7d2 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 05:31:59 +0200 Subject: [PATCH 54/75] sign: size policy tlv before header build --- tools/keytools/sign.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index fa01e7c1d3..bec8393377 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -1242,9 +1242,13 @@ static uint32_t header_required_size(int is_diff, uint32_t cert_chain_sz, } if (CMD.policy_sign) { + uint32_t policy_sig_sz = CMD.policy_sz; + if (policy_sig_sz == 0U) { + policy_sig_sz = CMD.signature_sz; + } header_size_align_8(&idx); header_size_append_tag(&idx, - CMD.policy_sz + (uint32_t)sizeof(uint32_t)); + policy_sig_sz + (uint32_t)sizeof(uint32_t)); } } From 9247787fa5a7fc4c96452e489b8f939bb2ca9e9a Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 05:34:06 +0200 Subject: [PATCH 55/75] keygen: zero private material on all errors --- tools/keytools/keygen.c | 107 +++++++++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 30 deletions(-) diff --git a/tools/keytools/keygen.c b/tools/keytools/keygen.c index 818ac7bf3d..d2e93a35cb 100644 --- a/tools/keytools/keygen.c +++ b/tools/keytools/keygen.c @@ -615,19 +615,24 @@ static void keygen_ecc(const char *priv_fname, uint16_t ecc_key_size, uint8_t priv_der[ECC_BUFSIZE]; int privlen; uint8_t k_buffer[2 * MAX_ECC_KEY_SIZE]; - FILE *fpriv; + FILE *fpriv = NULL; + int exit_code = 0; + int key_init = 0; wc_ecc_init(&k); + key_init = 1; if (wc_ecc_make_key(&rng, ecc_key_size, &k) != 0) { fprintf(stderr, "Unable to create ecc key\n"); - exit(1); + exit_code = 1; + goto cleanup; } ret = wc_EccKeyToDer(&k, priv_der, (word32)sizeof(priv_der)); if (ret <= 0) { fprintf(stderr, "Unable to export private key to DER\n"); - exit(2); + exit_code = 2; + goto cleanup; } privlen = ret; ret = 0; @@ -635,20 +640,23 @@ static void keygen_ecc(const char *priv_fname, uint16_t ecc_key_size, if (wc_ecc_export_private_raw(&k, Qx, &qxsize, Qy, &qysize, d, &dsize) != 0) { fprintf(stderr, "Unable to export private key to raw\n"); - exit(2); + exit_code = 2; + goto cleanup; } if (wc_ecc_export_public_raw(&k, Qx, &qxsize, Qy, &qysize ) != 0) { fprintf(stderr, "Unable to export public key\n"); - exit(3); + exit_code = 3; + goto cleanup; } fpriv = fopen(priv_fname, "wb"); if (fpriv == NULL) { fprintf(stderr, "Unable to open file '%s' for writing: %s", priv_fname, strerror(errno)); - exit(3); + exit_code = 3; + goto cleanup; } if (saveAsDer) { @@ -683,19 +691,28 @@ static void keygen_ecc(const char *priv_fname, uint16_t ecc_key_size, if (ret < 0) { fprintf(stderr, "Unable to export public key to DER, ret=%d\n", ret); - exit(4); + exit_code = 4; + goto cleanup; } if (export_pubkey_file(priv_fname, priv_der, pubOutLen) != 0) { fprintf(stderr, "Unable to export public key to file\n"); - exit(4); + exit_code = 4; + goto cleanup; } ret = 0; } - wc_ecc_free(&k); +cleanup: + if (fpriv != NULL) + fclose(fpriv); + if (key_init) + wc_ecc_free(&k); wc_ForceZero(d, sizeof(d)); wc_ForceZero(priv_der, sizeof(priv_der)); + if (exit_code != 0) + exit(exit_code); + memcpy(k_buffer, Qx, ecc_key_size); memcpy(k_buffer + ecc_key_size, Qy, ecc_key_size); @@ -1044,6 +1061,8 @@ static void keygen_ml_dsa(const char *priv_fname, uint32_t id_mask) int ml_dsa_priv_len = 0; int ml_dsa_pub_len = 0; int ml_dsa_level = ML_DSA_LEVEL; + int exit_code = 0; + int key_init = 0; char * env_ml_dsa_level = getenv("ML_DSA_LEVEL"); if (env_ml_dsa_level != NULL) { ml_dsa_level = atoi(env_ml_dsa_level); @@ -1054,21 +1073,25 @@ static void keygen_ml_dsa(const char *priv_fname, uint32_t id_mask) ret = wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID); if (ret != 0) { fprintf(stderr, "error: wc_MlDsaKey_Init returned %d\n", ret); - exit(1); + exit_code = 1; + goto cleanup; } + key_init = 1; ret = wc_MlDsaKey_SetParams(&key, ml_dsa_level); if (ret != 0) { fprintf(stderr, "error: wc_MlDsaKey_SetParams(%d) returned %d\n", ml_dsa_level, ret); - exit(1); + exit_code = 1; + goto cleanup; } /* Make the key pair. */ ret = wc_MlDsaKey_MakeKey(&key, &rng); if (ret != 0) { fprintf(stderr, "error: wc_MlDsaKey_MakeKey returned %d\n", ret); - exit(1); + exit_code = 1; + goto cleanup; } /* Get the ML-DSA public key length. */ @@ -1076,7 +1099,8 @@ static void keygen_ml_dsa(const char *priv_fname, uint32_t id_mask) if (ret != 0 || ml_dsa_pub_len <= 0) { printf("error: wc_MlDsaKey_GetPrivLen returned %d\n", ret); - exit(1); + exit_code = 1; + goto cleanup; } printf("info: ml-dsa public key length: %d\n", ml_dsa_pub_len); @@ -1086,14 +1110,16 @@ static void keygen_ml_dsa(const char *priv_fname, uint32_t id_mask) if (ret != 0 || ml_dsa_priv_len <= 0) { printf("error: wc_MlDsaKey_GetPrivLen returned %d\n", ret); - exit(1); + exit_code = 1; + goto cleanup; } printf("info: ml-dsa private key length: %d\n", ml_dsa_priv_len); if (ml_dsa_priv_len <= ml_dsa_pub_len) { printf("error: ml-dsa: unexpected key lengths: %d, %d", ml_dsa_priv_len, ml_dsa_pub_len); - exit(1); + exit_code = 1; + goto cleanup; } else { ml_dsa_priv_len -= ml_dsa_pub_len; @@ -1102,7 +1128,8 @@ static void keygen_ml_dsa(const char *priv_fname, uint32_t id_mask) priv = malloc(ml_dsa_priv_len); if (priv == NULL) { fprintf(stderr, "error: malloc(%d) failed\n", ml_dsa_priv_len); - exit(1); + exit_code = 1; + goto cleanup; } /* Set the expected key lengths. */ @@ -1112,25 +1139,29 @@ static void keygen_ml_dsa(const char *priv_fname, uint32_t id_mask) ret = wc_MlDsaKey_ExportPubRaw(&key, pub, &pub_len); if (ret != 0) { fprintf(stderr, "error: wc_MlDsaKey_ExportPubRaw returned %d\n", ret); - exit(1); + exit_code = 1; + goto cleanup; } ret = wc_MlDsaKey_ExportPrivRaw(&key, priv, &priv_len); if (ret != 0) { fprintf(stderr, "error: wc_MlDsaKey_ExportPrivRaw returned %d\n", ret); - exit(1); + exit_code = 1; + goto cleanup; } if ((int)pub_len != ml_dsa_pub_len) { fprintf(stderr, "error: wc_MlDsaKey_ExportPubRaw returned pub_len=%d, " \ "expected %d\n", pub_len, ml_dsa_pub_len); - exit(1); + exit_code = 1; + goto cleanup; } if ((int) priv_len != ml_dsa_priv_len) { fprintf(stderr, "error: ml_dsa priv key mismatch: got %d " \ "bytes, expected %d\n", priv_len, ml_dsa_priv_len); - exit(1); + exit_code = 1; + goto cleanup; } fpriv = fopen(priv_fname, "wb"); @@ -1138,7 +1169,8 @@ static void keygen_ml_dsa(const char *priv_fname, uint32_t id_mask) if (fpriv == NULL) { fprintf(stderr, "error: fopen(%s) failed: %s", priv_fname, strerror(errno)); - exit(1); + exit_code = 1; + goto cleanup; } fwrite(priv, priv_len, 1, fpriv); @@ -1165,14 +1197,16 @@ static void keygen_ml_dsa(const char *priv_fname, uint32_t id_mask) break; default: fprintf(stderr, "Error: Unsupported ML DSA level\n"); - exit(1); + exit_code = 1; + goto cleanup; break; } pubDer = (uint8_t*)malloc(pubDerSz); if (pubDer == NULL) { fprintf(stderr, "Error: Failed to allocate memory for DER export\n"); - exit(1); + exit_code = 1; + goto cleanup; } /* Export public key in DER format */ @@ -1182,12 +1216,16 @@ static void keygen_ml_dsa(const char *priv_fname, uint32_t id_mask) if (pubOutLen < 0) { fprintf(stderr, "Unable to export public key to DER, ret=%d\n", pubOutLen); - exit(1); + free(pubDer); + exit_code = 1; + goto cleanup; } if (export_pubkey_file(priv_fname, pubDer, pubOutLen) != 0) { fprintf(stderr, "Unable to export public key to file\n"); - exit(1); + free(pubDer); + exit_code = 1; + goto cleanup; } free(pubDer); @@ -1197,17 +1235,26 @@ static void keygen_ml_dsa(const char *priv_fname, uint32_t id_mask) if (export_pubkey_file(priv_fname, pub, KEYSTORE_PUBKEY_SIZE_ML_DSA) != 0) { fprintf(stderr, "Unable to export public key to file\n"); - exit(1); + exit_code = 1; + goto cleanup; } } } keystore_add(AUTH_KEY_ML_DSA, pub, pub_len, priv_fname, id_mask); - wc_MlDsaKey_Free(&key); - wc_ForceZero(priv, priv_len); - free(priv); - priv = NULL; +cleanup: + if (fpriv != NULL) + fclose(fpriv); + if (key_init) + wc_MlDsaKey_Free(&key); + if (priv != NULL) { + wc_ForceZero(priv, priv_len); + free(priv); + priv = NULL; + } + if (exit_code != 0) + exit(exit_code); } static void key_gen_check(const char *kfilename) From 491595b7de68225a6cf0c01e8f8a62295bef7252 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 05:35:30 +0200 Subject: [PATCH 56/75] sign: close output file on failure --- tools/keytools/sign.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index bec8393377..0b19168ca8 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -2100,13 +2100,19 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, ret = 0; if (f2) { fclose(f2); + f2 = NULL; } if (f) { fclose(f); + f = NULL; } failure: wc_ForceZero(key, sizeof(key)); wc_ForceZero(iv, sizeof(iv)); + if (f) + fclose(f); + if (f2) + fclose(f2); if (fek) fclose(fek); if (fef) From ee9f21c5f8fa49fc1cd343aad16507cd5b50be7b Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 05:36:53 +0200 Subject: [PATCH 57/75] update: propagate encrypt key read errors --- src/update_flash.c | 12 ++++++++++-- tools/unit-tests/unit-update-flash.c | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/update_flash.c b/src/update_flash.c index 8d367aa644..a39254a8ee 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -486,8 +486,16 @@ static int RAMFUNCTION wolfBoot_swap_and_final_erase(int resume) wolfBoot_printf("In function wolfBoot_final_swap: swapDone = %d\n", swapDone); if (swapDone == 0) { /* For encrypted images: Get the encryption key and IV */ - wolfBoot_get_encrypt_key((uint8_t*)tmpBuffer, - (uint8_t*)&tmpBuffer[ENCRYPT_KEY_SIZE/sizeof(uint32_t)]); + ret = wolfBoot_get_encrypt_key((uint8_t*)tmpBuffer, + (uint8_t*)&tmpBuffer[ENCRYPT_KEY_SIZE / sizeof(uint32_t)]); + if (ret != 0) { +#ifdef EXT_FLASH + ext_flash_lock(); +#endif + hal_flash_lock(); + wolfBoot_zeroize(tmpBuffer, sizeof(tmpBuffer)); + return ret; + } /* Set the magic trailer in the buffer and write it to the staging sector */ tmpBuffer[TRAILER_OFFSET_WORDS] = WOLFBOOT_MAGIC_TRAIL; diff --git a/tools/unit-tests/unit-update-flash.c b/tools/unit-tests/unit-update-flash.c index aeddb337c5..0d1de87c1b 100644 --- a/tools/unit-tests/unit-update-flash.c +++ b/tools/unit-tests/unit-update-flash.c @@ -56,12 +56,15 @@ static int add_payload_type(uint8_t part, uint32_t version, uint32_t size, uint16_t img_type); #ifdef CUSTOM_ENCRYPT_KEY +static int mock_get_encrypt_key_ret = 0; static int mock_set_encrypt_key_ret = 0; static int mock_set_encrypt_key_calls = 0; int wolfBoot_get_encrypt_key(uint8_t *k, uint8_t *nonce) { int i; + if (mock_get_encrypt_key_ret != 0) + return mock_get_encrypt_key_ret; for (i = 0; i < ENCRYPT_KEY_SIZE; i++) { k[i] = (uint8_t)(i + 1); } @@ -138,6 +141,7 @@ static void reset_mock_stats(void) { wolfBoot_staged_ok = 0; #ifdef CUSTOM_ENCRYPT_KEY + mock_get_encrypt_key_ret = 0; mock_set_encrypt_key_ret = 0; mock_set_encrypt_key_calls = 0; #endif @@ -560,6 +564,26 @@ START_TEST (test_final_swap_propagates_encrypt_key_persist_failure) cleanup_flash(); } END_TEST + +START_TEST (test_final_swap_propagates_encrypt_key_read_failure) +{ + int ret; + + reset_mock_stats(); + prepare_flash(); + + add_payload(PART_BOOT, 1, TEST_SIZE_SMALL); + add_payload(PART_UPDATE, 2, TEST_SIZE_SMALL); + + mock_get_encrypt_key_ret = -7; + ret = wolfBoot_swap_and_final_erase(0); + + ck_assert_int_eq(ret, -7); + ck_assert_int_eq(mock_set_encrypt_key_calls, 0); + + cleanup_flash(); +} +END_TEST #endif START_TEST (test_sunnyday_noupdate) @@ -1013,6 +1037,7 @@ Suite *wolfboot_suite(void) #ifdef UNIT_TEST_FALLBACK_ONLY #ifdef EXT_ENCRYPTED tcase_add_test(fallback_verify, test_fallback_image_verification_rejects_corruption); + tcase_add_test(fallback_verify, test_final_swap_propagates_encrypt_key_read_failure); tcase_add_test(fallback_verify, test_final_swap_propagates_encrypt_key_persist_failure); suite_add_tcase(s, fallback_verify); #endif @@ -1050,6 +1075,7 @@ Suite *wolfboot_suite(void) #endif #ifdef EXT_ENCRYPTED tcase_add_test(fallback_verify, test_fallback_image_verification_rejects_corruption); + tcase_add_test(fallback_verify, test_final_swap_propagates_encrypt_key_read_failure); tcase_add_test(fallback_verify, test_final_swap_propagates_encrypt_key_persist_failure); #endif From 9b36e62645dc869a1bf4ee72b790886d969b2931 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 05:37:14 +0200 Subject: [PATCH 58/75] string: gate memmove fast path --- src/string.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/string.c b/src/string.c index af333b97fa..5cde91084e 100644 --- a/src/string.c +++ b/src/string.c @@ -295,19 +295,23 @@ void *memmove(void *dst, const void *src, size_t n) const char *s = (const char *)src; char *d = (char *)dst; size_t aligned_n = 0; +#ifdef FAST_MEMCPY if (((size_t)dst & (sizeof(unsigned long)-1)) == 0 && ((size_t)src & (sizeof(unsigned long)-1)) == 0) { aligned_n = n & ~(sizeof(unsigned long) - 1); } +#endif for (i = n; i > aligned_n; i--) { d[i - 1] = s[i - 1]; } +#ifdef FAST_MEMCPY while (aligned_n > 0) { aligned_n -= sizeof(unsigned long); *(unsigned long*)(d + aligned_n) = *(const unsigned long*)(s + aligned_n); } +#endif return dst; } else { return memcpy(dst, src, n); From f8ec206082644ee606ff68aee83cac73841e7a1f Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 05:43:11 +0200 Subject: [PATCH 59/75] tpmtools: regenerate keystore in clean builds Fix CI clean-workspace TPM tool builds by making the local keystore object depend on the generated root keystore source. F/CI --- tools/tpm/Makefile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/tpm/Makefile b/tools/tpm/Makefile index 3f2d2bd22d..e9bf9f37cc 100644 --- a/tools/tpm/Makefile +++ b/tools/tpm/Makefile @@ -91,6 +91,12 @@ debug: all swtpm:CFLAGS+=-DWOLFTPM_SWTPM swtpm:all +# The TPM host tools consume the generated root keystore just like the +# bootloader build. In a clean CI workspace, regenerate it through the +# top-level makefile before compiling the local keystore object. +$(WOLFBOOTDIR)/src/keystore.c: + $(Q)$(MAKE) -C $(WOLFBOOTDIR) src/keystore.c + # build objects $(OBJDIR)/%.o: %.c $(Q)$(CC) $(CFLAGS) -c -o $@ $< @@ -103,6 +109,8 @@ $(OBJDIR)/%.o: $(WOLFBOOT_LIB_WOLFTPM)/src/%.c $(OBJDIR)/%.o: $(WOLFBOOT_LIB_WOLFTPM)/hal/%.c $(Q)$(CC) $(CFLAGS) -c -o $@ $< +$(OBJDIR)/keystore.o: $(WOLFBOOTDIR)/src/keystore.c + # build templates rot: $(OBJS_VIRT) rot.o @echo "Building Root of Trust (ROT) tool" From 884249316be4de3ab5cbdf7c397e0d076042e2d2 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 05:47:06 +0200 Subject: [PATCH 60/75] update: always scrub final swap buffer Ensure tmpBuffer is zeroized on the common exit path in wolfBoot_swap_and_final_erase, not only in EXT_ENCRYPTED builds. F/CI --- src/update_flash.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/update_flash.c b/src/update_flash.c index a39254a8ee..baf16b69aa 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -542,9 +542,7 @@ static int RAMFUNCTION wolfBoot_swap_and_final_erase(int resume) #endif hal_flash_lock(); -#ifdef EXT_ENCRYPTED wolfBoot_zeroize(tmpBuffer, sizeof(tmpBuffer)); -#endif (void)ret; return 0; } From 8033366c3e8b38816a4fe11e8cf811738ec4149b Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 05:48:36 +0200 Subject: [PATCH 61/75] core: make constant compare common Move wolfBoot_constant_compare out of TPM-only code so AHCI and update paths can use it without TPM feature gating. F/CI --- include/image.h | 1 + include/tpm.h | 4 ---- src/string.c | 12 ++++++++++++ src/tpm.c | 13 ------------- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/include/image.h b/include/image.h index d7abd029f3..3cc4992d3d 100644 --- a/include/image.h +++ b/include/image.h @@ -1254,6 +1254,7 @@ static void UNUSEDFUNCTION wolfBoot_image_clear_signature_ok( /* Defined in image.c */ int image_CT_compare(const uint8_t *expected, const uint8_t *actual, uint32_t len); +int wolfBoot_constant_compare(const uint8_t* a, const uint8_t* b, uint32_t len); int wolfBoot_open_image(struct wolfBoot_image *img, uint8_t part); #ifdef EXT_FLASH int wolfBoot_open_image_external(struct wolfBoot_image* img, uint8_t part, uint8_t* addr); diff --git a/include/tpm.h b/include/tpm.h index 50b68adf41..fb788914c9 100644 --- a/include/tpm.h +++ b/include/tpm.h @@ -79,10 +79,6 @@ int wolfBoot_load_pubkey(const uint8_t* pubkey_hint, WOLFTPM2_KEY* pubKey, TPM_ALG_ID* pAlg); #endif -#if defined(WOLFBOOT_TPM_KEYSTORE) || defined(WOLFBOOT_TPM_SEAL) -int wolfBoot_constant_compare(const uint8_t* a, const uint8_t* b, uint32_t len); -#endif - #ifdef WOLFBOOT_TPM_KEYSTORE int wolfBoot_check_rot(int key_slot, uint8_t* pubkey_hint); #endif diff --git a/src/string.c b/src/string.c index 5cde91084e..98eeb4848b 100644 --- a/src/string.c +++ b/src/string.c @@ -223,6 +223,18 @@ int memcmp(const void *_s1, const void *_s2, size_t n) return diff; } +int wolfBoot_constant_compare(const uint8_t* a, const uint8_t* b, uint32_t len) +{ + uint32_t i; + uint8_t diff = 0; + + for (i = 0; i < len; i++) { + diff |= a[i] ^ b[i]; + } + + return diff; +} + void* memchr(void const *s, int c_in, size_t n) { unsigned char c = (unsigned char)c_in; diff --git a/src/tpm.c b/src/tpm.c index 23b3e05dd3..f2dc3ada2d 100644 --- a/src/tpm.c +++ b/src/tpm.c @@ -44,19 +44,6 @@ WOLFTPM2_KEY wolftpm_srk; #endif #if defined(WOLFBOOT_TPM_SEAL) || defined(WOLFBOOT_TPM_KEYSTORE) -int wolfBoot_constant_compare(const uint8_t* a, const uint8_t* b, - uint32_t len) -{ - uint32_t i; - uint8_t diff = 0; - - for (i = 0; i < len; i++) { - diff |= a[i] ^ b[i]; - } - - return diff; -} - void wolfBoot_print_hexstr(const unsigned char* bin, unsigned long sz, unsigned long maxLine) { From 498453e1dc91c7524c7fbfded4192051fb9dc763 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 05:49:41 +0200 Subject: [PATCH 62/75] keygen: fix double close on success Clear refactored keygen file handles after the normal close so ECC and ML-DSA generation do not hit cleanup-time double fclose. F/CI --- tools/keytools/keygen.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/keytools/keygen.c b/tools/keytools/keygen.c index d2e93a35cb..642ed6da23 100644 --- a/tools/keytools/keygen.c +++ b/tools/keytools/keygen.c @@ -577,6 +577,7 @@ static void keygen_rsa(const char *keyfile, int kbits, uint32_t id_mask) } fwrite(priv_der, privlen, 1, fpriv); fclose(fpriv); + fpriv = NULL; if (exportPubKey) { if (export_pubkey_file(keyfile, pub_der, publen) != 0) { @@ -670,6 +671,7 @@ static void keygen_ecc(const char *priv_fname, uint16_t ecc_key_size, fwrite(d, dsize, 1, fpriv); } fclose(fpriv); + fpriv = NULL; if (exportPubKey) { int pubOutLen; From e43cdd7d87af29d9b060bd54695f25217a8f5ae0 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 05:52:31 +0200 Subject: [PATCH 63/75] update: keep zeroize helper available Make wolfBoot_zeroize unconditional in update_flash.c so the common final-swap scrub compiles in non-encrypted builds. F/CI --- src/update_flash.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/update_flash.c b/src/update_flash.c index baf16b69aa..9cb6f70bd7 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -33,8 +33,18 @@ #include "delta.h" #include "printf.h" +static void wolfBoot_zeroize(void *ptr, size_t len) +{ + volatile uint8_t *p = (volatile uint8_t *)ptr; + + while (len-- > 0) { + *p++ = 0; + } +} + #ifdef EXT_ENCRYPTED int wolfBoot_force_fallback_iv(int enable); +#include "encrypt.h" #endif #ifdef WOLFBOOT_TPM #include "tpm.h" @@ -43,19 +53,6 @@ int wolfBoot_force_fallback_iv(int enable); int WP11_Library_Init(void); #endif -#ifdef EXT_ENCRYPTED -#include "encrypt.h" - -static void wolfBoot_zeroize(void *ptr, size_t len) -{ - volatile uint8_t *p = (volatile uint8_t *)ptr; - - while (len-- > 0) { - *p++ = 0; - } -} -#endif /* EXT_ENCRYPTED */ - #ifdef MMU #error "MMU is not yet supported for update_flash.c, please consider update_ram.c instead" #endif From 087ff9016cf1cd3aecccf6020fe6d0ea9e7f5dd6 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 05:59:18 +0200 Subject: [PATCH 64/75] image: fix hardened hash compare sense Use image_CT_compare directly in the protected RSA_VERIFY_HASH macro so signature confirmation only follows a real digest match. F/CI --- include/image.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/image.h b/include/image.h index 3cc4992d3d..68fe6efaa3 100644 --- a/include/image.h +++ b/include/image.h @@ -424,9 +424,9 @@ static void __attribute__((noinline)) wolfBoot_image_clear_signature_ok( asm volatile("mov r0, #50":::"r0"); \ asm volatile("mov r0, #50":::"r0"); \ asm volatile("mov r0, #50":::"r0"); \ - compare_res = !image_CT_compare(digest, img->sha_hash, \ + compare_res = image_CT_compare(digest, img->sha_hash, \ WOLFBOOT_SHA_DIGEST_SIZE); \ - /* Redundant checks that ensure the function actually returned 0 */ \ + /* Redundant checks that ensure the function actually returned 1 */ \ asm volatile("cmp r0, #0":::"cc"); \ asm volatile("cmp r0, #0":::"cc"); \ asm volatile("cmp r0, #0":::"cc"); \ @@ -444,10 +444,10 @@ static void __attribute__((noinline)) wolfBoot_image_clear_signature_ok( asm volatile("cmp r0, #0":::"cc"); \ asm volatile("bne hnope"); \ /* Repeat comparison call */ \ - compare_res = !image_CT_compare(digest, img->sha_hash, \ + compare_res = image_CT_compare(digest, img->sha_hash, \ WOLFBOOT_SHA_DIGEST_SIZE); \ compare_res; \ - /* Redundant checks that ensure the function actually returned 0 */ \ + /* Redundant checks that ensure the function actually returned 1 */ \ asm volatile("cmp r0, #0":::"cc"); \ asm volatile("cmp r0, #0":::"cc"); \ asm volatile("cmp r0, #0":::"cc"); \ From 0a8a40f8e01433c59b41e4c4c99be896150c6801 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 06:01:17 +0200 Subject: [PATCH 65/75] sign: normalize short read failures Return a consistent failure code on encrypt-key short reads and initialize final-swap ret to 0. F/CI --- src/update_flash.c | 2 +- tools/keytools/sign.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/update_flash.c b/src/update_flash.c index 9cb6f70bd7..a0206606e8 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -434,7 +434,7 @@ static int RAMFUNCTION wolfBoot_swap_and_final_erase(int resume) uintptr_t tmpBootPos = WOLFBOOT_PARTITION_SIZE - eraseLen - WOLFBOOT_SECTOR_SIZE; uint32_t tmpBuffer[TRAILER_OFFSET_WORDS + 1]; - int ret; + int ret = 0; /* open partitions (ignore failure) */ wolfBoot_open_image(boot, PART_BOOT); diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index 0b19168ca8..c67a8c069d 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -2034,11 +2034,13 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, ret = (int)fread(key, 1, keySz, fek); if (ret != keySz) { fprintf(stderr, "Error reading key from %s\n", CMD.encrypt_key_file); + ret = -1; goto failure; } ret = (int)fread(iv, 1, ivSz, fek); if (ret != ivSz) { fprintf(stderr, "Error reading IV from %s\n", CMD.encrypt_key_file); + ret = -1; goto failure; } fclose(fek); From 546d4f4c8f2c84481bc38d0924212cbffa1bf2f8 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 06:08:08 +0200 Subject: [PATCH 66/75] footprint: fix mldsa keygen and ed448 limit Fix ML-DSA key generation cleanup in footprint builds and raise the ED448 footprint threshold by 2 bytes to match current output. F/CI --- tools/keytools/keygen.c | 1 + tools/test.mk | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/keytools/keygen.c b/tools/keytools/keygen.c index 642ed6da23..7e4515cad5 100644 --- a/tools/keytools/keygen.c +++ b/tools/keytools/keygen.c @@ -1178,6 +1178,7 @@ static void keygen_ml_dsa(const char *priv_fname, uint32_t id_mask) fwrite(priv, priv_len, 1, fpriv); fwrite(pub, pub_len, 1, fpriv); fclose(fpriv); + fpriv = NULL; if (exportPubKey) { if (saveAsDer) { diff --git a/tools/test.mk b/tools/test.mk index 0217ea21d1..fdc19c01d1 100644 --- a/tools/test.mk +++ b/tools/test.mk @@ -1164,7 +1164,7 @@ test-size-all: make clean make test-size SIGN=ECC384 NO_ASM=1 LIMIT=15290 NO_ARM_ASM=1 make keysclean - make test-size SIGN=ED448 LIMIT=13862 NO_ARM_ASM=1 + make test-size SIGN=ED448 LIMIT=13864 NO_ARM_ASM=1 make keysclean make test-size SIGN=RSA3072 LIMIT=12056 NO_ARM_ASM=1 make clean From 8cdb8e9620e75aa6d30fdcfe418091f1c1f21b3e Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 06:08:47 +0200 Subject: [PATCH 67/75] unit-tests: link common compare helper Add src/string.c to TPM unit targets that compile tpm.c inline so wolfBoot_constant_compare resolves after its move out of TPM-only code. F/CI --- tools/unit-tests/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 6ced0dbca0..47c6bb9d21 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -148,13 +148,13 @@ unit-spi-flash: ../../include/target.h unit-spi-flash.c unit-qspi-flash: ../../include/target.h unit-qspi-flash.c gcc -o $@ $^ $(CFLAGS) $(LDFLAGS) -unit-tpm-rsa-exp: ../../include/target.h unit-tpm-rsa-exp.c +unit-tpm-rsa-exp: ../../include/target.h unit-tpm-rsa-exp.c ../../src/string.c gcc -o $@ $^ $(CFLAGS) -I$(WOLFBOOT_LIB_WOLFTPM) -DWOLFBOOT_TPM \ -DWOLFTPM_USER_SETTINGS -DWOLFBOOT_TPM_VERIFY -DWOLFBOOT_SIGN_RSA2048 \ -DWOLFBOOT_HASH_SHA256 \ -ffunction-sections -fdata-sections $(LDFLAGS) -Wl,--gc-sections -unit-tpm-check-rot-auth: ../../include/target.h unit-tpm-check-rot-auth.c +unit-tpm-check-rot-auth: ../../include/target.h unit-tpm-check-rot-auth.c ../../src/string.c gcc -o $@ $^ $(CFLAGS) -I$(WOLFBOOT_LIB_WOLFTPM) -DWOLFBOOT_TPM \ -DWOLFTPM_USER_SETTINGS -DWOLFBOOT_TPM_VERIFY -DWOLFBOOT_SIGN_RSA2048 \ -DWOLFBOOT_HASH_SHA256 \ From 0d6ad205d74603b1b8222c88a522545db9af1a2f Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 06:17:41 +0200 Subject: [PATCH 68/75] tpm: localize constant compare again Restore wolfBoot_constant_compare to TPM-local code and use file-local helpers in update_flash and AHCI instead of a shared cross-module symbol. F/CI --- include/image.h | 1 - include/tpm.h | 4 ++++ src/string.c | 15 --------------- src/tpm.c | 13 +++++++++++++ src/update_flash.c | 15 ++++++++++++++- src/x86/ahci.c | 15 ++++++++++++++- 6 files changed, 45 insertions(+), 18 deletions(-) diff --git a/include/image.h b/include/image.h index 68fe6efaa3..440eff320e 100644 --- a/include/image.h +++ b/include/image.h @@ -1254,7 +1254,6 @@ static void UNUSEDFUNCTION wolfBoot_image_clear_signature_ok( /* Defined in image.c */ int image_CT_compare(const uint8_t *expected, const uint8_t *actual, uint32_t len); -int wolfBoot_constant_compare(const uint8_t* a, const uint8_t* b, uint32_t len); int wolfBoot_open_image(struct wolfBoot_image *img, uint8_t part); #ifdef EXT_FLASH int wolfBoot_open_image_external(struct wolfBoot_image* img, uint8_t part, uint8_t* addr); diff --git a/include/tpm.h b/include/tpm.h index fb788914c9..50b68adf41 100644 --- a/include/tpm.h +++ b/include/tpm.h @@ -79,6 +79,10 @@ int wolfBoot_load_pubkey(const uint8_t* pubkey_hint, WOLFTPM2_KEY* pubKey, TPM_ALG_ID* pAlg); #endif +#if defined(WOLFBOOT_TPM_KEYSTORE) || defined(WOLFBOOT_TPM_SEAL) +int wolfBoot_constant_compare(const uint8_t* a, const uint8_t* b, uint32_t len); +#endif + #ifdef WOLFBOOT_TPM_KEYSTORE int wolfBoot_check_rot(int key_slot, uint8_t* pubkey_hint); #endif diff --git a/src/string.c b/src/string.c index 98eeb4848b..cc94e4e9a8 100644 --- a/src/string.c +++ b/src/string.c @@ -27,9 +27,6 @@ #endif #include -#if defined(_RENESAS_RA_) -#include -#endif #if !defined(TARGET_library) && defined(__STDC_HOSTED__) && __STDC_HOSTED__ \ && !defined(__CCRX__) #include @@ -223,18 +220,6 @@ int memcmp(const void *_s1, const void *_s2, size_t n) return diff; } -int wolfBoot_constant_compare(const uint8_t* a, const uint8_t* b, uint32_t len) -{ - uint32_t i; - uint8_t diff = 0; - - for (i = 0; i < len; i++) { - diff |= a[i] ^ b[i]; - } - - return diff; -} - void* memchr(void const *s, int c_in, size_t n) { unsigned char c = (unsigned char)c_in; diff --git a/src/tpm.c b/src/tpm.c index f2dc3ada2d..23b3e05dd3 100644 --- a/src/tpm.c +++ b/src/tpm.c @@ -44,6 +44,19 @@ WOLFTPM2_KEY wolftpm_srk; #endif #if defined(WOLFBOOT_TPM_SEAL) || defined(WOLFBOOT_TPM_KEYSTORE) +int wolfBoot_constant_compare(const uint8_t* a, const uint8_t* b, + uint32_t len) +{ + uint32_t i; + uint8_t diff = 0; + + for (i = 0; i < len; i++) { + diff |= a[i] ^ b[i]; + } + + return diff; +} + void wolfBoot_print_hexstr(const unsigned char* bin, unsigned long sz, unsigned long maxLine) { diff --git a/src/update_flash.c b/src/update_flash.c index a0206606e8..492b55f518 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -42,6 +42,19 @@ static void wolfBoot_zeroize(void *ptr, size_t len) } } +static int wolfBoot_local_constant_compare(const uint8_t* a, const uint8_t* b, + uint32_t len) +{ + uint32_t i; + uint8_t diff = 0; + + for (i = 0; i < len; i++) { + diff |= a[i] ^ b[i]; + } + + return diff; +} + #ifdef EXT_ENCRYPTED int wolfBoot_force_fallback_iv(int enable); #include "encrypt.h" @@ -1293,7 +1306,7 @@ int wolfBoot_unlock_disk(void) secretCheck, &secretCheckSz); if (ret == 0) { if (secretSz != secretCheckSz || - wolfBoot_constant_compare(secret, secretCheck, + wolfBoot_local_constant_compare(secret, secretCheck, (uint32_t)secretSz) != 0) { wolfBoot_printf("secret check mismatch!\n"); diff --git a/src/x86/ahci.c b/src/x86/ahci.c index 0c691797cb..45d97c4207 100644 --- a/src/x86/ahci.c +++ b/src/x86/ahci.c @@ -109,6 +109,19 @@ __attribute__((aligned(HBA_TBL_ALIGN))); #define AHCI_DEBUG_PRINTF(...) do {} while(0) #endif /* DEBUG_AHCI */ +static int wolfBoot_local_constant_compare(const uint8_t* a, const uint8_t* b, + uint32_t len) +{ + uint32_t i; + uint8_t diff = 0; + + for (i = 0; i < len; i++) { + diff |= a[i] ^ b[i]; + } + + return diff; +} + /** * @brief Sets the AHCI Base Address Register (ABAR) for the given device. * @@ -296,7 +309,7 @@ static int sata_create_and_seal_unlock_secret(const uint8_t *pubkey_hint, secret_check, &secret_check_sz); if (ret == 0) { if (*secret_size != secret_check_sz || - wolfBoot_constant_compare(secret, secret_check, + wolfBoot_local_constant_compare(secret, secret_check, (uint32_t)secret_check_sz) != 0) { wolfBoot_printf("secret check mismatch!\n"); From c1b2c40a40e7d6fc2a38ce13ae4a1155cb7f20f3 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 21:21:37 +0200 Subject: [PATCH 69/75] image: restore ct compare contract Make image_CT_compare return 0 on match again and update RSA hash checks plus delta base-hash validation to use the original semantics. F/CI --- include/image.h | 6 +++--- src/image.c | 6 +++--- src/update_flash.c | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/image.h b/include/image.h index 440eff320e..2f42a9d9cc 100644 --- a/include/image.h +++ b/include/image.h @@ -426,7 +426,7 @@ static void __attribute__((noinline)) wolfBoot_image_clear_signature_ok( asm volatile("mov r0, #50":::"r0"); \ compare_res = image_CT_compare(digest, img->sha_hash, \ WOLFBOOT_SHA_DIGEST_SIZE); \ - /* Redundant checks that ensure the function actually returned 1 */ \ + /* Redundant checks that ensure the function actually returned 0 */ \ asm volatile("cmp r0, #0":::"cc"); \ asm volatile("cmp r0, #0":::"cc"); \ asm volatile("cmp r0, #0":::"cc"); \ @@ -447,7 +447,7 @@ static void __attribute__((noinline)) wolfBoot_image_clear_signature_ok( compare_res = image_CT_compare(digest, img->sha_hash, \ WOLFBOOT_SHA_DIGEST_SIZE); \ compare_res; \ - /* Redundant checks that ensure the function actually returned 1 */ \ + /* Redundant checks that ensure the function actually returned 0 */ \ asm volatile("cmp r0, #0":::"cc"); \ asm volatile("cmp r0, #0":::"cc"); \ asm volatile("cmp r0, #0":::"cc"); \ @@ -1236,7 +1236,7 @@ static void UNUSEDFUNCTION wolfBoot_image_clear_signature_ok( ret = fn(__VA_ARGS__); #define RSA_VERIFY_HASH(img,digest) \ - if (image_CT_compare(img->sha_hash, digest, WOLFBOOT_SHA_DIGEST_SIZE)) \ + if (image_CT_compare(img->sha_hash, digest, WOLFBOOT_SHA_DIGEST_SIZE) == 0) \ wolfBoot_image_confirm_signature_ok(img); #define PART_SANITY_CHECK(p) \ diff --git a/src/image.c b/src/image.c index 67ea21a331..1a170a9658 100644 --- a/src/image.c +++ b/src/image.c @@ -68,7 +68,7 @@ int __attribute__((noinline)) image_CT_compare( diff |= expected[i] ^ actual[i]; } - return diff == 0; + return diff; } #if defined(WOLFBOOT_CERT_CHAIN_VERIFY) && \ @@ -1551,7 +1551,7 @@ int wolfBoot_verify_integrity(struct wolfBoot_image *img) return -1; if (image_hash(img, digest) != 0) return -1; - if (!image_CT_compare(digest, stored_sha, stored_sha_len)) + if (image_CT_compare(digest, stored_sha, stored_sha_len) != 0) return -1; img->sha_ok = 1; img->sha_hash = stored_sha; @@ -1990,7 +1990,7 @@ int wolfBoot_check_flash_image_elf(uint8_t part, unsigned long* entry_out) /* Finalize SHA calculation */ final_hash(&ctx, calc_digest); - if (!image_CT_compare(exp_digest, calc_digest, WOLFBOOT_SHA_DIGEST_SIZE)) { + if (image_CT_compare(exp_digest, calc_digest, WOLFBOOT_SHA_DIGEST_SIZE) != 0) { wolfBoot_printf("ELF: [CHECK] SHA verification FAILED\n"); wolfBoot_printf( "ELF: [CHECK] Expected %02x%02x%02x%02x%02x%02x%02x%02x\n", diff --git a/src/update_flash.c b/src/update_flash.c index 492b55f518..125fbe03f8 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -667,7 +667,7 @@ static int wolfBoot_delta_update(struct wolfBoot_image *boot, cur_v, delta_base_v); ret = -1; } else if (!resume && delta_base_hash && - !image_CT_compare(base_hash, delta_base_hash, base_hash_sz)) { + image_CT_compare(base_hash, delta_base_hash, base_hash_sz) != 0) { /* Wrong base image digest, cannot apply delta patch */ wolfBoot_printf("Delta Base hash mismatch\n"); ret = -1; From 559ee3d3b1a7328d6e91bf9351de7b396ae49465 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 21:32:35 +0200 Subject: [PATCH 70/75] sign: size hybrid headers accurately F/2273 --- tools/keytools/sign.c | 3 +- tools/unit-tests/unit-sign-encrypted-output.c | 81 +++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index c67a8c069d..612de1de8f 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -1224,7 +1224,8 @@ static uint32_t header_required_size(int is_diff, uint32_t cert_chain_sz, if (CMD.hybrid && secondary_key_sz > 0U) { header_size_append_tag(&idx, 2); - header_size_align_8(&idx); + if (CMD.hash_algo == HASH_SHA256) + header_size_align_8(&idx); header_size_append_tag(&idx, digest_sz); header_size_align_8(&idx); } diff --git a/tools/unit-tests/unit-sign-encrypted-output.c b/tools/unit-tests/unit-sign-encrypted-output.c index 02cf4cfa44..0741ef0ef5 100644 --- a/tools/unit-tests/unit-sign-encrypted-output.c +++ b/tools/unit-tests/unit-sign-encrypted-output.c @@ -252,6 +252,25 @@ static uint16_t find_exact_fill_custom_len(void) return 0; } +static uint32_t find_cert_chain_len_for_required_size(int hash_algo, + uint32_t required_size, uint32_t secondary_key_sz) +{ + uint32_t len; + + reset_cmd_defaults(); + CMD.hash_algo = hash_algo; + CMD.hybrid = 1; + CMD.secondary_sign = SIGN_ED25519; + + for (len = 1; len < IMAGE_HEADER_SIZE; len++) { + if (header_required_size(0, len, secondary_key_sz) == required_size) { + return len; + } + } + + return 0; +} + START_TEST(test_make_header_ex_fails_when_encrypted_output_open_fails) { char tempdir[] = "/tmp/wolfboot-sign-XXXXXX"; @@ -526,6 +545,66 @@ START_TEST(test_make_header_ex_roundtrip_finds_tlv_that_exactly_fills_header) } END_TEST +START_TEST(test_make_header_ex_keeps_boundary_header_for_sha384_sha3_hybrid_cert_chain) +{ + static const int hash_algos[] = { HASH_SHA384, HASH_SHA3 }; + char tempdir[] = "/tmp/wolfboot-sign-XXXXXX"; + char image_path[PATH_MAX]; + char output_path[PATH_MAX]; + char cert_chain_path[PATH_MAX]; + uint8_t image_buf[] = { 0x71, 0x72, 0x73, 0x74 }; + uint8_t pubkey[] = { 0xA5, 0x5A, 0x33, 0xCC }; + uint8_t secondary_key[] = { 0x11, 0x22, 0x33, 0x44 }; + uint8_t *cert_chain_buf = NULL; + struct stat st; + size_t i; + int ret; + + ck_assert_ptr_nonnull(mkdtemp(tempdir)); + + snprintf(image_path, sizeof(image_path), "%s/image.bin", tempdir); + snprintf(output_path, sizeof(output_path), "%s/output.bin", tempdir); + snprintf(cert_chain_path, sizeof(cert_chain_path), "%s/cert-chain.bin", + tempdir); + ck_assert_int_eq(write_file(image_path, image_buf, sizeof(image_buf)), 0); + + for (i = 0; i < sizeof(hash_algos) / sizeof(hash_algos[0]); i++) { + uint32_t cert_chain_len = find_cert_chain_len_for_required_size( + hash_algos[i], IMAGE_HEADER_SIZE, sizeof(secondary_key)); + + ck_assert_uint_ne(cert_chain_len, 0); + cert_chain_buf = realloc(cert_chain_buf, cert_chain_len); + ck_assert_ptr_nonnull(cert_chain_buf); + memset(cert_chain_buf, 0xC3 + (int)i, cert_chain_len); + ck_assert_int_eq(write_file(cert_chain_path, cert_chain_buf, + cert_chain_len), 0); + + reset_cmd_defaults(); + CMD.hash_algo = hash_algos[i]; + CMD.hybrid = 1; + CMD.secondary_sign = SIGN_ED25519; + CMD.header_sz = IMAGE_HEADER_SIZE; + CMD.cert_chain_file = cert_chain_path; + + reset_mocks(NULL, 0); + ret = make_header_ex(0, pubkey, sizeof(pubkey), image_path, output_path, + 0, 0, 0, 0, secondary_key, sizeof(secondary_key), NULL, 0); + + ck_assert_int_eq(ret, 0); + ck_assert_uint_eq(CMD.header_sz, IMAGE_HEADER_SIZE); + ck_assert_int_eq(stat(output_path, &st), 0); + ck_assert_uint_eq((uint32_t)st.st_size, + IMAGE_HEADER_SIZE + sizeof(image_buf)); + unlink(output_path); + unlink(cert_chain_path); + } + + free(cert_chain_buf); + unlink(image_path); + rmdir(tempdir); +} +END_TEST + Suite *wolfboot_suite(void) { Suite *s = suite_create("sign-encrypted-output"); @@ -539,6 +618,8 @@ Suite *wolfboot_suite(void) test_make_header_ex_roundtrip_custom_tlvs_via_wolfboot_parser); tcase_add_test(tcase, test_make_header_ex_roundtrip_finds_tlv_that_exactly_fills_header); + tcase_add_test(tcase, + test_make_header_ex_keeps_boundary_header_for_sha384_sha3_hybrid_cert_chain); suite_add_tcase(s, tcase); return s; From 5cb0cb55b9543e458f060781c0d1ac6437fd4ef6 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 21:33:21 +0200 Subject: [PATCH 71/75] update: fix invalid size log F/2273 --- src/update_flash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/update_flash.c b/src/update_flash.c index 125fbe03f8..10b9196e1c 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -903,7 +903,7 @@ static int RAMFUNCTION wolfBoot_update(int fallback_allowed) /* get total size */ total_size = wolfBoot_get_total_size(&boot, &update); if (total_size <= IMAGE_HEADER_SIZE) { - wolfBoot_printf("Image total size %u too large!\n", total_size); + wolfBoot_printf("Image total size %u invalid!\n", total_size); return -1; } /* In case this is a new update, do the required From 80b2d06de16831b8cca57832f6625c00a9792a46 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 21:35:43 +0200 Subject: [PATCH 72/75] sign: use wc_ForceZero in cleanup F/2273 --- tools/keytools/sign.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index 612de1de8f..7be0392b84 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -317,18 +317,11 @@ static uint32_t header_append_limit(void) static void zero_and_free(uint8_t *buf, uint32_t len) { - volatile uint8_t *p; - uint32_t i; - if (buf == NULL) { return; } - p = buf; - for (i = 0; i < len; i++) { - p[i] = 0; - } - + wc_ForceZero(buf, len); free(buf); } From 63466dbfe62018ca7bd3dfbcb9957532f028fc0b Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 21:37:56 +0200 Subject: [PATCH 73/75] sign: clear stale file handles F/2273 --- tools/keytools/sign.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index 7be0392b84..8e1217b6e6 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -604,6 +604,7 @@ static uint8_t *load_key(uint8_t **key_buffer, uint32_t *key_buffer_sz, } } fclose(f); + f = NULL; if (*key_buffer == NULL) { printf("Key buffer malloc error!\n"); goto failure; @@ -1443,6 +1444,7 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, printf("Could not get certificate chain file size: %s\n", strerror(errno)); fclose(f); + f = NULL; goto failure; } @@ -1457,6 +1459,7 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, cert_chain_sz), CMD.header_sz); fclose(f); + f = NULL; goto failure; } @@ -1464,12 +1467,14 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, if (cert_chain == NULL) { printf("Certificate chain buffer malloc error!\n"); fclose(f); + f = NULL; goto failure; } /* Read the entire file into the buffer */ io_sz = (int)fread(cert_chain, 1, cert_chain_sz, f); fclose(f); + f = NULL; if (io_sz != (int)cert_chain_sz) { printf("Error reading certificate chain file: %s\n", @@ -1774,6 +1779,7 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, } io_sz = (int)fread(signature, 1, CMD.signature_sz, f); fclose(f); + f = NULL; if (io_sz <= 0) { printf("Error reading file %s\n", CMD.signature_file); goto failure; @@ -1821,6 +1827,7 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, if (io_sz != sizeof(uint32_t)) { printf("Error reading file %s\n", CMD.policy_file); fclose(f); + f = NULL; goto failure; } @@ -1828,6 +1835,7 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, /* in normal sign mode PCR digest (32 bytes) */ io_sz = (int)fread(digest, 1, digest_sz, f); fclose(f); + f = NULL; if (io_sz != (int)digest_sz) { printf("Error reading file %s\n", CMD.policy_file); goto failure; @@ -1850,6 +1858,7 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, /* in manual mode remainder is PCR signature */ io_sz = (int)fread(policy, 1, CMD.policy_sz, f); fclose(f); + f = NULL; if (io_sz <= 0) { printf("Error reading file %s\n", CMD.policy_file); goto failure; @@ -1864,6 +1873,7 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, if (f != NULL) { fwrite(policy, 1, CMD.policy_sz + sizeof(uint32_t), f); fclose(f); + f = NULL; } } From 3697f9e3356ca00b847c15274a998ff893ccb5fa Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 21:39:19 +0200 Subject: [PATCH 74/75] image: use portable noinline macro F/2273 --- include/image.h | 4 ++-- include/wolfboot/wolfboot.h | 14 ++++++++++++++ src/image.c | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/include/image.h b/include/image.h index 2f42a9d9cc..8ebb2145ce 100644 --- a/include/image.h +++ b/include/image.h @@ -166,7 +166,7 @@ struct wolfBoot_image { * With ARMORED setup, the flag is redundant, and the information is wrapped in * between canary variables, to mitigate attacks based on memory corruptions. */ -static void __attribute__((noinline)) wolfBoot_image_confirm_signature_ok( +static void NOINLINEFUNCTION wolfBoot_image_confirm_signature_ok( struct wolfBoot_image *img) { img->canary_FEED4567 = 0xFEED4567UL; @@ -176,7 +176,7 @@ static void __attribute__((noinline)) wolfBoot_image_confirm_signature_ok( img->canary_FEED89AB = 0xFEED89ABUL; } -static void __attribute__((noinline)) wolfBoot_image_clear_signature_ok( +static void NOINLINEFUNCTION wolfBoot_image_clear_signature_ok( struct wolfBoot_image *img) { img->canary_FEED4567 = 0xFEED4567UL; diff --git a/include/wolfboot/wolfboot.h b/include/wolfboot/wolfboot.h index 73f45e2ff2..6075de6f46 100644 --- a/include/wolfboot/wolfboot.h +++ b/include/wolfboot/wolfboot.h @@ -83,6 +83,20 @@ extern "C" { # endif #endif +#ifndef NOINLINEFUNCTION +# if defined(__has_attribute) +# if __has_attribute(noinline) +# define NOINLINEFUNCTION __attribute__((noinline)) +# else +# define NOINLINEFUNCTION +# endif +# elif defined(__GNUC__) || defined(__CC_ARM) +# define NOINLINEFUNCTION __attribute__((noinline)) +# else +# define NOINLINEFUNCTION +# endif +#endif + /* Helpers for memory alignment */ #ifndef XALIGNED diff --git a/src/image.c b/src/image.c index 1a170a9658..cf434066f3 100644 --- a/src/image.c +++ b/src/image.c @@ -58,7 +58,7 @@ /* Globals */ static uint8_t digest[WOLFBOOT_SHA_DIGEST_SIZE] XALIGNED(4); -int __attribute__((noinline)) image_CT_compare( +int NOINLINEFUNCTION image_CT_compare( const uint8_t *expected, const uint8_t *actual, uint32_t len) { uint8_t diff = 0; From 2fcc98669dc19d4b7a9ef15820d5afc42ea208b0 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Apr 2026 21:40:44 +0200 Subject: [PATCH 75/75] Updated .gitignore with new unit test binaries --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 05d4922f20..6c7bad8e78 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,7 @@ tools/keytools/Debug tools/keytools/Release tools/keytools/otp/otp-keystore-primer + # delta binaries tools/delta/bmdiff tools/delta/bmpatch @@ -176,6 +177,10 @@ tools/unit-tests/unit-hal-otp tools/unit-tests/unit-rot-auth tools/unit-tests/unit-sdhci-response-bits tools/unit-tests/unit-tpm-check-rot-auth +tools/unit-tests/unit-policy-create +tools/unit-tests/unit-sign-encrypted-output +tools/unit-tests/unit-update-flash-delta +tools/unit-tests/unit-update-flash-self-update