Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 91 additions & 9 deletions include/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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" \
Comment thread
danielinux marked this conversation as resolved.
"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 */ \
Expand Down Expand Up @@ -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"); \
}
Expand Down
57 changes: 54 additions & 3 deletions src/image.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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
Expand All @@ -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);
}

Expand Down
Loading