diff --git a/.github/workflows/test-parse-tools.yml b/.github/workflows/test-parse-tools.yml index 01c4977d5c..e2bdb93601 100644 --- a/.github/workflows/test-parse-tools.yml +++ b/.github/workflows/test-parse-tools.yml @@ -46,3 +46,12 @@ jobs: - name: Run fdt-parser test (nxp_t1024.dtb) run: | ./tools/fdt-parser/fdt-parser ./tools/fdt-parser/nxp_t1024.dtb -t + + - name: Run fdt-parser malformed-input corpus under ASan/UBSan + run: | + gcc -o /tmp/fdt-parser-asan -Wall -Werror -g -O1 \ + -fsanitize=address,undefined -fno-omit-frame-pointer \ + -I include -DWOLFBOOT_FDT -DWOLFBOOT_FDT_CORPUS -DPRINTF_ENABLED \ + tools/fdt-parser/fdt-parser.c src/fdt.c + ASAN_OPTIONS=detect_leaks=0 \ + /tmp/fdt-parser-asan ./tools/fdt-parser/nxp_t1024.dtb -f diff --git a/docs/Targets.md b/docs/Targets.md index 8f28417fa2..9e8db5d436 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -5007,7 +5007,7 @@ Image kernel-1: 0x200000 (24617472 bytes) Loading Image fdt-1: 0x1177A3DC -> 0x1000 (39384 bytes) Image fdt-1: 0x1000 (39384 bytes) Loading DTS: 0x1000 -> 0x1000 (39384 bytes) -FDT: Version 17, Size 39384 +FDT: Size 39384 FDT: Setting bootargs: earlycon root=/dev/mmcblk0p2 rootwait FDT: Set chosen (28076), bootargs=earlycon root=/dev/mmcblk0p2 rootwait Booting at 0x200000 diff --git a/hal/cm4.c b/hal/cm4.c index 0bc85a1439..fddcc3560b 100644 --- a/hal/cm4.c +++ b/hal/cm4.c @@ -152,9 +152,9 @@ void* hal_get_dts_update_address(void) * clock, serial no.) to the kernel it loads - which is wolfBoot. */ extern void* cm4_fw_dtb; -/* Upper bound for the firmware DTB copy. fdt_check_header() does not validate - * totalsize, so cap it: a valid DTB is well under the arm64 boot-protocol 2MB - * limit, and this keeps a corrupt header from overrunning the DTS landing zone. +/* Size of the DTS landing zone, and so the capacity handed to fdt_open() + * for the firmware DTB: a valid DTB is well under the arm64 boot-protocol + * 2MB limit, and this keeps a corrupt header from overrunning the zone. * Override with -DCM4_FDT_MAX_SIZE=. */ #ifndef CM4_FDT_MAX_SIZE #define CM4_FDT_MAX_SIZE 0x200000 @@ -358,24 +358,25 @@ void* hal_get_boot_dts(void) * validated NULL-DTB handoff is unchanged. */ return NULL; #else + fdt_ctx ctx; void *fdt = cm4_fw_dtb; uint32_t sz; int off; - if (fdt == NULL || fdt_check_header(fdt) != 0) { - wolfBoot_printf("cm4: no valid firmware DTB (%p)\n", fdt); + if (fdt == NULL) { + wolfBoot_printf("cm4: no firmware DTB\n"); return NULL; } - sz = (uint32_t)fdt_totalsize(fdt); - /* fdt_check_header() does not validate totalsize; bound it so a corrupt DTB - * header cannot make the copy/fixup clobber memory past the DTS landing - * zone (leaving room for the fixup headroom too). */ - if (sz < sizeof(struct fdt_header) || - sz > CM4_FDT_MAX_SIZE - WOLFBOOT_FDT_FIXUP_HEADROOM) { - wolfBoot_printf("cm4: firmware DTB size %u out of range\n", - (unsigned)sz); + /* Bound the parse by the largest DTB this path can ever accept: the + * DTS landing zone less the fixup headroom. A corrupt header can then + * neither be walked out of bounds here nor make the copy below clobber + * memory past that zone. */ + if (fdt_open(&ctx, fdt, + CM4_FDT_MAX_SIZE - WOLFBOOT_FDT_FIXUP_HEADROOM) != 0) { + wolfBoot_printf("cm4: no valid firmware DTB (%p)\n", fdt); return NULL; } + sz = fdt_size(&ctx); /* SECURITY: the firmware DTB lives on the unsigned FAT partition (unless * the RPi EEPROM secure-boot is enabled), so it is NOT covered by wolfBoot's * signature. Only /chosen/bootargs is overwritten below; /memory, @@ -405,12 +406,16 @@ void* hal_get_boot_dts(void) fdt = (void*)WOLFBOOT_LOAD_DTS_ADDRESS; /* Zero the appended headroom so the grown blob holds no uninitialized - * bytes, then record the new total size. */ + * bytes, then re-open on the landing zone and record the new size. */ memset((uint8_t*)fdt + sz, 0, WOLFBOOT_FDT_FIXUP_HEADROOM); - fdt_set_totalsize(fdt, sz + WOLFBOOT_FDT_FIXUP_HEADROOM); - off = fdt_find_node_offset(fdt, -1, "chosen"); + if (fdt_open(&ctx, fdt, CM4_FDT_MAX_SIZE) != 0 || + fdt_grow(&ctx, WOLFBOOT_FDT_FIXUP_HEADROOM) != 0) { + wolfBoot_printf("cm4: relocated DTB rejected; refusing firmware DTB\n"); + return NULL; + } + off = fdt_subnode_offset(&ctx, 0, "chosen"); if (off == -FDT_ERR_NOTFOUND) { - off = fdt_add_subnode(fdt, 0, "chosen"); + off = fdt_add_subnode(&ctx, 0, "chosen"); } if (off < 0) { /* Fail closed: without /chosen we cannot inject our known-good bootargs, @@ -426,13 +431,13 @@ void* hal_get_boot_dts(void) const char *args = LINUX_BOOTARGS; if (cm4_rauc_build_bootargs(cm4_bootargs, sizeof(cm4_bootargs)) == 0) args = cm4_bootargs; - if (fdt_fixup_str(fdt, off, "chosen", "bootargs", args) != 0) { + if (fdt_fixup_str(&ctx, off, "chosen", "bootargs", args) != 0) { wolfBoot_printf("cm4: DTB bootargs fixup failed; refusing firmware DTB\n"); return NULL; } } #else - if (fdt_fixup_str(fdt, off, "chosen", "bootargs", LINUX_BOOTARGS) != 0) { + if (fdt_fixup_str(&ctx, off, "chosen", "bootargs", LINUX_BOOTARGS) != 0) { wolfBoot_printf("cm4: DTB bootargs fixup failed; refusing firmware DTB\n"); return NULL; } diff --git a/hal/mpfs250.c b/hal/mpfs250.c index 86eac2155d..8f0fd3225d 100644 --- a/hal/mpfs250.c +++ b/hal/mpfs250.c @@ -601,10 +601,10 @@ int mpfs_read_serial_number(uint8_t *serial) #define MICROCHIP_OUI_1 0x04 #define MICROCHIP_OUI_2 0xA3 -static int mpfs_dts_fixup_inplace(void* dts_addr) +static int mpfs_dts_fixup_inplace(void* dts_addr, uint32_t capacity) { + fdt_ctx ctx; int off, ret; - struct fdt_header *fdt = (struct fdt_header *)dts_addr; uint8_t device_serial_number[DEVICE_SERIAL_NUMBER_SIZE]; uint8_t mac_addr[6]; #if defined(MPFS_DDR_INIT) && defined(WOLFBOOT_MMODE_SMODE_BOOT) @@ -620,31 +620,33 @@ static int mpfs_dts_fixup_inplace(void* dts_addr) unsigned int i; #endif - /* Verify FDT header */ - ret = fdt_check_header(dts_addr); + /* Validate the blob against the window it actually occupies. */ + ret = fdt_open(&ctx, dts_addr, capacity); if (ret != 0) { wolfBoot_printf("FDT: Invalid header! %d\n", ret); return ret; } - wolfBoot_printf("FDT: Version %d, Size %d\n", - fdt_version(fdt), fdt_totalsize(fdt)); + wolfBoot_printf("FDT: Size %d\n", (int)fdt_size(&ctx)); - /* Expand total size to allow adding/modifying properties. + /* Reserve free space to allow adding/modifying properties. * Sizing comes from WOLFBOOT_FDT_FIXUP_HEADROOM in include/fdt.h. */ - fdt_set_totalsize(fdt, - fdt_totalsize(fdt) + WOLFBOOT_FDT_FIXUP_HEADROOM); + ret = fdt_grow(&ctx, WOLFBOOT_FDT_FIXUP_HEADROOM); + if (ret != 0) { + wolfBoot_printf("FDT: No headroom for fixups (%d)\n", ret); + return ret; + } /* Find /chosen node */ - off = fdt_find_node_offset(fdt, -1, "chosen"); + off = fdt_subnode_offset(&ctx, 0, "chosen"); if (off < 0) { /* Create /chosen node if it doesn't exist */ - off = fdt_add_subnode(fdt, 0, "chosen"); + off = fdt_add_subnode(&ctx, 0, "chosen"); } if (off >= 0) { /* Set bootargs property */ - fdt_fixup_str(fdt, off, "chosen", "bootargs", LINUX_BOOTARGS); + fdt_fixup_str(&ctx, off, "chosen", "bootargs", LINUX_BOOTARGS); } #if defined(MPFS_DDR_INIT) && defined(WOLFBOOT_MMODE_SMODE_BOOT) @@ -665,9 +667,9 @@ static int mpfs_dts_fixup_inplace(void* dts_addr) * parked harts on the kernel's request (SMP). cpu@0 (E51) is already * disabled in the Yocto DTB; cpu@1 stays enabled so Linux boots on it. */ for (i = 0; i < sizeof(cpu_off) / sizeof(cpu_off[0]); i++) { - off = fdt_find_node_offset(fdt, -1, cpu_off[i]); + off = fdt_find_node_offset(&ctx, -1, cpu_off[i]); if (off >= 0) { - ret = fdt_fixup_str(fdt, off, cpu_off[i], "status", + ret = fdt_fixup_str(&ctx, off, cpu_off[i], "status", "disabled"); if (ret != 0) { wolfBoot_printf("FDT: Failed to disable %s (%d)\n", @@ -713,9 +715,9 @@ static int mpfs_dts_fixup_inplace(void* dts_addr) mac_addr[3], mac_addr[4], mac_addr[5]); /* Set local-mac-address for ethernet@20110000 (mac0) */ - off = fdt_find_node_offset(fdt, -1, "ethernet@20110000"); + off = fdt_find_node_offset(&ctx, -1, "ethernet@20110000"); if (off >= 0) { - ret = fdt_setprop(fdt, off, "local-mac-address", mac_addr, 6); + ret = fdt_setprop(&ctx, off, "local-mac-address", mac_addr, 6); if (ret != 0) { wolfBoot_printf("FDT: Failed to set mac0 address (%d)\n", ret); } @@ -732,9 +734,9 @@ static int mpfs_dts_fixup_inplace(void* dts_addr) mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); - off = fdt_find_node_offset(fdt, -1, "ethernet@20112000"); + off = fdt_find_node_offset(&ctx, -1, "ethernet@20112000"); if (off >= 0) { - ret = fdt_setprop(fdt, off, "local-mac-address", mac_addr, 6); + ret = fdt_setprop(&ctx, off, "local-mac-address", mac_addr, 6); if (ret != 0) { wolfBoot_printf("FDT: Failed to set mac1 address (%d)\n", ret); } @@ -824,9 +826,10 @@ int wolfBoot_fit_memcpy(void *dst, const void *src, uint32_t len) * (WOLFBOOT_LOAD_DTS_ADDRESS) but CPU writes to DDR do not land here, so copy * it (non-cached read) into an L2 scratch buffer, run the FDT fixups there * (CPU L2 writes work), then PDMA the result back to DDR. */ -int hal_dts_fixup(void* dts_addr) +int hal_dts_fixup(void* dts_addr, uint32_t capacity) { static uint8_t l2_dtb[64 * 1024] __attribute__((aligned(8))); + fdt_ctx ctx; const uint8_t *ddr_nc; uint32_t sz; int ret; @@ -835,29 +838,29 @@ int hal_dts_fixup(void* dts_addr) return -1; } ddr_nc = (const uint8_t *)((uintptr_t)dts_addr | 0x40000000UL); - if (fdt_check_header((void *)ddr_nc) != 0) { + /* The source is bounded by whichever is smaller: the caller's DDR + * window, or what the L2 scratch buffer can hold once the fixup + * headroom is set aside. fdt_open() enforces it, so the memcpy below + * cannot overrun l2_dtb however corrupt the header is. */ + sz = (uint32_t)(sizeof(l2_dtb) - WOLFBOOT_FDT_FIXUP_HEADROOM); + if (capacity < sz) { + sz = capacity; + } + if (fdt_open(&ctx, (void *)ddr_nc, sz) != 0) { wolfBoot_printf("FDT: invalid header at %p\n", dts_addr); return -1; } - sz = (uint32_t)fdt_totalsize((void *)ddr_nc); - /* Overflow-safe bound: compare without adding. A near-UINT32_MAX - * totalsize (fdt_check_header does not bound it) would make - * sz + WOLFBOOT_FDT_FIXUP_HEADROOM wrap to a small value that passes the - * check, after which memcpy(l2_dtb, ., sz) overruns the 64 KB buffer. - * sizeof(l2_dtb) (64 KB) is always greater than the headroom. */ - if (sz > sizeof(l2_dtb) - WOLFBOOT_FDT_FIXUP_HEADROOM) { - wolfBoot_printf("FDT: dtb too large for L2 fixup (%u > %u)\n", - (unsigned)sz, - (unsigned)(sizeof(l2_dtb) - WOLFBOOT_FDT_FIXUP_HEADROOM)); - return -1; - } + sz = fdt_size(&ctx); /* DDR (non-cached) -> L2 */ memcpy(l2_dtb, ddr_nc, sz); - /* fixup in the CPU-writable L2 buffer */ - ret = mpfs_dts_fixup_inplace(l2_dtb); + /* fixup in the CPU-writable L2 buffer, which may use the whole of it */ + ret = mpfs_dts_fixup_inplace(l2_dtb, (uint32_t)sizeof(l2_dtb)); /* L2 -> DDR via PDMA (expanded totalsize) */ - if (wolfBoot_fit_memcpy(dts_addr, l2_dtb, - (uint32_t)fdt_totalsize(l2_dtb)) != 0) { + if (fdt_open(&ctx, l2_dtb, (uint32_t)sizeof(l2_dtb)) != 0) { + wolfBoot_printf("FDT: fixed-up dtb rejected\n"); + return -1; + } + if (wolfBoot_fit_memcpy(dts_addr, l2_dtb, fdt_size(&ctx)) != 0) { wolfBoot_printf("FDT: dtb copy-back to DDR failed\n"); return -1; } @@ -868,12 +871,12 @@ int hal_dts_fixup(void* dts_addr) * run the fixups directly in place (the original behavior, kept so * FDT-enabled non-DDR builds do not silently fall back to the weak * no-op hal_dts_fixup). */ -int hal_dts_fixup(void* dts_addr) +int hal_dts_fixup(void* dts_addr, uint32_t capacity) { if (dts_addr == NULL) { return -1; } - return mpfs_dts_fixup_inplace(dts_addr); + return mpfs_dts_fixup_inplace(dts_addr, capacity); } #endif /* WOLFBOOT_RISCV_MMODE && MPFS_DDR_INIT */ diff --git a/hal/nxp_t10xx.c b/hal/nxp_t10xx.c index 9fcd2f7ecb..4732ae3a3f 100644 --- a/hal/nxp_t10xx.c +++ b/hal/nxp_t10xx.c @@ -3394,29 +3394,33 @@ void* hal_get_dts_address(void) return (void*)WOLFBOOT_DTS_BOOT_ADDRESS; } -int hal_dts_fixup(void* dts_addr) +int hal_dts_fixup(void* dts_addr, uint32_t capacity) { #ifndef BUILD_LOADER_STAGE1 - struct fdt_header *fdt = (struct fdt_header *)dts_addr; + fdt_ctx ctx; + fdt_ctx* fdt = &ctx; int off, i; uint32_t cell; uint32_t *reg; const char* prev_compat; - /* verify the FTD is valid */ - off = fdt_check_header(dts_addr); + /* Validate the blob against the window it actually occupies. */ + off = fdt_open(&ctx, dts_addr, capacity); if (off != 0) { wolfBoot_printf("FDT: Invalid header! %d\n", off); return off; } /* display FTD information */ - wolfBoot_printf("FDT: Version %d, Size %d\n", - fdt_version(fdt), fdt_totalsize(fdt)); + wolfBoot_printf("FDT: Size %d\n", (int)fdt_size(fdt)); - /* expand total size */ - fdt->totalsize += 2048; /* expand by 2KB */ - wolfBoot_printf("FDT: Expanded (2KB) to %d bytes\n", fdt->totalsize); + /* Reserve headroom for the fixups below. */ + off = fdt_grow(fdt, 2048U); + if (off != 0) { + wolfBoot_printf("FDT: No headroom for fixups (%d)\n", off); + return off; + } + wolfBoot_printf("FDT: Expanded (2KB) to %d bytes\n", (int)fdt_size(fdt)); /* fixup the memory region - single bank */ off = fdt_find_devtype(fdt, -1, "memory"); @@ -3513,6 +3517,11 @@ int hal_dts_fixup(void* dts_addr) wolfBoot_printf("FDT: Set %s@%d (%d), %s=%d,%d\n", "qman-portal", i, off, "fsl,liodn", liodns[0], liodns[1]); + /* Property cells are big-endian on the wire. Identity on the + * PowerPC targets that run this, but without it the host tests + * write host-order bytes that hardware never produces. */ + liodns[0] = cpu_to_fdt32(liodns[0]); + liodns[1] = cpu_to_fdt32(liodns[1]); fdt_setprop(fdt, off, "fsl,liodn", liodns, sizeof(liodns)); /* Add fman@0 node and fsl,liodon = FMAN_DMA_LIODN + index */ @@ -3521,6 +3530,7 @@ int hal_dts_fixup(void* dts_addr) liodns[0] = FMAN_DMA_LIODN + i + 1; wolfBoot_printf("FDT: Set %s@%d/%s (%d), %s=%d\n", "qman-portal", i, "fman@0", childoff, "fsl,liodn", liodns[0]); + liodns[0] = cpu_to_fdt32(liodns[0]); fdt_setprop(fdt, childoff, "fsl,liodn", liodns, sizeof(liodns[0])); off = childoff; } @@ -3585,8 +3595,14 @@ int hal_dts_fixup(void* dts_addr) 0x10, 0x00, 0x00, 0x00, 0x00, 0x80000000 }; uint32_t bus_range[2], base; - bus_range[0] = 0; - bus_range[1] = i-1; + unsigned int c; + + /* Cells are big-endian on the wire; a no-op on PowerPC. */ + for (c = 0; c < sizeof(dma_ranges)/sizeof(dma_ranges[0]); c++) { + dma_ranges[c] = cpu_to_fdt32(dma_ranges[c]); + } + bus_range[0] = cpu_to_fdt32(0); + bus_range[1] = cpu_to_fdt32((uint32_t)(i-1)); /* find offset for pci controlller base register */ off = fdt_node_offset_by_compatible(fdt, -1, "fsl,qoriq-pcie"); @@ -3632,6 +3648,7 @@ int hal_dts_fixup(void* dts_addr) #endif /* !BUILD_LOADER_STAGE1 */ (void)dts_addr; + (void)capacity; return 0; } #endif /* MMU */ diff --git a/hal/nxp_t2080.c b/hal/nxp_t2080.c index 66a522449f..d93716dd53 100644 --- a/hal/nxp_t2080.c +++ b/hal/nxp_t2080.c @@ -1725,31 +1725,33 @@ void* hal_get_dts_address(void) return (void*)WOLFBOOT_DTS_BOOT_ADDRESS; } -int hal_dts_fixup(void* dts_addr) +int hal_dts_fixup(void* dts_addr, uint32_t capacity) { #ifndef BUILD_LOADER_STAGE1 - struct fdt_header *fdt = (struct fdt_header *)dts_addr; + fdt_ctx ctx; + fdt_ctx* fdt = &ctx; int off; uint32_t *reg; - /* verify the FDT is valid */ - off = fdt_check_header(dts_addr); + /* Validate the blob against the window it actually occupies. */ + off = fdt_open(&ctx, dts_addr, capacity); if (off != 0) { wolfBoot_printf("FDT: Invalid header! %d\n", off); return off; } /* display FDT information */ - wolfBoot_printf("FDT: Version %d, Size %d\n", - fdt_version(fdt), fdt_totalsize(fdt)); + wolfBoot_printf("FDT: Size %d\n", (int)fdt_size(fdt)); - /* expand total size */ - { - uint32_t new_size = (uint32_t)fdt_totalsize(fdt) + 2048U; - fdt_set_totalsize(fdt, new_size); - wolfBoot_printf("FDT: Expanded (2KB) to %d bytes\n", - fdt_totalsize(fdt)); + /* Reserve headroom for the fixups below. The /memreserve/ inserts in + * particular shift the whole tree down, so this must succeed before + * any of them run. */ + off = fdt_grow(fdt, 2048U); + if (off != 0) { + wolfBoot_printf("FDT: No headroom for fixups (%d)\n", off); + return off; } + wolfBoot_printf("FDT: Expanded (2KB) to %d bytes\n", (int)fdt_size(fdt)); #ifdef ENABLE_OS64BIT /* /memreserve/ entries: keep VxWorks/Linux away from the spin-table @@ -1931,7 +1933,7 @@ int hal_dts_fixup(void* dts_addr) * the DTB value untouched during bootm; we override here so users * can change boot parameters without reflashing the DTB. */ #ifdef WOLFBOOT_BOOTARGS - off = fdt_find_node_offset(fdt, -1, "chosen"); + off = fdt_subnode_offset(fdt, 0, "chosen"); if (off < 0) { off = fdt_add_subnode(fdt, 0, "chosen"); } @@ -1942,6 +1944,7 @@ int hal_dts_fixup(void* dts_addr) #endif /* !BUILD_LOADER_STAGE1 */ (void)dts_addr; + (void)capacity; return 0; } #endif /* MMU */ diff --git a/hal/versal.c b/hal/versal.c index b274a8d71d..af82dbc050 100644 --- a/hal/versal.c +++ b/hal/versal.c @@ -1271,40 +1271,44 @@ void* hal_get_dts_update_address(void) * Called from do_boot() before jumping to the kernel. * * @param dts_addr: Pointer to the device tree blob in memory + * @param capacity: Bytes readable/writable at dts_addr; bounds every + * in-place fixup made here * @return: 0 on success, negative error code on failure */ -int hal_dts_fixup(void* dts_addr) +int hal_dts_fixup(void* dts_addr, uint32_t capacity) { + fdt_ctx ctx; int off, ret; - struct fdt_header *fdt = (struct fdt_header *)dts_addr; - /* Verify FDT header */ - ret = fdt_check_header(dts_addr); + /* Validate the blob against the window it actually occupies. */ + ret = fdt_open(&ctx, dts_addr, capacity); if (ret != 0) { wolfBoot_printf("FDT: Invalid header! %d\n", ret); return ret; } - wolfBoot_printf("FDT: Version %d, Size %d\n", - fdt_version(fdt), fdt_totalsize(fdt)); + wolfBoot_printf("FDT: Size %d\n", (int)fdt_size(&ctx)); - /* Expand total size to allow adding/modifying properties (bootargs and, - * when WOLFBOOT_FIT_RAMDISK is in play, linux,initrd-{start,end}). + /* Reserve free space to allow adding/modifying properties (bootargs + * and, when WOLFBOOT_FIT_RAMDISK is in play, linux,initrd-{start,end}). * Sizing comes from WOLFBOOT_FDT_FIXUP_HEADROOM in include/fdt.h. */ - fdt_set_totalsize(fdt, - fdt_totalsize(fdt) + WOLFBOOT_FDT_FIXUP_HEADROOM); + ret = fdt_grow(&ctx, WOLFBOOT_FDT_FIXUP_HEADROOM); + if (ret != 0) { + wolfBoot_printf("FDT: No headroom for fixups (%d)\n", ret); + return ret; + } /* Find /chosen node; create it only if genuinely missing. Any other * negative return (malformed FDT, etc.) is surfaced directly rather * than masked by a follow-on fdt_add_subnode() failure. */ - off = fdt_find_node_offset(fdt, -1, "chosen"); + off = fdt_subnode_offset(&ctx, 0, "chosen"); if (off == -FDT_ERR_NOTFOUND) { - off = fdt_add_subnode(fdt, 0, "chosen"); + off = fdt_add_subnode(&ctx, 0, "chosen"); } if (off >= 0) { /* Set bootargs property */ - fdt_fixup_str(fdt, off, "chosen", "bootargs", LINUX_BOOTARGS); + fdt_fixup_str(&ctx, off, "chosen", "bootargs", LINUX_BOOTARGS); } else { wolfBoot_printf("FDT: Failed to find/create chosen node (%d)\n", off); return off; diff --git a/hal/zynq.c b/hal/zynq.c index 09c2331416..06c38cfde9 100644 --- a/hal/zynq.c +++ b/hal/zynq.c @@ -2629,37 +2629,40 @@ void* hal_get_dts_address(void) #endif } -int hal_dts_fixup(void* dts_addr) +int hal_dts_fixup(void* dts_addr, uint32_t capacity) { + fdt_ctx ctx; int off, ret; - struct fdt_header *fdt = (struct fdt_header *)dts_addr; - /* Verify FDT header */ - ret = fdt_check_header(dts_addr); + /* Validate the blob against the window it actually occupies. Every + * bound the parser applies below comes from `capacity`, not from the + * size the blob claims for itself. */ + ret = fdt_open(&ctx, dts_addr, capacity); if (ret != 0) { wolfBoot_printf("FDT: Invalid header! %d\n", ret); return ret; } - wolfBoot_printf("FDT: Version %d, Size %d\n", - fdt_version(fdt), fdt_totalsize(fdt)); + wolfBoot_printf("FDT: Size %d\n", (int)fdt_size(&ctx)); - /* Expand totalsize so fdt_setprop() has in-blob free space to place - * a new/larger bootargs property and (when WOLFBOOT_FIT_RAMDISK is in - * play) the linux,initrd-{start,end} properties. Physical headroom is - * already guaranteed by the load-address layout (DTB at - * WOLFBOOT_LOAD_DTS_ADDRESS, kernel loaded much higher), so growing - * the header is safe. Sizing comes from WOLFBOOT_FDT_FIXUP_HEADROOM - * in include/fdt.h - same constant as hal/versal.c. */ - fdt_set_totalsize(fdt, - fdt_totalsize(fdt) + WOLFBOOT_FDT_FIXUP_HEADROOM); + /* Reserve in-blob free space so fdt_setprop() can place a new/larger + * bootargs property and (when WOLFBOOT_FIT_RAMDISK is in play) the + * linux,initrd-{start,end} properties. Unlike the old blind header + * bump, this fails closed if the window cannot hold it. Sizing comes + * from WOLFBOOT_FDT_FIXUP_HEADROOM in include/fdt.h - same constant + * as hal/versal.c. */ + ret = fdt_grow(&ctx, WOLFBOOT_FDT_FIXUP_HEADROOM); + if (ret != 0) { + wolfBoot_printf("FDT: No headroom for fixups (%d)\n", ret); + return ret; + } /* Find /chosen node; create it only if genuinely missing. Any other * negative return (malformed FDT, etc.) is surfaced directly rather * than masked by a follow-on fdt_add_subnode() failure. */ - off = fdt_find_node_offset(fdt, -1, "chosen"); + off = fdt_subnode_offset(&ctx, 0, "chosen"); if (off == -FDT_ERR_NOTFOUND) { - off = fdt_add_subnode(fdt, 0, "chosen"); + off = fdt_add_subnode(&ctx, 0, "chosen"); } if (off < 0) { wolfBoot_printf("FDT: Failed to find/create chosen node (%d)\n", off); @@ -2668,7 +2671,7 @@ int hal_dts_fixup(void* dts_addr) /* Set bootargs property - overrides PetaLinux default root= with * the wolfBoot partition layout. */ - ret = fdt_fixup_str(fdt, off, "chosen", "bootargs", LINUX_BOOTARGS); + ret = fdt_fixup_str(&ctx, off, "chosen", "bootargs", LINUX_BOOTARGS); if (ret < 0) { wolfBoot_printf("FDT: Failed to set bootargs (%d)\n", ret); return ret; diff --git a/include/fdt.h b/include/fdt.h index 2f60d3965b..625be6d0ce 100644 --- a/include/fdt.h +++ b/include/fdt.h @@ -1,6 +1,10 @@ /* fdt.h * - * Functions to help with flattened device tree (DTB) parsing + * Flattened device tree (DTB) parser. + * + * Written from the Devicetree Specification v0.4, section 5 ("Flattened + * Devicetree (DTB) Format"). The header field names below are the ones + * the specification itself defines in section 5.2. * * * Copyright (C) 2026 wolfSSL Inc. @@ -31,8 +35,16 @@ extern "C" { #include -#define FDT_MAGIC 0xD00DFEEDUL -#define FDT_SW_MAGIC (uint32_t)(~FDT_MAGIC) /* marker for run-time creation/edit of FDT */ +/* ------------------------------------------------------------------ */ +/* On-disk format (Devicetree Specification v0.4, section 5) */ +/* ------------------------------------------------------------------ */ + +#define FDT_MAGIC 0xD00DFEEDUL + +/* Only v17 blobs are accepted. v16 and earlier placed property values at + * a different alignment; dtc has emitted v17 for many years and wolfBoot + * has never had a target that needed the older layout. */ +#define FDT_SUPPORTED_VERSION 17 struct fdt_header { uint32_t magic; @@ -47,99 +59,98 @@ struct fdt_header { uint32_t size_dt_struct; }; +#define FDT_HEADER_SIZE 40 /* sizeof(struct fdt_header), spec section 5.2 */ + +/* Byte offset of each header field, in spec order. Used to read/write + * the header without depending on struct padding. */ +#define FDT_H_MAGIC 0U +#define FDT_H_TOTALSIZE 4U +#define FDT_H_OFF_STRUCT 8U +#define FDT_H_OFF_STRINGS 12U +#define FDT_H_OFF_RSVMAP 16U +#define FDT_H_VERSION 20U +#define FDT_H_LAST_COMP 24U +#define FDT_H_BOOT_CPUID 28U +#define FDT_H_SIZE_STRINGS 32U +#define FDT_H_SIZE_STRUCT 36U + struct fdt_reserve_entry { uint64_t address; uint64_t size; }; -struct fdt_prop { - uint32_t len; - uint32_t nameoff; -}; - -struct fdt_node_header { - uint32_t tag; - char name[0]; -}; - -struct fdt_property { - uint32_t tag; - uint32_t len; - uint32_t nameoff; - char data[0]; -}; - -#define FDT_TAGSIZE sizeof(uint32_t) -#define FDT_ALIGN(x, a) (((x) + (a) - 1) & ~((a) - 1)) -#define FDT_TAGALIGN(x) (FDT_ALIGN((x), FDT_TAGSIZE)) - -/* Bounds for the attacker-influenced fdt_totalsize before relocating or - * forwarding a DTB. MIN is the FDT v17 header size (also enforced by the - * signer): fdt_check_header validates magic/version but not totalsize, so a - * crafted header with a tiny totalsize must be rejected rather than - * loaded/forwarded as a partial tree. The MAX default assumes a staging - * window at WOLFBOOT_LOAD_DTS_ADDRESS of at least 1 MiB; targets with a - * smaller window must override WOLFBOOT_DTS_MAX_SIZE (see hal/nxp_ppc.h). */ -#ifndef WOLFBOOT_DTS_MAX_SIZE -#define WOLFBOOT_DTS_MAX_SIZE (1024U * 1024U) -#endif -#define WOLFBOOT_DTS_MIN_SIZE (40U) - -#define FDT_FIRST_SUPPORTED_VERSION 0x10 -#define FDT_LAST_SUPPORTED_VERSION 0x11 +#define FDT_RSV_ENTRY_SIZE 16 /* sizeof(struct fdt_reserve_entry) */ +/* Structure block tokens, spec section 5.4.1 */ #define FDT_BEGIN_NODE 0x00000001UL #define FDT_END_NODE 0x00000002UL #define FDT_PROP 0x00000003UL #define FDT_NOP 0x00000004UL #define FDT_END 0x00000009UL -#define FDT_ERR_BADMAGIC 1 -#define FDT_ERR_BADVERSION 2 -#define FDT_ERR_BADSTRUCTURE 3 -#define FDT_ERR_BADOFFSET 4 -#define FDT_ERR_BADSTATE 5 -#define FDT_ERR_NOTFOUND 6 -#define FDT_ERR_NOSPACE 7 -#define FDT_ERR_TRUNCATED 8 -#define FDT_ERR_INTERNAL 9 -#define FDT_ERR_EXISTS 10 - -#define FDT_PCI_PREFETCH (0x40000000) -#define FDT_PCI_MEM32 (0x02000000) -#define FDT_PCI_IO (0x01000000) -#define FDT_PCI_MEM64 (0x03000000) +#define FDT_TAGSIZE ((uint32_t)sizeof(uint32_t)) +#define FDT_ALIGN(x, a) (((x) + (a) - 1) & ~((a) - 1)) +#define FDT_TAGALIGN(x) (FDT_ALIGN((x), FDT_TAGSIZE)) + +/* Deepest node nesting accepted. Real trees are well under 10 levels; + * the cap keeps a hostile blob from driving an unbounded walk in a + * consumer that tracks depth. */ +#ifndef FDT_MAX_DEPTH +#define FDT_MAX_DEPTH 32 +#endif -uint32_t cpu_to_fdt32(uint32_t x); -uint64_t cpu_to_fdt64(uint64_t x); -uint32_t fdt32_to_cpu(uint32_t x); -uint64_t fdt64_to_cpu(uint64_t x); +/* ------------------------------------------------------------------ */ +/* Errors */ +/* ------------------------------------------------------------------ */ -#define fdt_get_header(fdt, field) (fdt32_to_cpu(((const struct fdt_header *)(fdt))->field)) -#define fdt_magic(fdt) (fdt_get_header(fdt, magic)) -#define fdt_totalsize(fdt) (fdt_get_header(fdt, totalsize)) -#define fdt_off_dt_struct(fdt) (fdt_get_header(fdt, off_dt_struct)) -#define fdt_off_dt_strings(fdt) (fdt_get_header(fdt, off_dt_strings)) -#define fdt_off_mem_rsvmap(fdt) (fdt_get_header(fdt, off_mem_rsvmap)) -#define fdt_version(fdt) (fdt_get_header(fdt, version)) -#define fdt_last_comp_version(fdt) (fdt_get_header(fdt, last_comp_version)) -#define fdt_boot_cpuid_phys(fdt) (fdt_get_header(fdt, boot_cpuid_phys)) -#define fdt_size_dt_strings(fdt) (fdt_get_header(fdt, size_dt_strings)) -#define fdt_size_dt_struct(fdt) (fdt_get_header(fdt, size_dt_struct)) - -#define fdt_set_header(fdt, field, val) (((struct fdt_header *)fdt)->field = cpu_to_fdt32(val)) -#define fdt_set_magic(fdt, val) (fdt_set_header(fdt, magic, (val))) -#define fdt_set_totalsize(fdt, val) (fdt_set_header(fdt, totalsize, (val))) -#define fdt_set_off_dt_struct(fdt, val) (fdt_set_header(fdt, off_dt_struct, (val))) -#define fdt_set_off_dt_strings(fdt, val) (fdt_set_header(fdt, off_dt_strings, (val))) -#define fdt_set_off_mem_rsvmap(fdt, val) (fdt_set_header(fdt, off_mem_rsvmap, (val))) -#define fdt_set_version(fdt, val) (fdt_set_header(fdt, version, (val))) -#define fdt_set_last_comp_version(fdt, val) (fdt_set_header(fdt, last_comp_version, (val))) -#define fdt_set_boot_cpuid_phys(fdt, val) (fdt_set_header(fdt, boot_cpuid_phys, (val))) -#define fdt_set_size_dt_strings(fdt, val) (fdt_set_header(fdt, size_dt_strings, (val))) -#define fdt_set_size_dt_struct(fdt, val) (fdt_set_header(fdt, size_dt_struct, (val))) - -/* Headroom (bytes) appended to fdt_totalsize() before wolfBoot inserts +/* wolfBoot's own error set and ordering - most frequently tested first. + * + * The int-returning functions return 0 or a positive offset on success, + * and the NEGATED value of one of these on failure. The pointer-returning + * ones return NULL on failure, reporting the reason through their length + * output where they have one (fdt_get_name, fdt_get_string, fdt_getprop, + * fdt_getprop_by_offset). fdt_size() reports a size, not a status: a + * context that is not open reads as 0. */ +#define FDT_ERR_NOTFOUND 1 /* no such node, property or string */ +#define FDT_ERR_EXISTS 2 /* node/property already present */ +#define FDT_ERR_NOSPACE 3 /* would not fit in the caller's buffer */ +#define FDT_ERR_BADOFFSET 4 /* offset does not name the expected tag */ +#define FDT_ERR_BADARG 5 /* NULL or otherwise unusable argument */ +#define FDT_ERR_BADMAGIC 6 /* header magic is not FDT_MAGIC */ +#define FDT_ERR_BADVERSION 7 /* not a v17 blob */ +#define FDT_ERR_BADLAYOUT 8 /* header block offsets/sizes inconsistent */ +#define FDT_ERR_BADSTRUCTURE 9 /* structure block is malformed */ +#define FDT_ERR_INTERNAL 10 /* should not happen; a bug if it does */ + +/* ------------------------------------------------------------------ */ +/* PCI address cell flags (used by the NXP QorIQ dts fixups) */ +/* ------------------------------------------------------------------ */ + +#define FDT_PCI_PREFETCH (0x40000000) +#define FDT_PCI_MEM32 (0x02000000) +#define FDT_PCI_IO (0x01000000) +#define FDT_PCI_MEM64 (0x03000000) + +/* ------------------------------------------------------------------ */ +/* Buffer sizing */ +/* ------------------------------------------------------------------ */ + +/* Size of the DTB staging window at WOLFBOOT_LOAD_DTS_ADDRESS, and so + * the capacity the boot paths hand to fdt_open() and hal_dts_fixup(). + * MIN is the v17 header size (also enforced by the signer). + * + * This is per-target, not a global ceiling: a target whose window is + * under the 1 MiB default MUST override it (hal/nxp_ppc.h uses 64 KiB). + * Too large is the dangerous direction - every parser bound is checked + * against the capacity given, so an over-large value lets an in-place + * fixup grow the blob past the real region. Pass a tighter bound + * directly when one is known (a FIT sub-image's declared length, say). */ +#ifndef WOLFBOOT_DTS_MAX_SIZE +#define WOLFBOOT_DTS_MAX_SIZE (1024U * 1024U) +#endif +#define WOLFBOOT_DTS_MIN_SIZE ((uint32_t)FDT_HEADER_SIZE) + +/* Headroom (bytes) reserved in fdt_totalsize() before wolfBoot inserts * /chosen properties. Sized to comfortably hold a full LINUX_BOOTARGS * plus, when WOLFBOOT_FIT_RAMDISK is enabled, two 64-bit * linux,initrd-{start,end} cells with property-name overhead. A target @@ -149,77 +160,239 @@ uint64_t fdt64_to_cpu(uint64_t x); #define WOLFBOOT_FDT_FIXUP_HEADROOM 768 #endif -int fdt_check_header(const void *fdt); -int fdt_next_node(const void *fdt, int offset, int *depth); -int fdt_first_property_offset(const void *fdt, int nodeoffset); -int fdt_next_property_offset(const void *fdt, int offset); -const struct fdt_property *fdt_get_property_by_offset(const void *fdt, int offset, int *lenp); - -const char* fdt_get_name(const void *fdt, int nodeoffset, int *len); -const char* fdt_get_string(const void *fdt, int stroffset, int *lenp); - -const void *fdt_getprop(const void *fdt, int nodeoffset, const char *name, int *lenp); -int fdt_setprop(void *fdt, int nodeoffset, const char *name, const void *val, int len); - -void* fdt_getprop_address(const void *fdt, int nodeoffset, const char *name); - -int fdt_find_node_offset(void* fdt, int startoff, const char* nodename); -int fdt_find_prop_offset(void* fdt, int startoff, const char* propname, const char* propval); - -int fdt_find_devtype(void* fdt, int startoff, const char* node); -int fdt_node_check_compatible(const void *fdt, int nodeoffset, const char *compatible); -int fdt_node_offset_by_compatible(const void *fdt, int startoffset, const char *compatible); -int fdt_add_subnode(void* fdt, int parentoff, const char* name); -int fdt_del_node(void *fdt, int nodeoffset); - -/* helpers to fix/append a property to a node */ -int fdt_fixup_str(void* fdt, int off, const char* node, const char* name, const char* str); -int fdt_fixup_val(void* fdt, int off, const char* node, const char* name, uint32_t val); -int fdt_fixup_val64(void* fdt, int off, const char* node, const char* name, uint64_t val); - -int fdt_shrink(void* fdt); -int fdt_add_mem_rsv(void* fdt, uint64_t address, uint64_t size); - -/* FIT */ -const char* fit_find_images(void* fdt, const char** pkernel, const char** pflat_dt, - const char** pramdisk, const char** pfpga); -/* Return a pointer to a subimage's "compatible" property, or NULL if - * absent. NOTE: "compatible" is a devicetree string-list (one or more - * NUL-separated strings); this returns a pointer to the raw property - * data, i.e. the FIRST string only. Callers that must inspect every - * entry (e.g. to detect "partial") have to obtain the property length - * via fdt_getprop() and scan the whole buffer - fit_load_fpga() does - * this internally rather than relying on this helper. */ -const char* fit_get_compatible(void* fdt, const char* image); -void* fit_load_image(void* fdt, const char* image, int* lenp); -void* fit_load_image_ex(void* fdt, const char* image, int* lenp, uint32_t out_max); -/* Load (and, if compressed, decompress) a FIT subimage directly to a - * caller-supplied destination buffer. Overrides the FIT image's - * `load` and `entry` properties - dst is both the destination and the - * value returned. dst_max bounds the decompressed size. */ -void* fit_load_image_to(void* fdt, const char* image, void* dst, - uint32_t dst_max, int* lenp); +/* ------------------------------------------------------------------ */ +/* Parse context */ +/* ------------------------------------------------------------------ */ + +/* A validated view of one blob, produced by fdt_open(). Offsets are held + * in host byte order, known consistent and known to fit in `capacity`. + * API node/property offsets are structure-block relative, so the root + * node is always offset 0. */ +struct fdt_ctx { + uint8_t* blob; /* base of the blob; NULL when closed */ + uint32_t capacity; /* usable bytes at blob, supplied by the caller */ + uint32_t totalsize; /* <= capacity */ + uint32_t off_rsv; /* memory reservation block, 8-byte aligned */ + uint32_t off_struct; /* structure block, 4-byte aligned */ + uint32_t size_struct; + uint32_t off_strings; /* strings block */ + uint32_t size_strings; +}; +typedef struct fdt_ctx fdt_ctx; + +/* ------------------------------------------------------------------ */ +/* Byte order */ +/* ------------------------------------------------------------------ */ + +uint32_t cpu_to_fdt32(uint32_t x); +uint64_t cpu_to_fdt64(uint64_t x); +uint32_t fdt32_to_cpu(uint32_t x); +uint64_t fdt64_to_cpu(uint64_t x); + +/* ------------------------------------------------------------------ */ +/* Open, size, resize */ +/* ------------------------------------------------------------------ */ -/* FDT initrd fixup: writes /chosen/linux,initrd-{start,end} as 64-bit - * big-endian properties. Creates /chosen if missing. Returns 0 on success - * or a negative FDT_ERR_*. */ -int fdt_fixup_initrd(void* fdt, uint64_t start, uint64_t size); +/* Validate `blob` and populate `ctx`. `capacity` is the bytes the caller + * can safely address at `blob`, NOT the blob's own idea of its size: + * every later bound is checked against it, so an over-large value + * defeats the parser's memory safety. `blob` must be 4-byte aligned. A + * property-less tree (empty strings block) is accepted. + * + * Returns 0, or a negative FDT_ERR_*. On failure ctx->blob is NULL. */ +int fdt_open(fdt_ctx* ctx, void* blob, uint32_t capacity); + +/* Declared totalsize from the header alone, for callers that must learn + * how many bytes to fetch before they hold the whole blob (a two-step + * read out of external flash, say). Checks magic, version and that + * totalsize is in [WOLFBOOT_DTS_MIN_SIZE, WOLFBOOT_DTS_MAX_SIZE]; no + * structural checks, so the complete blob still needs fdt_open(). */ +int fdt_peek_size(const void* hdr, uint32_t hdr_len, uint32_t* totalsize); + +/* Declared size of the blob (bytes). 0 if ctx is not open. */ +uint32_t fdt_size(const fdt_ctx* ctx); + +/* Record `extra` bytes of headroom past the tree's content in totalsize, + * so a later stage sees a blob with room to grow. -FDT_ERR_NOSPACE if the + * capacity cannot hold it. Mutations are bounded by capacity directly and + * do not need this. */ +int fdt_grow(fdt_ctx* ctx, uint32_t extra); + +/* Shrink totalsize down to exactly the bytes the tree occupies. */ +int fdt_shrink(fdt_ctx* ctx); + +/* ------------------------------------------------------------------ */ +/* Reading */ +/* ------------------------------------------------------------------ */ + +/* Next node in tree order; offset < 0 starts. Returns the offset, or + * -FDT_ERR_NOTFOUND at the end of the tree or when the walk leaves the + * subtree it started in. + * + * `depth`, if given, is incremented on entering a node and decremented on + * leaving one, so it counts levels BELOW the starting point. Initialise it + * to 0. Starting from offset < 0 the root is therefore reported at depth 1 + * and its children at depth 2; starting from a node offset, that node's + * direct children are reported at depth 1, which is how fdt_subnode_offset() + * distinguishes them from deeper descendants. */ +int fdt_next_node(const fdt_ctx* ctx, int offset, int* depth); + +/* Property iteration within one node. */ +int fdt_first_property_offset(const fdt_ctx* ctx, int nodeoffset); +int fdt_next_property_offset(const fdt_ctx* ctx, int propoffset); + +/* Value, name and length of the property at `propoffset`. Either output + * pointer may be NULL. Returns the value, or NULL on error (in which + * case *lenp, if given, holds the negative FDT_ERR_*). */ +const void* fdt_getprop_by_offset(const fdt_ctx* ctx, int propoffset, + const char** namep, int* lenp); + +/* Node name. *len receives its length, or a negative FDT_ERR_* if the + * offset is not a node. The root node's name is the empty string. */ +const char* fdt_get_name(const fdt_ctx* ctx, int nodeoffset, int* len); + +/* String from the strings block by its offset within that block. */ +const char* fdt_get_string(const fdt_ctx* ctx, int stroffset, int* lenp); + +/* Value of a named property, or NULL. *lenp receives the length, or a + * negative FDT_ERR_* on failure. */ +const void* fdt_getprop(const fdt_ctx* ctx, int nodeoffset, + const char* name, int* lenp); + +/* A 4- or 8-byte property interpreted as an address. NULL if absent or + * not one of those two widths. */ +void* fdt_getprop_address(const fdt_ctx* ctx, int nodeoffset, + const char* name); + +/* Resolve an absolute path such as "/" or "/soc/serial@21c0500". Path + * components match either the full node name or the part before the + * "@" unit address. Returns the node offset or a negative FDT_ERR_*. */ +int fdt_path_offset(const fdt_ctx* ctx, const char* path); + +/* Direct child of `parentoff` by name. */ +int fdt_subnode_offset(const fdt_ctx* ctx, int parentoff, const char* name); + +/* Search the whole tree from `startoff` (< 0 for the beginning) for an + * exact node-name match, at any depth - prefer fdt_path_offset() when + * the location is known. This and the two searches below report a + * genuine no-match as exactly -FDT_ERR_NOTFOUND and never as some other + * walk error, so an `off != -FDT_ERR_NOTFOUND` test cannot mistake one + * for an offset. A NULL argument or closed ctx still gives + * -FDT_ERR_BADARG. */ +int fdt_find_node_offset(const fdt_ctx* ctx, int startoff, + const char* nodename); + +/* Search the whole tree for a node carrying `propname` with the exact + * NUL-terminated string value `propval`. */ +int fdt_find_prop_offset(const fdt_ctx* ctx, int startoff, + const char* propname, const char* propval); + +/* Shorthand for fdt_find_prop_offset(..., "device_type", devtype). */ +int fdt_find_devtype(const fdt_ctx* ctx, int startoff, const char* devtype); + +/* Search the whole tree for a node whose "compatible" string list + * contains `compatible` as a complete entry. */ +int fdt_node_offset_by_compatible(const fdt_ctx* ctx, int startoff, + const char* compatible); + +/* ------------------------------------------------------------------ */ +/* Writing (in place) */ +/* ------------------------------------------------------------------ */ + +/* Set (or add) a property. Bounded by ctx->capacity. */ +int fdt_setprop(fdt_ctx* ctx, int nodeoffset, const char* name, + const void* val, int len); + +/* Add a child node. Returns its offset, -FDT_ERR_EXISTS if already + * present, or another negative FDT_ERR_*. */ +int fdt_add_subnode(fdt_ctx* ctx, int parentoff, const char* name); + +/* Remove a node and its entire subtree. */ +int fdt_del_node(fdt_ctx* ctx, int nodeoffset); + +/* Append an entry to the memory reservation block. */ +int fdt_add_mem_rsv(fdt_ctx* ctx, uint64_t address, uint64_t size); + +/* Logging wrappers around fdt_setprop() used by the HAL dts fixups. */ +int fdt_fixup_str(fdt_ctx* ctx, int off, const char* node, const char* name, + const char* str); +int fdt_fixup_val(fdt_ctx* ctx, int off, const char* node, const char* name, + uint32_t val); +int fdt_fixup_val64(fdt_ctx* ctx, int off, const char* node, const char* name, + uint64_t val); + +/* Write /chosen/linux,initrd-{start,end} as 64-bit big-endian values, + * creating /chosen if needed. Returns 0 or a negative FDT_ERR_*. */ +int fdt_fixup_initrd(fdt_ctx* ctx, uint64_t start, uint64_t size); + +/* ------------------------------------------------------------------ */ +/* Flattened uImage Tree (FIT) */ +/* ------------------------------------------------------------------ */ + +/* Boot configuration and the sub-image names it references. Output + * pointers may be NULL. Returns the configuration name, or NULL. + * + * Sub-images resolve as direct children of /images, configurations as + * direct children of /configurations. A tree-wide search by bare name is + * used only when the FIT has no /images node at all, so a node planted + * elsewhere cannot stand in for the real sub-image. */ +const char* fit_find_images(fdt_ctx* ctx, const char** pkernel, + const char** pflat_dt, const char** pramdisk, const char** pfpga); + +/* A sub-image's "compatible" property, or NULL. NOTE: this returns the + * raw property data, i.e. only the FIRST string of the list. To inspect + * every entry, take the length from fdt_getprop() and walk the NUL + * separators (fit_load_fpga() does that instead of using this). */ +const char* fit_get_compatible(fdt_ctx* ctx, const char* image); + +/* Stage a sub-image to its FIT-declared `load` address, decompressing + * if it declares compression="gzip", and return its entry address. */ +void* fit_load_image(fdt_ctx* ctx, const char* image, int* lenp); + +/* As fit_load_image(), with an explicit ceiling on the decompressed + * size at the FIT-declared destination. */ +void* fit_load_image_ex(fdt_ctx* ctx, const char* image, int* lenp, + uint32_t out_max); + +/* Load (and, if compressed, decompress) a sub-image directly to a + * caller-supplied destination, overriding the FIT image's `load` and + * `entry` properties. dst is both the destination and the value + * returned; dst_max bounds the decompressed size. */ +void* fit_load_image_to(fdt_ctx* ctx, const char* image, void* dst, + uint32_t dst_max, int* lenp); #ifdef WOLFBOOT_FIT_RAMDISK -/* Load a FIT ramdisk subimage (optionally relocated to - * WOLFBOOT_LOAD_RAMDISK_ADDRESS) and patch /chosen/linux,initrd-* - * in the supplied DTB. Returns 0 on success, -1 on load failure. - * Callers typically ignore the return value (log-and-continue). */ -int fit_load_ramdisk(void* fit, const char* ramdisk_node, void* dts_addr); +/* Load a FIT ramdisk sub-image (optionally relocated to + * WOLFBOOT_LOAD_RAMDISK_ADDRESS) and patch /chosen/linux,initrd-* in + * `dts`. Pass dts as NULL to skip the fixup. Returns 0 on success, -1 + * on load failure. Callers typically log and continue. */ +int fit_load_ramdisk(fdt_ctx* ctx, const char* ramdisk_node, fdt_ctx* dts); #endif #ifdef WOLFBOOT_FPGA_BITSTREAM -/* Locate a FIT fpga subimage, stage it to its `load` address (or use - * the in-FIT data pointer) and program the PL via hal_fpga_load(). - * Returns 0 on success (including when fpga_node is NULL), negative on - * failure. When WOLFBOOT_FPGA_NONFATAL is defined a programming failure - * is logged and 0 is returned instead. */ -int fit_load_fpga(void* fdt, const char* fpga_node); +/* Locate a FIT fpga sub-image, stage it and program the PL via + * hal_fpga_load(). Returns 0 on success (including when fpga_node is + * NULL), negative on failure. When WOLFBOOT_FPGA_NONFATAL is defined a + * programming failure is logged and 0 is returned instead. */ +int fit_load_fpga(fdt_ctx* ctx, const char* fpga_node); +#endif + + +/* ------------------------------------------------------------------ */ +/* Malformed-blob corpus (test builds only) */ +/* ------------------------------------------------------------------ */ + +#ifdef WOLFBOOT_FDT_CORPUS +/* Number of cases; short name and one-line description of each. */ +int fdt_corpus_count(void); +const char* fdt_corpus_name(int idx); +const char* fdt_corpus_desc(int idx); + +/* Build case idx, mutating the supplied known-good blob or synthesizing + * one. Returns a malloc'd buffer of exactly *outlen bytes - so a read + * past the declared size is a real heap overrun - or NULL. Caller frees. */ +uint8_t* fdt_corpus_build(int idx, const uint8_t* base, uint32_t baselen, + uint32_t* outlen); #endif #ifdef __cplusplus diff --git a/include/image.h b/include/image.h index bf59138a21..1fdf340e36 100644 --- a/include/image.h +++ b/include/image.h @@ -1713,7 +1713,9 @@ int image_CT_compare(const uint8_t *expected, const uint8_t *actual, int wolfBoot_hardened_CT_compare(const uint8_t *expected, const uint8_t *actual, uint32_t len); #if defined(MMU) || defined(WOLFBOOT_FDT) -int wolfBoot_get_dts_size(void *dts_addr); +/* Validate a DTB and return its size. `capacity` is the number of bytes + * readable at dts_addr. Callers holding only a header use fdt_peek_size(). */ +int wolfBoot_get_dts_size(void *dts_addr, uint32_t capacity); int wolfBoot_verify_dts_digest(const uint8_t *expected_digest, const void *dts_addr, uint32_t dts_size); #endif diff --git a/src/boot_aarch64.c b/src/boot_aarch64.c index 4e6c9e2ea2..c3d04c906e 100644 --- a/src/boot_aarch64.c +++ b/src/boot_aarch64.c @@ -133,9 +133,10 @@ void boot_entry_C(void) #ifdef MMU -int WEAKFUNCTION hal_dts_fixup(void* dts_addr) +int WEAKFUNCTION hal_dts_fixup(void* dts_addr, uint32_t capacity) { (void)dts_addr; + (void)capacity; return 0; } #endif @@ -158,7 +159,9 @@ void RAMFUNCTION do_boot(const uint32_t *app_offset) (uint32_t)(uintptr_t)app_offset, current_el()); #ifdef MMU wolfBoot_printf("do_boot: dts=0x%08x\n", (uint32_t)(uintptr_t)dts_offset); - hal_dts_fixup((uint32_t*)dts_offset); + /* WOLFBOOT_DTS_MAX_SIZE is this target's DTS staging-window size + * (see include/fdt.h); it bounds the fixups below. */ + hal_dts_fixup((uint32_t*)dts_offset, WOLFBOOT_DTS_MAX_SIZE); #endif #ifndef SKIP_GIC_INIT diff --git a/src/boot_ppc.c b/src/boot_ppc.c index 9a984df8a3..5c8199b91f 100644 --- a/src/boot_ppc.c +++ b/src/boot_ppc.c @@ -115,9 +115,10 @@ void WEAKFUNCTION hal_early_init(void) } #ifdef MMU -int WEAKFUNCTION hal_dts_fixup(void* dts_addr) +int WEAKFUNCTION hal_dts_fixup(void* dts_addr, uint32_t capacity) { (void)dts_addr; + (void)capacity; return 0; } #endif @@ -430,7 +431,9 @@ void do_boot(const uint32_t *app_offset) boot_entry entry = (boot_entry)app_offset; #ifdef MMU - hal_dts_fixup((uint32_t*)dts_offset); + /* WOLFBOOT_DTS_MAX_SIZE is this target's DTS staging-window size (see + * include/fdt.h); it bounds every in-place fixup below. */ + hal_dts_fixup((uint32_t*)dts_offset, WOLFBOOT_DTS_MAX_SIZE); #endif #if defined(DEBUG_UART) && defined(WOLFBOOT_ARCH_PPC) diff --git a/src/boot_riscv.c b/src/boot_riscv.c index 1b983beb12..a9424fb49f 100644 --- a/src/boot_riscv.c +++ b/src/boot_riscv.c @@ -358,9 +358,10 @@ uint64_t hal_get_timer_us(void) } #if defined(MMU) || defined(WOLFBOOT_FDT) -int WEAKFUNCTION hal_dts_fixup(void* dts_addr) +int WEAKFUNCTION hal_dts_fixup(void* dts_addr, uint32_t capacity) { (void)dts_addr; + (void)capacity; return 0; } #endif @@ -468,7 +469,9 @@ void do_boot(const uint32_t *app_offset) /* dts_offset is NULL when the loaded image was not a FIT (or had no * flat_dt): skip the fixup and hand off with dtb=0 rather than deref. */ if (dts_offset != NULL) { - hal_dts_fixup((uint32_t*)dts_offset); + /* WOLFBOOT_DTS_MAX_SIZE is this target's DTS staging-window + * size (see include/fdt.h); it bounds the fixups below. */ + hal_dts_fixup((uint32_t*)dts_offset, WOLFBOOT_DTS_MAX_SIZE); } dts_addr = (unsigned long)dts_offset; #elif defined(WOLFBOOT_RISCV_MMODE) || __riscv_xlen == 64 diff --git a/src/fdt.c b/src/fdt.c index 3ae87fa004..5972e70da3 100644 --- a/src/fdt.c +++ b/src/fdt.c @@ -1,6 +1,19 @@ /* fdt.c * - * Functions to help with flattened device tree (DTB) parsing + * Flattened device tree (DTB) parser, written from the Devicetree + * Specification v0.4 section 5. + * + * fdt_open() validates a blob once, in full: header layout inside the + * caller's capacity, then a walk of the whole structure block proving + * balanced nesting, one root, in-bounds NUL-terminated names, in-bounds + * property lengths and a terminating FDT_END. Everything after relies on + * those invariants rather than re-deriving them from attacker-controlled + * header fields on every access. + * + * Only the mutators can disturb the invariants, so each updates the + * context and the on-disk header in the same step that moves bytes, and + * bounds every move against ctx->capacity - never against a size read + * back out of the blob. * * * Copyright (C) 2026 wolfSSL Inc. @@ -49,6 +62,10 @@ #define WOLFBOOT_FIT_MAX_DECOMP (256U * 1024U * 1024U) #endif +/* ------------------------------------------------------------------ */ +/* Byte order */ +/* ------------------------------------------------------------------ */ + uint32_t cpu_to_fdt32(uint32_t x) { #ifdef BIG_ENDIAN_ORDER @@ -65,925 +82,1311 @@ uint64_t cpu_to_fdt64(uint64_t x) return (uint64_t)__builtin_bswap64(x); #endif } - uint32_t fdt32_to_cpu(uint32_t x) { -#ifdef BIG_ENDIAN_ORDER - return x; -#else - return (uint32_t)__builtin_bswap32(x); -#endif + return cpu_to_fdt32(x); } uint64_t fdt64_to_cpu(uint64_t x) { -#ifdef BIG_ENDIAN_ORDER - return x; -#else - return (uint64_t)__builtin_bswap64(x); -#endif + return cpu_to_fdt64(x); } -/* Internal Functions */ -static inline const void *fdt_offset_ptr_(const void *fdt, int offset) +/* ------------------------------------------------------------------ */ +/* Raw accessors */ +/* ------------------------------------------------------------------ */ + +/* fdt_open() requires a 4-byte aligned base, so aligned loads are safe. + * Left as plain `static`: some targets build at -O0 (OPTIMIZATION_LEVEL + * in arch.mk) where inlining these at ~40 sites grows the image. */ +static uint32_t fdt_rd32(const void* p) { - return (const char*)fdt + fdt_off_dt_struct(fdt) + offset; + return fdt32_to_cpu(*(const uint32_t*)p); } -static inline void *fdt_offset_ptr_w_(const void *fdt, int offset) +static void fdt_wr32(void* p, uint32_t v) { - return (char*)fdt + fdt_off_dt_struct(fdt) + offset; + *(uint32_t*)p = cpu_to_fdt32(v); } -static inline int fdt_data_size_(void *fdt) + +/* Reservation entries are 64-bit but the blob base is only guaranteed + * 4-byte aligned, so they are handled as two 32-bit halves. */ +static uint64_t fdt_rd64u(const uint8_t* p) { - /* the last portion of a FDT is the DT string, so use its offset and size to - * determine total size */ - 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); + return ((uint64_t)fdt_rd32(p) << 32) | (uint64_t)fdt_rd32(p + 4); +} +static void fdt_wr64u(uint8_t* p, uint64_t v) +{ + fdt_wr32(p, (uint32_t)(v >> 32)); + fdt_wr32(p + 4, (uint32_t)v); } -static const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int len) +static uint32_t fdt_hdr_get(const uint8_t* b, uint32_t field) +{ + return fdt_rd32(b + field); +} +static void fdt_hdr_put(uint8_t* b, uint32_t field, uint32_t v) { - unsigned int uoffset = offset; - unsigned int absoffset = offset + fdt_off_dt_struct(fdt); + fdt_wr32(b + field, v); +} - if (offset < 0) { - return NULL; - } - if ((absoffset < uoffset) - || ((absoffset + len) < absoffset) - || (absoffset + len) > fdt_totalsize(fdt)) { - return NULL; - } - if (fdt_version(fdt) >= 0x11) { - if (((uoffset + len) < uoffset) - || ((offset + len) > fdt_size_dt_struct(fdt))) { - return NULL; - } +/* Total bytes the tree currently occupies (the strings block is last). */ +static uint32_t fdt_data_end(const fdt_ctx* ctx) +{ + return ctx->off_strings + ctx->size_strings; +} + +/* Write the context's layout back to the header. totalsize never drops + * below the content; only fdt_shrink() pulls it down. Not inlined: every + * mutator ends with this and duplicating it costs more than the call. */ +static void __attribute__((noinline)) fdt_hdr_sync(fdt_ctx* ctx) +{ + uint32_t end = fdt_data_end(ctx); + + if (end > ctx->totalsize) { + ctx->totalsize = end; } - return fdt_offset_ptr_(fdt, offset); + fdt_hdr_put(ctx->blob, FDT_H_TOTALSIZE, ctx->totalsize); + fdt_hdr_put(ctx->blob, FDT_H_OFF_STRUCT, ctx->off_struct); + fdt_hdr_put(ctx->blob, FDT_H_SIZE_STRUCT, ctx->size_struct); + fdt_hdr_put(ctx->blob, FDT_H_OFF_STRINGS, ctx->off_strings); + fdt_hdr_put(ctx->blob, FDT_H_SIZE_STRINGS, ctx->size_strings); } -static uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset) +static int fdt_ctx_ok(const fdt_ctx* ctx) { - const uint32_t *tagp, *lenp; - uint32_t tag; - uint32_t proplen; - uint64_t next_off; - int offset = startoffset; - const char *p; + return (ctx != NULL && ctx->blob != NULL); +} - *nextoffset = -FDT_ERR_TRUNCATED; - tagp = fdt_offset_ptr(fdt, offset, FDT_TAGSIZE); - if (tagp == NULL) { - return FDT_END; /* premature end */ +/* ------------------------------------------------------------------ */ +/* Token stream */ +/* ------------------------------------------------------------------ */ + +/* Read the token at `off` (structure-block relative), report where the + * next starts. Safe on an unvalidated blob (every read bounded by + * size_struct) because fdt_open()'s validation pass uses it too. */ +static int fdt_tag_walk(const fdt_ctx* ctx, int off, uint32_t* tagp, + int* nextp) +{ + const uint8_t* sb = ctx->blob + ctx->off_struct; + uint32_t cur = (uint32_t)off; + uint32_t avail, tag, adv; + const uint8_t* nul; + + /* size_struct >= FDT_TAGSIZE (fdt_open), so this cannot wrap. + * Tracking bytes-remaining keeps every bound a 32-bit compare; as + * additions they would need 64-bit maths to stay wrap-safe. */ + if (off < 0 || (cur & (FDT_TAGSIZE - 1U)) != 0 + || cur > ctx->size_struct - FDT_TAGSIZE) { + return -FDT_ERR_BADOFFSET; } - tag = fdt32_to_cpu(*tagp); - offset += FDT_TAGSIZE; + tag = fdt_rd32(sb + cur); + cur += FDT_TAGSIZE; + avail = ctx->size_struct - cur; - *nextoffset = -FDT_ERR_BADSTRUCTURE; switch (tag) { case FDT_BEGIN_NODE: - /* skip name */ - do { - p = fdt_offset_ptr(fdt, offset++, 1); - } while (p && (*p != '\0')); - if (p == NULL) - return FDT_END; /* premature end */ + /* the name must terminate before the block does */ + nul = (const uint8_t*)memchr(sb + cur, '\0', (size_t)avail); + if (nul == NULL) { + return -FDT_ERR_BADSTRUCTURE; + } + adv = FDT_TAGALIGN((uint32_t)(nul - (sb + cur)) + 1U); break; - case FDT_PROP: - lenp = fdt_offset_ptr(fdt, offset, sizeof(*lenp)); - if (!lenp) { - return FDT_END; /* premature end */ - } - proplen = fdt32_to_cpu(*lenp); - /* A property value can never be larger than the blob itself. - * Reject an oversized length up front: otherwise the unsigned - * cursor arithmetic below wraps (e.g. len=0xFFFFFFFF advances - * offset by only 7 bytes), the malformed node slips past the - * fdt_offset_ptr() bounds check, and the bogus length propagates - * to callers as a negative int (a ~4GB memcpy size). */ - if (proplen > (uint32_t)fdt_totalsize(fdt)) { - return FDT_END; /* bad structure */ - } - /* skip-name offset, length and value. Accumulate the next cursor in a - * 64-bit unsigned so neither the addition nor the narrowing back to the - * signed int offset can overflow, then re-validate it against the blob - * size before continuing. */ - next_off = (uint64_t)offset - + (sizeof(struct fdt_property) - FDT_TAGSIZE) + proplen; - if (fdt_version(fdt) < 0x10 && proplen >= 8 && - ((next_off - proplen) % 8) != 0) { - next_off += 4; - } - if (next_off > (uint64_t)fdt_totalsize(fdt)) { - return FDT_END; /* bad structure */ - } - offset = (int)next_off; + if (avail < 2U * FDT_TAGSIZE) { + return -FDT_ERR_BADSTRUCTURE; + } + adv = fdt_rd32(sb + cur); + avail -= 2U * FDT_TAGSIZE; + cur += 2U * FDT_TAGSIZE; + /* Reject an oversized length BEFORE aligning it: FDT_TAGALIGN + * wraps to a small value for a length near UINT32_MAX, which + * would then pass the bound below. */ + if (adv > avail) { + return -FDT_ERR_BADSTRUCTURE; + } + adv = FDT_TAGALIGN(adv); break; - - case FDT_END: case FDT_END_NODE: case FDT_NOP: + case FDT_END: + adv = 0; break; - default: - return FDT_END; + return -FDT_ERR_BADSTRUCTURE; } - - if (!fdt_offset_ptr(fdt, startoffset, offset - startoffset)) { - return FDT_END; /* premature end */ + /* aligning up can push past the end by as much as 3 bytes */ + if (adv > avail) { + return -FDT_ERR_BADSTRUCTURE; } - *nextoffset = FDT_TAGALIGN(offset); - return tag; + *tagp = tag; + *nextp = (int)(cur + adv); + return 0; } -static int fdt_check_node_offset_(const void *fdt, int offset) +/* Offset of the first token inside a node, i.e. just past its opening + * token and name. */ +static int fdt_node_body(const fdt_ctx* ctx, int nodeoff) { - if ((offset < 0) || (offset % FDT_TAGSIZE) - || (fdt_next_tag(fdt, offset, &offset) != FDT_BEGIN_NODE)) { - return -FDT_ERR_BADOFFSET; - } - return offset; -} + uint32_t tag; + int next, rc; -static int fdt_check_prop_offset_(const void *fdt, int offset) -{ - if ((offset < 0) || (offset % FDT_TAGSIZE) - || (fdt_next_tag(fdt, offset, &offset) != FDT_PROP)) { + rc = fdt_tag_walk(ctx, nodeoff, &tag, &next); + if (rc != 0) { + return rc; + } + if (tag != FDT_BEGIN_NODE) { return -FDT_ERR_BADOFFSET; } - return offset; + return next; } -static int fdt_next_property_(const void *fdt, int offset) +/* Offset just past a node's closing token, i.e. the end of its subtree. */ +static int fdt_node_end(const fdt_ctx* ctx, int nodeoff) { uint32_t tag; - int nextoffset; + int cur = nodeoff; + int next, rc, depth = 0; - do { - tag = fdt_next_tag(fdt, offset, &nextoffset); - - switch (tag) { - case FDT_END: - if (nextoffset >= 0) + for (;;) { + rc = fdt_tag_walk(ctx, cur, &tag, &next); + if (rc != 0) { + return rc; + } + if (tag == FDT_BEGIN_NODE) { + depth++; + } + else if (tag == FDT_END_NODE) { + depth--; + if (depth == 0) { + return next; + } + if (depth < 0) { return -FDT_ERR_BADSTRUCTURE; - else - return nextoffset; - - case FDT_PROP: - return offset; + } } - offset = nextoffset; - } while (tag == FDT_NOP); - - return -FDT_ERR_NOTFOUND; + else if (tag == FDT_END) { + return -FDT_ERR_BADSTRUCTURE; + } + cur = next; + } } -static const struct fdt_property *fdt_get_property(const void *fdt, int offset, - const char *name, int *lenp, int *poffset) +/* From `off`, skip NOP tokens and return the offset of the property + * token that follows, or -FDT_ERR_NOTFOUND once the node's property + * list has ended. */ +static int fdt_prop_scan(const fdt_ctx* ctx, int off) { - int namelen = (int)strlen(name); - for (offset = fdt_first_property_offset(fdt, offset); - offset >= 0; - offset = fdt_next_property_offset(fdt, offset)) - { - int slen, stroffset; - const char *p; - const struct fdt_property *prop = - fdt_get_property_by_offset(fdt, offset, lenp); - if (prop == NULL) { - offset = -FDT_ERR_INTERNAL; - break; - } - stroffset = fdt32_to_cpu(prop->nameoff); + uint32_t tag; + int next, rc; - p = fdt_get_string(fdt, stroffset, &slen); - if (p && (slen == namelen) && (memcmp(p, name, namelen) == 0)) { - if (poffset) - *poffset = offset; - return prop; + for (;;) { + rc = fdt_tag_walk(ctx, off, &tag, &next); + if (rc != 0) { + return rc; } + if (tag == FDT_PROP) { + return off; + } + if (tag != FDT_NOP) { + return -FDT_ERR_NOTFOUND; + } + off = next; } - if (lenp) { - *lenp = offset; - } - return NULL; } -static void fdt_del_last_string_(void *fdt, const char *s) -{ - int newlen = strlen(s) + 1; - fdt_set_size_dt_strings(fdt, fdt_size_dt_strings(fdt) - newlen); -} +/* ------------------------------------------------------------------ */ +/* Strings block */ +/* ------------------------------------------------------------------ */ -static int fdt_splice_(void *fdt, void *splicepoint, int oldlen, int newlen) +static const char* fdt_strtab_at(const fdt_ctx* ctx, uint32_t stroff) { - int data_size; - char *p, *end; - - data_size = fdt_data_size_(fdt); - if (data_size < 0) - return data_size; - - p = splicepoint; - end = (char*)fdt + data_size; - if (((p + oldlen) < p) || ((p + oldlen) > end)) { - return -FDT_ERR_BADOFFSET; - } - if ((p < (char*)fdt) || ((end - oldlen + newlen) < (char*)fdt)) { - return -FDT_ERR_BADOFFSET; - } - if ((end - oldlen + newlen) > ((char*)fdt + fdt_totalsize(fdt))) { - return -FDT_ERR_NOSPACE; + if (stroff >= ctx->size_strings) { + return NULL; } - memmove(p + newlen, p + oldlen, end - p - oldlen); - return 0; + return (const char*)(ctx->blob + ctx->off_strings + stroff); } -static int fdt_splice_struct_(void *fdt, void *p, int oldlen, int newlen) +/* Find `s` in the strings block. Any position matching `s` plus its + * terminator works, so a name that is a tail of an entry shares it. */ +static int fdt_strtab_find(const fdt_ctx* ctx, const char* s, uint32_t* stroff) { - int err, delta; + const char* tab = (const char*)(ctx->blob + ctx->off_strings); + uint32_t need = (uint32_t)strlen(s) + 1U; + uint32_t i; - delta = newlen - oldlen; - err = fdt_splice_(fdt, p, oldlen, newlen); - if (err == 0) { - fdt_set_size_dt_struct(fdt, fdt_size_dt_struct(fdt) + delta); - fdt_set_off_dt_strings(fdt, fdt_off_dt_strings(fdt) + delta); + if (need > ctx->size_strings) { + return -FDT_ERR_NOTFOUND; } - return err; -} - -static int fdt_resize_property_(void *fdt, int nodeoffset, const char *name, - int len, struct fdt_property **prop) -{ - int err, oldlen; - - *prop = (struct fdt_property*)(uintptr_t) - fdt_get_property(fdt, nodeoffset, name, &oldlen, NULL); - if (*prop != NULL) { - err = fdt_splice_struct_(fdt, (*prop)->data, FDT_TAGALIGN(oldlen), - FDT_TAGALIGN(len)); - if (err == 0) { - (*prop)->len = cpu_to_fdt32(len); + for (i = 0; i <= ctx->size_strings - need; i++) { + if (memcmp(tab + i, s, need) == 0) { + *stroff = i; + return 0; } } - else { - err = oldlen; - } - return err; + return -FDT_ERR_NOTFOUND; } -static int fdt_splice_string_(void *fdt, int newlen) +/* ------------------------------------------------------------------ */ +/* Open / validate */ +/* ------------------------------------------------------------------ */ + +/* One pass over the structure block. fdt_tag_walk() proved each token's + * own bounds, so this adds only tree shape: balanced nesting, one root, + * properties inside a node, names in range, terminating FDT_END. */ +static int fdt_validate_struct(const fdt_ctx* ctx) { - int err; - uint32_t off = fdt_off_dt_strings(fdt); - uint32_t sz = fdt_size_dt_strings(fdt); - void *p; + const uint8_t* sb = ctx->blob + ctx->off_struct; + uint32_t tag, nameoff; + int cur = 0; + int next, rc, depth = 0, roots = 0; + + for (;;) { + rc = fdt_tag_walk(ctx, cur, &tag, &next); + if (rc != 0) { + return rc; + } + switch (tag) { + case FDT_BEGIN_NODE: + if (depth == 0 && ++roots > 1) { + return -FDT_ERR_BADSTRUCTURE; + } + if (++depth > FDT_MAX_DEPTH) { + return -FDT_ERR_BADSTRUCTURE; + } + break; - if (sz > UINT32_MAX - off) - return -FDT_ERR_BADOFFSET; - p = (char*)fdt + off + sz; + case FDT_PROP: + if (depth == 0) { + return -FDT_ERR_BADSTRUCTURE; /* property outside any node */ + } + /* In-range is enough: fdt_open() proved the block ends in a + * NUL, so every in-range offset terminates. */ + nameoff = fdt_rd32(sb + (uint32_t)cur + 2U * FDT_TAGSIZE); + if (nameoff >= ctx->size_strings) { + return -FDT_ERR_BADSTRUCTURE; + } + break; - if ((err = fdt_splice_(fdt, p, 0, newlen))) { - return err; - } - fdt_set_size_dt_strings(fdt, fdt_size_dt_strings(fdt) + newlen); - return 0; -} + case FDT_END_NODE: + if (depth == 0) { + return -FDT_ERR_BADSTRUCTURE; + } + depth--; + break; -static const char* fdt_find_string_(const char *strtab, int tabsize, const char *s) -{ - int len = strlen(s) + 1; - const char *last = strtab + tabsize - len; - const char *p; + case FDT_END: + if (depth != 0 || roots != 1) { + return -FDT_ERR_BADSTRUCTURE; + } + return 0; - for (p = strtab; p <= last; p++) { - if (memcmp(p, s, len) == 0) { - return p; + default: /* FDT_NOP */ + break; } + cur = next; } - return NULL; } -static int fdt_find_add_string_(void *fdt, const char *s, int *allocated) +/* Header checks shared by fdt_open() and fdt_peek_size(). `avail` is + * what the caller can read now (a peek may hold only the header); + * `maxtotal` bounds the size the blob may declare. */ +static int fdt_hdr_check(const uint8_t* b, uint32_t avail, uint32_t maxtotal, + uint32_t* totalp) { - int err, len; - char *strtab, *new; - const char *p; + uint32_t total; - strtab = (char*)fdt + fdt_off_dt_strings(fdt); - len = strlen(s) + 1; - *allocated = 0; - p = fdt_find_string_(strtab, fdt_size_dt_strings(fdt), s); - if (p) { /* found it */ - return (p - strtab); + /* 32-bit loads are used throughout the header and structure block, + * so the base has to be aligned for them. */ + if (b == NULL + || ((uintptr_t)b & (uintptr_t)(FDT_TAGSIZE - 1U)) != 0 + || avail < (uint32_t)FDT_HEADER_SIZE) { + return -FDT_ERR_BADARG; + } + if (fdt_hdr_get(b, FDT_H_MAGIC) != (uint32_t)FDT_MAGIC) { + return -FDT_ERR_BADMAGIC; } - new = strtab + fdt_size_dt_strings(fdt); - err = fdt_splice_string_(fdt, len); - if (err) { - return err; + /* A blob may declare a newer version so long as it stays readable as + * v17; anything that predates v17 is rejected outright. */ + if (fdt_hdr_get(b, FDT_H_VERSION) < (uint32_t)FDT_SUPPORTED_VERSION + || fdt_hdr_get(b, FDT_H_LAST_COMP) + > (uint32_t)FDT_SUPPORTED_VERSION) { + return -FDT_ERR_BADVERSION; } - *allocated = 1; - - memcpy(new, s, len); - return (new - strtab); + total = fdt_hdr_get(b, FDT_H_TOTALSIZE); + if (total < (uint32_t)FDT_HEADER_SIZE || total > maxtotal) { + return -FDT_ERR_BADLAYOUT; + } + *totalp = total; + return 0; } -static int fdt_add_property_(void *fdt, int nodeoffset, const char *name, - int len, struct fdt_property **prop) +int fdt_open(fdt_ctx* ctx, void* blob, uint32_t capacity) { - int err, proplen, nextoffset, namestroff, allocated; - - if ((nextoffset = fdt_check_node_offset_(fdt, nodeoffset)) < 0) { - return nextoffset; - } - namestroff = fdt_find_add_string_(fdt, name, &allocated); - if (namestroff < 0) { - return namestroff; + uint8_t* b = (uint8_t*)blob; + uint32_t total, off_rsv, off_struct, size_struct, off_strings; + uint32_t size_strings, i; + int rc; + + if (ctx == NULL) { + return -FDT_ERR_BADARG; + } + /* Closed until proven good. Every entry point gates on ctx->blob, so + * the other fields need no scrubbing while it is NULL. */ + ctx->blob = NULL; + + /* The bound that makes everything below safe: the blob does not get + * to declare a size larger than the buffer it actually lives in. */ + rc = fdt_hdr_check(b, capacity, capacity, &total); + if (rc != 0) { + return rc; + } + + off_rsv = fdt_hdr_get(b, FDT_H_OFF_RSVMAP); + off_struct = fdt_hdr_get(b, FDT_H_OFF_STRUCT); + size_struct = fdt_hdr_get(b, FDT_H_SIZE_STRUCT); + off_strings = fdt_hdr_get(b, FDT_H_OFF_STRINGS); + size_strings = fdt_hdr_get(b, FDT_H_SIZE_STRINGS); + + /* Blocks aligned, in order, non-overlapping, inside totalsize. Each + * 32-bit compare is kept wrap-safe by the one before it: an offset is + * proved in range before anything is subtracted from it. */ + if (off_rsv < (uint32_t)FDT_HEADER_SIZE + || (off_rsv & 7U) != 0 + || (off_struct & (FDT_TAGSIZE - 1U)) != 0 + || size_struct < FDT_TAGSIZE + || off_strings > total + || off_struct > off_strings + || off_struct < off_rsv + || off_struct - off_rsv < (uint32_t)FDT_RSV_ENTRY_SIZE + || off_strings - off_struct < size_struct + || total - off_strings < size_strings) { + return -FDT_ERR_BADLAYOUT; + } + /* A terminated block lets string offsets be handed out without a + * scan. An empty one is legal (no properties, no names) and needs no + * terminator - every offset into it is then out of range. */ + if (size_strings > 0 && b[off_strings + size_strings - 1U] != '\0') { + return -FDT_ERR_BADSTRUCTURE; } - *prop = fdt_offset_ptr_w_(fdt, nextoffset); - proplen = sizeof(**prop) + FDT_TAGALIGN(len); + /* The reservation block runs to an all-zero (0, 0) entry, which must + * appear before the structure block starts. */ + for (i = off_rsv;; i += (uint32_t)FDT_RSV_ENTRY_SIZE) { + uint32_t j; - err = fdt_splice_struct_(fdt, *prop, 0, proplen); - if (err) { - /* Delete the string if we failed to add it */ - if (allocated) - fdt_del_last_string_(fdt, name); - return err; + if (i > off_struct - (uint32_t)FDT_RSV_ENTRY_SIZE) { + return -FDT_ERR_BADLAYOUT; + } + for (j = 0; j < (uint32_t)FDT_RSV_ENTRY_SIZE; j++) { + if (b[i + j] != 0) { + break; + } + } + if (j == (uint32_t)FDT_RSV_ENTRY_SIZE) { + break; + } } - (*prop)->tag = cpu_to_fdt32(FDT_PROP); - (*prop)->nameoff = cpu_to_fdt32(namestroff); - (*prop)->len = cpu_to_fdt32(len); + ctx->blob = b; + ctx->capacity = capacity; + ctx->totalsize = total; + ctx->off_rsv = off_rsv; + ctx->off_struct = off_struct; + ctx->size_struct = size_struct; + ctx->off_strings = off_strings; + ctx->size_strings = size_strings; + + rc = fdt_validate_struct(ctx); + if (rc != 0) { + ctx->blob = NULL; + return rc; + } return 0; } -/* return: 0=no match, 1=matched */ -static int fdt_nodename_eq_(const void *fdt, int offset, const char *s, - int len) +int fdt_peek_size(const void* hdr, uint32_t hdr_len, uint32_t* totalsize) { - const char *p = fdt_offset_ptr(fdt, offset + FDT_TAGSIZE, len+1); - if (p == NULL || memcmp(p, s, len) != 0) { - return 0; - } - if (p[len] == '\0') { - return 1; - } else if (!memchr(s, '@', len) && (p[len] == '@')) { - return 1; + if (totalsize == NULL) { + return -FDT_ERR_BADARG; } - return 0; + /* Only the header is in hand, so the declared size is bounded by the + * generic staging maximum rather than by anything measured. */ + return fdt_hdr_check((const uint8_t*)hdr, hdr_len, WOLFBOOT_DTS_MAX_SIZE, + totalsize); } -static int fdt_subnode_offset_namelen(const void *fdt, int offset, - const char *name, int namelen) +uint32_t fdt_size(const fdt_ctx* ctx) { - int depth; - for (depth = 0; - (offset >= 0) && (depth >= 0); - offset = fdt_next_node(fdt, offset, &depth)) - { - if ((depth == 1) && fdt_nodename_eq_(fdt, offset, name, namelen)) { - return offset; - } - } - if (depth < 0) { - return -FDT_ERR_NOTFOUND; + if (!fdt_ctx_ok(ctx)) { + return 0; } - return offset; /* error */ + return ctx->totalsize; } - - -/* Public Functions */ -int fdt_check_header(const void *fdt) +int fdt_grow(fdt_ctx* ctx, uint32_t extra) { - if (fdt_magic(fdt) == FDT_MAGIC) { - uint32_t off_rsv = fdt_off_mem_rsvmap(fdt); - uint32_t off_struct = fdt_off_dt_struct(fdt); - uint32_t size_struct = fdt_size_dt_struct(fdt); - uint32_t off_strings = fdt_off_dt_strings(fdt); - uint32_t size_strings = fdt_size_dt_strings(fdt); + uint32_t end; - if (fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION) - return -FDT_ERR_BADVERSION; - if (fdt_last_comp_version(fdt) > FDT_LAST_SUPPORTED_VERSION) - return -FDT_ERR_BADVERSION; - /* The three structural areas must sit inside the blob and not - * overlap: reservation map, structure block and string table, - * in that order. The additions are made in 64-bit so the size - * fields cannot wrap around the comparison. */ - if (off_rsv > off_struct - || (uint64_t)off_struct + size_struct > off_strings - || (uint64_t)off_strings + size_strings > fdt_totalsize(fdt)) - return -FDT_ERR_BADSTRUCTURE; + if (!fdt_ctx_ok(ctx)) { + return -FDT_ERR_BADARG; } - else if (fdt_magic(fdt) == FDT_SW_MAGIC) { - if (fdt_size_dt_struct(fdt) == 0) - return -FDT_ERR_BADSTATE; + end = fdt_data_end(ctx); + if (extra > ctx->capacity - end) { + return -FDT_ERR_NOSPACE; /* callers report this */ } - else { - return -FDT_ERR_BADMAGIC; + ctx->totalsize = end + extra; + fdt_hdr_sync(ctx); + return 0; +} + +int fdt_shrink(fdt_ctx* ctx) +{ + if (!fdt_ctx_ok(ctx)) { + return -FDT_ERR_BADARG; } + ctx->totalsize = fdt_data_end(ctx); + fdt_hdr_sync(ctx); return 0; } -int fdt_next_node(const void *fdt, int offset, int *depth) +/* ------------------------------------------------------------------ */ +/* Reading */ +/* ------------------------------------------------------------------ */ + +int fdt_next_node(const fdt_ctx* ctx, int offset, int* depth) { - int nextoffset = 0; uint32_t tag; + int cur, next, rc; - if (offset >= 0) { - if ((nextoffset = fdt_check_node_offset_(fdt, offset)) < 0) - return nextoffset; + if (!fdt_ctx_ok(ctx)) { + return -FDT_ERR_BADARG; } - do { - offset = nextoffset; - tag = fdt_next_tag(fdt, offset, &nextoffset); - - switch (tag) { - case FDT_PROP: - case FDT_NOP: - break; - - case FDT_BEGIN_NODE: - if (depth) + if (offset < 0) { + next = 0; + } + else { + rc = fdt_tag_walk(ctx, offset, &tag, &next); + if (rc != 0) { + return rc; + } + if (tag != FDT_BEGIN_NODE) { + return -FDT_ERR_BADOFFSET; + } + } + for (;;) { + cur = next; + rc = fdt_tag_walk(ctx, cur, &tag, &next); + if (rc != 0) { + return rc; + } + if (tag == FDT_BEGIN_NODE) { + if (depth != NULL) { (*depth)++; - break; - - case FDT_END_NODE: - if (depth && ((--(*depth)) < 0)) - return nextoffset; - break; - - case FDT_END: - if ((nextoffset >= 0) - || ((nextoffset == -FDT_ERR_TRUNCATED) && !depth)) - return -FDT_ERR_NOTFOUND; - else - return nextoffset; + } + return cur; } - } while (tag != FDT_BEGIN_NODE); - - return offset; + if (tag == FDT_END_NODE) { + if (depth != NULL) { + (*depth)--; + if (*depth < 0) { + /* walked back out of the subtree we started in */ + return -FDT_ERR_NOTFOUND; + } + } + } + else if (tag == FDT_END) { + return -FDT_ERR_NOTFOUND; + } + } } -int fdt_first_property_offset(const void *fdt, int nodeoffset) +int fdt_first_property_offset(const fdt_ctx* ctx, int nodeoffset) { - int offset; - if ((offset = fdt_check_node_offset_(fdt, nodeoffset)) < 0) { - return offset; + int body; + + if (!fdt_ctx_ok(ctx)) { + return -FDT_ERR_BADARG; } - return fdt_next_property_(fdt, offset); + body = fdt_node_body(ctx, nodeoffset); + if (body < 0) { + return body; + } + return fdt_prop_scan(ctx, body); } -int fdt_next_property_offset(const void *fdt, int offset) + +int fdt_next_property_offset(const fdt_ctx* ctx, int propoffset) { - if ((offset = fdt_check_prop_offset_(fdt, offset)) < 0) { - return offset; + uint32_t tag; + int next, rc; + + if (!fdt_ctx_ok(ctx)) { + return -FDT_ERR_BADARG; + } + rc = fdt_tag_walk(ctx, propoffset, &tag, &next); + if (rc != 0) { + return rc; + } + if (tag != FDT_PROP) { + return -FDT_ERR_BADOFFSET; } - return fdt_next_property_(fdt, offset); + return fdt_prop_scan(ctx, next); } -const struct fdt_property *fdt_get_property_by_offset(const void *fdt, - int offset, int *lenp) +const void* fdt_getprop_by_offset(const fdt_ctx* ctx, int propoffset, + const char** namep, int* lenp) { - int err; - const struct fdt_property *prop; + const uint8_t* rec; + uint32_t tag; + int next, rc; - if ((err = fdt_check_prop_offset_(fdt, offset)) < 0) { - if (lenp) { - *lenp = err; + if (!fdt_ctx_ok(ctx)) { + rc = -FDT_ERR_BADARG; + } + else { + rc = fdt_tag_walk(ctx, propoffset, &tag, &next); + if (rc == 0 && tag != FDT_PROP) { + rc = -FDT_ERR_BADOFFSET; + } + } + if (rc != 0) { + if (lenp != NULL) { + *lenp = rc; } return NULL; } - prop = fdt_offset_ptr_(fdt, offset); - if (lenp) { - *lenp = fdt32_to_cpu(prop->len); + + rec = ctx->blob + ctx->off_struct + (uint32_t)propoffset; + if (lenp != NULL) { + *lenp = (int)fdt_rd32(rec + FDT_TAGSIZE); + } + if (namep != NULL) { + *namep = fdt_strtab_at(ctx, fdt_rd32(rec + 2U * FDT_TAGSIZE)); } - return prop; + return rec + 3U * FDT_TAGSIZE; } -const char* fdt_get_name(const void *fdt, int nodeoffset, int *len) +const char* fdt_get_name(const fdt_ctx* ctx, int nodeoffset, int* len) { - int err; - const struct fdt_node_header *nh = fdt_offset_ptr_(fdt, nodeoffset); - int namelen = 0; - const char* name = NULL; + const char* name; + uint32_t tag; + int next, rc; - err = fdt_check_header(fdt); - if (err == 0) { - err = fdt_check_node_offset_(fdt, nodeoffset); - if (err >= 0) { - name = nh->name; - namelen = (int)strlen(nh->name); + if (!fdt_ctx_ok(ctx)) { + rc = -FDT_ERR_BADARG; + } + else { + rc = fdt_tag_walk(ctx, nodeoffset, &tag, &next); + if (rc == 0 && tag != FDT_BEGIN_NODE) { + rc = -FDT_ERR_BADOFFSET; } } - if (err < 0) - namelen = err; - if (len) - *len = namelen; + if (rc != 0) { + if (len != NULL) { + *len = rc; + } + return NULL; + } + name = (const char*)(ctx->blob + ctx->off_struct + (uint32_t)nodeoffset + + FDT_TAGSIZE); + if (len != NULL) { + *len = (int)strlen(name); + } return name; } -const char* fdt_get_string(const void *fdt, int stroffset, int *lenp) +const char* fdt_get_string(const fdt_ctx* ctx, int stroffset, int* lenp) { - uint32_t strsize = fdt_size_dt_strings(fdt); - const char *s; - const char *end; - int err; + const char* s; - /* off_dt_strings/size_dt_strings are attacker-influenceable header - * fields; validate the layout against totalsize before forming the - * string-table pointer. */ - err = fdt_check_header(fdt); - if (err != 0) { - if (lenp) - *lenp = err; + if (!fdt_ctx_ok(ctx)) { + if (lenp != NULL) { + *lenp = -FDT_ERR_BADARG; + } return NULL; } - - if ((stroffset < 0) || ((uint32_t)stroffset >= strsize)) { - if (lenp) + if (stroffset < 0) { + if (lenp != NULL) { *lenp = -FDT_ERR_BADOFFSET; + } return NULL; } - - s = (const char*)fdt + fdt_off_dt_strings(fdt) + stroffset; - end = memchr(s, '\0', strsize - (uint32_t)stroffset); - if (end == NULL) { - if (lenp) - *lenp = -FDT_ERR_BADSTRUCTURE; + s = fdt_strtab_at(ctx, (uint32_t)stroffset); + if (s == NULL) { + if (lenp != NULL) { + *lenp = -FDT_ERR_BADOFFSET; + } return NULL; } - - if (lenp) { - *lenp = (int)(end - s); + /* the block ends in a NUL, so this cannot run past it */ + if (lenp != NULL) { + *lenp = (int)strlen(s); } return s; } +/* Offset of a named property within a node, or a negative FDT_ERR_*. */ +static int fdt_prop_find(const fdt_ctx* ctx, int nodeoffset, const char* name, + int* lenp) +{ + uint32_t namelen = (uint32_t)strlen(name); + uint32_t tag; + int cur, next, rc; + + /* Walks tokens directly: the public property iterators are used only + * by the host tools, so keeping them off this path lets the linker + * drop them from firmware. */ + cur = fdt_node_body(ctx, nodeoffset); + if (cur < 0) { + return cur; + } + for (;;) { + rc = fdt_tag_walk(ctx, cur, &tag, &next); + if (rc != 0) { + return rc; + } + if (tag == FDT_PROP) { + const uint8_t* rec = ctx->blob + ctx->off_struct + (uint32_t)cur; + const char* pname = fdt_strtab_at(ctx, + fdt_rd32(rec + 2U * FDT_TAGSIZE)); + + if (pname != NULL && strlen(pname) == namelen + && memcmp(pname, name, (size_t)namelen) == 0) { + if (lenp != NULL) { + *lenp = (int)fdt_rd32(rec + FDT_TAGSIZE); + } + return cur; + } + } + else if (tag != FDT_NOP) { + return -FDT_ERR_NOTFOUND; /* end of this node's properties */ + } + cur = next; + } +} -int fdt_setprop(void *fdt, int nodeoffset, const char *name, const void *val, - int len) +const void* fdt_getprop(const fdt_ctx* ctx, int nodeoffset, const char* name, + int* lenp) { - int err = 0; - void *prop_data; - struct fdt_property *prop; + int off, len = 0; - err = fdt_totalsize(fdt); /* confirm size in header */ - if (err > 0) { - err = fdt_resize_property_(fdt, nodeoffset, name, len, &prop); - if (err == -FDT_ERR_NOTFOUND) { - err = fdt_add_property_(fdt, nodeoffset, name, len, &prop); + if (!fdt_ctx_ok(ctx) || name == NULL) { + if (lenp != NULL) { + *lenp = -FDT_ERR_BADARG; } + return NULL; } - else { - err = FDT_ERR_BADSTRUCTURE; - } - if (err == 0) { - prop_data = prop->data; - if (len > 0) { - memcpy(prop_data, val, len); + off = fdt_prop_find(ctx, nodeoffset, name, &len); + if (off < 0) { + if (lenp != NULL) { + *lenp = off; } + return NULL; } - if (err != 0) { - wolfBoot_printf("FDT: Set prop failed! %d (name %s, off %d)\n", - err, name, nodeoffset); + if (lenp != NULL) { + *lenp = len; } - return err; + return ctx->blob + ctx->off_struct + (uint32_t)off + 3U * FDT_TAGSIZE; } -const void* fdt_getprop(const void *fdt, int nodeoffset, const char *name, - int *lenp) +void* fdt_getprop_address(const fdt_ctx* ctx, int nodeoffset, const char* name) { - int poffset; - const struct fdt_property *prop = fdt_get_property( - fdt, nodeoffset, name, lenp, &poffset); - if (prop != NULL) { - /* Handle alignment */ - if (fdt_version(fdt) < 0x10 && - (poffset + sizeof(*prop)) % 8 && fdt32_to_cpu(prop->len) >= 8) { - return prop->data + 4; - } - return prop->data; + const uint8_t* val; + int len = 0; + + val = (const uint8_t*)fdt_getprop(ctx, nodeoffset, name, &len); + if (val == NULL) { + return NULL; + } + if (len == 8) { + return (void*)(uintptr_t)fdt_rd64u(val); + } + if (len == 4) { + return (void*)(uintptr_t)fdt_rd32(val); } return NULL; } -void* fdt_getprop_address(const void *fdt, int nodeoffset, const char *name) +/* Does the node at `off` carry this name? A search term with no unit + * address ("serial") also matches a node that has one ("serial@21c0500"), + * which is how devicetree names are conventionally written. */ +static int fdt_name_eq(const fdt_ctx* ctx, int off, const char* name, + uint32_t namelen) { - void* ret = NULL; - int len = 0; - void* val = (void*)fdt_getprop(fdt, nodeoffset, name, &len); - if (val != NULL && len > 0) { - if (len == 8) { - uint64_t* val64 = (uint64_t*)val; - ret = (void*)((uintptr_t)fdt64_to_cpu(*val64)); - } - else if (len == 4) { - uint32_t* val32 = (uint32_t*)val; - ret = (void*)((uintptr_t)fdt32_to_cpu(*val32)); - } + const char* p = (const char*)(ctx->blob + ctx->off_struct + (uint32_t)off + + FDT_TAGSIZE); + /* Length first keeps the memcmp inside the name: `namelen` is the + * caller's and can exceed anything in the tree (a FIT `default` + * string, say). The name itself is NUL-terminated per fdt_open(). */ + uint32_t plen = (uint32_t)strlen(p); + + if (plen < namelen || memcmp(p, name, (size_t)namelen) != 0) { + return 0; + } + if (p[namelen] == '\0') { + return 1; } - return ret; + if (p[namelen] == '@' && memchr(name, '@', (size_t)namelen) == NULL) { + return 1; + } + return 0; } -int fdt_find_node_offset(void* fdt, int startoff, const char* nodename) +/* Direct child of `parentoff` by name, via fdt_next_node()'s depth + * tracking: from the parent at depth 0 the direct children are the nodes + * at depth 1, and its closing token ends the walk. */ +static int fdt_subnode_find(const fdt_ctx* ctx, int parentoff, + const char* name, uint32_t namelen) { - int off, nlen, fnlen; - const char* nstr = NULL; + int off, depth = 0; - if (nodename == NULL) - return -1; - - fnlen = (int)strlen(nodename); - for (off = fdt_next_node(fdt, startoff, NULL); + for (off = fdt_next_node(ctx, parentoff, &depth); off >= 0; - off = fdt_next_node(fdt, off, NULL)) + off = fdt_next_node(ctx, off, &depth)) { - nstr = fdt_get_name(fdt, off, &nlen); - if ((nlen == fnlen) && (memcmp(nstr, nodename, fnlen) == 0)) { - break; + if (depth == 1 && fdt_name_eq(ctx, off, name, namelen)) { + return off; + } + } + return -FDT_ERR_NOTFOUND; +} + +int fdt_subnode_offset(const fdt_ctx* ctx, int parentoff, const char* name) +{ + if (!fdt_ctx_ok(ctx) || name == NULL) { + return -FDT_ERR_BADARG; + } + return fdt_subnode_find(ctx, parentoff, name, (uint32_t)strlen(name)); +} + +int fdt_path_offset(const fdt_ctx* ctx, const char* path) +{ + const char* p; + int off = 0; /* the root node is always at offset 0 */ + + if (!fdt_ctx_ok(ctx) || path == NULL || *path != '/') { + return -FDT_ERR_BADARG; + } + p = path + 1; + while (*p != '\0') { + const char* seg = p; + uint32_t seglen; + + while (*p != '\0' && *p != '/') { + p++; + } + seglen = (uint32_t)(p - seg); + if (seglen > 0) { + off = fdt_subnode_find(ctx, off, seg, seglen); + if (off < 0) { + return off; + } + } + while (*p == '/') { + p++; } } return off; } -int fdt_find_prop_offset(void* fdt, int startoff, const char* propname, - const char* propval) +/* Shared walk for the tree-wide searches. `propname` NULL matches the + * node name; otherwise the named property must equal `needle` whole. The + * compatible search keeps its own loop so a target that never does one + * does not link the string-list matcher. + * + * Always returns exactly -FDT_ERR_NOTFOUND on no match: HAL callers test + * for that value and would read any other error as a usable offset. */ +static int fdt_seek(const fdt_ctx* ctx, int startoff, const char* propname, + const char* needle) { - int len, off, pvallen; const void* val; + uint32_t nlen; + int off, len; - if (propname == NULL || propval == NULL) - return -1; + if (!fdt_ctx_ok(ctx) || needle == NULL) { + return -FDT_ERR_BADARG; + } + nlen = (uint32_t)strlen(needle); - pvallen = (int)strlen(propval)+1; - for (off = fdt_next_node(fdt, startoff, NULL); + for (off = fdt_next_node(ctx, startoff, NULL); off >= 0; - off = fdt_next_node(fdt, off, NULL)) + off = fdt_next_node(ctx, off, NULL)) { - val = fdt_getprop(fdt, off, propname, &len); - if (val && (len == pvallen) && (memcmp(val, propval, len) == 0)) { - break; + if (propname == NULL) { + /* off came from fdt_next_node(), so it is a checked + * BEGIN_NODE and its name is NUL-terminated in bounds. + * Comparing the length first keeps the memcmp inside it. */ + const char* nm = (const char*)(ctx->blob + ctx->off_struct + + (uint32_t)off + FDT_TAGSIZE); + if (strlen(nm) == nlen && memcmp(nm, needle, (size_t)nlen) == 0) { + return off; + } + continue; + } + val = fdt_getprop(ctx, off, propname, &len); + if (val == NULL || len <= 0) { + continue; + } + if ((uint32_t)len == nlen + 1U + && memcmp(val, needle, (size_t)(nlen + 1U)) == 0) { + return off; } } - return off; + return -FDT_ERR_NOTFOUND; } -int fdt_find_devtype(void* fdt, int startoff, const char* node) +int fdt_find_node_offset(const fdt_ctx* ctx, int startoff, const char* nodename) { - return fdt_find_prop_offset(fdt, startoff, "device_type", node); + return fdt_seek(ctx, startoff, NULL, nodename); } -int fdt_node_offset_by_compatible(const void *fdt, int startoffset, - const char *compatible) +int fdt_find_prop_offset(const fdt_ctx* ctx, int startoff, const char* propname, + const char* propval) { - int offset; - int complen = (int)strlen(compatible); - for (offset = fdt_next_node(fdt, startoffset, NULL); - offset >= 0; - offset = fdt_next_node(fdt, offset, NULL)) - { - int len; - const char *prop = (const char*)fdt_getprop(fdt, offset, "compatible", - &len); - /* property list may contain multiple null terminated strings. - * Locate each entry's NUL terminator within the declared length - * first, then compare: the entry must be exactly as long as the - * wanted string, so no byte is ever read past the property and - * an unterminated trailing entry can neither match nor be - * misread as one. */ - while (prop != NULL && len > 0) { - const char* nextprop; - int entrylen; - - nextprop = memchr(prop, '\0', len); - if (nextprop == NULL) { - /* No NUL terminator within the declared length, break. */ - break; - } - entrylen = (int)(nextprop - prop); - if (entrylen == complen && - memcmp(compatible, prop, complen) == 0) { - return offset; - } - len -= entrylen + 1; - prop = nextprop + 1; + if (propname == NULL) { + return -FDT_ERR_BADARG; + } + return fdt_seek(ctx, startoff, propname, propval); +} + +int fdt_find_devtype(const fdt_ctx* ctx, int startoff, const char* devtype) +{ + return fdt_seek(ctx, startoff, "device_type", devtype); +} + +/* Does a NUL-separated string list of `len` bytes contain `str` as a + * whole entry? An unterminated trailing entry is ignored. Internal so it + * folds into its one caller and drops out of images without one. */ +static int fdt_stringlist_contains(const void* strlist, int len, const char* str) +{ + const char* p = (const char*)strlist; + uint32_t want; + + if (p == NULL || str == NULL || len <= 0) { + return 0; + } + want = (uint32_t)strlen(str); + while (len > 0) { + const char* nul = (const char*)memchr(p, '\0', (size_t)len); + uint32_t entrylen; + + if (nul == NULL) { + /* trailing bytes with no terminator: not a usable entry */ + break; + } + entrylen = (uint32_t)(nul - p); + if (entrylen == want && memcmp(p, str, (size_t)want) == 0) { + return 1; } + len -= (int)entrylen + 1; + p = nul + 1; } - return offset; + return 0; } -int fdt_add_subnode(void* fdt, int parentoff, const char *name) +int fdt_node_offset_by_compatible(const fdt_ctx* ctx, int startoff, + const char* compatible) { - int err; - struct fdt_node_header *nh; - int offset, nextoffset; - int nodelen; - uint32_t tag, *endtag; - int namelen = (int)strlen(name); + const void* val; + int off, len; + + if (!fdt_ctx_ok(ctx) || compatible == NULL) { + return -FDT_ERR_BADARG; + } + for (off = fdt_next_node(ctx, startoff, NULL); + off >= 0; + off = fdt_next_node(ctx, off, NULL)) + { + val = fdt_getprop(ctx, off, "compatible", &len); + if (val != NULL && fdt_stringlist_contains(val, len, compatible)) { + return off; + } + } + return -FDT_ERR_NOTFOUND; /* see fdt_find_node_offset() */ +} - err = fdt_check_header(fdt); - if (err != 0) - return err; - offset = fdt_subnode_offset_namelen(fdt, parentoff, name, namelen); - if (offset >= 0) - return -FDT_ERR_EXISTS; - else if (offset != -FDT_ERR_NOTFOUND) - return offset; - /* Find the node after properties */ - /* skip the first node (BEGIN_NODE) */ - fdt_next_tag(fdt, parentoff, &nextoffset); - do { - offset = nextoffset; - tag = fdt_next_tag(fdt, offset, &nextoffset); - } while ((tag == FDT_PROP) || (tag == FDT_NOP)); +/* ------------------------------------------------------------------ */ +/* Writing */ +/* ------------------------------------------------------------------ */ - nh = (struct fdt_node_header*)fdt_offset_ptr_w_(fdt, offset); - nodelen = sizeof(*nh) + FDT_TAGALIGN(namelen+1) + FDT_TAGSIZE; +/* Replace `oldlen` bytes at absolute blob offset `at` with `newlen` + * bytes, shifting everything up to the end of the tree. Bounded by the + * caller-supplied capacity, never by a value read out of the blob. */ +static int fdt_block_splice(fdt_ctx* ctx, uint32_t at, uint32_t oldlen, + uint32_t newlen) +{ + uint32_t end = fdt_data_end(ctx); + uint32_t tail; - err = fdt_splice_struct_(fdt, nh, 0, nodelen); - if (err == 0) { - nh->tag = cpu_to_fdt32(FDT_BEGIN_NODE); - memset(nh->name, 0, FDT_TAGALIGN(namelen+1)); - memcpy(nh->name, name, namelen); - endtag = (uint32_t*)((char *)nh + nodelen - FDT_TAGSIZE); - *endtag = cpu_to_fdt32(FDT_END_NODE); - err = offset; + if (at > end || oldlen > end - at) { + return -FDT_ERR_BADOFFSET; } - return err; + /* end <= totalsize <= capacity holds for an open context, so the + * subtraction cannot wrap. */ + if (newlen > oldlen && newlen - oldlen > ctx->capacity - end) { + return -FDT_ERR_NOSPACE; + } + tail = end - at - oldlen; + if (tail > 0) { + memmove(ctx->blob + at + newlen, ctx->blob + at + oldlen, + (size_t)tail); + } + return 0; } -int fdt_del_node(void *fdt, int nodeoffset) +int fdt_setprop(fdt_ctx* ctx, int nodeoffset, const char* name, + const void* val, int len) { - int err; - int endoffset; - int depth = 0; + uint32_t padlen, oldpad, stroff = 0, namelen, reclen, at, need; + int poff, oldlen = 0, body, rc, interned; + uint8_t* rec; - err = fdt_check_header(fdt); - if (err != 0) - return err; + if (!fdt_ctx_ok(ctx) || name == NULL) { + return -FDT_ERR_BADARG; + } + if (len < 0 || (val == NULL && len != 0)) { + return -FDT_ERR_BADARG; + } + if ((uint32_t)len > ctx->capacity) { + return -FDT_ERR_NOSPACE; + } + padlen = FDT_TAGALIGN((uint32_t)len); + + poff = fdt_prop_find(ctx, nodeoffset, name, &oldlen); + if (poff >= 0) { + /* resize in place */ + oldpad = FDT_TAGALIGN((uint32_t)oldlen); + at = ctx->off_struct + (uint32_t)poff + 3U * FDT_TAGSIZE; + rc = fdt_block_splice(ctx, at, oldpad, padlen); + if (rc != 0) { + wolfBoot_printf("FDT: set prop %s failed! %d\n", name, rc); + return rc; + } + ctx->size_struct = ctx->size_struct - oldpad + padlen; + ctx->off_strings = ctx->off_strings - oldpad + padlen; + } + else { + if (poff != -FDT_ERR_NOTFOUND) { + return poff; + } + /* Work out where the record goes before touching anything, so a + * bad node offset cannot leave a half-applied change behind. */ + body = fdt_node_body(ctx, nodeoffset); + if (body < 0) { + return body; + } + namelen = (uint32_t)strlen(name) + 1U; + interned = (fdt_strtab_find(ctx, name, &stroff) == 0); + reclen = 3U * FDT_TAGSIZE + padlen; + need = reclen + (interned ? 0U : namelen); + if (need > ctx->capacity - fdt_data_end(ctx)) { + wolfBoot_printf("FDT: no space for prop %s\n", name); + return -FDT_ERR_NOSPACE; + } + /* Space is now guaranteed for both steps, so neither can fail + * partway and leave the tree inconsistent. */ + if (!interned) { + stroff = ctx->size_strings; + memcpy(ctx->blob + ctx->off_strings + ctx->size_strings, name, + (size_t)namelen); + ctx->size_strings += namelen; + } + at = ctx->off_struct + (uint32_t)body; + rc = fdt_block_splice(ctx, at, 0, reclen); + if (rc != 0) { + return -FDT_ERR_INTERNAL; /* pre-checked; cannot happen */ + } + ctx->size_struct += reclen; + ctx->off_strings += reclen; - /* find end of node */ - endoffset = nodeoffset; - while ((endoffset >= 0) && (depth >= 0)) { - endoffset = fdt_next_node(fdt, endoffset, &depth); + rec = ctx->blob + at; + fdt_wr32(rec, FDT_PROP); + fdt_wr32(rec + 2U * FDT_TAGSIZE, stroff); + poff = body; } - if (endoffset < 0) - return endoffset; - return fdt_splice_struct_(fdt, fdt_offset_ptr_w_(fdt, nodeoffset), - endoffset - nodeoffset, 0); + rec = ctx->blob + ctx->off_struct + (uint32_t)poff; + fdt_wr32(rec + FDT_TAGSIZE, (uint32_t)len); + if (len > 0) { + memcpy(rec + 3U * FDT_TAGSIZE, val, (size_t)len); + } + if (padlen > (uint32_t)len) { + memset(rec + 3U * FDT_TAGSIZE + (uint32_t)len, 0, + (size_t)(padlen - (uint32_t)len)); + } + fdt_hdr_sync(ctx); + return 0; } +int fdt_add_subnode(fdt_ctx* ctx, int parentoff, const char* name) +{ + uint32_t namelen, nodelen, at; + uint32_t tag; + int cur, next, rc; + uint8_t* rec; + + if (!fdt_ctx_ok(ctx) || name == NULL) { + return -FDT_ERR_BADARG; + } + namelen = (uint32_t)strlen(name); + rc = fdt_subnode_find(ctx, parentoff, name, namelen); + if (rc >= 0) { + return -FDT_ERR_EXISTS; + } + if (rc != -FDT_ERR_NOTFOUND) { + return rc; + } + + /* new children go after the parent's own properties */ + cur = fdt_node_body(ctx, parentoff); + if (cur < 0) { + return cur; + } + for (;;) { + rc = fdt_tag_walk(ctx, cur, &tag, &next); + if (rc != 0) { + return rc; + } + if (tag != FDT_PROP && tag != FDT_NOP) { + break; + } + cur = next; + } + + nodelen = 2U * FDT_TAGSIZE + FDT_TAGALIGN(namelen + 1U); + at = ctx->off_struct + (uint32_t)cur; + rc = fdt_block_splice(ctx, at, 0, nodelen); + if (rc != 0) { + wolfBoot_printf("FDT: add subnode %s failed! %d\n", name, rc); + return rc; + } + ctx->size_struct += nodelen; + ctx->off_strings += nodelen; + + rec = ctx->blob + at; + fdt_wr32(rec, FDT_BEGIN_NODE); + memset(rec + FDT_TAGSIZE, 0, (size_t)FDT_TAGALIGN(namelen + 1U)); + memcpy(rec + FDT_TAGSIZE, name, (size_t)namelen); + fdt_wr32(rec + nodelen - FDT_TAGSIZE, FDT_END_NODE); + + fdt_hdr_sync(ctx); + return cur; +} -/* adjust the actual total size in the FDT header */ -int fdt_shrink(void* fdt) +int fdt_del_node(fdt_ctx* ctx, int nodeoffset) { - int total_size = fdt_data_size_(fdt); - if (total_size < 0) - return total_size; - return fdt_set_totalsize(fdt, (uint32_t)total_size); + uint32_t span; + int end, rc; + + if (!fdt_ctx_ok(ctx)) { + return -FDT_ERR_BADARG; + } + end = fdt_node_end(ctx, nodeoffset); + if (end < 0) { + return end; + } + span = (uint32_t)end - (uint32_t)nodeoffset; + rc = fdt_block_splice(ctx, ctx->off_struct + (uint32_t)nodeoffset, span, 0); + if (rc != 0) { + return rc; + } + ctx->size_struct -= span; + ctx->off_strings -= span; + fdt_hdr_sync(ctx); + return 0; } -/* Append a /memreserve/ entry. Inserts before the (0,0) terminator and - * shifts the structure block + strings block down by 16 bytes. Caller must - * have already grown totalsize via fdt_set_totalsize() to leave headroom. */ -int fdt_add_mem_rsv(void* fdt, uint64_t address, uint64_t size) +int fdt_add_mem_rsv(fdt_ctx* ctx, uint64_t address, uint64_t size) { - struct fdt_reserve_entry* rsv; - uint8_t* base = (uint8_t*)fdt; - uint32_t off_rsv; - uint32_t off_dt; - uint32_t off_str; - uint32_t size_str; - uint32_t total; - uint64_t data_end; - uint32_t shift; - uint32_t i; + uint32_t shift = (uint32_t)FDT_RSV_ENTRY_SIZE; + uint32_t end, slot; + uint8_t* base; - if (fdt == NULL) { - return -FDT_ERR_BADSTATE; - } - - off_rsv = fdt_off_mem_rsvmap(fdt); - off_dt = fdt_off_dt_struct(fdt); - off_str = fdt_off_dt_strings(fdt); - size_str = fdt_size_dt_strings(fdt); - total = fdt_totalsize(fdt); - /* 64-bit: a wrapped 32-bit end would slip past the checks below - * and re-wrap into the memmove length. */ - data_end = (uint64_t)off_str + (uint64_t)size_str; - shift = (uint32_t)sizeof(struct fdt_reserve_entry); /* 16 */ - - /* Validate the layout before using it: the structure block must - * start after the reserve map's terminator, and the string block - * after the structure block. 64-bit comparisons: a wrapped 32-bit - * sum would pass the check. */ - if (((uint64_t)off_rsv + shift > off_dt) || (off_str < off_dt)) { - return -FDT_ERR_BADSTRUCTURE; + if (!fdt_ctx_ok(ctx)) { + return -FDT_ERR_BADARG; } - if ((data_end + shift) > total) { + base = ctx->blob; + end = fdt_data_end(ctx); + if (shift > ctx->capacity - end) { return -FDT_ERR_NOSPACE; } - /* Find the (0,0) terminator in the reserve map. */ - rsv = (struct fdt_reserve_entry*)(base + off_rsv); - i = 0; - while ((rsv[i].address != 0ULL) || (rsv[i].size != 0ULL)) { - i++; - if (((uint64_t)off_rsv + (uint64_t)(i + 1U) * shift) > off_dt) { + /* Find the (0, 0) terminator; it must lie wholly inside the + * reservation block. Room for the new one comes from the shift. */ + for (slot = ctx->off_rsv;; slot += shift) { + uint32_t j; + + if (slot > ctx->off_struct - shift) { return -FDT_ERR_BADSTRUCTURE; } + for (j = 0; j < shift; j++) { + if (base[slot + j] != 0) { + break; + } + } + if (j == shift) { + break; + } } - /* Shift structure + strings down by 16 bytes. memmove handles overlap. - * The length comes from the validated 64-bit end. */ - memmove(base + off_dt + shift, base + off_dt, - (size_t)(data_end - off_dt)); + /* push the structure and strings blocks down to make room */ + memmove(base + ctx->off_struct + shift, base + ctx->off_struct, + (size_t)(end - ctx->off_struct)); + ctx->off_struct += shift; + ctx->off_strings += shift; - /* Insert new entry where the old terminator was, write new terminator. */ - rsv[i].address = cpu_to_fdt64(address); - rsv[i].size = cpu_to_fdt64(size); - rsv[i + 1].address = 0; - rsv[i + 1].size = 0; + fdt_wr64u(base + slot, address); + fdt_wr64u(base + slot + 8U, size); + fdt_wr64u(base + slot + shift, 0ULL); + fdt_wr64u(base + slot + shift + 8U, 0ULL); - /* Update header offsets. */ - fdt_set_off_dt_struct(fdt, off_dt + shift); - fdt_set_off_dt_strings(fdt, off_str + shift); + fdt_hdr_put(base, FDT_H_OFF_RSVMAP, ctx->off_rsv); + fdt_hdr_sync(ctx); wolfBoot_printf("FDT: /memreserve/ +0x%llx +0x%llx\n", (unsigned long long)address, (unsigned long long)size); return 0; } -/* FTD Fixup API's */ -int fdt_fixup_str(void* fdt, int off, const char* node, const char* name, +/* ------------------------------------------------------------------ */ +/* Fixup helpers */ +/* ------------------------------------------------------------------ */ + +int fdt_fixup_str(fdt_ctx* ctx, int off, const char* node, const char* name, const char* str) { wolfBoot_printf("FDT: Set %s (%d), %s=%s\n", node, off, name, str); - return fdt_setprop(fdt, off, name, str, strlen(str)+1); + return fdt_setprop(ctx, off, name, str, (int)strlen(str) + 1); } -int fdt_fixup_val(void* fdt, int off, const char* node, const char* name, +int fdt_fixup_val(fdt_ctx* ctx, int off, const char* node, const char* name, uint32_t val) { + uint32_t be; + wolfBoot_printf("FDT: Set %s (%d), %s=%u\n", node, off, name, val); - val = cpu_to_fdt32(val); - return fdt_setprop(fdt, off, name, &val, sizeof(val)); + be = cpu_to_fdt32(val); + return fdt_setprop(ctx, off, name, &be, (int)sizeof(be)); } -int fdt_fixup_val64(void* fdt, int off, const char* node, const char* name, +int fdt_fixup_val64(fdt_ctx* ctx, int off, const char* node, const char* name, uint64_t val) { + uint64_t be; + wolfBoot_printf("FDT: Set %s (%d), %s=%llu\n", node, off, name, (unsigned long long)val); - val = cpu_to_fdt64(val); - return fdt_setprop(fdt, off, name, &val, sizeof(val)); + be = cpu_to_fdt64(val); + return fdt_setprop(ctx, off, name, &be, (int)sizeof(be)); +} + +int fdt_fixup_initrd(fdt_ctx* ctx, uint64_t start, uint64_t size) +{ + int off, ret; + + if (!fdt_ctx_ok(ctx)) { + return -FDT_ERR_BADARG; + } + off = fdt_subnode_find(ctx, 0, "chosen", sizeof("chosen") - 1); + if (off == -FDT_ERR_NOTFOUND) { + off = fdt_add_subnode(ctx, 0, "chosen"); + } + if (off < 0) { + return off; + } + ret = fdt_fixup_val64(ctx, off, "chosen", "linux,initrd-start", start); + if (ret < 0) { + return ret; + } + ret = fdt_fixup_val64(ctx, off, "chosen", "linux,initrd-end", + start + size); + if (ret < 0) { + return ret; + } + return 0; } - -/* FIT Specific */ +/* ------------------------------------------------------------------ */ +/* Flattened uImage Tree (FIT) */ +/* ------------------------------------------------------------------ */ /* Returns the property value only when it is a NUL-terminated C string * within its declared length, else NULL: property values are opaque - * byte arrays and the names taken from them are passed to - * fdt_find_node_offset()/strcmp(), which strlen() them. */ -static const char* fit_getprop_string(const void* fdt, int offset, + * byte arrays and the names taken from them are passed to node lookups + * and strcmp(), which strlen() them. */ +static const char* fit_getprop_string(const fdt_ctx* ctx, int offset, const char* name) { int len = 0; - const char* val = (const char*)fdt_getprop(fdt, offset, name, &len); + const char* val = (const char*)fdt_getprop(ctx, offset, name, &len); - if (val == NULL || len <= 0 || memchr(val, '\0', len) == NULL) + if (val == NULL || len <= 0 || memchr(val, '\0', (size_t)len) == NULL) { return NULL; + } return val; } -const char* fit_find_images(void* fdt, const char** pkernel, const char** pflat_dt, - const char** pramdisk, const char** pfpga) +/* Resolve a sub-image node by name. A well-formed FIT keeps them under + * /images, so look there first and do not fall back when that node + * exists - a tree-wide search by bare name would let a node planted + * elsewhere stand in for the real sub-image. Only a FIT with no /images + * node at all falls back to the old behavior. */ +static int fit_image_offset(const fdt_ctx* ctx, const char* image) { - const void* val; - const char *conf = NULL, *kernel = NULL, *flat_dt = NULL, *ramdisk = NULL; - const char *fpga = NULL; + int images; + + if (image == NULL) { + return -FDT_ERR_BADARG; + } + images = fdt_subnode_find(ctx, 0, "images", sizeof("images") - 1); + if (images >= 0) { + return fdt_subnode_find(ctx, images, image, (uint32_t)strlen(image)); + } + return fdt_find_node_offset(ctx, -1, image); +} + +/* Fall back to locating a sub-image by its "type" property. */ +static const char* fit_name_by_type(const fdt_ctx* ctx, const char* type) +{ + const char* name; int off, len = 0; + off = fdt_find_prop_offset(ctx, -1, "type", type); + if (off < 0) { + return NULL; + } + name = fdt_get_name(ctx, off, &len); + if (name == NULL || len <= 0) { + return NULL; + } + return name; +} + +const char* fit_find_images(fdt_ctx* ctx, const char** pkernel, + const char** pflat_dt, const char** pramdisk, const char** pfpga) +{ + const char *conf = NULL, *kernel = NULL, *flat_dt = NULL; + const char *ramdisk = NULL, *fpga = NULL; + int confs, off; + + if (!fdt_ctx_ok(ctx)) { + return NULL; + } + /* Find the configuration to boot (optional). A target may override the * FIT's own `default` with a per-board selection (hal_fit_config_name). */ - off = fdt_find_node_offset(fdt, -1, "configurations"); - if (off > 0) { + confs = fdt_subnode_find(ctx, 0, "configurations", + sizeof("configurations") - 1); + if (confs >= 0) { #ifdef WOLFBOOT_FIT_CONFIG_SELECT conf = hal_fit_config_name(); /* If the target selected a config that is not present in this FIT, * fall back to the default rather than silently mis-selecting * images via the type-based search below. */ - if (conf != NULL && fdt_find_node_offset(fdt, -1, conf) <= 0) { + if (conf != NULL + && fdt_subnode_find(ctx, confs, conf, + (uint32_t)strlen(conf)) < 0) { wolfBoot_printf("FIT: configuration '%s' not found, " "using default\n", conf); conf = NULL; @@ -994,57 +1397,29 @@ const char* fit_find_images(void* fdt, const char** pkernel, const char** pflat_ if (conf == NULL) #endif { - conf = fit_getprop_string(fdt, off, "default"); + conf = fit_getprop_string(ctx, confs, "default"); } } - if (conf != NULL) { - off = fdt_find_node_offset(fdt, -1, conf); - if (off > 0) { - kernel = fit_getprop_string(fdt, off, "kernel"); - flat_dt = fit_getprop_string(fdt, off, "fdt"); - ramdisk = fit_getprop_string(fdt, off, "ramdisk"); - fpga = fit_getprop_string(fdt, off, "fpga"); + if (conf != NULL && confs >= 0) { + off = fdt_subnode_find(ctx, confs, conf, (uint32_t)strlen(conf)); + if (off >= 0) { + kernel = fit_getprop_string(ctx, off, "kernel"); + flat_dt = fit_getprop_string(ctx, off, "fdt"); + ramdisk = fit_getprop_string(ctx, off, "ramdisk"); + fpga = fit_getprop_string(ctx, off, "fpga"); } } if (kernel == NULL) { - /* find node with "type" == kernel */ - off = fdt_find_prop_offset(fdt, -1, "type", "kernel"); - if (off > 0) { - val = fdt_get_name(fdt, off, &len); - if (val != NULL && len > 0) { - kernel = (const char*)val; - } - } + kernel = fit_name_by_type(ctx, "kernel"); } if (flat_dt == NULL) { - /* find node with "type" == flat_dt */ - off = fdt_find_prop_offset(fdt, -1, "type", "flat_dt"); - if (off > 0) { - val = fdt_get_name(fdt, off, &len); - if (val != NULL && len > 0) { - flat_dt = (const char*)val; - } - } + flat_dt = fit_name_by_type(ctx, "flat_dt"); } if (ramdisk == NULL) { - /* find node with "type" == ramdisk */ - off = fdt_find_prop_offset(fdt, -1, "type", "ramdisk"); - if (off > 0) { - val = fdt_get_name(fdt, off, &len); - if (val != NULL && len > 0) { - ramdisk = (const char*)val; - } - } + ramdisk = fit_name_by_type(ctx, "ramdisk"); } if (fpga == NULL) { - /* find node with "type" == fpga */ - off = fdt_find_prop_offset(fdt, -1, "type", "fpga"); - if (off > 0) { - val = fdt_get_name(fdt, off, &len); - if (val != NULL && len > 0) { - fpga = (const char*)val; - } - } + fpga = fit_name_by_type(ctx, "fpga"); } if (pkernel) @@ -1059,59 +1434,25 @@ const char* fit_find_images(void* fdt, const char** pkernel, const char** pflat_ return conf; } -/* Returns a pointer to the first string of the node's "compatible" - * property (a NUL-separated DT string-list), or NULL. See the header for - * the multi-entry caveat. */ -const char* fit_get_compatible(void* fdt, const char* image) +const char* fit_get_compatible(fdt_ctx* ctx, const char* image) { const char* val; int off, len = 0; - if (image == NULL) { + if (!fdt_ctx_ok(ctx) || image == NULL) { return NULL; } - off = fdt_find_node_offset(fdt, -1, image); - if (off <= 0) { + off = fit_image_offset(ctx, image); + if (off < 0) { return NULL; } - val = (const char*)fdt_getprop(fdt, off, "compatible", &len); + val = (const char*)fdt_getprop(ctx, off, "compatible", &len); if (val != NULL && len > 0) { return val; } return NULL; } -int fdt_fixup_initrd(void* fdt, uint64_t start, uint64_t size) -{ - int off, ret; - uint64_t end; - - if (fdt == NULL) { - return -1; - } - - end = start + size; - - off = fdt_find_node_offset(fdt, -1, "chosen"); - if (off == -FDT_ERR_NOTFOUND) { - off = fdt_add_subnode(fdt, 0, "chosen"); - } - if (off < 0) { - return off; - } - - ret = fdt_fixup_val64(fdt, off, "chosen", "linux,initrd-start", start); - if (ret < 0) { - return ret; - } - ret = fdt_fixup_val64(fdt, off, "chosen", "linux,initrd-end", end); - if (ret < 0) { - return ret; - } - - return 0; -} - #ifdef WOLFBOOT_FIT_RAMDISK /* Defensive fallback: targets without a fixed relocation address * leave WOLFBOOT_LOAD_RAMDISK_ADDRESS at 0, in which case the @@ -1135,47 +1476,45 @@ int fdt_fixup_initrd(void* fdt, uint64_t start, uint64_t size) * straight into the override (with the override capacity acting as * the safety bound). Otherwise the address fit_load_image returned * (FIT-specified or in-FIT pointer) is used as-is. Caller passes the - * DTB pointer for the initrd fixup, or NULL to skip the fixup. + * DTB context for the initrd fixup, or NULL to skip the fixup. * * Returns 0 on success, -1 if the ramdisk node was found but the * load failed. The current callers ignore the return value * (log-and-continue), so a missing/failed ramdisk does not abort * the boot. */ -int fit_load_ramdisk(void* fit, const char* ramdisk_node, void* dts_addr) +int fit_load_ramdisk(fdt_ctx* ctx, const char* ramdisk_node, fdt_ctx* dts) { int rd_size = 0; - uint8_t *rd_ptr; - uint8_t *rd_dst; + uint8_t* rd_ptr; + uint8_t* rd_dst; - if (fit == NULL || ramdisk_node == NULL) { + if (!fdt_ctx_ok(ctx) || ramdisk_node == NULL) { return -1; } if (WOLFBOOT_LOAD_RAMDISK_ADDRESS != 0) { rd_dst = (uint8_t*)WOLFBOOT_LOAD_RAMDISK_ADDRESS; - rd_ptr = (uint8_t*)fit_load_image_to(fit, ramdisk_node, + rd_ptr = (uint8_t*)fit_load_image_to(ctx, ramdisk_node, rd_dst, (uint32_t)WOLFBOOT_FIT_MAX_RAMDISK, &rd_size); if (rd_ptr == NULL || rd_size <= 0) { wolfBoot_printf("FIT: ramdisk node present but load failed\n"); return -1; } - wolfBoot_printf("Loaded ramdisk: %p (%d bytes)\n", - rd_dst, rd_size); + wolfBoot_printf("Loaded ramdisk: %p (%d bytes)\n", rd_dst, rd_size); } else { - rd_ptr = (uint8_t*)fit_load_image(fit, ramdisk_node, &rd_size); + rd_ptr = (uint8_t*)fit_load_image(ctx, ramdisk_node, &rd_size); if (rd_ptr == NULL || rd_size <= 0) { wolfBoot_printf("FIT: ramdisk node present but load failed\n"); return -1; } rd_dst = rd_ptr; - wolfBoot_printf("Loaded ramdisk: %p (%d bytes)\n", - rd_dst, rd_size); + wolfBoot_printf("Loaded ramdisk: %p (%d bytes)\n", rd_dst, rd_size); } - if (dts_addr != NULL) { - int frc = fdt_fixup_initrd(dts_addr, - (uint64_t)(uintptr_t)rd_dst, (uint64_t)rd_size); + if (dts != NULL) { + int frc = fdt_fixup_initrd(dts, (uint64_t)(uintptr_t)rd_dst, + (uint64_t)rd_size); if (frc != 0) { wolfBoot_printf("FIT: fdt_fixup_initrd failed (rc=%d); " "kernel will not see initrd\n", frc); @@ -1208,7 +1547,7 @@ int __attribute__((weak)) wolfBoot_fit_memcpy(void *dst, const void *src, * also ignored when dst_override is in effect, since the caller wants * the override address back. */ -static void* fit_load_image_inner(void* fdt, const char* image, int* lenp, +static void* fit_load_image_inner(fdt_ctx* ctx, const char* image, int* lenp, uint32_t out_max, void* dst_override) { void *load, *entry, *data = NULL; @@ -1219,12 +1558,19 @@ static void* fit_load_image_inner(void* fdt, const char* image, int* lenp, BENCHMARK_DECLARE(); #endif - off = fdt_find_node_offset(fdt, -1, image); - if (off > 0) { + if (!fdt_ctx_ok(ctx)) { + if (lenp != NULL) { + *lenp = 0; + } + return NULL; + } + + off = fit_image_offset(ctx, image); + if (off >= 0) { /* get load and entry */ - data = (void*)fdt_getprop(fdt, off, "data", &len); - load = fdt_getprop_address(fdt, off, "load"); - entry = fdt_getprop_address(fdt, off, "entry"); + data = (void*)fdt_getprop(ctx, off, "data", &len); + load = fdt_getprop_address(ctx, off, "load"); + entry = fdt_getprop_address(ctx, off, "entry"); if (dst_override != NULL) { /* Caller-supplied destination replaces the FIT load * property and disables `entry` resolution. */ @@ -1240,7 +1586,7 @@ static void* fit_load_image_inner(void* fdt, const char* image, int* lenp, * is unknown, or when there is no place to decompress to - * instead of silently passing compressed bytes through as * raw. */ - comp = (const char*)fdt_getprop(fdt, off, "compression", + comp = (const char*)fdt_getprop(ctx, off, "compression", &complen); /* Compare within the declared property length: the value * must be exactly "gzip" or "none" (NUL-terminated). Any @@ -1348,37 +1694,37 @@ static void* fit_load_image_inner(void* fdt, const char* image, int* lenp, *lenp = len; } return data; - } -void* fit_load_image_ex(void* fdt, const char* image, int* lenp, +void* fit_load_image_ex(fdt_ctx* ctx, const char* image, int* lenp, uint32_t out_max) { - return fit_load_image_inner(fdt, image, lenp, out_max, NULL); + return fit_load_image_inner(ctx, image, lenp, out_max, NULL); } -void* fit_load_image(void* fdt, const char* image, int* lenp) +void* fit_load_image(fdt_ctx* ctx, const char* image, int* lenp) { - return fit_load_image_ex(fdt, image, lenp, WOLFBOOT_FIT_MAX_DECOMP); + return fit_load_image_ex(ctx, image, lenp, WOLFBOOT_FIT_MAX_DECOMP); } -void* fit_load_image_to(void* fdt, const char* image, void* dst, +void* fit_load_image_to(fdt_ctx* ctx, const char* image, void* dst, uint32_t dst_max, int* lenp) { if (dst == NULL) { return NULL; } - return fit_load_image_inner(fdt, image, lenp, dst_max, dst); + return fit_load_image_inner(ctx, image, lenp, dst_max, dst); } #ifdef WOLFBOOT_FPGA_BITSTREAM -/* Minimal length-bounded substring search (strstr is not provided by - * wolfBoot's freestanding string.c). Searches the first hlen bytes of - * haystack for needle. hlen is an explicit length so this works over a - * DT "compatible" property, which is a list of NUL-separated strings: - * a needle with no embedded NUL (e.g. "partial") matches within any one - * entry, and a NUL separator can never be part of the match. Returns 1 - * if found. */ +/* Length-bounded substring search (strstr is not provided by wolfBoot's + * freestanding string.c). Searches the first hlen bytes of haystack for + * needle. hlen is an explicit length so this works over a DT "compatible" + * property, which is a list of NUL-separated strings: a needle with no + * embedded NUL (e.g. "partial") matches within any one entry - which is + * what is wanted here, since the entry is typically a vendor-prefixed + * string such as "xlnx,fpga-partial" - and a NUL separator can never be + * part of the match. Returns 1 if found. */ static int fit_str_contains(const char* haystack, int hlen, const char* needle) { int nlen, i; @@ -1391,7 +1737,7 @@ static int fit_str_contains(const char* haystack, int hlen, const char* needle) return 0; } for (i = 0; i + nlen <= hlen; i++) { - if (strncmp(haystack + i, needle, (size_t)nlen) == 0) { + if (memcmp(haystack + i, needle, (size_t)nlen) == 0) { return 1; } } @@ -1405,7 +1751,7 @@ static int fit_str_contains(const char* haystack, int hlen, const char* needle) #define WOLFBOOT_FIT_MAX_FPGA WOLFBOOT_FIT_MAX_DECOMP #endif -int fit_load_fpga(void* fdt, const char* fpga_node) +int fit_load_fpga(fdt_ctx* ctx, const char* fpga_node) { void* data; const char* comp; @@ -1429,11 +1775,11 @@ int fit_load_fpga(void* fdt, const char* fpga_node) * is 0 we honor the FIT's own `load` property instead (fit_load_image * fails closed for a compressed sub-image that has no destination). */ #if defined(WOLFBOOT_LOAD_FPGA_ADDRESS) && (WOLFBOOT_LOAD_FPGA_ADDRESS != 0) - data = fit_load_image_to(fdt, fpga_node, + data = fit_load_image_to(ctx, fpga_node, (void*)(uintptr_t)(WOLFBOOT_LOAD_FPGA_ADDRESS), WOLFBOOT_FIT_MAX_FPGA, &len); #else - data = fit_load_image(fdt, fpga_node, &len); + data = fit_load_image(ctx, fpga_node, &len); #endif if (data == NULL || len <= 0) { wolfBoot_printf("FIT: failed to load fpga '%s'\n", fpga_node); @@ -1445,9 +1791,9 @@ int fit_load_fpga(void* fdt, const char* fpga_node) * string list (one or more NUL-separated entries), so scan the whole * property rather than only its first string. Default is full. */ comp = NULL; - coff = fdt_find_node_offset(fdt, -1, fpga_node); - if (coff > 0) { - comp = (const char*)fdt_getprop(fdt, coff, "compatible", &clen); + coff = fit_image_offset(ctx, fpga_node); + if (coff >= 0) { + comp = (const char*)fdt_getprop(ctx, coff, "compatible", &clen); } if (fit_str_contains(comp, clen, "partial")) { flags = HAL_FPGA_PARTIAL; @@ -1472,4 +1818,542 @@ int fit_load_fpga(void* fdt, const char* fpga_node) } #endif /* WOLFBOOT_FPGA_BITSTREAM */ + +/* ------------------------------------------------------------------ */ +/* Malformed-blob corpus (WOLFBOOT_FDT_CORPUS) */ +/* ------------------------------------------------------------------ */ + +/* Test-only. Derives deliberately-invalid blobs from a known-good one so + * the parser can be shown to reject each rather than read out of bounds. + * Driven by "fdt-parser -f"; never compiled into firmware. Deliberately + * writes headers with its own byte-wise helpers so a bug in the parser's + * accessors cannot mask a bug in the parser. */ +#ifdef WOLFBOOT_FDT_CORPUS + +#include + +static uint32_t corpus_rd32(const uint8_t* p) +{ + return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) + | ((uint32_t)p[2] << 8) | (uint32_t)p[3]; +} + +static void corpus_wr32(uint8_t* p, uint32_t v) +{ + p[0] = (uint8_t)(v >> 24); + p[1] = (uint8_t)(v >> 16); + p[2] = (uint8_t)(v >> 8); + p[3] = (uint8_t)v; +} + +static uint32_t corpus_hdr_get(const uint8_t* b, uint32_t field) +{ + return corpus_rd32(b + field); +} + +static void corpus_hdr_set(uint8_t* b, uint32_t field, uint32_t v) +{ + corpus_wr32(b + field, v); +} + +/* Locate the offset (absolute, within the blob) of the first tag of the + * given kind inside the structure block. Returns 0 if not found. The + * walk is intentionally naive - it is scanning a known-good blob. */ +static uint32_t corpus_find_tag(const uint8_t* b, uint32_t len, uint32_t want, + int skip) +{ + uint32_t off = corpus_hdr_get(b, FDT_H_OFF_STRUCT); + uint32_t end = off + corpus_hdr_get(b, FDT_H_SIZE_STRUCT); + uint32_t tag; + + if (end > len) + end = len; + + while (off + 4 <= end) { + tag = corpus_rd32(b + off); + if (tag == want) { + if (skip == 0) + return off; + skip--; + } + switch (tag) { + case FDT_BEGIN_NODE: + off += 4; + while (off < end && b[off] != '\0') + off++; + off = (off + 4) & ~3U; /* past the NUL, realigned */ + break; + case FDT_PROP: + if (off + 12 > end) + return 0; + off += 12 + ((corpus_rd32(b + off + 4) + 3U) & ~3U); + break; + case FDT_END: + return 0; + default: + off += 4; + break; + } + } + return 0; +} + +/* ---- mutators ------------------------------------------------------ */ +/* Each receives a private copy of the good blob and may shrink *len. + * The buffer is allocated at exactly the original length, so growing is + * not permitted. */ + +static void m_magic_bad(uint8_t* b, uint32_t* len) +{ + (void)len; + corpus_hdr_set(b, FDT_H_MAGIC, 0xDEADBEEFU); +} + +static void m_magic_off_by_one(uint8_t* b, uint32_t* len) +{ + (void)len; + corpus_hdr_set(b, FDT_H_MAGIC, (uint32_t)FDT_MAGIC + 1U); +} + +static void m_version_zero(uint8_t* b, uint32_t* len) +{ + (void)len; + corpus_hdr_set(b, FDT_H_VERSION, 0); +} + +static void m_version_16(uint8_t* b, uint32_t* len) +{ + (void)len; + corpus_hdr_set(b, FDT_H_VERSION, 16); +} + +static void m_lastcomp_future(uint8_t* b, uint32_t* len) +{ + (void)len; + corpus_hdr_set(b, FDT_H_LAST_COMP, 0x20); +} + +static void m_totalsize_huge(uint8_t* b, uint32_t* len) +{ + (void)len; + corpus_hdr_set(b, FDT_H_TOTALSIZE, 0x10000000U); +} + +static void m_totalsize_max(uint8_t* b, uint32_t* len) +{ + (void)len; + corpus_hdr_set(b, FDT_H_TOTALSIZE, 0xFFFFFFFFU); +} + +static void m_totalsize_tiny(uint8_t* b, uint32_t* len) +{ + (void)len; + corpus_hdr_set(b, FDT_H_TOTALSIZE, 8); +} + +static void m_totalsize_zero(uint8_t* b, uint32_t* len) +{ + (void)len; + corpus_hdr_set(b, FDT_H_TOTALSIZE, 0); +} + +static void m_off_struct_past_end(uint8_t* b, uint32_t* len) +{ + corpus_hdr_set(b, FDT_H_OFF_STRUCT, *len + 0x1000U); +} + +static void m_off_strings_past_end(uint8_t* b, uint32_t* len) +{ + corpus_hdr_set(b, FDT_H_OFF_STRINGS, *len + 0x1000U); +} + +static void m_blocks_swapped(uint8_t* b, uint32_t* len) +{ + uint32_t s = corpus_hdr_get(b, FDT_H_OFF_STRUCT); + (void)len; + corpus_hdr_set(b, FDT_H_OFF_STRUCT, corpus_hdr_get(b, FDT_H_OFF_STRINGS)); + corpus_hdr_set(b, FDT_H_OFF_STRINGS, s); +} + +static void m_blocks_overlap(uint8_t* b, uint32_t* len) +{ + (void)len; + /* Pull the string block back into the middle of the struct block. */ + corpus_hdr_set(b, FDT_H_OFF_STRINGS, + corpus_hdr_get(b, FDT_H_OFF_STRUCT) + (corpus_hdr_get(b, FDT_H_SIZE_STRUCT) / 2U)); +} + +static void m_size_struct_huge(uint8_t* b, uint32_t* len) +{ + (void)len; + corpus_hdr_set(b, FDT_H_SIZE_STRUCT, 0x0FFFFFFFU); +} + +static void m_size_strings_huge(uint8_t* b, uint32_t* len) +{ + (void)len; + corpus_hdr_set(b, FDT_H_SIZE_STRINGS, 0x0FFFFFFFU); +} + +static void m_size_struct_wrap(uint8_t* b, uint32_t* len) +{ + (void)len; + /* off + size wraps 32 bits. */ + corpus_hdr_set(b, FDT_H_SIZE_STRUCT, 0xFFFFFFF0U); +} + +static void m_off_rsv_unaligned(uint8_t* b, uint32_t* len) +{ + (void)len; + corpus_hdr_set(b, FDT_H_OFF_RSVMAP, FDT_HEADER_SIZE + 1U); +} + +static void m_off_rsv_in_header(uint8_t* b, uint32_t* len) +{ + (void)len; + corpus_hdr_set(b, FDT_H_OFF_RSVMAP, 4); +} + +static void m_off_rsv_past_struct(uint8_t* b, uint32_t* len) +{ + (void)len; + corpus_hdr_set(b, FDT_H_OFF_RSVMAP, corpus_hdr_get(b, FDT_H_OFF_STRUCT) + 0x100U); +} + +static void m_off_struct_unaligned(uint8_t* b, uint32_t* len) +{ + (void)len; + corpus_hdr_set(b, FDT_H_OFF_STRUCT, corpus_hdr_get(b, FDT_H_OFF_STRUCT) + 1U); +} + +static void m_rsv_no_terminator(uint8_t* b, uint32_t* len) +{ + uint32_t off = corpus_hdr_get(b, FDT_H_OFF_RSVMAP); + uint32_t end = corpus_hdr_get(b, FDT_H_OFF_STRUCT); + if (end > *len) + end = *len; + while (off < end) + b[off++] = 0xAA; +} + +static void m_truncated_half(uint8_t* b, uint32_t* len) +{ + (void)b; + /* Keep the header's totalsize, but hand the parser half the bytes. */ + *len = *len / 2U; +} + +static void m_truncated_header(uint8_t* b, uint32_t* len) +{ + (void)b; + *len = FDT_HEADER_SIZE - 4U; +} + +static void m_struct_no_end(uint8_t* b, uint32_t* len) +{ + uint32_t off = corpus_find_tag(b, *len, FDT_END, 0); + if (off != 0) + corpus_wr32(b + off, FDT_NOP); +} + +static void m_struct_bad_tag(uint8_t* b, uint32_t* len) +{ + uint32_t off = corpus_hdr_get(b, FDT_H_OFF_STRUCT); + if (off + 4 <= *len) + corpus_wr32(b + off, 0x42424242U); +} + +static void m_unbalanced_end_node(uint8_t* b, uint32_t* len) +{ + /* Turn the second node's opening tag into a close, unbalancing the + * nesting for the rest of the stream. */ + uint32_t off = corpus_find_tag(b, *len, FDT_BEGIN_NODE, 1); + if (off != 0) + corpus_wr32(b + off, FDT_END_NODE); +} + +static void m_prop_len_max(uint8_t* b, uint32_t* len) +{ + uint32_t off = corpus_find_tag(b, *len, FDT_PROP, 0); + if (off != 0) + corpus_wr32(b + off + 4, 0xFFFFFFFFU); +} + +static void m_prop_len_totalsize(uint8_t* b, uint32_t* len) +{ + uint32_t off = corpus_find_tag(b, *len, FDT_PROP, 0); + if (off != 0) + corpus_wr32(b + off + 4, corpus_hdr_get(b, FDT_H_TOTALSIZE)); +} + +static void m_prop_len_signed(uint8_t* b, uint32_t* len) +{ + /* Lands negative when the parser narrows the length to int. */ + uint32_t off = corpus_find_tag(b, *len, FDT_PROP, 0); + if (off != 0) + corpus_wr32(b + off + 4, 0x80000004U); +} + +static void m_prop_nameoff_past(uint8_t* b, uint32_t* len) +{ + uint32_t off = corpus_find_tag(b, *len, FDT_PROP, 0); + if (off != 0) + corpus_wr32(b + off + 8, corpus_hdr_get(b, FDT_H_SIZE_STRINGS) + 0x1000U); +} + +static void m_prop_nameoff_last(uint8_t* b, uint32_t* len) +{ + /* Exactly one past the end of the string block. */ + uint32_t off = corpus_find_tag(b, *len, FDT_PROP, 0); + if (off != 0) + corpus_wr32(b + off + 8, corpus_hdr_get(b, FDT_H_SIZE_STRINGS)); +} + +static void m_node_name_unterminated(uint8_t* b, uint32_t* len) +{ + /* Fill from the second node's name to the end of the struct block + * with non-NUL bytes so the name never terminates. */ + uint32_t off = corpus_find_tag(b, *len, FDT_BEGIN_NODE, 1); + uint32_t end = corpus_hdr_get(b, FDT_H_OFF_STRUCT) + corpus_hdr_get(b, FDT_H_SIZE_STRUCT); + if (off == 0) + return; + if (end > *len) + end = *len; + for (off += 4; off < end; off++) + b[off] = 'A'; +} + +static void m_strings_unterminated(uint8_t* b, uint32_t* len) +{ + uint32_t off = corpus_hdr_get(b, FDT_H_OFF_STRINGS); + uint32_t sz = corpus_hdr_get(b, FDT_H_SIZE_STRINGS); + if (sz == 0 || off + sz > *len) + return; + b[off + sz - 1U] = 'A'; +} + +static void m_prop_before_node(uint8_t* b, uint32_t* len) +{ + /* A property tag at depth 0 - properties must belong to a node. */ + uint32_t off = corpus_hdr_get(b, FDT_H_OFF_STRUCT); + if (off + 4 <= *len) + corpus_wr32(b + off, FDT_PROP); +} + +/* ---- synthesized blobs --------------------------------------------- */ + +/* Build a minimal blob whose structure block is supplied by the caller. + * The string block is a single NUL so any nameoff of 0 resolves. */ +static uint8_t* corpus_synth(const uint8_t* structblk, uint32_t structlen, + uint32_t* outlen) +{ + uint32_t off_rsv = FDT_HEADER_SIZE; + uint32_t off_struct = off_rsv + 16U; /* one (0,0) terminator entry */ + uint32_t off_strings = off_struct + structlen; + uint32_t total = off_strings + 1U; + uint8_t* b; + + total = (total + 3U) & ~3U; + b = calloc(1, total); + if (b == NULL) + return NULL; + + corpus_hdr_set(b, FDT_H_MAGIC, (uint32_t)FDT_MAGIC); + corpus_hdr_set(b, FDT_H_TOTALSIZE, total); + corpus_hdr_set(b, FDT_H_OFF_STRUCT, off_struct); + corpus_hdr_set(b, FDT_H_OFF_STRINGS, off_strings); + corpus_hdr_set(b, FDT_H_OFF_RSVMAP, off_rsv); + corpus_hdr_set(b, FDT_H_VERSION, 17); + corpus_hdr_set(b, FDT_H_LAST_COMP, 16); + corpus_hdr_set(b, FDT_H_SIZE_STRINGS, 1); + corpus_hdr_set(b, FDT_H_SIZE_STRUCT, structlen); + memcpy(b + off_struct, structblk, structlen); + /* string block is the trailing NUL already zeroed by calloc */ + + *outlen = total; + return b; +} + +/* Two sibling root nodes - the spec allows exactly one. */ +static uint8_t* s_two_roots(uint32_t* len) +{ + uint8_t sb[7 * 4]; + uint32_t i = 0; + corpus_wr32(sb + i, FDT_BEGIN_NODE); i += 4; + corpus_wr32(sb + i, 0); i += 4; /* empty name */ + corpus_wr32(sb + i, FDT_END_NODE); i += 4; + corpus_wr32(sb + i, FDT_BEGIN_NODE); i += 4; + corpus_wr32(sb + i, 0); i += 4; /* second root */ + corpus_wr32(sb + i, FDT_END_NODE); i += 4; + corpus_wr32(sb + i, FDT_END); i += 4; + return corpus_synth(sb, i, len); +} + +/* Deeply nested nodes - unbounded recursion or stack growth in a + * consumer would show up here. */ +static uint8_t* s_deep_nesting(uint32_t* len) +{ + const uint32_t depth = 10000; + uint32_t sz = (depth * 3U + 1U) * 4U; + uint8_t* sb = calloc(1, sz); + uint8_t* out; + uint32_t i = 0, d; + + if (sb == NULL) + return NULL; + for (d = 0; d < depth; d++) { + corpus_wr32(sb + i, FDT_BEGIN_NODE); i += 4; + corpus_wr32(sb + i, 0); i += 4; + } + for (d = 0; d < depth; d++) { + corpus_wr32(sb + i, FDT_END_NODE); i += 4; + } + corpus_wr32(sb + i, FDT_END); i += 4; + + out = corpus_synth(sb, i, len); + free(sb); + return out; +} + +/* Structure block that is nothing but an END tag - no root node. */ +static uint8_t* s_no_root(uint32_t* len) +{ + uint8_t sb[4]; + corpus_wr32(sb, FDT_END); + return corpus_synth(sb, sizeof(sb), len); +} + +/* Root node opened and never closed. */ +static uint8_t* s_unclosed_root(uint32_t* len) +{ + uint8_t sb[3 * 4]; + uint32_t i = 0; + corpus_wr32(sb + i, FDT_BEGIN_NODE); i += 4; + corpus_wr32(sb + i, 0); i += 4; + corpus_wr32(sb + i, FDT_END); i += 4; + return corpus_synth(sb, i, len); +} + +/* Empty structure block. */ +static uint8_t* s_empty_struct(uint32_t* len) +{ + static const uint8_t none[1] = { 0 }; + return corpus_synth(none, 0, len); +} + +/* ---- case table ---------------------------------------------------- */ + +typedef void (*mutate_fn)(uint8_t* b, uint32_t* len); +typedef uint8_t* (*synth_fn)(uint32_t* len); + +struct corpus_case { + const char* name; + const char* what; + mutate_fn mutate; + synth_fn build; +}; + +static const struct corpus_case CASES[] = { + { "magic_bad", "magic replaced with 0xDEADBEEF", m_magic_bad, NULL }, + { "magic_off_by_one", "magic + 1", m_magic_off_by_one, NULL }, + { "version_zero", "version = 0", m_version_zero, NULL }, + { "version_16", "version = 16 (pre-v17)", m_version_16, NULL }, + { "lastcomp_future", "last_comp_version = 0x20", m_lastcomp_future, NULL }, + { "totalsize_huge", "totalsize 256 MiB, buffer is not", m_totalsize_huge, NULL }, + { "totalsize_max", "totalsize = 0xFFFFFFFF", m_totalsize_max, NULL }, + { "totalsize_tiny", "totalsize = 8 (below header size)", m_totalsize_tiny, NULL }, + { "totalsize_zero", "totalsize = 0", m_totalsize_zero, NULL }, + { "off_struct_past_end", "off_dt_struct past the buffer", m_off_struct_past_end, NULL }, + { "off_strings_past_end", "off_dt_strings past the buffer", m_off_strings_past_end, NULL }, + { "blocks_swapped", "struct and strings offsets swapped", m_blocks_swapped, NULL }, + { "blocks_overlap", "strings block starts inside struct", m_blocks_overlap, NULL }, + { "size_struct_huge", "size_dt_struct = 256 MiB", m_size_struct_huge, NULL }, + { "size_strings_huge", "size_dt_strings = 256 MiB", m_size_strings_huge, NULL }, + { "size_struct_wrap", "off + size_dt_struct wraps 32 bits", m_size_struct_wrap, NULL }, + { "off_rsv_unaligned", "off_mem_rsvmap not 8-byte aligned", m_off_rsv_unaligned, NULL }, + { "off_rsv_in_header", "off_mem_rsvmap points into header", m_off_rsv_in_header, NULL }, + { "off_rsv_past_struct", "off_mem_rsvmap past off_dt_struct", m_off_rsv_past_struct, NULL }, + { "off_struct_unaligned", "off_dt_struct not 4-byte aligned", m_off_struct_unaligned, NULL }, + { "rsv_no_terminator", "reserve map has no (0,0) entry", m_rsv_no_terminator, NULL }, + { "truncated_half", "buffer is half the declared size", m_truncated_half, NULL }, + { "truncated_header", "buffer shorter than the header", m_truncated_header, NULL }, + { "struct_no_end", "FDT_END replaced with FDT_NOP", m_struct_no_end, NULL }, + { "struct_bad_tag", "unknown tag 0x42424242", m_struct_bad_tag, NULL }, + { "unbalanced_end_node", "extra FDT_END_NODE, nesting broken", m_unbalanced_end_node, NULL }, + { "prop_len_max", "property len = 0xFFFFFFFF", m_prop_len_max, NULL }, + { "prop_len_totalsize", "property len = totalsize", m_prop_len_totalsize, NULL }, + { "prop_len_signed", "property len negative as int", m_prop_len_signed, NULL }, + { "prop_nameoff_past", "property nameoff past string block", m_prop_nameoff_past, NULL }, + { "prop_nameoff_last", "property nameoff one past the end", m_prop_nameoff_last, NULL }, + { "node_name_unterm", "node name never NUL-terminated", m_node_name_unterminated, NULL }, + { "strings_unterm", "string block does not end in NUL", m_strings_unterminated, NULL }, + { "prop_before_node", "FDT_PROP at depth 0", m_prop_before_node, NULL }, + { "two_roots", "two sibling root nodes", NULL, s_two_roots }, + { "deep_nesting", "10000 levels of nesting", NULL, s_deep_nesting }, + { "no_root", "structure block is only FDT_END", NULL, s_no_root }, + { "unclosed_root", "root node never closed", NULL, s_unclosed_root }, + { "empty_struct", "size_dt_struct = 0", NULL, s_empty_struct }, +}; + +int fdt_corpus_count(void) +{ + return (int)(sizeof(CASES) / sizeof(CASES[0])); +} + +const char* fdt_corpus_name(int idx) +{ + if (idx < 0 || idx >= fdt_corpus_count()) + return NULL; + return CASES[idx].name; +} + +const char* fdt_corpus_desc(int idx) +{ + if (idx < 0 || idx >= fdt_corpus_count()) + return NULL; + return CASES[idx].what; +} + +uint8_t* fdt_corpus_build(int idx, const uint8_t* base, uint32_t baselen, + uint32_t* outlen) +{ + uint8_t* buf; + uint32_t len; + + if (idx < 0 || idx >= fdt_corpus_count()) + return NULL; + + if (CASES[idx].build != NULL) + return CASES[idx].build(outlen); + + if (base == NULL || baselen < FDT_HEADER_SIZE) + return NULL; + + /* Mutate a scratch copy first so a shrinking mutator can be honored + * by handing back a buffer allocated at exactly the final length - + * that is what lets ASan catch a read past the end. */ + buf = malloc(baselen); + if (buf == NULL) + return NULL; + memcpy(buf, base, baselen); + len = baselen; + CASES[idx].mutate(buf, &len); + + if (len < baselen) { + uint8_t* tight = malloc(len); + if (tight == NULL) { + free(buf); + return NULL; + } + memcpy(tight, buf, len); + free(buf); + buf = tight; + } + + *outlen = len; + return buf; +} + +#endif /* WOLFBOOT_FDT_CORPUS */ + #endif /* (MMU || WOLFBOOT_FDT) && !BUILD_LOADER_STAGE1 */ diff --git a/src/image.c b/src/image.c index 8b210be5db..66645863ff 100644 --- a/src/image.c +++ b/src/image.c @@ -1586,14 +1586,22 @@ int wolfBoot_open_image_address(struct wolfBoot_image *img, uint8_t *image) * This function retrieves the size of the Device Tree Blob (DTB) from * the given DTB address. * - * @param dts_addr The pointer to the Device Tree Blob (DTB) address. - * @return The size of the DTB in bytes, or -1 if the magic number is invalid. + * Fully validates the blob (header layout plus a structural walk) before + * reporting its size. Every bound is checked against `capacity`, the + * bytes actually readable at dts_addr, not against the size the blob + * claims for itself. + * + * @param dts_addr Device Tree Blob (DTB) address. + * @param capacity Bytes available at dts_addr. + * @return DTB size in bytes, or a negative FDT_ERR_*. */ -int wolfBoot_get_dts_size(void *dts_addr) +int wolfBoot_get_dts_size(void *dts_addr, uint32_t capacity) { - int ret = fdt_check_header(dts_addr); + fdt_ctx ctx; + int ret = fdt_open(&ctx, dts_addr, capacity); + if (ret == 0) { - ret = fdt_totalsize(dts_addr); + ret = (int)fdt_size(&ctx); } return ret; } @@ -1720,6 +1728,7 @@ int wolfBoot_open_image(struct wolfBoot_image *img, uint8_t part) } #ifdef MMU if (part == PART_DTS_BOOT || part == PART_DTS_UPDATE) { + uint32_t dts_sz = 0; img->hdr = (part == PART_DTS_BOOT) ? (void*)WOLFBOOT_DTS_BOOT_ADDRESS : (void*)WOLFBOOT_DTS_UPDATE_ADDRESS; @@ -1729,9 +1738,19 @@ int wolfBoot_open_image(struct wolfBoot_image *img, uint8_t part) image = fetch_hdr_cpy(img); else image = (uint8_t*)img->hdr; - ret = wolfBoot_get_dts_size(image); - if (ret < 0) - return -1; + /* Only the header is readable here: `image` may be + * fetch_hdr_cpy()'s IMAGE_HEADER_SIZE copy. The blob is validated + * in full when it is loaded. Copy into an aligned local first, + * because a memory-mapped partition base is not guaranteed to be + * 4-byte aligned and fdt_peek_size() requires that. */ + { + uint8_t hdr[FDT_HEADER_SIZE] XALIGNED(4); + + memcpy(hdr, image, sizeof(hdr)); + if (fdt_peek_size(hdr, (uint32_t)sizeof(hdr), &dts_sz) != 0) + return -1; + } + ret = (int)dts_sz; img->hdr_ok = 1; wolfBoot_image_set_fw_base(img, img->hdr); img->fw_size = (uint32_t)ret; diff --git a/src/update_disk.c b/src/update_disk.c index f17bab8826..73518af93b 100644 --- a/src/update_disk.c +++ b/src/update_disk.c @@ -247,7 +247,7 @@ static void disk_decrypted_header_clear(uint8_t *hdr) #endif /* DISK_ENCRYPT */ -extern int wolfBoot_get_dts_size(void *dts_addr); +extern int wolfBoot_get_dts_size(void *dts_addr, uint32_t capacity); #ifdef MMU /* Platform hook: return a DTB the boot firmware handed us (e.g. the RPi * firmware's fully-patched dtb), used below when the loaded image carries no @@ -304,6 +304,8 @@ void RAMFUNCTION wolfBoot_start(void) uint8_t *dts_addr = NULL; #ifdef WOLFBOOT_FDT uint32_t dts_size = 0; + /* Validated view of the FIT staged at load_address. */ + fdt_ctx fit_ctx; #endif #endif #if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU) @@ -602,17 +604,20 @@ void RAMFUNCTION wolfBoot_start(void) load_address = (uint32_t*)os_image.fw_base; #ifdef WOLFBOOT_FDT - /* Is this a Flattened uImage Tree (FIT) image (FDT format) */ - if (wolfBoot_get_dts_size(load_address) > 0) { - void* fit = (void*)load_address; + /* Is this a Flattened uImage Tree (FIT) image (FDT format)? The + * capacity handed to the parser is the number of verified bytes + * staged at load_address, so a FIT that overstates its own size is + * rejected here rather than read past. */ + if (fdt_open(&fit_ctx, (void*)load_address, os_image.fw_size) == 0) { + fdt_ctx* fit = &fit_ctx; const char *kernel = NULL, *flat_dt = NULL, *ramdisk = NULL; const char *fpga = NULL; #if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU) void *atf_load; #endif - wolfBoot_printf("Flattened uImage Tree: Version %d, Size %d\n", - fdt_version(fit), fdt_totalsize(fit)); + wolfBoot_printf("Flattened uImage Tree: Size %d\n", + (int)fdt_size(fit)); (void)fit_find_images(fit, &kernel, &flat_dt, &ramdisk, &fpga); #ifdef WOLFBOOT_FPGA_BITSTREAM @@ -653,9 +658,12 @@ void RAMFUNCTION wolfBoot_start(void) } #endif if (flat_dt != NULL) { - uint8_t *dts_ptr = fit_load_image(fit, flat_dt, NULL); - int parsed = (dts_ptr != NULL) - ? wolfBoot_get_dts_size(dts_ptr) : -1; + int dt_len = 0; + uint8_t *dts_ptr = fit_load_image(fit, flat_dt, &dt_len); + /* Bound the parse by the sub-image's own declared length, + * the tightest bound available here. */ + int parsed = (dts_ptr != NULL && dt_len > 0) + ? wolfBoot_get_dts_size(dts_ptr, (uint32_t)dt_len) : -1; if (dts_ptr != NULL && parsed >= (int)WOLFBOOT_DTS_MIN_SIZE && (uint32_t)parsed <= WOLFBOOT_DTS_MAX_SIZE) { @@ -680,7 +688,16 @@ void RAMFUNCTION wolfBoot_start(void) } #ifdef WOLFBOOT_FIT_RAMDISK if (ramdisk != NULL) { - (void)fit_load_ramdisk(fit, ramdisk, (void*)dts_addr); + fdt_ctx dts_ctx; + fdt_ctx* dts_for_initrd = NULL; + + /* The relocated DTB sits in the staging window, so that is + * the capacity the initrd fixup may grow into. */ + if (dts_addr != NULL && + fdt_open(&dts_ctx, dts_addr, WOLFBOOT_DTS_MAX_SIZE) == 0) { + dts_for_initrd = &dts_ctx; + } + (void)fit_load_ramdisk(fit, ramdisk, dts_for_initrd); } #else (void)ramdisk; diff --git a/src/update_ram.c b/src/update_ram.c index b244e8a9b5..649a3c51eb 100644 --- a/src/update_ram.c +++ b/src/update_ram.c @@ -295,13 +295,16 @@ void RAMFUNCTION wolfBoot_start(void) #endif #ifdef MMU uint32_t dts_size = 0; + /* Validated view of the FIT staged at load_address. */ + fdt_ctx fit_ctx; /* HDR_DEVICE_TREE_DIGEST snapshot, taken before the raw DTB is loaded. */ uint8_t dts_digest[WOLFBOOT_SHA_DIGEST_SIZE]; uint8_t *dts_tlv = NULL; uint16_t dts_tlv_len = 0; int dts_digest_present = 0; /* 0 absent, 1 valid, -1 malformed */ #if defined(EXT_FLASH) && defined(WOLFBOOT_DTS_BOOT_ADDRESS) - uint8_t dts_hdr[64]; /* FDT header peek (fdt_check_header needs >= 40) */ + /* FDT header peek (fdt_peek_size needs >= 40 bytes, 4-byte aligned) */ + uint8_t dts_hdr[64] __attribute__((aligned(4))); #endif #endif #if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU) @@ -612,17 +615,20 @@ void RAMFUNCTION wolfBoot_start(void) } } - /* Is this a Flattened uImage Tree (FIT) image (FDT format) */ - if (wolfBoot_get_dts_size(load_address) > 0) { - void* fit = (void*)load_address; + /* Is this a Flattened uImage Tree (FIT) image (FDT format)? The + * capacity handed to the parser is the number of verified bytes + * staged at load_address, so a FIT that overstates its own size is + * rejected here rather than read past. */ + if (fdt_open(&fit_ctx, (void*)load_address, os_image.fw_size) == 0) { + fdt_ctx* fit = &fit_ctx; const char *kernel = NULL, *flat_dt = NULL, *ramdisk = NULL; const char *fpga = NULL; #if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU) void *atf_load; #endif - wolfBoot_printf("Flattened uImage Tree: Version %d, Size %d\n", - fdt_version(fit), fdt_totalsize(fit)); + wolfBoot_printf("Flattened uImage Tree: Size %d\n", + (int)fdt_size(fit)); (void)fit_find_images(fit, &kernel, &flat_dt, &ramdisk, &fpga); #ifdef WOLFBOOT_FPGA_BITSTREAM @@ -659,9 +665,13 @@ void RAMFUNCTION wolfBoot_start(void) } #endif if (flat_dt != NULL) { - uint8_t *dts_ptr = fit_load_image(fit, flat_dt, NULL); - int parsed = (dts_ptr != NULL) - ? wolfBoot_get_dts_size(dts_ptr) : -1; + int dt_len = 0; + uint8_t *dts_ptr = fit_load_image(fit, flat_dt, &dt_len); + /* Bound the parse by the sub-image's own declared length, + * not by the generic staging maximum: that is the tightest + * bound available here. */ + int parsed = (dts_ptr != NULL && dt_len > 0) + ? wolfBoot_get_dts_size(dts_ptr, (uint32_t)dt_len) : -1; if (dts_ptr != NULL && parsed >= (int)WOLFBOOT_DTS_MIN_SIZE && (uint32_t)parsed <= WOLFBOOT_DTS_MAX_SIZE) { @@ -679,7 +689,16 @@ void RAMFUNCTION wolfBoot_start(void) } #ifdef WOLFBOOT_FIT_RAMDISK if (ramdisk != NULL) { - (void)fit_load_ramdisk(fit, ramdisk, (void*)dts_addr); + fdt_ctx dts_ctx; + fdt_ctx* dts_for_initrd = NULL; + + /* The relocated DTB sits in the staging window, so that is + * the capacity the initrd fixup may grow into. */ + if (dts_addr != NULL && + fdt_open(&dts_ctx, dts_addr, WOLFBOOT_DTS_MAX_SIZE) == 0) { + dts_for_initrd = &dts_ctx; + } + (void)fit_load_ramdisk(fit, ramdisk, dts_for_initrd); } #else (void)ramdisk; @@ -691,7 +710,7 @@ void RAMFUNCTION wolfBoot_start(void) * no usable address (NULL, or a flash offset on NO_XIP targets). */ dts_addr = hal_get_dts_address(); if (dts_addr != NULL) { - ret = wolfBoot_get_dts_size(dts_addr); + ret = wolfBoot_get_dts_size(dts_addr, WOLFBOOT_DTS_MAX_SIZE); if (ret < (int)WOLFBOOT_DTS_MIN_SIZE || (uint32_t)ret > WOLFBOOT_DTS_MAX_SIZE) { wolfBoot_printf("DTB parse/size check failed - ignoring\n"); @@ -711,13 +730,20 @@ void RAMFUNCTION wolfBoot_start(void) ret = ext_flash_read((uintptr_t)WOLFBOOT_DTS_BOOT_ADDRESS, dts_hdr, (int)sizeof(dts_hdr)); if (ret == (int)sizeof(dts_hdr)) { - ret = wolfBoot_get_dts_size(dts_hdr); - if (ret >= (int)WOLFBOOT_DTS_MIN_SIZE && - (uint32_t)ret <= WOLFBOOT_DTS_MAX_SIZE) { - dts_size = (uint32_t)ret; + uint32_t peeked = 0; + /* Only the header has been read so far; fdt_peek_size + * validates just that much and reports the size to + * fetch. The complete blob is validated below. */ + ret = fdt_peek_size(dts_hdr, (uint32_t)sizeof(dts_hdr), + &peeked); + if (ret == 0) { + dts_size = peeked; if (ext_flash_read((uintptr_t)WOLFBOOT_DTS_BOOT_ADDRESS, (uint8_t*)WOLFBOOT_LOAD_DTS_ADDRESS, (int)dts_size) - == (int)dts_size) + == (int)dts_size && + wolfBoot_get_dts_size( + (void*)WOLFBOOT_LOAD_DTS_ADDRESS, dts_size) + == (int)dts_size) dts_addr = (uint8_t*)WOLFBOOT_LOAD_DTS_ADDRESS; else dts_size = 0; diff --git a/tools/fdt-parser/Makefile b/tools/fdt-parser/Makefile index 78ea9bf97d..2be1a17fe9 100644 --- a/tools/fdt-parser/Makefile +++ b/tools/fdt-parser/Makefile @@ -4,7 +4,7 @@ CC=gcc CFLAGS=-Wall -g -ggdb -CFLAGS+=-I../../include -DWOLFBOOT_FDT -DPRINTF_ENABLED +CFLAGS+=-I../../include -DWOLFBOOT_FDT -DWOLFBOOT_FDT_CORPUS -DPRINTF_ENABLED EXE=fdt-parser LIBS= diff --git a/tools/fdt-parser/README.md b/tools/fdt-parser/README.md index 9011b8dc67..f422d26f57 100644 --- a/tools/fdt-parser/README.md +++ b/tools/fdt-parser/README.md @@ -6,6 +6,15 @@ Use `-i` to parse a Flattened uImage Tree (FIT) image. There is also a `-t` option that tests making several updates to the device tree (useful with the nxp_t1024.dtb). +Use `-f` to check the parser's handling of malformed input: it derives a corpus of deliberately-invalid blobs (the generator lives in `src/fdt.c` under `WOLFBOOT_FDT_CORPUS`) from the supplied file (bad magic, truncated buffers, wrapped offsets, unbalanced nesting, oversized property lengths, unterminated names, and so on) and reports whether each is rejected. Every case must be rejected; the tool exits non-zero if any is accepted or faults. Build it with `-fsanitize=address,undefined` to catch reads past a blob's real end: + +```sh +gcc -o /tmp/fdt-parser-asan -Wall -g -O1 -fsanitize=address,undefined \ + -I include -DWOLFBOOT_FDT -DWOLFBOOT_FDT_CORPUS -DPRINTF_ENABLED \ + tools/fdt-parser/fdt-parser.c src/fdt.c +/tmp/fdt-parser-asan ./tools/fdt-parser/nxp_t1024.dtb -f +``` + ## Building fdt-parser From root: `make fdt-parser` diff --git a/tools/fdt-parser/fdt-parser.c b/tools/fdt-parser/fdt-parser.c index 6bfa64b2c3..f669791502 100644 --- a/tools/fdt-parser/fdt-parser.c +++ b/tools/fdt-parser/fdt-parser.c @@ -30,12 +30,23 @@ #include #include +/* The corpus runner (-f) forks a child per case so a fault in one does not + * stop the sweep and a sanitizer abort stays observable. That is POSIX + * only; elsewhere -f reports that it is unavailable and the normal parse + * modes still build. */ +#if defined(__unix__) || defined(__APPLE__) + #define FDT_CORPUS_RUNNER + #include + #include +#endif + static int gEnableUnitTest = 0; static int gParseFit = 0; +static int gRunCorpus = 0; #define UNIT_TEST_GROW_SIZE 1024 /* Test case for "nxp_t1024.dtb" */ -static int fdt_test(void* fdt) +static int fdt_test(fdt_ctx* ctx) { int ret = 0, off, i; uint32_t *reg, oldsize; @@ -99,12 +110,13 @@ static int fdt_test(void* fdt) SET_LIODN("fsl,qoriq-pcie-v2.4", 308), }; - /* expand total size to allow growth */ - oldsize = fdt_totalsize(fdt); - fdt_set_totalsize(fdt, oldsize + UNIT_TEST_GROW_SIZE); + /* reserve headroom so the fixups below have somewhere to grow */ + oldsize = fdt_size(ctx); + ret = fdt_grow(ctx, UNIT_TEST_GROW_SIZE); + if (ret != 0) goto exit; /* fixup the memory region - single bank */ - off = fdt_find_devtype(fdt, -1, "memory"); + off = fdt_find_devtype(ctx, -1, "memory"); if (off != -FDT_ERR_NOTFOUND) { /* build addr/size as 64-bit */ uint8_t ranges[sizeof(uint64_t) * 2], *p = ranges; @@ -112,19 +124,19 @@ static int fdt_test(void* fdt) p += sizeof(uint64_t); *(uint64_t*)p = cpu_to_fdt64(DDR_SIZE); p += sizeof(uint64_t); - ret = fdt_setprop(fdt, off, "reg", ranges, (int)(p - ranges)); + ret = fdt_setprop(ctx, off, "reg", ranges, (int)(p - ranges)); if (ret != 0) goto exit; printf("FDT: Set memory, start=0x%x, size=0x%x\n", DDR_ADDRESS, (uint32_t)DDR_SIZE); } /* fixup CPU status and, release address and enable method */ - off = fdt_find_devtype(fdt, -1, "cpu"); + off = fdt_find_devtype(ctx, -1, "cpu"); while (off != -FDT_ERR_NOTFOUND) { int core; uint64_t core_spin_table_addr; - reg = (uint32_t*)fdt_getprop(fdt, off, "reg", NULL); + reg = (uint32_t*)fdt_getprop(ctx, off, "reg", NULL); if (reg == NULL) break; core = (int)fdt32_to_cpu(*reg); @@ -136,62 +148,62 @@ static int fdt_test(void* fdt) core_spin_table_addr = (uint64_t)((uintptr_t)( SPIN_TABLE_ADDR + (core * ENTRY_SIZE))); - ret = fdt_fixup_str(fdt, off, "cpu", "status", (core == 0) ? "okay" : "disabled"); + ret = fdt_fixup_str(ctx, off, "cpu", "status", (core == 0) ? "okay" : "disabled"); if (ret == 0) - ret = fdt_fixup_val64(fdt, off, "cpu", "cpu-release-addr", core_spin_table_addr); + ret = fdt_fixup_val64(ctx, off, "cpu", "cpu-release-addr", core_spin_table_addr); if (ret == 0) - ret = fdt_fixup_str(fdt, off, "cpu", "enable-method", "spin-table"); + ret = fdt_fixup_str(ctx, off, "cpu", "enable-method", "spin-table"); if (ret == 0) - ret = fdt_fixup_val(fdt, off, "cpu", "timebase-frequency", TIMEBASE_HZ); + ret = fdt_fixup_val(ctx, off, "cpu", "timebase-frequency", TIMEBASE_HZ); if (ret == 0) - ret = fdt_fixup_val(fdt, off, "cpu", "clock-frequency", PLAT_CLK); + ret = fdt_fixup_val(ctx, off, "cpu", "clock-frequency", PLAT_CLK); if (ret == 0) - ret = fdt_fixup_val(fdt, off, "cpu", "bus-frequency", PLAT_CLK); + ret = fdt_fixup_val(ctx, off, "cpu", "bus-frequency", PLAT_CLK); if (ret != 0) goto exit; - off = fdt_find_devtype(fdt, off, "cpu"); + off = fdt_find_devtype(ctx, off, "cpu"); } /* fixup the soc clock */ - off = fdt_find_devtype(fdt, -1, "soc"); + off = fdt_find_devtype(ctx, -1, "soc"); if (off != -FDT_ERR_NOTFOUND) { - ret = fdt_fixup_val(fdt, off, "soc", "bus-frequency", PLAT_CLK); + ret = fdt_fixup_val(ctx, off, "soc", "bus-frequency", PLAT_CLK); if (ret != 0) goto exit; } /* fixup the serial clocks */ - off = fdt_find_devtype(fdt, -1, "serial"); + off = fdt_find_devtype(ctx, -1, "serial"); while (off != -FDT_ERR_NOTFOUND) { - ret = fdt_fixup_val(fdt, off, "serial", "clock-frequency", BUS_CLK); + ret = fdt_fixup_val(ctx, off, "serial", "clock-frequency", BUS_CLK); if (ret != 0) goto exit; - off = fdt_find_devtype(fdt, off, "serial"); + off = fdt_find_devtype(ctx, off, "serial"); } /* fixup the QE bridge and bus blocks */ - off = fdt_find_devtype(fdt, -1, "qe"); + off = fdt_find_devtype(ctx, -1, "qe"); if (off != -FDT_ERR_NOTFOUND) { - ret = fdt_fixup_val(fdt, off, "qe", "clock-frequency", BUS_CLK); + ret = fdt_fixup_val(ctx, off, "qe", "clock-frequency", BUS_CLK); if (ret == 0) - ret = fdt_fixup_val(fdt, off, "qe", "bus-frequency", BUS_CLK); + ret = fdt_fixup_val(ctx, off, "qe", "bus-frequency", BUS_CLK); if (ret == 0) - ret = fdt_fixup_val(fdt, off, "qe", "brg-frequency", BUS_CLK/2); + ret = fdt_fixup_val(ctx, off, "qe", "brg-frequency", BUS_CLK/2); if (ret != 0) goto exit; } /* fixup the LIODN */ for (i=0; i<(int)(sizeof(liodn_tbl)/sizeof(struct liodn_id_table)); i++) { - off = fdt_node_offset_by_compatible(fdt, -1, liodn_tbl[i].compat); + off = fdt_node_offset_by_compatible(ctx, -1, liodn_tbl[i].compat); if (off >= 0) { - ret = fdt_fixup_val(fdt, off, liodn_tbl[i].compat, "fsl,liodn", + ret = fdt_fixup_val(ctx, off, liodn_tbl[i].compat, "fsl,liodn", liodn_tbl[i].id); if (ret != 0) goto exit; } } /* fixup the QMAN portals */ - off = fdt_node_offset_by_compatible(fdt, -1, "fsl,qman-portal"); + off = fdt_node_offset_by_compatible(ctx, -1, "fsl,qman-portal"); while (off != -FDT_ERR_NOTFOUND) { - const int *ci = fdt_getprop(fdt, off, "cell-index", NULL); + const int *ci = fdt_getprop(ctx, off, "cell-index", NULL); uint32_t portal_idx; uint32_t liodns[2]; if (!ci) @@ -209,24 +221,28 @@ static int fdt_test(void* fdt) liodns[1] = qp_info[i].fliodn; printf("FDT: Set %s@%d (%d), %s=%d,%d\n", "qman-portal", i, off, "fsl,liodn", liodns[0], liodns[1]); - ret = fdt_setprop(fdt, off, "fsl,liodn", liodns, sizeof(liodns)); + /* big-endian on the wire, matching hal/nxp_t10xx.c */ + liodns[0] = cpu_to_fdt32(liodns[0]); + liodns[1] = cpu_to_fdt32(liodns[1]); + ret = fdt_setprop(ctx, off, "fsl,liodn", liodns, sizeof(liodns)); if (ret != 0) goto exit; - off = fdt_node_offset_by_compatible(fdt, off, "fsl,qman-portal"); + off = fdt_node_offset_by_compatible(ctx, off, "fsl,qman-portal"); } /* mpic clock */ - off = fdt_find_devtype(fdt, -1, "open-pic"); + off = fdt_find_devtype(ctx, -1, "open-pic"); if (off != -FDT_ERR_NOTFOUND) { - ret = fdt_fixup_val(fdt, off, "open-pic", "clock-frequency", BUS_CLK); + ret = fdt_fixup_val(ctx, off, "open-pic", "clock-frequency", BUS_CLK); if (ret != 0) goto exit; } /* resize the device tree */ - fdt_shrink(fdt); + fdt_shrink(ctx); /* display information */ - printf("FDT Updated: Size %d -> %d\n", oldsize, fdt_totalsize(fdt)); + printf("FDT Updated: Size %u -> %u\n", + (unsigned int)oldsize, (unsigned int)fdt_size(ctx)); exit: printf("FDT Test Result: %d\n", ret); @@ -328,17 +344,17 @@ static int load_file(const char* filename, uint8_t** buf, size_t* bufLen) return ret; } -static void* dts_fit_image_addr(void* fit, uint32_t off, const char* prop) +static void* dts_fit_image_addr(const fdt_ctx* ctx, int off, const char* prop) { - void* val = fdt_getprop_address(fit, off, prop); + void* val = fdt_getprop_address(ctx, off, prop); printf("\t%s: %p\n", prop, val); return val; } -static const void* dts_fit_image_item(void* fit, uint32_t off, const char* prop) +static const void* dts_fit_image_item(const fdt_ctx* ctx, int off, const char* prop) { int len = 0; - const void* val = fdt_getprop(fit, off, prop, &len); + const void* val = fdt_getprop(ctx, off, prop, &len); if (val != NULL && len > 0) { if (len < 256) printf("\t%s (len %d): %s\n", prop, len, (const char*)val); @@ -348,71 +364,71 @@ static const void* dts_fit_image_item(void* fit, uint32_t off, const char* prop) return val; } -void dts_parse_fit_image(void* fit, const char* image, const char* desc) +void dts_parse_fit_image(fdt_ctx* ctx, const char* image, const char* desc) { int off; - if (fit != NULL) { - printf("%s Image: %s\n", desc, image); + if (image == NULL) { + return; } - - off = fdt_find_node_offset(fit, -1, image); - if (off > 0) { - dts_fit_image_item(fit, off, "description"); - dts_fit_image_item(fit, off, "type"); - dts_fit_image_item(fit, off, "os"); - dts_fit_image_item(fit, off, "arch"); - dts_fit_image_item(fit, off, "compression"); - dts_fit_image_addr(fit, off, "load"); - dts_fit_image_addr(fit, off, "entry"); - dts_fit_image_item(fit, off, "padding"); - dts_fit_image_item(fit, off, "data"); + printf("%s Image: %s\n", desc, image); + + off = fdt_find_node_offset(ctx, -1, image); + if (off >= 0) { + dts_fit_image_item(ctx, off, "description"); + dts_fit_image_item(ctx, off, "type"); + dts_fit_image_item(ctx, off, "os"); + dts_fit_image_item(ctx, off, "arch"); + dts_fit_image_item(ctx, off, "compression"); + dts_fit_image_addr(ctx, off, "load"); + dts_fit_image_addr(ctx, off, "entry"); + dts_fit_image_item(ctx, off, "padding"); + dts_fit_image_item(ctx, off, "data"); } } -int dts_parse_fit(void* image) +int dts_parse_fit(fdt_ctx* ctx) { const char *conf = NULL, *kernel = NULL, *flat_dt = NULL, *ramdisk = NULL; const char *fpga = NULL; - conf = fit_find_images(image, &kernel, &flat_dt, &ramdisk, &fpga); + conf = fit_find_images(ctx, &kernel, &flat_dt, &ramdisk, &fpga); if (conf != NULL) { printf("FIT: Found '%s' configuration\n", conf); - dts_fit_image_item(image, fdt_find_node_offset(image, -1, conf), + dts_fit_image_item(ctx, fdt_find_node_offset(ctx, -1, conf), "description"); } /* dump image information */ - dts_parse_fit_image(image, kernel, "Kernel"); - dts_parse_fit_image(image, flat_dt, "FDT"); + dts_parse_fit_image(ctx, kernel, "Kernel"); + dts_parse_fit_image(ctx, flat_dt, "FDT"); if (ramdisk != NULL) { - dts_parse_fit_image(image, ramdisk, "Ramdisk"); + dts_parse_fit_image(ctx, ramdisk, "Ramdisk"); } if (fpga != NULL) { - dts_parse_fit_image(image, fpga, "FPGA"); + dts_parse_fit_image(ctx, fpga, "FPGA"); } return 0; } -int dts_parse(void* image) +int dts_parse(fdt_ctx* ctx) { int ret = 0; - struct fdt_header *fdt = (struct fdt_header *)image; - const struct fdt_property* prop; - int nlen, plen, slen; - int noff, poff, soff; - const char* nstr = NULL, *pstr = NULL; + int nlen, plen; + int noff, poff; + const char *nstr = NULL, *pstr = NULL; + const void* pval; int depth = 0; #define MAX_DEPTH 24 char tabs[MAX_DEPTH+1] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; /* walk tree */ - for (noff = fdt_next_node(fdt, -1, &depth); + for (noff = fdt_next_node(ctx, -1, &depth); noff >= 0; - noff = fdt_next_node(fdt, noff, &depth)) + noff = fdt_next_node(ctx, noff, &depth)) { - nstr = fdt_get_name(fdt, noff, &nlen); + nstr = fdt_get_name(ctx, noff, &nlen); if (depth > MAX_DEPTH) depth = MAX_DEPTH; @@ -421,15 +437,12 @@ int dts_parse(void* image) printf("%s%s (node offset %d, depth %d, len %d):\n", &tabs[MAX_DEPTH-depth+1], nstr, noff, depth, nlen); - for (poff = fdt_first_property_offset(fdt, noff); + for (poff = fdt_first_property_offset(ctx, noff); poff >= 0; - poff = fdt_next_property_offset(fdt, poff)) + poff = fdt_next_property_offset(ctx, poff)) { - prop = fdt_get_property_by_offset(fdt, poff, &plen); - if (prop != NULL) { - soff = fdt32_to_cpu(prop->nameoff); - pstr = fdt_get_string(fdt, soff, &slen); - + pval = fdt_getprop_by_offset(ctx, poff, &pstr, &plen); + if (pval != NULL) { printf("%s%s (prop offset %d, len %d): ", &tabs[MAX_DEPTH-depth], pstr, poff, plen); if (plen > 32) @@ -438,10 +451,10 @@ int dts_parse(void* image) char file[260+1]; snprintf(file, sizeof(file), "%s.%s.bin", nstr, pstr); printf("Saving to file %s\n", file); - write_bin(file, (const uint8_t*)prop->data, plen); + write_bin(file, (const uint8_t*)pval, plen); } else { - print_bin((const uint8_t*)prop->data, plen); + print_bin((const uint8_t*)pval, plen); printf("\n"); } } @@ -451,12 +464,190 @@ int dts_parse(void* image) return ret; } + +/* ------------------------------------------------------------------ */ +/* Malformed-input corpus (-f) */ +/* ------------------------------------------------------------------ */ + +/* Exit codes used by the forked child below. A distinct value for + * "accepted" keeps it apart from the 1 a sanitizer exits with. */ +#define CORPUS_REJECTED 0 +#define CORPUS_ACCEPTED 42 + +/* Open a corpus blob and, if it is accepted, read everything a real + * consumer would touch. `len` is the exact allocation size, so any read + * past it is a genuine heap overrun for ASan to catch. */ +static int corpus_parse(uint8_t* buf, uint32_t len) +{ + fdt_ctx ctx; + int noff, poff, plen, nlen; + const char *nstr, *pstr; + const void* pval; + int depth = 0; + int nodes = 0; + + if (fdt_open(&ctx, buf, len) != 0) { + return CORPUS_REJECTED; + } + + for (noff = fdt_next_node(&ctx, -1, &depth); + noff >= 0; + noff = fdt_next_node(&ctx, noff, &depth)) + { + nstr = fdt_get_name(&ctx, noff, &nlen); + (void)nstr; + if (++nodes > 200000) { + break; /* runaway guard */ + } + for (poff = fdt_first_property_offset(&ctx, noff); + poff >= 0; + poff = fdt_next_property_offset(&ctx, poff)) + { + pval = fdt_getprop_by_offset(&ctx, poff, &pstr, &plen); + if (pval == NULL) { + break; + } + (void)pstr; + /* touch the payload the way a consumer would */ + if (plen > 0) { + volatile char sink = 0; + int i; + for (i = 0; i < plen; i++) { + sink = ((const char*)pval)[i]; + } + (void)sink; + } + } + } + return CORPUS_ACCEPTED; +} + +#ifdef FDT_CORPUS_RUNNER +/* Lookups on a VALID blob, with names far longer than anything in the + * tree. Search names reach the parser from attacker-influenced places (a + * FIT's `default` string, for one), so their length is not bounded by the + * blob - a comparison that trusted it would read past a node name. Run + * under ASan this is a real out-of-bounds check, not just a return-value + * check. */ +static void probe_hostile_lookups(uint8_t* blob, uint32_t len) +{ + static char big[4096]; + fdt_ctx ctx; + char path[4200]; + + if (fdt_open(&ctx, blob, len) != 0) { + return; + } + memset(big, 'a', sizeof(big) - 1); + big[sizeof(big) - 1] = '\0'; + + (void)fdt_find_node_offset(&ctx, -1, big); + (void)fdt_find_prop_offset(&ctx, -1, big, big); + (void)fdt_node_offset_by_compatible(&ctx, -1, big); + (void)fdt_getprop(&ctx, 0, big, NULL); + (void)fdt_subnode_offset(&ctx, 0, big); + snprintf(path, sizeof(path), "/%s", big); + (void)fdt_path_offset(&ctx, path); + snprintf(path, sizeof(path), "/cpus/%s", big); + (void)fdt_path_offset(&ctx, path); +} + +static int run_corpus(const uint8_t* base, uint32_t baselen) +{ + int i, n, accepted = 0, rejected = 0, faulted = 0; + + n = fdt_corpus_count(); + printf("Malformed-DTB corpus: %d cases\n\n", n); + printf("%-22s %-38s %s\n", "CASE", "WHAT", "RESULT"); + printf("%-22s %-38s %s\n", "----", "----", "------"); + + for (i = 0; i < n; i++) { + uint32_t len = 0; + uint8_t* buf; + const char* result; + pid_t pid; + int status = 0; + + buf = fdt_corpus_build(i, base, baselen, &len); + if (buf == NULL) { + printf("%-22s %-38s %s\n", fdt_corpus_name(i), + fdt_corpus_desc(i), "SKIP"); + continue; + } + + /* Each case runs in its own process so a fault in one does not + * stop the sweep, and so a sanitizer abort is observable. */ + fflush(stdout); + pid = fork(); + if (pid == 0) { + _exit(corpus_parse(buf, len)); + } + free(buf); + if (pid < 0) { + printf("fork failed\n"); + return -1; + } + waitpid(pid, &status, 0); + + if (WIFSIGNALED(status)) { + result = "FAULT"; + faulted++; + } + else if (WEXITSTATUS(status) == CORPUS_REJECTED) { + result = "rejected"; + rejected++; + } + else if (WEXITSTATUS(status) == CORPUS_ACCEPTED) { + result = "ACCEPTED"; + accepted++; + } + else { + result = "SANITIZER"; + faulted++; + } + printf("%-22s %-38s %s\n", fdt_corpus_name(i), fdt_corpus_desc(i), + result); + } + + printf("\nrejected=%d accepted=%d faulted=%d (of %d)\n", + rejected, accepted, faulted, n); + + { + uint32_t len = 0; + uint8_t* good = malloc(baselen); + + if (good != NULL) { + memcpy(good, base, baselen); + len = baselen; + probe_hostile_lookups(good, len); + free(good); + printf("hostile-lookup probes on a valid blob: done\n"); + } + } + + /* Every case in the corpus is invalid, so anything not rejected is + * a failure of the parser. */ + return (accepted == 0 && faulted == 0) ? 0 : -1; +} +#else /* !FDT_CORPUS_RUNNER */ +static int run_corpus(const uint8_t* base, uint32_t baselen) +{ + (void)base; + (void)baselen; + printf("-f (malformed-input corpus) needs fork()/waitpid(); " + "not available on this platform\n"); + return -1; +} +#endif /* FDT_CORPUS_RUNNER */ + static void Usage(void) { printf("Expected usage:\n"); printf("./tools/fdt-parser/fdt-parser [-t] [-i] filename\n"); printf("\t* -i: Parse Flattened uImage Tree (FIT) image\n"); printf("\t* -t: Test several updates (used with nxp_t1024.dtb)\n"); + printf("\t* -f: Check that malformed variants of the file are " + "rejected\n"); } int main(int argc, char *argv[]) @@ -464,6 +655,8 @@ int main(int argc, char *argv[]) int ret = 0; uint8_t *image = NULL; size_t imageSz = 0; + uint32_t capacity = 0; + fdt_ctx ctx; const char* filename = NULL; if (argc == 1 || (argc >= 2 && @@ -480,6 +673,9 @@ int main(int argc, char *argv[]) else if (strcmp(argv[argc-1], "-i") == 0) { gParseFit = 1; } + else if (strcmp(argv[argc-1], "-f") == 0) { + gRunCorpus = 1; + } else if (*argv[argc-1] != '-') { filename = argv[argc-1]; } @@ -489,27 +685,48 @@ int main(int argc, char *argv[]) argc--; } - printf("FDT Parser (%s):\n", filename); if (filename == NULL) { printf("Usage: fdt-parser [filename.dtb]\n"); return 0; } + printf("FDT Parser (%s):\n", filename); ret = load_file(filename, &image, &imageSz); if (ret == 0) { - /* check header */ - ret = fdt_check_header(image); + /* Must equal what load_file() allocated, which is imageSz plus + * UNIT_TEST_GROW_SIZE when -t is in use (see load_file). Every + * bound the parser applies comes from this, not from the blob's + * own totalsize, so it must neither overstate the allocation nor + * wrap while being narrowed from size_t. */ + if (imageSz > (size_t)(UINT32_MAX - UNIT_TEST_GROW_SIZE)) { + printf("File too large to parse (%llu bytes)\n", + (unsigned long long)imageSz); + free(image); + return -1; + } + capacity = (uint32_t)imageSz; + if (gEnableUnitTest) + capacity += UNIT_TEST_GROW_SIZE; + + ret = fdt_open(&ctx, image, capacity); if (ret != 0) { printf("FDT check failed %d!\n", ret); + free(image); return ret; } /* display information */ - printf("FDT Version %d, Size %d\n", - fdt_version(image), fdt_totalsize(image)); + printf("FDT Version %d, Size %u\n", + FDT_SUPPORTED_VERSION, (unsigned int)fdt_size(&ctx)); + } + if (ret == 0 && gRunCorpus) { + ret = run_corpus(image, (uint32_t)imageSz); + free(image); + printf("Return %d\n", ret); + return ret; } if (ret == 0 && gEnableUnitTest) { - ret = fdt_test(image); + ret = fdt_test(&ctx); if (ret == 0) { char outfilename[PATH_MAX]; strncpy(outfilename, filename, sizeof(outfilename)-1); @@ -523,10 +740,10 @@ int main(int argc, char *argv[]) } if (ret == 0) { if (gParseFit) { - ret = dts_parse_fit(image); + ret = dts_parse_fit(&ctx); } else { - ret = dts_parse(image); + ret = dts_parse(&ctx); } } free(image); diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index 103fdd34cc..594a327bdf 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -1350,7 +1350,10 @@ static int dts_hash_file(const char *file, int hash_algo, uint8_t *out, * last_comp_version@0x18. */ const uint32_t FDT_MAGIC = 0xd00dfeedU; const uint32_t FDT_HDR_SIZE = 40U; - const uint32_t FDT_FIRST_VER = 0x10U; + /* Must track include/fdt.h's FDT_SUPPORTED_VERSION: the bootloader + * parser accepts v17 and later only, so signing a v16 blob here would + * produce an image that fails to boot. */ + const uint32_t FDT_FIRST_VER = 0x11U; const uint32_t FDT_LAST_COMP_VER = 0x11U; FILE *f; uint8_t hdr[40]; diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index f09f32e513..34d902c830 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -394,17 +394,12 @@ unit-fwtpm-cmd-toctou: ../../include/target.h unit-fwtpm-cmd-toctou.c gcc -o $@ $^ $(CFLAGS) -I$(WOLFBOOT_LIB_WOLFTPM) \ -DWOLFTPM_USER_SETTINGS $(LDFLAGS) -# unit-fdt-memrsv-wrap: a wrapped string-block end in fdt_add_mem_rsv() -# must be rejected, not turned into a huge memmove (F-11045). -fdt_memrsv_extract.h: ../../src/fdt.c - sed -n '/^uint32_t cpu_to_fdt32/,/^}/p' $< > $@ - sed -n '/^uint64_t cpu_to_fdt64/,/^}/p' $< >> $@ - sed -n '/^uint32_t fdt32_to_cpu/,/^}/p' $< >> $@ - sed -n '/^uint64_t fdt64_to_cpu/,/^}/p' $< >> $@ - sed -n '/^int fdt_add_mem_rsv/,/^}/p' $< >> $@ - -unit-fdt-memrsv-wrap: unit-fdt-memrsv-wrap.c fdt_memrsv_extract.h - gcc -o $@ unit-fdt-memrsv-wrap.c -I../../include $(CFLAGS) $(LDFLAGS) +# unit-fdt-memrsv-wrap: layout validation in front of fdt_add_mem_rsv() +# (F-11045). Links the real parser rather than extracting one function. +unit-fdt-memrsv-wrap:CFLAGS+=-DWOLFBOOT_FDT +unit-fdt-memrsv-wrap: unit-fdt-memrsv-wrap.c ../../src/fdt.c + gcc -o $@ unit-fdt-memrsv-wrap.c ../../src/fdt.c -I../../include \ + $(CFLAGS) $(LDFLAGS) unit-tpm-blob: ../../include/target.h unit-tpm-blob.c gcc -o $@ $^ $(CFLAGS) -I$(WOLFBOOT_LIB_WOLFTPM) -DWOLFBOOT_TPM \ @@ -1296,7 +1291,7 @@ covclean: # Sources the extraction rules generate into this directory. Listed here # so "clean" removes them and so there is one place that names them. -GENERATED_SRC:=aurix_erased_extract.h fdt_memrsv_extract.h \ +GENERATED_SRC:=aurix_erased_extract.h \ hifive1_flash_write_extract.h nvm_cache_scrub_extract.h \ nxp_ls1028a_host.c nxp_p1021_host.c nxp_t10xx_fixup_extract.h \ p1021_erase_extract.h p1021_erase_fn_extract.h sdhci_host.c \ diff --git a/tools/unit-tests/unit-fdt-memrsv-wrap.c b/tools/unit-tests/unit-fdt-memrsv-wrap.c index e01dd0ffc6..e586484366 100644 --- a/tools/unit-tests/unit-fdt-memrsv-wrap.c +++ b/tools/unit-tests/unit-fdt-memrsv-wrap.c @@ -1,21 +1,24 @@ /* unit-fdt-memrsv-wrap.c * - * Regression test for F-11045: fdt_add_mem_rsv() in src/fdt.c added the - * 32-bit string-block offset and size without overflow checks. A - * wrapped data_end bypassed the capacity check, and the same wrapped - * expression derived the memmove length, so a malformed (or - * attacker-supplied) DTB produced a huge memmove - broad boot-time - * memory corruption. fdt_check_header validates only magic and - * version, so raw-DTB callers reach this code with inconsistent - * layout fields. + * Regression test for F-11045 and for the layout validation that now + * stands in front of it. * - * The fix uses 64-bit arithmetic for the block end, validates the - * ordering of the reserve map / structure / string blocks, and - * derives the move length only after validation. + * The original defect: fdt_add_mem_rsv() added the 32-bit string-block + * offset and size without overflow checks. A wrapped end bypassed the + * capacity check and the same wrapped expression derived the memmove + * length, so a malformed (or attacker-supplied) DTB produced a huge + * memmove - broad boot-time memory corruption. Raw-DTB callers reached + * that code with inconsistent layout fields because the header check of + * the day validated only magic and version. * - * The real fdt_add_mem_rsv() (plus the byte-order helpers) is - * extracted by the Makefile; the FDT under test is a crafted header - * plus block layout in a static buffer. + * Since the parser rewrite the inconsistent layouts are rejected by + * fdt_open() before any code can act on them, so the first two cases + * assert on fdt_open(). The insertion case then exercises the real + * fdt_add_mem_rsv() on a valid tree, including the bound that the whole + * insert is checked against the caller's window. + * + * This links the real src/fdt.c rather than sed-extracting one function + * out of it. * * Copyright (C) 2026 wolfSSL Inc. * @@ -42,45 +45,69 @@ #include "fdt.h" -#define wolfBoot_printf(...) ((void)0) +void wolfBoot_printf(const char *fmt, ...) +{ + (void)fmt; +} -/* Byte-order helpers and the code under test, from src/fdt.c - * (extracted by the Makefile). */ -#include "fdt_memrsv_extract.h" +#define BUF_SZ 128 +static uint8_t g_buf[BUF_SZ] __attribute__((aligned(4))); -#define FDT_MAGIC_V 0xD00DFEEDu -#define BUF_SZ 96 -static uint8_t g_buf[BUF_SZ]; +static void be32(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)(v >> 24); + p[1] = (uint8_t)(v >> 16); + p[2] = (uint8_t)(v >> 8); + p[3] = (uint8_t)v; +} -/* Craft a header with the given layout fields. The blocks are laid - * out as: 40-byte header, reserve map (16-byte terminator), a 4-byte - * structure block, a 4-byte string block, trailing slack. */ -static void make_fdt(uint32_t total, uint32_t off_rsv, uint32_t off_dt, - uint32_t off_str, uint32_t size_str) +static uint32_t rd32(const uint8_t *p) { - struct fdt_header *h = (struct fdt_header *)g_buf; + return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) + | ((uint32_t)p[2] << 8) | (uint32_t)p[3]; +} + +static uint64_t rd64(const uint8_t *p) +{ + uint64_t v = 0; + int i; + for (i = 0; i < 8; i++) { + v = (v << 8) | (uint64_t)p[i]; + } + return v; +} + + +/* Craft a header with the given layout fields, over a minimal but + * well-formed structure block. Block contents are written only where + * they fit: the malformed-layout cases carry out-of-buffer offsets and + * must be rejected before anything touches the blocks. */ +static void make_fdt(uint32_t total, uint32_t off_rsv, uint32_t off_dt, + uint32_t size_dt, uint32_t off_str, uint32_t size_str) +{ memset(g_buf, 0x77, sizeof(g_buf)); - h->magic = cpu_to_fdt32(FDT_MAGIC_V); - h->totalsize = cpu_to_fdt32(total); - h->off_dt_struct = cpu_to_fdt32(off_dt); - h->off_dt_strings = cpu_to_fdt32(off_str); - h->off_mem_rsvmap = cpu_to_fdt32(off_rsv); - h->version = cpu_to_fdt32(17); - h->last_comp_version = cpu_to_fdt32(16); - h->boot_cpuid_phys = cpu_to_fdt32(0); - h->size_dt_strings = cpu_to_fdt32(size_str); - h->size_dt_struct = cpu_to_fdt32(0); - - /* Block contents only where they fit: the malformed-layout tests - * carry out-of-buffer offsets, and the code under test must reject - * them before touching the blocks. */ - if (off_rsv + 16 <= BUF_SZ) { - memset(g_buf + off_rsv, 0, 16); + be32(g_buf + FDT_H_MAGIC, (uint32_t)FDT_MAGIC); + be32(g_buf + FDT_H_TOTALSIZE, total); + be32(g_buf + FDT_H_OFF_STRUCT, off_dt); + be32(g_buf + FDT_H_OFF_STRINGS, off_str); + be32(g_buf + FDT_H_OFF_RSVMAP, off_rsv); + be32(g_buf + FDT_H_VERSION, 17); + be32(g_buf + FDT_H_LAST_COMP, 16); + be32(g_buf + 28, 0); /* boot_cpuid_phys */ + be32(g_buf + FDT_H_SIZE_STRINGS, size_str); + be32(g_buf + FDT_H_SIZE_STRUCT, size_dt); + + if (off_rsv + FDT_RSV_ENTRY_SIZE <= BUF_SZ) { + memset(g_buf + off_rsv, 0, FDT_RSV_ENTRY_SIZE); } - if (off_dt + 4 <= BUF_SZ) { - memset(g_buf + off_dt, 0, 4); + if (off_dt + 16 <= BUF_SZ) { + /* root node, empty name, closed, then FDT_END */ + be32(g_buf + off_dt + 0, FDT_BEGIN_NODE); + be32(g_buf + off_dt + 4, 0); + be32(g_buf + off_dt + 8, FDT_END_NODE); + be32(g_buf + off_dt + 12, FDT_END); } if (off_str + 2 <= BUF_SZ) { g_buf[off_str] = 'x'; @@ -89,37 +116,43 @@ static void make_fdt(uint32_t total, uint32_t off_rsv, uint32_t off_dt, } /* off_str + size_str wraps past 2^32 and the wrapped end lands below - * off_dt: pre-fix the memmove length wraps to ~2^32 (crash/corruption), - * post-fix the 64-bit end is far past totalsize and rejected. */ + * off_dt. Pre-fix this produced a ~2^32 memmove length; now the layout + * never gets past fdt_open(). */ START_TEST (test_wrap_past_off_dt_rejected) { - int ret; + fdt_ctx ctx; /* off_str + size_str = 0x100000010 (wraps to 0x10 in 32-bit). */ - make_fdt(0x2000, 40, 0x100, 0xFFFFFFF0u, 0x100u); - - ret = fdt_add_mem_rsv(g_buf, 0x1000, 0x400); - ck_assert_int_ne(ret, 0); + make_fdt(0x2000, 40, 0x100, 16, 0xFFFFFFF0u, 0x100u); + ck_assert_int_lt(fdt_open(&ctx, g_buf, BUF_SZ), 0); } END_TEST -/* The wrapped end lands inside [off_dt, total): pre-fix this passed - * the capacity check, moved 0 bytes, and still published shifted - * header offsets (a silently corrupted FDT). Post-fix it is rejected. */ +/* The wrapped end lands inside [off_dt, total): pre-fix this passed the + * capacity check, moved 0 bytes, and still published shifted header + * offsets - a silently corrupted FDT. */ START_TEST (test_wrap_inside_range_rejected) { - uint32_t off_str_before; - int ret; - - /* off_str + size_str = 0x100000100 (wraps to 0x100 in 32-bit) - * which equals off_dt. */ - make_fdt(0x2000, 40, 0x100, 0xFFFFFF00u, 0x200u); - off_str_before = fdt_off_dt_strings(g_buf); - - ret = fdt_add_mem_rsv(g_buf, 0x1000, 0x400); - ck_assert_int_ne(ret, 0); - ck_assert_uint_eq(fdt_off_dt_strings(g_buf), off_str_before); - ck_assert_uint_eq(fdt_off_dt_struct(g_buf), 0x100u); + fdt_ctx ctx; + + /* off_str + size_str = 0x100000100 (wraps to 0x100), equal to off_dt. */ + make_fdt(0x2000, 40, 0x100, 16, 0xFFFFFF00u, 0x200u); + ck_assert_int_lt(fdt_open(&ctx, g_buf, BUF_SZ), 0); + + /* the header must be left exactly as it was found */ + ck_assert_uint_eq(rd32(g_buf + FDT_H_OFF_STRINGS), 0xFFFFFF00u); + ck_assert_uint_eq(rd32(g_buf + FDT_H_OFF_STRUCT), 0x100u); +} +END_TEST + +/* A totalsize larger than the window is rejected even though the block + * layout is internally consistent. */ +START_TEST (test_totalsize_past_window_rejected) +{ + fdt_ctx ctx; + + make_fdt(0x2000, 40, 56, 16, 72, 1); + ck_assert_int_lt(fdt_open(&ctx, g_buf, BUF_SZ), 0); } END_TEST @@ -128,33 +161,85 @@ END_TEST * and string blocks shift by 16, and the header offsets follow. */ START_TEST (test_valid_insertion_works) { - struct fdt_reserve_entry *rsv; - int i; - int ret; + fdt_ctx ctx; - /* Layout: header [0,40), rsv map [40,56) terminator, struct - * [56,60), strings [60,64); 32 bytes of slack for the shift. */ - make_fdt(96, 40, 56, 60, 4); + /* header [0,40), rsv map [40,56) terminator, struct [56,72), + * strings [72,73); the rest of the buffer is slack for the shift. */ + make_fdt(73, 40, 56, 16, 72, 1); + g_buf[72] = 0; /* strings block must end in a NUL */ - ret = fdt_add_mem_rsv(g_buf, 0x80000000ULL, 0x400000ULL); - ck_assert_int_eq(ret, 0); + ck_assert_int_eq(fdt_open(&ctx, g_buf, BUF_SZ), 0); + ck_assert_int_eq(fdt_add_mem_rsv(&ctx, 0x80000000ULL, 0x400000ULL), 0); /* New entry where the terminator was, then the terminator. */ - rsv = (struct fdt_reserve_entry *)(g_buf + 40); - ck_assert_uint_eq(fdt64_to_cpu(rsv[0].address), 0x80000000ULL); - ck_assert_uint_eq(fdt64_to_cpu(rsv[0].size), 0x400000ULL); - ck_assert_uint_eq(fdt64_to_cpu(rsv[1].address), 0ULL); - ck_assert_uint_eq(fdt64_to_cpu(rsv[1].size), 0ULL); + ck_assert_uint_eq(rd64(g_buf + 40), 0x80000000ULL); + ck_assert_uint_eq(rd64(g_buf + 48), 0x400000ULL); + ck_assert_uint_eq(rd64(g_buf + 56), 0ULL); + ck_assert_uint_eq(rd64(g_buf + 64), 0ULL); /* Header offsets shifted by one reserve entry (16 bytes). */ - ck_assert_uint_eq(fdt_off_dt_struct(g_buf), 56 + 16); - ck_assert_uint_eq(fdt_off_dt_strings(g_buf), 60 + 16); - - /* Structure block (4-byte end marker) and string block moved intact. */ - for (i = 72; i < 76; i++) - ck_assert_uint_eq(g_buf[i], 0); - ck_assert_uint_eq(g_buf[76], 'x'); - ck_assert_uint_eq(g_buf[77], 0); + ck_assert_uint_eq(rd32(g_buf + FDT_H_OFF_STRUCT), 56 + 16); + ck_assert_uint_eq(rd32(g_buf + FDT_H_OFF_STRINGS), 72 + 16); + + /* The structure block moved intact and the tree still parses. */ + ck_assert_uint_eq(rd32(g_buf + 72), FDT_BEGIN_NODE); + ck_assert_uint_eq(rd32(g_buf + 84), FDT_END); + ck_assert_int_eq(fdt_open(&ctx, g_buf, BUF_SZ), 0); + ck_assert_int_eq(fdt_path_offset(&ctx, "/"), 0); +} +END_TEST + +/* The T2080 boot path inserts three entries back to back, so the second + * and third have to walk past already-filled slots to find the + * terminator. Only the empty-map case was covered before. */ +START_TEST (test_repeated_insertion_walks_filled_slots) +{ + fdt_ctx ctx; + int i; + + make_fdt(73, 40, 56, 16, 72, 1); + g_buf[72] = 0; + + ck_assert_int_eq(fdt_open(&ctx, g_buf, BUF_SZ), 0); + for (i = 0; i < 3; i++) { + ck_assert_int_eq(fdt_add_mem_rsv(&ctx, + 0x80000000ULL + (uint64_t)i * 0x1000ULL, 0x1000ULL), 0); + } + + /* three entries at consecutive slots, then the terminator */ + for (i = 0; i < 3; i++) { + ck_assert_uint_eq(rd64(g_buf + 40 + (i * 16)), + 0x80000000ULL + (uint64_t)i * 0x1000ULL); + ck_assert_uint_eq(rd64(g_buf + 48 + (i * 16)), 0x1000ULL); + } + ck_assert_uint_eq(rd64(g_buf + 40 + (3 * 16)), 0ULL); + ck_assert_uint_eq(rd64(g_buf + 48 + (3 * 16)), 0ULL); + + /* the blocks moved down by exactly three entries, and it still parses */ + ck_assert_uint_eq(rd32(g_buf + FDT_H_OFF_STRUCT), 56 + (3 * 16)); + ck_assert_uint_eq(rd32(g_buf + FDT_H_OFF_STRINGS), 72 + (3 * 16)); + ck_assert_int_eq(fdt_open(&ctx, g_buf, BUF_SZ), 0); + ck_assert_int_eq(fdt_path_offset(&ctx, "/"), 0); +} +END_TEST + +/* The insert is bounded by the caller's window, not by the blob's own + * totalsize: with no room for the 16-byte shift it must fail closed. */ +START_TEST (test_insertion_without_room_rejected) +{ + fdt_ctx ctx; + + make_fdt(73, 40, 56, 16, 72, 1); + g_buf[72] = 0; + + /* window ends exactly at the tree's content */ + ck_assert_int_eq(fdt_open(&ctx, g_buf, 73), 0); + ck_assert_int_eq(fdt_add_mem_rsv(&ctx, 0x80000000ULL, 0x400000ULL), + -FDT_ERR_NOSPACE); + + /* nothing moved */ + ck_assert_uint_eq(rd32(g_buf + FDT_H_OFF_STRUCT), 56); + ck_assert_uint_eq(rd32(g_buf + FDT_H_OFF_STRINGS), 72); } END_TEST @@ -165,7 +250,10 @@ Suite *fdt_memrsv_wrap_suite(void) tcase_add_test(tc, test_wrap_past_off_dt_rejected); tcase_add_test(tc, test_wrap_inside_range_rejected); + tcase_add_test(tc, test_totalsize_past_window_rejected); tcase_add_test(tc, test_valid_insertion_works); + tcase_add_test(tc, test_repeated_insertion_walks_filled_slots); + tcase_add_test(tc, test_insertion_without_room_rejected); tcase_set_timeout(tc, 10); suite_add_tcase(s, tc); return s; diff --git a/tools/unit-tests/unit-fdt.c b/tools/unit-tests/unit-fdt.c index 5f05b2417f..29984841a5 100644 --- a/tools/unit-tests/unit-fdt.c +++ b/tools/unit-tests/unit-fdt.c @@ -1,6 +1,6 @@ /* unit-fdt.c * - * Unit tests for flattened device tree helpers. + * Unit tests for the flattened device tree parser. * * Copyright (C) 2026 wolfSSL Inc. * @@ -32,315 +32,807 @@ void wolfBoot_printf(const char *fmt, ...) (void)fmt; } +/* ------------------------------------------------------------------ */ +/* Blob construction helpers */ +/* ------------------------------------------------------------------ */ + + +static void be32(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)(v >> 24); + p[1] = (uint8_t)(v >> 16); + p[2] = (uint8_t)(v >> 8); + p[3] = (uint8_t)v; +} + +static uint32_t rd_be32(const uint8_t *p) +{ + return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) + | ((uint32_t)p[2] << 8) | (uint32_t)p[3]; +} + +static void hdr_set(uint8_t *buf, uint32_t field, uint32_t v) +{ + be32(buf + field, v); +} + +/* Lay out a well-formed v17 blob in buf: header, an empty reservation + * block, the caller's structure block and the caller's strings block. + * Returns the total size written. */ +static uint32_t build_fdt(uint8_t *buf, uint32_t bufsz, + const uint8_t *sblk, uint32_t slen, + const char *strblk, uint32_t strsz) +{ + uint32_t off_rsv = FDT_HEADER_SIZE; + uint32_t off_struct = off_rsv + FDT_RSV_ENTRY_SIZE; + uint32_t off_strings = off_struct + slen; + uint32_t total = off_strings + strsz; + + ck_assert_uint_le(total, bufsz); + memset(buf, 0, bufsz); + + hdr_set(buf, FDT_H_MAGIC, (uint32_t)FDT_MAGIC); + hdr_set(buf, FDT_H_TOTALSIZE, total); + hdr_set(buf, FDT_H_OFF_STRUCT, off_struct); + hdr_set(buf, FDT_H_OFF_STRINGS, off_strings); + hdr_set(buf, FDT_H_OFF_RSVMAP, off_rsv); + hdr_set(buf, FDT_H_VERSION, 17); + hdr_set(buf, FDT_H_LAST_COMP, 16); + hdr_set(buf, FDT_H_SIZE_STRUCT, slen); + hdr_set(buf, FDT_H_SIZE_STRINGS, strsz); + memcpy(buf + off_struct, sblk, slen); + memcpy(buf + off_strings, strblk, strsz); + return total; +} + +/* Structure block for a root node carrying one property: the raw `len` + * bytes at `val`, named by string offset `nameoff`. */ +static uint32_t build_root_prop_struct(uint8_t *sblk, uint32_t nameoff, + const uint8_t *val, uint32_t len) +{ + uint32_t pad = FDT_TAGALIGN(len); + uint32_t i = 0; + + be32(sblk + i, FDT_BEGIN_NODE); i += 4; + be32(sblk + i, 0); i += 4; /* empty root name */ + be32(sblk + i, FDT_PROP); i += 4; + be32(sblk + i, len); i += 4; + be32(sblk + i, nameoff); i += 4; + memset(sblk + i, 0, pad); + memcpy(sblk + i, val, len); i += pad; + be32(sblk + i, FDT_END_NODE); i += 4; + be32(sblk + i, FDT_END); i += 4; + return i; +} + +/* A root node whose `compatible` property holds the raw bytes val/len. */ +static uint32_t build_compat_fdt(uint8_t *buf, uint32_t bufsz, + const uint8_t *val, uint32_t len) +{ + uint8_t sblk[128]; + uint32_t slen = build_root_prop_struct(sblk, 0, val, len); + + return build_fdt(buf, bufsz, sblk, slen, + "compatible", (uint32_t)sizeof("compatible")); +} + +/* ------------------------------------------------------------------ */ +/* fdt_open: the capacity bound */ +/* ------------------------------------------------------------------ */ + +/* The check that did not exist before the rewrite: a blob may not + * declare itself larger than the buffer it lives in. Every other bound + * in the parser is derived from this one. */ +START_TEST(test_fdt_open_rejects_totalsize_beyond_capacity) +{ + static uint8_t buf[0x200]; + uint32_t total; + fdt_ctx ctx; + + total = build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abc\0", 4); + + /* honest capacity: accepted */ + ck_assert_int_eq(fdt_open(&ctx, buf, total), 0); + ck_assert_uint_eq(fdt_size(&ctx), total); + + /* one byte short of what the header claims: rejected */ + ck_assert_int_lt(fdt_open(&ctx, buf, total - 1), 0); + ck_assert_ptr_null(ctx.blob); + + /* header claims far more than the caller can address */ + hdr_set(buf, FDT_H_TOTALSIZE, 0x10000000); + ck_assert_int_lt(fdt_open(&ctx, buf, total), 0); +} +END_TEST + +START_TEST(test_fdt_open_rejects_unaligned_blob) +{ + static uint8_t buf[0x200 + 4]; + uint32_t total; + fdt_ctx ctx; + + total = build_compat_fdt(buf, sizeof(buf) - 4, + (const uint8_t *)"abc\0", 4); + /* the parser makes aligned 32-bit loads, so it must refuse a base + * it cannot make them from */ + memmove(buf + 1, buf, total); + ck_assert_int_lt(fdt_open(&ctx, buf + 1, total), 0); +} +END_TEST + +/* A structure block whose string table lies past the declared end of + * the blob must be rejected. */ +START_TEST(test_fdt_open_rejects_unbounded_string_area) +{ + static uint8_t buf[0x200]; + uint32_t total; + fdt_ctx ctx; + + total = build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abc\0", 4); + hdr_set(buf, FDT_H_OFF_STRINGS, total); + + ck_assert_int_lt(fdt_open(&ctx, buf, total), 0); +} +END_TEST + +/* The structure block must not overlap the string table. */ +START_TEST(test_fdt_open_rejects_overlapping_areas) +{ + static uint8_t buf[0x200]; + uint32_t total; + fdt_ctx ctx; + + total = build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abc\0", 4); + hdr_set(buf, FDT_H_SIZE_STRUCT, total); + + ck_assert_int_lt(fdt_open(&ctx, buf, total), 0); +} +END_TEST + +/* off_dt_strings + size_dt_strings must not wrap uint32_t. */ +START_TEST(test_fdt_open_rejects_dt_strings_area_overflow) +{ + static uint8_t buf[0x200]; + uint32_t total; + fdt_ctx ctx; + + total = build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abc\0", 4); + hdr_set(buf, FDT_H_SIZE_STRINGS, 0xFFFFFFFC); + + ck_assert_int_lt(fdt_open(&ctx, buf, total), 0); +} +END_TEST + +/* A property length that wraps the cursor arithmetic must be caught by + * the structural walk, not handed to a caller as a negative int that + * becomes a ~4GB memcpy size. */ +START_TEST(test_fdt_open_rejects_oversized_prop_len) +{ + static uint8_t buf[0x200]; + uint32_t total; + fdt_ctx ctx; + + total = build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abc\0", 4); + /* the property record starts 8 bytes into the structure block, so + * its length field is at +12 */ + be32(buf + rd_be32(buf + FDT_H_OFF_STRUCT) + 12, 0xFFFFFFFF); + + ck_assert_int_lt(fdt_open(&ctx, buf, total), 0); +} +END_TEST + +/* A tree with no properties has an empty strings block, which is legal + * and must be accepted - the parser's "strings block ends in a NUL" + * invariant only applies when there are strings. */ +START_TEST(test_fdt_open_accepts_empty_strings_block) +{ + static uint8_t buf[0x200]; + uint8_t sblk[4 * 4]; + uint32_t i = 0, total; + fdt_ctx ctx; + + be32(sblk + i, FDT_BEGIN_NODE); i += 4; + be32(sblk + i, 0); i += 4; + be32(sblk + i, FDT_END_NODE); i += 4; + be32(sblk + i, FDT_END); i += 4; + + total = build_fdt(buf, sizeof(buf), sblk, i, "", 0); + ck_assert_int_eq(fdt_open(&ctx, buf, total), 0); + ck_assert_int_eq(fdt_path_offset(&ctx, "/"), 0); + /* with no strings, every string offset is out of range */ + ck_assert_ptr_null(fdt_get_string(&ctx, 0, NULL)); + ck_assert_ptr_null(fdt_getprop(&ctx, 0, "compatible", NULL)); +} +END_TEST + +/* Nesting must balance and there must be exactly one root. */ +START_TEST(test_fdt_open_rejects_two_roots) +{ + static uint8_t buf[0x200]; + uint8_t sblk[7 * 4]; + uint32_t i = 0, total; + fdt_ctx ctx; + + be32(sblk + i, FDT_BEGIN_NODE); i += 4; + be32(sblk + i, 0); i += 4; + be32(sblk + i, FDT_END_NODE); i += 4; + be32(sblk + i, FDT_BEGIN_NODE); i += 4; /* a second root */ + be32(sblk + i, 0); i += 4; + be32(sblk + i, FDT_END_NODE); i += 4; + be32(sblk + i, FDT_END); i += 4; + + total = build_fdt(buf, sizeof(buf), sblk, i, "", 1); + ck_assert_int_lt(fdt_open(&ctx, buf, total), 0); +} +END_TEST + +/* ------------------------------------------------------------------ */ +/* fdt_get_string */ +/* ------------------------------------------------------------------ */ + +static uint32_t build_strings_fdt(uint8_t *buf, uint32_t bufsz) +{ + uint8_t sblk[64]; + uint32_t i = 0; + + be32(sblk + i, FDT_BEGIN_NODE); i += 4; + be32(sblk + i, 0); i += 4; + be32(sblk + i, FDT_END_NODE); i += 4; + be32(sblk + i, FDT_END); i += 4; + + return build_fdt(buf, bufsz, sblk, i, "serial\0console", 15); +} + START_TEST(test_fdt_get_string_rejects_out_of_range_offset) { - struct { - struct fdt_header hdr; - char strings[8]; - char after[4]; - } blob; + static uint8_t buf[0x200]; + uint32_t total; + fdt_ctx ctx; int len = 1234; const char *s; - memset(&blob, 0, sizeof(blob)); - fdt_set_totalsize(&blob, sizeof(blob.hdr) + sizeof(blob.strings)); - fdt_set_off_dt_strings(&blob, sizeof(blob.hdr)); - fdt_set_size_dt_strings(&blob, sizeof(blob.strings)); - fdt_set_magic(&blob, FDT_MAGIC); - fdt_set_version(&blob, 17); - fdt_set_last_comp_version(&blob, 16); - memcpy(blob.strings, "chosen", sizeof("chosen")); - blob.after[0] = 'X'; - blob.after[1] = '\0'; - - s = fdt_get_string(&blob, (int)sizeof(blob.strings), &len); + total = build_strings_fdt(buf, sizeof(buf)); + ck_assert_int_eq(fdt_open(&ctx, buf, total), 0); + s = fdt_get_string(&ctx, 15, &len); /* exactly one past the block */ ck_assert_ptr_null(s); ck_assert_int_eq(len, -FDT_ERR_BADOFFSET); + + s = fdt_get_string(&ctx, -1, &len); + ck_assert_ptr_null(s); + ck_assert_int_lt(len, 0); } END_TEST START_TEST(test_fdt_get_string_returns_string_with_valid_offset) { - struct { - struct fdt_header hdr; - char strings[16]; - } blob; + static uint8_t buf[0x200]; + uint32_t total; + fdt_ctx ctx; int len = -1; const char *s; - memset(&blob, 0, sizeof(blob)); - fdt_set_totalsize(&blob, sizeof(blob.hdr) + sizeof(blob.strings)); - fdt_set_off_dt_strings(&blob, sizeof(blob.hdr)); - fdt_set_size_dt_strings(&blob, sizeof(blob.strings)); - fdt_set_magic(&blob, FDT_MAGIC); - fdt_set_version(&blob, 17); - fdt_set_last_comp_version(&blob, 16); - memcpy(blob.strings, "serial\0console\0", 15); - - s = fdt_get_string(&blob, 7, &len); + total = build_strings_fdt(buf, sizeof(buf)); + ck_assert_int_eq(fdt_open(&ctx, buf, total), 0); + s = fdt_get_string(&ctx, 7, &len); ck_assert_ptr_nonnull(s); ck_assert_str_eq(s, "console"); ck_assert_int_eq(len, 7); } END_TEST -/* Minimal FIT with a single /images/kernel-1 node whose `data` property - * declares len=0xFFFFFFFF. There is no `load` (and no `compression`), so - * fit_load_image_inner() takes the pass-through branch. Before the - * fdt_next_tag() length check, the oversized len wrapped the cursor - * arithmetic, slipped past the bounds check, and was handed back as - * *lenp = -1 - which update_ram.c then aliased into a ~4GB memcpy size. - * The loader must instead fail closed (return NULL). */ -static const uint8_t fit_data_len_overflow[] = { - /* header */ - 0xd0, 0x0d, 0xfe, 0xed, /* magic */ - 0x00, 0x00, 0x00, 0x81, /* totalsize = 129 */ - 0x00, 0x00, 0x00, 0x38, /* off_dt_struct = 56 */ - 0x00, 0x00, 0x00, 0x7c, /* off_dt_strings = 124 */ - 0x00, 0x00, 0x00, 0x28, /* off_mem_rsvmap = 40 */ - 0x00, 0x00, 0x00, 0x11, /* version = 17 */ - 0x00, 0x00, 0x00, 0x10, /* last_comp_version = 16 */ - 0x00, 0x00, 0x00, 0x00, /* boot_cpuid_phys */ - 0x00, 0x00, 0x00, 0x05, /* size_dt_strings = 5 */ - 0x00, 0x00, 0x00, 0x44, /* size_dt_struct = 68 */ - /* mem_rsvmap terminator (offset 40) */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - /* struct block (offset 56) */ - 0x00, 0x00, 0x00, 0x01, /* BEGIN_NODE root */ - 0x00, 0x00, 0x00, 0x00, /* "" */ - 0x00, 0x00, 0x00, 0x01, /* BEGIN_NODE images */ - 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x00, 0x00, /* "images\0\0" */ - 0x00, 0x00, 0x00, 0x01, /* BEGIN_NODE kernel-1 */ - 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x2d, 0x31, - 0x00, 0x00, 0x00, 0x00, /* "kernel-1\0\0\0\0" */ - 0x00, 0x00, 0x00, 0x03, /* FDT_PROP */ - 0xff, 0xff, 0xff, 0xff, /* len = 0xFFFFFFFF */ - 0x00, 0x00, 0x00, 0x00, /* nameoff = 0 ("data") */ - 0x00, 0x00, 0x00, 0x00, /* data (4 bytes) */ - 0x00, 0x00, 0x00, 0x02, /* END_NODE kernel-1 */ - 0x00, 0x00, 0x00, 0x02, /* END_NODE images */ - 0x00, 0x00, 0x00, 0x02, /* END_NODE root */ - 0x00, 0x00, 0x00, 0x09, /* FDT_END */ - /* strings block (offset 124) */ - 0x64, 0x61, 0x74, 0x61, 0x00, /* "data\0" */ -}; +/* ------------------------------------------------------------------ */ +/* compatible string-list matching */ +/* ------------------------------------------------------------------ */ -START_TEST(test_fit_load_image_rejects_oversized_prop_len) +/* An entry whose declared length equals the search string leaves no + * room for a NUL, so it must not match: the comparison must not read + * the alignment padding as if it were the terminator. */ +START_TEST(test_fdt_compatible_unterminated_exact_len_no_match) { - static uint8_t fit_scratch[sizeof(fit_data_len_overflow)]; - int len = 0; - void *ret; + static uint8_t buf[0x200]; + uint32_t total; + fdt_ctx ctx; - memcpy(fit_scratch, fit_data_len_overflow, sizeof(fit_scratch)); + total = build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abc", 3); + ck_assert_int_eq(fdt_open(&ctx, buf, total), 0); + ck_assert_int_lt(fdt_node_offset_by_compatible(&ctx, -1, "abc"), 0); +} +END_TEST - ret = fit_load_image_ex(fit_scratch, "kernel-1", &len, 64 * 1024); +/* A properly terminated entry of exactly the search length matches. */ +START_TEST(test_fdt_compatible_terminated_exact_len_match) +{ + static uint8_t buf[0x200]; + uint32_t total; + fdt_ctx ctx; - /* Must fail closed: never return a live pointer with a negative - * length that a caller could turn into a giant memcpy size. */ - ck_assert_ptr_null(ret); + total = build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abc\0", 4); + ck_assert_int_eq(fdt_open(&ctx, buf, total), 0); + ck_assert_int_eq(fdt_node_offset_by_compatible(&ctx, -1, "abc"), 0); } 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) +/* Multi-string lists keep working: the second entry matches. */ +START_TEST(test_fdt_compatible_multi_string_list_match) { - static uint8_t buf[256]; - int rc; + static uint8_t buf[0x200]; + uint32_t total; + fdt_ctx ctx; - 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); + total = build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"xy\0abc\0", 8); + ck_assert_int_eq(fdt_open(&ctx, buf, total), 0); + ck_assert_int_eq(fdt_node_offset_by_compatible(&ctx, -1, "abc"), 0); +} +END_TEST - rc = fdt_shrink(buf); +/* A terminated entry that merely starts with the search string does not + * match - the entry is longer. */ +START_TEST(test_fdt_compatible_prefix_entry_no_match) +{ + static uint8_t buf[0x200]; + uint32_t total; + fdt_ctx ctx; - ck_assert_int_lt(rc, 0); - ck_assert_uint_eq(fdt_totalsize(buf), sizeof(buf)); + total = build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abcd\0", 5); + ck_assert_int_eq(fdt_open(&ctx, buf, total), 0); + ck_assert_int_lt(fdt_node_offset_by_compatible(&ctx, -1, "abc"), 0); } END_TEST -/* Minimal FDT whose root node carries a `compatible` property whose value has - * no NUL terminator within its declared length (len=4, "AAAA"). - * fdt_node_offset_by_compatible() walks the (possibly multi-string) compatible - * value with memchr(prop, '\0', len); */ -static const uint8_t fdt_compatible_no_terminator[] = { - /* header */ - 0xd0, 0x0d, 0xfe, 0xed, /* magic */ - 0x00, 0x00, 0x00, 0x63, /* totalsize = 99 */ - 0x00, 0x00, 0x00, 0x38, /* off_dt_struct = 56 */ - 0x00, 0x00, 0x00, 0x58, /* off_dt_strings = 88 */ - 0x00, 0x00, 0x00, 0x28, /* off_mem_rsvmap = 40 */ - 0x00, 0x00, 0x00, 0x11, /* version = 17 */ - 0x00, 0x00, 0x00, 0x10, /* last_comp_version = 16 */ - 0x00, 0x00, 0x00, 0x00, /* boot_cpuid_phys */ - 0x00, 0x00, 0x00, 0x0b, /* size_dt_strings = 11 */ - 0x00, 0x00, 0x00, 0x20, /* size_dt_struct = 32 */ - /* mem_rsvmap terminator (offset 40) */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - /* struct block (offset 56) */ - 0x00, 0x00, 0x00, 0x01, /* BEGIN_NODE root */ - 0x00, 0x00, 0x00, 0x00, /* "" */ - 0x00, 0x00, 0x00, 0x03, /* FDT_PROP */ - 0x00, 0x00, 0x00, 0x04, /* len = 4 */ - 0x00, 0x00, 0x00, 0x00, /* nameoff = 0 ("compatible") */ - 0x41, 0x41, 0x41, 0x41, /* value "AAAA" -- no NUL */ - 0x00, 0x00, 0x00, 0x02, /* END_NODE root */ - 0x00, 0x00, 0x00, 0x09, /* FDT_END */ - /* strings block (offset 88) */ - 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, /* "compatib" */ - 0x6c, 0x65, 0x00, /* "le\0" */ -}; - +/* A value with no NUL inside its declared length is a legal property + * (values are opaque byte arrays), so the blob is accepted - but the + * string-list walk must terminate rather than run past the property. */ START_TEST(test_fdt_node_offset_by_compatible_terminates_on_unterminated_prop) { - int off; + static uint8_t buf[0x200]; + uint32_t total; + fdt_ctx ctx; - /* complen = strlen("foo") = 3 <= 4 = declared property length, so the - * inner walk loop is entered; "foo" != "AAAA" so it does not match and - * falls through to the memchr() advance */ - off = fdt_node_offset_by_compatible(fdt_compatible_no_terminator, -1, "foo"); - - ck_assert_int_lt(off, 0); + total = build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"AAAA", 4); + ck_assert_int_eq(fdt_open(&ctx, buf, total), 0); + ck_assert_int_lt(fdt_node_offset_by_compatible(&ctx, -1, "foo"), 0); } END_TEST -/* Build a minimal FDT in buf (zero-initialized, so padding bytes are - * 0x00): root node with a `compatible` property whose raw value is - * `len` bytes at `val`. */ -static void build_compat_fdt(uint8_t *buf, size_t size, - const uint8_t *val, uint32_t len) +/* ------------------------------------------------------------------ */ +/* Path lookup */ +/* ------------------------------------------------------------------ */ + +/* Build /soc/serial@100 plus a decoy node also named "serial@100" at the + * top level, so a name-anywhere search and a path search disagree. */ +static uint32_t build_path_fdt(uint8_t *buf, uint32_t bufsz) { - struct fdt_header *hdr; - uint32_t *s; - uint32_t val_aligned = (len + 3u) & ~3u; - uint32_t struct_off = 0x40; - uint32_t strings_off = 0x80; - - memset(buf, 0, size); - - hdr = (struct fdt_header *)buf; - hdr->magic = fdt32_to_cpu(FDT_MAGIC); - fdt_set_totalsize(hdr, 0x100); - fdt_set_off_dt_struct(hdr, struct_off); - fdt_set_off_dt_strings(hdr, strings_off); - fdt_set_off_mem_rsvmap(hdr, 0x28); - fdt_set_version(hdr, 17); - fdt_set_last_comp_version(hdr, 16); - fdt_set_size_dt_struct(hdr, - 8u + 12u + val_aligned + 4u + 4u); /* node hdr, prop, end, FDT_END */ - fdt_set_size_dt_strings(hdr, sizeof("compatible")); - memcpy(buf + strings_off, "compatible", sizeof("compatible")); - - s = (uint32_t *)(buf + struct_off); - s[0] = fdt32_to_cpu(FDT_BEGIN_NODE); /* root */ - s[1] = 0; /* empty name */ - s[2] = fdt32_to_cpu(FDT_PROP); - s[3] = fdt32_to_cpu(len); - s[4] = fdt32_to_cpu(0); /* nameoff of "compatible" */ - memcpy(buf + struct_off + 8 + 12, val, len); - s[5 + val_aligned / 4u] = fdt32_to_cpu(FDT_END_NODE); - s[6 + val_aligned / 4u] = fdt32_to_cpu(FDT_END); -} - -/* A compatible entry whose declared length equals the search string - * (no room for a NUL) must not match: before the fix the comparison - * read one byte past the declared length (the 4-byte alignment - * padding, zero here) as if it were the terminator and accepted the - * entry. */ -START_TEST(test_fdt_compatible_unterminated_exact_len_no_match) + uint8_t sblk[128]; + uint32_t i = 0; + + be32(sblk + i, FDT_BEGIN_NODE); i += 4; + be32(sblk + i, 0); i += 4; /* root */ + + be32(sblk + i, FDT_BEGIN_NODE); i += 4; + memcpy(sblk + i, "serial@100\0\0", 12); i += 12; /* decoy at /serial@100 */ + be32(sblk + i, FDT_END_NODE); i += 4; + + be32(sblk + i, FDT_BEGIN_NODE); i += 4; + memcpy(sblk + i, "soc\0", 4); i += 4; + be32(sblk + i, FDT_BEGIN_NODE); i += 4; + memcpy(sblk + i, "serial@100\0\0", 12); i += 12; /* /soc/serial@100 */ + be32(sblk + i, FDT_END_NODE); i += 4; + be32(sblk + i, FDT_END_NODE); i += 4; /* close soc */ + + be32(sblk + i, FDT_END_NODE); i += 4; /* close root */ + be32(sblk + i, FDT_END); i += 4; + + return build_fdt(buf, bufsz, sblk, i, "", 1); +} + +START_TEST(test_fdt_path_offset_resolves_by_path) { - static uint8_t buf[0x100]; - int off; + static uint8_t buf[0x200]; + uint32_t total; + fdt_ctx ctx; + int by_path, by_name, soc; + + total = build_path_fdt(buf, sizeof(buf)); + ck_assert_int_eq(fdt_open(&ctx, buf, total), 0); + + ck_assert_int_eq(fdt_path_offset(&ctx, "/"), 0); + + by_path = fdt_path_offset(&ctx, "/soc/serial@100"); + ck_assert_int_gt(by_path, 0); + + /* The tree-wide name search finds the decoy first; the path lookup + * does not. That difference is the point of having both. */ + by_name = fdt_find_node_offset(&ctx, -1, "serial@100"); + ck_assert_int_gt(by_name, 0); + ck_assert_int_ne(by_path, by_name); + + /* a unit address may be omitted from a path component */ + ck_assert_int_eq(fdt_path_offset(&ctx, "/soc/serial"), by_path); + + /* the public direct-child lookup agrees with the path lookup */ + soc = fdt_path_offset(&ctx, "/soc"); + ck_assert_int_gt(soc, 0); + ck_assert_int_eq(fdt_subnode_offset(&ctx, soc, "serial"), by_path); + ck_assert_int_eq(fdt_subnode_offset(&ctx, soc, "serial@100"), by_path); + ck_assert_int_eq(fdt_subnode_offset(&ctx, 0, "missing"), + -FDT_ERR_NOTFOUND); + ck_assert_int_eq(fdt_subnode_offset(&ctx, 0, NULL), -FDT_ERR_BADARG); + /* asked at the root it finds the decoy, not the /soc one - a direct + * child lookup never descends */ + ck_assert_int_eq(fdt_subnode_offset(&ctx, 0, "serial"), by_name); + + ck_assert_int_lt(fdt_path_offset(&ctx, "/soc/nope"), 0); + ck_assert_int_lt(fdt_path_offset(&ctx, "soc"), 0); /* must be absolute */ + + /* A component far longer than any name in the tree must not match, + * and must not compare past the end of the name it is tested + * against. The lookup names come from attacker-influenced places + * (a FIT's `default` string, for one), so the length is not bounded + * by anything in the blob. */ + ck_assert_int_lt(fdt_path_offset(&ctx, + "/soc/serial@100aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), 0); + ck_assert_int_lt(fdt_find_node_offset(&ctx, + -1, "serial@100aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), 0); +} +END_TEST - build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abc", 3); +/* ------------------------------------------------------------------ */ +/* Writing is bounded by the capacity, not by the header */ +/* ------------------------------------------------------------------ */ - off = fdt_node_offset_by_compatible(buf, -1, "abc"); - ck_assert_int_lt(off, 0); +START_TEST(test_fdt_setprop_bounded_by_capacity) +{ + static uint8_t buf[0x400]; + uint8_t big[0x400]; + uint32_t total; + fdt_ctx ctx; + int len = 0; + const void *val; + + total = build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abc\0", 4); + + /* Capacity exactly the blob: there is no room to grow, so adding a + * property must fail closed rather than write past the end. */ + ck_assert_int_eq(fdt_open(&ctx, buf, total), 0); + ck_assert_int_eq(fdt_setprop(&ctx, 0, "status", "okay", 5), + -FDT_ERR_NOSPACE); + + /* Same blob, honest larger window: the write now fits. */ + ck_assert_int_eq(fdt_open(&ctx, buf, (uint32_t)sizeof(buf)), 0); + ck_assert_int_eq(fdt_setprop(&ctx, 0, "status", "okay", 5), 0); + + val = fdt_getprop(&ctx, 0, "status", &len); + ck_assert_ptr_nonnull(val); + ck_assert_int_eq(len, 5); + ck_assert_str_eq((const char *)val, "okay"); + + /* The original property is still readable and unchanged. */ + val = fdt_getprop(&ctx, 0, "compatible", &len); + ck_assert_ptr_nonnull(val); + ck_assert_int_eq(len, 4); + ck_assert_str_eq((const char *)val, "abc"); + + /* A value that cannot fit in the window fails closed too. */ + memset(big, 'x', sizeof(big)); + ck_assert_int_lt(fdt_setprop(&ctx, 0, "blob", big, (int)sizeof(big)), 0); + + /* ...and the tree is still intact after the refusal. */ + val = fdt_getprop(&ctx, 0, "status", &len); + ck_assert_ptr_nonnull(val); + ck_assert_int_eq(len, 5); } END_TEST -/* A properly terminated entry of exactly the search length matches. */ -START_TEST(test_fdt_compatible_terminated_exact_len_match) +/* fdt_grow() must refuse headroom the window cannot hold, where the old + * blind fdt_set_totalsize() would have silently promised it. */ +START_TEST(test_fdt_grow_bounded_by_capacity) { - static uint8_t buf[0x100]; - int off; + static uint8_t buf[0x400]; + uint32_t total; + fdt_ctx ctx; + + total = build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abc\0", 4); - build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abc\0", 4); + ck_assert_int_eq(fdt_open(&ctx, buf, total), 0); + ck_assert_int_eq(fdt_grow(&ctx, 1024), -FDT_ERR_NOSPACE); + ck_assert_uint_eq(fdt_size(&ctx), total); - off = fdt_node_offset_by_compatible(buf, -1, "abc"); - ck_assert_int_eq(off, 0); /* the root node */ + ck_assert_int_eq(fdt_open(&ctx, buf, (uint32_t)sizeof(buf)), 0); + ck_assert_int_eq(fdt_grow(&ctx, 64), 0); + ck_assert_uint_eq(fdt_size(&ctx), total + 64); + ck_assert_int_eq(fdt_shrink(&ctx), 0); + ck_assert_uint_eq(fdt_size(&ctx), total); } END_TEST -/* Multi-string compatible lists keep working: the second entry - * matches. */ -START_TEST(test_fdt_compatible_multi_string_list_match) + +/* ------------------------------------------------------------------ */ +/* Node insertion and removal */ +/* ------------------------------------------------------------------ */ + +/* Root with two children, "keep" (carrying a property) and "drop". */ +static uint32_t build_two_child_fdt(uint8_t *buf, uint32_t bufsz) { - static uint8_t buf[0x100]; - int off; + uint8_t sblk[128]; + uint32_t i = 0; + + be32(sblk + i, FDT_BEGIN_NODE); i += 4; + be32(sblk + i, 0); i += 4; /* root */ - build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"xy\0abc\0", 8); + be32(sblk + i, FDT_BEGIN_NODE); i += 4; + memcpy(sblk + i, "keep\0\0\0\0", 8); i += 8; + be32(sblk + i, FDT_PROP); i += 4; + be32(sblk + i, 4); i += 4; /* len */ + be32(sblk + i, 0); i += 4; /* nameoff "compatible" */ + memcpy(sblk + i, "abc\0", 4); i += 4; + be32(sblk + i, FDT_END_NODE); i += 4; - off = fdt_node_offset_by_compatible(buf, -1, "abc"); - ck_assert_int_eq(off, 0); + be32(sblk + i, FDT_BEGIN_NODE); i += 4; + memcpy(sblk + i, "drop\0\0\0\0", 8); i += 8; + be32(sblk + i, FDT_END_NODE); i += 4; + + be32(sblk + i, FDT_END_NODE); i += 4; /* close root */ + be32(sblk + i, FDT_END); i += 4; + + return build_fdt(buf, bufsz, sblk, i, "compatible", + (uint32_t)sizeof("compatible")); +} + +START_TEST(test_fdt_del_node_removes_subtree) +{ + static uint8_t buf[0x400]; + uint32_t total; + fdt_ctx ctx; + int drop, keep, len = 0; + const void *val; + + total = build_two_child_fdt(buf, sizeof(buf)); + ck_assert_int_eq(fdt_open(&ctx, buf, (uint32_t)sizeof(buf)), 0); + + drop = fdt_path_offset(&ctx, "/drop"); + ck_assert_int_gt(drop, 0); + ck_assert_int_eq(fdt_del_node(&ctx, drop), 0); + + /* the blob is still well formed, and only that node is gone */ + ck_assert_int_eq(fdt_open(&ctx, buf, (uint32_t)sizeof(buf)), 0); + ck_assert_int_lt(fdt_path_offset(&ctx, "/drop"), 0); + ck_assert_int_lt(fdt_find_node_offset(&ctx, -1, "drop"), 0); + + keep = fdt_path_offset(&ctx, "/keep"); + ck_assert_int_gt(keep, 0); + val = fdt_getprop(&ctx, keep, "compatible", &len); + ck_assert_ptr_nonnull(val); + ck_assert_int_eq(len, 4); + ck_assert_str_eq((const char *)val, "abc"); + + /* the content shrank; totalsize only follows once asked to, since + * mutations never pull it below what a consumer was promised */ + ck_assert_uint_eq(fdt_size(&ctx), total); + ck_assert_int_eq(fdt_shrink(&ctx), 0); + ck_assert_uint_lt(fdt_size(&ctx), total); + ck_assert_int_eq(fdt_open(&ctx, buf, (uint32_t)sizeof(buf)), 0); + ck_assert_int_gt(fdt_path_offset(&ctx, "/keep"), 0); } END_TEST -/* A terminated entry that merely starts with the search string does - * not match (the entry is longer). */ -START_TEST(test_fdt_compatible_prefix_entry_no_match) +START_TEST(test_fdt_del_node_rejects_bad_offset) { - static uint8_t buf[0x100]; - int off; + static uint8_t buf[0x400]; + fdt_ctx ctx; + int keep; + + (void)build_two_child_fdt(buf, sizeof(buf)); + ck_assert_int_eq(fdt_open(&ctx, buf, (uint32_t)sizeof(buf)), 0); + + keep = fdt_path_offset(&ctx, "/keep"); + ck_assert_int_gt(keep, 0); + + /* not a node token, and a wildly out-of-range offset */ + ck_assert_int_lt(fdt_del_node(&ctx, keep + 4), 0); + ck_assert_int_lt(fdt_del_node(&ctx, 0x7000), 0); + /* nothing was disturbed */ + ck_assert_int_eq(fdt_open(&ctx, buf, (uint32_t)sizeof(buf)), 0); + ck_assert_int_gt(fdt_path_offset(&ctx, "/keep"), 0); +} +END_TEST - build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abcd\0", 5); +/* fdt_add_subnode() is on the common boot path: every HAL that inserts + * /chosen into a DTB that lacks one goes through it. */ +START_TEST(test_fdt_add_subnode) +{ + static uint8_t buf[0x400]; + fdt_ctx ctx; + int off, again, len = 0; + const void *val; + + (void)build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abc\0", 4); + ck_assert_int_eq(fdt_open(&ctx, buf, (uint32_t)sizeof(buf)), 0); + ck_assert_int_lt(fdt_path_offset(&ctx, "/chosen"), 0); + + off = fdt_add_subnode(&ctx, 0, "chosen"); + ck_assert_int_gt(off, 0); + + /* the new node is reachable both ways, and takes properties */ + ck_assert_int_eq(fdt_path_offset(&ctx, "/chosen"), off); + ck_assert_int_eq(fdt_subnode_offset(&ctx, 0, "chosen"), off); + ck_assert_int_eq(fdt_setprop(&ctx, off, "bootargs", "console=ttyS0", 14), + 0); + + /* adding it twice is refused, and the tree is unchanged */ + again = fdt_add_subnode(&ctx, 0, "chosen"); + ck_assert_int_eq(again, -FDT_ERR_EXISTS); + + /* still parses, and the root's original property survived */ + ck_assert_int_eq(fdt_open(&ctx, buf, (uint32_t)sizeof(buf)), 0); + off = fdt_path_offset(&ctx, "/chosen"); + ck_assert_int_gt(off, 0); + val = fdt_getprop(&ctx, off, "bootargs", &len); + ck_assert_ptr_nonnull(val); + ck_assert_str_eq((const char *)val, "console=ttyS0"); + val = fdt_getprop(&ctx, 0, "compatible", &len); + ck_assert_ptr_nonnull(val); + ck_assert_str_eq((const char *)val, "abc"); +} +END_TEST - off = fdt_node_offset_by_compatible(buf, -1, "abc"); - ck_assert_int_lt(off, 0); +START_TEST(test_fdt_add_subnode_bounded_by_capacity) +{ + static uint8_t buf[0x400]; + uint32_t total; + fdt_ctx ctx; + + total = build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abc\0", 4); + + /* no room to grow: must fail closed and leave the tree intact */ + ck_assert_int_eq(fdt_open(&ctx, buf, total), 0); + ck_assert_int_eq(fdt_add_subnode(&ctx, 0, "chosen"), -FDT_ERR_NOSPACE); + ck_assert_int_eq(fdt_open(&ctx, buf, total), 0); + ck_assert_int_lt(fdt_path_offset(&ctx, "/chosen"), 0); + ck_assert_uint_eq(fdt_size(&ctx), total); } END_TEST -/* A finalized DTB whose string table lies past the declared end of - * the blob must be rejected: before the fix fdt_check_header() - * validated only magic and version, and fdt_get_string() formed the - * string-table pointer from the unvalidated header fields. */ -START_TEST(test_fdt_check_header_rejects_unbounded_string_area) +/* ------------------------------------------------------------------ */ +/* Property resize */ +/* ------------------------------------------------------------------ */ + +/* Overwriting an existing property is the branch that runs on every real + * boot (a DTB that already ships bootargs, T10xx rewriting reg/status). + * Exercise both directions, since grow and shrink use the same splice + * arithmetic with opposite signs. */ +START_TEST(test_fdt_setprop_resizes_existing_property) { - static uint8_t buf[0x100]; + static uint8_t buf[0x400]; + fdt_ctx ctx; int len = 0; - const char *s; + const void *val; + + (void)build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abc\0", 4); + ck_assert_int_eq(fdt_open(&ctx, buf, (uint32_t)sizeof(buf)), 0); + ck_assert_int_eq(fdt_setprop(&ctx, 0, "status", "okay", 5), 0); + + /* grow `compatible` past its original length */ + ck_assert_int_eq(fdt_setprop(&ctx, 0, "compatible", + "a-much-longer-compatible-string", 32), 0); + ck_assert_int_eq(fdt_open(&ctx, buf, (uint32_t)sizeof(buf)), 0); + val = fdt_getprop(&ctx, 0, "compatible", &len); + ck_assert_ptr_nonnull(val); + ck_assert_int_eq(len, 32); + ck_assert_str_eq((const char *)val, "a-much-longer-compatible-string"); + /* the sibling property moved intact */ + val = fdt_getprop(&ctx, 0, "status", &len); + ck_assert_ptr_nonnull(val); + ck_assert_int_eq(len, 5); + ck_assert_str_eq((const char *)val, "okay"); + + /* now shrink it back */ + ck_assert_int_eq(fdt_setprop(&ctx, 0, "compatible", "xy", 3), 0); + ck_assert_int_eq(fdt_open(&ctx, buf, (uint32_t)sizeof(buf)), 0); + val = fdt_getprop(&ctx, 0, "compatible", &len); + ck_assert_ptr_nonnull(val); + ck_assert_int_eq(len, 3); + ck_assert_str_eq((const char *)val, "xy"); + val = fdt_getprop(&ctx, 0, "status", &len); + ck_assert_ptr_nonnull(val); + ck_assert_int_eq(len, 5); + ck_assert_str_eq((const char *)val, "okay"); + + /* a zero-length property is legal */ + ck_assert_int_eq(fdt_setprop(&ctx, 0, "compatible", NULL, 0), 0); + ck_assert_int_eq(fdt_open(&ctx, buf, (uint32_t)sizeof(buf)), 0); + val = fdt_getprop(&ctx, 0, "compatible", &len); + ck_assert_ptr_nonnull(val); + ck_assert_int_eq(len, 0); +} +END_TEST - build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abc\0", 4); - /* push the string table past the declared end of the blob */ - fdt_set_off_dt_strings((struct fdt_header *)buf, 0x100); +/* ------------------------------------------------------------------ */ +/* initrd fixup */ +/* ------------------------------------------------------------------ */ - ck_assert_int_eq(fdt_check_header(buf), -FDT_ERR_BADSTRUCTURE); +static uint64_t rd_be64(const uint8_t *p) +{ + uint64_t v = 0; + int i; - s = fdt_get_string(buf, 0, &len); - ck_assert_ptr_null(s); - ck_assert_int_lt(len, 0); + for (i = 0; i < 8; i++) { + v = (v << 8) | (uint64_t)p[i]; + } + return v; +} + +START_TEST(test_fdt_fixup_initrd) +{ + static uint8_t buf[0x400]; + fdt_ctx ctx; + int off, len = 0; + const void *val; + + /* /chosen absent: it must be created */ + (void)build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abc\0", 4); + ck_assert_int_eq(fdt_open(&ctx, buf, (uint32_t)sizeof(buf)), 0); + ck_assert_int_eq(fdt_fixup_initrd(&ctx, 0x10000000ULL, 0x2000ULL), 0); + + ck_assert_int_eq(fdt_open(&ctx, buf, (uint32_t)sizeof(buf)), 0); + off = fdt_path_offset(&ctx, "/chosen"); + ck_assert_int_gt(off, 0); + + val = fdt_getprop(&ctx, off, "linux,initrd-start", &len); + ck_assert_ptr_nonnull(val); + ck_assert_int_eq(len, 8); + ck_assert_uint_eq(rd_be64((const uint8_t *)val), 0x10000000ULL); + + val = fdt_getprop(&ctx, off, "linux,initrd-end", &len); + ck_assert_ptr_nonnull(val); + ck_assert_int_eq(len, 8); + ck_assert_uint_eq(rd_be64((const uint8_t *)val), 0x10002000ULL); + + /* /chosen already present: the values are replaced in place */ + ck_assert_int_eq(fdt_fixup_initrd(&ctx, 0x20000000ULL, 0x100ULL), 0); + ck_assert_int_eq(fdt_open(&ctx, buf, (uint32_t)sizeof(buf)), 0); + off = fdt_path_offset(&ctx, "/chosen"); + ck_assert_int_gt(off, 0); + val = fdt_getprop(&ctx, off, "linux,initrd-end", &len); + ck_assert_ptr_nonnull(val); + ck_assert_uint_eq(rd_be64((const uint8_t *)val), 0x20000100ULL); } END_TEST -/* The structure block must not overlap the string table. */ -START_TEST(test_fdt_check_header_rejects_overlapping_areas) +/* ------------------------------------------------------------------ */ +/* fdt_peek_size */ +/* ------------------------------------------------------------------ */ + +START_TEST(test_fdt_peek_size_header_only) { - static uint8_t buf[0x100]; + static uint8_t buf[0x200]; + uint32_t total, peeked = 0; + + total = build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abc\0", 4); - build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abc\0", 4); - fdt_set_size_dt_struct((struct fdt_header *)buf, 0x100); + /* Reports the declared size from the header alone - the caller has + * not fetched the body yet. */ + ck_assert_int_eq(fdt_peek_size(buf, FDT_HEADER_SIZE, &peeked), 0); + ck_assert_uint_eq(peeked, total); - ck_assert_int_eq(fdt_check_header(buf), -FDT_ERR_BADSTRUCTURE); + /* Fewer bytes than a header is not enough to answer. */ + ck_assert_int_lt(fdt_peek_size(buf, FDT_HEADER_SIZE - 1, &peeked), 0); + + /* Bad magic and out-of-range sizes are still rejected. */ + hdr_set(buf, FDT_H_TOTALSIZE, WOLFBOOT_DTS_MAX_SIZE + 1); + ck_assert_int_lt(fdt_peek_size(buf, FDT_HEADER_SIZE, &peeked), 0); + hdr_set(buf, FDT_H_TOTALSIZE, total); + hdr_set(buf, FDT_H_MAGIC, 0xDEADBEEF); + ck_assert_int_lt(fdt_peek_size(buf, FDT_HEADER_SIZE, &peeked), 0); } END_TEST +/* ------------------------------------------------------------------ */ +/* FIT */ +/* ------------------------------------------------------------------ */ + /* FIT whose configuration `kernel` property is not NUL-terminated * within its declared length: fit_find_images() must still honor the * valid `default` but reject the malformed image name instead of @@ -387,21 +879,26 @@ static const uint8_t fit_cfg_unterminated_kernel[] = { START_TEST(test_fit_find_images_rejects_unterminated_image_name) { - static uint8_t fit_scratch[sizeof(fit_cfg_unterminated_kernel)]; - const char *conf = NULL, *kern = NULL, *fdt = NULL; + static uint8_t fit_scratch[sizeof(fit_cfg_unterminated_kernel)] + __attribute__((aligned(4))); + const char *conf = NULL, *kern = NULL, *dt = NULL; const char *rd = NULL, *fpga = NULL; + fdt_ctx ctx; memcpy(fit_scratch, fit_cfg_unterminated_kernel, sizeof(fit_scratch)); + ck_assert_int_eq(fdt_open(&ctx, fit_scratch, + (uint32_t)sizeof(fit_scratch)), 0); - conf = fit_find_images(fit_scratch, &kern, &fdt, &rd, &fpga); + conf = fit_find_images(&ctx, &kern, &dt, &rd, &fpga); /* The valid `default` is still honored... */ + ck_assert_ptr_nonnull(conf); ck_assert_str_eq(conf, "conf-1"); /* ...but the config's `kernel` property is not NUL-terminated * within its declared length, so it must be rejected rather than - * passed on to fdt_find_node_offset()/strlen(). */ + * passed on to a lookup that would strlen() it. */ ck_assert_ptr_null(kern); - ck_assert_ptr_null(fdt); + ck_assert_ptr_null(dt); ck_assert_ptr_null(rd); ck_assert_ptr_null(fpga); } @@ -411,20 +908,34 @@ static Suite *fdt_suite(void) { Suite *s = suite_create("fdt"); TCase *tc = tcase_create("fdt"); - /* Separate case with a hard timeout so an unterminated-property - * regression is reported as a failure rather than hanging the suite. */ + /* Separate case with a hard timeout so a non-terminating walk is + * reported as a failure rather than hanging the suite. */ TCase *tc_dos = tcase_create("fdt-dos"); + tcase_add_test(tc, test_fdt_open_rejects_totalsize_beyond_capacity); + tcase_add_test(tc, test_fdt_open_rejects_unaligned_blob); + tcase_add_test(tc, test_fdt_open_rejects_unbounded_string_area); + tcase_add_test(tc, test_fdt_open_rejects_overlapping_areas); + tcase_add_test(tc, test_fdt_open_rejects_dt_strings_area_overflow); + tcase_add_test(tc, test_fdt_open_rejects_oversized_prop_len); + tcase_add_test(tc, test_fdt_open_accepts_empty_strings_block); + tcase_add_test(tc, test_fdt_open_rejects_two_roots); 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); tcase_add_test(tc, test_fdt_compatible_unterminated_exact_len_no_match); tcase_add_test(tc, test_fdt_compatible_terminated_exact_len_match); tcase_add_test(tc, test_fdt_compatible_multi_string_list_match); tcase_add_test(tc, test_fdt_compatible_prefix_entry_no_match); - tcase_add_test(tc, test_fdt_check_header_rejects_unbounded_string_area); - tcase_add_test(tc, test_fdt_check_header_rejects_overlapping_areas); + tcase_add_test(tc, test_fdt_path_offset_resolves_by_path); + tcase_add_test(tc, test_fdt_setprop_bounded_by_capacity); + tcase_add_test(tc, test_fdt_grow_bounded_by_capacity); + tcase_add_test(tc, test_fdt_del_node_removes_subtree); + tcase_add_test(tc, test_fdt_del_node_rejects_bad_offset); + tcase_add_test(tc, test_fdt_add_subnode); + tcase_add_test(tc, test_fdt_add_subnode_bounded_by_capacity); + tcase_add_test(tc, test_fdt_setprop_resizes_existing_property); + tcase_add_test(tc, test_fdt_fixup_initrd); + tcase_add_test(tc, test_fdt_peek_size_header_only); tcase_add_test(tc, test_fit_find_images_rejects_unterminated_image_name); suite_add_tcase(s, tc); diff --git a/tools/unit-tests/unit-fit-fpga.c b/tools/unit-tests/unit-fit-fpga.c index 9902d8a467..6b612f8b11 100644 --- a/tools/unit-tests/unit-fit-fpga.c +++ b/tools/unit-tests/unit-fit-fpga.c @@ -49,7 +49,7 @@ static uint8_t g_struct[4096]; static uint32_t g_struct_len; static char g_strings[1024]; static uint32_t g_strings_len; -static uint8_t g_blob[8192]; +static uint8_t g_blob[8192] __attribute__((aligned(4))); static void be32_put(uint8_t* p, uint32_t v) { @@ -111,8 +111,12 @@ static void prop_str(const char* name, const char* val) } /* Assemble the header + reserve map + struct + strings into g_blob. */ -static void* fit_finish(void) +/* Returns a validated view of the built blob, or NULL if it did not + * pass fdt_open() - which is itself a useful assertion for these + * fixtures. */ +static fdt_ctx* fit_finish(void) { + static fdt_ctx ctx; uint32_t off_rsv = 40; /* header is 40 bytes (v17) */ uint32_t off_struct = off_rsv + 16; /* one terminating rsv entry */ uint32_t off_strings = off_struct + g_struct_len; @@ -133,7 +137,10 @@ static void* fit_finish(void) /* reserve map terminator already zeroed */ memcpy(g_blob + off_struct, g_struct, g_struct_len); memcpy(g_blob + off_strings, g_strings, g_strings_len); - return g_blob; + if (fdt_open(&ctx, g_blob, (uint32_t)sizeof(g_blob)) != 0) { + return NULL; + } + return &ctx; } static void fit_reset(void) @@ -153,7 +160,7 @@ START_TEST(test_fit_fpga_via_config) { const char *kernel = NULL, *flat_dt = NULL, *ramdisk = NULL, *fpga = NULL; const char* comp; - void* fit; + fdt_ctx* fit; fit_reset(); struct_u32(FDT_BEGIN_NODE); struct_str(""); /* root */ @@ -188,7 +195,7 @@ END_TEST START_TEST(test_fit_fpga_via_type_fallback) { const char *fpga = NULL; - void* fit; + fdt_ctx* fit; fit_reset(); struct_u32(FDT_BEGIN_NODE); struct_str(""); @@ -211,7 +218,7 @@ END_TEST START_TEST(test_fit_fpga_absent) { const char *fpga = (const char*)0x1; /* poison */ - void* fit; + fdt_ctx* fit; fit_reset(); struct_u32(FDT_BEGIN_NODE); struct_str(""); @@ -233,7 +240,7 @@ END_TEST START_TEST(test_fit_compatible_absent) { const char* comp; - void* fit; + fdt_ctx* fit; fit_reset(); struct_u32(FDT_BEGIN_NODE); struct_str(""); diff --git a/tools/unit-tests/unit-fit-gzip.c b/tools/unit-tests/unit-fit-gzip.c index 7c175fdacc..2b6ac863ae 100644 --- a/tools/unit-tests/unit-fit-gzip.c +++ b/tools/unit-tests/unit-fit-gzip.c @@ -182,6 +182,20 @@ static const uint8_t fit_gzip_no_load[] = { /* Test cases */ /* ------------------------------------------------------------------------- */ + +/* Open a validated view of a hand-built fixture. Returns NULL if the + * fixture does not pass fdt_open(), which the callers below treat the + * same as a load failure. */ +static fdt_ctx* fit_open(void* blob, uint32_t len) +{ + static fdt_ctx ctx; + + if (fdt_open(&ctx, blob, len) != 0) { + return NULL; + } + return &ctx; +} + #ifdef WOLFBOOT_GZIP START_TEST(test_fit_to_gzip_success) @@ -194,10 +208,10 @@ START_TEST(test_fit_to_gzip_success) /* fit_with_gzip_kernel is read-only; copy into a writable scratch * since fit_load_image_to() does not modify the FIT, but we still * want a clean buffer per test. */ - static uint8_t fit_scratch[sizeof(fit_with_gzip_kernel)]; + static uint8_t fit_scratch[sizeof(fit_with_gzip_kernel)] __attribute__((aligned(4))); memcpy(fit_scratch, fit_with_gzip_kernel, sizeof(fit_scratch)); - ret = fit_load_image_to(fit_scratch, "kernel-1", + ret = fit_load_image_to(fit_open(fit_scratch, (uint32_t)sizeof(fit_scratch)), "kernel-1", buf, (uint32_t)sizeof(buf), &len); ck_assert_ptr_eq(ret, buf); ck_assert_int_eq(len, FIT_PLAIN_LEN); @@ -210,10 +224,10 @@ START_TEST(test_fit_to_gzip_corrupt_returns_null) uint8_t buf[64]; int len = -1; void *ret; - static uint8_t fit_scratch[sizeof(fit_with_corrupt_gzip)]; + static uint8_t fit_scratch[sizeof(fit_with_corrupt_gzip)] __attribute__((aligned(4))); memcpy(fit_scratch, fit_with_corrupt_gzip, sizeof(fit_scratch)); - ret = fit_load_image_to(fit_scratch, "kernel-1", + ret = fit_load_image_to(fit_open(fit_scratch, (uint32_t)sizeof(fit_scratch)), "kernel-1", buf, (uint32_t)sizeof(buf), &len); ck_assert_ptr_null(ret); } @@ -224,10 +238,10 @@ START_TEST(test_fit_to_none_compression_copies_plain) uint8_t buf[64]; int len = -1; void *ret; - static uint8_t fit_scratch[sizeof(fit_with_none_comp)]; + static uint8_t fit_scratch[sizeof(fit_with_none_comp)] __attribute__((aligned(4))); memcpy(fit_scratch, fit_with_none_comp, sizeof(fit_scratch)); - ret = fit_load_image_to(fit_scratch, "kernel-1", + ret = fit_load_image_to(fit_open(fit_scratch, (uint32_t)sizeof(fit_scratch)), "kernel-1", buf, (uint32_t)sizeof(buf), &len); ck_assert_ptr_eq(ret, buf); ck_assert_int_eq(len, FIT_PLAIN_LEN); @@ -241,10 +255,10 @@ START_TEST(test_fit_ex_gzip_no_load_returns_null) * must refuse rather than pass compressed bytes through as raw. */ int len = -1; void *ret; - static uint8_t fit_scratch[sizeof(fit_gzip_no_load)]; + static uint8_t fit_scratch[sizeof(fit_gzip_no_load)] __attribute__((aligned(4))); memcpy(fit_scratch, fit_gzip_no_load, sizeof(fit_scratch)); - ret = fit_load_image_ex(fit_scratch, "kernel-1", &len, 64 * 1024); + ret = fit_load_image_ex(fit_open(fit_scratch, (uint32_t)sizeof(fit_scratch)), "kernel-1", &len, 64 * 1024); ck_assert_ptr_null(ret); } END_TEST @@ -258,10 +272,10 @@ START_TEST(test_fit_to_gzip_disabled_returns_null) uint8_t buf[64]; int len = -1; void *ret; - static uint8_t fit_scratch[sizeof(fit_with_gzip_kernel)]; + static uint8_t fit_scratch[sizeof(fit_with_gzip_kernel)] __attribute__((aligned(4))); memcpy(fit_scratch, fit_with_gzip_kernel, sizeof(fit_scratch)); - ret = fit_load_image_to(fit_scratch, "kernel-1", + ret = fit_load_image_to(fit_open(fit_scratch, (uint32_t)sizeof(fit_scratch)), "kernel-1", buf, (uint32_t)sizeof(buf), &len); ck_assert_ptr_null(ret); } @@ -279,7 +293,7 @@ START_TEST(test_fit_to_none_unterminated_fails_closed) uint8_t buf[64]; int len = -1; void *ret; - static uint8_t fit_scratch[sizeof(fit_with_none_comp)]; + static uint8_t fit_scratch[sizeof(fit_with_none_comp)] __attribute__((aligned(4))); uint8_t *p; unsigned i; @@ -301,7 +315,7 @@ START_TEST(test_fit_to_none_unterminated_fails_closed) p[-6] = 0; p[-5] = 4; - ret = fit_load_image_to(fit_scratch, "kernel-1", + ret = fit_load_image_to(fit_open(fit_scratch, (uint32_t)sizeof(fit_scratch)), "kernel-1", buf, (uint32_t)sizeof(buf), &len); ck_assert_ptr_null(ret); } @@ -314,10 +328,10 @@ START_TEST(test_fit_to_lzma_unknown_returns_null) uint8_t buf[64]; int len = -1; void *ret; - static uint8_t fit_scratch[sizeof(fit_with_lzma)]; + static uint8_t fit_scratch[sizeof(fit_with_lzma)] __attribute__((aligned(4))); memcpy(fit_scratch, fit_with_lzma, sizeof(fit_scratch)); - ret = fit_load_image_to(fit_scratch, "kernel-1", + ret = fit_load_image_to(fit_open(fit_scratch, (uint32_t)sizeof(fit_scratch)), "kernel-1", buf, (uint32_t)sizeof(buf), &len); ck_assert_ptr_null(ret); } @@ -330,12 +344,12 @@ START_TEST(test_fit_to_none_oversized_rejected) uint8_t buf[64]; int len = -1; void *ret; - static uint8_t fit_scratch[sizeof(fit_with_none_comp)]; + static uint8_t fit_scratch[sizeof(fit_with_none_comp)] __attribute__((aligned(4))); memcpy(fit_scratch, fit_with_none_comp, sizeof(fit_scratch)); memset(buf, 0xA5, sizeof(buf)); /* payload is FIT_PLAIN_LEN (23) bytes; bound the destination below it */ - ret = fit_load_image_to(fit_scratch, "kernel-1", buf, 8, &len); + ret = fit_load_image_to(fit_open(fit_scratch, (uint32_t)sizeof(fit_scratch)), "kernel-1", buf, 8, &len); ck_assert_ptr_null(ret); /* nothing may be written at or past the out_max bound */ ck_assert_uint_eq(buf[8], 0xA5); diff --git a/tools/unit-tests/unit-t10xx-dts-memac.c b/tools/unit-tests/unit-t10xx-dts-memac.c index cfb86b6e04..6104eba895 100644 --- a/tools/unit-tests/unit-t10xx-dts-memac.c +++ b/tools/unit-tests/unit-t10xx-dts-memac.c @@ -130,7 +130,7 @@ static struct phy_device phydevs[5]; #define DTB_BUF_SIZE (16 * 1024) struct dtb { - uint8_t buf[DTB_BUF_SIZE]; + uint8_t buf[DTB_BUF_SIZE] __attribute__((aligned(4))); uint32_t struct_off; /* absolute */ uint32_t struct_end; /* relative to the struct block */ uint32_t strings_off; /* absolute */ @@ -150,7 +150,10 @@ static uint32_t align4(uint32_t v) static void dtb_init(struct dtb *d) { memset(d, 0, sizeof(*d)); - d->struct_off = 0x30; + /* 0x28 (the reservation block) + one 16-byte (0,0) terminator entry: + * the spec requires that entry, and the parser checks there is room + * for it before the structure block starts. */ + d->struct_off = 0x38; d->struct_end = 0; d->strings_off = 0x400; d->strings_end = 1; /* offset 0 is the empty name */ @@ -262,8 +265,8 @@ static void dtb_finalize(struct dtb *d) hdr->off_dt_struct = cpu_to_fdt32(d->struct_off); hdr->off_dt_strings = cpu_to_fdt32(d->strings_off); hdr->off_mem_rsvmap = cpu_to_fdt32(0x28); - /* the 8 bytes after the 40-byte header are zero: an empty - * reservation list at the canonical spot */ + /* the 16 bytes after the 40-byte header are zero: an empty + * reservation list (its (0,0) terminator) at the canonical spot */ hdr->version = cpu_to_fdt32(17); hdr->last_comp_version = cpu_to_fdt32(16); hdr->boot_cpuid_phys = cpu_to_fdt32(0); @@ -358,6 +361,16 @@ static void teardown(void) * phydevs holds that port at FM1_10GEC1 (slot 4). It must be mapped * there, not skipped: skipping silently drops the 10G MAC fixup. Pre * fix the loop read phydevs[8] out of bounds instead. */ +/* A validated view of the built blob. The whole buffer is the window the + * fixups may use, which is what hal_dts_fixup() is told below. */ +static fdt_ctx* dtb_ctx(struct dtb *d) +{ + static fdt_ctx ctx; + + ck_assert_int_eq(fdt_open(&ctx, d->buf, (uint32_t)DTB_BUF_SIZE), 0); + return &ctx; +} + START_TEST(test_memac_10g_cell_index_mapped) { struct dtb d; @@ -365,11 +378,11 @@ START_TEST(test_memac_10g_cell_index_mapped) const void *mac; dtb_build_memac(&d, "memac0", 8); - ck_assert_int_eq(hal_dts_fixup(d.buf), 0); + ck_assert_int_eq(hal_dts_fixup(d.buf, (uint32_t)DTB_BUF_SIZE), 0); - off = fdt_node_offset_by_compatible(d.buf, -1, "fsl,fman-memac"); + off = fdt_node_offset_by_compatible(dtb_ctx(&d), -1, "fsl,fman-memac"); ck_assert_int_gt(off, 0); - mac = fdt_getprop(d.buf, off, "local-mac-address", &len); + mac = fdt_getprop(dtb_ctx(&d), off, "local-mac-address", &len); ck_assert_ptr_nonnull(mac); ck_assert_int_eq(len, 6); ck_assert_int_eq(((const uint8_t *)mac)[5], 0x14); /* phydevs[4] */ @@ -385,11 +398,11 @@ START_TEST(test_memac_unmapped_cell_index_skipped) const void *mac; dtb_build_memac(&d, "memac0", 9); - ck_assert_int_eq(hal_dts_fixup(d.buf), 0); + ck_assert_int_eq(hal_dts_fixup(d.buf, (uint32_t)DTB_BUF_SIZE), 0); - off = fdt_node_offset_by_compatible(d.buf, -1, "fsl,fman-memac"); + off = fdt_node_offset_by_compatible(dtb_ctx(&d), -1, "fsl,fman-memac"); ck_assert_int_gt(off, 0); - mac = fdt_getprop(d.buf, off, "local-mac-address", NULL); + mac = fdt_getprop(dtb_ctx(&d), off, "local-mac-address", NULL); ck_assert_ptr_null(mac); } END_TEST @@ -402,11 +415,11 @@ START_TEST(test_memac_valid_cell_index_fixed) const void *mac; dtb_build_memac(&d, "memac0", 1); - ck_assert_int_eq(hal_dts_fixup(d.buf), 0); + ck_assert_int_eq(hal_dts_fixup(d.buf, (uint32_t)DTB_BUF_SIZE), 0); - off = fdt_node_offset_by_compatible(d.buf, -1, "fsl,fman-memac"); + off = fdt_node_offset_by_compatible(dtb_ctx(&d), -1, "fsl,fman-memac"); ck_assert_int_gt(off, 0); - mac = fdt_getprop(d.buf, off, "local-mac-address", &len); + mac = fdt_getprop(dtb_ctx(&d), off, "local-mac-address", &len); ck_assert_ptr_nonnull(mac); ck_assert_int_eq(len, 6); ck_assert_int_eq(((const uint8_t *)mac)[5], 0x11); /* phydevs[1] */ @@ -422,19 +435,19 @@ START_TEST(test_memac_mixed_oob_then_valid) const void *mac; dtb_build_memac2(&d, 9, 2); - ck_assert_int_eq(hal_dts_fixup(d.buf), 0); + ck_assert_int_eq(hal_dts_fixup(d.buf, (uint32_t)DTB_BUF_SIZE), 0); - off0 = fdt_node_offset_by_compatible(d.buf, -1, "fsl,fman-memac"); - off1 = fdt_node_offset_by_compatible(d.buf, off0, "fsl,fman-memac"); + off0 = fdt_node_offset_by_compatible(dtb_ctx(&d), -1, "fsl,fman-memac"); + off1 = fdt_node_offset_by_compatible(dtb_ctx(&d), off0, "fsl,fman-memac"); ck_assert_int_gt(off0, 0); ck_assert_int_gt(off1, 0); /* node 0 (index 9): no phydevs slot, skipped */ - mac = fdt_getprop(d.buf, off0, "local-mac-address", NULL); + mac = fdt_getprop(dtb_ctx(&d), off0, "local-mac-address", NULL); ck_assert_ptr_null(mac); /* node 1 (index 2): fixed from phydevs[2] */ - mac = fdt_getprop(d.buf, off1, "local-mac-address", &len); + mac = fdt_getprop(dtb_ctx(&d), off1, "local-mac-address", &len); ck_assert_ptr_nonnull(mac); ck_assert_int_eq(len, 6); ck_assert_int_eq(((const uint8_t *)mac)[5], 0x12); @@ -484,11 +497,11 @@ START_TEST(test_fman_root_node_compatible_fixed) const void *clk; dtb_build_root_compat(&d, "fsl,fman"); - ck_assert_int_eq(hal_dts_fixup(d.buf), 0); + ck_assert_int_eq(hal_dts_fixup(d.buf, (uint32_t)DTB_BUF_SIZE), 0); - off = fdt_node_offset_by_compatible(d.buf, -1, "fsl,fman"); + off = fdt_node_offset_by_compatible(dtb_ctx(&d), -1, "fsl,fman"); ck_assert_int_eq(off, 0); /* the root node */ - clk = fdt_getprop(d.buf, off, "clock-frequency", &len); + clk = fdt_getprop(dtb_ctx(&d), off, "clock-frequency", &len); ck_assert_ptr_nonnull(clk); ck_assert_int_eq(len, 4); ck_assert_uint_eq(fdt32_to_cpu(*(const uint32_t *)clk), 100000000U); @@ -503,11 +516,11 @@ START_TEST(test_fman_child_node_fixed) const void *clk; dtb_build_compat_node(&d, "fman", "fsl,fman"); - ck_assert_int_eq(hal_dts_fixup(d.buf), 0); + ck_assert_int_eq(hal_dts_fixup(d.buf, (uint32_t)DTB_BUF_SIZE), 0); - off = fdt_node_offset_by_compatible(d.buf, -1, "fsl,fman"); + off = fdt_node_offset_by_compatible(dtb_ctx(&d), -1, "fsl,fman"); ck_assert_int_gt(off, 0); - clk = fdt_getprop(d.buf, off, "clock-frequency", &len); + clk = fdt_getprop(dtb_ctx(&d), off, "clock-frequency", &len); ck_assert_ptr_nonnull(clk); ck_assert_int_eq(len, 4); ck_assert_uint_eq(fdt32_to_cpu(*(const uint32_t *)clk), 100000000U); @@ -523,15 +536,15 @@ START_TEST(test_esdhc_node_fixed) const void *status; dtb_build_compat_node(&d, "esdhc", "fsl,esdhc"); - ck_assert_int_eq(hal_dts_fixup(d.buf), 0); + ck_assert_int_eq(hal_dts_fixup(d.buf, (uint32_t)DTB_BUF_SIZE), 0); - off = fdt_node_offset_by_compatible(d.buf, -1, "fsl,esdhc"); + off = fdt_node_offset_by_compatible(dtb_ctx(&d), -1, "fsl,esdhc"); ck_assert_int_gt(off, 0); - clk = fdt_getprop(d.buf, off, "clock-frequency", &len); + clk = fdt_getprop(dtb_ctx(&d), off, "clock-frequency", &len); ck_assert_ptr_nonnull(clk); ck_assert_int_eq(len, 4); ck_assert_uint_eq(fdt32_to_cpu(*(const uint32_t *)clk), 100000000U); - status = fdt_getprop(d.buf, off, "status", &len); + status = fdt_getprop(dtb_ctx(&d), off, "status", &len); ck_assert_ptr_nonnull(status); ck_assert_int_eq(len, 5); ck_assert_str_eq(status, "okay"); @@ -546,13 +559,62 @@ START_TEST(test_fman_esdhc_absent_skipped) int off; dtb_build_compat_node(&d, "ethernet", "fsl,eth"); - ck_assert_int_eq(hal_dts_fixup(d.buf), 0); + ck_assert_int_eq(hal_dts_fixup(d.buf, (uint32_t)DTB_BUF_SIZE), 0); - ck_assert_int_eq(fdt_node_offset_by_compatible(d.buf, -1, "fsl,fman"), + ck_assert_int_eq(fdt_node_offset_by_compatible(dtb_ctx(&d), -1, "fsl,fman"), -FDT_ERR_NOTFOUND); - off = fdt_node_offset_by_compatible(d.buf, -1, "fsl,eth"); + off = fdt_node_offset_by_compatible(dtb_ctx(&d), -1, "fsl,eth"); + ck_assert_int_gt(off, 0); + ck_assert_ptr_null(fdt_getprop(dtb_ctx(&d), off, "clock-frequency", NULL)); +} +END_TEST + +/* The qman-portal fixup writes fsl,liodn as raw cells rather than through + * fdt_fixup_val(), so those writes carry their own big-endian conversion. + * Device tree cells are big-endian on the wire; a host build writing host + * order would produce a tree hardware never sees. Lock the byte order in, + * for the portal itself and for the fman@0 child it inserts. */ +START_TEST(test_qman_portal_liodn_is_big_endian) +{ + struct dtb d; + const char *compat = "fsl,qman-portal"; + uint32_t reg[4] = {cpu_to_fdt32(0), cpu_to_fdt32(0), cpu_to_fdt32(0), + cpu_to_fdt32(0x10000000U)}; + const void *liodn; + int off, child, len; + + dtb_init(&d); + dtb_begin_node(&d, ""); + dtb_begin_node(&d, "memory"); + dtb_prop_raw(&d, "reg", reg, sizeof(reg)); + dtb_end_node(&d); + dtb_begin_node(&d, "qportal0"); + dtb_prop_raw(&d, "compatible", compat, strlen(compat) + 1); + dtb_prop_u32(&d, "cell-index", 0); + dtb_end_node(&d); + dtb_end_node(&d); /* root */ + dtb_finalize(&d); + + ck_assert_int_eq(hal_dts_fixup(d.buf, (uint32_t)DTB_BUF_SIZE), 0); + + off = fdt_node_offset_by_compatible(dtb_ctx(&d), -1, "fsl,qman-portal"); ck_assert_int_gt(off, 0); - ck_assert_ptr_null(fdt_getprop(d.buf, off, "clock-frequency", NULL)); + + /* qp_info[0] is {dliodn 1, fliodn 27} */ + liodn = fdt_getprop(dtb_ctx(&d), off, "fsl,liodn", &len); + ck_assert_ptr_nonnull(liodn); + ck_assert_int_eq(len, 8); + ck_assert_uint_eq(fdt32_to_cpu(((const uint32_t *)liodn)[0]), 1U); + ck_assert_uint_eq(fdt32_to_cpu(((const uint32_t *)liodn)[1]), 27U); + + /* the inserted fman@0 child carries FMAN_DMA_LIODN + index + 1 */ + child = fdt_subnode_offset(dtb_ctx(&d), off, "fman@0"); + ck_assert_int_gt(child, 0); + liodn = fdt_getprop(dtb_ctx(&d), child, "fsl,liodn", &len); + ck_assert_ptr_nonnull(liodn); + ck_assert_int_eq(len, 4); + ck_assert_uint_eq(fdt32_to_cpu(*(const uint32_t *)liodn), + (uint32_t)(FMAN_DMA_LIODN + 1)); } END_TEST @@ -570,6 +632,7 @@ Suite *t10xx_dts_memac_suite(void) tcase_add_test(tc, test_fman_child_node_fixed); tcase_add_test(tc, test_esdhc_node_fixed); tcase_add_test(tc, test_fman_esdhc_absent_skipped); + tcase_add_test(tc, test_qman_portal_liodn_is_big_endian); suite_add_tcase(s, tc); return s; diff --git a/tools/unit-tests/unit-update-disk-fit.c b/tools/unit-tests/unit-update-disk-fit.c index 0b401d8da5..9d2d8b5457 100644 --- a/tools/unit-tests/unit-update-disk-fit.c +++ b/tools/unit-tests/unit-update-disk-fit.c @@ -229,23 +229,33 @@ int wolfBoot_verify_authenticity(struct wolfBoot_image* img) /* The loaded payload is treated as a FIT container, and the sub-image * returned by fit_load_image() is a flat device tree whose parsed * size is mock_dts_size. */ -int wolfBoot_get_dts_size(void *dts_addr) +int wolfBoot_get_dts_size(void *dts_addr, uint32_t capacity) { (void)dts_addr; + (void)capacity; return mock_dts_size; } -/* Only reached through the fdt_version()/fdt_totalsize() trace macros here. */ -uint32_t fdt32_to_cpu(uint32_t x) +/* Accept the staged payload as a FIT. The real parser validates it; here + * we only need update_disk.c to take the FIT branch. */ +int fdt_open(fdt_ctx* ctx, void* blob, uint32_t capacity) { - return ((x & 0x000000FFU) << 24) | ((x & 0x0000FF00U) << 8) | - ((x & 0x00FF0000U) >> 8) | ((x & 0xFF000000U) >> 24); + memset(ctx, 0, sizeof(*ctx)); + ctx->blob = (uint8_t*)blob; + ctx->capacity = capacity; + ctx->totalsize = mock_dts_size; + return 0; +} + +uint32_t fdt_size(const fdt_ctx* ctx) +{ + return (ctx != NULL) ? ctx->totalsize : 0; } -const char* fit_find_images(void* fdt, const char** pkernel, +const char* fit_find_images(fdt_ctx* ctx, const char** pkernel, const char** pflat_dt, const char** pramdisk, const char** pfpga) { - (void)fdt; + (void)ctx; if (pkernel != NULL) *pkernel = NULL; if (pflat_dt != NULL) @@ -257,9 +267,9 @@ const char* fit_find_images(void* fdt, const char** pkernel, return "conf"; } -void* fit_load_image(void* fdt, const char* image, int* lenp) +void* fit_load_image(fdt_ctx* ctx, const char* image, int* lenp) { - (void)fdt; + (void)ctx; (void)image; if (lenp != NULL) *lenp = mock_dts_size; diff --git a/tools/unit-tests/unit-update-disk-oob.c b/tools/unit-tests/unit-update-disk-oob.c index 39ef7d57c5..793357b58d 100644 --- a/tools/unit-tests/unit-update-disk-oob.c +++ b/tools/unit-tests/unit-update-disk-oob.c @@ -215,8 +215,9 @@ int wolfBoot_verify_authenticity(struct wolfBoot_image* img) return mock_verify_authenticity_ret; } -int wolfBoot_get_dts_size(void *dts_addr) +int wolfBoot_get_dts_size(void *dts_addr, uint32_t capacity) { + (void)capacity; (void)dts_addr; return -1; } diff --git a/tools/unit-tests/unit-update-disk.c b/tools/unit-tests/unit-update-disk.c index dae1349d7b..69876c4811 100644 --- a/tools/unit-tests/unit-update-disk.c +++ b/tools/unit-tests/unit-update-disk.c @@ -193,8 +193,9 @@ int wolfBoot_verify_authenticity(struct wolfBoot_image* img) return mock_verify_authenticity_ret; } -int wolfBoot_get_dts_size(void *dts_addr) +int wolfBoot_get_dts_size(void *dts_addr, uint32_t capacity) { + (void)capacity; (void)dts_addr; return -1; }