Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
55e13b5
F-4782: bound hdr->pos in bitmap_put to prevent OOB write from corrup…
danielinux Jun 10, 2026
b3cfb9e
F-4717: reject PCI BARs whose region overruns the pool to prevent cur…
danielinux Jun 10, 2026
306a304
F-4716: reject SDHCI block addresses that overflow 32-bit SD command …
danielinux Jun 10, 2026
e0f271b
F-4715: bound GPT partition-entry array size before CRC scan in disk_…
danielinux Jun 10, 2026
93a8fe8
F-4714: compute GPT header offset in 64 bits to prevent uint32_t over…
danielinux Jun 10, 2026
0f01a9d
F-4692: guard squashelf segment-offset alignment against integer over…
danielinux Jun 10, 2026
6a60ea3
F-4651: bound multiboot2 header_length to prevent OOB tag walk
danielinux Jun 10, 2026
71545e9
F-4339: widen uart-flash-server bounds checks to 64-bit to stop uint3…
danielinux Jun 10, 2026
3423173
F-4258: reject GPT partitions whose LBA extent overflows uint64_t
danielinux Jun 10, 2026
f7e7733
F-5747: guard fdt_data_size_ and fdt_splice_string_ against off_dt_st…
danielinux Jun 10, 2026
2b0fde4
F-5745: fix off-by-one in hal_flash_erase skipping last page when len…
danielinux Jun 10, 2026
c165905
F-5357: zeroize digest and uuid_be stack buffers in hal_uds_derive_ke…
danielinux Jun 10, 2026
d3bd09f
F-5351: test wolfBoot_open_self_address rejects bad magic with hdr_ok…
danielinux Jun 10, 2026
712f389
F-5348: always evaluate keyslot_CT_hint_matches for every slot
danielinux Jun 10, 2026
286581c
F-5093: fix OOB memset in x86_paging_setup_ptp and non-looping panic
danielinux Jun 10, 2026
87fc929
F-5092: fix off-by-one in fwtpm_nv_read/write/erase offset guard
danielinux Jun 10, 2026
9bfecfb
Fix fwTPM unit test CI config
danielinux Jun 10, 2026
f7bf241
Guard mcxn UDS zeroize helper
danielinux Jun 10, 2026
f20cd30
F-4972: zeroize stack wc_MlDsaKey after Free in keygen_ml_dsa cleanup
danielinux Jun 10, 2026
652febd
F-4971: zeroize stack ed448_key after Free in keygen_ed448 cleanup
danielinux Jun 10, 2026
ca3e76a
F-4970: zeroize stack ecc_key after Free in keygen_ecc cleanup
danielinux Jun 10, 2026
793cec4
F-4969: zeroize stack RsaKey after FreeRsaKey in keygen_rsa cleanup
danielinux Jun 10, 2026
3349bef
F-4966: fix nwords=0 truncation on sub-word write to 16-bit NOR flash
danielinux Jun 10, 2026
5958185
F-4788: zeroize stack ed25519_key after Free in keygen_ed25519 cleanup
danielinux Jun 10, 2026
051c41c
F-4786: fix delta_base_version LE decode in base_diff for big-endian …
danielinux Jun 10, 2026
2a3cbf2
F-4784: erase psa_store object data on write-mode open to clear resid…
danielinux Jun 10, 2026
a24f107
F-4649: erase pkcs11_store object sectors on write-mode open to clear…
danielinux Jun 10, 2026
63b58c5
F-4647: bound strlen to ATA_SECURITY_PASSWORD_LEN in passphrase path
danielinux Jun 10, 2026
ef16945
F-4420: zeroize XmssKey on all error paths in keygen_xmss
danielinux Jun 10, 2026
aebecba
F-4419: zeroize LmsKey on all error paths in keygen_lms
danielinux Jun 10, 2026
ad3bc88
F-4418: fix BSS-clear collision guard to use mem_size instead of file…
danielinux Jun 10, 2026
9e390e8
F-4414: replace strtol with strtoul for fw_version to fix saturation …
danielinux Jun 10, 2026
82142f8
F-4412: add equal-version emergency rollback test to pin >= operator
danielinux Jun 10, 2026
893973a
Handle fdt splice size errors (addressed Mattia's comment)
danielinux Jun 10, 2026
88befb6
Preserve neighboring store objects on truncate (addressed Fenrir's
danielinux Jun 10, 2026
590164e
Fix x86 FSP freestanding build regressions
danielinux Jun 10, 2026
423c1d4
Revert misguided secret_size guard in sata_unlock_disk
danielinux Jun 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/test-external-library-paths.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
21 changes: 19 additions & 2 deletions hal/mcxn.c
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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)
{
Expand All @@ -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 */
}
Expand Down
21 changes: 21 additions & 0 deletions hal/nxp_t10xx.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion hal/stm32h7.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions include/gpt.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions include/x86/ata.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion include/x86/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
16 changes: 15 additions & 1 deletion src/disk.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/elf.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
28 changes: 23 additions & 5 deletions src/fdt.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment thread
mattia-moffa marked this conversation as resolved.
return (int)(off + sz);
}

static const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int len)
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
Expand Down
6 changes: 3 additions & 3 deletions src/fwtpm_callable.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down
8 changes: 8 additions & 0 deletions src/gpt.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions src/image.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
7 changes: 6 additions & 1 deletion src/multiboot.c
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
12 changes: 12 additions & 0 deletions src/pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
37 changes: 37 additions & 0 deletions src/pkcs11_store.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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();
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
}


Expand Down
Loading
Loading