[PATCH] boot: Add fit_config_get_hash_list() to build signed node list
Ahmad Fatoum
a.fatoum at pengutronix.de
Thu Mar 5 12:43:26 CET 2026
Hello Simon,
On 3/4/26 2:51 PM, Simon Glass wrote:
> From: Simon Glass <simon.glass at canonical.com>
>
> The hashed-nodes property in a FIT signature node lists which FDT paths
> are included in the signature hash. It is intended as a hint so should
> not be used for verification.
>
> Add a function to build the node list from scratch by iterating the
> configuration's image references. Skip properties known not to be image
> references. For each image, collect the path plus all hash and cipher
> subnodes.
>
> Use the new function in fit_config_check_sig() instead of reading
> 'hashed-nodes'.
>
> Update the docs to cover this. The FIT spec can be updated separately.
Thanks very much, I had something similar in mind.
Otherwise, the compatibility break would've been a nightmare.
Cheers,
Ahmad
>
> Signed-off-by: Simon Glass <simon.glass at canonical.com>
> ---
>
> boot/image-fit-sig.c | 227 +++++++++++++++++++++++++++++-------
> doc/usage/fit/signature.rst | 19 ++-
> 2 files changed, 195 insertions(+), 51 deletions(-)
>
> diff --git a/boot/image-fit-sig.c b/boot/image-fit-sig.c
> index f23e9d5d0b0..f426ead13c0 100644
> --- a/boot/image-fit-sig.c
> +++ b/boot/image-fit-sig.c
> @@ -18,6 +18,7 @@ DECLARE_GLOBAL_DATA_PTR;
> #include <u-boot/hash-checksum.h>
>
> #define IMAGE_MAX_HASHED_NODES 100
> +#define FIT_MAX_HASH_PATH_BUF 4096
>
> /**
> * fit_region_make_list() - Make a list of image regions
> @@ -229,6 +230,179 @@ int fit_image_verify_required_sigs(const void *fit, int image_noffset,
> return 0;
> }
>
> +/**
> + * fit_config_add_hash() - Add hash nodes for one image to the node list
> + *
> + * Adds the image path, all its hash-* subnode paths, and its cipher
> + * subnode path (if present) to the packed buffer.
> + *
> + * @fit: FIT blob
> + * @image_noffset: Image node offset (e.g. /images/kernel-1)
> + * @node_inc: Array of path pointers to fill
> + * @count: Pointer to current count (updated on return)
> + * @max_nodes: Maximum entries in @node_inc
> + * @buf: Buffer for packed path strings
> + * @buf_used: Pointer to bytes used in @buf (updated on return)
> + * @buf_len: Total size of @buf
> + * Return: 0 on success, -ve on error
> + */
> +static int fit_config_add_hash(const void *fit, int image_noffset,
> + char **node_inc, int *count, int max_nodes,
> + char *buf, int *buf_used, int buf_len)
> +{
> + int noffset, hash_count, ret, len;
> +
> + if (*count >= max_nodes)
> + return -ENOSPC;
> +
> + ret = fdt_get_path(fit, image_noffset, buf + *buf_used,
> + buf_len - *buf_used);
> + if (ret < 0)
> + return -ENOENT;
> + len = strlen(buf + *buf_used) + 1;
> + node_inc[(*count)++] = buf + *buf_used;
> + *buf_used += len;
> +
> + /* Add all this image's hash subnodes */
> + hash_count = 0;
> + for (noffset = fdt_first_subnode(fit, image_noffset);
> + noffset >= 0;
> + noffset = fdt_next_subnode(fit, noffset)) {
> + const char *name = fit_get_name(fit, noffset, NULL);
> +
> + if (strncmp(name, FIT_HASH_NODENAME,
> + strlen(FIT_HASH_NODENAME)))
> + continue;
> + if (*count >= max_nodes)
> + return -ENOSPC;
> + ret = fdt_get_path(fit, noffset, buf + *buf_used,
> + buf_len - *buf_used);
> + if (ret < 0)
> + return -ENOENT;
> + len = strlen(buf + *buf_used) + 1;
> + node_inc[(*count)++] = buf + *buf_used;
> + *buf_used += len;
> + hash_count++;
> + }
> +
> + if (!hash_count) {
> + printf("No hash nodes in image '%s'\n",
> + fdt_get_name(fit, image_noffset, NULL));
> + return -ENOMSG;
> + }
> +
> + /* Add this image's cipher node if present */
> + noffset = fdt_subnode_offset(fit, image_noffset, FIT_CIPHER_NODENAME);
> + if (noffset != -FDT_ERR_NOTFOUND) {
> + if (noffset < 0)
> + return -EIO;
> + if (*count >= max_nodes)
> + return -ENOSPC;
> + ret = fdt_get_path(fit, noffset, buf + *buf_used,
> + buf_len - *buf_used);
> + if (ret < 0)
> + return -ENOENT;
> + len = strlen(buf + *buf_used) + 1;
> + node_inc[(*count)++] = buf + *buf_used;
> + *buf_used += len;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * fit_config_get_hash_list() - Build the list of nodes to hash
> + *
> + * Works through every image referenced by the configuration and collects the
> + * node paths: root + config + all referenced images with their hash and
> + * cipher subnodes.
> + *
> + * Properties known not to be image references (description, compatible,
> + * default, load-only) are skipped, so any new image type is covered by default.
> + *
> + * @fit: FIT blob
> + * @conf_noffset: Configuration node offset
> + * @node_inc: Array to fill with path string pointers
> + * @max_nodes: Size of @node_inc array
> + * @buf: Buffer for packed null-terminated path strings
> + * @buf_len: Size of @buf
> + * Return: number of entries in @node_inc, or -ve on error
> + */
> +static int fit_config_get_hash_list(const void *fit, int conf_noffset,
> + char **node_inc, int max_nodes,
> + char *buf, int buf_len)
> +{
> + const char *conf_name;
> + int image_count;
> + int prop_offset;
> + int used = 0;
> + int count = 0;
> + int ret, len;
> +
> + conf_name = fit_get_name(fit, conf_noffset, NULL);
> +
> + /* Always include the root node and the configuration node */
> + if (max_nodes < 2)
> + return -ENOSPC;
> +
> + len = 2; /* "/" + nul */
> + if (len > buf_len)
> + return -ENOSPC;
> + strcpy(buf, "/");
> + node_inc[count++] = buf;
> + used += len;
> +
> + len = snprintf(buf + used, buf_len - used, "%s/%s", FIT_CONFS_PATH,
> + conf_name) + 1;
> + if (used + len > buf_len)
> + return -ENOSPC;
> + node_inc[count++] = buf + used;
> + used += len;
> +
> + /* Process each image referenced by the config */
> + image_count = 0;
> + fdt_for_each_property_offset(prop_offset, fit, conf_noffset) {
> + const char *prop_name;
> + int img_count, i;
> +
> + fdt_getprop_by_offset(fit, prop_offset, &prop_name, NULL);
> + if (!prop_name)
> + continue;
> +
> + /* Skip properties that are not image references */
> + if (!strcmp(prop_name, FIT_DESC_PROP) ||
> + !strcmp(prop_name, FIT_COMPAT_PROP) ||
> + !strcmp(prop_name, FIT_DEFAULT_PROP))
> + continue;
> +
> + img_count = fdt_stringlist_count(fit, conf_noffset, prop_name);
> + for (i = 0; i < img_count; i++) {
> + int noffset;
> +
> + noffset = fit_conf_get_prop_node_index(fit,
> + conf_noffset,
> + prop_name, i);
> + if (noffset < 0)
> + continue;
> +
> + ret = fit_config_add_hash(fit, noffset, node_inc,
> + &count, max_nodes, buf, &used,
> + buf_len);
> + if (ret < 0)
> + return ret;
> +
> + image_count++;
> + }
> + }
> +
> + if (!image_count) {
> + printf("No images in config '%s'\n", conf_name);
> + return -ENOMSG;
> + }
> +
> + return count;
> +}
> +
> /**
> * fit_config_check_sig() - Check the signature of a config
> *
> @@ -269,20 +443,16 @@ static int fit_config_check_sig(const void *fit, int noffset, int conf_noffset,
> FIT_DATA_POSITION_PROP,
> FIT_DATA_OFFSET_PROP,
> };
> -
> - const char *prop, *end, *name;
> + char *node_inc[IMAGE_MAX_HASHED_NODES];
> + char hash_buf[FIT_MAX_HASH_PATH_BUF];
> struct image_sign_info info;
> const uint32_t *strings;
> - const char *config_name;
> uint8_t *fit_value;
> int fit_value_len;
> - bool found_config;
> int max_regions;
> - int i, prop_len;
> char path[200];
> int count;
>
> - config_name = fit_get_name(fit, conf_noffset, NULL);
> debug("%s: fdt=%p, conf='%s', sig='%s'\n", __func__, key_blob,
> fit_get_name(fit, noffset, NULL),
> fit_get_name(key_blob, required_keynode, NULL));
> @@ -297,45 +467,12 @@ static int fit_config_check_sig(const void *fit, int noffset, int conf_noffset,
> return -1;
> }
>
> - /* Count the number of strings in the property */
> - prop = fdt_getprop(fit, noffset, "hashed-nodes", &prop_len);
> - end = prop ? prop + prop_len : prop;
> - for (name = prop, count = 0; name < end; name++)
> - if (!*name)
> - count++;
> - if (!count) {
> - *err_msgp = "Can't get hashed-nodes property";
> - return -1;
> - }
> -
> - if (prop && prop_len > 0 && prop[prop_len - 1] != '\0') {
> - *err_msgp = "hashed-nodes property must be null-terminated";
> - return -1;
> - }
> -
> - /* Add a sanity check here since we are using the stack */
> - if (count > IMAGE_MAX_HASHED_NODES) {
> - *err_msgp = "Number of hashed nodes exceeds maximum";
> - return -1;
> - }
> -
> - /* Create a list of node names from those strings */
> - char *node_inc[count];
> -
> - debug("Hash nodes (%d):\n", count);
> - found_config = false;
> - for (name = prop, i = 0; name < end; name += strlen(name) + 1, i++) {
> - debug(" '%s'\n", name);
> - node_inc[i] = (char *)name;
> - if (!strncmp(FIT_CONFS_PATH, name, strlen(FIT_CONFS_PATH)) &&
> - name[sizeof(FIT_CONFS_PATH) - 1] == '/' &&
> - !strcmp(name + sizeof(FIT_CONFS_PATH), config_name)) {
> - debug(" (found config node %s)", config_name);
> - found_config = true;
> - }
> - }
> - if (!found_config) {
> - *err_msgp = "Selected config not in hashed nodes";
> + /* Build the node list from the config, ignoring hashed-nodes */
> + count = fit_config_get_hash_list(fit, conf_noffset,
> + node_inc, IMAGE_MAX_HASHED_NODES,
> + hash_buf, sizeof(hash_buf));
> + if (count < 0) {
> + *err_msgp = "Failed to build hash node list";
> return -1;
> }
>
> diff --git a/doc/usage/fit/signature.rst b/doc/usage/fit/signature.rst
> index e5b5a8432e9..da08cc75c3a 100644
> --- a/doc/usage/fit/signature.rst
> +++ b/doc/usage/fit/signature.rst
> @@ -353,20 +353,27 @@ meantime.
> Details
> -------
> The signature node contains a property ('hashed-nodes') which lists all the
> -nodes that the signature was made over. The image is walked in order and each
> -tag processed as follows:
> +nodes that the signature was made over. The signer (mkimage) writes this
> +property as a record of what was included in the hash. During verification,
> +however, U-Boot does not read 'hashed-nodes'. Instead it rebuilds the node
> +list from the configuration's own image references (kernel, fdt, ramdisk,
> +etc.), since 'hashed-nodes' is not itself covered by the signature. The
> +rebuilt list always includes the root node, the configuration node, each
> +referenced image node and its hash/cipher subnodes.
> +
> +The image is walked in order and each tag processed as follows:
>
> DTB_BEGIN_NODE
> The tag and the following name are included in the signature
> - if the node or its parent are present in 'hashed-nodes'
> + if the node or its parent are present in the node list
>
> DTB_END_NODE
> The tag is included in the signature if the node or its parent
> - are present in 'hashed-nodes'
> + are present in the node list
>
> DTB_PROPERTY
> The tag, the length word, the offset in the string table, and
> - the data are all included if the current node is present in 'hashed-nodes'
> + the data are all included if the current node is present in the node list
> and the property name is not 'data'.
>
> DTB_END
> @@ -374,7 +381,7 @@ DTB_END
>
> DTB_NOP
> The tag is included in the signature if the current node is present
> - in 'hashed-nodes'
> + in the node list
>
> In addition, the signature contains a property 'hashed-strings' which contains
> the offset and length in the string table of the strings that are to be
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
More information about the U-Boot
mailing list