All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v5 0/8] fit: dm-verity support
@ 2026-05-15 23:37 Daniel Golle
  2026-05-15 23:37 ` [PATCH v5 1/8] image: fit: add dm-verity property name constants Daniel Golle
                   ` (7 more replies)
  0 siblings, 8 replies; 9+ messages in thread
From: Daniel Golle @ 2026-05-15 23:37 UTC (permalink / raw)
  To: Tom Rini, Simon Glass, Mario Six, Quentin Schulz, Kory Maincent,
	Mattijs Korpershoek, Peng Fan, Martin Schwan, Daniel Golle,
	Anshul Dalal, Sughosh Ganu, Ilias Apalodimas, Ludwig Nussel,
	Benjamin ROBIN, Marek Vasut, James Hilliard, Julien Stephan,
	David Lechner, Kunihiko Hayashi, Neil Armstrong, Svyatoslav Ryhel,
	Michal Simek, Pieter Van Trappen, Dinesh Maniyam, Sam Protsenko,
	Mayuresh Chitale, Shiji Yang, Jonas Karlman, Wolfgang Wallner,
	Aristo Chen, Rasmus Villemoes, Francois Berder, u-boot

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/u-boot@lists.denx.de/msg565945.html
    v1:  https://www.mail-archive.com/u-boot@lists.denx.de/msg569472.html
    v2:  https://www.mail-archive.com/u-boot@lists.denx.de/msg570599.html
    v3:  https://www.mail-archive.com/u-boot@lists.denx.de/msg573223.html
    v4:  https://www.mail-archive.com/u-boot@lists.denx.de/msg574000.html

[2] flat-image-tree dm-verity node spec:
    https://github.com/open-source-firmware/flat-image-tree/commit/795fd5fd7f0121d0cb03efb1900aafc61c704771

v5: address comments by Heinrich Schuchardt and Simon Glass
 * mkimage: drop unused image_noffset parameter from
   fit_image_process_verity()
 * mkimage: replace popen() and the valid_algos[] allowlist with
   fork()/execvp(), eliminating shell-injection risk and allowlist
   drift
 * mkimage: drop the verity-data-file FDT property; cache the
   expanded buffer (original data + Merkle hash tree) in memory keyed
   by image name, unlink the temporary file immediately after read-
   back, and expose fit_verity_get_expanded() so fit_extract_data()
   consumes the buffer directly -- removes the tmpfile-leak surface
   along the way
 * mkimage: use unsigned int for data-block-size / hash-block-size
   on the host side too (consistency with v3 runtime change)
 * doc: document that the fitblk driver requires each filesystem
   sub-image to be aligned to the underlying block-device block size,
   and that 'mkimage -B <align>' (typically -B 0x1000) achieves this;
   clarify that this is independent of the dm-verity data-block-size
   / hash-block-size properties

v4: address comments by Simon Glass
 * pytest: verify the computed digest with veritysetup verify against
   the external data section
 * pytest: parametrize test_mkimage_verity with matched and mismatched
   block sizes to exercise hash-start-block != num-data-blocks
 * pytest: use run_and_log_expect_exception() with the expected
   diagnostic for the no-external-data case

v3: address comments by Heinrich Schuchardt and Simon Glass
 * use unsigned int instead of int for data-block-size and hash-block-size
 * replace printf() with log_err() for the "broken dm-verity metadata"
   diagnostic
 * use FIT_VERITY_*_PROP, FIT_TYPE_PROP and FIT_LOADABLE_PROP constants
   in the unit test instead of literal strings
 * extend the mkimage block-count overflow check to also cover
   hash_start_block (matters when hash-block-size < data-block-size)
 * doc: clarify that hash-start-block only equals num-data-blocks when
   data-block-size == hash-block-size
 * pytest: drop unused 'struct' import and the home-rolled
   have_veritysetup() helper in favour of
   @pytest.mark.requiredtool('veritysetup')

