mirror of https://github.com/wolfSSL/wolfBoot.git
F-9756: validate PT_LOAD segments before the scatter flash hash walk
wolfBoot_check_flash_image_elf() fed every PT_LOAD entry's
paddr/BASE_OFF straight into update_hash_flash_addr() with the 64-bit
file_size truncated to the uint32_t the reader consumes, and never
bounded an intermediate segment's file layout against the manifest.
The read loop then memcpy's from (or drives the flash driver at)
whatever address the image declares - an unauthenticated partition
(e.g. WOLFBOOT_SKIP_BOOT_VERIFY builds) or a corrupt one could walk
the hash over unmapped memory.
Validate each loadable segment before hashing and fail the check
instead of continuing:
- file_size must fit the uint32_t hash length,
- offset + file_size must stay inside the manifest image
(overflow-safe comparison; previously only the last segment was
checked, after the loop),
- paddr + BASE_OFF + file_size must not overflow the address space.
The mismatch log no longer prints the first 8 digest bytes.
Note: a full flash-region bound for paddr needs a configured
scatter-region size; no such knob exists in the target configuration
today (scattered segments are deliberately placed outside the
boot/update/swap partitions), so the region check is left as a
follow-up.
unit-image-elf-scatter gains three cases with a multi-segment
fixture: a 2^32 file_size and a segment layout extending past
fw_size (both verified OK pre-fix because the stored digest matched
the truncated/out-of-layout walk) are now rejected with -1, and a
paddr whose range overflows the address space is rejected before any
flash read (pre-fix: read at 0xfffffffffffffffb, segfault on host).
pull/868/head
parent
d554bbb98b
commit
f08c62a000
38
src/image.c
38
src/image.c
|
|
@ -2242,7 +2242,35 @@ int wolfBoot_check_flash_image_elf(uint8_t part, unsigned long* entry_out)
|
|||
|
||||
/* Handle loadable segments */
|
||||
if (type == ELF_PT_LOAD) {
|
||||
uintptr_t load_addr = (uintptr_t)(paddr + BASE_OFF);
|
||||
uint64_t seg_start;
|
||||
uintptr_t load_addr;
|
||||
|
||||
/* Validate the segment before hashing: the flash-address hash
|
||||
* reader consumes a uint32_t length, the file layout must stay
|
||||
* inside the manifest image, and the paddr range must not
|
||||
* overflow. Reject instead of continuing. */
|
||||
if (filesz > UINT32_MAX) {
|
||||
wolfBoot_printf("ELF: [CHECK] ERROR: segment file_size "
|
||||
"%lu does not fit a 32-bit length\n",
|
||||
(unsigned long)filesz);
|
||||
return -1;
|
||||
}
|
||||
if (offset > (uint64_t)boot.fw_size ||
|
||||
filesz > (uint64_t)boot.fw_size - offset) {
|
||||
wolfBoot_printf("ELF: [CHECK] ERROR: segment offset %lu + "
|
||||
"size %lu exceeds image size %u\n",
|
||||
(unsigned long)offset,
|
||||
(unsigned long)filesz, boot.fw_size);
|
||||
return -1;
|
||||
}
|
||||
seg_start = paddr + (uint64_t)BASE_OFF;
|
||||
if (seg_start < paddr || seg_start > UINT64_MAX - filesz) {
|
||||
wolfBoot_printf("ELF: [CHECK] ERROR: segment paddr range "
|
||||
"overflows\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
load_addr = (uintptr_t)seg_start;
|
||||
/* Feed the loadable parts to the hash function */
|
||||
wolfBoot_printf("ELF: [CHECK] Hashing loadable segment: "
|
||||
"paddr = 0x%08lx, loadaddr = 0x%08lx, "
|
||||
|
|
@ -2315,14 +2343,6 @@ int wolfBoot_check_flash_image_elf(uint8_t part, unsigned long* entry_out)
|
|||
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",
|
||||
exp_digest[0], exp_digest[1], exp_digest[2], exp_digest[3],
|
||||
exp_digest[4], exp_digest[5], exp_digest[6], exp_digest[7]);
|
||||
wolfBoot_printf(
|
||||
"ELF: [CHECK] Calculated %02x%02x%02x%02x%02x%02x%02x%02x\n",
|
||||
calc_digest[0], calc_digest[1], calc_digest[2], calc_digest[3],
|
||||
calc_digest[4], calc_digest[5], calc_digest[6], calc_digest[7]);
|
||||
return -2;
|
||||
}
|
||||
wolfBoot_printf("ELF: [CHECK] Verification successful\n");
|
||||
|
|
|
|||
|
|
@ -258,6 +258,74 @@ static void patch_expected_digest(const uint8_t *digest)
|
|||
memcpy(manifest + 12, digest, WOLFBOOT_SHA_DIGEST_SIZE);
|
||||
}
|
||||
|
||||
/* --- Multi-segment fixtures for the PT_LOAD bounds-rejection tests ---
|
||||
*
|
||||
* The manifest holds image header + ELF header + N program headers
|
||||
* (tightly packed). Each segment's flash-resident payload lives in its
|
||||
* own static array referenced by ph.paddr. */
|
||||
#define SEG1_SIZE 0x2000U
|
||||
#define SEG2_SIZE 64U
|
||||
|
||||
static uint8_t seg1_flash[SEG1_SIZE];
|
||||
static uint8_t seg2_flash[SEG2_SIZE];
|
||||
|
||||
struct seg_spec {
|
||||
uint64_t offset;
|
||||
uint64_t filesz;
|
||||
uint64_t paddr;
|
||||
uint8_t *payload; /* pattern-filled for fillsz bytes */
|
||||
uint32_t fillsz;
|
||||
};
|
||||
|
||||
static void build_scattered_image_n(const struct seg_spec *segs, unsigned n,
|
||||
uint32_t fw_size)
|
||||
{
|
||||
uint8_t *manifest = (uint8_t *)(uintptr_t)MOCK_ADDRESS_BOOT;
|
||||
uint32_t magic = WOLFBOOT_MAGIC;
|
||||
size_t pht_sz = sizeof(elf64_header) + n * sizeof(elf64_program_header);
|
||||
elf64_header *eh;
|
||||
elf64_program_header *ph;
|
||||
unsigned i, j;
|
||||
|
||||
memset(manifest, 0, IMAGE_HEADER_SIZE + pht_sz);
|
||||
|
||||
/* manifest header with a zeroed HDR_HASH TLV (patched by caller) */
|
||||
memcpy(manifest + 0, &magic, sizeof(magic));
|
||||
memcpy(manifest + 4, &fw_size, sizeof(fw_size));
|
||||
write_le16(manifest + 8, HDR_HASH);
|
||||
write_le16(manifest + 10, WOLFBOOT_SHA_DIGEST_SIZE);
|
||||
|
||||
eh = (elf64_header *)(manifest + IMAGE_HEADER_SIZE);
|
||||
memcpy(eh->ident, ELF_IDENT_STR, 4);
|
||||
eh->ident[ELF_CLASS_OFF] = ELF_CLASS_64;
|
||||
eh->ident[5] = ELF_ENDIAN_LITTLE;
|
||||
eh->type = ELF_HET_EXEC;
|
||||
eh->machine = 0;
|
||||
eh->version = 1;
|
||||
eh->entry = 0x2000;
|
||||
eh->ph_offset = sizeof(elf64_header);
|
||||
eh->flags = 0;
|
||||
eh->header_size = sizeof(elf64_header);
|
||||
eh->ph_entry_size = sizeof(elf64_program_header);
|
||||
eh->ph_entry_count = n;
|
||||
|
||||
ph = (elf64_program_header *)((uint8_t *)eh + sizeof(elf64_header));
|
||||
for (i = 0; i < n; i++) {
|
||||
memset(&ph[i], 0, sizeof(ph[i]));
|
||||
ph[i].type = ELF_PT_LOAD;
|
||||
ph[i].offset = segs[i].offset;
|
||||
ph[i].paddr = segs[i].paddr;
|
||||
ph[i].file_size = segs[i].filesz;
|
||||
ph[i].mem_size = segs[i].filesz;
|
||||
ph[i].align = 1;
|
||||
for (j = 0; j < segs[i].fillsz; j++) {
|
||||
segs[i].payload[j] = (uint8_t)(0x60U + i + j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define ELF_HDR_SZ_2 (sizeof(elf64_header) + 2 * sizeof(elf64_program_header))
|
||||
|
||||
static void map_boot_partition(void)
|
||||
{
|
||||
int ret = mmap_file("/tmp/wolfboot-unit-elf-scatter-boot.bin",
|
||||
|
|
@ -330,12 +398,152 @@ START_TEST(test_elf_scatter_corrupted_segment_rejected)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
/* A segment whose 64-bit file_size does not fit the uint32_t length the
|
||||
* flash hash reader consumes must be rejected outright. Pre-fix, the
|
||||
* size was silently truncated (2^32 -> 0 bytes hashed) and an image
|
||||
* whose stored digest matched the truncated walk verified OK. */
|
||||
START_TEST(test_elf_scatter_filesz_over_32bit_rejected)
|
||||
{
|
||||
uint8_t expected_digest[WOLFBOOT_SHA_DIGEST_SIZE];
|
||||
unsigned long entry = 0;
|
||||
uint32_t fw_size = (uint32_t)ELF_HDR_SZ_2 + SEG2_SIZE;
|
||||
struct seg_spec segs[2];
|
||||
struct wolfBoot_image boot;
|
||||
wolfBoot_hash_t ctx;
|
||||
int ret;
|
||||
|
||||
map_boot_partition();
|
||||
|
||||
memset(seg1_flash, 0, sizeof(seg1_flash));
|
||||
memset(seg2_flash, 0, sizeof(seg2_flash));
|
||||
segs[0].offset = ELF_HDR_SZ_2 + 0x1000U; /* layout gap, never hashed */
|
||||
segs[0].filesz = 0x100000000ULL; /* 2^32: truncates to 0 */
|
||||
segs[0].paddr = (uint64_t)(uintptr_t)seg2_flash; /* 0 bytes read */
|
||||
segs[0].payload = seg2_flash;
|
||||
segs[0].fillsz = 0;
|
||||
segs[1].offset = ELF_HDR_SZ_2;
|
||||
segs[1].filesz = SEG2_SIZE;
|
||||
segs[1].paddr = (uint64_t)(uintptr_t)seg2_flash;
|
||||
segs[1].payload = seg2_flash;
|
||||
segs[1].fillsz = SEG2_SIZE;
|
||||
|
||||
build_scattered_image_n(segs, 2, fw_size);
|
||||
|
||||
/* Replay the pre-fix walk: the oversized segment contributes zero
|
||||
* hashed bytes, only seg2 does. */
|
||||
ck_assert_int_eq(wolfBoot_open_image(&boot, PART_BOOT), 0);
|
||||
ck_assert_int_eq(header_hash(&ctx, &boot), 0);
|
||||
ck_assert_int_eq(update_hash_flash_fwimg(&ctx, &boot, 0, (uint32_t)ELF_HDR_SZ_2), 0);
|
||||
ck_assert_int_eq(
|
||||
update_hash_flash_addr(&ctx, (uintptr_t)seg2_flash, SEG2_SIZE,
|
||||
PART_IS_EXT(&boot)),
|
||||
0);
|
||||
ck_assert_int_eq(final_hash(&ctx, expected_digest), 0);
|
||||
patch_expected_digest(expected_digest);
|
||||
|
||||
ret = wolfBoot_check_flash_image_elf(PART_BOOT, &entry);
|
||||
|
||||
/* Pre-fix this verified OK (ret 0) with the truncated size. */
|
||||
ck_assert_int_eq(ret, -1);
|
||||
|
||||
unmap_boot_partition();
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* A segment whose file layout (offset + file_size) extends past the
|
||||
* manifest image must be rejected. Pre-fix, intermediate segments were
|
||||
* never bounds-checked (only the last one, after the loop) and an image
|
||||
* whose stored digest matched the out-of-layout walk verified OK. */
|
||||
START_TEST(test_elf_scatter_segment_beyond_fw_size_rejected)
|
||||
{
|
||||
uint8_t expected_digest[WOLFBOOT_SHA_DIGEST_SIZE];
|
||||
unsigned long entry = 0;
|
||||
uint32_t fw_size = (uint32_t)ELF_HDR_SZ_2 + SEG2_SIZE;
|
||||
struct seg_spec segs[2];
|
||||
struct wolfBoot_image boot;
|
||||
wolfBoot_hash_t ctx;
|
||||
int ret;
|
||||
|
||||
map_boot_partition();
|
||||
|
||||
memset(seg1_flash, 0, sizeof(seg1_flash));
|
||||
memset(seg2_flash, 0, sizeof(seg2_flash));
|
||||
segs[0].offset = ELF_HDR_SZ_2;
|
||||
segs[0].filesz = SEG1_SIZE; /* 0x2000 > the 64-byte layout slack */
|
||||
segs[0].paddr = (uint64_t)(uintptr_t)seg1_flash;
|
||||
segs[0].payload = seg1_flash;
|
||||
segs[0].fillsz = SEG1_SIZE;
|
||||
segs[1].offset = ELF_HDR_SZ_2;
|
||||
segs[1].filesz = SEG2_SIZE;
|
||||
segs[1].paddr = (uint64_t)(uintptr_t)seg2_flash;
|
||||
segs[1].payload = seg2_flash;
|
||||
segs[1].fillsz = SEG2_SIZE;
|
||||
|
||||
build_scattered_image_n(segs, 2, fw_size);
|
||||
|
||||
/* Replay the pre-fix walk: both segments are hashed in full at their
|
||||
* paddr locations despite seg0's layout extending past fw_size. */
|
||||
ck_assert_int_eq(wolfBoot_open_image(&boot, PART_BOOT), 0);
|
||||
ck_assert_int_eq(header_hash(&ctx, &boot), 0);
|
||||
ck_assert_int_eq(update_hash_flash_fwimg(&ctx, &boot, 0, (uint32_t)ELF_HDR_SZ_2), 0);
|
||||
ck_assert_int_eq(
|
||||
update_hash_flash_addr(&ctx, (uintptr_t)seg1_flash, SEG1_SIZE,
|
||||
PART_IS_EXT(&boot)),
|
||||
0);
|
||||
ck_assert_int_eq(
|
||||
update_hash_flash_addr(&ctx, (uintptr_t)seg2_flash, SEG2_SIZE,
|
||||
PART_IS_EXT(&boot)),
|
||||
0);
|
||||
ck_assert_int_eq(final_hash(&ctx, expected_digest), 0);
|
||||
patch_expected_digest(expected_digest);
|
||||
|
||||
ret = wolfBoot_check_flash_image_elf(PART_BOOT, &entry);
|
||||
|
||||
/* Pre-fix this verified OK (ret 0). */
|
||||
ck_assert_int_eq(ret, -1);
|
||||
|
||||
unmap_boot_partition();
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* A paddr whose segment range overflows the address space must be
|
||||
* rejected before any flash read. Pre-fix this walked off into
|
||||
* unmapped memory (segfault here; bus fault/hang on target). */
|
||||
START_TEST(test_elf_scatter_paddr_range_overflow_rejected)
|
||||
{
|
||||
unsigned long entry = 0;
|
||||
uint32_t fw_size = IMG_FW_SIZE;
|
||||
struct seg_spec segs[1];
|
||||
int ret;
|
||||
|
||||
map_boot_partition();
|
||||
|
||||
memset(seg2_flash, 0, sizeof(seg2_flash));
|
||||
segs[0].offset = ELF_HDR_SZ;
|
||||
segs[0].filesz = SEG_SIZE;
|
||||
segs[0].paddr = UINT64_MAX - 4; /* +SEG_SIZE wraps past UINT64_MAX */
|
||||
segs[0].payload = seg2_flash;
|
||||
segs[0].fillsz = SEG_SIZE;
|
||||
|
||||
build_scattered_image_n(segs, 1, fw_size);
|
||||
|
||||
ret = wolfBoot_check_flash_image_elf(PART_BOOT, &entry);
|
||||
|
||||
ck_assert_int_eq(ret, -1);
|
||||
|
||||
unmap_boot_partition();
|
||||
}
|
||||
END_TEST
|
||||
|
||||
Suite *elf_scatter_suite(void)
|
||||
{
|
||||
Suite *s = suite_create("ELF flash-scatter image check");
|
||||
TCase *tc = tcase_create("wolfBoot_check_flash_image_elf");
|
||||
tcase_add_test(tc, test_elf_scatter_valid_image_verifies_ok);
|
||||
tcase_add_test(tc, test_elf_scatter_corrupted_segment_rejected);
|
||||
tcase_add_test(tc, test_elf_scatter_filesz_over_32bit_rejected);
|
||||
tcase_add_test(tc, test_elf_scatter_segment_beyond_fw_size_rejected);
|
||||
tcase_add_test(tc, test_elf_scatter_paddr_range_overflow_rejected);
|
||||
tcase_set_timeout(tc, 10);
|
||||
suite_add_tcase(s, tc);
|
||||
return s;
|
||||
|
|
|
|||
Loading…
Reference in New Issue