Skip to content

Commit 3db629e

Browse files
committed
fdt: rewrite device tree parser with capacity bound and full validation
1 parent 626f3ab commit 3db629e

30 files changed

Lines changed: 3642 additions & 1562 deletions

.github/workflows/test-parse-tools.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,12 @@ jobs:
4646
- name: Run fdt-parser test (nxp_t1024.dtb)
4747
run: |
4848
./tools/fdt-parser/fdt-parser ./tools/fdt-parser/nxp_t1024.dtb -t
49+
50+
- name: Run fdt-parser malformed-input corpus under ASan/UBSan
51+
run: |
52+
gcc -o /tmp/fdt-parser-asan -Wall -Werror -g -O1 \
53+
-fsanitize=address,undefined -fno-omit-frame-pointer \
54+
-I include -DWOLFBOOT_FDT -DWOLFBOOT_FDT_CORPUS -DPRINTF_ENABLED \
55+
tools/fdt-parser/fdt-parser.c src/fdt.c
56+
ASAN_OPTIONS=detect_leaks=0 \
57+
/tmp/fdt-parser-asan ./tools/fdt-parser/nxp_t1024.dtb -f

docs/Targets.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5007,7 +5007,7 @@ Image kernel-1: 0x200000 (24617472 bytes)
50075007
Loading Image fdt-1: 0x1177A3DC -> 0x1000 (39384 bytes)
50085008
Image fdt-1: 0x1000 (39384 bytes)
50095009
Loading DTS: 0x1000 -> 0x1000 (39384 bytes)
5010-
FDT: Version 17, Size 39384
5010+
FDT: Size 39384
50115011
FDT: Setting bootargs: earlycon root=/dev/mmcblk0p2 rootwait
50125012
FDT: Set chosen (28076), bootargs=earlycon root=/dev/mmcblk0p2 rootwait
50135013
Booting at 0x200000

hal/cm4.c

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,9 @@ void* hal_get_dts_update_address(void)
152152
* clock, serial no.) to the kernel it loads - which is wolfBoot. */
153153
extern void* cm4_fw_dtb;
154154

155-
/* Upper bound for the firmware DTB copy. fdt_check_header() does not validate
156-
* totalsize, so cap it: a valid DTB is well under the arm64 boot-protocol 2MB
157-
* limit, and this keeps a corrupt header from overrunning the DTS landing zone.
155+
/* Size of the DTS landing zone, and so the capacity handed to fdt_open()
156+
* for the firmware DTB: a valid DTB is well under the arm64 boot-protocol
157+
* 2MB limit, and this keeps a corrupt header from overrunning the zone.
158158
* Override with -DCM4_FDT_MAX_SIZE=<bytes>. */
159159
#ifndef CM4_FDT_MAX_SIZE
160160
#define CM4_FDT_MAX_SIZE 0x200000
@@ -358,24 +358,25 @@ void* hal_get_boot_dts(void)
358358
* validated NULL-DTB handoff is unchanged. */
359359
return NULL;
360360
#else
361+
fdt_ctx ctx;
361362
void *fdt = cm4_fw_dtb;
362363
uint32_t sz;
363364
int off;
364365

