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 <[email protected]>
Reviewed-by: Simon Glass <[email protected]>
---
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 2d2709aa5b1..548789892b2 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

Reply via email to