diff --git a/include/image.h b/include/image.h index 2d9aefa16e..5357ffa8e3 100644 --- a/include/image.h +++ b/include/image.h @@ -173,6 +173,7 @@ struct wolfBoot_image { uint32_t sha_ok; uint32_t canary_FEEDCAFE; uint32_t not_sha_ok; + uintptr_t not_fw_base; /* complement of fw_base, for FI hardening */ uint32_t not_ext; /* image is no longer external */ }; @@ -229,6 +230,18 @@ static void NOINLINEFUNCTION wolfBoot_image_clear_sha_ok( img->not_sha_ok = 1UL; } +/** + * Records the image entry base together with its complement, so that a single + * fault on the pointer that do_boot() jumps through can be detected by + * FW_BASE_SANITY_CHECK() before the branch is taken. + */ +static void NOINLINEFUNCTION wolfBoot_image_set_fw_base( + struct wolfBoot_image *img, void *base) +{ + img->fw_base = (uint8_t *)base; + img->not_fw_base = ~(uintptr_t)base; +} + /** * Final sanity check, performed just before do_boot, or before starting an * update that has been verified. @@ -757,6 +770,43 @@ static void NOINLINEFUNCTION wolfBoot_image_clear_sha_ok( asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \ asm volatile("bne .-12") +/** + * Hardened assertion that the image entry base is consistent with its stored + * complement (fw_base == ~not_fw_base), performed immediately before do_boot() + * dereferences fw_base. A single fault on either the pointer or the loads + * fails the check safely (spins) instead of redirecting the boot jump. + */ +#define FW_BASE_SANITY_CHECK(p) \ + do { \ + /* Single asm block so the compiler cannot allocate over r0/r2 between \ + * steps: r2 = fw_base, r0 = ~not_fw_base (== expected fw_base), then \ + * a redundant self-trapping comparison that spins on any mismatch. */ \ + asm volatile( \ + "mov r2, %[fwb]\n\t" \ + "mov r0, %[nfwb]\n\t" \ + "mvn r0, r0\n\t" \ + "cmp r2, r0\n\t" \ + "cmp r2, r0\n\t" \ + "cmp r2, r0\n\t" \ + "bne .\n\t" \ + "cmp r2, r0\n\t" \ + "cmp r2, r0\n\t" \ + "cmp r2, r0\n\t" \ + "bne .-4\n\t" \ + "cmp r2, r0\n\t" \ + "cmp r2, r0\n\t" \ + "cmp r2, r0\n\t" \ + "bne .-8\n\t" \ + "cmp r2, r0\n\t" \ + "cmp r2, r0\n\t" \ + "cmp r2, r0\n\t" \ + "bne .-12\n\t" \ + : \ + : [fwb] "r" ((uintptr_t)(p)->fw_base), \ + [nfwb] "r" ((uintptr_t)(p)->not_fw_base) \ + : "r0", "r2", "cc"); \ + } while (0) + /** * ECC / Ed / PQ signature verification. * Those verify functions set an additional value 'p_res' @@ -1521,6 +1571,11 @@ static void UNUSEDFUNCTION wolfBoot_image_clear_sha_ok( { img->sha_ok = 0; } +static void UNUSEDFUNCTION wolfBoot_image_set_fw_base( + struct wolfBoot_image *img, void *base) +{ + img->fw_base = (uint8_t *)base; +} #define likely(x) (x) #define unlikely(x) (x) @@ -1560,6 +1615,8 @@ static void UNUSEDFUNCTION wolfBoot_image_clear_sha_ok( if ((p)->sha_ok != 1) \ wolfBoot_panic() +#define FW_BASE_SANITY_CHECK(p) do{} while(0) + #define CONFIRM_MASK_VALID(id, mask) \ if ((mask & (1UL << id)) != (1UL << id)) \ wolfBoot_panic() @@ -1571,6 +1628,8 @@ static void UNUSEDFUNCTION wolfBoot_image_clear_sha_ok( /* Defined in image.c */ int image_CT_compare(const uint8_t *expected, const uint8_t *actual, uint32_t len); +int wolfBoot_hardened_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 97841b660a..6e24f3daf9 100644 --- a/src/image.c +++ b/src/image.c @@ -71,6 +71,25 @@ int NOINLINEFUNCTION image_CT_compare( return (diff != 0U) ? 1 : 0; } +/** + * Fault-hardened equality check around image_CT_compare(): the constant-time + * comparison is run twice and a match is reported only when both independent + * calls agree. A single instruction-skip fault can subvert at most one of the + * two calls (or one of the two result checks), so a genuine mismatch is still + * detected. Returns 0 when equal, non-zero otherwise. + */ +int NOINLINEFUNCTION wolfBoot_hardened_CT_compare( + const uint8_t *expected, const uint8_t *actual, uint32_t len) +{ + volatile int r1 = image_CT_compare(expected, actual, len); + volatile int r2 = image_CT_compare(expected, actual, len); + /* Combine both results without branching: non-zero if either independent + * comparison reported a mismatch. This preserves image_CT_compare()'s 0/1 + * return semantics and avoids data-dependent control flow, while a single + * fault can still subvert at most one of the two calls. */ + return (r1 | r2); +} + #if defined(WOLFBOOT_CERT_CHAIN_VERIFY) && \ (defined(WOLFBOOT_ENABLE_WOLFHSM_CLIENT) || \ defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER)) @@ -1424,7 +1443,7 @@ int wolfBoot_open_image_address(struct wolfBoot_image *img, uint8_t *image) } #endif img->hdr_ok = 1; - img->fw_base = img->hdr + IMAGE_HEADER_SIZE; + wolfBoot_image_set_fw_base(img, img->hdr + IMAGE_HEADER_SIZE); #ifdef EXT_FLASH img->hdr_cache = image; #endif @@ -1488,7 +1507,7 @@ int wolfBoot_open_image(struct wolfBoot_image *img, uint8_t part) if (part == PART_SWAP) { img->hdr = (void*)WOLFBOOT_PARTITION_SWAP_ADDRESS; img->hdr_ok = 1; - img->fw_base = img->hdr; + wolfBoot_image_set_fw_base(img, img->hdr); img->fw_size = WOLFBOOT_SECTOR_SIZE; return 0; } @@ -1507,7 +1526,7 @@ int wolfBoot_open_image(struct wolfBoot_image *img, uint8_t part) if (ret < 0) return -1; img->hdr_ok = 1; - img->fw_base = img->hdr; + wolfBoot_image_set_fw_base(img, img->hdr); img->fw_size = (uint32_t)ret; return 0; } @@ -1620,7 +1639,7 @@ int wolfBoot_open_self_address(struct wolfBoot_image* img, uint8_t* hdr, return -1; } #endif - img->fw_base = image; + wolfBoot_image_set_fw_base(img, image); img->part = PART_SELF; img->hdr_ok = 1; @@ -2111,7 +2130,8 @@ 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) != 0) { + if (wolfBoot_hardened_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 9e433c260b..28d3c8d6cc 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -709,7 +709,8 @@ 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) != 0) { + wolfBoot_hardened_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; @@ -1270,7 +1271,7 @@ static int RAMFUNCTION wolfBoot_update(int fallback_allowed) wolfBoot_printf( "Scattered image correctly verified. Setting entry point to %lx\n", entry); - boot.fw_base = (void*)entry; + wolfBoot_image_set_fw_base(&boot, (void*)entry); #endif /* Direct Swap without power fail safety */ @@ -1618,7 +1619,7 @@ void RAMFUNCTION wolfBoot_start(void) wolfBoot_printf( "Scattered image correctly verified. Setting entry point to %lx\n", entry); - boot.fw_base = (void*)entry; + wolfBoot_image_set_fw_base(&boot, (void*)entry); #endif @@ -1650,6 +1651,7 @@ void RAMFUNCTION wolfBoot_start(void) #endif #ifndef WOLFBOOT_SKIP_BOOT_VERIFY PART_SANITY_CHECK(&boot); + FW_BASE_SANITY_CHECK(&boot); #endif do_boot((void *)boot.fw_base); }