v2: address comments by Simon Glass
 * use is_power_of_2() for pre-boot sanity check
 * let fit_verity_build_cmdline() return 0 on success
 * add comment explaining why bootm_start() calls fit_verity_free()
 * use existing hex2bin() (and adapt it to be usable for host-tools)
 * fix stale comment still including superblock despite veritysetup
   being called with --no-superblock
 * add power-of-two check for data-block-size and hash-block-size to
   mkimage
 * don't ignore return value of fdt_delprop()
 * various documentation fixes, minimal example
 * add pytest for mkimage part
 * add run-time unit test for cmdline generation part

Daniel Golle (8):
  image: fit: add dm-verity property name constants
  boot: fit: support generating DM verity cmdline parameters
  include: hexdump: make hex2bin() usable from host tools
  tools: mkimage: add dm-verity Merkle-tree generation
  doc: fit: add dm-verity boot parameter documentation
  test: boot: add runtime unit test for fit_verity_build_cmdline()
  test: py: add mkimage dm-verity round-trip test
  configs: sandbox: enable CONFIG_FIT_VERITY

 boot/Kconfig                       |  20 ++
 boot/bootm.c                       |  13 +
 boot/image-board.c                 |   5 +
 boot/image-fit.c                   | 337 +++++++++++++++++++++++
 configs/sandbox64_defconfig        |   1 +
 configs/sandbox_defconfig          |   1 +
 configs/sandbox_flattree_defconfig |   1 +
 doc/usage/fit/dm-verity.rst        | 304 +++++++++++++++++++++
 doc/usage/fit/index.rst            |   1 +
 include/hexdump.h                  |   8 +-
 include/image.h                    | 115 +++++++-
 test/boot/Makefile                 |   1 +
 test/boot/fit_verity.c             | 306 +++++++++++++++++++++
 test/cmd_ut.c                      |   2 +
 test/py/tests/test_fit_verity.py   | 175 ++++++++++++
 tools/fit_image.c                  |  91 ++++++-
 tools/image-host.c                 | 414 ++++++++++++++++++++++++++++-
 17 files changed, 1783 insertions(+), 12 deletions(-)
 create mode 100644 doc/usage/fit/dm-verity.rst
 create mode 100644 test/boot/fit_verity.c
 create mode 100644 test/py/tests/test_fit_verity.py

-- 
2.54.0

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v5 1/8] image: fit: add dm-verity property name constants
  2026-05-15 23:37 [PATCH v5 0/8] fit: dm-verity support Daniel Golle
@ 2026-05-15 23:37 ` Daniel Golle
  2026-05-15 23:37 ` [PATCH v5 2/8] boot: fit: support generating DM verity cmdline parameters Daniel Golle
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Daniel Golle @ 2026-05-15 23:37 UTC (permalink / raw)
  To: Tom Rini, Simon Glass, Mario Six, Quentin Schulz, Kory Maincent,
	Mattijs Korpershoek, Peng Fan, Martin Schwan, Daniel Golle,
	Anshul Dalal, Sughosh Ganu, Ilias Apalodimas, Ludwig Nussel,
	Benjamin ROBIN, Marek Vasut, James Hilliard, Julien Stephan,
	David Lechner, Kunihiko Hayashi, Neil Armstrong, Svyatoslav Ryhel,
	Michal Simek, Pieter Van Trappen, Dinesh Maniyam, Sam Protsenko,
	Mayuresh Chitale, Shiji Yang, Jonas Karlman, Wolfgang Wallner,
	Aristo Chen, Rasmus Villemoes, Francois Berder, u-boot

Add FIT_VERITY_NODENAME and the complete set of FIT_VERITY_*_PROP
constants for the dm-verity child node of filesystem-type images, plus
the five optional boolean error-handling property names aligned with the
flat-image-tree specification.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Reviewed-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Tom Rini <trini@konsulko.com>
---
v5: no changes
v4: no changes
v3: no changes
v2: no changes
---
 include/image.h | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/include/image.h b/include/image.h