365-
if (fdt == NULL || fdt_check_header(fdt) != 0) {
366-
wolfBoot_printf("cm4: no valid firmware DTB (%p)\n", fdt);
366+
if (fdt == NULL) {
367+
wolfBoot_printf("cm4: no firmware DTB\n");
367368
return NULL;
368369
}
369-
sz = (uint32_t)fdt_totalsize(fdt);
370-
/* fdt_check_header() does not validate totalsize; bound it so a corrupt DTB
371-
* header cannot make the copy/fixup clobber memory past the DTS landing
372-
* zone (leaving room for the fixup headroom too). */
373-
if (sz < sizeof(struct fdt_header) ||
374-
sz > CM4_FDT_MAX_SIZE - WOLFBOOT_FDT_FIXUP_HEADROOM) {
375-
wolfBoot_printf("cm4: firmware DTB size %u out of range\n",
376-
(unsigned)sz);
370+
/* Bound the parse by the largest DTB this path can ever accept: the
371+
* DTS landing zone less the fixup headroom. A corrupt header can then
372+
* neither be walked out of bounds here nor make the copy below clobber
373+
* memory past that zone. */
374+
if (fdt_open(&ctx, fdt,
375+
CM4_FDT_MAX_SIZE - WOLFBOOT_FDT_FIXUP_HEADROOM) != 0) {
376+
wolfBoot_printf("cm4: no valid firmware DTB (%p)\n", fdt);
377377
return NULL;
378378
}
379+
sz = fdt_size(&ctx);
379380
/* SECURITY: the firmware DTB lives on the unsigned FAT partition (unless
380381
* the RPi EEPROM secure-boot is enabled), so it is NOT covered by wolfBoot's
381382
* signature. Only /chosen/bootargs is overwritten below; /memory,
@@ -405,12 +406,16 @@ void* hal_get_boot_dts(void)
405406
fdt = (void*)WOLFBOOT_LOAD_DTS_ADDRESS;
406407

407408
/* Zero the appended headroom so the grown blob holds no uninitialized
408-
* bytes, then record the new total size. */
409+
* bytes, then re-open on the landing zone and record the new size. */
409410
memset((uint8_t*)fdt + sz, 0, WOLFBOOT_FDT_FIXUP_HEADROOM);
410-
fdt_set_totalsize(fdt, sz + WOLFBOOT_FDT_FIXUP_HEADROOM);
411-
off = fdt_find_node_offset(fdt, -1, "chosen");
411+
if (fdt_open(&ctx, fdt, CM4_FDT_MAX_SIZE) != 0 ||
412+
fdt_grow(&ctx, WOLFBOOT_FDT_FIXUP_HEADROOM) != 0) {
413+
wolfBoot_printf("cm4: relocated DTB rejected; refusing firmware DTB\n");
414+
return NULL;
415+
}
416+
off = fdt_subnode_offset(&ctx, 0, "chosen");
412417
if (off == -FDT_ERR_NOTFOUND) {
413-
off = fdt_add_subnode(fdt, 0, "chosen");
418+
off = fdt_add_subnode(&ctx, 0, "chosen");
414419
}
415420
if (off < 0) {
416421
/* Fail closed: without /chosen we cannot inject our known-good bootargs,
@@ -426,13 +431,13 @@ void* hal_get_boot_dts(void)
426431
const char *args = LINUX_BOOTARGS;
427432
if (cm4_rauc_build_bootargs(cm4_bootargs, sizeof(cm4_bootargs)) == 0)
428433
args = cm4_bootargs;
429-
if (fdt_fixup_str(fdt, off, "chosen", "bootargs", args) != 0) {
434+
if (fdt_fixup_str(&ctx, off, "chosen", "bootargs", args) != 0) {
430435
wolfBoot_printf("cm4: DTB bootargs fixup failed; refusing firmware DTB\n");
431436
return NULL;
432437
}
433438
}
434439
#else
435-
if (fdt_fixup_str(fdt, off, "chosen", "bootargs", LINUX_BOOTARGS) != 0) {
440+
if (fdt_fixup_str(&ctx, off, "chosen", "bootargs", LINUX_BOOTARGS) != 0) {
436441
wolfBoot_printf("cm4: DTB bootargs fixup failed; refusing firmware DTB\n");
437442
return NULL;
438443
}

hal/mpfs250.c

Lines changed: 41 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -601,10 +601,10 @@ int mpfs_read_serial_number(uint8_t *serial)
601601
#define MICROCHIP_OUI_1 0x04
602602
#define MICROCHIP_OUI_2 0xA3
603603

604-
static int mpfs_dts_fixup_inplace(void* dts_addr)
604+
static int mpfs_dts_fixup_inplace(void* dts_addr, uint32_t capacity)
605605
{
606+
fdt_ctx ctx;
606607
int off, ret;
607-
struct fdt_header *fdt = (struct fdt_header *)dts_addr;
608608
uint8_t device_serial_number[DEVICE_SERIAL_NUMBER_SIZE];
609609
uint8_t mac_addr[6];
610610
#if defined(MPFS_DDR_INIT) && defined(WOLFBOOT_MMODE_SMODE_BOOT)
@@ -620,31 +620,33 @@ static int mpfs_dts_fixup_inplace(void* dts_addr)
620620
unsigned int i;
621621
#endif
622622

623-
/* Verify FDT header */
624-
ret = fdt_check_header(dts_addr);
623+
/* Validate the blob against the window it actually occupies. */
624+
ret = fdt_open(&ctx, dts_addr, capacity);
625625
if (ret != 0) {
626626
wolfBoot_printf("FDT: Invalid header! %d\n", ret);
627627
return ret;
628628
}
629629

630-
wolfBoot_printf("FDT: Version %d, Size %d\n",
631-
fdt_version(fdt), fdt_totalsize(fdt));
630+
wolfBoot_printf("FDT: Size %d\n", (int)fdt_size(&ctx));
632631

633-
/* Expand total size to allow adding/modifying properties.
632+
/* Reserve free space to allow adding/modifying properties.
634633
* Sizing comes from WOLFBOOT_FDT_FIXUP_HEADROOM in include/fdt.h. */
635-
fdt_set_totalsize(fdt,
636-
fdt_totalsize(fdt) + WOLFBOOT_FDT_FIXUP_HEADROOM);
634+
ret = fdt_grow(&ctx, WOLFBOOT_FDT_FIXUP_HEADROOM);
635+
if (ret != 0) {
636+
wolfBoot_printf("FDT: No headroom for fixups (%d)\n", ret);
637+
return ret;
638+
}
637639

638640
/* Find /chosen node */
639-
off = fdt_find_node_offset(fdt, -1, "chosen");
641+
off = fdt_subnode_offset(&ctx, 0, "chosen");
640642
if (off < 0) {
641643
/* Create /chosen node if it doesn't exist */
642-
off = fdt_add_subnode(fdt, 0, "chosen");
644+
off = fdt_add_subnode(&ctx, 0, "chosen");
643645
}
644646

645647
if (off >= 0) {
646648
/* Set bootargs property */
647-
fdt_fixup_str(fdt, off, "chosen", "bootargs", LINUX_BOOTARGS);
649+
fdt_fixup_str(&ctx, off, "chosen", "bootargs", LINUX_BOOTARGS);
648650
}
649651

650652
#if defined(MPFS_DDR_INIT) && defined(WOLFBOOT_MMODE_SMODE_BOOT)
@@ -665,9 +667,9 @@ static int mpfs_dts_fixup_inplace(void* dts_addr)
665667
* parked harts on the kernel's request (SMP). cpu@0 (E51) is already
666668
* disabled in the Yocto DTB; cpu@1 stays enabled so Linux boots on it. */
667669
for (i = 0; i < sizeof(cpu_off) / sizeof(cpu_off[0]); i++) {
668-
off = fdt_find_node_offset(fdt, -1, cpu_off[i]);
670+
off = fdt_find_node_offset(&ctx, -1, cpu_off[i]);
669671
if (off >= 0) {
670-
ret = fdt_fixup_str(fdt, off, cpu_off[i], "status",
672+
ret = fdt_fixup_str(&ctx, off, cpu_off[i], "status",
671673
"disabled");
672674
if (ret != 0) {
673675
wolfBoot_printf("FDT: Failed to disable %s (%d)\n",
@@ -713,9 +715,9 @@ static int mpfs_dts_fixup_inplace(void* dts_addr)
713715
mac_addr[3], mac_addr[4], mac_addr[5]);
714716

715717
/* Set local-mac-address for ethernet@20110000 (mac0) */
716-
off = fdt_find_node_offset(fdt, -1, "ethernet@20110000");
718+
off = fdt_find_node_offset(&ctx, -1, "ethernet@20110000");
717719
if (off >= 0) {
718-
ret = fdt_setprop(fdt, off, "local-mac-address", mac_addr, 6);
720+
ret = fdt_setprop(&ctx, off, "local-mac-address", mac_addr, 6);
719721
if (ret != 0) {
720722
wolfBoot_printf("FDT: Failed to set mac0 address (%d)\n", ret);
721723
}
@@ -732,9 +734,9 @@ static int mpfs_dts_fixup_inplace(void* dts_addr)
732734
mac_addr[0], mac_addr[1], mac_addr[2],
733735
mac_addr[3], mac_addr[4], mac_addr[5]);
734736

735-
off = fdt_find_node_offset(fdt, -1, "ethernet@20112000");
737+
off = fdt_find_node_offset(&ctx, -1, "ethernet@20112000");
736738
if (off >= 0) {
737-
ret = fdt_setprop(fdt, off, "local-mac-address", mac_addr, 6);
739+
ret = fdt_setprop(&ctx, off, "local-mac-address", mac_addr, 6);
738740
if (ret != 0) {
739741
wolfBoot_printf("FDT: Failed to set mac1 address (%d)\n", ret);
740742
}
@@ -824,9 +826,10 @@ int wolfBoot_fit_memcpy(void *dst, const void *src, uint32_t len)
824826
* (WOLFBOOT_LOAD_DTS_ADDRESS) but CPU writes to DDR do not land here, so copy
825827
* it (non-cached read) into an L2 scratch buffer, run the FDT fixups there
826828
* (CPU L2 writes work), then PDMA the result back to DDR. */
827-
int hal_dts_fixup(void* dts_addr)
829+
int hal_dts_fixup(void* dts_addr, uint32_t capacity)
828830
{
829831
static uint8_t l2_dtb[64 * 1024] __attribute__((aligned(8)));
832+
fdt_ctx ctx;
830833
const uint8_t *ddr_nc;
831834
uint32_t sz;
832835
int ret;
@@ -835,29 +838,29 @@ int hal_dts_fixup(void* dts_addr)
835838
return -1;
836839
}
837840
ddr_nc = (const uint8_t *)((uintptr_t)dts_addr | 0x40000000UL);
838-
if (fdt_check_header((void *)ddr_nc) != 0) {
841+
/* The source is bounded by whichever is smaller: the caller's DDR
842+
* window, or what the L2 scratch buffer can hold once the fixup
843+
* headroom is set aside. fdt_open() enforces it, so the memcpy below
844+
* cannot overrun l2_dtb however corrupt the header is. */
845+
sz = (uint32_t)(sizeof(l2_dtb) - WOLFBOOT_FDT_FIXUP_HEADROOM);
846+
if (capacity < sz) {
847+
sz = capacity;
848+
}
849+
if (fdt_open(&ctx, (void *)ddr_nc, sz) != 0) {
839850
wolfBoot_printf("FDT: invalid header at %p\n", dts_addr);
840851
return -1;
841852
}
842-
sz = (uint32_t)fdt_totalsize((void *)ddr_nc);
843-
/* Overflow-safe bound: compare without adding. A near-UINT32_MAX
844-
* totalsize (fdt_check_header does not bound it) would make
845-
* sz + WOLFBOOT_FDT_FIXUP_HEADROOM wrap to a small value that passes the
846-
* check, after which memcpy(l2_dtb, ., sz) overruns the 64 KB buffer.
847-
* sizeof(l2_dtb) (64 KB) is always greater than the headroom. */
848-
if (sz > sizeof(l2_dtb) - WOLFBOOT_FDT_FIXUP_HEADROOM) {
849-
wolfBoot_printf("FDT: dtb too large for L2 fixup (%u > %u)\n",
850-
(unsigned)sz,
851-
(unsigned)(sizeof(l2_dtb) - WOLFBOOT_FDT_FIXUP_HEADROOM));
852-
return -1;
853-
}
853+
sz = fdt_size(&ctx);
854854
/* DDR (non-cached) -> L2 */
855855
memcpy(l2_dtb, ddr_nc, sz);
856-
/* fixup in the CPU-writable L2 buffer */
857-
ret = mpfs_dts_fixup_inplace(l2_dtb);
856+
/* fixup in the CPU-writable L2 buffer, which may use the whole of it */
857+
ret = mpfs_dts_fixup_inplace(l2_dtb, (uint32_t)sizeof(l2_dtb));
858858
/* L2 -> DDR via PDMA (expanded totalsize) */
859-
if (wolfBoot_fit_memcpy(dts_addr, l2_dtb,
860-
(uint32_t)fdt_totalsize(l2_dtb)) != 0) {
859+
if (fdt_open(&ctx, l2_dtb, (uint32_t)sizeof(l2_dtb)) != 0) {
860+
wolfBoot_printf("FDT: fixed-up dtb rejected\n");
861+
return -1;
862+
}
863+
if (wolfBoot_fit_memcpy(dts_addr, l2_dtb, fdt_size(&ctx)) != 0) {
861864
wolfBoot_printf("FDT: dtb copy-back to DDR failed\n");
862865
return -1;
863866
}
@@ -868,12 +871,12 @@ int hal_dts_fixup(void* dts_addr)
868871
* run the fixups directly in place (the original behavior, kept so
869872
* FDT-enabled non-DDR builds do not silently fall back to the weak
870873
* no-op hal_dts_fixup). */
871-
int hal_dts_fixup(void* dts_addr)
874+
int hal_dts_fixup(void* dts_addr, uint32_t capacity)
872875
{
873876
if (dts_addr == NULL) {
874877
return -1;
875878
}
876-
return mpfs_dts_fixup_inplace(dts_addr);
879+
return mpfs_dts_fixup_inplace(dts_addr, capacity);
877880
}
878881
#endif /* WOLFBOOT_RISCV_MMODE && MPFS_DDR_INIT */
879882

hal/nxp_t10xx.c

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3394,29 +3394,33 @@ void* hal_get_dts_address(void)
33943394
return (void*)WOLFBOOT_DTS_BOOT_ADDRESS;
33953395
}
33963396

3397-
int hal_dts_fixup(void* dts_addr)
3397+
int hal_dts_fixup(void* dts_addr, uint32_t capacity)
33983398
{
33993399
#ifndef BUILD_LOADER_STAGE1
3400-
struct fdt_header *fdt = (struct fdt_header *)dts_addr;
3400+
fdt_ctx ctx;
3401+
fdt_ctx* fdt = &ctx;
34013402
int off, i;
34023403
uint32_t cell;
34033404
uint32_t *reg;
34043405
const char* prev_compat;
34053406

3406-
/* verify the FTD is valid */
3407-
off = fdt_check_header(dts_addr);
3407+
/* Validate the blob against the window it actually occupies. */
3408+
off = fdt_open(&ctx, dts_addr, capacity);
34083409
if (off != 0) {
34093410
wolfBoot_printf("FDT: Invalid header! %d\n", off);
34103411
return off;
34113412
}
34123413

34133414
/* display FTD information */
3414-
wolfBoot_printf("FDT: Version %d, Size %d\n",
3415-
fdt_version(fdt), fdt_totalsize(fdt));
3415+
wolfBoot_printf("FDT: Size %d\n", (int)fdt_size(fdt));
34163416

3417-
/* expand total size */
3418-
fdt->totalsize += 2048; /* expand by 2KB */
3419-
wolfBoot_printf("FDT: Expanded (2KB) to %d bytes\n", fdt->totalsize);
3417+
/* Reserve headroom for the fixups below. */
3418+
off = fdt_grow(fdt, 2048U);
3419+
if (off != 0) {
3420+
wolfBoot_printf("FDT: No headroom for fixups (%d)\n", off);
3421+
return off;
3422+
}
3423+
wolfBoot_printf("FDT: Expanded (2KB) to %d bytes\n", (int)fdt_size(fdt));
34203424

34213425
/* fixup the memory region - single bank */
34223426
off = fdt_find_devtype(fdt, -1, "memory");
@@ -3513,6 +3517,11 @@ int hal_dts_fixup(void* dts_addr)
35133517

35143518
wolfBoot_printf("FDT: Set %s@%d (%d), %s=%d,%d\n",
35153519
"qman-portal", i, off, "fsl,liodn", liodns[0], liodns[1]);
3520+
/* Property cells are big-endian on the wire. Identity on the
3521+
* PowerPC targets that run this, but without it the host tests
3522+
* write host-order bytes that hardware never produces. */
3523+
liodns[0] = cpu_to_fdt32(liodns[0]);
3524+
liodns[1] = cpu_to_fdt32(liodns[1]);
35163525
fdt_setprop(fdt, off, "fsl,liodn", liodns, sizeof(liodns));
35173526

35183527
/* Add fman@0 node and fsl,liodon = FMAN_DMA_LIODN + index */
@@ -3521,6 +3530,7 @@ int hal_dts_fixup(void* dts_addr)
35213530
liodns[0] = FMAN_DMA_LIODN + i + 1;
35223531
wolfBoot_printf("FDT: Set %s@%d/%s (%d), %s=%d\n",
35233532
"qman-portal", i, "fman@0", childoff, "fsl,liodn", liodns[0]);
3533+
liodns[0] = cpu_to_fdt32(liodns[0]);
35243534
fdt_setprop(fdt, childoff, "fsl,liodn", liodns, sizeof(liodns[0]));
35253535
off = childoff;
35263536
}
@@ -3585,8 +3595,14 @@ int hal_dts_fixup(void* dts_addr)
35853595
0x10, 0x00, 0x00, 0x00, 0x00, 0x80000000
35863596
};
35873597
uint32_t bus_range[2], base;
3588-
bus_range[0] = 0;
3589-
bus_range[1] = i-1;
3598+
unsigned int c;
3599+
3600+
/* Cells are big-endian on the wire; a no-op on PowerPC. */
3601+
for (c = 0; c < sizeof(dma_ranges)/sizeof(dma_ranges[0]); c++) {
3602+
dma_ranges[c] = cpu_to_fdt32(dma_ranges[c]);
3603+
}
3604+
bus_range[0] = cpu_to_fdt32(0);
3605+
bus_range[1] = cpu_to_fdt32((uint32_t)(i-1));
35903606

35913607
/* find offset for pci controlller base register */
35923608
off = fdt_node_offset_by_compatible(fdt, -1, "fsl,qoriq-pcie");
@@ -3632,6 +3648,7 @@ int hal_dts_fixup(void* dts_addr)
36323648

36333649
#endif /* !BUILD_LOADER_STAGE1 */
36343650
(void)dts_addr;
3651+
(void)capacity;
36353652
return 0;
36363653
}
36373654
#endif /* MMU */

0 commit comments

Comments
 (0)