* [PATCH 1/4] image: fit: add dm-verity property name constants
2026-04-02 3:08 [PATCH 0/4] fit: dm-verity support Daniel Golle
@ 2026-04-02 3:08 ` Daniel Golle
2026-04-02 16:32 ` [1/4] " Simon Glass
2026-04-02 3:08 ` [PATCH 2/4] boot: fit: support generating DM verity cmdline parameters Daniel Golle
` (2 subsequent siblings)
3 siblings, 1 reply; 7+ messages in thread
From: Daniel Golle @ 2026-04-02 3:08 UTC (permalink / raw)
To: Tom Rini, Quentin Schulz, Kory Maincent, Simon Glass,
Mattijs Korpershoek, Peng Fan, Marek Vasut, Daniel Golle,
Martin Schwan, Anshul Dalal, Ilias Apalodimas, Sughosh Ganu,
牛 志宏, Benjamin ROBIN, Aristo Chen,
James Hilliard, Frank Wunderlich, Mayuresh Chitale,
Neil Armstrong, Wolfgang Wallner, Rasmus Villemoes,
Francois Berder, Shiji Yang, 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>
---
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.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [1/4] image: fit: add dm-verity property name constants
2026-04-02 3:08 ` [PATCH 1/4] image: fit: add dm-verity property name constants Daniel Golle
@ 2026-04-02 16:32 ` Simon Glass
0 siblings, 0 replies; 7+ messages in thread
From: Simon Glass @ 2026-04-02 16:32 UTC (permalink / raw)
To: daniel
Cc: Tom Rini, Quentin Schulz, Kory Maincent, Simon Glass,
Mattijs Korpershoek, Peng Fan, Marek Vasut, Martin Schwan,
Anshul Dalal, Ilias Apalodimas, Sughosh Ganu,
牛 志宏, Benjamin ROBIN, Aristo Chen,
James Hilliard, Frank Wunderlich, Mayuresh Chitale,
Neil Armstrong, Wolfgang Wallner, Rasmus Villemoes,
Francois Berder, Shiji Yang, u-boot
On 2026-04-02T03:08:27, Daniel Golle <daniel@makrotopia.org> wrote:
> image: fit: add dm-verity property name constants
> image: fit: add dm-verity property name constants
>
> 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>
>
> include/image.h | 17 +++++++++++++++++
> 1 file changed, 17 insertions(+)
Reviewed-by: Simon Glass <sjg@chromium.org>
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 2/4] boot: fit: support generating DM verity cmdline parameters
2026-04-02 3:08 [PATCH 0/4] fit: dm-verity support Daniel Golle
2026-04-02 3:08 ` [PATCH 1/4] image: fit: add dm-verity property name constants Daniel Golle
@ 2026-04-02 3:08 ` Daniel Golle
2026-04-02 16:37 ` [2/4] " Simon Glass
2026-04-02 3:09 ` [PATCH 3/4] tools: mkimage: add dm-verity Merkle-tree generation Daniel Golle
2026-04-02 3:09 ` [PATCH 4/4] doc: fit: add dm-verity boot parameter documentation Daniel Golle
3 siblings, 1 reply; 7+ messages in thread
From: Daniel Golle @ 2026-04-02 3:08 UTC (permalink / raw)
To: Tom Rini, Quentin Schulz, Kory Maincent, Simon Glass,
Mattijs Korpershoek, Peng Fan, Marek Vasut, Daniel Golle,
Martin Schwan, Anshul Dalal, Ilias Apalodimas, Sughosh Ganu,
牛 志宏, Benjamin ROBIN, Aristo Chen,
James Hilliard, Frank Wunderlich, Mayuresh Chitale,
Neil Armstrong, Wolfgang Wallner, Rasmus Villemoes,
Francois Berder, Shiji Yang, 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>
---
boot/Kconfig | 21 +++
boot/bootm.c | 7 +
boot/image-board.c | 5 +
boot/image-fit.c | 336 +++++++++++++++++++++++++++++++++++++++++++++
include/image.h | 80 ++++++++++-
5 files changed, 448 insertions(+), 1 deletion(-)
diff --git a/boot/Kconfig b/boot/Kconfig
index ab31b8f40ed..5895763c378 100644
--- a/boot/Kconfig
+++ b/boot/Kconfig
@@ -142,6 +142,27 @@ 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 4bdca22ea8c..00625e1fb48 100644
--- a/boot/bootm.c
+++ b/boot/bootm.c
@@ -242,6 +242,7 @@ static int boot_get_kernel(const char *addr_fit, struct bootm_headers *images,
static int bootm_start(void)
{
+ fit_verity_free(&images);
memset((void *)&images, 0, sizeof(images));
images.verify = env_get_yesno("verify");
@@ -1070,6 +1071,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 067ad236081..ae500747f46 100644
--- a/boot/image-fit.c
+++ b/boot/image-fit.c
@@ -22,7 +22,9 @@ extern void *aligned_alloc(size_t alignment, size_t size);
#else
#include <linux/compiler.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 +245,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 u8 *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 +306,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 +2682,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;
+ 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 || data_block_size < 512 ||
+ !hash_block_size || hash_block_size < 512 ||
+ !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 %d %d %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) {
+ printf("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 found;
+
+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..0bd0bd4fc5c 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: number of verity targets found (>=0), or -ve errno
+ */
+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.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [2/4] boot: fit: support generating DM verity cmdline parameters
2026-04-02 3:08 ` [PATCH 2/4] boot: fit: support generating DM verity cmdline parameters Daniel Golle
@ 2026-04-02 16:37 ` Simon Glass
0 siblings, 0 replies; 7+ messages in thread
From: Simon Glass @ 2026-04-02 16:37 UTC (permalink / raw)
To: daniel
Cc: Tom Rini, Quentin Schulz, Kory Maincent, Simon Glass,
Mattijs Korpershoek, Peng Fan, Marek Vasut, Martin Schwan,
Anshul Dalal, Ilias Apalodimas, Sughosh Ganu,
牛 志宏, Benjamin ROBIN, Aristo Chen,
James Hilliard, Frank Wunderlich, Mayuresh Chitale,
Neil Armstrong, Wolfgang Wallner, Rasmus Villemoes,
Francois Berder, Shiji Yang, u-boot
Hi Daniel,
On 2026-04-02T03:08:27, Daniel Golle <daniel@makrotopia.org> wrote:
> boot: fit: support generating DM verity cmdline parameters
> boot: fit: support generating DM verity cmdline parameters
>
> 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>
> diff --git a/boot/Kconfig b/boot/Kconfig
> @@ -142,6 +142,27 @@ config FIT_CIPHER
> +config FIT_VERITY
> + ...
> + and BOOTSTD bootmeths.
> +
> +
Drop extra blank line.
> diff --git a/boot/image-fit.c b/boot/image-fit.c
> @@ -2642,3 +2682,299 @@ out:
> + if (!data_block_size || data_block_size < 512 ||
> + !hash_block_size || hash_block_size < 512 ||
> + !num_data_blocks)
> + return -EINVAL;
dm-verity requires block sizes to be a power of two (and divisible by
512). The first check `!data_block_size` is redundant given the `<
512` check that follows. Please can you add a power-of-two validation
using is_power_of_2() from <linux/log2.h>? Otherwise the kernel will
likely reject the table at boot time with a confusing error.
> diff --git a/boot/image-fit.c b/boot/image-fit.c
> @@ -2642,3 +2682,299 @@ out:
> +int fit_verity_build_cmdline(const void *fit, int conf_noffset,
> + struct bootm_headers *images)
> +{
> + ...
> + 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 found;
When found > 0, this returns a positive value which could be confused
with an error by callers that only check `< 0`. The call site in
boot_get_loadable() does `if (fit_img_result < 0) return
fit_img_result;` so it works, but I suspect returning 0 for success
and keeping the count internal would be cleaner. What do you think?
> diff --git a/boot/bootm.c b/boot/bootm.c
> @@ -242,6 +242,7 @@ static int boot_get_kernel(...)
> static int bootm_start(void)
> {
> + fit_verity_free(&images);
> memset((void *)&images, 0, sizeof(images));
Just to check: fit_verity_free() is called here before memset(), which
means the pointers must already be valid (or NULL) from a prior boot
attempt. This works because fit_verity_free() checks for NULL
internally, and .bss is zeroed. How about a comment explaining the
ordering?
Regards,
Simon
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 3/4] tools: mkimage: add dm-verity Merkle-tree generation
2026-04-02 3:08 [PATCH 0/4] fit: dm-verity support Daniel Golle
2026-04-02 3:08 ` [PATCH 1/4] image: fit: add dm-verity property name constants Daniel Golle
2026-04-02 3:08 ` [PATCH 2/4] boot: fit: support generating DM verity cmdline parameters Daniel Golle
@ 2026-04-02 3:09 ` Daniel Golle
2026-04-02 3:09 ` [PATCH 4/4] doc: fit: add dm-verity boot parameter documentation Daniel Golle
3 siblings, 0 replies; 7+ messages in thread
From: Daniel Golle @ 2026-04-02 3:09 UTC (permalink / raw)
To: Tom Rini, Quentin Schulz, Kory Maincent, Simon Glass,
Mattijs Korpershoek, Peng Fan, Marek Vasut, Daniel Golle,
Martin Schwan, Anshul Dalal, Ilias Apalodimas, Sughosh Ganu,
牛 志宏, Benjamin ROBIN, Aristo Chen,
James Hilliard, Frank Wunderlich, Mayuresh Chitale,
Neil Armstrong, Wolfgang Wallner, Rasmus Villemoes,
Francois Berder, Shiji Yang, 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>
---
tools/fit_image.c | 111 +++++++++++++-
tools/image-host.c | 369 ++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 470 insertions(+), 10 deletions(-)
diff --git a/tools/fit_image.c b/tools/fit_image.c
index 1dbc14c63e4..7db74bcc848 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,62 @@ static int fit_write_images(struct image_tool_params *params, char *fdt)
return 0;
}
+/**
+ * fit_copy_image_data() - copy image data, using verity temp file if present
+ * @fdt: FIT blob
+ * @node: image node offset
+ * @buf: destination buffer
+ * @buf_ptr: write offset within @buf
+ * @data: embedded image data (used when no verity temp file exists)
+ * @lenp: in/out: on entry, length of @data; on exit, bytes written
+ *
+ * When fit_image_process_verity() has run, a temp-file path is stored in
+ * the dm-verity subnode. Read that file (original data + hash tree) into
+ * @buf instead of copying the embedded data property.
+ *
+ * Return: 0 on success, -EIO on error
+ */
+static int fit_copy_image_data(void *fdt, int node, void *buf,
+ int buf_ptr, const void *data, int *lenp)
+{
+ const char *vfile;
+ char vfile_buf[256];
+ struct stat vst;
+ int vfd;
+ int vn;
+
+ vn = fdt_subnode_offset(fdt, node, FIT_VERITY_NODENAME);
+ vfile = NULL;
+ if (vn >= 0)
+ vfile = fdt_getprop(fdt, vn, "verity-data-file", NULL);
+ if (!vfile) {
+ memcpy(buf + buf_ptr, data, *lenp);
+ return 0;
+ }
+
+ /* Copy path -- FDT shifts after delprop */
+ snprintf(vfile_buf, sizeof(vfile_buf), "%s", vfile);
+ fdt_delprop(fdt, vn, "verity-data-file");
+
+ if (stat(vfile_buf, &vst)) {
+ fprintf(stderr, "Can't stat verity data: %s\n",
+ strerror(errno));
+ return -EIO;
+ }
+ vfd = open(vfile_buf, O_RDONLY);
+ if (vfd < 0 || read(vfd, buf + buf_ptr, vst.st_size) != vst.st_size) {
+ fprintf(stderr, "Can't read verity data: %s\n",
+ strerror(errno));
+ if (vfd >= 0)
+ close(vfd);
+ return -EIO;
+ }
+ close(vfd);
+ *lenp = vst.st_size;
+ unlink(vfile_buf);
+ return 0;
+}
+
/**
* fit_write_configs() - Write out a list of configurations to the FIT
*
@@ -653,6 +725,11 @@ 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;
+ int vn;
+ const char *vf;
+ struct stat vst;
fd = mmap_fdt(params->cmdname, fname, 0, &fdt, &sbuf, false, false);
if (fd < 0)
@@ -686,11 +763,30 @@ 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 to account for the difference.
+ */
+ fdt_for_each_subnode(node, fdt, images) {
+ vn = fdt_subnode_offset(fdt, node, FIT_VERITY_NODENAME);
+ orig_len = 0;
+
+ if (vn < 0)
+ continue;
+ vf = fdt_getprop(fdt, vn, "verity-data-file", NULL);
+ if (!vf)
+ continue;
+ fdt_getprop(fdt, node, FIT_DATA_PROP, &orig_len);
+ if (!stat(vf, &vst) && vst.st_size > orig_len)
+ verity_extra += vst.st_size - 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 +817,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..ebfa2c8a9bb 100644
--- a/tools/image-host.c
+++ b/tools/image-host.c
@@ -14,6 +14,8 @@
#include <image.h>
#include <version.h>
+#include <sys/stat.h>
+
#if CONFIG_IS_ENABLED(FIT_SIGNATURE)
#include <openssl/pem.h>
#include <openssl/evp.h>
@@ -24,6 +26,35 @@
#include <openssl/err.h>
#endif
+/**
+ * fit_hex2bin() - convert an ASCII hex string to binary
+ *
+ * @dst: output buffer (at least @count bytes)
+ * @src: NUL-terminated hex string (at least 2*@count characters)
+ * @count: number of bytes to produce
+ * Return: 0 on success, -1 on invalid input
+ */
+static int fit_hex2bin(uint8_t *dst, const char *src, size_t count)
+{
+ while (count--) {
+ int hi, lo;
+ char c;
+
+ c = *src++;
+ hi = (c >= '0' && c <= '9') ? c - '0' :
+ (c >= 'a' && c <= 'f') ? c - 'a' + 10 :
+ (c >= 'A' && c <= 'F') ? c - 'A' + 10 : -1;
+ c = *src++;
+ lo = (c >= '0' && c <= '9') ? c - '0' :
+ (c >= 'a' && c <= 'f') ? c - 'a' + 10 :
+ (c >= 'A' && c <= 'F') ? c - 'A' + 10 : -1;
+ if (hi < 0 || lo < 0)
+ return -1;
+ *dst++ = (hi << 4) | lo;
+ }
+ return 0;
+}
+
/**
* fit_set_hash_value - set hash value in requested has node
* @fit: pointer to the FIT format image header
@@ -626,6 +656,301 @@ int fit_image_cipher_data(const char *keydir, void *keydest,
image_noffset, cipher_node_offset, data, size, cmdname);
}
+/**
+ * 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 + verity superblock + hash tree) is returned
+ * through @expanded_data / @expanded_size so that hash and signature
+ * subnodes can be computed over the complete image. The FIT ``data``
+ * property is intentionally NOT replaced -- the expanded content is kept in
+ * a temporary file whose path is stored in the ``verity-data-file``
+ * property; ``fit_extract_data()`` picks it up later.
+ *
+ * @fit: FIT blob (read-write)
+ * @image_name: image unit name (for diagnostics)
+ * @image_noffset: image node offset (parent of dm-verity)
+ * @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 image_noffset, 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;
+ int data_block_size, hash_block_size;
+ size_t num_data_blocks, hash_offset;
+ uint32_t hash_start_block;
+ char tmpfile[] = "/tmp/mkimage-verity-XXXXXX";
+ char cmd[512];
+ 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);
+
+ /*
+ * Validate algo against a known-good list before interpolating it
+ * into the shell command passed to popen(). An unchecked value
+ * could allow command injection when mkimage processes a crafted
+ * .its file (e.g. algo = "sha256; rm -rf /").
+ *
+ * List derived from the veritysetup(8) man page (cryptsetup 2.x).
+ */
+ {
+ static const char * const valid_algos[] = {
+ "sha1", "sha224", "sha256", "sha384", "sha512",
+ "ripemd160", "whirlpool",
+ "sha3-224", "sha3-256", "sha3-384", "sha3-512",
+ "stribog256", "stribog512",
+ "sm3",
+ /* blake2b/blake2s with explicit digest sizes */
+ "blake2b-160", "blake2b-256", "blake2b-384",
+ "blake2b-512",
+ "blake2s-128", "blake2s-160", "blake2s-224",
+ "blake2s-256",
+ NULL
+ };
+ int i;
+
+ for (i = 0; valid_algos[i]; i++)
+ if (!strcmp(algo, valid_algos[i]))
+ break;
+ if (!valid_algos[i]) {
+ fprintf(stderr,
+ "Unsupported dm-verity hash algorithm '%s' in '%s'\n",
+ algo, image_name);
+ return -EINVAL;
+ }
+ }
+
+ 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 || hash_block_size < 512) {
+ fprintf(stderr,
+ "Invalid block sizes 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;
+ }
+
+ 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;
+
+ snprintf(cmd, sizeof(cmd),
+ "veritysetup format \"%s\" \"%s\" --no-superblock --hash=%s --data-block-size=%d --hash-block-size=%d --hash-offset=%zu 2>&1",
+ tmpfile, tmpfile, algo, data_block_size, hash_block_size,
+ hash_offset);
+
+ fp = popen(cmd, "r");
+ if (!fp) {
+ fprintf(stderr, "Can't run veritysetup: %s\n",
+ strerror(errno));
+ ret = -EIO;
+ goto err_unlink;
+ }
+
+ /* parse key: value lines from veritysetup stdout */
+ 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);
+ }
+
+ ret = pclose(fp);
+ if (ret) {
+ fprintf(stderr, "veritysetup failed (exit %d) for '%s'\n",
+ WEXITSTATUS(ret), 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 (fit_hex2bin(digest_bin, root_hash_hex, digest_len) ||
+ fit_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;
+
+ /* 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 temp-file path so that fit_extract_data() can use
+ * the expanded content for the external-data section.
+ */
+ ret = fdt_setprop_string(fit, verity_noffset,
+ "verity-data-file", tmpfile);
+ if (ret) {
+ ret = (ret == -FDT_ERR_NOSPACE) ? -ENOSPC : -EIO;
+ 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 +978,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 +995,12 @@ 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;
+ 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 +1010,39 @@ 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,
+ image_noffset,
+ 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
@@ -704,10 +1061,13 @@ int fit_image_add_verification_data(const char *keydir, const char *keyfile,
comment, require_keys, engine_id, cmdname,
algo_name);
}
- if (ret < 0)
+ if (ret < 0) {
+ free(verity_data);
return ret;
+ }
}
+ free(verity_data);
return 0;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH 4/4] doc: fit: add dm-verity boot parameter documentation
2026-04-02 3:08 [PATCH 0/4] fit: dm-verity support Daniel Golle
` (2 preceding siblings ...)
2026-04-02 3:09 ` [PATCH 3/4] tools: mkimage: add dm-verity Merkle-tree generation Daniel Golle
@ 2026-04-02 3:09 ` Daniel Golle
3 siblings, 0 replies; 7+ messages in thread
From: Daniel Golle @ 2026-04-02 3:09 UTC (permalink / raw)
To: Tom Rini, Quentin Schulz, Kory Maincent, Simon Glass,
Mattijs Korpershoek, Peng Fan, Marek Vasut, Daniel Golle,
Martin Schwan, Anshul Dalal, Ilias Apalodimas, Sughosh Ganu,
牛 志宏, Benjamin ROBIN, Aristo Chen,
James Hilliard, Frank Wunderlich, Mayuresh Chitale,
Neil Armstrong, Wolfgang Wallner, Rasmus Villemoes,
Francois Berder, Shiji Yang, 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>
---
doc/usage/fit/dm-verity.rst | 279 ++++++++++++++++++++++++++++++++++++
doc/usage/fit/index.rst | 1 +
2 files changed, 280 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..99f9ea66286
--- /dev/null
+++ b/doc/usage/fit/dm-verity.rst
@@ -0,0 +1,279 @@
+.. 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>;
+ };
+
+ fdt {
+ description = "Device tree blob";
+ data = /incbin/("./board.dtb");
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "none";
+ };
+
+ rootfs {
+ description = "SquashFS root filesystem";
+ data = /incbin/("./rootfs.squashfs");
+ type = "filesystem";
+ arch = "arm64";
+ compression = "none";
+
+ dm-verity {
+ data-block-size = <4096>;
+ hash-block-size = <4096>;
+ num-data-blocks = <3762>;
+ hash-start-block = <3762>;
+ algo = "sha256";
+ digest = [8e 67 91 63 7f 93 cb b8 1f c4 52 99 e2 03 cb e8
+ 5c a2 e4 7a 38 f5 05 1b dd ee ce 92 d7 b1 c9 f9];
+ salt = [aa 7b 11 f8 db 8f e2 e5 bf d4 ec a1 d1 8a 22 b5
+ de 7e a3 9d 2e 1b 93 bb 72 72 ce 0c 6c a3 cc 8e];
+ };
+ };
+ };
+
+ 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.
+
+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.
+
+These values are the same ones produced by ``veritysetup format`` and can
+typically be obtained from its output or from the verity superblock.
+The ``digest`` and ``salt`` byte arrays correspond to the hex-encoded
+``Root hash`` and ``Salt`` printed by ``veritysetup format``.
+
+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 back the expanded file (original data + verity superblock +
+ Merkle hash tree) and replaces the image's ``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)
+ mkimage -E -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. Consequently ``hash-start-block``
+ equals ``num-data-blocks``.
+
+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.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread