diff options
| author | Tom Rini <trini@konsulko.com> | 2026-07-06 18:26:12 -0600 |
|---|---|---|
| committer | Tom Rini <trini@konsulko.com> | 2026-07-06 18:26:12 -0600 |
| commit | ee5d46b45ec0c63f8f9dd1e816e0dac3452ccc3d (patch) | |
| tree | 800cd9e204ca027144070101884c0d5d3c00130f /tools | |
| parent | ece349ade2973e220f524ce59e59711cc919263f (diff) | |
| parent | a18265f1ccb7a272721ed4286ed3b5a6182ff424 (diff) | |
| download | u-boot-master.tar.gz u-boot-master.zip | |
Merge branch 'next'HEADmasterWIP/06Jul2026
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/Makefile | 3 | ||||
| -rw-r--r-- | tools/binman/etype/nxp_imx9image.py | 3 | ||||
| -rw-r--r-- | tools/fdtgrep.c | 3 | ||||
| -rw-r--r-- | tools/fit_image.c | 101 | ||||
| -rw-r--r-- | tools/image-host.c | 414 | ||||
| -rw-r--r-- | tools/libfdt/fdt_check.c | 2 |
6 files changed, 513 insertions, 13 deletions
diff --git a/tools/Makefile b/tools/Makefile index 1a5f425ecda..535a5d51c89 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -82,7 +82,8 @@ HOSTCFLAGS_image-host.o += \ # The following files are synced with upstream DTC. # Use synced versions from scripts/dtc/libfdt/. LIBFDT_OBJS := $(addprefix libfdt/, fdt.o fdt_ro.o fdt_wip.o fdt_sw.o fdt_rw.o \ - fdt_strerror.o fdt_empty_tree.o fdt_addresses.o fdt_overlay.o) + fdt_strerror.o fdt_empty_tree.o fdt_addresses.o fdt_overlay.o \ + fdt_check.o) RSA_OBJS-$(CONFIG_TOOLS_LIBCRYPTO) := $(addprefix generated/lib/rsa/, \ rsa-sign.o rsa-verify.o \ diff --git a/tools/binman/etype/nxp_imx9image.py b/tools/binman/etype/nxp_imx9image.py index e0e806f2b7f..138563ba33a 100644 --- a/tools/binman/etype/nxp_imx9image.py +++ b/tools/binman/etype/nxp_imx9image.py @@ -36,7 +36,8 @@ class Entry_nxp_imx9image(Entry_mkimage): 'append', 'boot-from', 'cntr-version', 'container', 'dummy-ddr', 'dummy-v2x', 'hold', 'image', 'soc-type' ] - external_files = ['oei-m33-tcm.bin', 'm33_image.bin', 'bl31.bin'] + external_files = ['oei-m33-tcm.bin', 'm33_image.bin', 'bl31.bin', + 'tee.bin'] with open(self.config_filename, 'w', encoding='utf-8') as f: for prop in self._node.props.values(): diff --git a/tools/fdtgrep.c b/tools/fdtgrep.c index b4c041070f5..dba7240001f 100644 --- a/tools/fdtgrep.c +++ b/tools/fdtgrep.c @@ -355,6 +355,9 @@ static int display_fdt_by_regions(struct display_info *disp, const void *blob, case FDT_BEGIN_NODE: name = fdt_get_name(blob, offset, &len); + if (!name) + return len; + fprintf(f, "%*s%s {", depth++ * shift, "", *name ? name : "/"); break; diff --git a/tools/fit_image.c b/tools/fit_image.c index 1dbc14c63e4..5831b07c090 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); @@ -265,8 +281,14 @@ static void get_basename(char *str, int size, const char *fname) */ p = strrchr(fname, '/'); start = p ? p + 1 : fname; - p = strrchr(fname, '.'); - end = p ? p : fname + strlen(fname); + /* + * Search for the extension dot only within the basename. Searching + * the whole path would let a dot in the directory part (for example + * "./mydt" or "a.b/c") place 'end' before 'start' and produce a + * negative length, which the size check below does not catch. + */ + p = strrchr(start, '.'); + end = p ? p : start + strlen(start); len = end - start; if (len >= size) len = size - 1; @@ -471,6 +493,41 @@ static int fit_write_images(struct image_tool_params *params, char *fdt) } /** + * fit_copy_image_data() - copy image data, using cached verity expansion + * @fdt: FIT blob + * @node: image node offset + * @buf: destination buffer + * @buf_ptr: write offset within @buf + * @data: embedded image data (used when no dm-verity expansion exists) + * @lenp: in/out: on entry, length of @data; on exit, bytes written + * + * When fit_image_process_verity() has run, the expanded image data + * (original + hash tree) is cached in memory. Look it up by image name + * and copy from the cached buffer rather than the embedded ``data`` + * property; fall back to @data otherwise. + * + * Return: 0 on success + */ +static int fit_copy_image_data(void *fdt, int node, void *buf, + int buf_ptr, const void *data, int *lenp) +{ + const char *image_name = fdt_get_name(fdt, node, NULL); + const void *vdata; + size_t vsize; + + if (image_name && + !fit_verity_get_expanded(image_name, &vdata, &vsize)) { + memcpy(buf + buf_ptr, vdata, vsize); + *lenp = vsize; + return 0; + } + + memcpy(buf + buf_ptr, data, *lenp); + + return 0; +} + +/** * fit_write_configs() - Write out a list of configurations to the FIT * * If there are device tree files, we include a configuration for each, which @@ -653,6 +710,8 @@ static int fit_extract_data(struct image_tool_params *params, const char *fname) int node; int align_size = 0; int len = 0; + int verity_extra = 0; + int orig_len; fd = mmap_fdt(params->cmdname, fname, 0, &fdt, &sbuf, false, false); if (fd < 0) @@ -687,10 +746,33 @@ static int fit_extract_data(struct image_tool_params *params, const char *fname) } /* + * When dm-verity is active the external data for an image is + * larger than the embedded data property (original + hash tree). + * Walk images once more and consult the in-memory cache for the + * actual expanded size. + */ + fdt_for_each_subnode(node, fdt, images) { + const char *image_name; + const void *vdata; + size_t vsize; + + orig_len = 0; + if (fdt_subnode_offset(fdt, node, FIT_VERITY_NODENAME) < 0) + continue; + image_name = fdt_get_name(fdt, node, NULL); + if (!image_name || + fit_verity_get_expanded(image_name, &vdata, &vsize)) + continue; + fdt_getprop(fdt, node, FIT_DATA_PROP, &orig_len); + if ((int)vsize > orig_len) + verity_extra += (int)vsize - orig_len; + } + + /* * Allocate space to hold the image data we will extract, * extral space allocate for image alignment to prevent overflow. */ - buf = calloc(1, fit_size + align_size); + buf = calloc(1, fit_size + align_size + verity_extra); if (!buf) { ret = -ENOMEM; goto err_munmap; @@ -721,7 +803,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..8f1e7be4066 100644 --- a/tools/image-host.c +++ b/tools/image-host.c @@ -12,8 +12,12 @@ #include <bootm.h> #include <fdt_region.h> #include <image.h> +#include <hexdump.h> #include <version.h> +#include <sys/stat.h> +#include <sys/wait.h> + #if CONFIG_IS_ENABLED(FIT_SIGNATURE) #include <openssl/pem.h> #include <openssl/evp.h> @@ -626,6 +630,376 @@ int fit_image_cipher_data(const char *keydir, void *keydest, image_noffset, cipher_node_offset, data, size, cmdname); } +/* + * In-memory cache of dm-verity expanded buffers (original data followed + * by the Merkle hash tree), keyed by image unit name. Populated by + * fit_image_process_verity() and consumed by fit_extract_data() / + * fit_copy_image_data() so that the expanded content never leaves the + * mkimage process address space. + */ +struct fit_verity_blob { + char *name; + void *data; + size_t size; + struct fit_verity_blob *next; +}; + +static struct fit_verity_blob *fit_verity_blobs; + +/* Stash a malloc'd expanded buffer; takes ownership of @data on success. */ +static int fit_verity_stash(const char *name, void *data, size_t size) +{ + struct fit_verity_blob *b; + + b = calloc(1, sizeof(*b)); + if (!b) + return -ENOMEM; + b->name = strdup(name); + if (!b->name) { + free(b); + return -ENOMEM; + } + b->data = data; + b->size = size; + b->next = fit_verity_blobs; + fit_verity_blobs = b; + + return 0; +} + +int fit_verity_get_expanded(const char *name, const void **data, size_t *size) +{ + struct fit_verity_blob *b; + + for (b = fit_verity_blobs; b; b = b->next) { + if (!strcmp(b->name, name)) { + *data = b->data; + *size = b->size; + return 0; + } + } + + return -ENOENT; +} + +/** + * fit_image_process_verity() - Run veritysetup and fill dm-verity properties + * + * Extracts the embedded image data to a temporary file, runs + * ``veritysetup format`` to generate the Merkle hash tree (appended to the + * same file), parses Root hash / Salt from its stdout, and writes the + * computed properties (digest, salt, num-data-blocks, hash-start-block) + * back into the FIT dm-verity subnode. + * + * The expanded data (original data + hash tree) is read back into a + * malloc'd buffer and stashed in an in-memory cache keyed by @image_name + * via fit_verity_stash(). The same buffer is returned through + * @expanded_data / @expanded_size so that hash and signature subnodes + * can be computed over the complete image; the returned pointer is a + * *view* of the cached buffer and must not be freed by the caller. + * fit_extract_data() later retrieves the same buffer via + * fit_verity_get_expanded() to write the external data section. + * + * @fit: FIT blob (read-write) + * @image_name: image unit name (for diagnostics) + * @verity_noffset: dm-verity subnode offset + * @data: embedded image data + * @data_size: size of @data in bytes + * @expanded_data: output -- malloc'd buffer with expanded content + * @expanded_size: output -- size of @expanded_data + * Return: 0 on success, -ve on error (-ENOSPC when the FIT blob is full) + */ +static int fit_image_process_verity(void *fit, const char *image_name, + int verity_noffset, + const void *data, size_t data_size, + void **expanded_data, size_t *expanded_size) +{ + const char *algo_prop; + char algo[64]; + const fdt32_t *val; + unsigned int data_block_size, hash_block_size; + uint32_t num_data_blocks; + size_t hash_offset; + uint32_t hash_start_block; + char tmpfile[] = "/tmp/mkimage-verity-XXXXXX"; + char dbs_arg[32], hbs_arg[32], algo_arg[80], hoff_arg[40]; + int pipefd[2]; + pid_t pid; + FILE *fp; + char line[256]; + char *colon, *value, *end; + char root_hash_hex[256] = {0}; + char salt_hex[256] = {0}; + uint8_t digest_bin[FIT_MAX_HASH_LEN]; + uint8_t salt_bin[FIT_MAX_HASH_LEN]; + int digest_len = 0, salt_len = 0; + void *expanded = NULL; + struct stat st; + int fd, ret; + + *expanded_data = NULL; + *expanded_size = 0; + + algo_prop = fdt_getprop(fit, verity_noffset, FIT_VERITY_ALGO_PROP, + NULL); + if (!algo_prop) { + fprintf(stderr, + "Missing '%s' in dm-verity node of '%s'\n", + FIT_VERITY_ALGO_PROP, image_name); + return -EINVAL; + } + /* Local copy -- the FDT pointer goes stale after fdt_setprop(). */ + snprintf(algo, sizeof(algo), "%s", algo_prop); + + val = fdt_getprop(fit, verity_noffset, FIT_VERITY_DBS_PROP, NULL); + if (!val) { + fprintf(stderr, + "Missing '%s' in dm-verity node of '%s'\n", + FIT_VERITY_DBS_PROP, image_name); + return -EINVAL; + } + data_block_size = fdt32_to_cpu(*val); + + val = fdt_getprop(fit, verity_noffset, FIT_VERITY_HBS_PROP, NULL); + if (!val) { + fprintf(stderr, + "Missing '%s' in dm-verity node of '%s'\n", + FIT_VERITY_HBS_PROP, image_name); + return -EINVAL; + } + hash_block_size = fdt32_to_cpu(*val); + + if (data_block_size < 512 || (data_block_size & (data_block_size - 1)) || + hash_block_size < 512 || (hash_block_size & (hash_block_size - 1))) { + fprintf(stderr, + "Block sizes must be >= 512 and a power of two in dm-verity node of '%s'\n", + image_name); + return -EINVAL; + } + + if (data_size % data_block_size) { + fprintf(stderr, + "Image '%s' size %zu not a multiple of data-block-size %d\n", + image_name, data_size, data_block_size); + return -EINVAL; + } + + if (data_size / data_block_size > UINT32_MAX || + data_size / hash_block_size > UINT32_MAX) { + fprintf(stderr, + "Image '%s' too large for dm-verity (> 2^32 blocks)\n", + image_name); + return -EINVAL; + } + num_data_blocks = data_size / data_block_size; + hash_offset = data_size; + + fd = mkstemp(tmpfile); + if (fd < 0) { + fprintf(stderr, "Can't create temp file: %s\n", + strerror(errno)); + return -EIO; + } + + if (write(fd, data, data_size) != (ssize_t)data_size) { + fprintf(stderr, "Can't write temp file: %s\n", + strerror(errno)); + ret = -EIO; + goto err_unlink; + } + close(fd); + fd = -1; + + /* + * Invoke veritysetup via fork/execvp -- no shell, so each argument + * goes verbatim to the binary and the algo string cannot inject + * additional commands no matter how crafted the .its is. + */ + snprintf(algo_arg, sizeof(algo_arg), "--hash=%s", algo); + snprintf(dbs_arg, sizeof(dbs_arg), "--data-block-size=%u", + data_block_size); + snprintf(hbs_arg, sizeof(hbs_arg), "--hash-block-size=%u", + hash_block_size); + snprintf(hoff_arg, sizeof(hoff_arg), "--hash-offset=%zu", hash_offset); + + if (pipe(pipefd) < 0) { + fprintf(stderr, "Can't create pipe: %s\n", strerror(errno)); + ret = -EIO; + goto err_unlink; + } + + pid = fork(); + if (pid < 0) { + fprintf(stderr, "Can't fork: %s\n", strerror(errno)); + close(pipefd[0]); + close(pipefd[1]); + ret = -EIO; + goto err_unlink; + } + + if (pid == 0) { + /* child: redirect stdout+stderr to pipe write-end, then exec */ + char *argv[] = { + "veritysetup", "format", tmpfile, tmpfile, + "--no-superblock", algo_arg, dbs_arg, hbs_arg, + hoff_arg, NULL, + }; + + close(pipefd[0]); + if (dup2(pipefd[1], STDOUT_FILENO) < 0 || + dup2(pipefd[1], STDERR_FILENO) < 0) + _exit(127); + close(pipefd[1]); + execvp(argv[0], argv); + fprintf(stderr, "Can't exec veritysetup: %s\n", + strerror(errno)); + _exit(127); + } + + /* parent: parse key: value lines from veritysetup stdout */ + close(pipefd[1]); + fp = fdopen(pipefd[0], "r"); + if (!fp) { + fprintf(stderr, "Can't fdopen veritysetup pipe: %s\n", + strerror(errno)); + close(pipefd[0]); + waitpid(pid, NULL, 0); + ret = -EIO; + goto err_unlink; + } + + while (fgets(line, sizeof(line), fp)) { + colon = strchr(line, ':'); + if (!colon) + continue; + value = colon + 1; + while (*value == ' ' || *value == '\t') + value++; + end = value + strlen(value) - 1; + while (end > value && (*end == '\n' || *end == '\r' || + *end == ' ')) + *end-- = '\0'; + + if (!strncmp(line, "Root hash:", 10)) + snprintf(root_hash_hex, sizeof(root_hash_hex), + "%s", value); + else if (!strncmp(line, "Salt:", 5)) + snprintf(salt_hex, sizeof(salt_hex), "%s", value); + } + fclose(fp); + + if (waitpid(pid, &ret, 0) < 0 || !WIFEXITED(ret) || + WEXITSTATUS(ret) != 0) { + fprintf(stderr, "veritysetup failed for '%s'\n", image_name); + ret = -EIO; + goto err_unlink; + } + + if (!root_hash_hex[0] || !salt_hex[0]) { + fprintf(stderr, "Failed to parse veritysetup output for '%s'\n", + image_name); + ret = -EIO; + goto err_unlink; + } + + digest_len = strlen(root_hash_hex) / 2; + salt_len = strlen(salt_hex) / 2; + + if (digest_len > (int)sizeof(digest_bin) || + salt_len > (int)sizeof(salt_bin)) { + fprintf(stderr, "Hash/salt too long for '%s'\n", image_name); + ret = -EINVAL; + goto err_unlink; + } + + if (hex2bin(digest_bin, root_hash_hex, digest_len) || + hex2bin(salt_bin, salt_hex, salt_len)) { + fprintf(stderr, "Invalid hex in veritysetup output for '%s'\n", + image_name); + ret = -EINVAL; + goto err_unlink; + } + + if (stat(tmpfile, &st)) { + fprintf(stderr, "Can't stat temp file: %s\n", + strerror(errno)); + ret = -EIO; + goto err_unlink; + } + + expanded = malloc(st.st_size); + if (!expanded) { + ret = -ENOMEM; + goto err_unlink; + } + + fd = open(tmpfile, O_RDONLY); + if (fd < 0 || read(fd, expanded, st.st_size) != st.st_size) { + fprintf(stderr, "Can't read back temp file: %s\n", + strerror(errno)); + ret = -EIO; + goto err_free; + } + close(fd); + fd = -1; + + /* Temp file is no longer needed -- expanded buffer lives in memory. */ + unlink(tmpfile); + + /* hash tree starts immediately after data (no superblock) */ + hash_start_block = hash_offset / hash_block_size; + + ret = fdt_setprop(fit, verity_noffset, FIT_VERITY_DIGEST_PROP, + digest_bin, digest_len); + if (ret) { + ret = (ret == -FDT_ERR_NOSPACE) ? -ENOSPC : -EIO; + goto err_free; + } + + ret = fdt_setprop(fit, verity_noffset, FIT_VERITY_SALT_PROP, + salt_bin, salt_len); + if (ret) { + ret = (ret == -FDT_ERR_NOSPACE) ? -ENOSPC : -EIO; + goto err_free; + } + + ret = fdt_setprop_u32(fit, verity_noffset, FIT_VERITY_NBLK_PROP, + num_data_blocks); + if (ret) { + ret = (ret == -FDT_ERR_NOSPACE) ? -ENOSPC : -EIO; + goto err_free; + } + + ret = fdt_setprop_u32(fit, verity_noffset, FIT_VERITY_HBLK_PROP, + hash_start_block); + if (ret) { + ret = (ret == -FDT_ERR_NOSPACE) ? -ENOSPC : -EIO; + goto err_free; + } + + /* + * Stash the expanded buffer in the in-process cache; fit_extract_data() + * looks it up via fit_verity_get_expanded() to populate the external + * data section. On success the cache takes ownership of @expanded. + */ + ret = fit_verity_stash(image_name, expanded, st.st_size); + if (ret) + goto err_free; + + *expanded_data = expanded; + *expanded_size = st.st_size; + + return 0; + +err_free: + free(expanded); +err_unlink: + if (fd >= 0) + close(fd); + unlink(tmpfile); + return ret; +} + /** * fit_image_add_verification_data() - calculate/set verig. data for image node * @@ -652,6 +1026,8 @@ int fit_image_cipher_data(const char *keydir, void *keydest, * * For signature details, please see doc/usage/fit/signature.rst * + * For dm-verity details, please see doc/usage/fit/dm-verity.rst + * * @keydir Directory containing *.key and *.crt files (or NULL) * @keydest FDT Blob to write public keys into (NULL if none) * @fit: Pointer to the FIT format image header @@ -667,9 +1043,16 @@ int fit_image_add_verification_data(const char *keydir, const char *keyfile, const char *cmdname, const char* algo_name) { const char *image_name; + const char *node_name; const void *data; size_t size; + /* + * View pointer into the dm-verity cache (owned by image-host.c). + * Do not free; the cache lives until mkimage exits. + */ + void *verity_data = NULL; int noffset; + int ret; /* Get image data and data length */ if (fit_image_get_emb_data(fit, image_noffset, &data, &size)) { @@ -679,13 +1062,38 @@ int fit_image_add_verification_data(const char *keydir, const char *keyfile, image_name = fit_get_name(fit, image_noffset, NULL); - /* Process all hash subnodes of the component image node */ + /* + * Pass 1 -- dm-verity: run veritysetup to produce the Merkle + * hash tree and fill in computed metadata. The expanded + * content (original data + hash tree) is returned in + * verity_data so that pass 2 hashes the complete image. + */ for (noffset = fdt_first_subnode(fit, image_noffset); noffset >= 0; noffset = fdt_next_subnode(fit, noffset)) { - const char *node_name; - int ret = 0; + if (!strcmp(fit_get_name(fit, noffset, NULL), + FIT_VERITY_NODENAME)) { + ret = fit_image_process_verity(fit, image_name, + noffset, + data, size, + &verity_data, + &size); + if (ret) + return ret; + if (verity_data) + data = verity_data; + break; + } + } + /* + * Pass 2 -- hashes and signatures: compute over the (possibly + * expanded) image data. + */ + for (noffset = fdt_first_subnode(fit, image_noffset); + noffset >= 0; + noffset = fdt_next_subnode(fit, noffset)) { + ret = 0; /* * Check subnode name, must be equal to "hash" or "signature". * Multiple hash nodes require unique unit node diff --git a/tools/libfdt/fdt_check.c b/tools/libfdt/fdt_check.c new file mode 100644 index 00000000000..a17a0194f5c --- /dev/null +++ b/tools/libfdt/fdt_check.c @@ -0,0 +1,2 @@ +#include "fdt_host.h" +#include "../scripts/dtc/libfdt/fdt_check.c" |
