[PATCH v2] boot: Add fit_config_get_hash_list() to build signed node list

Ahmad Fatoum a.fatoum at pengutronix.de
Wed Mar 11 15:17:23 CET 2026


Hello Simon,

On 3/6/26 2:20 AM, Simon Glass wrote:
> From: Simon Glass <simon.glass at canonical.com>

> +	/* 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) ||

I know you need to mimic what mkimage does on the other side, but I
found this surprising:

The configuration node name is probably hashed along with the FDT node,
but compatible isn't, so relying on compatible means an attacker can
trivially control which configuration is booted.

> +		    !strcmp(prop_name, FIT_DEFAULT_PROP))

I am aware of /configurations/default, but what does a default property
within a configuration signify?

Thanks,
Ahmad

> +			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
> diff --git a/test/py/tests/test_vboot.py b/test/py/tests/test_vboot.py
> index 7a7f9c379de..19f3f981379 100644
> --- a/test/py/tests/test_vboot.py
> +++ b/test/py/tests/test_vboot.py
> @@ -362,10 +362,14 @@ def test_vboot(ubman, name, sha_algo, padding, sign_options, required,
>              shutil.copyfile(fit, efit)
>              vboot_evil.add_evil_node(fit, efit, evil_kernel, 'kernel@')
>  
> -            msg = 'Signature checking prevents use of unit addresses (@) in nodes'
> +            # fit_check_sign catches this via signature mismatch (the @
> +            # node is hashed instead of the real one)
>              utils.run_and_log_expect_exception(
>                  ubman, [fit_check_sign, '-f', efit, '-k', dtb],
> -                1, msg)
> +                1, 'Failed to verify required signature')
> +
> +            # bootm catches it earlier, at fit_check_format() time
> +            msg = 'Signature checking prevents use of unit addresses (@) in nodes'
>              run_bootm(sha_algo, 'evil kernel@', msg, False, efit)
>  
>          # Create a new properly signed fit and replace header bytes

-- 
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