Hello Simon,

On 3/4/26 2:51 PM, Simon Glass wrote:
> From: Simon Glass <[email protected]>
> 
> 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 <[email protected]>
> ---
> 
>  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 |

Reply via email to