summaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
authorTom Rini <[email protected]>2026-05-27 13:44:20 -0600
committerTom Rini <[email protected]>2026-05-27 13:44:20 -0600
commit746a986fe247fc0b3d52ad7ae7027e0a6d57d12c (patch)
tree9fd87b6016fb21d043deab6fffabb8ddad9fd36c /boot
parent8d5f30b52f7c800c2177188fc4d331fb7af2c46a (diff)
parent89d3c1fe1b0fb5db15fce96a7e6db7885ebf240e (diff)
Merge patch series "fit: dm-verity support"
Daniel Golle <[email protected]> says: This series adds dm-verity support to U-Boot's FIT image infrastructure. It is the first logical subset of the larger OpenWrt boot method series posted as an RFC in February 2026 [1], extracted here for independent review and merging. OpenWrt's firmware model embeds a read-only squashfs or erofs root filesystem directly inside a uImage.FIT container as a FILESYSTEM-type loadable FIT image. At boot the kernel maps this sub-image directly from the underlying block device via the fitblk driver (/dev/fit0, /dev/fit1, ...), the goal is that the bootloader never even copies it to RAM. dm-verity enables the kernel to verify the integrity of those mapped filesystems at read time, with a Merkle hash tree stored contiguously in the same sub-image just after the data. Two kernel command-line parameters are required: dm-mod.create= -- the device-mapper target table for the verity device dm-mod.waitfor= -- a comma-separated list of block devices to wait for before dm-init sets up the targets (needed when fitblk probes late, e.g. because it depends on NVMEM calibration data) The FIT dm-verity node schema was upstreamed into the flat-image-tree specification [2], which this implementation tries to follow exactly. The runtime feature is guarded behind CONFIG_FIT_VERITY. If not enabled the resulting binary size remains unchanged. If enabled the binary size increases by about 3kB. [1] previous submissions: RFC: https://www.mail-archive.com/[email protected]/msg565945.html v1: https://www.mail-archive.com/[email protected]/msg569472.html v2: https://www.mail-archive.com/[email protected]/msg570599.html v3: https://www.mail-archive.com/[email protected]/msg573223.html v4: https://www.mail-archive.com/[email protected]/msg574000.html [2] flat-image-tree dm-verity node spec: https://github.com/open-source-firmware/flat-image-tree/commit/795fd5fd7f0121d0cb03efb1900aafc61c704771 Link: https://lore.kernel.org/r/[email protected]
Diffstat (limited to 'boot')
-rw-r--r--boot/Kconfig20
-rw-r--r--boot/bootm.c13
-rw-r--r--boot/image-board.c5
-rw-r--r--boot/image-fit.c337
4 files changed, 375 insertions, 0 deletions
diff --git a/boot/Kconfig b/boot/Kconfig
index ae6f09a6ede..e1114aea843 100644
--- a/boot/Kconfig
+++ b/boot/Kconfig
@@ -142,6 +142,26 @@ config FIT_CIPHER
Enable the feature of data ciphering/unciphering in the tool mkimage
and in the u-boot support of the FIT image.
+config FIT_VERITY
+ bool "dm-verity boot parameter generation from FIT metadata"
+ depends on FIT && OF_LIBFDT
+ help
+ When a FIT configuration contains loadable sub-images of type
+ IH_TYPE_FILESYSTEM with a dm-verity subnode, this option enables
+ building the dm-mod.create= and dm-mod.waitfor= kernel
+ command-line parameters from the verity metadata
+ (data-block-size, hash-block-size, num-data-blocks,
+ hash-start-block, algorithm, digest, salt) stored in the FIT.
+
+ The generated parameters reference /dev/fitN block devices that
+ Linux's uImage.FIT block driver assigns to loadable sub-images.
+
+ During FIT parsing (BOOTM_STATE_FINDOTHER), verity cmdline
+ fragments are stored in struct bootm_headers and automatically
+ appended to the bootargs environment variable during
+ BOOTM_STATE_OS_PREP. This works from both the bootm command
+ and BOOTSTD bootmeths.
+
config FIT_VERBOSE
bool "Show verbose messages when FIT images fail"
help
diff --git a/boot/bootm.c b/boot/bootm.c
index 4836d6b2d41..ec74873b503 100644
--- a/boot/bootm.c
+++ b/boot/bootm.c
@@ -243,6 +243,13 @@ static int boot_get_kernel(const char *addr_fit, struct bootm_headers *images,
static int bootm_start(void)
{
+ /*
+ * Free dm-verity allocations from a prior boot attempt before
+ * zeroing the structure. The pointers are guaranteed to be valid
+ * or NULL: .bss is zero-initialised, and memset() below zeroes
+ * them again after every boot.
+ */
+ fit_verity_free(&images);
memset((void *)&images, 0, sizeof(images));
images.verify = env_get_yesno("verify");
@@ -1071,6 +1078,12 @@ int bootm_run_states(struct bootm_info *bmi, int states)
/* For Linux OS do all substitutions at console processing */
if (images->os.os == IH_OS_LINUX)
flags = BOOTM_CL_ALL;
+ ret = fit_verity_apply_bootargs(images);
+ if (ret) {
+ printf("dm-verity bootargs failed (err=%d)\n", ret);
+ ret = CMD_RET_FAILURE;
+ goto err;
+ }
ret = bootm_process_cmdline_env(flags);
if (ret) {
printf("Cmdline setup failed (err=%d)\n", ret);
diff --git a/boot/image-board.c b/boot/image-board.c
index 005d60caf5c..265f29d44ff 100644
--- a/boot/image-board.c
+++ b/boot/image-board.c
@@ -810,6 +810,11 @@ int boot_get_loadable(struct bootm_headers *images)
fit_loadable_process(img_type, img_data, img_len);
}
+
+ fit_img_result = fit_verity_build_cmdline(buf, conf_noffset,
+ images);
+ if (fit_img_result < 0)
+ return fit_img_result;
break;
default:
printf("The given image format is not supported (corrupt?)\n");
diff --git a/boot/image-fit.c b/boot/image-fit.c
index 884a5105df5..1cf8e7be957 100644
--- a/boot/image-fit.c
+++ b/boot/image-fit.c
@@ -21,8 +21,11 @@
extern void *aligned_alloc(size_t alignment, size_t size);
#else
#include <linux/compiler.h>
+#include <linux/log2.h>
#include <linux/sizes.h>
+#include <env.h>
#include <errno.h>
+#include <hexdump.h>
#include <log.h>
#include <mapmem.h>
#include <asm/io.h>
@@ -235,6 +238,39 @@ static void fit_image_print_data(const void *fit, int noffset, const char *p,
}
}
+static __maybe_unused void fit_image_print_dm_verity(const void *fit,
+ int noffset,
+ const char *p)
+{
+#if defined(USE_HOSTCC) || CONFIG_IS_ENABLED(FIT_VERITY)
+ const char *algo;
+ const uint8_t *bin;
+ int len, i;
+
+ algo = fdt_getprop(fit, noffset, FIT_VERITY_ALGO_PROP, NULL);
+ if (algo)
+ printf("%s Verity algo: %s\n", p, algo);
+
+ bin = fdt_getprop(fit, noffset, FIT_VERITY_DIGEST_PROP,
+ &len);
+ if (bin && len > 0) {
+ printf("%s Verity hash: ", p);
+ for (i = 0; i < len; i++)
+ printf("%02x", bin[i]);
+ printf("\n");
+ }
+
+ bin = fdt_getprop(fit, noffset, FIT_VERITY_SALT_PROP,
+ &len);
+ if (bin && len > 0) {
+ printf("%s Verity salt: ", p);
+ for (i = 0; i < len; i++)
+ printf("%02x", bin[i]);
+ printf("\n");
+ }
+#endif
+}
+
/**
* fit_image_print_verification_data() - prints out the hash/signature details
* @fit: pointer to the FIT format image header
@@ -263,6 +299,11 @@ static void fit_image_print_verification_data(const void *fit, int noffset,
strlen(FIT_SIG_NODENAME))) {
fit_image_print_data(fit, noffset, p, "Sign");
}
+#if defined(USE_HOSTCC) || CONFIG_IS_ENABLED(FIT_VERITY)
+ else if (!strcmp(name, FIT_VERITY_NODENAME)) {
+ fit_image_print_dm_verity(fit, noffset, p);
+ }
+#endif
}
/**
@@ -2589,3 +2630,299 @@ out:
return fdt_noffset;
}
#endif
+
+#if !defined(USE_HOSTCC) && CONFIG_IS_ENABLED(FIT_VERITY)
+
+static const char *const verity_opt_props[] = {
+ FIT_VERITY_OPT_RESTART,
+ FIT_VERITY_OPT_PANIC,
+ FIT_VERITY_OPT_RERR,
+ FIT_VERITY_OPT_PERR,
+ FIT_VERITY_OPT_ONCE,
+};
+
+/**
+ * fit_verity_build_target() - build one dm-verity target specification
+ * @fit: pointer to the FIT blob
+ * @img_noffset: image node offset containing the dm-verity subnode
+ * @loadable_idx: index of this loadable (for /dev/fitN)
+ * @uname: unit name of the image
+ * @separator: true if a ";" prefix is needed (not the first target)
+ * @buf: output buffer, or NULL to measure only
+ * @bufsize: size of @buf (ignored when @buf is NULL)
+ *
+ * Parses all dm-verity properties from the image's ``dm-verity`` child
+ * node and writes (or measures) a dm target specification string of the
+ * form used by the ``dm-mod.create`` kernel parameter.
+ *
+ * Return: number of characters that would be written (excluding '\0'),
+ * or -ve errno on error (e.g. missing mandatory property)
+ */
+static int fit_verity_build_target(const void *fit, int img_noffset,
+ int loadable_idx, const char *uname,
+ bool separator, char *buf, int bufsize)
+{
+ const char *algorithm;
+ const u8 *digest_raw, *salt_raw;
+ const fdt32_t *val;
+ char *digest_hex = NULL, *salt_hex = NULL, *opt_buf = NULL;
+ int verity_node;
+ unsigned int data_block_size, hash_block_size;
+ int num_data_blocks, hash_start_block;
+ u64 data_sectors;
+ int digest_len, salt_len;
+ int opt_count, opt_off, opt_buf_size;
+ int len;
+ int i;
+
+ verity_node = fdt_subnode_offset(fit, img_noffset, FIT_VERITY_NODENAME);
+ if (verity_node < 0)
+ return -ENOENT;
+
+ /* Mandatory u32 properties */
+ val = fdt_getprop(fit, verity_node, FIT_VERITY_DBS_PROP, NULL);
+ if (!val)
+ return -EINVAL;
+ data_block_size = fdt32_to_cpu(*val);
+
+ val = fdt_getprop(fit, verity_node, FIT_VERITY_HBS_PROP, NULL);
+ if (!val)
+ return -EINVAL;
+ hash_block_size = fdt32_to_cpu(*val);
+
+ val = fdt_getprop(fit, verity_node, FIT_VERITY_NBLK_PROP, NULL);
+ if (!val)
+ return -EINVAL;
+ num_data_blocks = fdt32_to_cpu(*val);
+
+ val = fdt_getprop(fit, verity_node, FIT_VERITY_HBLK_PROP, NULL);
+ if (!val)
+ return -EINVAL;
+ hash_start_block = fdt32_to_cpu(*val);
+
+ if (data_block_size < 512U || !is_power_of_2(data_block_size) ||
+ hash_block_size < 512U || !is_power_of_2(hash_block_size) ||
+ !num_data_blocks)
+ return -EINVAL;
+
+ /* Mandatory string */
+ algorithm = fdt_getprop(fit, verity_node, FIT_VERITY_ALGO_PROP, NULL);
+ if (!algorithm)
+ return -EINVAL;
+
+ /* Mandatory byte arrays */
+ digest_raw = fdt_getprop(fit, verity_node, FIT_VERITY_DIGEST_PROP,
+ &digest_len);
+ if (!digest_raw || digest_len <= 0)
+ return -EINVAL;
+
+ salt_raw = fdt_getprop(fit, verity_node, FIT_VERITY_SALT_PROP,
+ &salt_len);
+ if (!salt_raw || salt_len <= 0)
+ return -EINVAL;
+
+ /* Hex-encode digest and salt into dynamically sized buffers */
+ digest_hex = malloc(digest_len * 2 + 1);
+ salt_hex = malloc(salt_len * 2 + 1);
+ if (!digest_hex || !salt_hex) {
+ len = -ENOMEM;
+ goto out;
+ }
+ *bin2hex(digest_hex, digest_raw, digest_len) = '\0';
+ *bin2hex(salt_hex, salt_raw, salt_len) = '\0';
+
+ data_sectors = (u64)num_data_blocks * ((u64)data_block_size / 512);
+
+ /* Compute space needed for optional boolean properties */
+ opt_buf_size = 1; /* NUL terminator */
+ for (i = 0; i < ARRAY_SIZE(verity_opt_props); i++)
+ opt_buf_size += strlen(verity_opt_props[i]) + 1;
+ opt_buf = malloc(opt_buf_size);
+ if (!opt_buf) {
+ len = -ENOMEM;
+ goto out;
+ }
+
+ /* Collect optional boolean properties */
+ opt_count = 0;
+ opt_off = 0;
+ opt_buf[0] = '\0';
+ for (i = 0; i < ARRAY_SIZE(verity_opt_props); i++) {
+ if (fdt_getprop(fit, verity_node,
+ verity_opt_props[i], NULL)) {
+ const char *s = verity_opt_props[i];
+ int slen = strlen(s);
+
+ if (opt_off)
+ opt_buf[opt_off++] = ' ';
+ /* Copy with hyphen-to-underscore conversion */
+ while (slen-- > 0) {
+ opt_buf[opt_off++] =
+ (*s == '-') ? '_' : *s;
+ s++;
+ }
+ opt_buf[opt_off] = '\0';
+ opt_count++;
+ }
+ }
+
+ /* Emit (or measure) the target spec */
+ len = snprintf(buf, buf ? bufsize : 0,
+ "%s%s,,, ro,0 %llu verity 1 /dev/fit%d /dev/fit%d %u %u %d %d %s %s %s",
+ separator ? ";" : "", uname,
+ (unsigned long long)data_sectors, loadable_idx, loadable_idx,
+ data_block_size, hash_block_size,
+ num_data_blocks, hash_start_block,
+ algorithm, digest_hex, salt_hex);
+ if (opt_count) {
+ int extra = snprintf(buf ? buf + len : NULL,
+ buf ? bufsize - len : 0,
+ " %d %s", opt_count, opt_buf);
+ len += extra;
+ }
+
+out:
+ free(digest_hex);
+ free(salt_hex);
+ free(opt_buf);
+ return len;
+}
+
+int fit_verity_build_cmdline(const void *fit, int conf_noffset,
+ struct bootm_headers *images)
+{
+ int images_noffset;
+ int dm_create_len = 0, dm_waitfor_len = 0;
+ char *dm_create = NULL, *dm_waitfor = NULL;
+ const char *uname;
+ int loadable_idx;
+ int found = 0;
+ int ret = 0;
+
+ images_noffset = fdt_path_offset(fit, FIT_IMAGES_PATH);
+ if (images_noffset < 0)
+ return 0;
+
+ for (loadable_idx = 0;
+ (uname = fdt_stringlist_get(fit, conf_noffset,
+ FIT_LOADABLE_PROP,
+ loadable_idx, NULL));
+ loadable_idx++) {
+ int img_noffset, need;
+ u8 img_type;
+ char *tmp;
+
+ img_noffset = fdt_subnode_offset(fit, images_noffset, uname);
+ if (img_noffset < 0)
+ continue;
+
+ if (fit_image_get_type(fit, img_noffset, &img_type) ||
+ img_type != IH_TYPE_FILESYSTEM)
+ continue;
+
+ /* Measure first, then allocate and write */
+ need = fit_verity_build_target(fit, img_noffset,
+ loadable_idx, uname,
+ found > 0, NULL, 0);
+ if (need == -ENOENT)
+ continue; /* no dm-verity subnode -- fine */
+ if (need < 0) {
+ log_err("FIT: broken dm-verity metadata in '%s'\n",
+ uname);
+ ret = need;
+ goto err;
+ }
+
+ tmp = realloc(dm_create, dm_create_len + need + 1);
+ if (!tmp) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ dm_create = tmp;
+ fit_verity_build_target(fit, img_noffset, loadable_idx,
+ uname, found > 0,
+ dm_create + dm_create_len,
+ need + 1);
+ dm_create_len += need;
+
+ /* Grow dm_waitfor buffer */
+ need = snprintf(NULL, 0, "%s/dev/fit%d",
+ dm_waitfor_len ? "," : "",
+ loadable_idx);
+ tmp = realloc(dm_waitfor, dm_waitfor_len + need + 1);
+ if (!tmp) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ dm_waitfor = tmp;
+ sprintf(dm_waitfor + dm_waitfor_len, "%s/dev/fit%d",
+ dm_waitfor_len ? "," : "",
+ loadable_idx);
+ dm_waitfor_len += need;
+
+ found++;
+ }
+
+ if (found) {
+ /* Transfer ownership to the bootm_headers */
+ images->dm_mod_create = dm_create;
+ images->dm_mod_waitfor = dm_waitfor;
+ } else {
+ free(dm_create);
+ free(dm_waitfor);
+ }
+
+ return 0;
+
+err:
+ free(dm_create);
+ free(dm_waitfor);
+ return ret;
+}
+
+/**
+ * fmt used by both the measurement and the actual write of bootargs.
+ * Shared to guarantee they stay in sync.
+ */
+#define VERITY_BOOTARGS_FMT "%s%sdm-mod.create=\"%s\" dm-mod.waitfor=\"%s\""
+
+int fit_verity_apply_bootargs(const struct bootm_headers *images)
+{
+ const char *existing;
+ char *newargs;
+ int len;
+
+ if (!images->dm_mod_create)
+ return 0;
+
+ existing = env_get("bootargs");
+ if (!existing)
+ existing = "";
+
+ /* Measure */
+ len = snprintf(NULL, 0, VERITY_BOOTARGS_FMT,
+ existing, existing[0] ? " " : "",
+ images->dm_mod_create, images->dm_mod_waitfor);
+
+ newargs = malloc(len + 1);
+ if (!newargs)
+ return -ENOMEM;
+
+ snprintf(newargs, len + 1, VERITY_BOOTARGS_FMT,
+ existing, existing[0] ? " " : "",
+ images->dm_mod_create, images->dm_mod_waitfor);
+
+ env_set("bootargs", newargs);
+ free(newargs);
+
+ return 0;
+}
+
+void fit_verity_free(struct bootm_headers *images)
+{
+ free(images->dm_mod_create);
+ free(images->dm_mod_waitfor);
+ images->dm_mod_create = NULL;
+ images->dm_mod_waitfor = NULL;
+}
+#endif /* FIT_VERITY */