index 34efac6056d..482446a8115 100644
--- a/include/image.h
+++ b/include/image.h
@@ -1079,6 +1079,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"
-- 
2.54.0

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH v5 2/8] boot: fit: support generating DM verity cmdline parameters
  2026-05-15 23:37 [PATCH v5 0/8] fit: dm-verity support Daniel Golle
  2026-05-15 23:37 ` [PATCH v5 1/8] image: fit: add dm-verity property name constants Daniel Golle
@ 2026-05-15 23:37 ` Daniel Golle
  2026-05-15 23:37 ` [PATCH v5 3/8] include: hexdump: make hex2bin() usable from host tools Daniel Golle
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Daniel Golle @ 2026-05-15 23:37 UTC (permalink / raw)
  To: Tom Rini, Simon Glass, Mario Six, Quentin Schulz, Kory Maincent,
	Mattijs Korpershoek, Peng Fan, Martin Schwan, Daniel Golle,
	Anshul Dalal, Sughosh Ganu, Ilias Apalodimas, Ludwig Nussel,
	Benjamin ROBIN, Marek Vasut, James Hilliard, Julien Stephan,
	David Lechner, Kunihiko Hayashi, Neil Armstrong, Svyatoslav Ryhel,
	Michal Simek, Pieter Van Trappen, Dinesh Maniyam, Sam Protsenko,
	Mayuresh Chitale, Shiji Yang, Jonas Karlman, Wolfgang Wallner,
	Aristo Chen, Rasmus Villemoes, Francois Berder, u-boot

Add fit_verity_build_cmdline(): when a FILESYSTEM loadable carries a
dm-verity subnode, construct the dm-mod.create= kernel cmdline parameter
from the verity metadata (block-size, data-blocks, algo, root-hash,
salt) and append it to bootargs.

Also add dm-mod.waitfor=/dev/fit0[,/dev/fitN] for each dm-verity device
so the kernel waits for the underlying FIT block device to appear before
setting up device-mapper targets. This is needed when the block driver
probes late, e.g. because it depends on NVMEM calibration data.

The dm-verity target references /dev/fitN where N is the loadable's
index in the configuration -- matching the order Linux's FIT block
driver assigns block devices.  hash-start-block is read directly from
the FIT dm-verity node; mkimage ensures its value equals num-data-blocks
by invoking veritysetup with --no-superblock.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
v5: no changes
v4: no changes
v3:
 * use unsigned int for data_block_size and hash_block_size
 * replace printf() with log_err() for the "broken dm-verity metadata"
v2:
 * use is_power_of_2() for pre-boot sanity check
 * let fit_verity_build_cmdline() return 0 on success
 * add comment explaining why bootm_start() calls fit_verity_free()
---
 boot/Kconfig       |  20 +++
 boot/bootm.c       |  13 ++
 boot/image-board.c |   5 +
 boot/image-fit.c   | 337 +++++++++++++++++++++++++++++++++++++++++++++
 include/image.h    |  80 ++++++++++-
 5 files changed, 454 insertions(+), 1 deletion(-)

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..d6a54a49fce 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 b0fcaf6e17f..906b75216b2 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>
@@ -243,6 +246,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 +307,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
 }
 
 /**
@@ -2642,3 +2683,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/include/image.h b/include/image.h
index 482446a8115..fe2361a667e 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
  *
-- 
2.54.0

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH v5 3/8] include: hexdump: make hex2bin() usable from host tools
  2026-05-15 23:37 [PATCH v5 0/8] fit: dm-verity support Daniel Golle
  2026-05-15 23:37 ` [PATCH v5 1/8] image: fit: add dm-verity property name constants Daniel Golle
  2026-05-15 23:37 ` [PATCH v5 2/8] boot: fit: support generating DM verity cmdline parameters Daniel Golle
@ 2026-05-15 23:37 ` Daniel Golle
  2026-05-15 23:38 ` [PATCH v5 4/8] tools: mkimage: add dm-verity Merkle-tree generation Daniel Golle
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Daniel Golle @ 2026-05-15 23:37 UTC (permalink / raw)
  To: Tom Rini, Simon Glass, Mario Six, Quentin Schulz, Kory Maincent,
	Mattijs Korpershoek, Peng Fan, Martin Schwan, Daniel Golle,
	Anshul Dalal, Sughosh Ganu, Ilias Apalodimas, Ludwig Nussel,
	Benjamin ROBIN, Marek Vasut, James Hilliard, Julien Stephan,
	David Lechner, Kunihiko Hayashi, Neil Armstrong, Svyatoslav Ryhel,
	Michal Simek, Pieter Van Trappen, Dinesh Maniyam, Sam Protsenko,
	Mayuresh Chitale, Shiji Yang, Jonas Karlman, Wolfgang Wallner,
	Aristo Chen, Rasmus Villemoes, Francois Berder, u-boot

Make hexdump.h work in host-tool builds by using 'uint8_t' instead
of 'u8', and including either user-space libc <ctype.h> for host-tools
or <linux/ctype.h> when building U-Boot itself.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
v5: no changes
v4: no changes
v3: no changes
v2: new patch
---
 include/hexdump.h | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

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++);
-- 
2.54.0

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH v5 4/8] tools: mkimage: add dm-verity Merkle-tree generation
  2026-05-15 23:37 [PATCH v5 0/8] fit: dm-verity support Daniel Golle
                   ` (2 preceding siblings ...)
  2026-05-15 23:37 ` [PATCH v5 3/8] include: hexdump: make hex2bin() usable from host tools Daniel Golle
@ 2026-05-15 23:38 ` Daniel Golle
  2026-05-15 23:38 ` [PATCH v5 5/8] doc: fit: add dm-verity boot parameter documentation Daniel Golle
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Daniel Golle @ 2026-05-15 23:38 UTC (permalink / raw)
  To: Tom Rini, Simon Glass, Mario Six, Quentin Schulz, Kory Maincent,
	Mattijs Korpershoek, Peng Fan, Martin Schwan, Daniel Golle,
	Anshul Dalal, Sughosh Ganu, Ilias Apalodimas, Ludwig Nussel,
	Benjamin ROBIN, Marek Vasut, James Hilliard, Julien Stephan,
	David Lechner, Kunihiko Hayashi, Neil Armstrong, Svyatoslav Ryhel,
	Michal Simek, Pieter Van Trappen, Dinesh Maniyam, Sam Protsenko,
	Mayuresh Chitale, Shiji Yang, Jonas Karlman, Wolfgang Wallner,
	Aristo Chen, Rasmus Villemoes, Francois Berder, u-boot

When mkimage encounters a dm-verity subnode inside a component image
node it now automatically invokes veritysetup(8) with --no-superblock
to generate the Merkle hash tree, screen-scrapes the Root hash and Salt
from the tool output, and writes the computed properties back into the
FIT blob.

The user only needs to specify algorithm, data-block-size, and
hash-block-size in the ITS; mkimage fills in digest, salt,
num-data-blocks, and hash-start-block. Because --no-superblock is
used, hash-start-block equals num-data-blocks with no off-by-one.

The image data property is replaced with the expanded content (original
data followed directly by the hash tree) so that subsequent hash and
signature subnodes operate on the complete image.

fit_image_add_verification_data() is restructured into two passes:
dm-verity first (may grow data), then hashes and signatures.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
v5:
 * drop the unused image_noffset parameter from
   fit_image_process_verity()
 * replace popen() and the valid_algos[] allowlist with
   fork()/execvp(); shell-injection vector gone, allowlist drift gone
 * drop the verity-data-file FDT property; cache the expanded buffer
   in memory keyed by image name, expose fit_verity_get_expanded()
   so fit_extract_data() consumes the buffer directly; the temporary
   file is unlinked immediately after read-back, eliminating the
   tmpfile-leak surface
 * use unsigned int for data-block-size / hash-block-size on the
   host side too (consistency with v3 runtime change)
v4: no changes
v3: extend the block-count overflow check to also cover hash_start_block
    (matters when hash-block-size < data-block-size)
v2:
 * use existing hex2bin() instead of open-coding it
 * fix stale comment still mentioning the verity superblock
 * add power-of-two check for data-block-size and hash-block-size
 * don't ignore return value of fdt_delprop()
---
 include/image.h    |  18 ++
 tools/fit_image.c  |  91 +++++++++-
 tools/image-host.c | 414 ++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 514 insertions(+), 9 deletions(-)

diff --git a/include/image.h b/include/image.h
index fe2361a667e..15275579268 100644
--- a/include/image.h
+++ b/include/image.h
@@ -1427,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/tools/fit_image.c b/tools/fit_image.c
index 1dbc14c63e4..6af55fa0355 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);
@@ -470,6 +486,41 @@ static int fit_write_images(struct image_tool_params *params, char *fdt)
 	return 0;
 }
 
+/**
+ * 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
  *
@@ -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)
@@ -686,11 +739,34 @@ static int fit_extract_data(struct image_tool_params *params, const char *fname)
 		align_size += 4;
 	}
 
+	/*
+	 * 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..4bed4c4d580 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
-- 
2.54.0

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH v5 5/8] doc: fit: add dm-verity boot parameter documentation
  2026-05-15 23:37 [PATCH v5 0/8] fit: dm-verity support Daniel Golle
                   ` (3 preceding siblings ...)
  2026-05-15 23:38 ` [PATCH v5 4/8] tools: mkimage: add dm-verity Merkle-tree generation Daniel Golle
@ 2026-05-15 23:38 ` Daniel Golle
  2026-05-15 23:38 ` [PATCH v5 6/8] test: boot: add runtime unit test for fit_verity_build_cmdline() Daniel Golle
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Daniel Golle @ 2026-05-15 23:38 UTC (permalink / raw)
  To: Tom Rini, Simon Glass, Mario Six, Quentin Schulz, Kory Maincent,
	Mattijs Korpershoek, Peng Fan, Martin Schwan, Daniel Golle,
	Anshul Dalal, Sughosh Ganu, Ilias Apalodimas, Ludwig Nussel,
	Benjamin ROBIN, Marek Vasut, James Hilliard, Julien Stephan,
	David Lechner, Kunihiko Hayashi, Neil Armstrong, Svyatoslav Ryhel,
	Michal Simek, Pieter Van Trappen, Dinesh Maniyam, Sam Protsenko,
	Mayuresh Chitale, Shiji Yang, Jonas Karlman, Wolfgang Wallner,
	Aristo Chen, Rasmus Villemoes, Francois Berder, u-boot

Add documentation for CONFIG_FIT_VERITY which allows U-Boot to
construct dm-mod.create= and dm-mod.waitfor= kernel command-line
parameters from dm-verity metadata embedded in FIT filesystem
sub-images.

The new document covers the relationship between FIT loadable indices
and the /dev/fitN block devices that the Linux uImage.FIT block driver
creates, provides a complete .its example with a dm-verity-protected
SquashFS root filesystem, describes all required and optional dm-verity
subnode properties and explains how mkimage generates the verity
metadata automatically.

dm-verity is only supported for external-data FIT images (mkimage -E);
mkimage aborts with an error if the flag is omitted.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
v5: document that the fitblk driver requires each filesystem
    sub-image to be aligned to the underlying block-device block
    size, and that 'mkimage -B <align>' (typically -B 0x1000)
    achieves this; clarify that this is independent of the dm-verity
    data-block-size / hash-block-size properties; reflect the v4
    refactor that dropped the verity-data-file property by updating
    the step that previously talked about a temporary file
v4: no changes
v3: clarify that hash-start-block only equals num-data-blocks when
    data-block-size == hash-block-size; in the general case it is
    data_size / hash-block-size
v2: trim minimal .its example to user-provided properties only and
    remove stale references to a verity superblock
---
 doc/usage/fit/dm-verity.rst | 304 ++++++++++++++++++++++++++++++++++++
 doc/usage/fit/index.rst     |   1 +
 2 files changed, 305 insertions(+)
 create mode 100644 doc/usage/fit/dm-verity.rst

diff --git a/doc/usage/fit/dm-verity.rst b/doc/usage/fit/dm-verity.rst
new file mode 100644
index 00000000000..7aad7739435
--- /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
-- 
2.54.0

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH v5 6/8] test: boot: add runtime unit test for fit_verity_build_cmdline()
  2026-05-15 23:37 [PATCH v5 0/8] fit: dm-verity support Daniel Golle
                   ` (4 preceding siblings ...)
  2026-05-15 23:38 ` [PATCH v5 5/8] doc: fit: add dm-verity boot parameter documentation Daniel Golle
@ 2026-05-15 23:38 ` Daniel Golle
  2026-05-15 23:38 ` [PATCH v5 7/8] test: py: add mkimage dm-verity round-trip test Daniel Golle
  2026-05-15 23:38 ` [PATCH v5 8/8] configs: sandbox: enable CONFIG_FIT_VERITY Daniel Golle
  7 siblings, 0 replies; 9+ messages in thread
