diff options
| author | Tom Rini <trini@konsulko.com> | 2026-07-06 18:26:12 -0600 |
|---|---|---|
| committer | Tom Rini <trini@konsulko.com> | 2026-07-06 18:26:12 -0600 |
| commit | ee5d46b45ec0c63f8f9dd1e816e0dac3452ccc3d (patch) | |
| tree | 800cd9e204ca027144070101884c0d5d3c00130f /boot | |
| parent | ece349ade2973e220f524ce59e59711cc919263f (diff) | |
| parent | a18265f1ccb7a272721ed4286ed3b5a6182ff424 (diff) | |
| download | u-boot-master.tar.gz u-boot-master.zip | |
Merge branch 'next'HEADmasterWIP/06Jul2026
Diffstat (limited to 'boot')
| -rw-r--r-- | boot/Kconfig | 43 | ||||
| -rw-r--r-- | boot/bootdev-uclass.c | 17 | ||||
| -rw-r--r-- | boot/bootm.c | 48 | ||||
| -rw-r--r-- | boot/cedit.c | 2 | ||||
| -rw-r--r-- | boot/fdt_region.c | 15 | ||||
| -rw-r--r-- | boot/fdt_support.c | 27 | ||||
| -rw-r--r-- | boot/image-board.c | 7 | ||||
| -rw-r--r-- | boot/image-fdt.c | 81 | ||||
| -rw-r--r-- | boot/image-fit-sig.c | 19 | ||||
| -rw-r--r-- | boot/image-fit.c | 538 |
10 files changed, 649 insertions, 148 deletions
diff --git a/boot/Kconfig b/boot/Kconfig index ae6f09a6ede..8e468c56176 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -103,6 +103,19 @@ config FIT_FULL_CHECK of bugs or omissions in the code. This includes a bad structure, multiple root nodes and the like. +config CONTROL_DTB_AS_FIT + bool "Allow U-Boot's control DTB to act as FIT image" + help + Enable this to exempt U-Boot's control DTB from the sanity + checks done to ensure FIT images are valid. This can for + example be used to embed whole scripts in the control DTB, + that can then be invoked using 'source ${fdtcontroladdr}'. + In a secure boot setup, this is safe, as the control DTB is + necessarily covered by any mechanism verifying U-Boot and + can therefore be trusted. This only affects the case where + the image being checked is gd->fdt_blob. See + doc/develop/devicetree/control.rst for details. + config FIT_SIGNATURE bool "Enable signature verification of FIT uImages" depends on DM @@ -142,6 +155,26 @@ config FIT_CIPHER Enable the feature of data ciphering/unciphering in the tool mkimage and in the u-boot support of the FIT image. +config FIT_VERITY + bool "dm-verity boot parameter generation from FIT metadata" + depends on FIT && OF_LIBFDT + help + When a FIT configuration contains loadable sub-images of type + IH_TYPE_FILESYSTEM with a dm-verity subnode, this option enables + building the dm-mod.create= and dm-mod.waitfor= kernel + command-line parameters from the verity metadata + (data-block-size, hash-block-size, num-data-blocks, + hash-start-block, algorithm, digest, salt) stored in the FIT. + + The generated parameters reference /dev/fitN block devices that + Linux's uImage.FIT block driver assigns to loadable sub-images. + + During FIT parsing (BOOTM_STATE_FINDOTHER), verity cmdline + fragments are stored in struct bootm_headers and automatically + appended to the bootargs environment variable during + BOOTM_STATE_OS_PREP. This works from both the bootm command + and BOOTSTD bootmeths. + config FIT_VERBOSE bool "Show verbose messages when FIT images fail" help @@ -653,9 +686,9 @@ config BOOTMETH_QFW depends on QFW default y help - Use QEMU parameters -kernel, -initrd, -append to determine the kernel, - initial RAM disk, and kernel command line parameters to boot an - operating system. U-Boot's control device-tree is passed to the kernel. + Use QEMU parameters -kernel, -initrd, -append to determine the kernel, + initial RAM disk, and kernel command line parameters to boot an + operating system. U-Boot's control device-tree is passed to the kernel. config BOOTMETH_VBE bool "Bootdev support for Verified Boot for Embedded" @@ -1051,7 +1084,7 @@ config MEASURED_BOOT to use some attestation tools on your system. if MEASURED_BOOT - config MEASURE_DEVICETREE +config MEASURE_DEVICETREE bool "Measure the devicetree image" default y if MEASURED_BOOT help @@ -1060,7 +1093,7 @@ if MEASURED_BOOT Therefore, it should not be measured into the TPM. In that case, disable the measurement here. - config MEASURE_IGNORE_LOG +config MEASURE_IGNORE_LOG bool "Ignore the existing event log" help On platforms that use an event log memory region that persists diff --git a/boot/bootdev-uclass.c b/boot/bootdev-uclass.c index 3f8dc2c3c4e..55e1a6c4e02 100644 --- a/boot/bootdev-uclass.c +++ b/boot/bootdev-uclass.c @@ -566,13 +566,18 @@ int bootdev_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, { const struct bootdev_ops *ops = bootdev_get_ops(dev); - log_debug("->get_bootflow %s,%x=%p\n", dev->name, iter->part, - ops->get_bootflow); bootflow_init(bflow, dev, iter->method); - if (!ops->get_bootflow) - return default_get_bootflow(dev, iter, bflow); - return ops->get_bootflow(dev, iter, bflow); + if (ops && ops->get_bootflow) { + log_debug("->get_bootflow %s,%x=%p\n", dev->name, iter->part, + ops->get_bootflow); + + return ops->get_bootflow(dev, iter, bflow); + } + + log_debug("->get_bootflow %s,%x is unset\n", dev->name, iter->part); + + return default_get_bootflow(dev, iter, bflow); } int bootdev_next_label(struct bootflow_iter *iter, struct udevice **devp, @@ -664,8 +669,6 @@ int bootdev_next_prio(struct bootflow_iter *iter, struct udevice **devp) BOOTFLOWIF_SHOW); log_debug("- bootdev_hunt_prio() ret %d\n", ret); - if (ret) - return log_msg_ret("hun", ret); } } else { ret = device_probe(dev); diff --git a/boot/bootm.c b/boot/bootm.c index 4836d6b2d41..803d6406be4 100644 --- a/boot/bootm.c +++ b/boot/bootm.c @@ -243,6 +243,13 @@ static int boot_get_kernel(const char *addr_fit, struct bootm_headers *images, static int bootm_start(void) { + /* + * Free dm-verity allocations from a prior boot attempt before + * zeroing the structure. The pointers are guaranteed to be valid + * or NULL: .bss is zero-initialised, and memset() below zeroes + * them again after every boot. + */ + fit_verity_free(&images); memset((void *)&images, 0, sizeof(images)); images.verify = env_get_yesno("verify"); @@ -323,6 +330,10 @@ static int bootm_find_os(const char *cmd_name, const char *addr_fit) images.os.type = image_get_type(os_hdr); images.os.comp = image_get_comp(os_hdr); images.os.os = image_get_os(os_hdr); + if (images.os.os >= IH_OS_COUNT) { + printf("Unsupported OS type %d\n", images.os.os); + return 1; + } images.os.end = image_get_image_end(os_hdr); images.os.load = image_get_load(os_hdr); @@ -364,11 +375,17 @@ static int bootm_find_os(const char *cmd_name, const char *addr_fit) images.os.end = fit_get_end(images.fit_hdr_os); if (fit_image_get_load(images.fit_hdr_os, images.fit_noffset_os, - &images.os.load)) { + &images.os.load) && + images.os.type != IH_TYPE_KERNEL_NOLOAD) { puts("Can't get image load address!\n"); bootstage_error(BOOTSTAGE_ID_FIT_LOADADDR); return 1; } + if (images.os.load && images.os.type == IH_TYPE_KERNEL_NOLOAD) { + puts("WARNING: load address set for kernel_noload image, ignoring\n"); + images.os.load = 0; + } + break; #endif #ifdef CONFIG_ANDROID_BOOT_IMAGE @@ -416,7 +433,7 @@ static int bootm_find_os(const char *cmd_name, const char *addr_fit) ret = fit_image_get_entry(images.fit_hdr_os, images.fit_noffset_os, &images.ep); - if (ret) { + if (ret && images.os.type != IH_TYPE_KERNEL_NOLOAD) { puts("Can't get entry point property!\n"); return 1; } @@ -610,7 +627,8 @@ static int bootm_load_os(struct bootm_headers *images, int boot_progress) ulong blob_end = os.end; ulong image_start = os.image_start; ulong image_len = os.image_len; - ulong flush_start = ALIGN_DOWN(load, ARCH_DMA_MINALIGN); + ulong decomp_len = CONFIG_SYS_BOOTM_LEN; + ulong flush_start; bool no_overlap; void *load_buf, *image_buf; int err; @@ -618,16 +636,16 @@ static int bootm_load_os(struct bootm_headers *images, int boot_progress) /* * For a "noload" compressed kernel we need to allocate a buffer large * enough to decompress in to and use that as the load address now. - * Assume that the kernel compression is at most a factor of 4 since - * zstd almost achieves that. + * Allow up to 8x compression: this comfortably covers what zstd and xz + * achieve on real kernels, with headroom for well-compressed payloads. * Use an alignment of 2MB since this might help arm64 */ if (os.type == IH_TYPE_KERNEL_NOLOAD && os.comp != IH_COMP_NONE) { - ulong req_size = ALIGN(image_len * 4, SZ_1M); phys_addr_t addr; + decomp_len = ALIGN(image_len * 8, SZ_1M); err = lmb_alloc_mem(LMB_MEM_ALLOC_ANY, SZ_2M, &addr, - req_size, LMB_NONE); + decomp_len, LMB_NONE); if (err) return 1; @@ -635,23 +653,27 @@ static int bootm_load_os(struct bootm_headers *images, int boot_progress) images->os.load = (ulong)addr; images->ep = (ulong)addr; debug("Allocated %lx bytes at %lx for kernel (size %lx) decompression\n", - req_size, load, image_len); + decomp_len, load, image_len); } load_buf = map_sysmem(load, 0); image_buf = map_sysmem(os.image_start, image_len); err = image_decomp(os.comp, load, os.image_start, os.type, load_buf, image_buf, image_len, - CONFIG_SYS_BOOTM_LEN, &load_end); + decomp_len, &load_end); if (err) { err = handle_decomp_error(os.comp, load_end - load, - CONFIG_SYS_BOOTM_LEN, err); + decomp_len, err); + if (os.type == IH_TYPE_KERNEL_NOLOAD && os.comp != IH_COMP_NONE) + printf("Note: noload decompression buffer is %#lx bytes (not CONFIG_SYS_BOOTM_LEN)\n", + decomp_len); bootstage_error(BOOTSTAGE_ID_DECOMP_IMAGE); return err; } /* We need the decompressed image size in the next steps */ images->os.image_len = load_end - load; + flush_start = ALIGN_DOWN(load, ARCH_DMA_MINALIGN); flush_cache(flush_start, ALIGN(load_end, ARCH_DMA_MINALIGN) - flush_start); debug(" kernel loaded at 0x%08lx, end = 0x%08lx\n", load, load_end); @@ -1071,6 +1093,12 @@ int bootm_run_states(struct bootm_info *bmi, int states) /* For Linux OS do all substitutions at console processing */ if (images->os.os == IH_OS_LINUX) flags = BOOTM_CL_ALL; + ret = fit_verity_apply_bootargs(images); + if (ret) { + printf("dm-verity bootargs failed (err=%d)\n", ret); + ret = CMD_RET_FAILURE; + goto err; + } ret = bootm_process_cmdline_env(flags); if (ret) { printf("Cmdline setup failed (err=%d)\n", ret); diff --git a/boot/cedit.c b/boot/cedit.c index 56dc7c6af15..b1b79d1752a 100644 --- a/boot/cedit.c +++ b/boot/cedit.c @@ -500,6 +500,8 @@ static int h_read_settings(struct scene_obj *obj, void *vpriv) tline = (struct scene_obj_textline *)obj; val = ofnode_read_prop(node, obj->name, &len); + if (!val) + return log_msg_ret("tline", -ENOENT); if (len >= tline->max_chars) return log_msg_ret("str", -ENOSPC); strcpy(abuf_data(&tline->buf), val); diff --git a/boot/fdt_region.c b/boot/fdt_region.c index 295ea08ac91..dd6e87925be 100644 --- a/boot/fdt_region.c +++ b/boot/fdt_region.c @@ -69,6 +69,8 @@ int fdt_find_regions(const void *fdt, char * const inc[], int inc_count, include = want >= 2; stop_at = offset; prop = fdt_get_property_by_offset(fdt, offset, NULL); + if (!prop) + return -FDT_ERR_BADSTRUCTURE; str = fdt_string(fdt, fdt32_to_cpu(prop->nameoff)); if (!str) return -FDT_ERR_BADSTRUCTURE; @@ -86,6 +88,8 @@ int fdt_find_regions(const void *fdt, char * const inc[], int inc_count, if (depth == FDT_MAX_DEPTH) return -FDT_ERR_BADSTRUCTURE; name = fdt_get_name(fdt, offset, &len); + if (!name) + return len; /* The root node must have an empty name */ if (!depth && *name) @@ -271,7 +275,11 @@ int fdt_add_alias_regions(const void *fdt, struct fdt_region *region, int count, int target, next; prop = fdt_get_property_by_offset(fdt, offset, NULL); + if (!prop) + return -FDT_ERR_BADSTRUCTURE; name = fdt_string(fdt, fdt32_to_cpu(prop->nameoff)); + if (!name) + return -FDT_ERR_BADSTRUCTURE; target = fdt_path_offset(fdt, name); if (!region_list_contains_offset(info, fdt, target)) continue; @@ -520,7 +528,11 @@ int fdt_next_region(const void *fdt, case FDT_PROP: stop_at = offset; prop = fdt_get_property_by_offset(fdt, offset, NULL); + if (!prop) + return -FDT_ERR_BADSTRUCTURE; str = fdt_string(fdt, fdt32_to_cpu(prop->nameoff)); + if (!str) + return -FDT_ERR_BADSTRUCTURE; val = h_include(priv, fdt, last_node, FDT_IS_PROP, str, strlen(str) + 1); if (val == -1) { @@ -553,6 +565,9 @@ int fdt_next_region(const void *fdt, if (p.depth == FDT_MAX_DEPTH) return -FDT_ERR_BADSTRUCTURE; name = fdt_get_name(fdt, offset, &len); + if (!name) + return len; + if (p.end - path + 2 + len >= path_len) return -FDT_ERR_NOSPACE; diff --git a/boot/fdt_support.c b/boot/fdt_support.c index 1c215e548db..2941df55996 100644 --- a/boot/fdt_support.c +++ b/boot/fdt_support.c @@ -160,6 +160,12 @@ static int fdt_fixup_stdout(void *fdt, int chosenoff) goto noalias; } + if (len > (int)sizeof(tmp)) { + debug("%s: %s alias path too long (%d bytes)\n", + __func__, sername, len); + return -FDT_ERR_NOSPACE; + } + /* fdt_setprop may break "path" so we copy it to tmp buffer */ memcpy(tmp, path, len); @@ -545,13 +551,13 @@ int fdt_fixup_memory_banks(void *blob, u64 start[], u64 size[], int banks) if (banks > MEMORY_BANKS_MAX) { printf("%s: num banks %d exceeds hardcoded limit %d." " Recompile with higher MEMORY_BANKS_MAX?\n", - __FUNCTION__, banks, MEMORY_BANKS_MAX); + __func__, banks, MEMORY_BANKS_MAX); return -1; } err = fdt_check_header(blob); if (err < 0) { - printf("%s: %s\n", __FUNCTION__, fdt_strerror(err)); + printf("%s: %s\n", __func__, fdt_strerror(err)); return err; } @@ -1497,7 +1503,7 @@ static u64 __of_translate_address(const void *blob, int node_offset, /* Cound address cells & copy address locally */ bus->count_cells(blob, parent, &na, &ns); if (!OF_CHECK_COUNTS(na, ns)) { - printf("%s: Bad cell count for %s\n", __FUNCTION__, + printf("%s: Bad cell count for %s\n", __func__, fdt_get_name(blob, node_offset, NULL)); goto bail; } @@ -1524,8 +1530,8 @@ static u64 __of_translate_address(const void *blob, int node_offset, pbus = of_match_bus(blob, parent); pbus->count_cells(blob, parent, &pna, &pns); if (!OF_CHECK_COUNTS(pna, pns)) { - printf("%s: Bad cell count for %s\n", __FUNCTION__, - fdt_get_name(blob, node_offset, NULL)); + printf("%s: Bad cell count for %s\n", __func__, + fdt_get_name(blob, node_offset, NULL)); break; } @@ -1612,7 +1618,7 @@ int fdt_get_dma_range(const void *blob, int node, phys_addr_t *cpu, bus_node = of_match_bus(blob, node); bus_node->count_cells(blob, node, &na, &ns); if (!OF_CHECK_COUNTS(na, ns)) { - printf("%s: Bad cell count for %s\n", __FUNCTION__, + printf("%s: Bad cell count for %s\n", __func__, fdt_get_name(blob, node, NULL)); return -EINVAL; goto out; @@ -1621,12 +1627,19 @@ int fdt_get_dma_range(const void *blob, int node, phys_addr_t *cpu, bus_node = of_match_bus(blob, parent); bus_node->count_cells(blob, parent, &pna, &pns); if (!OF_CHECK_COUNTS(pna, pns)) { - printf("%s: Bad cell count for %s\n", __FUNCTION__, + printf("%s: Bad cell count for %s\n", __func__, fdt_get_name(blob, parent, NULL)); return -EINVAL; goto out; } + if (len < (int)((na + pna + ns) * sizeof(*ranges))) { + debug("%s: dma-ranges too short for %s\n", __func__, + fdt_get_name(blob, node, NULL)); + ret = -EINVAL; + goto out; + } + *bus = fdt_read_number(ranges, na); *cpu = fdt_translate_dma_address(blob, node, ranges + na); *size = fdt_read_number(ranges + na + pna, ns); diff --git a/boot/image-board.c b/boot/image-board.c index 005d60caf5c..67938fdd200 100644 --- a/boot/image-board.c +++ b/boot/image-board.c @@ -118,7 +118,7 @@ phys_addr_t env_get_bootm_low(void) #if defined(CFG_SYS_SDRAM_BASE) return CFG_SYS_SDRAM_BASE; #elif defined(CONFIG_ARM) || defined(CONFIG_MICROBLAZE) || defined(CONFIG_RISCV) - return gd->bd->bi_dram[0].start; + return gd->dram[0].start; #else return 0; #endif @@ -810,6 +810,11 @@ int boot_get_loadable(struct bootm_headers *images) fit_loadable_process(img_type, img_data, img_len); } + + fit_img_result = fit_verity_build_cmdline(buf, conf_noffset, + images); + if (fit_img_result < 0) + return fit_img_result; break; default: printf("The given image format is not supported (corrupt?)\n"); diff --git a/boot/image-fdt.c b/boot/image-fdt.c index a3a4fb8b558..9e0e0f93edd 100644 --- a/boot/image-fdt.c +++ b/boot/image-fdt.c @@ -69,35 +69,50 @@ static const struct legacy_img_hdr *image_get_fdt(ulong fdt_addr) } #endif -static void boot_fdt_reserve_region(u64 addr, u64 size, u32 flags) +/** + * boot_fdt_handle_region - Reserve or free a given FDT region in LMB + * @addr: Reservation base address + * @size: Reservation size + * @flags: Reservation flags + * @free: Indicate if region is being freed or allocated + * + * Add or free a given reservation from LMB. This reports to the user if any + * errors occurred during either operation. + */ +static void boot_fdt_handle_region(u64 addr, u64 size, u32 flags, bool free) { long ret; phys_addr_t rsv_addr; rsv_addr = (phys_addr_t)addr; - ret = lmb_alloc_mem(LMB_MEM_ALLOC_ADDR, 0, &rsv_addr, size, flags); + if (free) + ret = lmb_free(rsv_addr, size, flags); + else + ret = lmb_alloc_mem(LMB_MEM_ALLOC_ADDR, 0, &rsv_addr, size, + flags); + if (!ret) { - debug(" reserving fdt memory region: addr=%llx size=%llx flags=%x\n", - (unsigned long long)addr, + debug(" %s fdt memory region: addr=%llx size=%llx flags=%x\n", + free ? "freed" : "reserved", (unsigned long long)addr, (unsigned long long)size, flags); - } else if (ret != -EEXIST && ret != -EINVAL) { - puts("ERROR: reserving fdt memory region failed "); - printf("(addr=%llx size=%llx flags=%x)\n", - (unsigned long long)addr, - (unsigned long long)size, flags); + } else { + printf("ERROR: %s fdt memory region failed (addr=%llx size=%llx flags=%x): %ld\n", + free ? "freeing" : "reserving", (unsigned long long)addr, + (unsigned long long)size, flags, ret); } } /** - * boot_fdt_add_mem_rsv_regions - Mark the memreserve and reserved-memory - * sections as unusable + * boot_fdt_handle_mem_rsv_regions - Handle FDT memreserve and reserved-memory + * sections * @fdt_blob: pointer to fdt blob base address + * @free: indicate if regions are being freed * - * Adds the and reserved-memorymemreserve regions in the dtb to the lmb block. - * Adding the memreserve regions prevents u-boot from using them to store the - * initrd or the fdt blob. + * Adds or removes reserved-memory and memreserve regions in the dtb to the lmb + * block. Adding the memreserve regions prevents u-boot from using them to store + * the initrd or the fdt blob. */ -void boot_fdt_add_mem_rsv_regions(void *fdt_blob) +static void boot_fdt_handle_mem_rsv_regions(const void *fdt_blob, bool free) { uint64_t addr, size; int i, total, ret; @@ -105,15 +120,12 @@ void boot_fdt_add_mem_rsv_regions(void *fdt_blob) struct fdt_resource res; u32 flags; - if (fdt_check_header(fdt_blob) != 0) - return; - /* process memreserve sections */ total = fdt_num_mem_rsv(fdt_blob); for (i = 0; i < total; i++) { if (fdt_get_mem_rsv(fdt_blob, i, &addr, &size) != 0) continue; - boot_fdt_reserve_region(addr, size, LMB_NOOVERWRITE); + boot_fdt_handle_region(addr, size, LMB_NOOVERWRITE, free); } /* process reserved-memory */ @@ -131,7 +143,7 @@ void boot_fdt_add_mem_rsv_regions(void *fdt_blob) flags = LMB_NOMAP; addr = res.start; size = res.end - res.start + 1; - boot_fdt_reserve_region(addr, size, flags); + boot_fdt_handle_region(addr, size, flags, free); } subnode = fdt_next_subnode(fdt_blob, subnode); @@ -140,6 +152,31 @@ void boot_fdt_add_mem_rsv_regions(void *fdt_blob) } /** + * boot_fdt_add_mem_rsv_regions - Add FDT memreserve and reserved-memory + * sections + * @fdt_blob: pointer to fdt blob base address + * + * Adds reserved-memory and memreserve regions in the dtb to the lmb block. + * Adding the memreserve regions prevents u-boot from using them to store the + * initrd or the fdt blob. + * + * This function will attempt to clean the currently active reservations if a + * new device tree blob is given. This must be called before gd->fdt_blob is + * switched. + */ +void boot_fdt_add_mem_rsv_regions(const void *fdt_blob) +{ + if (fdt_check_header(fdt_blob) != 0) + return; + + /* Remove old regions */ + if (gd->fdt_blob != fdt_blob) + boot_fdt_handle_mem_rsv_regions(gd->fdt_blob, true); + + boot_fdt_handle_mem_rsv_regions(fdt_blob, false); +} + +/** * boot_relocate_fdt - relocate flat device tree * @of_flat_tree: pointer to a char* variable, will hold fdt start address * @of_size: pointer to a ulong variable, will hold fdt length @@ -223,8 +260,8 @@ int boot_relocate_fdt(char **of_flat_tree, ulong *of_size) of_start = NULL; for (bank = 0; bank < CONFIG_NR_DRAM_BANKS; bank++) { - start = gd->bd->bi_dram[bank].start; - size = gd->bd->bi_dram[bank].size; + start = gd->dram[bank].start; + size = gd->dram[bank].size; /* DRAM bank addresses are too low, skip it. */ if (start + size < low) diff --git a/boot/image-fit-sig.c b/boot/image-fit-sig.c index 433df20281f..9b5ab754561 100644 --- a/boot/image-fit-sig.c +++ b/boot/image-fit-sig.c @@ -452,6 +452,8 @@ static int fit_config_check_sig(const void *fit, int noffset, int conf_noffset, int max_regions; char path[200]; int count; + int len; + uint32_t size; debug("%s: fdt=%p, conf='%s', sig='%s'\n", __func__, key_blob, fit_get_name(fit, noffset, NULL), @@ -506,14 +508,27 @@ static int fit_config_check_sig(const void *fit, int noffset, int conf_noffset, } /* Add the strings */ - strings = fdt_getprop(fit, noffset, "hashed-strings", NULL); + strings = fdt_getprop(fit, noffset, "hashed-strings", &len); if (strings) { + if (len < (int)(2 * sizeof(fdt32_t))) { + *err_msgp = "Invalid hashed-strings property"; + return -1; + } + size = fdt32_to_cpu(strings[1]); + /* + * The offset should be already validated by fdt_check_header(); + * validate the size here. + */ + if (size > fdt_size_dt_strings(fit)) { + *err_msgp = "Strings region is out of bounds"; + return -1; + } /* * The strings region offset must be a static 0x0. * This is set in tool/image-host.c */ fdt_regions[count].offset = fdt_off_dt_strings(fit); - fdt_regions[count].size = fdt32_to_cpu(strings[1]); + fdt_regions[count].size = size; count++; } diff --git a/boot/image-fit.c b/boot/image-fit.c index b0fcaf6e17f..044a40e1910 100644 --- a/boot/image-fit.c +++ b/boot/image-fit.c @@ -21,8 +21,11 @@ extern void *aligned_alloc(size_t alignment, size_t size); #else #include <linux/compiler.h> +#include <linux/log2.h> #include <linux/sizes.h> +#include <env.h> #include <errno.h> +#include <hexdump.h> #include <log.h> #include <mapmem.h> #include <asm/io.h> @@ -41,6 +44,7 @@ DECLARE_GLOBAL_DATA_PTR; #include <bootm.h> #include <image.h> #include <bootstage.h> +#include <fdt_region.h> #include <upl.h> #include <u-boot/crc.h> @@ -156,18 +160,10 @@ static void fit_get_debug(const void *fit, int noffset, int fit_get_subimage_count(const void *fit, int images_noffset) { int noffset; - int ndepth; int count = 0; - /* Process its subnodes, print out component images details */ - for (ndepth = 0, count = 0, - noffset = fdt_next_node(fit, images_noffset, &ndepth); - (noffset >= 0) && (ndepth > 0); - noffset = fdt_next_node(fit, noffset, &ndepth)) { - if (ndepth == 1) { - count++; - } - } + fdt_for_each_subnode(noffset, fit, images_noffset) + count++; return count; } @@ -243,6 +239,39 @@ static void fit_image_print_data(const void *fit, int noffset, const char *p, } } +static __maybe_unused void fit_image_print_dm_verity(const void *fit, + int noffset, + const char *p) +{ +#if defined(USE_HOSTCC) || CONFIG_IS_ENABLED(FIT_VERITY) + const char *algo; + const uint8_t *bin; + int len, i; + + algo = fdt_getprop(fit, noffset, FIT_VERITY_ALGO_PROP, NULL); + if (algo) + printf("%s Verity algo: %s\n", p, algo); + + bin = fdt_getprop(fit, noffset, FIT_VERITY_DIGEST_PROP, + &len); + if (bin && len > 0) { + printf("%s Verity hash: ", p); + for (i = 0; i < len; i++) + printf("%02x", bin[i]); + printf("\n"); + } + + bin = fdt_getprop(fit, noffset, FIT_VERITY_SALT_PROP, + &len); + if (bin && len > 0) { + printf("%s Verity salt: ", p); + for (i = 0; i < len; i++) + printf("%02x", bin[i]); + printf("\n"); + } +#endif +} + /** * fit_image_print_verification_data() - prints out the hash/signature details * @fit: pointer to the FIT format image header @@ -271,6 +300,11 @@ static void fit_image_print_verification_data(const void *fit, int noffset, strlen(FIT_SIG_NODENAME))) { fit_image_print_data(fit, noffset, p, "Sign"); } +#if defined(USE_HOSTCC) || CONFIG_IS_ENABLED(FIT_VERITY) + else if (!strcmp(name, FIT_VERITY_NODENAME)) { + fit_image_print_dm_verity(fit, noffset, p); + } +#endif } /** @@ -291,7 +325,7 @@ static void fit_conf_print(const void *fit, int noffset, const char *p) const char *uname; int ret; int fdt_index, loadables_index; - int ndepth; + int sub_noffset; /* Mandatory properties */ ret = fit_get_desc(fit, noffset, &desc); @@ -357,14 +391,8 @@ static void fit_conf_print(const void *fit, int noffset, const char *p) } /* Process all hash subnodes of the component configuration node */ - for (ndepth = 0, noffset = fdt_next_node(fit, noffset, &ndepth); - (noffset >= 0) && (ndepth > 0); - noffset = fdt_next_node(fit, noffset, &ndepth)) { - if (ndepth == 1) { - /* Direct child node of the component configuration node */ - fit_image_print_verification_data(fit, noffset, p); - } - } + fdt_for_each_subnode(sub_noffset, fit, noffset) + fit_image_print_verification_data(fit, sub_noffset, p); } /** @@ -386,8 +414,7 @@ void fit_print_contents(const void *fit) int images_noffset; int confs_noffset; int noffset; - int ndepth; - int count = 0; + int count; int ret; const char *p; time_t timestamp; @@ -424,20 +451,12 @@ void fit_print_contents(const void *fit) } /* Process its subnodes, print out component images details */ - for (ndepth = 0, count = 0, - noffset = fdt_next_node(fit, images_noffset, &ndepth); - (noffset >= 0) && (ndepth > 0); - noffset = fdt_next_node(fit, noffset, &ndepth)) { - if (ndepth == 1) { - /* - * Direct child node of the images parent node, - * i.e. component image node. - */ - printf("%s Image %u (%s)\n", p, count++, - fit_get_name(fit, noffset, NULL)); + count = 0; + fdt_for_each_subnode(noffset, fit, images_noffset) { + printf("%s Image %u (%s)\n", p, count++, + fit_get_name(fit, noffset, NULL)); - fit_image_print(fit, noffset, p); - } + fit_image_print(fit, noffset, p); } /* Find configurations parent node offset */ @@ -449,25 +468,17 @@ void fit_print_contents(const void *fit) } /* get default configuration unit name from default property */ - uname = (char *)fdt_getprop(fit, noffset, FIT_DEFAULT_PROP, NULL); + uname = (char *)fdt_getprop(fit, confs_noffset, FIT_DEFAULT_PROP, NULL); if (uname) printf("%s Default Configuration: '%s'\n", p, uname); /* Process its subnodes, print out configurations details */ - for (ndepth = 0, count = 0, - noffset = fdt_next_node(fit, confs_noffset, &ndepth); - (noffset >= 0) && (ndepth > 0); - noffset = fdt_next_node(fit, noffset, &ndepth)) { - if (ndepth == 1) { - /* - * Direct child node of the configurations parent node, - * i.e. configuration node. - */ - printf("%s Configuration %u (%s)\n", p, count++, - fit_get_name(fit, noffset, NULL)); + count = 0; + fdt_for_each_subnode(noffset, fit, confs_noffset) { + printf("%s Configuration %u (%s)\n", p, count++, + fit_get_name(fit, noffset, NULL)); - fit_conf_print(fit, noffset, p); - } + fit_conf_print(fit, noffset, p); } } @@ -494,7 +505,6 @@ void fit_image_print(const void *fit, int image_noffset, const char *p) ulong load, entry; const void *data; int noffset; - int ndepth; int ret; if (!CONFIG_IS_ENABLED(FIT_PRINT)) @@ -584,14 +594,8 @@ void fit_image_print(const void *fit, int image_noffset, const char *p) } /* Process all hash subnodes of the component image node */ - for (ndepth = 0, noffset = fdt_next_node(fit, image_noffset, &ndepth); - (noffset >= 0) && (ndepth > 0); - noffset = fdt_next_node(fit, noffset, &ndepth)) { - if (ndepth == 1) { - /* Direct child node of the component image node */ - fit_image_print_verification_data(fit, noffset, p); - } - } + fdt_for_each_subnode(noffset, fit, image_noffset) + fit_image_print_verification_data(fit, noffset, p); } /** @@ -1069,23 +1073,72 @@ int fit_image_get_data(const void *fit, int noffset, const void **data, int offset; int len; int ret; + size_t fdt_total_size_aligned; + uintptr_t max_offset; if (!fit_image_get_data_position(fit, noffset, &offset)) { + if (offset < 0) { + printf("Invalid external data position: %d\n", offset); + return -EINVAL; + } + external_data = true; } else if (!fit_image_get_data_offset(fit, noffset, &offset)) { - external_data = true; /* * For FIT with external data, figure out where * the external images start. This is the base * for the data-offset properties in each image. */ - offset += ((fdt_totalsize(fit) + 3) & ~3); + fdt_total_size_aligned = ((fdt_totalsize(fit) + 3) & ~3); + /* The resulting offset cannot exceed INT_MAX */ + if (offset < 0 || fdt_total_size_aligned > INT_MAX - offset) { + printf("Invalid external data offset: %d\n", offset); + return -EINVAL; + } + offset += fdt_total_size_aligned; + + external_data = true; } if (external_data) { debug("External Data\n"); + + max_offset = UINTPTR_MAX - (uintptr_t)fit; + /* Check that external data offset is within the addressable range */ + if (offset > max_offset) { + printf("Invalid external data offset: %d\n", offset); + return -EINVAL; + } + ret = fit_image_get_data_size(fit, noffset, &len); if (!ret) { + if (len < 0) { + printf("Invalid external data size: %d\n", len); + return -EINVAL; + } + /* + * For non-signed FIT images, we can only check that + * (offset + len) doesn't exceed the addressable range. + * For signed FITs, we can additionally check that + * (offset + len) doesn't exceed the allowed FIT image + * maximum size. + */ + if (len > max_offset - offset + /* + * #if (not a runtime if) is required: FIT_SIGNATURE_MAX_SIZE + * depends on FIT_SIGNATURE, so CONFIG_VAL(FIT_SIGNATURE_MAX_SIZE) + * is undefined when signing is disabled and referencing it + * here would fail to compile. + */ +#if CONFIG_IS_ENABLED(FIT_SIGNATURE) + || offset > CONFIG_VAL(FIT_SIGNATURE_MAX_SIZE) || + len > CONFIG_VAL(FIT_SIGNATURE_MAX_SIZE) - offset +#endif + ) { + printf("FIT external data is out of bounds (offset=%d, size=%d)\n", + offset, len); + return -EINVAL; + } *data = fit + offset; *size = len; } @@ -1439,7 +1492,7 @@ int fit_image_verify(const void *fit, int image_noffset) size_t size; char *err_msg = ""; - if (IS_ENABLED(CONFIG_FIT_SIGNATURE) && strchr(name, '@')) { + if (CONFIG_IS_ENABLED(FIT_SIGNATURE) && strchr(name, '@')) { /* * We don't support this since libfdt considers names with the * name root but different @ suffix to be equal @@ -1477,7 +1530,6 @@ int fit_all_image_verify(const void *fit) { int images_noffset; int noffset; - int ndepth; int count; /* Find images parent node offset */ @@ -1491,23 +1543,15 @@ int fit_all_image_verify(const void *fit) /* Process all image subnodes, check hashes for each */ printf("## Checking hash(es) for FIT Image at %08lx ...\n", (ulong)fit); - for (ndepth = 0, count = 0, - noffset = fdt_next_node(fit, images_noffset, &ndepth); - (noffset >= 0) && (ndepth > 0); - noffset = fdt_next_node(fit, noffset, &ndepth)) { - if (ndepth == 1) { - /* - * Direct child node of the images parent node, - * i.e. component image node. - */ - printf(" Hash(es) for Image %u (%s): ", count, - fit_get_name(fit, noffset, NULL)); - count++; + count = 0; + fdt_for_each_subnode(noffset, fit, images_noffset) { + printf(" Hash(es) for Image %u (%s): ", count, + fit_get_name(fit, noffset, NULL)); + count++; - if (!fit_image_verify(fit, noffset)) - return 0; - printf("\n"); - } + if (!fit_image_verify(fit, noffset)) + return 0; + printf("\n"); } return 1; } @@ -1643,20 +1687,24 @@ int fit_image_check_comp(const void *fit, int noffset, uint8_t comp) * * @fit: FIT to check * @parent: Parent node to check - * Return: 0 if OK, -EADDRNOTAVAIL is a node has a name containing '@' + * @depth: Current recursion depth + * Return: 0 if OK, or error value */ -static int fdt_check_no_at(const void *fit, int parent) +static int fdt_check_no_at(const void *fit, int parent, int depth) { const char *name; int node; int ret; + if (depth >= FDT_MAX_DEPTH) + return -FDT_ERR_BADSTRUCTURE; + name = fdt_get_name(fit, parent, NULL); if (!name || strchr(name, '@')) return -EADDRNOTAVAIL; fdt_for_each_subnode(node, fit, parent) { - ret = fdt_check_no_at(fit, node); + ret = fdt_check_no_at(fit, node, depth + 1); if (ret) return ret; } @@ -1664,6 +1712,16 @@ static int fdt_check_no_at(const void *fit, int parent) return 0; } +static int fit_check_images_node(const void *fit) +{ + if (fdt_path_offset(fit, FIT_IMAGES_PATH) < 0) { + log_debug("Wrong FIT format: no images parent node\n"); + return -ENOENT; + } + + return 0; +} + int fit_check_format(const void *fit, ulong size) { int ret; @@ -1676,6 +1734,13 @@ int fit_check_format(const void *fit, ulong size) return -ENOEXEC; } + /* + * For the control DTB to act as a FIT image, we only require + * an /images node. + */ + if (CONFIG_IS_ENABLED(CONTROL_DTB_AS_FIT) && fit == gd_fdt_blob()) + return fit_check_images_node(fit); + if (CONFIG_IS_ENABLED(FIT_FULL_CHECK)) { /* * If we are not given the size, make do with calculating it. @@ -1696,7 +1761,7 @@ int fit_check_format(const void *fit, ulong size) * attached. Protect against this by disallowing unit addresses. */ if (!ret && CONFIG_IS_ENABLED(FIT_SIGNATURE)) { - ret = fdt_check_no_at(fit, 0); + ret = fdt_check_no_at(fit, 0, 0); if (ret) { log_debug("FIT check error %d\n", ret); @@ -1724,17 +1789,11 @@ int fit_check_format(const void *fit, ulong size) } /* mandatory subimages parent '/images' node */ - if (fdt_path_offset(fit, FIT_IMAGES_PATH) < 0) { - log_debug("Wrong FIT format: no images parent node\n"); - return -ENOENT; - } - - return 0; + return fit_check_images_node(fit); } int fit_conf_find_compat(const void *fit, const void *fdt) { - int ndepth = 0; int noffset, confs_noffset, images_noffset; const void *fdt_compat; int fdt_compat_len; @@ -1757,9 +1816,7 @@ int fit_conf_find_compat(const void *fit, const void *fdt) /* * Loop over the configurations in the FIT image. */ - for (noffset = fdt_next_node(fit, confs_noffset, &ndepth); - (noffset >= 0) && (ndepth > 0); - noffset = fdt_next_node(fit, noffset, &ndepth)) { + fdt_for_each_subnode(noffset, fit, confs_noffset) { const void *fdt; const char *kfdt_name; int kfdt_noffset, compat_noffset; @@ -1768,9 +1825,6 @@ int fit_conf_find_compat(const void *fit, const void *fdt) size_t sz; int i; - if (ndepth > 1) - continue; - /* If there's a compat property in the config node, use that. */ if (fdt_getprop(fit, noffset, FIT_COMPAT_PROP, NULL)) { fdt = fit; /* search in FIT image */ @@ -2642,3 +2696,299 @@ out: return fdt_noffset; } #endif + +#if !defined(USE_HOSTCC) && CONFIG_IS_ENABLED(FIT_VERITY) + +static const char *const verity_opt_props[] = { + FIT_VERITY_OPT_RESTART, + FIT_VERITY_OPT_PANIC, + FIT_VERITY_OPT_RERR, + FIT_VERITY_OPT_PERR, + FIT_VERITY_OPT_ONCE, +}; + +/** + * fit_verity_build_target() - build one dm-verity target specification + * @fit: pointer to the FIT blob + * @img_noffset: image node offset containing the dm-verity subnode + * @loadable_idx: index of this loadable (for /dev/fitN) + * @uname: unit name of the image + * @separator: true if a ";" prefix is needed (not the first target) + * @buf: output buffer, or NULL to measure only + * @bufsize: size of @buf (ignored when @buf is NULL) + * + * Parses all dm-verity properties from the image's ``dm-verity`` child + * node and writes (or measures) a dm target specification string of the + * form used by the ``dm-mod.create`` kernel parameter. + * + * Return: number of characters that would be written (excluding '\0'), + * or -ve errno on error (e.g. missing mandatory property) + */ +static int fit_verity_build_target(const void *fit, int img_noffset, + int loadable_idx, const char *uname, + bool separator, char *buf, int bufsize) +{ + const char *algorithm; + const u8 *digest_raw, *salt_raw; + const fdt32_t *val; + char *digest_hex = NULL, *salt_hex = NULL, *opt_buf = NULL; + int verity_node; + unsigned int data_block_size, hash_block_size; + int num_data_blocks, hash_start_block; + u64 data_sectors; + int digest_len, salt_len; + int opt_count, opt_off, opt_buf_size; + int len; + int i; + + verity_node = fdt_subnode_offset(fit, img_noffset, FIT_VERITY_NODENAME); + if (verity_node < 0) + return -ENOENT; + + /* Mandatory u32 properties */ + val = fdt_getprop(fit, verity_node, FIT_VERITY_DBS_PROP, NULL); + if (!val) + return -EINVAL; + data_block_size = fdt32_to_cpu(*val); + + val = fdt_getprop(fit, verity_node, FIT_VERITY_HBS_PROP, NULL); + if (!val) + return -EINVAL; + hash_block_size = fdt32_to_cpu(*val); + + val = fdt_getprop(fit, verity_node, FIT_VERITY_NBLK_PROP, NULL); + if (!val) + return -EINVAL; + num_data_blocks = fdt32_to_cpu(*val); + + val = fdt_getprop(fit, verity_node, FIT_VERITY_HBLK_PROP, NULL); + if (!val) + return -EINVAL; + hash_start_block = fdt32_to_cpu(*val); + + if (data_block_size < 512U || !is_power_of_2(data_block_size) || + hash_block_size < 512U || !is_power_of_2(hash_block_size) || + !num_data_blocks) + return -EINVAL; + + /* Mandatory string */ + algorithm = fdt_getprop(fit, verity_node, FIT_VERITY_ALGO_PROP, NULL); + if (!algorithm) + return -EINVAL; + + /* Mandatory byte arrays */ + digest_raw = fdt_getprop(fit, verity_node, FIT_VERITY_DIGEST_PROP, + &digest_len); + if (!digest_raw || digest_len <= 0) + return -EINVAL; + + salt_raw = fdt_getprop(fit, verity_node, FIT_VERITY_SALT_PROP, + &salt_len); + if (!salt_raw || salt_len <= 0) + return -EINVAL; + + /* Hex-encode digest and salt into dynamically sized buffers */ + digest_hex = malloc(digest_len * 2 + 1); + salt_hex = malloc(salt_len * 2 + 1); + if (!digest_hex || !salt_hex) { + len = -ENOMEM; + goto out; + } + *bin2hex(digest_hex, digest_raw, digest_len) = '\0'; + *bin2hex(salt_hex, salt_raw, salt_len) = '\0'; + + data_sectors = (u64)num_data_blocks * ((u64)data_block_size / 512); + + /* Compute space needed for optional boolean properties */ + opt_buf_size = 1; /* NUL terminator */ + for (i = 0; i < ARRAY_SIZE(verity_opt_props); i++) + opt_buf_size += strlen(verity_opt_props[i]) + 1; + opt_buf = malloc(opt_buf_size); + if (!opt_buf) { + len = -ENOMEM; + goto out; + } + + /* Collect optional boolean properties */ + opt_count = 0; + opt_off = 0; + opt_buf[0] = '\0'; + for (i = 0; i < ARRAY_SIZE(verity_opt_props); i++) { + if (fdt_getprop(fit, verity_node, + verity_opt_props[i], NULL)) { + const char *s = verity_opt_props[i]; + int slen = strlen(s); + + if (opt_off) + opt_buf[opt_off++] = ' '; + /* Copy with hyphen-to-underscore conversion */ + while (slen-- > 0) { + opt_buf[opt_off++] = + (*s == '-') ? '_' : *s; + s++; + } + opt_buf[opt_off] = '\0'; + opt_count++; + } + } + + /* Emit (or measure) the target spec */ + len = snprintf(buf, buf ? bufsize : 0, + "%s%s,,, ro,0 %llu verity 1 /dev/fit%d /dev/fit%d %u %u %d %d %s %s %s", + separator ? ";" : "", uname, + (unsigned long long)data_sectors, loadable_idx, loadable_idx, + data_block_size, hash_block_size, + num_data_blocks, hash_start_block, + algorithm, digest_hex, salt_hex); + if (opt_count) { + int extra = snprintf(buf ? buf + len : NULL, + buf ? bufsize - len : 0, + " %d %s", opt_count, opt_buf); + len += extra; + } + +out: + free(digest_hex); + free(salt_hex); + free(opt_buf); + return len; +} + +int fit_verity_build_cmdline(const void *fit, int conf_noffset, + struct bootm_headers *images) +{ + int images_noffset; + int dm_create_len = 0, dm_waitfor_len = 0; + char *dm_create = NULL, *dm_waitfor = NULL; + const char *uname; + int loadable_idx; + int found = 0; + int ret = 0; + + images_noffset = fdt_path_offset(fit, FIT_IMAGES_PATH); + if (images_noffset < 0) + return 0; + + for (loadable_idx = 0; + (uname = fdt_stringlist_get(fit, conf_noffset, + FIT_LOADABLE_PROP, + loadable_idx, NULL)); + loadable_idx++) { + int img_noffset, need; + u8 img_type; + char *tmp; + + img_noffset = fdt_subnode_offset(fit, images_noffset, uname); + if (img_noffset < 0) + continue; + + if (fit_image_get_type(fit, img_noffset, &img_type) || + img_type != IH_TYPE_FILESYSTEM) + continue; + + /* Measure first, then allocate and write */ + need = fit_verity_build_target(fit, img_noffset, + loadable_idx, uname, + found > 0, NULL, 0); + if (need == -ENOENT) + continue; /* no dm-verity subnode -- fine */ + if (need < 0) { + log_err("FIT: broken dm-verity metadata in '%s'\n", + uname); + ret = need; + goto err; + } + + tmp = realloc(dm_create, dm_create_len + need + 1); + if (!tmp) { + ret = -ENOMEM; + goto err; + } + dm_create = tmp; + fit_verity_build_target(fit, img_noffset, loadable_idx, + uname, found > 0, + dm_create + dm_create_len, + need + 1); + dm_create_len += need; + + /* Grow dm_waitfor buffer */ + need = snprintf(NULL, 0, "%s/dev/fit%d", + dm_waitfor_len ? "," : "", + loadable_idx); + tmp = realloc(dm_waitfor, dm_waitfor_len + need + 1); + if (!tmp) { + ret = -ENOMEM; + goto err; + } + dm_waitfor = tmp; + sprintf(dm_waitfor + dm_waitfor_len, "%s/dev/fit%d", + dm_waitfor_len ? "," : "", + loadable_idx); + dm_waitfor_len += need; + + found++; + } + + if (found) { + /* Transfer ownership to the bootm_headers */ + images->dm_mod_create = dm_create; + images->dm_mod_waitfor = dm_waitfor; + } else { + free(dm_create); + free(dm_waitfor); + } + + return 0; + +err: + free(dm_create); + free(dm_waitfor); + return ret; +} + +/** + * fmt used by both the measurement and the actual write of bootargs. + * Shared to guarantee they stay in sync. + */ +#define VERITY_BOOTARGS_FMT "%s%sdm-mod.create=\"%s\" dm-mod.waitfor=\"%s\"" + +int fit_verity_apply_bootargs(const struct bootm_headers *images) +{ + const char *existing; + char *newargs; + int len; + + if (!images->dm_mod_create) + return 0; + + existing = env_get("bootargs"); + if (!existing) + existing = ""; + + /* Measure */ + len = snprintf(NULL, 0, VERITY_BOOTARGS_FMT, + existing, existing[0] ? " " : "", + images->dm_mod_create, images->dm_mod_waitfor); + + newargs = malloc(len + 1); + if (!newargs) + return -ENOMEM; + + snprintf(newargs, len + 1, VERITY_BOOTARGS_FMT, + existing, existing[0] ? " " : "", + images->dm_mod_create, images->dm_mod_waitfor); + + env_set("bootargs", newargs); + free(newargs); + + return 0; +} + +void fit_verity_free(struct bootm_headers *images) +{ + free(images->dm_mod_create); + free(images->dm_mod_waitfor); + images->dm_mod_create = NULL; + images->dm_mod_waitfor = NULL; +} +#endif /* FIT_VERITY */ |
