mirror of https://github.com/wolfSSL/wolfBoot.git
Merge pull request #792 from danielinux/fenrir-fixes-2026-06-10
Fenrir fixes 2026 06 10pull/801/head
commit
d4bb9409d2
|
|
@ -86,4 +86,6 @@ jobs:
|
|||
echo "=== Building unit tests with external paths ==="
|
||||
make -C tools/unit-tests \
|
||||
WOLFBOOT_LIB_WOLFSSL="$(realpath ../external-libs/wolfssl)" \
|
||||
WOLFBOOT_LIB_WOLFPKCS11="$(realpath ../external-libs/wolfPKCS11)"
|
||||
WOLFBOOT_LIB_WOLFPKCS11="$(realpath ../external-libs/wolfPKCS11)" \
|
||||
WOLFBOOT_LIB_WOLFPSA="$(realpath ../external-libs/wolfPSA)" \
|
||||
WOLFBOOT_LIB_WOLFTPM="$(realpath ../external-libs/wolfTPM)"
|
||||
|
|
|
|||
|
|
@ -199,6 +199,7 @@ tools/unit-tests/unit-linux-loader-syssize
|
|||
tools/unit-tests/unit-mpusize
|
||||
tools/unit-tests/unit-otp-keystore
|
||||
tools/unit-tests/unit-tpm-api-names
|
||||
tools/unit-tests/unit-elf-bss-guard
|
||||
|
||||
|
||||
# Elf preprocessing tools
|
||||
|
|
@ -207,6 +208,7 @@ tools/squashelf/**
|
|||
!tools/squashelf/Makefile
|
||||
!tools/squashelf/README.md
|
||||
!tools/squashelf/test-range-overflow.py
|
||||
!tools/squashelf/test-align-overflow.py
|
||||
|
||||
# Generated configuration files
|
||||
.config
|
||||
|
|
|
|||
21
hal/mcxn.c
21
hal/mcxn.c
|
|
@ -71,6 +71,15 @@
|
|||
#include <wolfssl/wolfcrypt/sha256.h>
|
||||
#include <wolfssl/wolfcrypt/sha512.h>
|
||||
|
||||
#ifdef WOLFBOOT_UDS_UID_FALLBACK_FORTEST
|
||||
static NOINLINEFUNCTION void hal_uds_zeroize(void *ptr, size_t len)
|
||||
{
|
||||
volatile uint8_t *p = (volatile uint8_t *)ptr;
|
||||
while (len-- > 0U)
|
||||
*p++ = 0U;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Derive UDS from device UUID for software DICE testing.
|
||||
* NOT secure — UUID is publicly observable. Only enabled with
|
||||
* WOLFBOOT_UDS_UID_FALLBACK_FORTEST. */
|
||||
|
|
@ -121,8 +130,11 @@ int hal_uds_derive_key(uint8_t *out, size_t out_len)
|
|||
ret = wc_Sha384Final(&hash, digest);
|
||||
wc_Sha384Free(&hash);
|
||||
}
|
||||
if (ret != 0)
|
||||
if (ret != 0) {
|
||||
hal_uds_zeroize(uuid_be, sizeof(uuid_be));
|
||||
hal_uds_zeroize(digest, sizeof(digest));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
#elif defined(WOLFBOOT_HASH_SHA256)
|
||||
{
|
||||
|
|
@ -133,14 +145,19 @@ int hal_uds_derive_key(uint8_t *out, size_t out_len)
|
|||
ret = wc_Sha256Final(&hash, digest);
|
||||
wc_Sha256Free(&hash);
|
||||
}
|
||||
if (ret != 0)
|
||||
if (ret != 0) {
|
||||
hal_uds_zeroize(uuid_be, sizeof(uuid_be));
|
||||
hal_uds_zeroize(digest, sizeof(digest));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (copy_len > out_len)
|
||||
copy_len = out_len;
|
||||
XMEMCPY(out, digest, copy_len);
|
||||
hal_uds_zeroize(digest, sizeof(digest));
|
||||
hal_uds_zeroize(uuid_be, sizeof(uuid_be));
|
||||
return 0;
|
||||
#endif /* WOLFBOOT_UDS_UID_FALLBACK_FORTEST */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3261,6 +3261,27 @@ int hal_flash_write(uint32_t address, const uint8_t *data, int len)
|
|||
sector, offset, xfer, pos);
|
||||
#endif
|
||||
|
||||
#if FLASH_CFI_WIDTH == 16
|
||||
/* sub-word (1-byte) write: read-modify-write the containing 16-bit word */
|
||||
if (nwords == 0) {
|
||||
uint16_t word = FLASH_IO16_READ(sector, offset);
|
||||
if ((address % 2) == 0)
|
||||
word = ((uint16_t)data[pos] << 8) | (word & 0x00FFU);
|
||||
else
|
||||
word = (word & 0xFF00U) | (uint16_t)data[pos];
|
||||
hal_flash_unlock_sector(sector);
|
||||
FLASH_IO8_WRITE(sector, offset, AMD_CMD_WRITE_TO_BUFFER);
|
||||
FLASH_IO16_WRITE(sector, offset, 0);
|
||||
FLASH_IO16_WRITE(sector, offset, word);
|
||||
FLASH_IO8_WRITE(sector, offset, AMD_CMD_WRITE_BUFFER_CONFIRM);
|
||||
hal_flash_status_wait(sector, 0x44, 200*1000);
|
||||
address++;
|
||||
pos++;
|
||||
len--;
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
hal_flash_unlock_sector(sector);
|
||||
FLASH_IO8_WRITE(sector, offset, AMD_CMD_WRITE_TO_BUFFER);
|
||||
#if FLASH_CFI_WIDTH == 16
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ int RAMFUNCTION hal_flash_erase(uint32_t address, int len)
|
|||
return -1;
|
||||
end_address = (address - FLASHMEM_ADDRESS_SPACE) + len - 1;
|
||||
for (p = (address - FLASHMEM_ADDRESS_SPACE);
|
||||
p < end_address;
|
||||
p <= end_address;
|
||||
p += FLASH_PAGE_SIZE)
|
||||
{
|
||||
if (p < FLASH_BANK2_BASE_REL) {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@
|
|||
#define GPT_MBR_BOOTSIG_OFFSET 0x01FE
|
||||
#define GPT_MBR_BOOTSIG_VALUE 0xAA55
|
||||
#define GPT_PART_ENTRY_SIZE 256
|
||||
/* UEFI reserves 128 partition entries by default; bound the partition-entry
|
||||
* array (n_part * array_sz) used by the CRC scan to this many entries so a
|
||||
* crafted header cannot force an unbounded number of disk reads. */
|
||||
#define GPT_MAX_PART_ENTRIES 128
|
||||
|
||||
/**
|
||||
* @brief MBR partition table entry structure.
|
||||
|
|
|
|||
|
|
@ -106,6 +106,8 @@ enum ata_security_state ata_security_get_state(int);
|
|||
/* Constants for security set commands */
|
||||
#define ATA_SECURITY_COMMAND_LEN (256 * 2)
|
||||
#define ATA_SECURITY_PASSWORD_OFFSET (1 * 2)
|
||||
/* ATA-8 ACS: password field is words 1-16 = 32 bytes */
|
||||
#define ATA_SECURITY_PASSWORD_LEN 32
|
||||
#define ATA_ERR_BUSY -2
|
||||
#define ATA_ERR_OP_IN_PROGRESS -3
|
||||
#define ATA_ERR_OP_NOT_IN_PROGRESS -4
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ void io_write32(uint16_t port, uint32_t value);
|
|||
uint32_t io_read32(uint16_t port);
|
||||
void reset(uint8_t warm);
|
||||
void delay(int msec);
|
||||
void panic(void);
|
||||
__attribute__((noreturn)) void panic(void);
|
||||
void cpuid(uint32_t eax_param,
|
||||
uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx);
|
||||
int cpuid_is_1gb_page_supported(void);
|
||||
|
|
|
|||
16
src/disk.c
16
src/disk.c
|
|
@ -149,7 +149,8 @@ int disk_open(int drv)
|
|||
wolfBoot_printf("Found GPT PTE at sector %u\r\n", gpt_lba);
|
||||
|
||||
/* Read GPT header */
|
||||
r = disk_read(drv, GPT_SECTOR_SIZE * gpt_lba, GPT_SECTOR_SIZE, sector);
|
||||
r = disk_read(drv, (uint64_t)GPT_SECTOR_SIZE * gpt_lba, GPT_SECTOR_SIZE,
|
||||
sector);
|
||||
if (r < 0) {
|
||||
wolfBoot_printf("Disk read failed\r\n");
|
||||
Drives[drv].is_open = 0;
|
||||
|
|
@ -169,6 +170,19 @@ int disk_open(int drv)
|
|||
array_addr = ptable.start_array * GPT_SECTOR_SIZE;
|
||||
bytes_left = (uint64_t)ptable.n_part * ptable.array_sz;
|
||||
|
||||
/* Reject an implausibly large partition-entry array before scanning
|
||||
* it to compute the CRC. n_part and array_sz come straight from the
|
||||
* (attacker-craftable) GPT header whose only gate is a recomputable
|
||||
* CRC32, so without this bound a header with e.g. n_part=0xFFFFFFFF
|
||||
* forces ~10^9 disk reads before the part_crc mismatch is caught: a
|
||||
* pre-auth denial of service. */
|
||||
if (bytes_left >
|
||||
(uint64_t)GPT_MAX_PART_ENTRIES * GPT_PART_ENTRY_SIZE) {
|
||||
wolfBoot_printf("GPT partition entry array too large\r\n");
|
||||
Drives[drv].is_open = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
gpt_crc32_init(&part_crc);
|
||||
while (bytes_left > 0) {
|
||||
chunk = GPT_SECTOR_SIZE;
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ int elf_load_image_mmu(uint8_t *image, uint32_t image_sz, uintptr_t *pentry,
|
|||
* Only protect headers [i+1, entry_count) not yet parsed.
|
||||
* Use memmove for safe in-place ELF loading (e.g., RAM boot). */
|
||||
if (i + 1 >= entry_count || /* last header, nothing left to protect */
|
||||
(uint8_t*)vaddr + file_size <= (entry_off + ((i + 1) * entry_size)) ||
|
||||
(uint8_t*)vaddr + mem_size <= (entry_off + ((i + 1) * entry_size)) ||
|
||||
(uint8_t*)vaddr > (entry_off + entry_count * entry_size))
|
||||
{
|
||||
memmove((void*)vaddr, image + offset, file_size);
|
||||
|
|
|
|||
28
src/fdt.c
28
src/fdt.c
|
|
@ -96,7 +96,11 @@ static inline int fdt_data_size_(void *fdt)
|
|||
{
|
||||
/* the last portion of a FDT is the DT string, so use its offset and size to
|
||||
* determine total size */
|
||||
return fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt);
|
||||
uint64_t off = (uint64_t)fdt_off_dt_strings(fdt);
|
||||
uint64_t sz = (uint64_t)fdt_size_dt_strings(fdt);
|
||||
if (off + sz > (uint64_t)UINT32_MAX)
|
||||
return -FDT_ERR_BADOFFSET;
|
||||
return (int)(off + sz);
|
||||
}
|
||||
|
||||
static const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int len)
|
||||
|
|
@ -277,9 +281,15 @@ static void fdt_del_last_string_(void *fdt, const char *s)
|
|||
|
||||
static int fdt_splice_(void *fdt, void *splicepoint, int oldlen, int newlen)
|
||||
{
|
||||
int data_size;
|
||||
char *p, *end;
|
||||
|
||||
data_size = fdt_data_size_(fdt);
|
||||
if (data_size < 0)
|
||||
return data_size;
|
||||
|
||||
p = splicepoint;
|
||||
end = (char*)fdt + fdt_data_size_(fdt);
|
||||
end = (char*)fdt + data_size;
|
||||
if (((p + oldlen) < p) || ((p + oldlen) > end)) {
|
||||
return -FDT_ERR_BADOFFSET;
|
||||
}
|
||||
|
|
@ -329,7 +339,13 @@ static int fdt_resize_property_(void *fdt, int nodeoffset, const char *name,
|
|||
static int fdt_splice_string_(void *fdt, int newlen)
|
||||
{
|
||||
int err;
|
||||
void *p = (char*)fdt + fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt);
|
||||
uint32_t off = fdt_off_dt_strings(fdt);
|
||||
uint32_t sz = fdt_size_dt_strings(fdt);
|
||||
void *p;
|
||||
|
||||
if (sz > UINT32_MAX - off)
|
||||
return -FDT_ERR_BADOFFSET;
|
||||
p = (char*)fdt + off + sz;
|
||||
|
||||
if ((err = fdt_splice_(fdt, p, 0, newlen))) {
|
||||
return err;
|
||||
|
|
@ -794,8 +810,10 @@ int fdt_del_node(void *fdt, int nodeoffset)
|
|||
/* adjust the actual total size in the FDT header */
|
||||
int fdt_shrink(void* fdt)
|
||||
{
|
||||
uint32_t total_size = fdt_data_size_(fdt);
|
||||
return fdt_set_totalsize(fdt, total_size);
|
||||
int total_size = fdt_data_size_(fdt);
|
||||
if (total_size < 0)
|
||||
return total_size;
|
||||
return fdt_set_totalsize(fdt, (uint32_t)total_size);
|
||||
}
|
||||
|
||||
/* FTD Fixup API's */
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ static int fwtpm_nv_read(void *ctx, word32 offset, byte *buf, word32 size)
|
|||
{
|
||||
uint8_t *nv = (uint8_t *)ctx;
|
||||
|
||||
if (nv == NULL || buf == NULL || offset > WCS_FWTPM_NV_SIZE ||
|
||||
if (nv == NULL || buf == NULL || offset >= WCS_FWTPM_NV_SIZE ||
|
||||
size > (WCS_FWTPM_NV_SIZE - offset)) {
|
||||
return BAD_FUNC_ARG;
|
||||
}
|
||||
|
|
@ -74,7 +74,7 @@ static int fwtpm_nv_write(void *ctx, word32 offset, const byte *buf,
|
|||
{
|
||||
uint8_t *nv = (uint8_t *)ctx;
|
||||
|
||||
if (nv == NULL || buf == NULL || offset > WCS_FWTPM_NV_SIZE ||
|
||||
if (nv == NULL || buf == NULL || offset >= WCS_FWTPM_NV_SIZE ||
|
||||
size > (WCS_FWTPM_NV_SIZE - offset)) {
|
||||
return BAD_FUNC_ARG;
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ static int fwtpm_nv_erase(void *ctx, word32 offset, word32 size)
|
|||
{
|
||||
uint8_t *nv = (uint8_t *)ctx;
|
||||
|
||||
if (nv == NULL || offset > WCS_FWTPM_NV_SIZE ||
|
||||
if (nv == NULL || offset >= WCS_FWTPM_NV_SIZE ||
|
||||
size > (WCS_FWTPM_NV_SIZE - offset)) {
|
||||
return BAD_FUNC_ARG;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,6 +205,14 @@ int gpt_parse_partition(const uint8_t *entry_data, uint32_t entry_size,
|
|||
if (pe->last == 0) {
|
||||
return -1;
|
||||
}
|
||||
/* Reject extents whose byte offset would overflow uint64_t: (pe->last + 1)
|
||||
* must not wrap and (pe->last + 1) * GPT_SECTOR_SIZE must stay representable.
|
||||
* Without this, pe->last = UINT64_MAX yields part->end = UINT64_MAX, which
|
||||
* defeats the bounds check in disk_part_read(). pe->first <= pe->last, so
|
||||
* bounding pe->last also bounds the part->start multiply. */
|
||||
if (pe->last >= (UINT64_MAX / GPT_SECTOR_SIZE)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Extract partition info (convert LBA to byte offsets) */
|
||||
part->start = pe->first * GPT_SECTOR_SIZE;
|
||||
|
|
|
|||
|
|
@ -2538,10 +2538,11 @@ int keyslot_id_by_sha(const uint8_t *hint)
|
|||
int match_id = -1;
|
||||
|
||||
for (id = 0; id < keystore_num_pubkeys(); id++) {
|
||||
int match;
|
||||
key_hash(id, digest);
|
||||
if ((match_id < 0) && keyslot_CT_hint_matches(digest, hint)) {
|
||||
match = keyslot_CT_hint_matches(digest, hint);
|
||||
if (match && (match_id < 0))
|
||||
match_id = id;
|
||||
}
|
||||
}
|
||||
return match_id;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -265,7 +265,12 @@ int mb2_build_boot_info_header(uint8_t *mb2_boot_info,
|
|||
idx = (uint8_t*)hdr + sizeof(*hdr);
|
||||
hdr->reserved = 0;
|
||||
header_length = ((struct mb2_header *)mb2_header)->header_length;
|
||||
if (header_length < sizeof(struct mb2_header))
|
||||
/* Per the Multiboot2 spec the whole header must lie within the first
|
||||
* MB2_HEADER_MAX_OFF bytes of the image. Bounding header_length here keeps
|
||||
* the tag walker inside the header window and prevents an oversized value
|
||||
* (e.g. 0xFFFFFFFF) from inflating its end pointer into an OOB read. */
|
||||
if (header_length < sizeof(struct mb2_header) ||
|
||||
header_length > MB2_HEADER_MAX_OFF)
|
||||
return -1;
|
||||
info_req_tag =
|
||||
(struct mb2_tag_info_req *)mb2_find_tag_by_type(
|
||||
|
|
|
|||
12
src/pci.c
12
src/pci.c
|
|
@ -510,6 +510,18 @@ static int pci_program_bar(uint8_t bus, uint8_t dev, uint8_t fun,
|
|||
goto restore_bar;
|
||||
}
|
||||
|
||||
/* pci_enum_next_aligned32 only validates the aligned start address against
|
||||
* the pool limit, not the end of the region. A malformed BAR advertising a
|
||||
* huge length (e.g. a 2GB MMIO BAR with bar_align == 0x80000000) can pass
|
||||
* that check at a start that leaves [bar_value, bar_value + length) running
|
||||
* past the pool, wrapping the 32-bit cursor below to 0. Reject it here so
|
||||
* the whole region fits under the limit without overflowing. */
|
||||
if ((uint64_t)bar_value + (uint64_t)length > (uint64_t)limit) {
|
||||
PCI_DEBUG_PRINTF("PCI device region does not fit in the pool... skipping\r\n");
|
||||
ret = -1;
|
||||
goto restore_bar;
|
||||
}
|
||||
|
||||
pci_config_write32(bus, dev, fun, bar_off, bar_value);
|
||||
if (*is_64bit)
|
||||
pci_config_write32(bus, dev, fun, bar_off + 4, 0x0);
|
||||
|
|
|
|||
|
|
@ -360,6 +360,35 @@ static void update_store_size(struct obj_hdr *hdr, uint32_t size)
|
|||
cache_commit(0);
|
||||
}
|
||||
|
||||
static void erase_object_payload(uint8_t *buf)
|
||||
{
|
||||
uint32_t erase_off;
|
||||
uint32_t erase_end;
|
||||
uint32_t sector_base;
|
||||
|
||||
erase_off = (uint32_t)((uintptr_t)buf - (uintptr_t)vault_base) +
|
||||
(2U * sizeof(uint32_t));
|
||||
erase_end = (uint32_t)((uintptr_t)buf - (uintptr_t)vault_base) +
|
||||
KEYVAULT_OBJ_SIZE;
|
||||
sector_base = erase_off - (erase_off % WOLFBOOT_SECTOR_SIZE);
|
||||
|
||||
while (sector_base < erase_end) {
|
||||
uint32_t erase_start = erase_off;
|
||||
uint32_t erase_stop = sector_base + WOLFBOOT_SECTOR_SIZE;
|
||||
|
||||
if (erase_start < sector_base)
|
||||
erase_start = sector_base;
|
||||
if (erase_stop > erase_end)
|
||||
erase_stop = erase_end;
|
||||
|
||||
memcpy(cached_sector, vault_base + sector_base, WOLFBOOT_SECTOR_SIZE);
|
||||
memset(cached_sector + (erase_start - sector_base), 0xFF,
|
||||
erase_stop - erase_start);
|
||||
cache_commit(sector_base);
|
||||
sector_base += WOLFBOOT_SECTOR_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Find a free handle in openstores_handles[] array
|
||||
* to manage the interaction with the API.
|
||||
*
|
||||
|
|
@ -381,6 +410,7 @@ int wolfPKCS11_Store_Open(int type, CK_ULONG id1, CK_ULONG id2, int read,
|
|||
{
|
||||
struct store_handle *handle;
|
||||
uint8_t *buf;
|
||||
int is_new = 0;
|
||||
|
||||
/* Check if there is one handle available to open the slot */
|
||||
handle = find_free_handle();
|
||||
|
|
@ -409,6 +439,7 @@ int wolfPKCS11_Store_Open(int type, CK_ULONG id1, CK_ULONG id2, int read,
|
|||
*store = NULL;
|
||||
return NOT_AVAILABLE_E;
|
||||
}
|
||||
is_new = 1;
|
||||
} else { /* buf != NULL, readonly */
|
||||
handle->hdr = find_object_header(type, id1, id2);
|
||||
if (!handle->hdr) {
|
||||
|
|
@ -430,6 +461,12 @@ int wolfPKCS11_Store_Open(int type, CK_ULONG id1, CK_ULONG id2, int read,
|
|||
handle->flags &= ~STORE_FLAGS_READONLY;
|
||||
/* Truncate the slot when opening in write mode */
|
||||
update_store_size(handle->hdr, 2 * sizeof(uint32_t));
|
||||
/* Erase object data sectors to clear residual key material from a
|
||||
* prior (longer) payload. New objects are already in a fresh sector
|
||||
* from create_object(), so only do this for existing objects. */
|
||||
if (!is_new) {
|
||||
erase_object_payload(buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -126,6 +126,12 @@ static void bitmap_put(uint32_t pos, int val)
|
|||
uint32_t bit = pos % 8;
|
||||
uint8_t *bitmap = cached_sector + sizeof(uint32_t);
|
||||
|
||||
/* Reject out-of-range positions (e.g. a power-fault-corrupted hdr->pos
|
||||
* left as erased flash) to avoid an out-of-bounds write past the
|
||||
* bitmap, which lives within cached_sector. */
|
||||
if (pos >= KEYVAULT_MAX_ITEMS)
|
||||
return;
|
||||
|
||||
if (val != 0) {
|
||||
bitmap[octet] |= (1 << bit);
|
||||
} else {
|
||||
|
|
@ -353,6 +359,35 @@ static void update_store_size(struct obj_hdr *hdr, uint32_t size)
|
|||
cache_commit(0);
|
||||
}
|
||||
|
||||
static void erase_object_payload(uint8_t *buf)
|
||||
{
|
||||
uint32_t erase_off;
|
||||
uint32_t erase_end;
|
||||
uint32_t sector_base;
|
||||
|
||||
erase_off = (uint32_t)((uintptr_t)buf - (uintptr_t)vault_base) +
|
||||
(2U * sizeof(uint32_t));
|
||||
erase_end = (uint32_t)((uintptr_t)buf - (uintptr_t)vault_base) +
|
||||
KEYVAULT_OBJ_SIZE;
|
||||
sector_base = erase_off - (erase_off % WOLFBOOT_SECTOR_SIZE);
|
||||
|
||||
while (sector_base < erase_end) {
|
||||
uint32_t erase_start = erase_off;
|
||||
uint32_t erase_stop = sector_base + WOLFBOOT_SECTOR_SIZE;
|
||||
|
||||
if (erase_start < sector_base)
|
||||
erase_start = sector_base;
|
||||
if (erase_stop > erase_end)
|
||||
erase_stop = erase_end;
|
||||
|
||||
memcpy(cached_sector, vault_base + sector_base, WOLFBOOT_SECTOR_SIZE);
|
||||
memset(cached_sector + (erase_start - sector_base), 0xFF,
|
||||
erase_stop - erase_start);
|
||||
cache_commit(sector_base);
|
||||
sector_base += WOLFBOOT_SECTOR_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Find a free handle in openstores_handles[] array
|
||||
* to manage the interaction with the API.
|
||||
*
|
||||
|
|
@ -374,6 +409,7 @@ int wolfPSA_Store_Open(int type, unsigned long id1, unsigned long id2, int read,
|
|||
{
|
||||
struct store_handle *handle;
|
||||
uint8_t *buf;
|
||||
int is_new = 0;
|
||||
|
||||
/* Check if there is one handle available to open the slot */
|
||||
handle = find_free_handle();
|
||||
|
|
@ -402,6 +438,7 @@ int wolfPSA_Store_Open(int type, unsigned long id1, unsigned long id2, int read,
|
|||
*store = NULL;
|
||||
return NOT_AVAILABLE_E;
|
||||
}
|
||||
is_new = 1;
|
||||
} else { /* buf != NULL, readonly */
|
||||
handle->hdr = find_object_header(type, id1, id2);
|
||||
if (!handle->hdr) {
|
||||
|
|
@ -423,6 +460,11 @@ int wolfPSA_Store_Open(int type, unsigned long id1, unsigned long id2, int read,
|
|||
handle->flags &= ~STORE_FLAGS_READONLY;
|
||||
/* Truncate the slot when opening in write mode */
|
||||
update_store_size(handle->hdr, 2 * sizeof(uint32_t));
|
||||
/* Erase the object data region so a shorter write does not leave
|
||||
* residual key material from the previous (longer) payload. */
|
||||
if (!is_new) {
|
||||
erase_object_payload(buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
16
src/sdhci.c
16
src/sdhci.c
|
|
@ -1586,7 +1586,13 @@ int disk_read(int drv, uint64_t start, uint32_t count, uint8_t *buf)
|
|||
#endif
|
||||
|
||||
while (count > 0) {
|
||||
block_addr = (start / SDHCI_BLOCK_SIZE);
|
||||
uint64_t block_addr64 = (start / SDHCI_BLOCK_SIZE);
|
||||
/* SD/SDHC command argument is 32-bit: reject addresses that would
|
||||
* otherwise be silently truncated and wrap to an in-range sector. */
|
||||
if (block_addr64 > 0xFFFFFFFFUL) {
|
||||
return -1;
|
||||
}
|
||||
block_addr = (uint32_t)block_addr64;
|
||||
read_sz = count;
|
||||
if (read_sz > SDHCI_BLOCK_SIZE) {
|
||||
read_sz = SDHCI_BLOCK_SIZE;
|
||||
|
|
@ -1641,7 +1647,13 @@ int disk_write(int drv, uint64_t start, uint32_t count, const uint8_t *buf)
|
|||
#endif
|
||||
|
||||
while (count > 0) {
|
||||
block_addr = (start / SDHCI_BLOCK_SIZE);
|
||||
uint64_t block_addr64 = (start / SDHCI_BLOCK_SIZE);
|
||||
/* SD/SDHC command argument is 32-bit: reject addresses that would
|
||||
* otherwise be silently truncated and wrap to an in-range sector. */
|
||||
if (block_addr64 > 0xFFFFFFFFUL) {
|
||||
return -1;
|
||||
}
|
||||
block_addr = (uint32_t)block_addr64;
|
||||
write_sz = count;
|
||||
if (write_sz > SDHCI_BLOCK_SIZE) {
|
||||
write_sz = SDHCI_BLOCK_SIZE;
|
||||
|
|
|
|||
|
|
@ -436,6 +436,15 @@ int sata_unlock_disk(int drv, int freeze)
|
|||
r = sata_get_unlock_secret(secret, &secret_size);
|
||||
if (r != 0)
|
||||
goto cleanup;
|
||||
/* No secret_size check here on purpose: the unlock secret is a
|
||||
* variable-length base64 string (ATA_SECRET_RANDOM_BYTES random bytes
|
||||
* encoded, e.g. 29 bytes), not a fixed ATA_UNLOCK_DISK_KEY_SZ key, so an
|
||||
* equality check is wrong. A size guard is also unnecessary:
|
||||
* sata_get_unlock_secret() passes sizeof(secret) (ATA_UNLOCK_DISK_KEY_SZ)
|
||||
* as the wolfBoot_unseal() capacity, so secret_size can never exceed the
|
||||
* buffer. The non-NUL-terminated read in the ATA passphrase path is bounded
|
||||
* separately by strnlen(passphrase, ATA_SECURITY_PASSWORD_LEN) in
|
||||
* security_command_passphrase(). */
|
||||
ata_st = ata_security_get_state(drv);
|
||||
wolfBoot_printf("ATA: Security state SEC%d\r\n", ata_st);
|
||||
#if defined(TARGET_x86_fsp_qemu)
|
||||
|
|
|
|||
|
|
@ -457,13 +457,19 @@ static int security_command_passphrase(int drv, uint8_t ata_cmd,
|
|||
struct hba_cmd_table *tbl;
|
||||
struct fis_reg_h2d *cmdfis;
|
||||
struct ata_drive *ata = &ATA_Drv[drv];
|
||||
size_t passphrase_len = 0;
|
||||
int ret;
|
||||
int slot = prepare_cmd_h2d_slot(drv, buffer,
|
||||
ATA_SECURITY_COMMAND_LEN, 1);
|
||||
memset(buffer, 0, ATA_SECURITY_COMMAND_LEN);
|
||||
if (master)
|
||||
buffer[0] = 0x1;
|
||||
memcpy(buffer + ATA_SECURITY_PASSWORD_OFFSET, passphrase, strlen(passphrase));
|
||||
while (passphrase_len < ATA_SECURITY_PASSWORD_LEN &&
|
||||
passphrase[passphrase_len] != '\0') {
|
||||
passphrase_len++;
|
||||
}
|
||||
memcpy(buffer + ATA_SECURITY_PASSWORD_OFFSET, passphrase,
|
||||
passphrase_len);
|
||||
if (slot < 0) {
|
||||
return slot;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -300,9 +300,11 @@ uint64_t hal_get_timer_us(void)
|
|||
* This function is used for error handling when the system encounters an unrecoverable issue.
|
||||
* It enters an infinite loop, causing a panic state.
|
||||
*/
|
||||
void panic()
|
||||
__attribute__((noreturn)) void panic()
|
||||
{
|
||||
hlt();
|
||||
while(1) {
|
||||
hlt();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
/* TGL mptable */
|
||||
static struct mptable _mptable = {
|
||||
.mpf = {
|
||||
.signature = "_MP_",
|
||||
.signature = {'_', 'M', 'P', '_'},
|
||||
.phy_addr = (int)MPTABLE_LOAD_BASE + sizeof(struct mp_float),
|
||||
.length = 1,
|
||||
.spec_rev = 0x4,
|
||||
|
|
@ -45,7 +45,7 @@ static struct mptable _mptable = {
|
|||
.feature5 = 0
|
||||
},
|
||||
.mpc_table = {
|
||||
.signature = MPC_SIGNATURE,
|
||||
.signature = {'P', 'C', 'M', 'P'},
|
||||
.base_table_len = sizeof(struct mp_conf_table_header) +
|
||||
sizeof(struct mp_conf_entry_processor) * MP_CPU_NUM_ENTRY +
|
||||
sizeof(struct mp_conf_entry_bus) * MP_BUS_NUM_ENTRY +
|
||||
|
|
@ -248,7 +248,7 @@ static struct mptable _mptable = {
|
|||
/* MPtables for qemu */
|
||||
struct mptable _mptable = {
|
||||
.mpf = {
|
||||
.signature = "_MP_",
|
||||
.signature = {'_', 'M', 'P', '_'},
|
||||
.phy_addr = (int)MPTABLE_LOAD_BASE + sizeof(struct mp_float),
|
||||
.length = 1,
|
||||
.spec_rev = 1,
|
||||
|
|
@ -260,7 +260,7 @@ struct mptable _mptable = {
|
|||
.feature5 = 0
|
||||
},
|
||||
.mpc_table = {
|
||||
.signature = MPC_SIGNATURE,
|
||||
.signature = {'P', 'C', 'M', 'P'},
|
||||
.base_table_len = sizeof(struct mp_conf_table_header) +
|
||||
sizeof(struct mp_conf_entry_processor) * MP_CPU_NUM_ENTRY +
|
||||
sizeof(struct mp_conf_entry_bus) * MP_BUS_NUM_ENTRY +
|
||||
|
|
|
|||
|
|
@ -156,12 +156,12 @@ static void x86_paging_setup_ptp(uint64_t* e)
|
|||
{
|
||||
uint8_t *ptp;
|
||||
|
||||
ptp = &page_table_pages[page_table_page_used * PAGE_TABLE_PAGE_SIZE];
|
||||
page_table_page_used++;
|
||||
if (page_table_page_used == WOLFBOOT_PTP_NUM) {
|
||||
if (page_table_page_used >= WOLFBOOT_PTP_NUM) {
|
||||
wolfBoot_printf("No more page table page structure\r\n");
|
||||
panic();
|
||||
}
|
||||
ptp = &page_table_pages[page_table_page_used * PAGE_TABLE_PAGE_SIZE];
|
||||
page_table_page_used++;
|
||||
x86_paging_setup_entry(e, (uintptr_t)ptp);
|
||||
memset(ptp, 0, PAGE_TABLE_PAGE_SIZE);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -614,6 +614,7 @@ cleanup:
|
|||
wc_ForceZero(priv_der, sizeof(priv_der));
|
||||
if (rsa_init)
|
||||
wc_FreeRsaKey(&k);
|
||||
wc_ForceZero(&k, sizeof(k));
|
||||
if (exit_code != 0)
|
||||
exit(exit_code);
|
||||
}
|
||||
|
|
@ -725,6 +726,7 @@ cleanup:
|
|||
fclose(fpriv);
|
||||
if (key_init)
|
||||
wc_ecc_free(&k);
|
||||
wc_ForceZero(&k, sizeof(k));
|
||||
wc_ForceZero(d, sizeof(d));
|
||||
wc_ForceZero(priv_der, sizeof(priv_der));
|
||||
|
||||
|
|
@ -799,6 +801,7 @@ cleanup:
|
|||
wc_ForceZero(priv, sizeof(priv));
|
||||
if (key_init == 0)
|
||||
wc_ed25519_free(&k);
|
||||
wc_ForceZero(&k, sizeof(k));
|
||||
if (exit_code != 0)
|
||||
exit(exit_code);
|
||||
}
|
||||
|
|
@ -859,6 +862,7 @@ cleanup:
|
|||
wc_ForceZero(priv, sizeof(priv));
|
||||
if (key_init == 0)
|
||||
wc_ed448_free(&k);
|
||||
wc_ForceZero(&k, sizeof(k));
|
||||
if (exit_code != 0)
|
||||
exit(exit_code);
|
||||
}
|
||||
|
|
@ -874,6 +878,8 @@ static void keygen_lms(const char *priv_fname, uint32_t id_mask)
|
|||
word32 pub_len = sizeof(lms_pub);
|
||||
int lms_levels, lms_height, lms_winternitz;
|
||||
char *env_lms_levels, *env_lms_height, *env_lms_winternitz;
|
||||
int exit_code = 0;
|
||||
int key_init = 0;
|
||||
|
||||
lms_levels = LMS_LEVELS;
|
||||
lms_height = LMS_HEIGHT;
|
||||
|
|
@ -892,15 +898,18 @@ static void keygen_lms(const char *priv_fname, uint32_t id_mask)
|
|||
ret = wc_LmsKey_Init(&key, NULL, INVALID_DEVID);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "error: wc_LmsKey_Init returned %d\n", ret);
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
key_init = 1;
|
||||
|
||||
ret = wc_LmsKey_SetParameters(&key, lms_levels, lms_height, lms_winternitz);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "error: wc_LmsKey_SetParameters(%d, %d, %d)" \
|
||||
" returned %d\n", lms_levels, lms_height,
|
||||
lms_winternitz, ret);
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
printf("info: using LMS parameters: L%d-H%d-W%d\n", lms_levels,
|
||||
|
|
@ -909,37 +918,43 @@ static void keygen_lms(const char *priv_fname, uint32_t id_mask)
|
|||
ret = wc_LmsKey_SetWriteCb(&key, lms_write_key);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "error: wc_LmsKey_SetWriteCb returned %d\n", ret);
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = wc_LmsKey_SetReadCb(&key, lms_read_key);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "error: wc_LmsKey_SetReadCb returned %d\n", ret);
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = wc_LmsKey_SetContext(&key, (void *) priv_fname);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "error: wc_LmsKey_SetContext returned %d\n", ret);
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = wc_LmsKey_MakeKey(&key, &rng);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "error: wc_LmsKey_MakeKey returned %d\n", ret);
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = wc_LmsKey_ExportPubRaw(&key, lms_pub, &pub_len);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "error: wc_LmsKey_ExportPubRaw returned %d\n", ret);
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (pub_len != sizeof(lms_pub)) {
|
||||
fprintf(stderr, "error: wc_LmsKey_ExportPubRaw returned pub_len=%d\n" \
|
||||
", expected %d\n", pub_len, (int)sizeof(lms_pub));
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Append the public key to the private keyfile. */
|
||||
|
|
@ -947,7 +962,8 @@ static void keygen_lms(const char *priv_fname, uint32_t id_mask)
|
|||
if (!fpriv) {
|
||||
fprintf(stderr, "error: fopen(%s, \"r+\") returned %d\n", priv_fname,
|
||||
ret);
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
fseek(fpriv, 64, SEEK_SET);
|
||||
|
|
@ -957,14 +973,20 @@ static void keygen_lms(const char *priv_fname, uint32_t id_mask)
|
|||
if (exportPubKey) {
|
||||
if (export_pubkey_file(priv_fname, lms_pub, KEYSTORE_PUBKEY_SIZE_LMS) != 0) {
|
||||
fprintf(stderr, "Unable to export public key to file\n");
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
keystore_add(AUTH_KEY_LMS, lms_pub, KEYSTORE_PUBKEY_SIZE_LMS, priv_fname, id_mask);
|
||||
|
||||
wc_LmsKey_Free(&key);
|
||||
wc_ForceZero(&key, sizeof(key));
|
||||
cleanup:
|
||||
if (key_init) {
|
||||
wc_LmsKey_Free(&key);
|
||||
wc_ForceZero(&key, sizeof(key));
|
||||
}
|
||||
if (exit_code)
|
||||
exit(exit_code);
|
||||
}
|
||||
|
||||
#include "../xmss/xmss_common.h"
|
||||
|
|
@ -978,12 +1000,16 @@ static void keygen_xmss(const char *priv_fname, uint32_t id_mask)
|
|||
byte xmss_pub[XMSS_SHA256_PUBLEN];
|
||||
char *xmss_params = getenv("XMSS_PARAMS");
|
||||
word32 pub_len = sizeof(xmss_pub);
|
||||
int exit_code = 0;
|
||||
int key_init = 0;
|
||||
|
||||
ret = wc_XmssKey_Init(&key, NULL, INVALID_DEVID);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "error: wc_XmssKey_Init returned %d\n", ret);
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
key_init = 1;
|
||||
|
||||
if (xmss_params == NULL)
|
||||
xmss_params = WOLFBOOT_XMSS_PARAMS;
|
||||
|
|
@ -992,7 +1018,8 @@ static void keygen_xmss(const char *priv_fname, uint32_t id_mask)
|
|||
if (ret != 0) {
|
||||
fprintf(stderr, "error: wc_XmssKey_SetParamStr(%s)" \
|
||||
" returned %d\n", xmss_params, ret);
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
printf("info: using XMSS parameters: %s\n", xmss_params);
|
||||
|
|
@ -1000,26 +1027,30 @@ static void keygen_xmss(const char *priv_fname, uint32_t id_mask)
|
|||
ret = wc_XmssKey_SetWriteCb(&key, xmss_write_key);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "error: wc_XmssKey_SetWriteCb returned %d\n", ret);
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = wc_XmssKey_SetReadCb(&key, xmss_read_key);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "error: wc_XmssKey_SetReadCb returned %d\n", ret);
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = wc_XmssKey_SetContext(&key, (void *) priv_fname);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "error: wc_XmssKey_SetContext returned %d\n", ret);
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Make the key pair. */
|
||||
ret = wc_XmssKey_MakeKey(&key, &rng);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "error: wc_XmssKey_MakeKey returned %d\n", ret);
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Get the XMSS/XMSS^MT secret key length. */
|
||||
|
|
@ -1027,19 +1058,22 @@ static void keygen_xmss(const char *priv_fname, uint32_t id_mask)
|
|||
if (ret != 0 || priv_sz <= 0) {
|
||||
printf("error: wc_XmssKey_GetPrivLen returned %d\n",
|
||||
ret);
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = wc_XmssKey_ExportPubRaw(&key, xmss_pub, &pub_len);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "error: wc_XmssKey_ExportPubRaw returned %d\n", ret);
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (pub_len != sizeof(xmss_pub)) {
|
||||
fprintf(stderr, "error: wc_XmssKey_ExportPubRaw returned pub_len=%d\n" \
|
||||
", expected %d\n", pub_len, (int)sizeof(xmss_pub));
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Append the public key to the private keyfile. */
|
||||
|
|
@ -1047,7 +1081,8 @@ static void keygen_xmss(const char *priv_fname, uint32_t id_mask)
|
|||
if (!fpriv) {
|
||||
fprintf(stderr, "error: fopen(%s, \"r+\") returned %d\n", priv_fname,
|
||||
ret);
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
fseek(fpriv, priv_sz, SEEK_SET);
|
||||
|
|
@ -1057,15 +1092,20 @@ static void keygen_xmss(const char *priv_fname, uint32_t id_mask)
|
|||
if (exportPubKey) {
|
||||
if (export_pubkey_file(priv_fname, xmss_pub, KEYSTORE_PUBKEY_SIZE_XMSS) != 0) {
|
||||
fprintf(stderr, "Unable to export public key to file\n");
|
||||
exit(1);
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
keystore_add(AUTH_KEY_XMSS, xmss_pub, KEYSTORE_PUBKEY_SIZE_XMSS, priv_fname, id_mask);
|
||||
|
||||
wc_XmssKey_Free(&key);
|
||||
wc_ForceZero(&key, sizeof(key));
|
||||
cleanup:
|
||||
if (key_init) {
|
||||
wc_XmssKey_Free(&key);
|
||||
wc_ForceZero(&key, sizeof(key));
|
||||
}
|
||||
if (exit_code)
|
||||
exit(exit_code);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1269,6 +1309,7 @@ cleanup:
|
|||
fclose(fpriv);
|
||||
if (key_init)
|
||||
wc_MlDsaKey_Free(&key);
|
||||
wc_ForceZero(&key, sizeof(key));
|
||||
if (priv != NULL) {
|
||||
wc_ForceZero(priv, priv_len);
|
||||
free(priv);
|
||||
|
|
|
|||
|
|
@ -1443,7 +1443,17 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz,
|
|||
/* No pad bytes, version is aligned */
|
||||
|
||||
/* Append Version field */
|
||||
fw_version32 = strtol(CMD.fw_version, NULL, 10);
|
||||
{
|
||||
unsigned long tmp_ver;
|
||||
errno = 0;
|
||||
tmp_ver = strtoul(CMD.fw_version, NULL, 10);
|
||||
if (errno == ERANGE || tmp_ver > (unsigned long)UINT32_MAX) {
|
||||
fprintf(stderr, "Error: firmware version out of range: %s\n",
|
||||
CMD.fw_version);
|
||||
goto failure;
|
||||
}
|
||||
fw_version32 = (uint32_t)tmp_ver;
|
||||
}
|
||||
header_append_tag_u32(header, &header_idx, HDR_VERSION, fw_version32);
|
||||
|
||||
/* Append pad bytes, so timestamp val field is 8-byte aligned */
|
||||
|
|
@ -2317,7 +2327,8 @@ static int base_diff(const char *f_base, uint8_t *pubkey, uint32_t pubkey_sz, in
|
|||
int r;
|
||||
uint32_t patch_sz, patch_inv_sz;
|
||||
uint32_t patch_inv_off;
|
||||
uint32_t *delta_base_version = NULL;
|
||||
uint8_t *delta_base_version = NULL;
|
||||
uint32_t delta_base_version_val = 0;
|
||||
uint16_t delta_base_version_sz = 0;
|
||||
WB_DIFF_CTX diff_ctx;
|
||||
int ret = -1;
|
||||
|
|
@ -2378,12 +2389,20 @@ static int base_diff(const char *f_base, uint8_t *pubkey, uint32_t pubkey_sz, in
|
|||
#endif
|
||||
|
||||
/* Check base image version */
|
||||
delta_base_version_sz = sign_tool_find_header((uint8_t *)base + 8, HDR_VERSION, (void *)&delta_base_version);
|
||||
if ((delta_base_version_sz != sizeof(uint32_t)) || (*delta_base_version == 0)) {
|
||||
delta_base_version_sz = sign_tool_find_header((uint8_t *)base + 8, HDR_VERSION, &delta_base_version);
|
||||
if (delta_base_version_sz != sizeof(uint32_t)) {
|
||||
printf("Could not read firmware version from base file %s\n", f_base);
|
||||
goto cleanup;
|
||||
}
|
||||
delta_base_version_val = (uint32_t)delta_base_version[0] |
|
||||
((uint32_t)delta_base_version[1] << 8) |
|
||||
((uint32_t)delta_base_version[2] << 16) |
|
||||
((uint32_t)delta_base_version[3] << 24);
|
||||
if (delta_base_version_val == 0) {
|
||||
printf("Could not read firmware version from base file %s\n", f_base);
|
||||
goto cleanup;
|
||||
} else {
|
||||
printf("Delta base version: %u\n", *delta_base_version);
|
||||
printf("Delta base version: %u\n", delta_base_version_val);
|
||||
}
|
||||
|
||||
/* Retrieve the hash digest of the base image */
|
||||
|
|
@ -2588,7 +2607,7 @@ static int base_diff(const char *f_base, uint8_t *pubkey, uint32_t pubkey_sz, in
|
|||
/* Create delta file, with header, from the resulting patch */
|
||||
|
||||
ret = make_header_delta(pubkey, pubkey_sz, wolfboot_delta_file, CMD.output_diff_file,
|
||||
*delta_base_version, patch_sz, patch_inv_off, patch_inv_sz, base_hash, base_hash_sz);
|
||||
delta_base_version_val, patch_sz, patch_inv_off, patch_inv_sz, base_hash, base_hash_sz);
|
||||
|
||||
cleanup:
|
||||
if (dest) {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ debug: all
|
|||
|
||||
test: $(TARGET)
|
||||
python3 test-range-overflow.py ./$(TARGET)
|
||||
python3 test-align-overflow.py ./$(TARGET)
|
||||
|
||||
$(TARGET): $(TARGET).o
|
||||
@echo "Building squashelf tool"
|
||||
|
|
|
|||
|
|
@ -752,10 +752,31 @@ int main(int argCount, char** argValues)
|
|||
/* Align the segment according to its alignment requirement if
|
||||
* needed */
|
||||
if (p_align > 1) {
|
||||
/* Guard against overflow in the round-up (CWE-190). p_align
|
||||
* comes straight from a possibly crafted program header; a
|
||||
* large value would wrap current_offset to a small value and
|
||||
* place this segment's data over the ELF header. */
|
||||
if (current_offset > UINT64_MAX - (p_align - 1)) {
|
||||
fprintf(stderr,
|
||||
"Segment %zu alignment 0x%lx overflows file "
|
||||
"offset 0x%lx\n",
|
||||
i, (unsigned long)p_align,
|
||||
(unsigned long)current_offset);
|
||||
goto cleanup;
|
||||
}
|
||||
current_offset =
|
||||
(current_offset + p_align - 1) & ~(p_align - 1);
|
||||
}
|
||||
|
||||
/* The 32-bit program header offset is a uint32_t; a larger value
|
||||
* would be silently truncated and corrupt the output layout. */
|
||||
if (current_offset > UINT32_MAX) {
|
||||
fprintf(stderr,
|
||||
"Segment %zu offset 0x%lx exceeds 32-bit ELF range\n",
|
||||
i, (unsigned long)current_offset);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Update the segment's offset */
|
||||
ph32_array[i].offset = current_offset;
|
||||
p_filesz = ph32_array[i].file_size;
|
||||
|
|
@ -770,6 +791,18 @@ int main(int argCount, char** argValues)
|
|||
/* Align the segment according to its alignment requirement if
|
||||
* needed */
|
||||
if (p_align > 1) {
|
||||
/* Guard against overflow in the round-up (CWE-190). p_align
|
||||
* comes straight from a possibly crafted program header; a
|
||||
* value near UINT64_MAX would wrap current_offset to a small
|
||||
* value and place this segment's data over the ELF header. */
|
||||
if (current_offset > UINT64_MAX - (p_align - 1)) {
|
||||
fprintf(stderr,
|
||||
"Segment %zu alignment 0x%lx overflows file "
|
||||
"offset 0x%lx\n",
|
||||
i, (unsigned long)p_align,
|
||||
(unsigned long)current_offset);
|
||||
goto cleanup;
|
||||
}
|
||||
current_offset =
|
||||
(current_offset + p_align - 1) & ~(p_align - 1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,160 @@
|
|||
#!/usr/bin/env python3
|
||||
# test-align-overflow.py
|
||||
#
|
||||
# Regression test for the integer overflow in squashelf's segment-offset
|
||||
# alignment round-up (current_offset = (current_offset + p_align - 1) &
|
||||
# ~(p_align - 1)). p_align comes straight from a possibly crafted program
|
||||
# header. A value near UINT64_MAX (ELF64) wraps the sum to a tiny value, and a
|
||||
# value that rounds the offset past 2^32 (ELF32) is silently truncated to a
|
||||
# uint32_t. Either way the segment data lands at a wrong (often zero) file
|
||||
# offset, clobbering the ELF header while squashelf still reports success.
|
||||
# After the fix squashelf must reject such inputs with a non-zero exit instead
|
||||
# of writing a corrupt output ELF.
|
||||
#
|
||||
# Copyright (C) 2026 wolfSSL Inc.
|
||||
#
|
||||
# This file is part of wolfBoot.
|
||||
#
|
||||
# wolfBoot is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# wolfBoot is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
import os
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
EHSIZE64 = 64
|
||||
PHSIZE64 = 56
|
||||
EHSIZE32 = 52
|
||||
PHSIZE32 = 32
|
||||
UINT64_MAX = 0xFFFFFFFFFFFFFFFF
|
||||
UINT32_MAX = 0xFFFFFFFF
|
||||
|
||||
|
||||
def make_elf64(path, align, filesz=0x10):
|
||||
ph_off = EHSIZE64
|
||||
seg_off = ph_off + PHSIZE64
|
||||
ident = b"\x7fELF" + bytes([2, 1, 1, 0]) + b"\x00" * 8 # ELF64, little-endian
|
||||
ehdr = ident + struct.pack(
|
||||
"<HHIQQQIHHHHHH",
|
||||
2, # type ET_EXEC
|
||||
62, # machine x86-64
|
||||
1, # version
|
||||
0, # entry
|
||||
ph_off, # ph_offset
|
||||
0, # sh_offset
|
||||
0, # flags
|
||||
EHSIZE64, # header_size
|
||||
PHSIZE64, # ph_entry_size
|
||||
1, # ph_entry_count
|
||||
0, 0, 0)
|
||||
phdr = struct.pack(
|
||||
"<IIQQQQQQ",
|
||||
1, # PT_LOAD
|
||||
5, # flags R+X
|
||||
seg_off, # offset
|
||||
0x1000, # vaddr
|
||||
0x1000, # paddr
|
||||
filesz, # file_size
|
||||
filesz, # mem_size
|
||||
align) # align
|
||||
with open(path, "wb") as f:
|
||||
f.write(ehdr + phdr + b"\xAA" * filesz)
|
||||
|
||||
|
||||
def make_elf32(path, align, filesz=0x10):
|
||||
ph_off = EHSIZE32
|
||||
seg_off = ph_off + PHSIZE32
|
||||
ident = b"\x7fELF" + bytes([1, 1, 1, 0]) + b"\x00" * 8 # ELF32, little-endian
|
||||
ehdr = ident + struct.pack(
|
||||
"<HHIIIIIHHHHHH",
|
||||
2, # type ET_EXEC
|
||||
3, # machine x86
|
||||
1, # version
|
||||
0, # entry
|
||||
ph_off, # ph_offset
|
||||
0, # sh_offset
|
||||
0, # flags
|
||||
EHSIZE32, # header_size
|
||||
PHSIZE32, # ph_entry_size
|
||||
1, # ph_entry_count
|
||||
0, 0, 0)
|
||||
phdr = struct.pack(
|
||||
"<IIIIIIII",
|
||||
1, # PT_LOAD
|
||||
seg_off, # offset
|
||||
0x1000, # vaddr
|
||||
0x1000, # paddr
|
||||
filesz, # file_size
|
||||
filesz, # mem_size
|
||||
5, # flags R+X
|
||||
align) # align
|
||||
with open(path, "wb") as f:
|
||||
f.write(ehdr + phdr + b"\xAA" * filesz)
|
||||
|
||||
|
||||
def run(squashelf, infile, outfile):
|
||||
return subprocess.run(
|
||||
[squashelf, infile, outfile],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode
|
||||
|
||||
|
||||
def main():
|
||||
squashelf = sys.argv[1] if len(sys.argv) > 1 else "./squashelf"
|
||||
rc = 0
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
# 1) ELF64 alignment near UINT64_MAX wraps the round-up to a tiny
|
||||
# offset. squashelf MUST reject it (non-zero exit), not write a
|
||||
# corrupt ELF over its own header and report success.
|
||||
bad64 = os.path.join(d, "align64.elf")
|
||||
out64 = os.path.join(d, "align64.out")
|
||||
make_elf64(bad64, align=UINT64_MAX)
|
||||
if run(squashelf, bad64, out64) == 0:
|
||||
print("FAIL: ELF64 overflowing alignment was accepted")
|
||||
rc = 1
|
||||
else:
|
||||
print("PASS: ELF64 overflowing alignment rejected")
|
||||
|
||||
# 2) ELF32 alignment that rounds the offset past 2^32; the uint32_t
|
||||
# offset field would silently truncate (to 0 here). Must be rejected.
|
||||
bad32 = os.path.join(d, "align32.elf")
|
||||
out32 = os.path.join(d, "align32.out")
|
||||
make_elf32(bad32, align=UINT32_MAX)
|
||||
if run(squashelf, bad32, out32) == 0:
|
||||
print("FAIL: ELF32 truncating alignment was accepted")
|
||||
rc = 1
|
||||
else:
|
||||
print("PASS: ELF32 truncating alignment rejected")
|
||||
|
||||
# 3) Regression guard: normal page-aligned segments must still succeed.
|
||||
ok64 = os.path.join(d, "ok64.elf")
|
||||
ok64o = os.path.join(d, "ok64.out")
|
||||
make_elf64(ok64, align=0x1000)
|
||||
if run(squashelf, ok64, ok64o) != 0:
|
||||
print("FAIL: normal ELF64 segment was rejected")
|
||||
rc = 1
|
||||
else:
|
||||
print("PASS: normal ELF64 segment kept")
|
||||
|
||||
ok32 = os.path.join(d, "ok32.elf")
|
||||
ok32o = os.path.join(d, "ok32.out")
|
||||
make_elf32(ok32, align=0x1000)
|
||||
if run(squashelf, ok32, ok32o) != 0:
|
||||
print("FAIL: normal ELF32 segment was rejected")
|
||||
rc = 1
|
||||
else:
|
||||
print("PASS: normal ELF32 segment kept")
|
||||
|
||||
sys.exit(rc)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -16,6 +16,11 @@ $(EXE): $(EXE).o libwolfboot.o
|
|||
libwolfboot.o: ../../src/libwolfboot.c
|
||||
$(Q)$(CC) $(CFLAGS) -c -o $(@) $(^)
|
||||
|
||||
test_overflow: test_overflow.c
|
||||
$(Q)$(CC) -Wall -g -o $@ $^ -lutil
|
||||
|
||||
test: $(EXE) test_overflow
|
||||
$(Q)./test_overflow ./$(EXE)
|
||||
|
||||
clean:
|
||||
$(Q)rm -f *.o $(EXE)
|
||||
$(Q)rm -f *.o $(EXE) test_overflow
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
/* Regression test for the uint32_t wraparound bounds-check bypass in
|
||||
* uart_flash_erase()/read()/write() (finding F-4339).
|
||||
*
|
||||
* Drives the real ufserver binary over a pseudo-terminal and sends an ERASE
|
||||
* command with address=0x1000 and len=0xFFFFFFFF. With the buggy uint32_t
|
||||
* guard, address+len wraps to 0xFFF (< FIRMWARE_PARTITION_SIZE+SWAP_SIZE), the
|
||||
* guard is bypassed and the erase loop walks far past the 0x21000-byte mmap,
|
||||
* crashing the server with SIGSEGV. With the 64-bit guard the command is
|
||||
* rejected and the server stays alive.
|
||||
*
|
||||
* Usage: test_overflow ./ufserver
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <pty.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#define CMD_HDR_WOLF 'W'
|
||||
#define CMD_HDR_ERASE 0x03
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int master, slave;
|
||||
char slavename[256];
|
||||
pid_t pid;
|
||||
char tmpl[] = "/tmp/ufserver_fw_XXXXXX";
|
||||
int fd;
|
||||
uint8_t buf[64];
|
||||
int i;
|
||||
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "usage: %s ./ufserver\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Create a small (non-WOLF) firmware file; mmap_firmware() pads it to
|
||||
* FIRMWARE_PARTITION_SIZE + SWAP_SIZE (0x21000). */
|
||||
fd = mkstemp(tmpl);
|
||||
if (fd < 0) { perror("mkstemp"); return 2; }
|
||||
memset(buf, 0xA5, sizeof(buf));
|
||||
if (write(fd, buf, sizeof(buf)) != (ssize_t)sizeof(buf)) { perror("write"); return 2; }
|
||||
close(fd);
|
||||
|
||||
if (openpty(&master, &slave, slavename, NULL, NULL) != 0) {
|
||||
perror("openpty");
|
||||
return 2;
|
||||
}
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0) { perror("fork"); return 2; }
|
||||
if (pid == 0) {
|
||||
/* Child: run the server attached to the slave pty. */
|
||||
close(master);
|
||||
close(slave);
|
||||
fd = open("/dev/null", O_WRONLY);
|
||||
if (fd >= 0) { dup2(fd, STDOUT_FILENO); }
|
||||
execl(argv[1], argv[1], tmpl, slavename, (char *)NULL);
|
||||
perror("execl");
|
||||
_exit(127);
|
||||
}
|
||||
|
||||
/* Parent: drive the server. */
|
||||
close(slave);
|
||||
usleep(200000); /* let the child open and configure the port */
|
||||
|
||||
/* STX selecting a flash command, then the ERASE opcode. */
|
||||
buf[0] = CMD_HDR_WOLF;
|
||||
buf[1] = CMD_HDR_ERASE;
|
||||
/* address = 0x00001000 (little-endian raw bytes) */
|
||||
buf[2] = 0x00; buf[3] = 0x10; buf[4] = 0x00; buf[5] = 0x00;
|
||||
/* len = 0xFFFFFFFF -> address+len wraps to 0xFFF in uint32_t */
|
||||
buf[6] = 0xFF; buf[7] = 0xFF; buf[8] = 0xFF; buf[9] = 0xFF;
|
||||
if (write(master, buf, 10) != 10) { perror("write master"); }
|
||||
|
||||
/* Drain ack bytes the server sends back so it never blocks. */
|
||||
fcntl(master, F_SETFL, O_NONBLOCK);
|
||||
|
||||
/* Give the server time to process (and crash, if vulnerable). */
|
||||
for (i = 0; i < 20; i++) {
|
||||
uint8_t drain[64];
|
||||
(void)read(master, drain, sizeof(drain));
|
||||
usleep(50000);
|
||||
int status;
|
||||
pid_t r = waitpid(pid, &status, WNOHANG);
|
||||
if (r == pid) {
|
||||
unlink(tmpl);
|
||||
if (WIFSIGNALED(status)) {
|
||||
fprintf(stderr, "FAIL: server died from signal %d "
|
||||
"(OOB access from wrapped bounds check)\n",
|
||||
WTERMSIG(status));
|
||||
return 1;
|
||||
}
|
||||
fprintf(stderr, "FAIL: server exited unexpectedly (status %d)\n",
|
||||
status);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Still alive after processing the malicious command: guard held. */
|
||||
kill(pid, SIGKILL);
|
||||
waitpid(pid, NULL, 0);
|
||||
unlink(tmpl);
|
||||
printf("PASS: server rejected wrapped address+len and stayed alive\n");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -280,7 +280,7 @@ static void uart_flash_erase(uint8_t *base, int ud)
|
|||
return;
|
||||
if (read_word(ud,&len) != 4)
|
||||
return;
|
||||
if (address + len > (FIRMWARE_PARTITION_SIZE + SWAP_SIZE))
|
||||
if ((uint64_t)address + (uint64_t)len > (FIRMWARE_PARTITION_SIZE + SWAP_SIZE))
|
||||
return;
|
||||
if (address < FIRMWARE_PARTITION_SIZE) {
|
||||
printmsg(msgEraseUpdate);
|
||||
|
|
@ -319,7 +319,7 @@ static void uart_flash_read(uint8_t *base, int ud)
|
|||
#if LOG_FLASH_ADDRESS
|
||||
printf("Read @%x\n", address);
|
||||
#endif
|
||||
if (address + len > (FIRMWARE_PARTITION_SIZE + SWAP_SIZE))
|
||||
if ((uint64_t)address + (uint64_t)len > (FIRMWARE_PARTITION_SIZE + SWAP_SIZE))
|
||||
return;
|
||||
for (i = 0; i < len; i++) {
|
||||
write(ud, base + address + i, 1);
|
||||
|
|
@ -344,7 +344,7 @@ static void uart_flash_write(uint8_t *base, int ud)
|
|||
#if LOG_FLASH_ADDRESS
|
||||
printf("Write @%x\n", address);
|
||||
#endif
|
||||
if (address + len > (FIRMWARE_PARTITION_SIZE + SWAP_SIZE))
|
||||
if ((uint64_t)address + (uint64_t)len > (FIRMWARE_PARTITION_SIZE + SWAP_SIZE))
|
||||
return;
|
||||
for (i = 0; i < len; i++) {
|
||||
read(ud, base + address + i, 1);
|
||||
|
|
|
|||
|
|
@ -64,6 +64,9 @@ TESTS+=unit-fit-fpga
|
|||
TESTS+=unit-mpusize
|
||||
TESTS+=unit-flash-erase-h7
|
||||
TESTS+=unit-otp-keystore
|
||||
TESTS+=unit-x86-paging-oob
|
||||
TESTS+=unit-fwtpm-nv-oob
|
||||
TESTS+=unit-elf-bss-guard
|
||||
|
||||
# linux_loader.c is x86 32-bit only, so its unit tests need a working 32-bit
|
||||
# (multilib) toolchain. Probe whether "gcc -m32" can link, and only add the
|
||||
|
|
@ -211,6 +214,10 @@ unit-fwtpm-stub: ../../include/target.h unit-fwtpm-stub.c
|
|||
-DWOLFTPM_USER_SETTINGS -ffunction-sections -fdata-sections \
|
||||
$(LDFLAGS) -Wl,--gc-sections
|
||||
|
||||
unit-fwtpm-nv-oob: ../../include/target.h unit-fwtpm-nv-oob.c
|
||||
gcc -o $@ $^ $(CFLAGS) -I$(WOLFBOOT_LIB_WOLFTPM) \
|
||||
-DWOLFTPM_USER_SETTINGS $(LDFLAGS)
|
||||
|
||||
unit-tpm-blob: ../../include/target.h unit-tpm-blob.c
|
||||
gcc -o $@ $^ $(CFLAGS) -I$(WOLFBOOT_LIB_WOLFTPM) -DWOLFBOOT_TPM \
|
||||
-DWOLFTPM_USER_SETTINGS -DWOLFBOOT_TPM_SEAL -DWOLFBOOT_SIGN_RSA2048 \
|
||||
|
|
@ -443,6 +450,14 @@ unit-disk: unit-disk.c gpt-sfdisk-test.h
|
|||
unit-multiboot: unit-multiboot.c
|
||||
gcc -o $@ unit-multiboot.c $(CFLAGS) $(LDFLAGS)
|
||||
|
||||
unit-x86-paging-oob: ../../include/target.h unit-x86-paging-oob.c
|
||||
gcc -o $@ unit-x86-paging-oob.c $(CFLAGS) \
|
||||
-ffunction-sections -fdata-sections $(LDFLAGS) -Wl,--gc-sections
|
||||
|
||||
unit-elf-bss-guard: unit-elf-bss-guard.c
|
||||
gcc -o $@ $< -I../../include -DWOLFBOOT_ELF -DARCH_FLASH_OFFSET=0 \
|
||||
-DWOLFBOOT_NO_PRINTF -g $(LDFLAGS)
|
||||
|
||||
%.o:%.c
|
||||
gcc -c -o $@ $^ $(CFLAGS)
|
||||
|
||||
|
|
|
|||
|
|
@ -35,10 +35,14 @@ static uint8_t fake_disk[FAKE_DISK_SIZE];
|
|||
/* Set to a byte offset to make disk_read fail at that address. -1 = no fail */
|
||||
static int64_t mock_disk_read_fail_at = -1;
|
||||
|
||||
/* Number of disk_read calls since last reset (used to detect scan blow-ups) */
|
||||
static unsigned int disk_read_count = 0;
|
||||
|
||||
/* Mock disk I/O — copies to/from fake_disk buffer */
|
||||
int disk_read(int drv, uint64_t start, uint32_t count, uint8_t *buf)
|
||||
{
|
||||
(void)drv;
|
||||
disk_read_count++;
|
||||
if (mock_disk_read_fail_at >= 0 && (int64_t)start == mock_disk_read_fail_at)
|
||||
return -1;
|
||||
if (start + count > FAKE_DISK_SIZE)
|
||||
|
|
@ -559,6 +563,25 @@ START_TEST(test_gpt_parse_partition_last_zero)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_gpt_parse_partition_last_overflow)
|
||||
{
|
||||
/* pe->last = UINT64_MAX passes the first>last and last==0 guards, but
|
||||
* (pe->last + 1) * GPT_SECTOR_SIZE - 1 would wrap to UINT64_MAX, defeating
|
||||
* the bounds check in disk_part_read. Must be rejected. */
|
||||
uint8_t entry[128];
|
||||
struct gpt_part_entry *pe = (struct gpt_part_entry *)entry;
|
||||
struct gpt_part_info info;
|
||||
|
||||
memset(entry, 0, sizeof(entry));
|
||||
pe->type[0] = 0x0001020304050607ULL;
|
||||
pe->type[1] = 0x08090A0B0C0D0E0FULL;
|
||||
pe->first = 1;
|
||||
pe->last = 0xFFFFFFFFFFFFFFFFULL;
|
||||
|
||||
ck_assert_int_eq(gpt_parse_partition(entry, 128, &info), -1);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* ============================================================
|
||||
* Coverage tests: disk.c error paths and boundary conditions
|
||||
* ============================================================ */
|
||||
|
|
@ -625,6 +648,55 @@ START_TEST(test_disk_open_gpt_large_array_sz)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_disk_open_gpt_rejects_huge_part_array)
|
||||
{
|
||||
/* A crafted GPT header with a valid (recomputed) header CRC but an
|
||||
* enormous n_part * array_sz must be rejected before the partition-entry
|
||||
* CRC scan loop runs, otherwise it forces a pre-auth DoS via one disk
|
||||
* read per 512-byte chunk of the (here 8 MB) declared array. */
|
||||
struct guid_ptable *gpt_hdr;
|
||||
|
||||
build_gpt_disk();
|
||||
|
||||
gpt_hdr = (struct guid_ptable *)(fake_disk + GPT_SECTOR_SIZE);
|
||||
gpt_hdr->n_part = 0x10000; /* 65536 entries ... */
|
||||
gpt_hdr->array_sz = 128; /* ... * 128 bytes = 8 MB */
|
||||
finalize_gpt_header_crc(gpt_hdr); /* attacker can always fix header CRC */
|
||||
|
||||
disk_read_count = 0;
|
||||
ck_assert_int_eq(disk_open(0), -1);
|
||||
/* Only the MBR sector and the GPT header sector may be read; the
|
||||
* partition-array scan must not run at all. */
|
||||
ck_assert_uint_le(disk_read_count, 2);
|
||||
ck_assert_int_eq(Drives[0].is_open, 0);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_disk_open_gpt_lba_no_overflow)
|
||||
{
|
||||
/* The protective-MBR lba_first is an attacker-controlled uint32_t. The
|
||||
* GPT-header byte offset must be computed in 64 bits. If it is computed
|
||||
* as the 32-bit product GPT_SECTOR_SIZE * gpt_lba, it wraps for
|
||||
* gpt_lba >= 0x800000: 512 * 0x800001 wraps to 0x200, silently
|
||||
* redirecting the read back to LBA 1 (the real GPT header) instead of
|
||||
* the out-of-range LBA the field actually names. */
|
||||
struct gpt_mbr_part_entry *mbr_entry;
|
||||
|
||||
build_gpt_disk();
|
||||
|
||||
/* Point the protective entry at an LBA whose 512* product overflows a
|
||||
* 32-bit unsigned back to 0x200 (LBA 1). */
|
||||
mbr_entry = (struct gpt_mbr_part_entry *)(fake_disk + GPT_MBR_ENTRY_START);
|
||||
mbr_entry->lba_first = 0x800001;
|
||||
|
||||
/* With correct 64-bit arithmetic the header read targets byte
|
||||
* 0x100000200, far past the fake disk, so disk_open must fail rather
|
||||
* than wrap to LBA 1 and accept the table. */
|
||||
ck_assert_int_eq(disk_open(0), -1);
|
||||
ck_assert_int_eq(Drives[0].is_open, 0);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_disk_open_gpt_empty_entry_mid_table)
|
||||
{
|
||||
/* GPT header says 3 partitions but entry[1] has zeroed type GUID.
|
||||
|
|
@ -954,6 +1026,7 @@ Suite *wolfboot_suite(void)
|
|||
tcase_add_test(tc_disk_bugs, test_gpt_partition_end_inclusive);
|
||||
tcase_add_test(tc_disk_bugs, test_disk_open_failure_clears_is_open);
|
||||
tcase_add_test(tc_disk_bugs, test_gpt_parse_partition_last_zero);
|
||||
tcase_add_test(tc_disk_bugs, test_gpt_parse_partition_last_overflow);
|
||||
suite_add_tcase(s, tc_disk_bugs);
|
||||
|
||||
TCase *tc_cov = tcase_create("disk-coverage");
|
||||
|
|
@ -961,6 +1034,8 @@ Suite *wolfboot_suite(void)
|
|||
tcase_add_test(tc_cov, test_disk_open_mbr_bad_bootsig);
|
||||
tcase_add_test(tc_cov, test_disk_open_gpt_excess_partitions);
|
||||
tcase_add_test(tc_cov, test_disk_open_gpt_large_array_sz);
|
||||
tcase_add_test(tc_cov, test_disk_open_gpt_rejects_huge_part_array);
|
||||
tcase_add_test(tc_cov, test_disk_open_gpt_lba_no_overflow);
|
||||
tcase_add_test(tc_cov, test_disk_open_gpt_empty_entry_mid_table);
|
||||
tcase_add_test(tc_cov, test_disk_open_mbr_zero_lba_entry);
|
||||
tcase_add_test(tc_cov, test_open_part_invalid_drive);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,141 @@
|
|||
/* unit-elf-bss-guard.c
|
||||
*
|
||||
* Regression test for F-4418: elf_load_image_mmu collision guard uses
|
||||
* file_size rather than mem_size, so a BSS-bearing PT_LOAD segment can
|
||||
* overwrite program headers that have not been parsed yet.
|
||||
*
|
||||
* Copyright (C) 2026 wolfSSL Inc.
|
||||
*
|
||||
* This file is part of wolfBoot.
|
||||
*
|
||||
* wolfBoot is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* wolfBoot is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <check.h>
|
||||
|
||||
#include "elf.h"
|
||||
|
||||
/* Pull in elf.c directly (avoids a separate link step). */
|
||||
#include "../../src/elf.c"
|
||||
|
||||
/* 4 KiB is plenty for the ELF64 header + 9 program headers (568 bytes). */
|
||||
#define TEST_IMG_SIZE 4096
|
||||
|
||||
static uint8_t g_image[TEST_IMG_SIZE];
|
||||
|
||||
/*
|
||||
* Build a minimal ELF64 executable image in g_image with entry_count = 9
|
||||
* program headers (one more than ELF_MAX_PH = 8). That forces
|
||||
* elf_load_image_mmu to leave the headers in the original image buffer
|
||||
* rather than caching them on the stack.
|
||||
*
|
||||
* ph[0]: PT_LOAD, file_size=0, mem_size=56 (= sizeof elf64_program_header).
|
||||
* vaddr is set to the runtime address of ph[1] so that the BSS
|
||||
* zero-fill (memset) writes exactly over ph[1] when the guard is
|
||||
* evaluated with file_size instead of mem_size.
|
||||
*
|
||||
* ph[1]: sentinel — type field set to ELF_PT_LOAD (1). With the bug the
|
||||
* BSS memset zeroes ph[1].type to 0; with the fix the write is
|
||||
* blocked and the sentinel is preserved.
|
||||
*
|
||||
* ph[2..8]: type=0, mem_size=0 — skipped by the loader.
|
||||
*/
|
||||
START_TEST(test_elf_bss_guard_no_header_corruption)
|
||||
{
|
||||
elf64_header *hdr;
|
||||
elf64_program_header *ph;
|
||||
uintptr_t entry = 0;
|
||||
int i, ret;
|
||||
|
||||
memset(g_image, 0, sizeof(g_image));
|
||||
|
||||
hdr = (elf64_header *)g_image;
|
||||
memcpy(hdr->ident, ELF_IDENT_STR, 4);
|
||||
hdr->ident[ELF_CLASS_OFF] = ELF_CLASS_64;
|
||||
hdr->ident[5] = ELF_ENDIAN_LITTLE;
|
||||
hdr->type = ELF_HET_EXEC;
|
||||
hdr->version = 1;
|
||||
hdr->entry = 0x1000;
|
||||
hdr->ph_offset = sizeof(elf64_header); /* 64 */
|
||||
hdr->ph_entry_size = sizeof(elf64_program_header); /* 56 */
|
||||
hdr->ph_entry_count = ELF_MAX_PH + 1; /* 9 */
|
||||
|
||||
ph = (elf64_program_header *)(g_image + sizeof(elf64_header));
|
||||
|
||||
/*
|
||||
* ph[0]: BSS-only PT_LOAD segment.
|
||||
* vaddr points at ph[1] so that a memset of mem_size bytes zeroes it.
|
||||
* With the buggy guard (file_size check):
|
||||
* vaddr + file_size = ph[1]_start + 0 = ph[1]_start <= ph[1]_start → TRUE
|
||||
* → write proceeds; memset zeroes ph[1].
|
||||
* With the fixed guard (mem_size check):
|
||||
* vaddr + mem_size = ph[1]_start + 56 > ph[1]_start → FALSE
|
||||
* → write is skipped; ph[1] intact.
|
||||
*/
|
||||
ph[0].type = ELF_PT_LOAD;
|
||||
ph[0].flags = 0;
|
||||
ph[0].offset = 0;
|
||||
ph[0].vaddr = (uint64_t)(uintptr_t)(ph + 1); /* = &ph[1] */
|
||||
ph[0].paddr = ph[0].vaddr;
|
||||
ph[0].file_size = 0;
|
||||
ph[0].mem_size = sizeof(elf64_program_header); /* 56 bytes of BSS */
|
||||
ph[0].align = 1;
|
||||
|
||||
/* ph[1]: sentinel — preserve this type value to detect corruption. */
|
||||
ph[1].type = ELF_PT_LOAD;
|
||||
ph[1].mem_size = 0; /* skipped by the "mem_size == 0" guard if reached */
|
||||
|
||||
/* ph[2..8]: empty, will be skipped. */
|
||||
for (i = 2; i <= ELF_MAX_PH; i++) {
|
||||
ph[i].type = 0;
|
||||
ph[i].mem_size = 0;
|
||||
}
|
||||
|
||||
ret = elf_load_image_mmu(g_image, sizeof(g_image), &entry, NULL);
|
||||
ck_assert_int_eq(ret, 0);
|
||||
|
||||
/*
|
||||
* ph[1].type must not have been zeroed by the BSS memset from ph[0].
|
||||
* Failure here means the guard compared vaddr+file_size instead of
|
||||
* vaddr+mem_size, allowing the memset to corrupt the unread header.
|
||||
*/
|
||||
ck_assert_msg(ph[1].type != 0,
|
||||
"BSS memset from ph[0] corrupted ph[1].type: "
|
||||
"collision guard must use mem_size, not file_size");
|
||||
}
|
||||
END_TEST
|
||||
|
||||
Suite *elf_bss_guard_suite(void)
|
||||
{
|
||||
Suite *s = suite_create("ELF BSS guard");
|
||||
TCase *tc = tcase_create("bss-header-collision");
|
||||
tcase_add_test(tc, test_elf_bss_guard_no_header_corruption);
|
||||
tcase_set_timeout(tc, 10);
|
||||
suite_add_tcase(s, tc);
|
||||
return s;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int fails;
|
||||
Suite *s = elf_bss_guard_suite();
|
||||
SRunner *sr = srunner_create(s);
|
||||
srunner_run_all(sr, CK_NORMAL);
|
||||
fails = srunner_ntests_failed(sr);
|
||||
srunner_free(sr);
|
||||
return fails;
|
||||
}
|
||||
|
|
@ -136,6 +136,27 @@ START_TEST(test_fit_load_image_rejects_oversized_prop_len)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
/* off_dt_strings=4, size_dt_strings=0xFFFFFFFC: sum overflows uint32_t to 0.
|
||||
* Before the fix, fdt_data_size_() returned 0 and fdt_shrink() silently set
|
||||
* totalsize=0. After the fix fdt_shrink() must return an error and leave
|
||||
* totalsize unchanged. */
|
||||
START_TEST(test_fdt_shrink_rejects_dt_strings_area_overflow)
|
||||
{
|
||||
static uint8_t buf[256];
|
||||
int rc;
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
fdt_set_totalsize(buf, sizeof(buf));
|
||||
fdt_set_off_dt_strings(buf, 4);
|
||||
fdt_set_size_dt_strings(buf, 0xFFFFFFFC);
|
||||
|
||||
rc = fdt_shrink(buf);
|
||||
|
||||
ck_assert_int_lt(rc, 0);
|
||||
ck_assert_uint_eq(fdt_totalsize(buf), sizeof(buf));
|
||||
}
|
||||
END_TEST
|
||||
|
||||
static Suite *fdt_suite(void)
|
||||
{
|
||||
Suite *s = suite_create("fdt");
|
||||
|
|
@ -144,6 +165,7 @@ static Suite *fdt_suite(void)
|
|||
tcase_add_test(tc, test_fdt_get_string_rejects_out_of_range_offset);
|
||||
tcase_add_test(tc, test_fdt_get_string_returns_string_with_valid_offset);
|
||||
tcase_add_test(tc, test_fit_load_image_rejects_oversized_prop_len);
|
||||
tcase_add_test(tc, test_fdt_shrink_rejects_dt_strings_area_overflow);
|
||||
suite_add_tcase(s, tc);
|
||||
|
||||
return s;
|
||||
|
|
|
|||
|
|
@ -137,6 +137,21 @@ START_TEST(test_erase_bank1_two_sectors)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
/* Regression for F-5745: when len % FLASH_PAGE_SIZE != 0 the final page was
|
||||
* skipped because end_address = base + len - 1 lands exactly on the start of
|
||||
* that last page and the strict `p < end_address` guard excludes it. */
|
||||
START_TEST(test_erase_unaligned_len_covers_last_page)
|
||||
{
|
||||
reset_mocks();
|
||||
/* len = PAGE_SIZE + 1: two pages must be erased (sectors 0 and 1). */
|
||||
hal_flash_erase(0x08000000UL, FLASH_PAGE_SIZE + 1);
|
||||
|
||||
ck_assert_int_eq(erase_log_n, 2);
|
||||
ck_assert_uint_eq(snb_of(erase_cr1[0]), 0);
|
||||
ck_assert_uint_eq(snb_of(erase_cr1[1]), 1);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
Suite *flash_erase_suite(void)
|
||||
{
|
||||
Suite *s = suite_create("flash-erase-h7");
|
||||
|
|
@ -145,6 +160,7 @@ Suite *flash_erase_suite(void)
|
|||
tcase_add_test(tc, test_erase_bank2_two_sectors);
|
||||
tcase_add_test(tc, test_erase_bank2_single_sector);
|
||||
tcase_add_test(tc, test_erase_bank1_two_sectors);
|
||||
tcase_add_test(tc, test_erase_unaligned_len_covers_last_page);
|
||||
|
||||
suite_add_tcase(s, tc);
|
||||
return s;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
/* unit-fwtpm-nv-oob.c
|
||||
*
|
||||
* Regression test: fwtpm_nv_read/write/erase use > instead of >= in the
|
||||
* offset guard, so offset == WCS_FWTPM_NV_SIZE with size == 0 passes the
|
||||
* guard and returns TPM_RC_SUCCESS instead of BAD_FUNC_ARG.
|
||||
*/
|
||||
|
||||
#include <check.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#define WOLFBOOT_TZ_FWTPM
|
||||
|
||||
/* Minimal types/macros that fwtpm_callable.c uses from wolftpm headers.
|
||||
* Poison the include guards of the heavy fwtpm headers so they become no-ops;
|
||||
* we define the handful of types/symbols we actually need ourselves. */
|
||||
#define _FWTPM_H_
|
||||
#define _FWTPM_COMMAND_H_
|
||||
#define _FWTPM_NV_H_
|
||||
|
||||
/* tpm2_types.h only needs these guards poisoned to avoid the full wolfcrypt
|
||||
* pull-in; the macros it actually provides (XMEMCPY/XMEMSET etc.) come from
|
||||
* the guards below. */
|
||||
#define WOLFTPM2_NO_WOLFCRYPT
|
||||
|
||||
typedef uint8_t byte;
|
||||
typedef uint32_t word32;
|
||||
|
||||
#define BAD_FUNC_ARG (-173)
|
||||
#define TPM_RC_SUCCESS 0x000
|
||||
#define TPM_RC_FAILURE 0x101
|
||||
#define TPM_RC_INITIALIZE 0x100
|
||||
|
||||
#define XMEMCPY(d,s,l) memcpy((d),(s),(l))
|
||||
#define XMEMSET(b,c,l) memset((b),(c),(l))
|
||||
|
||||
#define TPM2_HEADER_SIZE 10
|
||||
|
||||
/* Minimal FWTPM_NV_HAL type matching the nested struct in the real FWTPM_CTX.
|
||||
* Field layout must match so the static initialiser in fwtpm_callable.c works. */
|
||||
typedef struct FWTPM_NV_HAL_S {
|
||||
int (*read)(void *ctx, word32 offset, byte *buf, word32 size);
|
||||
int (*write)(void *ctx, word32 offset, const byte *buf, word32 size);
|
||||
int (*erase)(void *ctx, word32 offset, word32 size);
|
||||
void *ctx;
|
||||
word32 maxSize;
|
||||
} FWTPM_NV_HAL;
|
||||
|
||||
/* Minimal FWTPM_CTX — only the field wcs_fwtpm_init/transmit actually touch */
|
||||
typedef struct {
|
||||
int wasStarted;
|
||||
} FWTPM_CTX;
|
||||
|
||||
/* Stub symbols referenced by fwtpm_callable.c */
|
||||
unsigned int _start_heap;
|
||||
unsigned int _heap_size;
|
||||
|
||||
void *wolfboot_store_sbrk(unsigned int incr, uint8_t **heap,
|
||||
uint8_t *start, uint32_t size)
|
||||
{
|
||||
(void)incr; (void)heap; (void)start; (void)size;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int FWTPM_Init(FWTPM_CTX *ctx) { (void)ctx; return 0; }
|
||||
int FWTPM_NV_SetHAL(FWTPM_CTX *ctx, FWTPM_NV_HAL *hal)
|
||||
{ (void)ctx; (void)hal; return 0; }
|
||||
int FWTPM_ProcessCommand(FWTPM_CTX *ctx, const byte *cmdBuf, int cmdSize,
|
||||
byte *rspBuf, int *rspSize, int locality)
|
||||
{
|
||||
(void)ctx; (void)cmdBuf; (void)cmdSize;
|
||||
(void)rspBuf; (void)rspSize; (void)locality;
|
||||
return TPM_RC_SUCCESS;
|
||||
}
|
||||
|
||||
/* Bring in the code under test as part of this translation unit so we can
|
||||
* reach the static fwtpm_nv_hal struct and the static NV callbacks. */
|
||||
#include "../../src/fwtpm_callable.c"
|
||||
|
||||
/* ── tests ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
START_TEST(nv_read_rejects_offset_at_boundary)
|
||||
{
|
||||
byte buf[8] = {0};
|
||||
ck_assert_int_eq(
|
||||
fwtpm_nv_hal.read(fwtpm_nv_hal.ctx, WCS_FWTPM_NV_SIZE, buf, 0),
|
||||
BAD_FUNC_ARG);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(nv_write_rejects_offset_at_boundary)
|
||||
{
|
||||
byte buf[8] = {0};
|
||||
ck_assert_int_eq(
|
||||
fwtpm_nv_hal.write(fwtpm_nv_hal.ctx, WCS_FWTPM_NV_SIZE, buf, 0),
|
||||
BAD_FUNC_ARG);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(nv_erase_rejects_offset_at_boundary)
|
||||
{
|
||||
ck_assert_int_eq(
|
||||
fwtpm_nv_hal.erase(fwtpm_nv_hal.ctx, WCS_FWTPM_NV_SIZE, 0),
|
||||
BAD_FUNC_ARG);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* Sanity: valid access at last byte must succeed */
|
||||
START_TEST(nv_read_accepts_last_valid_offset)
|
||||
{
|
||||
byte buf[1] = {0};
|
||||
ck_assert_int_eq(
|
||||
fwtpm_nv_hal.read(fwtpm_nv_hal.ctx, WCS_FWTPM_NV_SIZE - 1, buf, 1),
|
||||
TPM_RC_SUCCESS);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
static Suite *fwtpm_nv_oob_suite(void)
|
||||
{
|
||||
Suite *s = suite_create("fwtpm_nv_oob");
|
||||
TCase *tc = tcase_create("boundary_guard");
|
||||
tcase_add_test(tc, nv_read_rejects_offset_at_boundary);
|
||||
tcase_add_test(tc, nv_write_rejects_offset_at_boundary);
|
||||
tcase_add_test(tc, nv_erase_rejects_offset_at_boundary);
|
||||
tcase_add_test(tc, nv_read_accepts_last_valid_offset);
|
||||
suite_add_tcase(s, tc);
|
||||
return s;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int fails;
|
||||
Suite *s = fwtpm_nv_oob_suite();
|
||||
SRunner *sr = srunner_create(s);
|
||||
srunner_run_all(sr, CK_NORMAL);
|
||||
fails = srunner_ntests_failed(sr);
|
||||
srunner_free(sr);
|
||||
return fails == 0 ? 0 : 1;
|
||||
}
|
||||
|
|
@ -883,6 +883,16 @@ START_TEST(test_open_image)
|
|||
ck_assert_int_eq(ret, -1);
|
||||
ck_assert_uint_eq(img.hdr_ok, 0);
|
||||
|
||||
/* Self header must reject bad magic and leave hdr_ok cleared */
|
||||
memset(self_hdr, 0xFF, sizeof(self_hdr));
|
||||
((uint32_t *)self_hdr)[0] = ~WOLFBOOT_MAGIC;
|
||||
|
||||
memset(&img, 0, sizeof(img));
|
||||
ret = wolfBoot_open_self_address(&img, self_hdr,
|
||||
(uint8_t *)WOLFBOOT_PARTITION_BOOT_ADDRESS);
|
||||
ck_assert_int_eq(ret, -1);
|
||||
ck_assert_uint_eq(img.hdr_ok, 0);
|
||||
|
||||
/* Self header must reject sizes beyond the partition payload budget */
|
||||
memset(self_hdr, 0xFF, sizeof(self_hdr));
|
||||
((uint32_t *)self_hdr)[0] = WOLFBOOT_MAGIC;
|
||||
|
|
|
|||
|
|
@ -424,6 +424,42 @@ START_TEST(test_build_info_header_length_underflow)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
/* check that an attacker-controlled header_length larger than the 32 KiB
|
||||
* Multiboot2 header window cannot inflate the tag walker's bounds. With
|
||||
* header_length = 0xFFFFFFFF the subtraction header_length - sizeof(struct
|
||||
* mb2_header) yields ~4GB, so end = tags + ~4GB in mb2_find_tag_by_type and the
|
||||
* size guard becomes ineffective, letting the walker read far past the image.
|
||||
*
|
||||
* We place a fake info_req tag right after the header. Without the upper-bound
|
||||
* fix mb2_find_tag_by_type finds it and the function returns 0 (success). With
|
||||
* the fix the oversized header_length is rejected and it returns -1. */
|
||||
START_TEST(test_build_info_header_length_overflow)
|
||||
{
|
||||
uint8_t header[48] __attribute__((aligned(8)));
|
||||
uint8_t boot_info[256];
|
||||
struct stage2_parameter p = make_stage2();
|
||||
struct mb2_header *h = (struct mb2_header *)header;
|
||||
struct mb2_tag_info_req *info;
|
||||
struct mb2_tag *term;
|
||||
memset(header, 0, sizeof(header));
|
||||
h->magic = MB2_MAGIC;
|
||||
h->header_length = 0xFFFFFFFFu; /* far larger than the 32 KiB window */
|
||||
|
||||
info = (struct mb2_tag_info_req *)(header + sizeof(struct mb2_header));
|
||||
info->type = 1; /* MB2_TAG_TYPE_INFO_REQ */
|
||||
info->flags = 0;
|
||||
info->size = 12;
|
||||
info->mbi_tag_types[0] = 4; /* MB2_REQ_TAG_BASIC_MEM_INFO */
|
||||
|
||||
term = (struct mb2_tag *)(header + 32);
|
||||
term->type = 0; term->flags = 0; term->size = 8;
|
||||
|
||||
ck_assert_int_eq(
|
||||
mb2_build_boot_info_header(boot_info, header, &p,
|
||||
sizeof(boot_info)), -1);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_dump_header_length_underflow)
|
||||
{
|
||||
uint8_t header[48] __attribute__((aligned(8)));
|
||||
|
|
@ -767,6 +803,7 @@ Suite *wolfboot_suite(void)
|
|||
tcase_add_test(tc_build, test_build_info_unsupported_tag);
|
||||
tcase_add_test(tc_build, test_build_info_malformed_size);
|
||||
tcase_add_test(tc_build, test_build_info_header_length_underflow);
|
||||
tcase_add_test(tc_build, test_build_info_header_length_overflow);
|
||||
tcase_add_test(tc_build, test_dump_header_length_underflow);
|
||||
suite_add_tcase(s, tc_build);
|
||||
|
||||
|
|
|
|||
|
|
@ -1000,6 +1000,57 @@ START_TEST(test_program_bar_no_space)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
/* test_program_bar_2gb_no_wrap: a device advertising a 2GB non-prefetchable
|
||||
* 32-bit MMIO BAR (bar_align == 0x80000000, length == 0x80000000) must be
|
||||
* rejected. The aligned start 0x80000000 passes the start-only limit check in
|
||||
* pci_enum_next_aligned32, but [start, start+length) overruns the 128MB pool
|
||||
* and the cursor update *base = start + length wraps to 0 in uint32_t. That
|
||||
* wrap collides every subsequent BAR onto the low IVT/BIOS region and
|
||||
* mis-programs the bridge MMIO window to a spurious 2GB range. The oversized
|
||||
* BAR must be skipped and the allocator left untouched. */
|
||||
START_TEST(test_program_bar_2gb_no_wrap)
|
||||
{
|
||||
struct test_pci_topology t;
|
||||
struct pci_enum_info info;
|
||||
uint8_t is_64bit = 0;
|
||||
int dev_node;
|
||||
int ret;
|
||||
uint32_t bar0_val;
|
||||
|
||||
test_pci_init(&t);
|
||||
dev_node = test_pci_add_dev(&t, 0, 0, 0x1234, 0x5678, TEST_PCI_ROOT_BUS);
|
||||
/* 2GB 32-bit non-prefetchable MMIO BAR */
|
||||
test_pci_dev_set_bar(&t, dev_node, 0, 0x80000000, TEST_PCI_BAR_MMIO);
|
||||
test_pci_commit(&t);
|
||||
|
||||
/* Pre-fill BAR0 so we can confirm it is restored, not programmed. */
|
||||
{
|
||||
uint32_t orig = 0xBEEF0000;
|
||||
memcpy(&t.nodes[dev_node].cfg[PCI_BAR0_OFFSET], &orig, 4);
|
||||
}
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.mem = 0x80000000;
|
||||
info.mem_limit = 0x88000000;
|
||||
info.mem_pf = 0x90000000;
|
||||
info.mem_pf_limit = 0xFFFFFFFF;
|
||||
info.io = 0x2000;
|
||||
|
||||
ret = pci_program_bar(0, 0, 0, 0, &info, &is_64bit);
|
||||
/* the oversized BAR must not be accepted */
|
||||
ck_assert_int_ne(ret, 0);
|
||||
|
||||
/* the allocator cursor must NOT have wrapped to 0 */
|
||||
ck_assert_uint_eq(info.mem, 0x80000000);
|
||||
|
||||
/* BAR0 restored, never programmed onto the MMIO window */
|
||||
bar0_val = pci_config_read32(0, 0, 0, PCI_BAR0_OFFSET);
|
||||
ck_assert_uint_eq(bar0_val, 0xBEEF0000);
|
||||
|
||||
test_pci_cleanup(&t);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* test_program_bars_iteration: full BAR iteration with mixed types */
|
||||
START_TEST(test_program_bars_iteration)
|
||||
{
|
||||
|
|
@ -1786,6 +1837,10 @@ Suite *wolfboot_suite(void)
|
|||
tcase_add_test(tc_bar_nospace, test_program_bar_no_space);
|
||||
suite_add_tcase(s, tc_bar_nospace);
|
||||
|
||||
TCase *tc_bar_2gb = tcase_create("program-bar-2gb-no-wrap");
|
||||
tcase_add_test(tc_bar_2gb, test_program_bar_2gb_no_wrap);
|
||||
suite_add_tcase(s, tc_bar_2gb);
|
||||
|
||||
TCase *tc_bars_iter = tcase_create("program-bars-iteration");
|
||||
tcase_add_test(tc_bars_iter, test_program_bars_iteration);
|
||||
suite_add_tcase(s, tc_bars_iter);
|
||||
|
|
|
|||
|
|
@ -455,6 +455,55 @@ START_TEST(test_find_object_search_stops_at_header_sector)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
/* Prove F-4649: shorter overwrite must not leave prior key bytes in flash.
|
||||
* Write 512 bytes of 0xAA, then reopen and write 50 bytes of 0xBB.
|
||||
* Raw flash at offsets [8+50 .. 8+512) must be 0xFF (erased), not 0xAA. */
|
||||
START_TEST(test_shorter_overwrite_erases_residual_key_material)
|
||||
{
|
||||
const int type = DYNAMIC_TYPE_RSA;
|
||||
const CK_ULONG id_tok = 42;
|
||||
const CK_ULONG id_obj = 84;
|
||||
void *store = NULL;
|
||||
struct store_handle *h;
|
||||
unsigned char large_key[512];
|
||||
unsigned char small_key[50];
|
||||
uint8_t *obj_flash;
|
||||
uint32_t i;
|
||||
int ret;
|
||||
|
||||
memset(large_key, 0xAA, sizeof(large_key));
|
||||
memset(small_key, 0xBB, sizeof(small_key));
|
||||
|
||||
ret = mmap_file(vault_path, vault_base, keyvault_size, NULL);
|
||||
ck_assert_int_eq(ret, 0);
|
||||
memset(vault_base, 0xEE, keyvault_size);
|
||||
|
||||
/* Write large key to a fresh slot */
|
||||
ret = wolfPKCS11_Store_Open(type, id_tok, id_obj, 0, &store);
|
||||
ck_assert_int_eq(ret, 0);
|
||||
ret = wolfPKCS11_Store_Write(store, large_key, sizeof(large_key));
|
||||
ck_assert_int_eq(ret, (int)sizeof(large_key));
|
||||
wolfPKCS11_Store_Close(store);
|
||||
|
||||
/* Reopen in write mode and store a shorter key */
|
||||
ret = wolfPKCS11_Store_Open(type, id_tok, id_obj, 0, &store);
|
||||
ck_assert_int_eq(ret, 0);
|
||||
h = store;
|
||||
obj_flash = h->buffer;
|
||||
ret = wolfPKCS11_Store_Write(store, small_key, sizeof(small_key));
|
||||
ck_assert_int_eq(ret, (int)sizeof(small_key));
|
||||
wolfPKCS11_Store_Close(store);
|
||||
|
||||
/* Flash beyond the new payload must be erased (0xFF), not old 0xAA */
|
||||
for (i = 2 * sizeof(uint32_t) + sizeof(small_key);
|
||||
i < 2 * sizeof(uint32_t) + sizeof(large_key); i++) {
|
||||
ck_assert_msg(obj_flash[i] == 0xFF,
|
||||
"Residual key material at object offset %u: 0x%02x (expected 0xFF)",
|
||||
i, obj_flash[i]);
|
||||
}
|
||||
}
|
||||
END_TEST
|
||||
|
||||
Suite *wolfboot_suite(void)
|
||||
{
|
||||
/* Suite initialization */
|
||||
|
|
@ -466,18 +515,21 @@ Suite *wolfboot_suite(void)
|
|||
TCase* tcase_delete_object = tcase_create("delete_object");
|
||||
TCase* tcase_delete_corrupted = tcase_create("delete_corrupted_pos");
|
||||
TCase* tcase_find_bounds = tcase_create("find_bounds");
|
||||
TCase* tcase_remanence = tcase_create("shorter_overwrite_erases_residual");
|
||||
tcase_add_test(tcase_store_and_load_objs, test_store_and_load_objs);
|
||||
tcase_add_test(tcase_cross_sector_write, test_cross_sector_write_preserves_length);
|
||||
tcase_add_test(tcase_close, test_close_clears_handle_state);
|
||||
tcase_add_test(tcase_delete_object, test_delete_object_ignores_metadata_prefix);
|
||||
tcase_add_test(tcase_delete_corrupted, test_delete_object_corrupted_pos_no_oob);
|
||||
tcase_add_test(tcase_find_bounds, test_find_object_search_stops_at_header_sector);
|
||||
tcase_add_test(tcase_remanence, test_shorter_overwrite_erases_residual_key_material);
|
||||
suite_add_tcase(s, tcase_store_and_load_objs);
|
||||
suite_add_tcase(s, tcase_cross_sector_write);
|
||||
suite_add_tcase(s, tcase_close);
|
||||
suite_add_tcase(s, tcase_delete_object);
|
||||
suite_add_tcase(s, tcase_delete_corrupted);
|
||||
suite_add_tcase(s, tcase_find_bounds);
|
||||
suite_add_tcase(s, tcase_remanence);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -154,6 +154,44 @@ START_TEST(test_delete_object_ignores_metadata_prefix)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_delete_object_corrupted_pos_no_oob)
|
||||
{
|
||||
enum { type = WOLFPSA_STORE_KEY };
|
||||
const uint32_t tok_id = 0x0A0B0C0DU;
|
||||
const uint32_t obj_id = 0x10203040U;
|
||||
struct obj_hdr *hdr;
|
||||
int ret;
|
||||
|
||||
ret = mmap_file("/tmp/wolfboot-unit-psa-keyvault.bin", vault_base,
|
||||
keyvault_size, NULL);
|
||||
ck_assert_int_eq(ret, 0);
|
||||
memset(vault_base, 0xFF, keyvault_size);
|
||||
|
||||
/* Valid header magic and zeroed bitmap so check_vault() accepts the
|
||||
* sector without restoring/reinitializing it. */
|
||||
((uint32_t *)vault_base)[0] = VAULT_HEADER_MAGIC;
|
||||
memset(vault_base + sizeof(uint32_t), 0x00, BITMAP_SIZE);
|
||||
|
||||
/* Simulate a power-fault-corrupted node: valid tok/obj/type but the
|
||||
* 'pos' field was never written and is left as erased flash
|
||||
* (WOLFPSA_INVALID_ID). delete_object() must not turn this into an
|
||||
* out-of-bounds bitmap_put(0xFFFFFFFF, 0). */
|
||||
hdr = NODES_TABLE;
|
||||
hdr->token_id = tok_id;
|
||||
hdr->object_id = obj_id;
|
||||
hdr->type = type;
|
||||
hdr->pos = WOLFPSA_INVALID_ID;
|
||||
hdr->size = 2 * sizeof(uint32_t);
|
||||
|
||||
delete_object(type, tok_id, obj_id);
|
||||
|
||||
/* If we get here without a crash, the OOB write was avoided. The node
|
||||
* should also have been invalidated. */
|
||||
ck_assert_uint_eq(NODES_TABLE->token_id, WOLFPSA_INVALID_ID);
|
||||
ck_assert_uint_eq(NODES_TABLE->object_id, WOLFPSA_INVALID_ID);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_find_object_search_stops_at_header_sector)
|
||||
{
|
||||
enum { type = WOLFPSA_STORE_KEY };
|
||||
|
|
@ -184,22 +222,72 @@ START_TEST(test_find_object_search_stops_at_header_sector)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_shorter_overwrite_clears_tail)
|
||||
{
|
||||
enum { type = WOLFPSA_STORE_KEY };
|
||||
const unsigned long id1 = 3;
|
||||
const unsigned long id2 = 4;
|
||||
void *store = NULL;
|
||||
unsigned char long_key[200];
|
||||
unsigned char short_key[32];
|
||||
uint8_t *obj_buf;
|
||||
int ret, i;
|
||||
|
||||
ret = mmap_file("/tmp/wolfboot-unit-psa-keyvault.bin", vault_base,
|
||||
keyvault_size, NULL);
|
||||
ck_assert_int_eq(ret, 0);
|
||||
memset(vault_base, 0xFF, keyvault_size);
|
||||
|
||||
memset(long_key, 0xAA, sizeof(long_key));
|
||||
memset(short_key, 0xBB, sizeof(short_key));
|
||||
|
||||
/* First write: 200-byte key */
|
||||
ret = wolfPSA_Store_Open(type, id1, id2, 0, &store);
|
||||
ck_assert_int_eq(ret, 0);
|
||||
ret = wolfPSA_Store_Write(store, long_key, 200);
|
||||
ck_assert_int_eq(ret, 200);
|
||||
wolfPSA_Store_Close(store);
|
||||
|
||||
/* Second write: 32-byte key (shorter than original) */
|
||||
ret = wolfPSA_Store_Open(type, id1, id2, 0, &store);
|
||||
ck_assert_int_eq(ret, 0);
|
||||
ret = wolfPSA_Store_Write(store, short_key, 32);
|
||||
ck_assert_int_eq(ret, 32);
|
||||
wolfPSA_Store_Close(store);
|
||||
|
||||
/* Tail bytes (beyond the new key) must NOT contain the old 0xAA pattern */
|
||||
obj_buf = find_object_buffer(type, id1, id2);
|
||||
ck_assert_ptr_nonnull(obj_buf);
|
||||
for (i = 2 * (int)sizeof(uint32_t) + 32;
|
||||
i < 2 * (int)sizeof(uint32_t) + 200; i++) {
|
||||
ck_assert_msg(obj_buf[i] != 0xAA,
|
||||
"Old key material survives at offset %d: 0x%02x", i, obj_buf[i]);
|
||||
}
|
||||
}
|
||||
END_TEST
|
||||
|
||||
Suite *wolfboot_suite(void)
|
||||
{
|
||||
Suite *s = suite_create("wolfBoot-psa-store");
|
||||
TCase *tcase_write = tcase_create("cross_sector_write");
|
||||
TCase *tcase_close = tcase_create("close_state");
|
||||
TCase *tcase_delete = tcase_create("delete_object");
|
||||
TCase *tcase_delete_corrupted = tcase_create("delete_corrupted_pos");
|
||||
TCase *tcase_find_bounds = tcase_create("find_bounds");
|
||||
TCase *tcase_tail = tcase_create("shorter_overwrite_clears_tail");
|
||||
|
||||
tcase_add_test(tcase_write, test_cross_sector_write_preserves_length);
|
||||
tcase_add_test(tcase_close, test_close_clears_handle_state);
|
||||
tcase_add_test(tcase_delete, test_delete_object_ignores_metadata_prefix);
|
||||
tcase_add_test(tcase_delete_corrupted, test_delete_object_corrupted_pos_no_oob);
|
||||
tcase_add_test(tcase_find_bounds, test_find_object_search_stops_at_header_sector);
|
||||
tcase_add_test(tcase_tail, test_shorter_overwrite_clears_tail);
|
||||
suite_add_tcase(s, tcase_write);
|
||||
suite_add_tcase(s, tcase_close);
|
||||
suite_add_tcase(s, tcase_delete);
|
||||
suite_add_tcase(s, tcase_delete_corrupted);
|
||||
suite_add_tcase(s, tcase_find_bounds);
|
||||
suite_add_tcase(s, tcase_tail);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -168,6 +168,37 @@ START_TEST(test_disk_write_unaligned_spans_blocks)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
/* A byte offset whose block address (offset / 512) does not fit in the 32-bit
|
||||
* SD command argument must be rejected, not silently truncated/wrapped to an
|
||||
* in-range sector. Here offset / 512 == 0x100000000, which truncates to 0. */
|
||||
START_TEST(test_disk_read_rejects_block_addr_overflow)
|
||||
{
|
||||
uint8_t out[SDHCI_BLOCK_SIZE];
|
||||
uint64_t start = (uint64_t)0x100000000ULL * SDHCI_BLOCK_SIZE;
|
||||
|
||||
reset_mock_state();
|
||||
|
||||
/* Must fail rather than wrap and read sector 0 (mock_disk[0..]). */
|
||||
ck_assert_int_ne(disk_read(0, start, sizeof(out), out), 0);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_disk_write_rejects_block_addr_overflow)
|
||||
{
|
||||
uint8_t in[SDHCI_BLOCK_SIZE];
|
||||
uint8_t before[sizeof(mock_disk)];
|
||||
uint64_t start = (uint64_t)0x100000000ULL * SDHCI_BLOCK_SIZE;
|
||||
|
||||
reset_mock_state();
|
||||
memcpy(before, mock_disk, sizeof(before));
|
||||
memset(in, 0x5A, sizeof(in));
|
||||
|
||||
/* Must fail rather than wrap and overwrite sector 0. */
|
||||
ck_assert_int_ne(disk_write(0, start, sizeof(in), in), 0);
|
||||
ck_assert_mem_eq(mock_disk, before, sizeof(before));
|
||||
}
|
||||
END_TEST
|
||||
|
||||
Suite *sdhci_disk_suite(void)
|
||||
{
|
||||
Suite *s = suite_create("sdhci-disk");
|
||||
|
|
@ -175,6 +206,8 @@ Suite *sdhci_disk_suite(void)
|
|||
|
||||
tcase_add_test(tc, test_disk_read_unaligned_spans_blocks);
|
||||
tcase_add_test(tc, test_disk_write_unaligned_spans_blocks);
|
||||
tcase_add_test(tc, test_disk_read_rejects_block_addr_overflow);
|
||||
tcase_add_test(tc, test_disk_write_rejects_block_addr_overflow);
|
||||
suite_add_tcase(s, tc);
|
||||
|
||||
return s;
|
||||
|
|
|
|||
|
|
@ -988,6 +988,28 @@ START_TEST (test_emergency_rollback) {
|
|||
cleanup_flash();
|
||||
}
|
||||
|
||||
START_TEST (test_emergency_rollback_equal_versions) {
|
||||
uint8_t testing_flags[5] = { IMG_STATE_TESTING, 'B', 'O', 'O', 'T' };
|
||||
uint8_t st = 0;
|
||||
reset_mock_stats();
|
||||
prepare_flash();
|
||||
add_payload(PART_BOOT, 1, TEST_SIZE_SMALL);
|
||||
add_payload(PART_UPDATE, 1, TEST_SIZE_SMALL);
|
||||
/* Set the testing flag in the last five bytes of the BOOT partition */
|
||||
hal_flash_unlock();
|
||||
hal_flash_write(WOLFBOOT_PARTITION_BOOT_ADDRESS + WOLFBOOT_PARTITION_SIZE - 5,
|
||||
testing_flags, 5);
|
||||
hal_flash_lock();
|
||||
|
||||
wolfBoot_start();
|
||||
ck_assert(!wolfBoot_panicked);
|
||||
ck_assert(wolfBoot_staged_ok);
|
||||
/* After equal-version emergency rollback, boot partition must be SUCCESS */
|
||||
ck_assert_int_eq(wolfBoot_get_partition_state(PART_BOOT, &st), 0);
|
||||
ck_assert_uint_eq(st, IMG_STATE_SUCCESS);
|
||||
cleanup_flash();
|
||||
}
|
||||
|
||||
START_TEST (test_emergency_rollback_failure_due_to_bad_update) {
|
||||
uint8_t testing_flags[5] = { IMG_STATE_TESTING, 'B', 'O', 'O', 'T' };
|
||||
uint8_t wrong_update_magic[4] = { 'G', 'O', 'L', 'F' };
|
||||
|
|
@ -1531,6 +1553,7 @@ Suite *wolfboot_suite(void)
|
|||
tcase_add_test(zero_size_update, test_zero_size_update_rejected);
|
||||
tcase_add_test(invalid_sha, test_invalid_sha);
|
||||
tcase_add_test(emergency_rollback, test_emergency_rollback);
|
||||
tcase_add_test(emergency_rollback, test_emergency_rollback_equal_versions);
|
||||
tcase_add_test(emergency_rollback_failure_due_to_bad_update, test_emergency_rollback_failure_due_to_bad_update);
|
||||
tcase_add_test(empty_boot_partition_update, test_empty_boot_partition_update);
|
||||
tcase_add_test(empty_boot_but_update_sha_corrupted_denied, test_empty_boot_but_update_sha_corrupted_denied);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
/* unit-x86-paging-oob.c
|
||||
*
|
||||
* Regression test for OOB memset in x86_paging_setup_ptp when pool is
|
||||
* exhausted. The guard must fire for any call where page_table_page_used
|
||||
* >= WOLFBOOT_PTP_NUM, not only the exact == boundary. With the old
|
||||
* single-hlt panic() the guard check can be bypassed on a second call,
|
||||
* writing 4 KB one page past the end of page_table_pages[].
|
||||
*/
|
||||
|
||||
#include <check.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
static jmp_buf panic_jmp;
|
||||
static int panic_count = 0;
|
||||
|
||||
/* Satisfies __attribute__((noreturn)) via longjmp; allows test to continue
|
||||
* after a guarded panic call without executing the code that follows it. */
|
||||
__attribute__((noreturn)) void panic(void)
|
||||
{
|
||||
panic_count++;
|
||||
longjmp(panic_jmp, 1);
|
||||
}
|
||||
|
||||
/* paging.c includes <printf.h> which maps wolfBoot_printf to fprintf(stderr,
|
||||
* ...) on Linux, so no extra stub is needed. The cr3-reading static function
|
||||
* x86_paging_get_paget_table_root() is compiled but never called here. */
|
||||
#include "../../src/x86/paging.c"
|
||||
|
||||
static void reset_pool(void)
|
||||
{
|
||||
page_table_page_used = 0;
|
||||
panic_count = 0;
|
||||
memset(page_table_pages, 0, sizeof(page_table_pages));
|
||||
}
|
||||
|
||||
/* Verify that x86_paging_setup_ptp triggers panic for ANY call when the pool
|
||||
* is full (page_table_page_used >= WOLFBOOT_PTP_NUM), not only the first. */
|
||||
START_TEST(test_ptp_guard_triggers_on_full_pool)
|
||||
{
|
||||
uint64_t e = 0;
|
||||
|
||||
reset_pool();
|
||||
page_table_page_used = WOLFBOOT_PTP_NUM;
|
||||
|
||||
/* An over-limit call must trigger panic (longjmp). */
|
||||
if (setjmp(panic_jmp) == 0) {
|
||||
x86_paging_setup_ptp(&e);
|
||||
/* Reaching here means the guard was bypassed — the bug is present. */
|
||||
ck_abort_msg("panic not triggered for over-limit allocation (== guard bypassed)");
|
||||
}
|
||||
ck_assert_int_eq(panic_count, 1);
|
||||
|
||||
/* page_table_page_used must not have advanced past WOLFBOOT_PTP_NUM;
|
||||
* with the fix the counter is checked before being incremented. */
|
||||
ck_assert_int_le(page_table_page_used, WOLFBOOT_PTP_NUM);
|
||||
|
||||
/* A second over-limit call must also trigger panic (>= guard). */
|
||||
if (setjmp(panic_jmp) == 0) {
|
||||
x86_paging_setup_ptp(&e);
|
||||
ck_abort_msg("panic not triggered on second over-limit allocation");
|
||||
}
|
||||
ck_assert_int_eq(panic_count, 2);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
static Suite *paging_oob_suite(void)
|
||||
{
|
||||
Suite *s = suite_create("x86_paging_oob");
|
||||
TCase *tc = tcase_create("setup_ptp_guard");
|
||||
tcase_add_test(tc, test_ptp_guard_triggers_on_full_pool);
|
||||
suite_add_tcase(s, tc);
|
||||
return s;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
Suite *s = paging_oob_suite();
|
||||
SRunner *sr = srunner_create(s);
|
||||
srunner_run_all(sr, CK_NORMAL);
|
||||
int failed = srunner_ntests_failed(sr);
|
||||
srunner_free(sr);
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
Loading…
Reference in New Issue