[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