From: Daniel Golle @ 2026-05-15 23:38 UTC (permalink / raw)
  To: Tom Rini, Simon Glass, Mario Six, Quentin Schulz, Kory Maincent,
	Mattijs Korpershoek, Peng Fan, Martin Schwan, Daniel Golle,
	Anshul Dalal, Sughosh Ganu, Ilias Apalodimas, Ludwig Nussel,
	Benjamin ROBIN, Marek Vasut, James Hilliard, Julien Stephan,
	David Lechner, Kunihiko Hayashi, Neil Armstrong, Svyatoslav Ryhel,
	Michal Simek, Pieter Van Trappen, Dinesh Maniyam, Sam Protsenko,
	Mayuresh Chitale, Shiji Yang, Jonas Karlman, Wolfgang Wallner,
	Aristo Chen, Rasmus Villemoes, Francois Berder, u-boot

Add test/boot/fit_verity.c with four tests that construct FIT blobs
in memory and exercise fit_verity_build_cmdline().

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
v5: no changes
v4: no changes
v3: replace literal property name strings with the FIT_VERITY_*_PROP
v2: new patch
---
 test/boot/Makefile     |   1 +
 test/boot/fit_verity.c | 306 +++++++++++++++++++++++++++++++++++++++++
 test/cmd_ut.c          |   2 +
 3 files changed, 309 insertions(+)
 create mode 100644 test/boot/fit_verity.c

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..8011459a1cb
--- /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 <daniel@makrotopia.org>
+ */
+
+#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"),
-- 
2.54.0

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH v5 7/8] test: py: add mkimage dm-verity round-trip test
  2026-05-15 23:37 [PATCH v5 0/8] fit: dm-verity support Daniel Golle
                   ` (5 preceding siblings ...)
  2026-05-15 23:38 ` [PATCH v5 6/8] test: boot: add runtime unit test for fit_verity_build_cmdline() Daniel Golle
