summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Rini <[email protected]>2026-05-27 13:44:20 -0600
committerTom Rini <[email protected]>2026-05-27 13:44:20 -0600
commit746a986fe247fc0b3d52ad7ae7027e0a6d57d12c (patch)
tree9fd87b6016fb21d043deab6fffabb8ddad9fd36c
parent8d5f30b52f7c800c2177188fc4d331fb7af2c46a (diff)
parent89d3c1fe1b0fb5db15fce96a7e6db7885ebf240e (diff)
Merge patch series "fit: dm-verity support"
Daniel Golle <[email protected]> says: This series adds dm-verity support to U-Boot's FIT image infrastructure. It is the first logical subset of the larger OpenWrt boot method series posted as an RFC in February 2026 [1], extracted here for independent review and merging. OpenWrt's firmware model embeds a read-only squashfs or erofs root filesystem directly inside a uImage.FIT container as a FILESYSTEM-type loadable FIT image. At boot the kernel maps this sub-image directly from the underlying block device via the fitblk driver (/dev/fit0, /dev/fit1, ...), the goal is that the bootloader never even copies it to RAM. dm-verity enables the kernel to verify the integrity of those mapped filesystems at read time, with a Merkle hash tree stored contiguously in the same sub-image just after the data. Two kernel command-line parameters are required: dm-mod.create= -- the device-mapper target table for the verity device dm-mod.waitfor= -- a comma-separated list of block devices to wait for before dm-init sets up the targets (needed when fitblk probes late, e.g. because it depends on NVMEM calibration data) The FIT dm-verity node schema was upstreamed into the flat-image-tree specification [2], which this implementation tries to follow exactly. The runtime feature is guarded behind CONFIG_FIT_VERITY. If not enabled the resulting binary size remains unchanged. If enabled the binary size increases by about 3kB. [1] previous submissions: RFC: https://www.mail-archive.com/[email protected]/msg565945.html v1: https://www.mail-archive.com/[email protected]/msg569472.html v2: https://www.mail-archive.com/[email protected]/msg570599.html v3: https://www.mail-archive.com/[email protected]/msg573223.html v4: https://www.mail-archive.com/[email protected]/msg574000.html [2] flat-image-tree dm-verity node spec: https://github.com/open-source-firmware/flat-image-tree/commit/795fd5fd7f0121d0cb03efb1900aafc61c704771 Link: https://lore.kernel.org/r/[email protected]
-rw-r--r--boot/Kconfig20
-rw-r--r--boot/bootm.c13
-rw-r--r--boot/image-board.c5
-rw-r--r--boot/image-fit.c337
-rw-r--r--configs/sandbox64_defconfig1
-rw-r--r--configs/sandbox_defconfig1
-rw-r--r--configs/sandbox_flattree_defconfig1
-rw-r--r--doc/usage/fit/dm-verity.rst304
-rw-r--r--doc/usage/fit/index.rst1
-rw-r--r--include/hexdump.h8
-rw-r--r--include/image.h115
-rw-r--r--test/boot/Makefile1
-rw-r--r--test/boot/fit_verity.c306
-rw-r--r--test/cmd_ut.c2
-rw-r--r--test/py/tests/test_fit_verity.py175
-rw-r--r--tools/fit_image.c91
-rw-r--r--tools/image-host.c414
17 files changed, 1783 insertions, 12 deletions
diff --git a/boot/Kconfig b/boot/Kconfig
index ae6f09a6ede..e1114aea843 100644
--- a/boot/Kconfig
+++ b/boot/Kconfig
@@ -142,6 +142,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
diff --git a/boot/bootm.c b/boot/bootm.c
index 4836d6b2d41..ec74873b503 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");
@@ -1071,6 +1078,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/image-board.c b/boot/image-board.c
index 005d60caf5c..265f29d44ff 100644
--- a/boot/image-board.c
+++ b/boot/image-board.c
@@ -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-fit.c b/boot/image-fit.c
index 884a5105df5..1cf8e7be957 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>
@@ -235,6 +238,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
@@ -263,6 +299,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
}
/**
@@ -2589,3 +2630,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 */
diff --git a/configs/sandbox64_defconfig b/configs/sandbox64_defconfig
index 5bf6146b1d0..f5d5b21e733 100644
--- a/configs/sandbox64_defconfig
+++ b/configs/sandbox64_defconfig
@@ -18,6 +18,7 @@ CONFIG_EFI_RT_VOLATILE_STORE=y
CONFIG_BUTTON_CMD=y
CONFIG_FIT=y
CONFIG_FIT_SIGNATURE=y
+CONFIG_FIT_VERITY=y
CONFIG_FIT_VERBOSE=y
CONFIG_LEGACY_IMAGE_FORMAT=y
CONFIG_BOOTSTAGE=y
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index ba800f7d19d..ff4a6eb285a 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -22,6 +22,7 @@ CONFIG_BUTTON_CMD=y
CONFIG_FIT=y
CONFIG_FIT_SIGNATURE=y
CONFIG_FIT_CIPHER=y
+CONFIG_FIT_VERITY=y
CONFIG_FIT_VERBOSE=y
CONFIG_BOOTMETH_ANDROID=y
CONFIG_BOOTMETH_RAUC=y
diff --git a/configs/sandbox_flattree_defconfig b/configs/sandbox_flattree_defconfig
index a14dd5beb31..4ad2bb01673 100644
--- a/configs/sandbox_flattree_defconfig
+++ b/configs/sandbox_flattree_defconfig
@@ -14,6 +14,7 @@ CONFIG_EFI_CAPSULE_CRT_FILE="board/sandbox/capsule_pub_key_good.crt"
CONFIG_BUTTON_CMD=y
CONFIG_FIT=y
CONFIG_FIT_SIGNATURE=y
+CONFIG_FIT_VERITY=y
CONFIG_FIT_VERBOSE=y
CONFIG_LEGACY_IMAGE_FORMAT=y
CONFIG_BOOTSTAGE=y
diff --git a/doc/usage/fit/dm-verity.rst b/doc/usage/fit/dm-verity.rst
new file mode 100644
index 00000000000..800a18fceae
--- /dev/null
+++ b/doc/usage/fit/dm-verity.rst
@@ -0,0 +1,304 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+FIT dm-verity Boot Parameters
+=============================
+
+Introduction
+------------
+
+Linux's dm-verity device-mapper target provides transparent integrity
+checking of block devices using a Merkle tree. It is commonly used to
+protect read-only root filesystems such as SquashFS images.
+
+When a FIT image packages the root filesystem as a loadable sub-image of
+type ``filesystem`` (``IH_TYPE_FILESYSTEM``), the verity metadata can be
+stored alongside the image data in a ``dm-verity`` subnode. U-Boot reads
+this metadata at boot time and generates the kernel command-line parameters
+that Linux needs to activate the verity target, eliminating the need for
+an initramfs or userspace helper to set up dm-verity.
+
+This feature is enabled by ``CONFIG_FIT_VERITY`` (see ``boot/Kconfig``).
+
+Prerequisites
+-------------
+
+* **Linux uImage.FIT block driver** – the kernel must include the FIT block
+ driver that exposes loadable sub-images as ``/dev/fit0``, ``/dev/fit1``,
+ etc. The driver assigns device numbers in the order loadables appear in
+ the FIT configuration.
+
+* **dm-verity support in the kernel** – ``CONFIG_DM_VERITY`` must be
+ enabled so the kernel can process the ``dm-mod.create=`` parameter.
+
+* **CONFIG_FIT_VERITY** enabled in U-Boot.
+
+How it works
+------------
+
+The implementation is split into a **build** phase and an **apply** phase,
+both of which run automatically within the ``bootm`` state machine. No boot
+method needs to call verity functions explicitly.
+
+**Build phase** (``BOOTM_STATE_FINDOTHER`` → ``boot_get_loadable()``)
+
+1. After all loadable sub-images have been loaded,
+ ``fit_verity_build_cmdline()`` iterates the configuration's
+ ``loadables`` list.
+
+2. For each loadable that is an ``IH_TYPE_FILESYSTEM`` image **and**
+ contains a ``dm-verity`` child node, a dm-verity target specification is
+ built by the helper ``fit_verity_build_target()``.
+
+3. The dm-verity target references ``/dev/fitN``, where *N* is the
+ zero-based index of the loadable in the configuration. This matches the
+ numbering used by the Linux FIT block driver.
+
+4. The resulting fragments are stored in ``struct bootm_headers``:
+
+ ``images->dm_mod_create``
+ The full dm-verity target table. Multiple targets are separated by ``;``.
+
+ ``images->dm_mod_waitfor``
+ Comma-separated list of ``/dev/fitN`` devices so the kernel waits for
+ the underlying FIT block devices to appear before activating
+ device-mapper.
+
+**Apply phase** (``BOOTM_STATE_OS_PREP``)
+
+5. Just before ``bootm_process_cmdline_env()`` processes the ``bootargs``
+ environment variable, ``fit_verity_apply_bootargs()`` appends the
+ ``dm-mod.create=`` and ``dm-mod.waitfor=`` parameters.
+
+**Bootmeth integration**
+
+ Because the fragments are stored in ``struct bootm_headers``, a boot
+ method can check ``fit_verity_active(images)`` between bootm state
+ invocations. A typical pattern splits ``bootm_run_states()`` into two
+ calls -- one for ``START|FINDOS|FINDOTHER|LOADOS`` and one for
+ ``OS_PREP|OS_GO`` -- and inspects ``fit_verity_active()`` in
+ between to decide whether to add a ``root=`` parameter pointing at the
+ dm-verity device.
+
+FIT image source (.its) example
+-------------------------------
+
+Below is a minimal ``.its`` file showing a kernel and a dm-verity-protected
+root filesystem packaged as a FIT. Only the three user-provided properties
+(``algo``, ``data-block-size``, ``hash-block-size``) are included; ``mkimage``
+computes and fills in ``digest``, ``salt``, ``num-data-blocks``, and
+``hash-start-block`` automatically (see `Generating verity metadata`_ below)::
+
+ /dts-v1/;
+
+ / {
+ description = "Kernel + dm-verity rootfs";
+ #address-cells = <1>;
+
+ images {
+ kernel {
+ description = "Linux kernel";
+ data = /incbin/("./Image.gz");
+ type = "kernel";
+ arch = "arm64";
+ os = "linux";
+ compression = "gzip";
+ load = <0x44000000>;
+ entry = <0x44000000>;
+ hash-1 {
+ algo = "sha256";
+ };
+ };
+
+ fdt {
+ description = "Device tree blob";
+ data = /incbin/("./board.dtb");
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "none";
+ hash-1 {
+ algo = "sha256";
+ };
+ };
+
+ rootfs {
+ description = "SquashFS root filesystem";
+ data = /incbin/("./rootfs.squashfs");
+ type = "filesystem";
+ arch = "arm64";
+ compression = "none";
+ hash-1 {
+ algo = "sha256";
+ };
+
+ dm-verity {
+ algo = "sha256";
+ data-block-size = <4096>;
+ hash-block-size = <4096>;
+ };
+ };
+ };
+
+ configurations {
+ default = "config-1";
+ config-1 {
+ description = "Boot with dm-verity rootfs";
+ kernel = "kernel";
+ fdt = "fdt";
+ loadables = "rootfs";
+ };
+ };
+ };
+
+With this configuration U-Boot produces a kernel command line similar to::
+
+ dm-mod.create="rootfs,,, ro,0 <data_sectors> verity 1 \
+ /dev/fit0 /dev/fit0 4096 4096 3762 3762 sha256 \
+ 8e6791637f93cbb81fc45299e203cbe85ca2e47a38f5051bddeece92d7b1c9f9 \
+ aa7b11f8db8fe2e5bfd4eca1d18a22b5de7ea39d2e1b93bb7272ce0c6ca3cc8e" \
+ dm-mod.waitfor=/dev/fit0
+
+dm-verity subnode properties
+----------------------------
+
+User-provided properties (required in the ``.its``):
+
+.. list-table::
+ :header-rows: 1
+ :widths: 20 15 65
+
+ * - Property
+ - Type
+ - Description
+ * - ``algo``
+ - string
+ - Hash algorithm name, e.g. ``"sha256"``.
+ * - ``data-block-size``
+ - u32
+ - Data block size in bytes (>= 512, typically 4096).
+ * - ``hash-block-size``
+ - u32
+ - Hash block size in bytes (>= 512, typically 4096).
+
+Computed properties (filled in by ``mkimage``):
+
+.. list-table::
+ :header-rows: 1
+ :widths: 20 15 65
+
+ * - Property
+ - Type
+ - Description
+ * - ``num-data-blocks``
+ - u32
+ - Number of data blocks in the filesystem image (computed from the
+ image size and ``data-block-size``).
+ * - ``hash-start-block``
+ - u32
+ - Offset in ``hash-block-size``-sized blocks from the start of the
+ sub-image to the root block of the hash tree.
+ * - ``digest``
+ - byte array
+ - Root hash of the Merkle tree, stored as raw bytes. Length must match
+ the output size of ``algo``.
+ * - ``salt``
+ - byte array
+ - Salt used when computing the Merkle tree, stored as raw bytes.
+
+These values are the same ones produced by ``veritysetup format`` and can
+typically be obtained from its output.
+The ``digest`` and ``salt`` byte arrays correspond to the hex-encoded
+``Root hash`` and ``Salt`` printed by ``veritysetup format``.
+
+Optional boolean properties (when present, they are collected and appended
+as dm-verity optional parameters with hyphens converted to underscores):
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 70
+
+ * - Property
+ - Description
+ * - ``restart-on-corruption``
+ - Restart the system on data corruption.
+ * - ``panic-on-corruption``
+ - Panic the system on data corruption.
+ * - ``restart-on-error``
+ - Restart the system on I/O error.
+ * - ``panic-on-error``
+ - Panic the system on I/O error.
+ * - ``check-at-most-once``
+ - Verify data blocks only on first read.
+
+
+Generating verity metadata
+--------------------------
+
+``mkimage`` automates the entire process. When it encounters a
+``dm-verity`` subnode, it:
+
+1. Writes the embedded image data to a temporary file.
+2. Runs ``veritysetup format`` with the user-supplied algorithm and
+ block sizes.
+3. Parses ``Root hash`` and ``Salt`` from ``veritysetup`` stdout.
+4. Reads the expanded content (original data + Merkle hash tree) back
+ into an in-memory buffer and removes the temporary file. The
+ external-data section written to the .itb file uses this buffer in
+ place of the original ``data`` property.
+5. Writes the computed ``digest``, ``salt``, ``num-data-blocks``, and
+ ``hash-start-block`` properties into the ``dm-verity`` subnode.
+
+Images with ``dm-verity`` subnodes **must** use external data layout
+(``mkimage -E``). ``mkimage`` will abort with an error if ``-E`` is
+not specified.
+
+Usage::
+
+ # Create the filesystem image
+ mksquashfs rootfs/ rootfs.squashfs -comp xz
+
+ # Build the FIT (dm-verity is computed automatically); align each
+ # external-data section to the block size of the underlying storage
+ # (see the alignment note below).
+ mkimage -E -B 0x1000 -f image.its image.itb
+
+``veritysetup`` (from the cryptsetup_ package) must be installed on
+the build host.
+
+.. _cryptsetup: https://gitlab.com/cryptsetup/cryptsetup
+
+.. note::
+
+ ``veritysetup format`` is invoked with ``--no-superblock``, so no
+ on-disk superblock is written between the data and hash regions.
+ The Merkle hash tree is appended directly to the image data within
+ the FIT external data section. ``hash-start-block`` is therefore
+ computed as ``data_size / hash-block-size`` (the offset of the hash
+ region in units of ``hash-block-size``). When ``data-block-size``
+ equals ``hash-block-size`` this happens to equal ``num-data-blocks``.
+
+.. note::
+
+ The Linux ``fitblk`` driver currently requires each ``filesystem``
+ sub-image to start and end on block boundaries of the underlying
+ block device (typically 512 bytes, sometimes 4 KiB for eMMC or NVMe
+ with 4 KiB native sectors). Use ``mkimage -B <align>`` to pad
+ external-data sections to that boundary; ``-B 0x1000`` is a safe
+ default for the storage in common use.
+
+ This alignment requirement comes from the kernel-side ``fitblk``
+ driver to avoid unaligned-access fix-up overhead in block I/O, and
+ is **independent** of the dm-verity ``data-block-size`` and
+ ``hash-block-size`` properties -- those describe the block sizes
+ used by the device-mapper verity target itself, not storage
+ alignment.
+
+Kconfig
+-------
+
+``CONFIG_FIT_VERITY``
+ Depends on ``CONFIG_FIT`` and ``CONFIG_OF_LIBFDT``.
+ When enabled, ``fit_verity_build_cmdline()`` and
+ ``fit_verity_apply_bootargs()`` are compiled into the boot path.
+ When disabled, the functions are static inlines returning 0, so there
+ is no code-size impact. Works with both the ``bootm`` command and
+ BOOTSTD boot methods.
diff --git a/doc/usage/fit/index.rst b/doc/usage/fit/index.rst
index 6c78d8584ed..d17582b1d64 100644
--- a/doc/usage/fit/index.rst
+++ b/doc/usage/fit/index.rst
@@ -11,6 +11,7 @@ images that it reads and boots. Documentation about FIT is available in
:maxdepth: 1
beaglebone_vboot
+ dm-verity
howto
kernel_fdt
kernel_fdts_compressed
diff --git a/include/hexdump.h b/include/hexdump.h
index f2ca4793d69..5cb48d79efe 100644
--- a/include/hexdump.h
+++ b/include/hexdump.h
@@ -7,7 +7,11 @@
#ifndef HEXDUMP_H
#define HEXDUMP_H
+#ifdef USE_HOSTCC
+#include <ctype.h>
+#else
#include <linux/ctype.h>
+#endif
#include <linux/types.h>
enum dump_prefix_t {
@@ -20,7 +24,7 @@ extern const char hex_asc[];
#define hex_asc_lo(x) hex_asc[((x) & 0x0f)]
#define hex_asc_hi(x) hex_asc[((x) & 0xf0) >> 4]
-static inline char *hex_byte_pack(char *buf, u8 byte)
+static inline char *hex_byte_pack(char *buf, uint8_t byte)
{
*buf++ = hex_asc_hi(byte);
*buf++ = hex_asc_lo(byte);
@@ -52,7 +56,7 @@ static inline int hex_to_bin(char ch)
*
* Return 0 on success, -1 in case of bad input.
*/
-static inline int hex2bin(u8 *dst, const char *src, size_t count)
+static inline int hex2bin(uint8_t *dst, const char *src, size_t count)
{
while (count--) {
int hi = hex_to_bin(*src++);
diff --git a/include/image.h b/include/image.h
index 34efac6056d..7b16284257a 100644
--- a/include/image.h
+++ b/include/image.h
@@ -396,7 +396,19 @@ struct bootm_headers {
ulong cmdline_start;
ulong cmdline_end;
struct bd_info *kbd;
-#endif
+
+#if CONFIG_IS_ENABLED(FIT_VERITY)
+ /*
+ * dm-verity kernel command-line fragments, populated during FIT
+ * parsing by fit_verity_build_cmdline(). Bootmeths can check
+ * fit_verity_active() between bootm states, and
+ * fit_verity_apply_bootargs() appends these to the "bootargs"
+ * env var during BOOTM_STATE_OS_PREP.
+ */
+ char *dm_mod_create;
+ char *dm_mod_waitfor;
+#endif /* FIT_VERITY */
+#endif /* !USE_HOSTCC */
int verify; /* env_get("verify")[0] != 'n' */
@@ -756,6 +768,72 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
int arch, int image_ph_type, int bootstage_id,
enum fit_load_op load_op, ulong *datap, ulong *lenp);
+#if !defined(USE_HOSTCC) && CONFIG_IS_ENABLED(FIT_VERITY)
+/**
+ * fit_verity_build_cmdline() - build dm-verity cmdline from FIT metadata
+ * @fit: pointer to the FIT blob
+ * @conf_noffset: configuration node offset in @fit
+ * @images: bootm headers; dm_mod_create / dm_mod_waitfor are
+ * populated on success
+ *
+ * Called automatically from boot_get_loadable() during FIT parsing.
+ * For each IH_TYPE_FILESYSTEM loadable with a dm-verity subnode,
+ * builds the corresponding dm target specification.
+ *
+ * Return: 0 on success, -ve errno on error
+ */
+int fit_verity_build_cmdline(const void *fit, int conf_noffset,
+ struct bootm_headers *images);
+
+/**
+ * fit_verity_apply_bootargs() - append dm-verity params to bootargs env
+ * @images: bootm headers with dm-verity cmdline fragments
+ *
+ * Called from BOOTM_STATE_OS_PREP before bootm_process_cmdline_env().
+ *
+ * Return: 0 on success, -ve errno on error
+ */
+int fit_verity_apply_bootargs(const struct bootm_headers *images);
+
+/**
+ * fit_verity_active() - check whether dm-verity targets were found
+ * @images: bootm headers
+ *
+ * Return: true if at least one dm-verity target was built
+ */
+static inline bool fit_verity_active(const struct bootm_headers *images)
+{
+ return !!images->dm_mod_create;
+}
+
+/**
+ * fit_verity_free() - free dm-verity cmdline allocations
+ * @images: bootm headers
+ */
+void fit_verity_free(struct bootm_headers *images);
+
+#else /* !FIT_VERITY */
+
+static inline int fit_verity_build_cmdline(const void *fit, int conf_noffset,
+ struct bootm_headers *images)
+{
+ return 0;
+}
+
+static inline int fit_verity_apply_bootargs(const struct bootm_headers *images)
+{
+ return 0;
+}
+
+static inline bool fit_verity_active(const struct bootm_headers *images)
+{
+ return false;
+}
+
+static inline void fit_verity_free(struct bootm_headers *images) {}
+
+#endif /* FIT_VERITY */
+
/**
* image_locate_script() - Locate the raw script in an image
*
@@ -1079,6 +1157,23 @@ int booti_setup(ulong image, ulong *relocated_addr, ulong *size,
#define FIT_CIPHER_NODENAME "cipher"
#define FIT_ALGO_PROP "algo"
+/* dm-verity node */
+#define FIT_VERITY_NODENAME "dm-verity"
+#define FIT_VERITY_ALGO_PROP "algo"
+#define FIT_VERITY_DBS_PROP "data-block-size"
+#define FIT_VERITY_HBS_PROP "hash-block-size"
+#define FIT_VERITY_NBLK_PROP "num-data-blocks"
+#define FIT_VERITY_HBLK_PROP "hash-start-block"
+#define FIT_VERITY_DIGEST_PROP "digest"
+#define FIT_VERITY_SALT_PROP "salt"
+
+/* dm-verity error-handling modes (optional boolean property names) */
+#define FIT_VERITY_OPT_RESTART "restart-on-corruption"
+#define FIT_VERITY_OPT_PANIC "panic-on-corruption"
+#define FIT_VERITY_OPT_RERR "restart-on-error"
+#define FIT_VERITY_OPT_PERR "panic-on-error"
+#define FIT_VERITY_OPT_ONCE "check-at-most-once"
+
/* image node */
#define FIT_DATA_PROP "data"
#define FIT_DATA_POSITION_PROP "data-position"
@@ -1332,6 +1427,24 @@ int fit_add_verification_data(const char *keydir, const char *keyfile,
const char *cmdname, const char *algo_name,
struct image_summary *summary);
+#ifdef USE_HOSTCC
+/**
+ * fit_verity_get_expanded() - look up the cached dm-verity expanded buffer
+ *
+ * After mkimage has run veritysetup on a FILESYSTEM image, the original
+ * data concatenated with the Merkle hash tree is cached in memory keyed
+ * by image name. fit_extract_data() retrieves it to write the external
+ * data section without having to re-read a temporary file from disk.
+ *
+ * @name: image unit name (FDT node name under /images)
+ * @data: output -- pointer to cached buffer (do NOT free; lifetime
+ * ends when mkimage exits)
+ * @size: output -- size of @data in bytes
+ * Return: 0 if a cache entry exists for @name, -ENOENT otherwise
+ */
+int fit_verity_get_expanded(const char *name, const void **data, size_t *size);
+#endif /* USE_HOSTCC */
+
/**
* fit_image_verify_with_data() - Verify an image with given data
*
diff --git a/test/boot/Makefile b/test/boot/Makefile
index 89538d4f0a6..d98f212b243 100644
--- a/test/boot/Makefile
+++ b/test/boot/Makefile
@@ -15,6 +15,7 @@ endif
ifdef CONFIG_SANDBOX
obj-$(CONFIG_$(PHASE_)CMDLINE) += bootm.o
endif
+obj-$(CONFIG_$(PHASE_)FIT_VERITY) += fit_verity.o
obj-$(CONFIG_MEASURED_BOOT) += measurement.o
ifdef CONFIG_OF_LIVE
diff --git a/test/boot/fit_verity.c b/test/boot/fit_verity.c
new file mode 100644
index 00000000000..7459a9d6f81
--- /dev/null
+++ b/test/boot/fit_verity.c
@@ -0,0 +1,306 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Tests for FIT dm-verity cmdline generation
+ *
+ * Copyright 2026 Daniel Golle <[email protected]>
+ */
+
+#include <image.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+#define FIT_VERITY_TEST(_name, _flags) UNIT_TEST(_name, _flags, fit_verity)
+
+/* FIT blob buffer size — generous to avoid FDT_ERR_NOSPACE */
+#define FIT_BUF_SIZE 4096
+
+/* Test digest (32 bytes = sha256) */
+static const u8 test_digest[32] = {
+ 0x8e, 0x67, 0x91, 0x63, 0x7f, 0x93, 0xcb, 0xb8,
+ 0x1f, 0xc4, 0x52, 0x99, 0xe2, 0x03, 0xcb, 0xe8,
+ 0x5c, 0xa2, 0xe4, 0x7a, 0x38, 0xf5, 0x05, 0x1b,
+ 0xdd, 0xee, 0xce, 0x92, 0xd7, 0xb1, 0xc9, 0xf9,
+};
+
+/* Test salt (32 bytes) */
+static const u8 test_salt[32] = {
+ 0xaa, 0x7b, 0x11, 0xf8, 0xdb, 0x8f, 0xe2, 0xe5,
+ 0xbf, 0xd4, 0xec, 0xa1, 0xd1, 0x8a, 0x22, 0xb5,
+ 0xde, 0x7e, 0xa3, 0x9d, 0x2e, 0x1b, 0x93, 0xbb,
+ 0x72, 0x72, 0xce, 0x0c, 0x6c, 0xa3, 0xcc, 0x8e,
+};
+
+/**
+ * build_verity_fit() - construct a minimal FIT blob with dm-verity metadata
+ * @buf: output buffer (at least FIT_BUF_SIZE bytes)
+ * @num_loadables: number of filesystem loadables to create (1 or 2)
+ *
+ * Builds a FIT blob containing:
+ * - /images/rootfsN with type="filesystem" and a dm-verity subnode
+ * - /configurations/conf-1 referencing the loadable(s)
+ *
+ * Return: configuration node offset, or -ve on error
+ */
+static int build_verity_fit(void *buf, int num_loadables)
+{
+ int images_node, conf_node, confs_node, img_node, verity_node;
+ fdt32_t val;
+ int ret, i;
+ char name[32];
+ /*
+ * Build the loadables string list. FDT stringlists are concatenated
+ * NUL-terminated strings. E.g. "rootfs0\0rootfs1\0"
+ */
+ char loadables[128];
+ int loadables_len = 0;
+
+ ret = fdt_create_empty_tree(buf, FIT_BUF_SIZE);
+ if (ret)
+ return ret;
+
+ /* /images */
+ images_node = fdt_add_subnode(buf, 0, "images");
+ if (images_node < 0)
+ return images_node;
+
+ for (i = 0; i < num_loadables; i++) {
+ snprintf(name, sizeof(name), "rootfs%d", i);
+
+ img_node = fdt_add_subnode(buf, images_node, name);
+ if (img_node < 0)
+ return img_node;
+
+ ret = fdt_setprop_string(buf, img_node, FIT_TYPE_PROP,
+ "filesystem");
+ if (ret)
+ return ret;
+
+ verity_node = fdt_add_subnode(buf, img_node,
+ FIT_VERITY_NODENAME);
+ if (verity_node < 0)
+ return verity_node;
+
+ ret = fdt_setprop_string(buf, verity_node,
+ FIT_VERITY_ALGO_PROP, "sha256");
+ if (ret)
+ return ret;
+
+ val = cpu_to_fdt32(4096);
+ ret = fdt_setprop(buf, verity_node, FIT_VERITY_DBS_PROP,
+ &val, sizeof(val));
+ if (ret)
+ return ret;
+
+ ret = fdt_setprop(buf, verity_node, FIT_VERITY_HBS_PROP,
+ &val, sizeof(val));
+ if (ret)
+ return ret;
+
+ val = cpu_to_fdt32(100);
+ ret = fdt_setprop(buf, verity_node, FIT_VERITY_NBLK_PROP,
+ &val, sizeof(val));
+ if (ret)
+ return ret;
+
+ val = cpu_to_fdt32(100);
+ ret = fdt_setprop(buf, verity_node, FIT_VERITY_HBLK_PROP,
+ &val, sizeof(val));
+ if (ret)
+ return ret;
+
+ ret = fdt_setprop(buf, verity_node, FIT_VERITY_DIGEST_PROP,
+ test_digest, sizeof(test_digest));
+ if (ret)
+ return ret;
+
+ ret = fdt_setprop(buf, verity_node, FIT_VERITY_SALT_PROP,
+ test_salt, sizeof(test_salt));
+ if (ret)
+ return ret;
+
+ /* Append to loadables stringlist */
+ loadables_len += snprintf(loadables + loadables_len,
+ sizeof(loadables) - loadables_len,
+ "%s", name) + 1;
+ }
+
+ /* /configurations/conf-1 */
+ confs_node = fdt_add_subnode(buf, 0, "configurations");
+ if (confs_node < 0)
+ return confs_node;
+
+ conf_node = fdt_add_subnode(buf, confs_node, "conf-1");
+ if (conf_node < 0)
+ return conf_node;
+
+ ret = fdt_setprop(buf, conf_node, FIT_LOADABLE_PROP,
+ loadables, loadables_len);
+ if (ret)
+ return ret;
+
+ return conf_node;
+}
+
+/* Test: single dm-verity loadable produces correct cmdline fragments */
+static int fit_verity_test_single(struct unit_test_state *uts)
+{
+ char buf[FIT_BUF_SIZE];
+ struct bootm_headers images;
+ int conf_noffset;
+
+ conf_noffset = build_verity_fit(buf, 1);
+ ut_assert(conf_noffset >= 0);
+
+ memset(&images, 0, sizeof(images));
+ ut_assertok(fit_verity_build_cmdline(buf, conf_noffset, &images));
+
+ /* dm_mod_create should contain the target spec for rootfs0 */
+ ut_assertnonnull(images.dm_mod_create);
+ ut_assert(strstr(images.dm_mod_create, "rootfs0,,,"));
+ ut_assert(strstr(images.dm_mod_create, "verity 1"));
+ ut_assert(strstr(images.dm_mod_create, "/dev/fit0"));
+ ut_assert(strstr(images.dm_mod_create, "4096 4096 100 100"));
+ ut_assert(strstr(images.dm_mod_create, "sha256"));
+ /* Check hex-encoded digest prefix */
+ ut_assert(strstr(images.dm_mod_create, "8e6791637f93cbb8"));
+ /* Check hex-encoded salt prefix */
+ ut_assert(strstr(images.dm_mod_create, "aa7b11f8db8fe2e5"));
+
+ /* dm_mod_waitfor should reference /dev/fit0 */
+ ut_assertnonnull(images.dm_mod_waitfor);
+ ut_asserteq_str("/dev/fit0", images.dm_mod_waitfor);
+
+ fit_verity_free(&images);
+ ut_assertnull(images.dm_mod_create);
+ ut_assertnull(images.dm_mod_waitfor);
+
+ return 0;
+}
+FIT_VERITY_TEST(fit_verity_test_single, 0);
+
+/* Test: FIT with no dm-verity subnode returns 0, pointers stay NULL */
+static int fit_verity_test_no_verity(struct unit_test_state *uts)
+{
+ char buf[FIT_BUF_SIZE];
+ struct bootm_headers images;
+ int conf_node, images_node, img_node, confs_node;
+ int ret;
+
+ ret = fdt_create_empty_tree(buf, FIT_BUF_SIZE);
+ ut_assertok(ret);
+
+ images_node = fdt_add_subnode(buf, 0, "images");
+ ut_assert(images_node >= 0);
+
+ img_node = fdt_add_subnode(buf, images_node, "rootfs");
+ ut_assert(img_node >= 0);
+ ut_assertok(fdt_setprop_string(buf, img_node, FIT_TYPE_PROP,
+ "filesystem"));
+ /* No dm-verity subnode */
+
+ confs_node = fdt_add_subnode(buf, 0, "configurations");
+ ut_assert(confs_node >= 0);
+ conf_node = fdt_add_subnode(buf, confs_node, "conf-1");
+ ut_assert(conf_node >= 0);
+ ut_assertok(fdt_setprop_string(buf, conf_node, FIT_LOADABLE_PROP,
+ "rootfs"));
+
+ memset(&images, 0, sizeof(images));
+ ut_asserteq(0, fit_verity_build_cmdline(buf, conf_node, &images));
+ ut_assertnull(images.dm_mod_create);
+ ut_assertnull(images.dm_mod_waitfor);
+
+ return 0;
+}
+FIT_VERITY_TEST(fit_verity_test_no_verity, 0);
+
+/* Test: two dm-verity loadables produce combined cmdline */
+static int fit_verity_test_two_loadables(struct unit_test_state *uts)
+{
+ char buf[FIT_BUF_SIZE];
+ struct bootm_headers images;
+ int conf_noffset;
+
+ conf_noffset = build_verity_fit(buf, 2);
+ ut_assert(conf_noffset >= 0);
+
+ memset(&images, 0, sizeof(images));
+ ut_assertok(fit_verity_build_cmdline(buf, conf_noffset, &images));
+
+ /* Both targets should appear, separated by ";" */
+ ut_assertnonnull(images.dm_mod_create);
+ ut_assert(strstr(images.dm_mod_create, "rootfs0,,,"));
+ ut_assert(strstr(images.dm_mod_create, ";rootfs1,,,"));
+ ut_assert(strstr(images.dm_mod_create, "/dev/fit0"));
+ ut_assert(strstr(images.dm_mod_create, "/dev/fit1"));
+
+ /* dm_mod_waitfor should list both devices */
+ ut_assertnonnull(images.dm_mod_waitfor);
+ ut_assert(strstr(images.dm_mod_waitfor, "/dev/fit0"));
+ ut_assert(strstr(images.dm_mod_waitfor, "/dev/fit1"));
+
+ fit_verity_free(&images);
+ return 0;
+}
+FIT_VERITY_TEST(fit_verity_test_two_loadables, 0);
+
+/* Test: invalid block size (not power of two) returns -EINVAL */
+static int fit_verity_test_bad_blocksize(struct unit_test_state *uts)
+{
+ char buf[FIT_BUF_SIZE];
+ struct bootm_headers images;
+ int images_node, conf_node, confs_node, img_node, verity_node;
+ fdt32_t val;
+ int ret;
+
+ ret = fdt_create_empty_tree(buf, FIT_BUF_SIZE);
+ ut_assertok(ret);
+
+ images_node = fdt_add_subnode(buf, 0, "images");
+ ut_assert(images_node >= 0);
+
+ img_node = fdt_add_subnode(buf, images_node, "rootfs");
+ ut_assert(img_node >= 0);
+ ut_assertok(fdt_setprop_string(buf, img_node, FIT_TYPE_PROP,
+ "filesystem"));
+
+ verity_node = fdt_add_subnode(buf, img_node, FIT_VERITY_NODENAME);
+ ut_assert(verity_node >= 0);
+
+ ut_assertok(fdt_setprop_string(buf, verity_node,
+ FIT_VERITY_ALGO_PROP, "sha256"));
+
+ /* 3000 is not a power of two */
+ val = cpu_to_fdt32(3000);
+ ut_assertok(fdt_setprop(buf, verity_node, FIT_VERITY_DBS_PROP,
+ &val, sizeof(val)));
+ val = cpu_to_fdt32(4096);
+ ut_assertok(fdt_setprop(buf, verity_node, FIT_VERITY_HBS_PROP,
+ &val, sizeof(val)));
+
+ val = cpu_to_fdt32(100);
+ ut_assertok(fdt_setprop(buf, verity_node, FIT_VERITY_NBLK_PROP,
+ &val, sizeof(val)));
+ ut_assertok(fdt_setprop(buf, verity_node, FIT_VERITY_HBLK_PROP,
+ &val, sizeof(val)));
+
+ ut_assertok(fdt_setprop(buf, verity_node, FIT_VERITY_DIGEST_PROP,
+ test_digest, sizeof(test_digest)));
+ ut_assertok(fdt_setprop(buf, verity_node, FIT_VERITY_SALT_PROP,
+ test_salt, sizeof(test_salt)));
+
+ confs_node = fdt_add_subnode(buf, 0, "configurations");
+ ut_assert(confs_node >= 0);
+ conf_node = fdt_add_subnode(buf, confs_node, "conf-1");
+ ut_assert(conf_node >= 0);
+ ut_assertok(fdt_setprop_string(buf, conf_node, FIT_LOADABLE_PROP,
+ "rootfs"));
+
+ memset(&images, 0, sizeof(images));
+ ut_asserteq(-EINVAL, fit_verity_build_cmdline(buf, conf_node, &images));
+ ut_assertnull(images.dm_mod_create);
+ ut_assertnull(images.dm_mod_waitfor);
+
+ return 0;
+}
+FIT_VERITY_TEST(fit_verity_test_bad_blocksize, 0);
diff --git a/test/cmd_ut.c b/test/cmd_ut.c
index 44e5fdfdaa6..d1b376f617c 100644
--- a/test/cmd_ut.c
+++ b/test/cmd_ut.c
@@ -59,6 +59,7 @@ SUITE_DECL(env);
SUITE_DECL(exit);
SUITE_DECL(fdt);
SUITE_DECL(fdt_overlay);
+SUITE_DECL(fit_verity);
SUITE_DECL(font);
SUITE_DECL(hush);
SUITE_DECL(lib);
@@ -86,6 +87,7 @@ static struct suite suites[] = {
SUITE(exit, "shell exit and variables"),
SUITE(fdt, "fdt command"),
SUITE(fdt_overlay, "device tree overlays"),
+ SUITE(fit_verity, "FIT dm-verity cmdline generation"),
SUITE(font, "font command"),
SUITE(hush, "hush behaviour"),
SUITE(lib, "library functions"),
diff --git a/test/py/tests/test_fit_verity.py b/test/py/tests/test_fit_verity.py
new file mode 100644
index 00000000000..f1b6262ed0e
--- /dev/null
+++ b/test/py/tests/test_fit_verity.py
@@ -0,0 +1,175 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Copyright 2026 Daniel Golle <[email protected]>
+
+"""
+Test mkimage dm-verity Merkle-tree generation
+
+Build a minimal .its with a dm-verity subnode (user-provided properties only),
+run mkimage -E, and verify that the computed properties (digest, salt,
+num-data-blocks, hash-start-block) are written into the resulting FIT.
+The computed digest is then re-verified by running ``veritysetup verify``
+against the external data section of the .itb.
+
+This test does not run the sandbox. It only exercises the host tool 'mkimage'.
+Requires 'veritysetup' from the cryptsetup package on the build host.
+"""
+
+import os
+import struct
+import pytest
+import utils
+
+ITS_TEMPLATE = """\
+/dts-v1/;
+
+/ {
+ description = "dm-verity test";
+ #address-cells = <1>;
+
+ images {
+ rootfs {
+ description = "test filesystem";
+ data = /incbin/("./rootfs.bin");
+ type = "filesystem";
+ arch = "sandbox";
+ compression = "none";
+
+ dm-verity {
+ algo = "sha256";
+ data-block-size = <%d>;
+ hash-block-size = <%d>;
+ };
+ };
+ };
+
+ configurations {
+ default = "conf-1";
+ conf-1 {
+ description = "test config";
+ loadables = "rootfs";
+ };
+ };
+};
+"""
+
+def _fdt_totalsize(path):
+ """Read the totalsize field from an FDT header (offset 4, big-endian u32)."""
+ with open(path, 'rb') as f:
+ magic, totalsize = struct.unpack('>II', f.read(8))
+ assert magic == 0xd00dfeed, f'not an FDT: magic={magic:#x}'
+ return totalsize
+
+
+def _run_round_trip(ubman, tempdir, data_block_size, hash_block_size):
+ """Build a FIT with dm-verity, verify written properties, re-verify with veritysetup."""
+ mkimage = ubman.config.build_dir + '/tools/mkimage'
+
+ rootfs_file = os.path.join(tempdir, 'rootfs.bin')
+ its_file = os.path.join(tempdir, 'image.its')
+ fit_file = os.path.join(tempdir, 'image.itb')
+
+ # 64 data blocks of 0xa5
+ num_blocks = 64
+ data_size = data_block_size * num_blocks
+ with open(rootfs_file, 'wb') as f:
+ f.write(bytes([0xa5]) * data_size)
+
+ with open(its_file, 'w') as f:
+ f.write(ITS_TEMPLATE % (data_block_size, hash_block_size))
+
+ dtc_args = f'-I dts -O dtb -i {tempdir}'
+ utils.run_and_log(ubman,
+ [mkimage, '-E', '-D', dtc_args, '-f', its_file, fit_file])
+
+ def fdt_get(node, prop):
+ val = utils.run_and_log(ubman, f'fdtget {fit_file} {node} {prop}')
+ return val.strip()
+
+ def fdt_get_hex(node, prop):
+ val = utils.run_and_log(ubman, f'fdtget -tbx {fit_file} {node} {prop}')
+ return ''.join(b.zfill(2) for b in val.strip().split())
+
+ verity_path = '/images/rootfs/dm-verity'
+
+ assert fdt_get(verity_path, 'algo') == 'sha256'
+ assert int(fdt_get(verity_path, 'data-block-size')) == data_block_size
+ assert int(fdt_get(verity_path, 'hash-block-size')) == hash_block_size
+
+ nblk = int(fdt_get(verity_path, 'num-data-blocks'))
+ assert nblk == num_blocks, f'num-data-blocks {nblk} != {num_blocks}'
+
+ hblk = int(fdt_get(verity_path, 'hash-start-block'))
+ # With --no-superblock, hash-start-block = data_size / hash-block-size
+ assert hblk == data_size // hash_block_size, \
+ f'hash-start-block {hblk} != {data_size // hash_block_size}'
+
+ digest = fdt_get_hex(verity_path, 'digest')
+ assert len(digest) == 64 and digest != '0' * 64
+ salt = fdt_get_hex(verity_path, 'salt')
+ assert len(salt) == 64
+
+ # Re-verify the digest with veritysetup against the .itb's external data.
+ # With -E, image data sits after the FIT FDT at (fdt_totalsize + data-offset).
+ data_offset = int(fdt_get('/images/rootfs', 'data-offset'))
+ data_size_full = int(fdt_get('/images/rootfs', 'data-size'))
+ ext_pos = _fdt_totalsize(fit_file) + data_offset
+ expanded = os.path.join(tempdir, 'expanded.bin')
+ with open(fit_file, 'rb') as src, open(expanded, 'wb') as dst:
+ src.seek(ext_pos)
+ dst.write(src.read(data_size_full))
+
+ utils.run_and_log(ubman, [
+ 'veritysetup', 'verify', expanded, expanded, digest,
+ '--no-superblock',
+ f'--data-block-size={data_block_size}',
+ f'--hash-block-size={hash_block_size}',
+ f'--data-blocks={nblk}',
+ '--hash=sha256',
+ f'--salt={salt}',
+ f'--hash-offset={data_size}',
+ ])
+
+
[email protected]('veritysetup')
[email protected]('data_block_size,hash_block_size,subdir', [
+ (4096, 4096, 'verity-equal'),
+ (4096, 1024, 'verity-unequal'),
+])
+def test_mkimage_verity(ubman, data_block_size, hash_block_size, subdir):
+ """mkimage writes correct dm-verity properties and the digest verifies.
+
+ Run with matching and mismatched block sizes so the
+ ``hash-start-block != num-data-blocks`` path is exercised.
+ """
+ tempdir = os.path.join(ubman.config.result_dir, subdir)
+ os.makedirs(tempdir, exist_ok=True)
+ _run_round_trip(ubman, tempdir, data_block_size, hash_block_size)
+
+
[email protected]('veritysetup')
+def test_mkimage_verity_requires_external(ubman):
+ """mkimage rejects dm-verity without -E with the expected diagnostic."""
+
+ mkimage = ubman.config.build_dir + '/tools/mkimage'
+ tempdir = os.path.join(ubman.config.result_dir, 'verity_no_ext')
+ os.makedirs(tempdir, exist_ok=True)
+
+ rootfs_file = os.path.join(tempdir, 'rootfs.bin')
+ its_file = os.path.join(tempdir, 'image.its')
+ fit_file = os.path.join(tempdir, 'image.itb')
+
+ with open(rootfs_file, 'wb') as f:
+ f.write(bytes([0xa5]) * 4096 * 8)
+
+ with open(its_file, 'w') as f:
+ f.write(ITS_TEMPLATE % (4096, 4096))
+
+ dtc_args = f'-I dts -O dtb -i {tempdir}'
+ utils.run_and_log_expect_exception(
+ ubman,
+ [mkimage, '-D', dtc_args, '-f', its_file, fit_file],
+ 1, 'dm-verity requires external data')
diff --git a/tools/fit_image.c b/tools/fit_image.c
index 1dbc14c63e4..b53088bf783 100644
--- a/tools/fit_image.c
+++ b/tools/fit_image.c
@@ -40,10 +40,10 @@ static int fit_estimate_hash_sig_size(struct image_tool_params *params, const ch
return -EIO;
/*
- * Walk the FIT image, looking for nodes named hash* and
- * signature*. Since the interesting nodes are subnodes of an
- * image or configuration node, we are only interested in
- * those at depth exactly 3.
+ * Walk the FIT image, looking for nodes named hash*,
+ * signature*, and dm-verity. Since the interesting nodes are
+ * subnodes of an image or configuration node, we are only
+ * interested in those at depth exactly 3.
*
* The estimate for a hash node is based on a sha512 digest
* being 64 bytes, with another 64 bytes added to account for
@@ -55,6 +55,10 @@ static int fit_estimate_hash_sig_size(struct image_tool_params *params, const ch
* account for fdt overhead and the various other properties
* (hashed-nodes etc.) that will also be filled in.
*
+ * For a dm-verity node the small metadata properties (digest,
+ * salt, two u32s and a temp-file path) are written into the
+ * FDT by fit_image_process_verity().
+ *
* One could try to be more precise in the estimates by
* looking at the "algo" property and, in the case of
* configuration signatures, the sign-images property. Also,
@@ -76,6 +80,18 @@ static int fit_estimate_hash_sig_size(struct image_tool_params *params, const ch
if (signing && !strncmp(name, FIT_SIG_NODENAME, strlen(FIT_SIG_NODENAME)))
estimate += 1024;
+
+ if (!strcmp(name, FIT_VERITY_NODENAME)) {
+ if (!params->external_data) {
+ fprintf(stderr,
+ "%s: dm-verity requires external data (-E)\n",
+ params->cmdname);
+ munmap(fdt, sbuf.st_size);
+ close(fd);
+ return -EINVAL;
+ }
+ estimate += 256;
+ }
}
munmap(fdt, sbuf.st_size);
@@ -471,6 +487,41 @@ static int fit_write_images(struct image_tool_params *params, char *fdt)
}
/**
+ * fit_copy_image_data() - copy image data, using cached verity expansion
+ * @fdt: FIT blob
+ * @node: image node offset
+ * @buf: destination buffer
+ * @buf_ptr: write offset within @buf
+ * @data: embedded image data (used when no dm-verity expansion exists)
+ * @lenp: in/out: on entry, length of @data; on exit, bytes written
+ *
+ * When fit_image_process_verity() has run, the expanded image data
+ * (original + hash tree) is cached in memory. Look it up by image name
+ * and copy from the cached buffer rather than the embedded ``data``
+ * property; fall back to @data otherwise.
+ *
+ * Return: 0 on success
+ */
+static int fit_copy_image_data(void *fdt, int node, void *buf,
+ int buf_ptr, const void *data, int *lenp)
+{
+ const char *image_name = fdt_get_name(fdt, node, NULL);
+ const void *vdata;
+ size_t vsize;
+
+ if (image_name &&
+ !fit_verity_get_expanded(image_name, &vdata, &vsize)) {
+ memcpy(buf + buf_ptr, vdata, vsize);
+ *lenp = vsize;
+ return 0;
+ }
+
+ memcpy(buf + buf_ptr, data, *lenp);
+
+ return 0;
+}
+
+/**
* fit_write_configs() - Write out a list of configurations to the FIT
*
* If there are device tree files, we include a configuration for each, which
@@ -653,6 +704,8 @@ static int fit_extract_data(struct image_tool_params *params, const char *fname)
int node;
int align_size = 0;
int len = 0;
+ int verity_extra = 0;
+ int orig_len;
fd = mmap_fdt(params->cmdname, fname, 0, &fdt, &sbuf, false, false);
if (fd < 0)
@@ -687,10 +740,33 @@ static int fit_extract_data(struct image_tool_params *params, const char *fname)
}
/*
+ * When dm-verity is active the external data for an image is
+ * larger than the embedded data property (original + hash tree).
+ * Walk images once more and consult the in-memory cache for the
+ * actual expanded size.
+ */
+ fdt_for_each_subnode(node, fdt, images) {
+ const char *image_name;
+ const void *vdata;
+ size_t vsize;
+
+ orig_len = 0;
+ if (fdt_subnode_offset(fdt, node, FIT_VERITY_NODENAME) < 0)
+ continue;
+ image_name = fdt_get_name(fdt, node, NULL);
+ if (!image_name ||
+ fit_verity_get_expanded(image_name, &vdata, &vsize))
+ continue;
+ fdt_getprop(fdt, node, FIT_DATA_PROP, &orig_len);
+ if ((int)vsize > orig_len)
+ verity_extra += (int)vsize - orig_len;
+ }
+
+ /*
* Allocate space to hold the image data we will extract,
* extral space allocate for image alignment to prevent overflow.
*/
- buf = calloc(1, fit_size + align_size);
+ buf = calloc(1, fit_size + align_size + verity_extra);
if (!buf) {
ret = -ENOMEM;
goto err_munmap;
@@ -721,7 +797,10 @@ static int fit_extract_data(struct image_tool_params *params, const char *fname)
data = fdt_getprop(fdt, node, FIT_DATA_PROP, &len);
if (!data)
continue;
- memcpy(buf + buf_ptr, data, len);
+
+ ret = fit_copy_image_data(fdt, node, buf, buf_ptr, data, &len);
+ if (ret)
+ goto err_munmap;
debug("Extracting data size %x\n", len);
ret = fdt_delprop(fdt, node, FIT_DATA_PROP);
diff --git a/tools/image-host.c b/tools/image-host.c
index 8b550af0dc1..8f1e7be4066 100644
--- a/tools/image-host.c
+++ b/tools/image-host.c
@@ -12,8 +12,12 @@
#include <bootm.h>
#include <fdt_region.h>
#include <image.h>
+#include <hexdump.h>
#include <version.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
#if CONFIG_IS_ENABLED(FIT_SIGNATURE)
#include <openssl/pem.h>
#include <openssl/evp.h>
@@ -626,6 +630,376 @@ int fit_image_cipher_data(const char *keydir, void *keydest,
image_noffset, cipher_node_offset, data, size, cmdname);
}
+/*
+ * In-memory cache of dm-verity expanded buffers (original data followed
+ * by the Merkle hash tree), keyed by image unit name. Populated by
+ * fit_image_process_verity() and consumed by fit_extract_data() /
+ * fit_copy_image_data() so that the expanded content never leaves the
+ * mkimage process address space.
+ */
+struct fit_verity_blob {
+ char *name;
+ void *data;
+ size_t size;
+ struct fit_verity_blob *next;
+};
+
+static struct fit_verity_blob *fit_verity_blobs;
+
+/* Stash a malloc'd expanded buffer; takes ownership of @data on success. */
+static int fit_verity_stash(const char *name, void *data, size_t size)
+{
+ struct fit_verity_blob *b;
+
+ b = calloc(1, sizeof(*b));
+ if (!b)
+ return -ENOMEM;
+ b->name = strdup(name);
+ if (!b->name) {
+ free(b);
+ return -ENOMEM;
+ }
+ b->data = data;
+ b->size = size;
+ b->next = fit_verity_blobs;
+ fit_verity_blobs = b;
+
+ return 0;
+}
+
+int fit_verity_get_expanded(const char *name, const void **data, size_t *size)
+{
+ struct fit_verity_blob *b;
+
+ for (b = fit_verity_blobs; b; b = b->next) {
+ if (!strcmp(b->name, name)) {
+ *data = b->data;
+ *size = b->size;
+ return 0;
+ }
+ }
+
+ return -ENOENT;
+}
+
+/**
+ * fit_image_process_verity() - Run veritysetup and fill dm-verity properties
+ *
+ * Extracts the embedded image data to a temporary file, runs
+ * ``veritysetup format`` to generate the Merkle hash tree (appended to the
+ * same file), parses Root hash / Salt from its stdout, and writes the
+ * computed properties (digest, salt, num-data-blocks, hash-start-block)
+ * back into the FIT dm-verity subnode.
+ *
+ * The expanded data (original data + hash tree) is read back into a
+ * malloc'd buffer and stashed in an in-memory cache keyed by @image_name
+ * via fit_verity_stash(). The same buffer is returned through
+ * @expanded_data / @expanded_size so that hash and signature subnodes
+ * can be computed over the complete image; the returned pointer is a
+ * *view* of the cached buffer and must not be freed by the caller.
+ * fit_extract_data() later retrieves the same buffer via
+ * fit_verity_get_expanded() to write the external data section.
+ *
+ * @fit: FIT blob (read-write)
+ * @image_name: image unit name (for diagnostics)
+ * @verity_noffset: dm-verity subnode offset
+ * @data: embedded image data
+ * @data_size: size of @data in bytes
+ * @expanded_data: output -- malloc'd buffer with expanded content
+ * @expanded_size: output -- size of @expanded_data
+ * Return: 0 on success, -ve on error (-ENOSPC when the FIT blob is full)
+ */
+static int fit_image_process_verity(void *fit, const char *image_name,
+ int verity_noffset,
+ const void *data, size_t data_size,
+ void **expanded_data, size_t *expanded_size)
+{
+ const char *algo_prop;
+ char algo[64];
+ const fdt32_t *val;
+ unsigned int data_block_size, hash_block_size;
+ uint32_t num_data_blocks;
+ size_t hash_offset;
+ uint32_t hash_start_block;
+ char tmpfile[] = "/tmp/mkimage-verity-XXXXXX";
+ char dbs_arg[32], hbs_arg[32], algo_arg[80], hoff_arg[40];
+ int pipefd[2];
+ pid_t pid;
+ FILE *fp;
+ char line[256];
+ char *colon, *value, *end;
+ char root_hash_hex[256] = {0};
+ char salt_hex[256] = {0};
+ uint8_t digest_bin[FIT_MAX_HASH_LEN];
+ uint8_t salt_bin[FIT_MAX_HASH_LEN];
+ int digest_len = 0, salt_len = 0;
+ void *expanded = NULL;
+ struct stat st;
+ int fd, ret;
+
+ *expanded_data = NULL;
+ *expanded_size = 0;
+
+ algo_prop = fdt_getprop(fit, verity_noffset, FIT_VERITY_ALGO_PROP,
+ NULL);
+ if (!algo_prop) {
+ fprintf(stderr,
+ "Missing '%s' in dm-verity node of '%s'\n",
+ FIT_VERITY_ALGO_PROP, image_name);
+ return -EINVAL;
+ }
+ /* Local copy -- the FDT pointer goes stale after fdt_setprop(). */
+ snprintf(algo, sizeof(algo), "%s", algo_prop);
+
+ val = fdt_getprop(fit, verity_noffset, FIT_VERITY_DBS_PROP, NULL);
+ if (!val) {
+ fprintf(stderr,
+ "Missing '%s' in dm-verity node of '%s'\n",
+ FIT_VERITY_DBS_PROP, image_name);
+ return -EINVAL;
+ }
+ data_block_size = fdt32_to_cpu(*val);
+
+ val = fdt_getprop(fit, verity_noffset, FIT_VERITY_HBS_PROP, NULL);
+ if (!val) {
+ fprintf(stderr,
+ "Missing '%s' in dm-verity node of '%s'\n",
+ FIT_VERITY_HBS_PROP, image_name);
+ return -EINVAL;
+ }
+ hash_block_size = fdt32_to_cpu(*val);
+
+ if (data_block_size < 512 || (data_block_size & (data_block_size - 1)) ||
+ hash_block_size < 512 || (hash_block_size & (hash_block_size - 1))) {
+ fprintf(stderr,
+ "Block sizes must be >= 512 and a power of two in dm-verity node of '%s'\n",
+ image_name);
+ return -EINVAL;
+ }
+
+ if (data_size % data_block_size) {
+ fprintf(stderr,
+ "Image '%s' size %zu not a multiple of data-block-size %d\n",
+ image_name, data_size, data_block_size);
+ return -EINVAL;
+ }
+
+ if (data_size / data_block_size > UINT32_MAX ||
+ data_size / hash_block_size > UINT32_MAX) {
+ fprintf(stderr,
+ "Image '%s' too large for dm-verity (> 2^32 blocks)\n",
+ image_name);
+ return -EINVAL;
+ }
+ num_data_blocks = data_size / data_block_size;
+ hash_offset = data_size;
+
+ fd = mkstemp(tmpfile);
+ if (fd < 0) {
+ fprintf(stderr, "Can't create temp file: %s\n",
+ strerror(errno));
+ return -EIO;
+ }
+
+ if (write(fd, data, data_size) != (ssize_t)data_size) {
+ fprintf(stderr, "Can't write temp file: %s\n",
+ strerror(errno));
+ ret = -EIO;
+ goto err_unlink;
+ }
+ close(fd);
+ fd = -1;
+
+ /*
+ * Invoke veritysetup via fork/execvp -- no shell, so each argument
+ * goes verbatim to the binary and the algo string cannot inject
+ * additional commands no matter how crafted the .its is.
+ */
+ snprintf(algo_arg, sizeof(algo_arg), "--hash=%s", algo);
+ snprintf(dbs_arg, sizeof(dbs_arg), "--data-block-size=%u",
+ data_block_size);
+ snprintf(hbs_arg, sizeof(hbs_arg), "--hash-block-size=%u",
+ hash_block_size);
+ snprintf(hoff_arg, sizeof(hoff_arg), "--hash-offset=%zu", hash_offset);
+
+ if (pipe(pipefd) < 0) {
+ fprintf(stderr, "Can't create pipe: %s\n", strerror(errno));
+ ret = -EIO;
+ goto err_unlink;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ fprintf(stderr, "Can't fork: %s\n", strerror(errno));
+ close(pipefd[0]);
+ close(pipefd[1]);
+ ret = -EIO;
+ goto err_unlink;
+ }
+
+ if (pid == 0) {
+ /* child: redirect stdout+stderr to pipe write-end, then exec */
+ char *argv[] = {
+ "veritysetup", "format", tmpfile, tmpfile,
+ "--no-superblock", algo_arg, dbs_arg, hbs_arg,
+ hoff_arg, NULL,
+ };
+
+ close(pipefd[0]);
+ if (dup2(pipefd[1], STDOUT_FILENO) < 0 ||
+ dup2(pipefd[1], STDERR_FILENO) < 0)
+ _exit(127);
+ close(pipefd[1]);
+ execvp(argv[0], argv);
+ fprintf(stderr, "Can't exec veritysetup: %s\n",
+ strerror(errno));
+ _exit(127);
+ }
+
+ /* parent: parse key: value lines from veritysetup stdout */
+ close(pipefd[1]);
+ fp = fdopen(pipefd[0], "r");
+ if (!fp) {
+ fprintf(stderr, "Can't fdopen veritysetup pipe: %s\n",
+ strerror(errno));
+ close(pipefd[0]);
+ waitpid(pid, NULL, 0);
+ ret = -EIO;
+ goto err_unlink;
+ }
+
+ while (fgets(line, sizeof(line), fp)) {
+ colon = strchr(line, ':');
+ if (!colon)
+ continue;
+ value = colon + 1;
+ while (*value == ' ' || *value == '\t')
+ value++;
+ end = value + strlen(value) - 1;
+ while (end > value && (*end == '\n' || *end == '\r' ||
+ *end == ' '))
+ *end-- = '\0';
+
+ if (!strncmp(line, "Root hash:", 10))
+ snprintf(root_hash_hex, sizeof(root_hash_hex),
+ "%s", value);
+ else if (!strncmp(line, "Salt:", 5))
+ snprintf(salt_hex, sizeof(salt_hex), "%s", value);
+ }
+ fclose(fp);
+
+ if (waitpid(pid, &ret, 0) < 0 || !WIFEXITED(ret) ||
+ WEXITSTATUS(ret) != 0) {
+ fprintf(stderr, "veritysetup failed for '%s'\n", image_name);
+ ret = -EIO;
+ goto err_unlink;
+ }
+
+ if (!root_hash_hex[0] || !salt_hex[0]) {
+ fprintf(stderr, "Failed to parse veritysetup output for '%s'\n",
+ image_name);
+ ret = -EIO;
+ goto err_unlink;
+ }
+
+ digest_len = strlen(root_hash_hex) / 2;
+ salt_len = strlen(salt_hex) / 2;
+
+ if (digest_len > (int)sizeof(digest_bin) ||
+ salt_len > (int)sizeof(salt_bin)) {
+ fprintf(stderr, "Hash/salt too long for '%s'\n", image_name);
+ ret = -EINVAL;
+ goto err_unlink;
+ }
+
+ if (hex2bin(digest_bin, root_hash_hex, digest_len) ||
+ hex2bin(salt_bin, salt_hex, salt_len)) {
+ fprintf(stderr, "Invalid hex in veritysetup output for '%s'\n",
+ image_name);
+ ret = -EINVAL;
+ goto err_unlink;
+ }
+
+ if (stat(tmpfile, &st)) {
+ fprintf(stderr, "Can't stat temp file: %s\n",
+ strerror(errno));
+ ret = -EIO;
+ goto err_unlink;
+ }
+
+ expanded = malloc(st.st_size);
+ if (!expanded) {
+ ret = -ENOMEM;
+ goto err_unlink;
+ }
+
+ fd = open(tmpfile, O_RDONLY);
+ if (fd < 0 || read(fd, expanded, st.st_size) != st.st_size) {
+ fprintf(stderr, "Can't read back temp file: %s\n",
+ strerror(errno));
+ ret = -EIO;
+ goto err_free;
+ }
+ close(fd);
+ fd = -1;
+
+ /* Temp file is no longer needed -- expanded buffer lives in memory. */
+ unlink(tmpfile);
+
+ /* hash tree starts immediately after data (no superblock) */
+ hash_start_block = hash_offset / hash_block_size;
+
+ ret = fdt_setprop(fit, verity_noffset, FIT_VERITY_DIGEST_PROP,
+ digest_bin, digest_len);
+ if (ret) {
+ ret = (ret == -FDT_ERR_NOSPACE) ? -ENOSPC : -EIO;
+ goto err_free;
+ }
+
+ ret = fdt_setprop(fit, verity_noffset, FIT_VERITY_SALT_PROP,
+ salt_bin, salt_len);
+ if (ret) {
+ ret = (ret == -FDT_ERR_NOSPACE) ? -ENOSPC : -EIO;
+ goto err_free;
+ }
+
+ ret = fdt_setprop_u32(fit, verity_noffset, FIT_VERITY_NBLK_PROP,
+ num_data_blocks);
+ if (ret) {
+ ret = (ret == -FDT_ERR_NOSPACE) ? -ENOSPC : -EIO;
+ goto err_free;
+ }
+
+ ret = fdt_setprop_u32(fit, verity_noffset, FIT_VERITY_HBLK_PROP,
+ hash_start_block);
+ if (ret) {
+ ret = (ret == -FDT_ERR_NOSPACE) ? -ENOSPC : -EIO;
+ goto err_free;
+ }
+
+ /*
+ * Stash the expanded buffer in the in-process cache; fit_extract_data()
+ * looks it up via fit_verity_get_expanded() to populate the external
+ * data section. On success the cache takes ownership of @expanded.
+ */
+ ret = fit_verity_stash(image_name, expanded, st.st_size);
+ if (ret)
+ goto err_free;
+
+ *expanded_data = expanded;
+ *expanded_size = st.st_size;
+
+ return 0;
+
+err_free:
+ free(expanded);
+err_unlink:
+ if (fd >= 0)
+ close(fd);
+ unlink(tmpfile);
+ return ret;
+}
+
/**
* fit_image_add_verification_data() - calculate/set verig. data for image node
*
@@ -652,6 +1026,8 @@ int fit_image_cipher_data(const char *keydir, void *keydest,
*
* For signature details, please see doc/usage/fit/signature.rst
*
+ * For dm-verity details, please see doc/usage/fit/dm-verity.rst
+ *
* @keydir Directory containing *.key and *.crt files (or NULL)
* @keydest FDT Blob to write public keys into (NULL if none)
* @fit: Pointer to the FIT format image header
@@ -667,9 +1043,16 @@ int fit_image_add_verification_data(const char *keydir, const char *keyfile,
const char *cmdname, const char* algo_name)
{
const char *image_name;
+ const char *node_name;
const void *data;
size_t size;
+ /*
+ * View pointer into the dm-verity cache (owned by image-host.c).
+ * Do not free; the cache lives until mkimage exits.
+ */
+ void *verity_data = NULL;
int noffset;
+ int ret;
/* Get image data and data length */
if (fit_image_get_emb_data(fit, image_noffset, &data, &size)) {
@@ -679,13 +1062,38 @@ int fit_image_add_verification_data(const char *keydir, const char *keyfile,
image_name = fit_get_name(fit, image_noffset, NULL);
- /* Process all hash subnodes of the component image node */
+ /*
+ * Pass 1 -- dm-verity: run veritysetup to produce the Merkle
+ * hash tree and fill in computed metadata. The expanded
+ * content (original data + hash tree) is returned in
+ * verity_data so that pass 2 hashes the complete image.
+ */
for (noffset = fdt_first_subnode(fit, image_noffset);
noffset >= 0;
noffset = fdt_next_subnode(fit, noffset)) {
- const char *node_name;
- int ret = 0;
+ if (!strcmp(fit_get_name(fit, noffset, NULL),
+ FIT_VERITY_NODENAME)) {
+ ret = fit_image_process_verity(fit, image_name,
+ noffset,
+ data, size,
+ &verity_data,
+ &size);
+ if (ret)
+ return ret;
+ if (verity_data)
+ data = verity_data;
+ break;
+ }
+ }
+ /*
+ * Pass 2 -- hashes and signatures: compute over the (possibly
+ * expanded) image data.
+ */
+ for (noffset = fdt_first_subnode(fit, image_noffset);
+ noffset >= 0;
+ noffset = fdt_next_subnode(fit, noffset)) {
+ ret = 0;
/*
* Check subnode name, must be equal to "hash" or "signature".
* Multiple hash nodes require unique unit node