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
227 changes: 227 additions & 0 deletions include/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ struct wolfBoot_image {
uint32_t not_signature_ok;
uint32_t canary_FEED89AB;
uint32_t sha_ok;
uint32_t canary_FEEDCAFE;
uint32_t not_sha_ok;
uint32_t not_ext; /* image is no longer external */
};

Expand Down Expand Up @@ -202,6 +204,31 @@ static void NOINLINEFUNCTION wolfBoot_image_clear_signature_ok(
img->canary_FEED89AB = 0xFEED89ABUL;
}

/**
* This function sets the flag that indicates the digest (integrity) check
* succeeded for the wolfBoot_image.
*
* 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(
struct wolfBoot_image *img)
{
img->canary_FEED89AB = 0xFEED89ABUL;
img->sha_ok = 1UL;
img->canary_FEEDCAFE = 0xFEEDCAFEUL;
img->not_sha_ok = ~(1UL);
}

static void NOINLINEFUNCTION wolfBoot_image_clear_sha_ok(
struct wolfBoot_image *img)
{
img->canary_FEED89AB = 0xFEED89ABUL;
img->sha_ok = 0UL;
img->canary_FEEDCAFE = 0xFEEDCAFEUL;
img->not_sha_ok = 1UL;
}

/**
* Final sanity check, performed just before do_boot, or before starting an
* update that has been verified.
Expand Down Expand Up @@ -265,6 +292,55 @@ static void NOINLINEFUNCTION wolfBoot_image_clear_signature_ok(
asm volatile("mov r2, #0":::"r2"); \
asm volatile("mov r2, #0":::"r2"); \
asm volatile("mov r2, #0":::"r2"); \
/* Loading ~(sha_ok) flag, verifying */ \
asm volatile("mov r2, %0" ::"r"((p)->not_sha_ok):"r2"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("bne ."); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("bne .-4"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("bne .-8"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("bne .-12"); \
/* Redundant set of r2=0 */ \
asm volatile("mov r2, #0":::"r2"); \
asm volatile("mov r2, #0":::"r2"); \
asm volatile("mov r2, #0":::"r2"); \
asm volatile("mov r2, #0":::"r2"); \
asm volatile("mov r2, #0":::"r2"); \
/* Loading canary value, verifying */ \
asm volatile("mov r2, %0" ::"r"((p)->canary_FEEDCAFE):"r2"); \
asm volatile("mov r0, %0" ::"r"(0xFEEDCAFE):"r0"); \
asm volatile("cmp r2, r0":::"cc"); \
asm volatile("cmp r2, r0":::"cc"); \
asm volatile("cmp r2, r0":::"cc"); \
asm volatile("bne ."); \
asm volatile("cmp r2, r0":::"cc"); \
asm volatile("cmp r2, r0":::"cc"); \
asm volatile("cmp r2, r0":::"cc"); \
asm volatile("bne .-4"); \
asm volatile("cmp r2, r0":::"cc"); \
asm volatile("cmp r2, r0":::"cc"); \
asm volatile("cmp r2, r0":::"cc"); \
asm volatile("bne .-8"); \
asm volatile("cmp r2, r0":::"cc"); \
asm volatile("cmp r2, r0":::"cc"); \
asm volatile("cmp r2, r0":::"cc"); \
asm volatile("bne .-12"); \
/* Redundant set of r2=0 */ \
asm volatile("mov r2, #0":::"r2"); \
asm volatile("mov r2, #0":::"r2"); \
asm volatile("mov r2, #0":::"r2"); \
asm volatile("mov r2, #0":::"r2"); \
asm volatile("mov r2, #0":::"r2"); \
/* Loading signature_ok flag, verifying */ \
asm volatile("mov r2, %0" ::"r"((p)->signature_ok):"r2"); \
asm volatile("cmp r2, #1":::"cc"); \
Expand Down Expand Up @@ -553,6 +629,134 @@ static void NOINLINEFUNCTION wolfBoot_image_clear_signature_ok(
asm volatile("nop"); \
}

/* Redundant, canary-wrapped test for a confirmed integrity (digest) check. */
#define SHA_OK(imgp) (((imgp)->sha_ok == 1) && \
((imgp)->not_sha_ok == ~(uint32_t)1))

Comment thread
danielinux marked this conversation as resolved.
/**
* 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.
*
* Uses GAS local numeric labels (5f/5:) for safe multi-expansion.
*/
#define VERIFY_INTEGRITY_FN(img, computed_digest, stored) \
{ \
volatile int compare_res; \
if (!(img) || !(stored)) \
asm volatile("b 5f"); \
/* Redundant set of r0=50 */ \
asm volatile("mov r0, #50":::"r0"); \
asm volatile("mov r0, #50":::"r0"); \
asm volatile("mov r0, #50":::"r0"); \
compare_res = image_CT_compare((computed_digest), (stored), \
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"); \
asm volatile("cmp r0, #0":::"cc"); \
asm volatile("bne 5f"); \
asm volatile("cmp r0, #0":::"cc"); \
asm volatile("cmp r0, #0":::"cc"); \
asm volatile("cmp r0, #0":::"cc"); \
asm volatile("bne 5f"); \
asm volatile("cmp r0, #0":::"cc"); \
asm volatile("cmp r0, #0":::"cc"); \
asm volatile("cmp r0, #0":::"cc"); \
asm volatile("bne 5f"); \
asm volatile("cmp r0, #0":::"cc"); \
asm volatile("cmp r0, #0":::"cc"); \
asm volatile("cmp r0, #0":::"cc"); \
asm volatile("bne 5f"); \
/* Repeat comparison call */ \
compare_res = image_CT_compare((computed_digest), (stored), \
WOLFBOOT_SHA_DIGEST_SIZE); \
compare_res; \
/* 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"); \
asm volatile("bne 5f"); \
asm volatile("cmp r0, #0":::"cc"); \
asm volatile("cmp r0, #0":::"cc"); \
asm volatile("cmp r0, #0":::"cc"); \
asm volatile("bne 5f"); \
asm volatile("cmp r0, #0":::"cc"); \
asm volatile("cmp r0, #0":::"cc"); \
asm volatile("cmp r0, #0":::"cc"); \
asm volatile("bne 5f"); \
asm volatile("cmp r0, #0":::"cc"); \
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 */ \
(img)->sha_hash = (stored); \
wolfBoot_image_confirm_sha_ok(img); \
asm volatile("5:"); \
asm volatile("nop"); \
}

/**
* Hardened assertion that the integrity (digest) check has actually been
* performed and passed. Mirrors the sha portion of PART_SANITY_CHECK and is
* used right after the integrity re-check in wolfBoot_verify_authenticity(),
* so that skipping the re-check (or its result) cannot let an unverified
* image proceed to signature verification.
*/
#define SHA_SANITY_CHECK(p) \
/* Redundant set of r2=0 */ \
asm volatile("mov r2, #0":::"r2"); \
asm volatile("mov r2, #0":::"r2"); \
asm volatile("mov r2, #0":::"r2"); \
asm volatile("mov r2, #0":::"r2"); \
asm volatile("mov r2, #0":::"r2"); \
/* Loading sha_ok flag, verifying */ \
asm volatile("mov r2, %0" ::"r"((p)->sha_ok):"r2"); \
asm volatile("cmp r2, #1":::"cc"); \
asm volatile("cmp r2, #1":::"cc"); \
asm volatile("cmp r2, #1":::"cc"); \
asm volatile("bne ."); \
asm volatile("cmp r2, #1":::"cc"); \
asm volatile("cmp r2, #1":::"cc"); \
asm volatile("cmp r2, #1":::"cc"); \
asm volatile("bne .-4"); \
asm volatile("cmp r2, #1":::"cc"); \
asm volatile("cmp r2, #1":::"cc"); \
asm volatile("cmp r2, #1":::"cc"); \
asm volatile("bne .-8"); \
asm volatile("cmp r2, #1":::"cc"); \
asm volatile("cmp r2, #1":::"cc"); \
asm volatile("cmp r2, #1":::"cc"); \
asm volatile("bne .-12"); \
/* Redundant set of r2=0 */ \
asm volatile("mov r2, #0":::"r2"); \
asm volatile("mov r2, #0":::"r2"); \
asm volatile("mov r2, #0":::"r2"); \
asm volatile("mov r2, #0":::"r2"); \
asm volatile("mov r2, #0":::"r2"); \
/* Loading ~(sha_ok) flag, verifying */ \
asm volatile("mov r2, %0" ::"r"((p)->not_sha_ok):"r2"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("bne ."); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("bne .-4"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("bne .-8"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("cmp r2, #0xFFFFFFFE":::"cc"); \
asm volatile("bne .-12")

/**
* ECC / Ed / PQ signature verification.
* Those verify functions set an additional value 'p_res'
Expand Down Expand Up @@ -1307,6 +1511,16 @@ static void UNUSEDFUNCTION wolfBoot_image_clear_signature_ok(
{
img->signature_ok = 0;
}
static void UNUSEDFUNCTION wolfBoot_image_confirm_sha_ok(
struct wolfBoot_image *img)
{
img->sha_ok = 1;
}
static void UNUSEDFUNCTION wolfBoot_image_clear_sha_ok(
struct wolfBoot_image *img)
{
img->sha_ok = 0;
}

#define likely(x) (x)
#define unlikely(x) (x)
Expand All @@ -1329,10 +1543,23 @@ static void UNUSEDFUNCTION wolfBoot_image_clear_signature_ok(
pss_data, pss_data_sz, hash_type) == 0) \
wolfBoot_image_confirm_signature_ok(img);

#define SHA_OK(imgp) ((imgp)->sha_ok == 1)

#define VERIFY_INTEGRITY_FN(img, computed_digest, stored) \
if (image_CT_compare((computed_digest), (stored), \
WOLFBOOT_SHA_DIGEST_SIZE) == 0) { \
(img)->sha_hash = (stored); \
wolfBoot_image_confirm_sha_ok(img); \
}

#define PART_SANITY_CHECK(p) \
if (((p)->hdr_ok != 1) || ((p)->sha_ok != 1) || ((p)->signature_ok != 1)) \
wolfBoot_panic()

#define SHA_SANITY_CHECK(p) \
if ((p)->sha_ok != 1) \
wolfBoot_panic()

#define CONFIRM_MASK_VALID(id, mask) \
if ((mask & (1UL << id)) != (1UL << id)) \
wolfBoot_panic()
Expand Down
19 changes: 16 additions & 3 deletions src/image.c
Original file line number Diff line number Diff line change
Expand Up @@ -1641,15 +1641,24 @@ int wolfBoot_verify_integrity(struct wolfBoot_image *img)
{
uint8_t *stored_sha;
uint16_t stored_sha_len;
/* Reset any cached integrity state up-front, so that a stale sha_ok (and
* its complement/canary) left over from a previous verification of a
* re-used image cannot survive a failed comparison and produce a
* false-positive SHA_OK() result below. */
img->sha_hash = NULL;
wolfBoot_image_clear_sha_ok(img);
stored_sha_len = get_header(img, WOLFBOOT_SHA_HDR, &stored_sha);
if (stored_sha_len != WOLFBOOT_SHA_DIGEST_SIZE)
return -1;
if (image_hash(img, digest) != 0)
return -1;
if (image_CT_compare(digest, stored_sha, stored_sha_len) != 0)
/* Redundant, fault-hardened digest comparison. On a match this records the
* verified digest and sets sha_ok (plus its complement/canary under
* ARMORED) through an unskippable callback; otherwise the flags stay
* cleared and SHA_OK() below fails the check. */
VERIFY_INTEGRITY_FN(img, digest, stored_sha);
if (!SHA_OK(img))
return -1;
img->sha_ok = 1;
img->sha_hash = stored_sha;
return 0;
}

Expand Down Expand Up @@ -2309,6 +2318,10 @@ int wolfBoot_verify_authenticity(struct wolfBoot_image *img)
if (wolfBoot_verify_integrity(img) != 0)
return -1;
}
/* Integrity must hold before authenticity: assert it in a fault-hardened
* way so that skipping the re-check above (or its result) cannot let an
* image with an unverified digest reach signature verification. */
SHA_SANITY_CHECK(img);
image_part = image_type & HDR_IMG_TYPE_PART_MASK;
#ifdef WOLFBOOT_NO_KEYSTORE
/* No local keystore is linked: there is no per-key partition permission
Expand Down
2 changes: 1 addition & 1 deletion tools/test.mk
Original file line number Diff line number Diff line change
Expand Up @@ -1185,7 +1185,7 @@ test-all: clean


test-size-all:
make test-size SIGN=NONE LIMIT=5048 NO_ARM_ASM=1
make test-size SIGN=NONE LIMIT=5072 NO_ARM_ASM=1
make keysclean
make test-size SIGN=ED25519 LIMIT=11844 NO_ARM_ASM=1
make keysclean
Expand Down
Loading