@ 2026-05-15 23:38 ` Daniel Golle
  2026-05-15 23:38 ` [PATCH v5 8/8] configs: sandbox: enable CONFIG_FIT_VERITY Daniel Golle
  7 siblings, 0 replies; 9+ messages in thread
From: Daniel Golle @ 2026-05-15 23:38 UTC (permalink / raw)
  To: Tom Rini, Simon Glass, Mario Six, Quentin Schulz, Kory Maincent,
	Mattijs Korpershoek, Peng Fan, Martin Schwan, Daniel Golle,
	Anshul Dalal, Sughosh Ganu, Ilias Apalodimas, Ludwig Nussel,
	Benjamin ROBIN, Marek Vasut, James Hilliard, Julien Stephan,
	David Lechner, Kunihiko Hayashi, Neil Armstrong, Svyatoslav Ryhel,
	Michal Simek, Pieter Van Trappen, Dinesh Maniyam, Sam Protsenko,
	Mayuresh Chitale, Shiji Yang, Jonas Karlman, Wolfgang Wallner,
	Aristo Chen, Rasmus Villemoes, Francois Berder, u-boot

Add test/py/tests/test_fit_verity.py covering:
 - mkimage writes correct dm-verity properties for matched and
   mismatched block sizes (4096/4096 and 4096/1024);
 - veritysetup verify re-checks the digest against the .itb's
   external data section;
 - mkimage rejects dm-verity images built without -E.

