diff --git a/include/image.h b/include/image.h index b9f6108e17..040a235e00 100644 --- a/include/image.h +++ b/include/image.h @@ -212,7 +212,7 @@ static void NOINLINEFUNCTION wolfBoot_image_clear_signature_ok( * As with the signature flag, the value is redundant and wrapped between * canary variables, so that a single fault cannot forge a valid state. */ -static void NOINLINEFUNCTION wolfBoot_image_confirm_sha_ok( +static void NOINLINEFUNCTION UNUSEDFUNCTION wolfBoot_image_confirm_sha_ok( struct wolfBoot_image *img) { img->canary_FEED89AB = 0xFEED89ABUL; @@ -230,6 +230,31 @@ static void NOINLINEFUNCTION wolfBoot_image_clear_sha_ok( img->not_sha_ok = 1UL; } +/** + * Sets sha_ok from the value the digest comparison produced rather than from + * the control flow that reached here. Sole setter of sha_ok in the ARMORED + * integrity path, so skipping the call leaves the flag clear. + */ +static void NOINLINEFUNCTION wolfBoot_image_seal_sha_ok( + struct wolfBoot_image *img, uint32_t witness) +{ + volatile uint32_t z1 = 0U, z2 = 0U, z3 = 0U, ok = 0U; + + z1 = (witness - 1U) >> 31; /* 1 iff witness == 0 */ + z2 = 1U ^ ((witness | (0U - witness)) >> 31); /* 1 iff witness == 0 */ + z3 = (witness == 0U) ? 1U : 0U; /* 1 iff witness == 0 */ + + ok = z1 & z2 & z3; + ok &= z1; + ok &= z2; + ok &= z3; + + img->canary_FEED89AB = 0xFEED89ABUL; + img->sha_ok = ok; + img->canary_FEEDCAFE = 0xFEEDCAFEUL; + img->not_sha_ok = ~ok; +} + /** * 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 @@ -646,20 +671,75 @@ static void NOINLINEFUNCTION wolfBoot_image_set_fw_base( #define SHA_OK(imgp) (((imgp)->sha_ok == 1) && \ ((imgp)->not_sha_ok == ~(uint32_t)1)) +/** + * Digest comparison with no call and no return register, run inline as an + * additional gate. Emits a witness in `out`, zero only on a clean full pass. + * Branches to label 5 on mismatch, like the checks around it. + */ +#define CT_COMPARE_INLINE(out, a, b, len) \ + asm volatile( \ + "mov r0, %1\n" /* left */ \ + "mov r1, %2\n" /* right */ \ + "mov r2, #0\n" /* index */ \ + "mov r2, #0\n" \ + "mov r2, #0\n" \ + "mov r3, #0\n" /* difference accumulator */ \ + "mov r3, #0\n" \ + "mov r3, #0\n" \ + "mov r6, %3\n" /* iteration budget */ \ + "mov r6, %3\n" \ + "mov r6, %3\n" \ + "7:\n" \ + "ldrb r4, [r0, r2]\n" \ + "ldrb r5, [r1, r2]\n" \ + "eors r4, r4, r5\n" \ + "orrs r3, r3, r4\n" \ + "adds r2, r2, #1\n" \ + "subs r6, r6, #1\n" \ + "beq 8f\n" /* budget spent: stop regardless */ \ + "cmp r2, %3\n" \ + "blo 7b\n" \ + "8:\n" \ + /* witness = difference | iteration shortfall */ \ + "eor r4, r2, %3\n" \ + "orr %0, r3, r4\n" \ + "orr %0, %0, r3\n" \ + /* every byte matched */ \ + "cmp r3, #0\n" \ + "cmp r3, #0\n" \ + "cmp r3, #0\n" \ + "bne 5f\n" \ + "cmp r3, #0\n" \ + "cmp r3, #0\n" \ + "cmp r3, #0\n" \ + "bne 5f\n" \ + /* and the loop really ran over the whole buffer */ \ + "cmp r2, %3\n" \ + "cmp r2, %3\n" \ + "cmp r2, %3\n" \ + "bne 5f\n" \ + "cmp r2, %3\n" \ + "cmp r2, %3\n" \ + "cmp r2, %3\n" \ + "bne 5f\n" \ + : "=&r"(out) \ + : "r"(a), "r"(b), "r"((uint32_t)(len)) \ + : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "cc", "memory") + /** * Digest (integrity) verification. * - * Compare the freshly computed digest against the stored one twice, and after - * each call ensure via redundant checks that image_CT_compare() actually - * returned 0. Only then record the verified digest and confirm sha_ok through - * the unskippable callback. A single instruction skip can neither coerce - * image_CT_compare() into a false match nor set the sha_ok flag on its own. + * Compare the freshly computed digest against the stored one twice through + * image_CT_compare(), checking each result redundantly, then a third time + * inline. Only then record the digest and raise sha_ok from the witness. * - * Uses GAS local numeric labels (5f/5:) for safe multi-expansion. + * Uses GAS local numeric labels (5f/5:, 7f/7b, 8f/8:) for safe expansion. */ #define VERIFY_INTEGRITY_FN(img, computed_digest, stored) \ { \ volatile int compare_res; \ + /* Pre-set to "mismatch". */ \ + volatile uint32_t ct_witness = 0xFFFFFFFFU; \ if (!(img) || !(stored)) \ asm volatile("b 5f"); \ /* Redundant set of r0=50 */ \ @@ -706,9 +786,11 @@ static void NOINLINEFUNCTION wolfBoot_image_set_fw_base( asm volatile("cmp r0, #0":::"cc"); \ asm volatile("cmp r0, #0":::"cc"); \ asm volatile("bne 5f"); \ - /* Integrity confirmed: record verified digest and set sha_ok */ \ + /* Third comparison, inline */ \ + CT_COMPARE_INLINE(ct_witness, (computed_digest), (stored), \ + WOLFBOOT_SHA_DIGEST_SIZE); \ (img)->sha_hash = (stored); \ - wolfBoot_image_confirm_sha_ok(img); \ + wolfBoot_image_seal_sha_ok((img), ct_witness); \ asm volatile("5:"); \ asm volatile("nop"); \ } diff --git a/src/image.c b/src/image.c index 6118419559..1af07da248 100644 --- a/src/image.c +++ b/src/image.c @@ -58,6 +58,57 @@ /* Globals */ static uint8_t digest[WOLFBOOT_SHA_DIGEST_SIZE] XALIGNED(4); +#ifdef WOLFBOOT_ARMORED + +/* Accumulator seed. Low byte clear so byte differences are never masked. */ +#define CT_SENTINEL 0xA5C3F000U + +/** + * Constant-time buffer comparison, hardened against instruction skips. + * Returns 0 when equal, non-zero otherwise. + */ +int NOINLINEFUNCTION image_CT_compare( + const uint8_t *expected, const uint8_t *actual, uint32_t len) +{ + volatile uint32_t diff = CT_SENTINEL; + volatile uint32_t witness = 0U; + volatile uint32_t count = 0U; + volatile uint32_t i = 0U; + volatile uint32_t budget = len; + volatile uint32_t res = 0U; + uint32_t expected_witness; + uint32_t len_is_zero; + + /* Two counters bound the loop, so either one can end it. */ + for (i = 0; (i < len) && (budget != 0U); i++) { + diff |= (uint32_t)(expected[i] ^ actual[i]); + witness += i + 1U; + count++; + budget--; + } + + expected_witness = (len * (len + 1U)) / 2U; /* sum(1..len) */ + len_is_zero = 1U ^ ((len | (0U - len)) >> 31); + + /* Folded twice, branch-free. */ + res = (diff ^ CT_SENTINEL); + res |= (witness ^ expected_witness); + res |= (count ^ len); + res |= (i ^ len); + res |= len_is_zero; + res |= (diff ^ CT_SENTINEL); + res |= (witness ^ expected_witness); + res |= (count ^ len); + res |= (i ^ len); + res |= len_is_zero; + + return (int)res; +} + +#undef CT_SENTINEL + +#else + int NOINLINEFUNCTION image_CT_compare( const uint8_t *expected, const uint8_t *actual, uint32_t len) { @@ -71,6 +122,8 @@ int NOINLINEFUNCTION image_CT_compare( return (diff != 0U) ? 1 : 0; } +#endif /* WOLFBOOT_ARMORED */ + /** * Fault-hardened equality check around image_CT_compare(): the constant-time * comparison is run twice and a match is reported only when both independent @@ -84,9 +137,7 @@ int NOINLINEFUNCTION wolfBoot_hardened_CT_compare( 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. */ + * comparison reported a mismatch. */ return (r1 | r2); }