{"id":2218920,"url":"http://patchwork.ozlabs.org/api/1.0/patches/2218920/?format=json","project":{"id":18,"url":"http://patchwork.ozlabs.org/api/1.0/projects/18/?format=json","name":"U-Boot","link_name":"uboot","list_id":"u-boot.lists.denx.de","list_email":"u-boot@lists.denx.de","web_url":null,"scm_url":null,"webscm_url":null},"msgid":"<e7f8a6fe31255e5b2b842de9297d0d98695046ed.1775099118.git.daniel@makrotopia.org>","date":"2026-04-02T03:09:05","name":"[3/4] tools: mkimage: add dm-verity Merkle-tree generation","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"f33e876de615c91f0c466475fc27524eb00e1e74","submitter":{"id":64091,"url":"http://patchwork.ozlabs.org/api/1.0/people/64091/?format=json","name":"Daniel Golle","email":"daniel@makrotopia.org"},"delegate":{"id":3651,"url":"http://patchwork.ozlabs.org/api/1.0/users/3651/?format=json","username":"trini","first_name":"Tom","last_name":"Rini","email":"trini@ti.com"},"mbox":"http://patchwork.ozlabs.org/project/uboot/patch/e7f8a6fe31255e5b2b842de9297d0d98695046ed.1775099118.git.daniel@makrotopia.org/mbox/","series":[{"id":498423,"url":"http://patchwork.ozlabs.org/api/1.0/series/498423/?format=json","date":"2026-04-02T03:08:27","name":"fit: dm-verity support","version":1,"mbox":"http://patchwork.ozlabs.org/series/498423/mbox/"}],"check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2218920/checks/","tags":{},"headers":{"Return-Path":"<u-boot-bounces@lists.denx.de>","X-Original-To":"incoming@patchwork.ozlabs.org","Delivered-To":"patchwork-incoming@legolas.ozlabs.org","Authentication-Results":["legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de\n (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; helo=phobos.denx.de;\n envelope-from=u-boot-bounces@lists.denx.de; receiver=patchwork.ozlabs.org)","phobos.denx.de;\n dmarc=none (p=none dis=none) header.from=makrotopia.org","phobos.denx.de;\n spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de","phobos.denx.de; dmarc=none (p=none dis=none)\n header.from=makrotopia.org","phobos.denx.de;\n spf=pass smtp.mailfrom=daniel@makrotopia.org"],"Received":["from phobos.denx.de (phobos.denx.de\n [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fmRfX0Jssz1yFv\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 02 Apr 2026 14:09:40 +1100 (AEDT)","from h2850616.stratoserver.net (localhost [IPv6:::1])\n\tby phobos.denx.de (Postfix) with ESMTP id 34521840B5;\n\tThu,  2 Apr 2026 05:09:30 +0200 (CEST)","by phobos.denx.de (Postfix, from userid 109)\n id 38087840B5; Thu,  2 Apr 2026 05:09:29 +0200 (CEST)","from pidgin.makrotopia.org (pidgin.makrotopia.org\n [IPv6:2a07:2ec0:3002::65])\n (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits))\n (No client certificate requested)\n by phobos.denx.de (Postfix) with ESMTPS id AE051840CB\n for <u-boot@lists.denx.de>; Thu,  2 Apr 2026 05:09:21 +0200 (CEST)","from local\n by pidgin.makrotopia.org with esmtpsa (TLS1.3:TLS_AES_256_GCM_SHA384:256)\n (Exim 4.99) (envelope-from <daniel@makrotopia.org>)\n id 1w88Qe-000000007hW-1r7k; Thu, 02 Apr 2026 03:09:08 +0000"],"X-Spam-Checker-Version":"SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de","X-Spam-Level":"","X-Spam-Status":"No, score=-1.9 required=5.0 tests=BAYES_00,\n RCVD_IN_DNSWL_BLOCKED,SPF_HELO_NONE,SPF_PASS autolearn=ham\n autolearn_force=no version=3.4.2","Date":"Thu, 2 Apr 2026 04:09:05 +0100","From":"Daniel Golle <daniel@makrotopia.org>","To":"Tom Rini <trini@konsulko.com>, Quentin Schulz <quentin.schulz@cherry.de>,\n Kory Maincent <kory.maincent@bootlin.com>, Simon Glass <sjg@chromium.org>,\n Mattijs Korpershoek <mkorpershoek@kernel.org>, Peng Fan <peng.fan@nxp.com>,\n Marek Vasut <marek.vasut+renesas@mailbox.org>,\n Daniel Golle <daniel@makrotopia.org>, Martin Schwan <m.schwan@phytec.de>,\n Anshul Dalal <anshuld@ti.com>,\n Ilias Apalodimas <ilias.apalodimas@linaro.org>,\n Sughosh Ganu <sughosh.ganu@arm.com>,\n =?utf-8?b?54mbIOW/l+Wujw==?= <Zone.Niuzh@hotmail.com>,\n Benjamin ROBIN <dev@benjarobin.fr>, Aristo Chen <jj251510319013@gmail.com>,\n James Hilliard <james.hilliard1@gmail.com>,\n Frank Wunderlich <frank-w@public-files.de>,\n Mayuresh Chitale <mchitale@ventanamicro.com>,\n Neil Armstrong <neil.armstrong@linaro.org>,\n Wolfgang Wallner <wolfgang.wallner@at.abb.com>,\n Rasmus Villemoes <ravi@prevas.dk>, Francois Berder <fberder@outlook.fr>,\n Shiji Yang <yangshiji66@outlook.com>, u-boot@lists.denx.de","Subject":"[PATCH 3/4] tools: mkimage: add dm-verity Merkle-tree generation","Message-ID":"\n <e7f8a6fe31255e5b2b842de9297d0d98695046ed.1775099118.git.daniel@makrotopia.org>","References":"<cover.1775099118.git.daniel@makrotopia.org>","MIME-Version":"1.0","Content-Type":"text/plain; charset=us-ascii","Content-Disposition":"inline","In-Reply-To":"<cover.1775099118.git.daniel@makrotopia.org>","X-BeenThere":"u-boot@lists.denx.de","X-Mailman-Version":"2.1.39","Precedence":"list","List-Id":"U-Boot discussion <u-boot.lists.denx.de>","List-Unsubscribe":"<https://lists.denx.de/options/u-boot>,\n <mailto:u-boot-request@lists.denx.de?subject=unsubscribe>","List-Archive":"<https://lists.denx.de/pipermail/u-boot/>","List-Post":"<mailto:u-boot@lists.denx.de>","List-Help":"<mailto:u-boot-request@lists.denx.de?subject=help>","List-Subscribe":"<https://lists.denx.de/listinfo/u-boot>,\n <mailto:u-boot-request@lists.denx.de?subject=subscribe>","Errors-To":"u-boot-bounces@lists.denx.de","Sender":"\"U-Boot\" <u-boot-bounces@lists.denx.de>","X-Virus-Scanned":"clamav-milter 0.103.8 at phobos.denx.de","X-Virus-Status":"Clean"},"content":"When mkimage encounters a dm-verity subnode inside a component image\nnode it now automatically invokes veritysetup(8) with --no-superblock\nto generate the Merkle hash tree, screen-scrapes the Root hash and Salt\nfrom the tool output, and writes the computed properties back into the\nFIT blob.\n\nThe user only needs to specify algorithm, data-block-size, and\nhash-block-size in the ITS; mkimage fills in digest, salt,\nnum-data-blocks, and hash-start-block.  Because --no-superblock is\nused, hash-start-block equals num-data-blocks with no off-by-one.\n\nThe image data property is replaced with the expanded content (original\ndata followed directly by the hash tree) so that subsequent hash and\nsignature subnodes operate on the complete image.\n\nfit_image_add_verification_data() is restructured into two passes:\ndm-verity first (may grow data), then hashes and signatures.\n\nSigned-off-by: Daniel Golle <daniel@makrotopia.org>\n---\n tools/fit_image.c  | 111 +++++++++++++-\n tools/image-host.c | 369 ++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 470 insertions(+), 10 deletions(-)","diff":"diff --git a/tools/fit_image.c b/tools/fit_image.c\nindex 1dbc14c63e4..7db74bcc848 100644\n--- a/tools/fit_image.c\n+++ b/tools/fit_image.c\n@@ -40,10 +40,10 @@ static int fit_estimate_hash_sig_size(struct image_tool_params *params, const ch\n \t\treturn -EIO;\n \n \t/*\n-\t * Walk the FIT image, looking for nodes named hash* and\n-\t * signature*. Since the interesting nodes are subnodes of an\n-\t * image or configuration node, we are only interested in\n-\t * those at depth exactly 3.\n+\t * Walk the FIT image, looking for nodes named hash*,\n+\t * signature*, and dm-verity.  Since the interesting nodes are\n+\t * subnodes of an image or configuration node, we are only\n+\t * interested in those at depth exactly 3.\n \t *\n \t * The estimate for a hash node is based on a sha512 digest\n \t * being 64 bytes, with another 64 bytes added to account for\n@@ -55,6 +55,10 @@ static int fit_estimate_hash_sig_size(struct image_tool_params *params, const ch\n \t * account for fdt overhead and the various other properties\n \t * (hashed-nodes etc.) that will also be filled in.\n \t *\n+\t * For a dm-verity node the small metadata properties (digest,\n+\t * salt, two u32s and a temp-file path) are written into the\n+\t * FDT by fit_image_process_verity().\n+\t *\n \t * One could try to be more precise in the estimates by\n \t * looking at the \"algo\" property and, in the case of\n \t * configuration signatures, the sign-images property. Also,\n@@ -76,6 +80,18 @@ static int fit_estimate_hash_sig_size(struct image_tool_params *params, const ch\n \n \t\tif (signing && !strncmp(name, FIT_SIG_NODENAME, strlen(FIT_SIG_NODENAME)))\n \t\t\testimate += 1024;\n+\n+\t\tif (!strcmp(name, FIT_VERITY_NODENAME)) {\n+\t\t\tif (!params->external_data) {\n+\t\t\t\tfprintf(stderr,\n+\t\t\t\t\t\"%s: dm-verity requires external data (-E)\\n\",\n+\t\t\t\t\tparams->cmdname);\n+\t\t\t\tmunmap(fdt, sbuf.st_size);\n+\t\t\t\tclose(fd);\n+\t\t\t\treturn -EINVAL;\n+\t\t\t}\n+\t\t\testimate += 256;\n+\t\t}\n \t}\n \n \tmunmap(fdt, sbuf.st_size);\n@@ -470,6 +486,62 @@ static int fit_write_images(struct image_tool_params *params, char *fdt)\n \treturn 0;\n }\n \n+/**\n+ * fit_copy_image_data() - copy image data, using verity temp file if present\n+ * @fdt:\t\tFIT blob\n+ * @node:\t\timage node offset\n+ * @buf:\t\tdestination buffer\n+ * @buf_ptr:\twrite offset within @buf\n+ * @data:\t\tembedded image data (used when no verity temp file exists)\n+ * @lenp:\t\tin/out: on entry, length of @data; on exit, bytes written\n+ *\n+ * When fit_image_process_verity() has run, a temp-file path is stored in\n+ * the dm-verity subnode.  Read that file (original data + hash tree) into\n+ * @buf instead of copying the embedded data property.\n+ *\n+ * Return: 0 on success, -EIO on error\n+ */\n+static int fit_copy_image_data(void *fdt, int node, void *buf,\n+\t\t\t       int buf_ptr, const void *data, int *lenp)\n+{\n+\tconst char *vfile;\n+\tchar vfile_buf[256];\n+\tstruct stat vst;\n+\tint vfd;\n+\tint vn;\n+\n+\tvn = fdt_subnode_offset(fdt, node, FIT_VERITY_NODENAME);\n+\tvfile = NULL;\n+\tif (vn >= 0)\n+\t\tvfile = fdt_getprop(fdt, vn, \"verity-data-file\", NULL);\n+\tif (!vfile) {\n+\t\tmemcpy(buf + buf_ptr, data, *lenp);\n+\t\treturn 0;\n+\t}\n+\n+\t/* Copy path -- FDT shifts after delprop */\n+\tsnprintf(vfile_buf, sizeof(vfile_buf), \"%s\", vfile);\n+\tfdt_delprop(fdt, vn, \"verity-data-file\");\n+\n+\tif (stat(vfile_buf, &vst)) {\n+\t\tfprintf(stderr, \"Can't stat verity data: %s\\n\",\n+\t\t\tstrerror(errno));\n+\t\treturn -EIO;\n+\t}\n+\tvfd = open(vfile_buf, O_RDONLY);\n+\tif (vfd < 0 || read(vfd, buf + buf_ptr, vst.st_size) != vst.st_size) {\n+\t\tfprintf(stderr, \"Can't read verity data: %s\\n\",\n+\t\t\tstrerror(errno));\n+\t\tif (vfd >= 0)\n+\t\t\tclose(vfd);\n+\t\treturn -EIO;\n+\t}\n+\tclose(vfd);\n+\t*lenp = vst.st_size;\n+\tunlink(vfile_buf);\n+\treturn 0;\n+}\n+\n /**\n  * fit_write_configs() - Write out a list of configurations to the FIT\n  *\n@@ -653,6 +725,11 @@ static int fit_extract_data(struct image_tool_params *params, const char *fname)\n \tint node;\n \tint align_size = 0;\n \tint len = 0;\n+\tint verity_extra = 0;\n+\tint orig_len;\n+\tint vn;\n+\tconst char *vf;\n+\tstruct stat vst;\n \n \tfd = mmap_fdt(params->cmdname, fname, 0, &fdt, &sbuf, false, false);\n \tif (fd < 0)\n@@ -686,11 +763,30 @@ static int fit_extract_data(struct image_tool_params *params, const char *fname)\n \t\talign_size += 4;\n \t}\n \n+\t/*\n+\t * When dm-verity is active the external data for an image is\n+\t * larger than the embedded data property (original + hash tree).\n+\t * Walk images once more to account for the difference.\n+\t */\n+\tfdt_for_each_subnode(node, fdt, images) {\n+\t\tvn = fdt_subnode_offset(fdt, node, FIT_VERITY_NODENAME);\n+\t\torig_len = 0;\n+\n+\t\tif (vn < 0)\n+\t\t\tcontinue;\n+\t\tvf = fdt_getprop(fdt, vn, \"verity-data-file\", NULL);\n+\t\tif (!vf)\n+\t\t\tcontinue;\n+\t\tfdt_getprop(fdt, node, FIT_DATA_PROP, &orig_len);\n+\t\tif (!stat(vf, &vst) && vst.st_size > orig_len)\n+\t\t\tverity_extra += vst.st_size - orig_len;\n+\t}\n+\n \t/*\n \t * Allocate space to hold the image data we will extract,\n \t * extral space allocate for image alignment to prevent overflow.\n \t */\n-\tbuf = calloc(1, fit_size + align_size);\n+\tbuf = calloc(1, fit_size + align_size + verity_extra);\n \tif (!buf) {\n \t\tret = -ENOMEM;\n \t\tgoto err_munmap;\n@@ -721,7 +817,10 @@ static int fit_extract_data(struct image_tool_params *params, const char *fname)\n \t\tdata = fdt_getprop(fdt, node, FIT_DATA_PROP, &len);\n \t\tif (!data)\n \t\t\tcontinue;\n-\t\tmemcpy(buf + buf_ptr, data, len);\n+\n+\t\tret = fit_copy_image_data(fdt, node, buf, buf_ptr, data, &len);\n+\t\tif (ret)\n+\t\t\tgoto err_munmap;\n \t\tdebug(\"Extracting data size %x\\n\", len);\n \n \t\tret = fdt_delprop(fdt, node, FIT_DATA_PROP);\ndiff --git a/tools/image-host.c b/tools/image-host.c\nindex 8b550af0dc1..ebfa2c8a9bb 100644\n--- a/tools/image-host.c\n+++ b/tools/image-host.c\n@@ -14,6 +14,8 @@\n #include <image.h>\n #include <version.h>\n \n+#include <sys/stat.h>\n+\n #if CONFIG_IS_ENABLED(FIT_SIGNATURE)\n #include <openssl/pem.h>\n #include <openssl/evp.h>\n@@ -24,6 +26,35 @@\n #include <openssl/err.h>\n #endif\n \n+/**\n+ * fit_hex2bin() - convert an ASCII hex string to binary\n+ *\n+ * @dst:   output buffer (at least @count bytes)\n+ * @src:   NUL-terminated hex string (at least 2*@count characters)\n+ * @count: number of bytes to produce\n+ * Return: 0 on success, -1 on invalid input\n+ */\n+static int fit_hex2bin(uint8_t *dst, const char *src, size_t count)\n+{\n+\twhile (count--) {\n+\t\tint hi, lo;\n+\t\tchar c;\n+\n+\t\tc = *src++;\n+\t\thi = (c >= '0' && c <= '9') ? c - '0' :\n+\t\t     (c >= 'a' && c <= 'f') ? c - 'a' + 10 :\n+\t\t     (c >= 'A' && c <= 'F') ? c - 'A' + 10 : -1;\n+\t\tc = *src++;\n+\t\tlo = (c >= '0' && c <= '9') ? c - '0' :\n+\t\t     (c >= 'a' && c <= 'f') ? c - 'a' + 10 :\n+\t\t     (c >= 'A' && c <= 'F') ? c - 'A' + 10 : -1;\n+\t\tif (hi < 0 || lo < 0)\n+\t\t\treturn -1;\n+\t\t*dst++ = (hi << 4) | lo;\n+\t}\n+\treturn 0;\n+}\n+\n /**\n  * fit_set_hash_value - set hash value in requested has node\n  * @fit: pointer to the FIT format image header\n@@ -626,6 +656,301 @@ int fit_image_cipher_data(const char *keydir, void *keydest,\n \t\timage_noffset, cipher_node_offset, data, size, cmdname);\n }\n \n+/**\n+ * fit_image_process_verity() - Run veritysetup and fill dm-verity properties\n+ *\n+ * Extracts the embedded image data to a temporary file, runs\n+ * ``veritysetup format`` to generate the Merkle hash tree (appended to the\n+ * same file), parses Root hash / Salt from its stdout, and writes the\n+ * computed properties (digest, salt, num-data-blocks, hash-start-block)\n+ * back into the FIT dm-verity subnode.\n+ *\n+ * The expanded data (original + verity superblock + hash tree) is returned\n+ * through @expanded_data / @expanded_size so that hash and signature\n+ * subnodes can be computed over the complete image.  The FIT ``data``\n+ * property is intentionally NOT replaced -- the expanded content is kept in\n+ * a temporary file whose path is stored in the ``verity-data-file``\n+ * property; ``fit_extract_data()`` picks it up later.\n+ *\n+ * @fit:\t\tFIT blob (read-write)\n+ * @image_name:\t\timage unit name (for diagnostics)\n+ * @image_noffset:\timage node offset (parent of dm-verity)\n+ * @verity_noffset:\tdm-verity subnode offset\n+ * @data:\t\tembedded image data\n+ * @data_size:\t\tsize of @data in bytes\n+ * @expanded_data:\toutput -- malloc'd buffer with expanded content\n+ * @expanded_size:\toutput -- size of @expanded_data\n+ * Return: 0 on success, -ve on error (-ENOSPC when the FIT blob is full)\n+ */\n+static int fit_image_process_verity(void *fit, const char *image_name,\n+\t\t\t\t    int image_noffset, int verity_noffset,\n+\t\t\t\t    const void *data, size_t data_size,\n+\t\t\t\t    void **expanded_data, size_t *expanded_size)\n+{\n+\tconst char *algo_prop;\n+\tchar algo[64];\n+\tconst fdt32_t *val;\n+\tint data_block_size, hash_block_size;\n+\tsize_t num_data_blocks, hash_offset;\n+\tuint32_t hash_start_block;\n+\tchar tmpfile[] = \"/tmp/mkimage-verity-XXXXXX\";\n+\tchar cmd[512];\n+\tFILE *fp;\n+\tchar line[256];\n+\tchar *colon, *value, *end;\n+\tchar root_hash_hex[256] = {0};\n+\tchar salt_hex[256] = {0};\n+\tuint8_t digest_bin[FIT_MAX_HASH_LEN];\n+\tuint8_t salt_bin[FIT_MAX_HASH_LEN];\n+\tint digest_len = 0, salt_len = 0;\n+\tvoid *expanded = NULL;\n+\tstruct stat st;\n+\tint fd, ret;\n+\n+\t*expanded_data = NULL;\n+\t*expanded_size = 0;\n+\n+\talgo_prop = fdt_getprop(fit, verity_noffset, FIT_VERITY_ALGO_PROP,\n+\t\t\t\tNULL);\n+\tif (!algo_prop) {\n+\t\tfprintf(stderr,\n+\t\t\t\"Missing '%s' in dm-verity node of '%s'\\n\",\n+\t\t\tFIT_VERITY_ALGO_PROP, image_name);\n+\t\treturn -EINVAL;\n+\t}\n+\t/* Local copy -- the FDT pointer goes stale after fdt_setprop(). */\n+\tsnprintf(algo, sizeof(algo), \"%s\", algo_prop);\n+\n+\t/*\n+\t * Validate algo against a known-good list before interpolating it\n+\t * into the shell command passed to popen().  An unchecked value\n+\t * could allow command injection when mkimage processes a crafted\n+\t * .its file (e.g. algo = \"sha256; rm -rf /\").\n+\t *\n+\t * List derived from the veritysetup(8) man page (cryptsetup 2.x).\n+\t */\n+\t{\n+\t\tstatic const char * const valid_algos[] = {\n+\t\t\t\"sha1\", \"sha224\", \"sha256\", \"sha384\", \"sha512\",\n+\t\t\t\"ripemd160\", \"whirlpool\",\n+\t\t\t\"sha3-224\", \"sha3-256\", \"sha3-384\", \"sha3-512\",\n+\t\t\t\"stribog256\", \"stribog512\",\n+\t\t\t\"sm3\",\n+\t\t\t/* blake2b/blake2s with explicit digest sizes */\n+\t\t\t\"blake2b-160\", \"blake2b-256\", \"blake2b-384\",\n+\t\t\t\"blake2b-512\",\n+\t\t\t\"blake2s-128\", \"blake2s-160\", \"blake2s-224\",\n+\t\t\t\"blake2s-256\",\n+\t\t\tNULL\n+\t\t};\n+\t\tint i;\n+\n+\t\tfor (i = 0; valid_algos[i]; i++)\n+\t\t\tif (!strcmp(algo, valid_algos[i]))\n+\t\t\t\tbreak;\n+\t\tif (!valid_algos[i]) {\n+\t\t\tfprintf(stderr,\n+\t\t\t\t\"Unsupported dm-verity hash algorithm '%s' in '%s'\\n\",\n+\t\t\t\talgo, image_name);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t}\n+\n+\tval = fdt_getprop(fit, verity_noffset, FIT_VERITY_DBS_PROP, NULL);\n+\tif (!val) {\n+\t\tfprintf(stderr,\n+\t\t\t\"Missing '%s' in dm-verity node of '%s'\\n\",\n+\t\t\tFIT_VERITY_DBS_PROP, image_name);\n+\t\treturn -EINVAL;\n+\t}\n+\tdata_block_size = fdt32_to_cpu(*val);\n+\n+\tval = fdt_getprop(fit, verity_noffset, FIT_VERITY_HBS_PROP, NULL);\n+\tif (!val) {\n+\t\tfprintf(stderr,\n+\t\t\t\"Missing '%s' in dm-verity node of '%s'\\n\",\n+\t\t\tFIT_VERITY_HBS_PROP, image_name);\n+\t\treturn -EINVAL;\n+\t}\n+\thash_block_size = fdt32_to_cpu(*val);\n+\n+\tif (data_block_size < 512 || hash_block_size < 512) {\n+\t\tfprintf(stderr,\n+\t\t\t\"Invalid block sizes in dm-verity node of '%s'\\n\",\n+\t\t\timage_name);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (data_size % data_block_size) {\n+\t\tfprintf(stderr,\n+\t\t\t\"Image '%s' size %zu not a multiple of data-block-size %d\\n\",\n+\t\t\timage_name, data_size, data_block_size);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tnum_data_blocks = data_size / data_block_size;\n+\thash_offset = data_size;\n+\n+\tfd = mkstemp(tmpfile);\n+\tif (fd < 0) {\n+\t\tfprintf(stderr, \"Can't create temp file: %s\\n\",\n+\t\t\tstrerror(errno));\n+\t\treturn -EIO;\n+\t}\n+\n+\tif (write(fd, data, data_size) != (ssize_t)data_size) {\n+\t\tfprintf(stderr, \"Can't write temp file: %s\\n\",\n+\t\t\tstrerror(errno));\n+\t\tret = -EIO;\n+\t\tgoto err_unlink;\n+\t}\n+\tclose(fd);\n+\tfd = -1;\n+\n+\tsnprintf(cmd, sizeof(cmd),\n+\t\t \"veritysetup format \\\"%s\\\" \\\"%s\\\" --no-superblock --hash=%s --data-block-size=%d --hash-block-size=%d --hash-offset=%zu 2>&1\",\n+\t\t tmpfile, tmpfile, algo, data_block_size, hash_block_size,\n+\t\t hash_offset);\n+\n+\tfp = popen(cmd, \"r\");\n+\tif (!fp) {\n+\t\tfprintf(stderr, \"Can't run veritysetup: %s\\n\",\n+\t\t\tstrerror(errno));\n+\t\tret = -EIO;\n+\t\tgoto err_unlink;\n+\t}\n+\n+\t/* parse key: value lines from veritysetup stdout */\n+\twhile (fgets(line, sizeof(line), fp)) {\n+\t\tcolon = strchr(line, ':');\n+\t\tif (!colon)\n+\t\t\tcontinue;\n+\t\tvalue = colon + 1;\n+\t\twhile (*value == ' ' || *value == '\\t')\n+\t\t\tvalue++;\n+\t\tend = value + strlen(value) - 1;\n+\t\twhile (end > value && (*end == '\\n' || *end == '\\r' ||\n+\t\t\t\t       *end == ' '))\n+\t\t\t*end-- = '\\0';\n+\n+\t\tif (!strncmp(line, \"Root hash:\", 10))\n+\t\t\tsnprintf(root_hash_hex, sizeof(root_hash_hex),\n+\t\t\t\t \"%s\", value);\n+\t\telse if (!strncmp(line, \"Salt:\", 5))\n+\t\t\tsnprintf(salt_hex, sizeof(salt_hex), \"%s\", value);\n+\t}\n+\n+\tret = pclose(fp);\n+\tif (ret) {\n+\t\tfprintf(stderr, \"veritysetup failed (exit %d) for '%s'\\n\",\n+\t\t\tWEXITSTATUS(ret), image_name);\n+\t\tret = -EIO;\n+\t\tgoto err_unlink;\n+\t}\n+\n+\tif (!root_hash_hex[0] || !salt_hex[0]) {\n+\t\tfprintf(stderr, \"Failed to parse veritysetup output for '%s'\\n\",\n+\t\t\timage_name);\n+\t\tret = -EIO;\n+\t\tgoto err_unlink;\n+\t}\n+\n+\tdigest_len = strlen(root_hash_hex) / 2;\n+\tsalt_len   = strlen(salt_hex) / 2;\n+\n+\tif (digest_len > (int)sizeof(digest_bin) ||\n+\t    salt_len > (int)sizeof(salt_bin)) {\n+\t\tfprintf(stderr, \"Hash/salt too long for '%s'\\n\", image_name);\n+\t\tret = -EINVAL;\n+\t\tgoto err_unlink;\n+\t}\n+\n+\tif (fit_hex2bin(digest_bin, root_hash_hex, digest_len) ||\n+\t    fit_hex2bin(salt_bin, salt_hex, salt_len)) {\n+\t\tfprintf(stderr, \"Invalid hex in veritysetup output for '%s'\\n\",\n+\t\t\timage_name);\n+\t\tret = -EINVAL;\n+\t\tgoto err_unlink;\n+\t}\n+\n+\tif (stat(tmpfile, &st)) {\n+\t\tfprintf(stderr, \"Can't stat temp file: %s\\n\",\n+\t\t\tstrerror(errno));\n+\t\tret = -EIO;\n+\t\tgoto err_unlink;\n+\t}\n+\n+\texpanded = malloc(st.st_size);\n+\tif (!expanded) {\n+\t\tret = -ENOMEM;\n+\t\tgoto err_unlink;\n+\t}\n+\n+\tfd = open(tmpfile, O_RDONLY);\n+\tif (fd < 0 || read(fd, expanded, st.st_size) != st.st_size) {\n+\t\tfprintf(stderr, \"Can't read back temp file: %s\\n\",\n+\t\t\tstrerror(errno));\n+\t\tret = -EIO;\n+\t\tgoto err_free;\n+\t}\n+\tclose(fd);\n+\tfd = -1;\n+\n+\t/* hash tree starts immediately after data (no superblock) */\n+\thash_start_block = hash_offset / hash_block_size;\n+\n+\tret = fdt_setprop(fit, verity_noffset, FIT_VERITY_DIGEST_PROP,\n+\t\t\t  digest_bin, digest_len);\n+\tif (ret) {\n+\t\tret = (ret == -FDT_ERR_NOSPACE) ? -ENOSPC : -EIO;\n+\t\tgoto err_free;\n+\t}\n+\n+\tret = fdt_setprop(fit, verity_noffset, FIT_VERITY_SALT_PROP,\n+\t\t\t  salt_bin, salt_len);\n+\tif (ret) {\n+\t\tret = (ret == -FDT_ERR_NOSPACE) ? -ENOSPC : -EIO;\n+\t\tgoto err_free;\n+\t}\n+\n+\tret = fdt_setprop_u32(fit, verity_noffset, FIT_VERITY_NBLK_PROP,\n+\t\t\t      num_data_blocks);\n+\tif (ret) {\n+\t\tret = (ret == -FDT_ERR_NOSPACE) ? -ENOSPC : -EIO;\n+\t\tgoto err_free;\n+\t}\n+\n+\tret = fdt_setprop_u32(fit, verity_noffset, FIT_VERITY_HBLK_PROP,\n+\t\t\t      hash_start_block);\n+\tif (ret) {\n+\t\tret = (ret == -FDT_ERR_NOSPACE) ? -ENOSPC : -EIO;\n+\t\tgoto err_free;\n+\t}\n+\n+\t/*\n+\t * Stash the temp-file path so that fit_extract_data() can use\n+\t * the expanded content for the external-data section.\n+\t */\n+\tret = fdt_setprop_string(fit, verity_noffset,\n+\t\t\t\t \"verity-data-file\", tmpfile);\n+\tif (ret) {\n+\t\tret = (ret == -FDT_ERR_NOSPACE) ? -ENOSPC : -EIO;\n+\t\tgoto err_free;\n+\t}\n+\n+\t*expanded_data = expanded;\n+\t*expanded_size = st.st_size;\n+\treturn 0;\n+\n+err_free:\n+\tfree(expanded);\n+err_unlink:\n+\tif (fd >= 0)\n+\t\tclose(fd);\n+\tunlink(tmpfile);\n+\treturn ret;\n+}\n+\n /**\n  * fit_image_add_verification_data() - calculate/set verig. data for image node\n  *\n@@ -652,6 +978,8 @@ int fit_image_cipher_data(const char *keydir, void *keydest,\n  *\n  * For signature details, please see doc/usage/fit/signature.rst\n  *\n+ * For dm-verity details, please see doc/usage/fit/dm-verity.rst\n+ *\n  * @keydir\tDirectory containing *.key and *.crt files (or NULL)\n  * @keydest\tFDT Blob to write public keys into (NULL if none)\n  * @fit:\tPointer to the FIT format image header\n@@ -667,9 +995,12 @@ int fit_image_add_verification_data(const char *keydir, const char *keyfile,\n \t\tconst char *cmdname, const char* algo_name)\n {\n \tconst char *image_name;\n+\tconst char *node_name;\n \tconst void *data;\n \tsize_t size;\n+\tvoid *verity_data = NULL;\n \tint noffset;\n+\tint ret;\n \n \t/* Get image data and data length */\n \tif (fit_image_get_emb_data(fit, image_noffset, &data, &size)) {\n@@ -679,13 +1010,39 @@ int fit_image_add_verification_data(const char *keydir, const char *keyfile,\n \n \timage_name = fit_get_name(fit, image_noffset, NULL);\n \n-\t/* Process all hash subnodes of the component image node */\n+\t/*\n+\t * Pass 1 -- dm-verity: run veritysetup to produce the Merkle\n+\t * hash tree and fill in computed metadata.  The expanded\n+\t * content (original data + hash tree) is returned in\n+\t * verity_data so that pass 2 hashes the complete image.\n+\t */\n \tfor (noffset = fdt_first_subnode(fit, image_noffset);\n \t     noffset >= 0;\n \t     noffset = fdt_next_subnode(fit, noffset)) {\n-\t\tconst char *node_name;\n-\t\tint ret = 0;\n+\t\tif (!strcmp(fit_get_name(fit, noffset, NULL),\n+\t\t\t    FIT_VERITY_NODENAME)) {\n+\t\t\tret = fit_image_process_verity(fit, image_name,\n+\t\t\t\t\t\t       image_noffset,\n+\t\t\t\t\t\t       noffset,\n+\t\t\t\t\t\t       data, size,\n+\t\t\t\t\t\t       &verity_data,\n+\t\t\t\t\t\t       &size);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n+\t\t\tif (verity_data)\n+\t\t\t\tdata = verity_data;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n \n+\t/*\n+\t * Pass 2 -- hashes and signatures: compute over the (possibly\n+\t * expanded) image data.\n+\t */\n+\tfor (noffset = fdt_first_subnode(fit, image_noffset);\n+\t     noffset >= 0;\n+\t     noffset = fdt_next_subnode(fit, noffset)) {\n+\t\tret = 0;\n \t\t/*\n \t\t * Check subnode name, must be equal to \"hash\" or \"signature\".\n \t\t * Multiple hash nodes require unique unit node\n@@ -704,10 +1061,13 @@ int fit_image_add_verification_data(const char *keydir, const char *keyfile,\n \t\t\t\tcomment, require_keys, engine_id, cmdname,\n \t\t\t\talgo_name);\n \t\t}\n-\t\tif (ret < 0)\n+\t\tif (ret < 0) {\n+\t\t\tfree(verity_data);\n \t\t\treturn ret;\n+\t\t}\n \t}\n \n+\tfree(verity_data);\n \treturn 0;\n }\n \n","prefixes":["3/4"]}