All tests are skipped if veritysetup is not installed on the host.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
v5: no changes
v4:
 * verify the computed digest with veritysetup verify against the
   external data section
 * parametrize test_mkimage_verity with matched and mismatched
   block sizes to exercise hash-start-block != num-data-blocks
 * use run_and_log_expect_exception() with the expected diagnostic
   for the no-external-data case
v3: drop unused 'struct' import and the home-rolled have_veritysetup()
    helper; use @pytest.mark.requiredtool('veritysetup') instead
v2: new patch
---
 test/py/tests/test_fit_verity.py | 175 +++++++++++++++++++++++++++++++
 1 file changed, 175 insertions(+)
 create mode 100644 test/py/tests/test_fit_verity.py

diff --git a/test/py/tests/test_fit_verity.py b/test/py/tests/test_fit_verity.py
new file mode 100644
index 00000000000..5952c189858
--- /dev/null
+++ b/test/py/tests/test_fit_verity.py
@@ -0,0 +1,175 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Copyright 2026 Daniel Golle <daniel@makrotopia.org>
+
+"""
+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}',
+    ])
+
+
+@pytest.mark.requiredtool('dtc')
+@pytest.mark.requiredtool('fdtget')
+@pytest.mark.requiredtool('veritysetup')
+@pytest.mark.parametrize('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)
+
+
+@pytest.mark.requiredtool('dtc')
+@pytest.mark.requiredtool('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')
-- 
2.54.0

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH v5 8/8] configs: sandbox: enable CONFIG_FIT_VERITY
  2026-05-15 23:37 [PATCH v5 0/8] fit: dm-verity support Daniel Golle
                   ` (6 preceding siblings ...)
  2026-05-15 23:38 ` [PATCH v5 7/8] test: py: add mkimage dm-verity round-trip test Daniel Golle
@ 2026-05-15 23:38 ` Daniel Golle
  7 siblings, 0 replies; 9+ messages in thread
From: Daniel Golle @ 2026-05-15 23:38 UTC (permalink / raw)
  To: Tom Rini, Simon Glass, Mario Six, Quentin Schulz, Kory Maincent,
	Mattijs Korpershoek, Peng Fan, Martin Schwan, Daniel Golle,
	Anshul Dalal, Sughosh Ganu, Ilias Apalodimas, Ludwig Nussel,
	Benjamin ROBIN, Marek Vasut, James Hilliard, Julien Stephan,
	David Lechner, Kunihiko Hayashi, Neil Armstrong, Svyatoslav Ryhel,
	Michal Simek, Pieter Van Trappen, Dinesh Maniyam, Sam Protsenko,
	Mayuresh Chitale, Shiji Yang, Jonas Karlman, Wolfgang Wallner,
	Aristo Chen, Rasmus Villemoes, Francois Berder, u-boot

Enable FIT_VERITY in the sandbox configs that build a full U-Boot
binary so CI may exercise the new dm-verity unit test
(test/boot/fit_verity.c) and the mkimage pytest
(test/py/tests/test_fit_verity.py) introduced earlier in this series.

The SPL/VPL/noinst variants only load U-Boot proper, never an OS, so
dm-verity is meaningless there and is not enabled.

Suggested-by: Tom Rini <trini@konsulko.com>
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
v5: no changes
v4: new patch
---
 configs/sandbox64_defconfig        | 1 +
 configs/sandbox_defconfig          | 1 +
 configs/sandbox_flattree_defconfig | 1 +
 3 files changed, 3 insertions(+)

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
-- 
2.54.0

^ permalink raw reply related	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-05-16  1:05 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-15 23:37 [PATCH v5 0/8] fit: dm-verity support Daniel Golle
2026-05-15 23:37 ` [PATCH v5 1/8] image: fit: add dm-verity property name constants Daniel Golle
2026-05-15 23:37 ` [PATCH v5 2/8] boot: fit: support generating DM verity cmdline parameters Daniel Golle
2026-05-15 23:37 ` [PATCH v5 3/8] include: hexdump: make hex2bin() usable from host tools Daniel Golle
2026-05-15 23:38 ` [PATCH v5 4/8] tools: mkimage: add dm-verity Merkle-tree generation Daniel Golle
2026-05-15 23:38 ` [PATCH v5 5/8] doc: fit: add dm-verity boot parameter documentation Daniel Golle
2026-05-15 23:38 ` [PATCH v5 6/8] test: boot: add runtime unit test for fit_verity_build_cmdline() Daniel Golle
2026-05-15 23:38 ` [PATCH v5 7/8] test: py: add mkimage dm-verity round-trip test Daniel Golle
2026-05-15 23:38 ` [PATCH v5 8/8] configs: sandbox: enable CONFIG_FIT_VERITY Daniel Golle

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.