summaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
authorTom Rini <[email protected]>2026-08-10 12:32:48 -0600
committerTom Rini <[email protected]>2026-08-10 12:37:16 -0600
commit6ea67890d034c8f285dcea438dcee2e06af3921c (patch)
treee28d966945f8a2f359044c661785e9b887f55366 /boot
parent28515d7ff5285e0b942f15577e3198f15c667a25 (diff)
parentfe9877c7d9dea740985edd11f7ff583e311568be (diff)
Merge patch series "boot: fit: authenticate the dm-verity roothash"
Daniel Golle <[email protected]> says: A signed FIT configuration can delegate the integrity of a (potentially large) root filesystem image to the kernel's dm-verity instead of having U-Boot hash the whole payload at boot: the FIT carries a "dm-verity" subnode with the roothash, salt and block parameters, U-Boot passes the roothash to Linux through the dm-mod.create bootargs, and dm-verity then validates the filesystem block by block against it. For that to be safe the roothash has to be trusted, and in a signed configuration the only thing that establishes trust is the configuration signature. The roothash was not covered by it. fit_config_add_hash() collected the image node, its hash subnodes and its cipher subnode into the signed region, but not the dm-verity subnode, so the roothash, the sole integrity anchor for the filesystem, was left unsigned. The result is a verified-boot bypass for the root filesystem: an attacker who can rewrite the boot medium can replace the filesystem, recompute a matching dm-verity tree, write the new roothash into the unsigned dm-verity subnode, and the configuration signature still verifies. dm-verity then faithfully validates the malicious filesystem against the attacker's roothash. This series closes the gap. Link: https://lore.kernel.org/r/[email protected]
Diffstat (limited to 'boot')
-rw-r--r--boot/image-fit-sig.c108
1 files changed, 69 insertions, 39 deletions
diff --git a/boot/image-fit-sig.c b/boot/image-fit-sig.c
index fe7ca6e4ab5..a0c50bba4cf 100644
--- a/boot/image-fit-sig.c
+++ b/boot/image-fit-sig.c
@@ -231,13 +231,10 @@ int fit_image_verify_required_sigs(const void *fit, int image_noffset,
}
/**
- * 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_config_add_node() - Append one node's path to the hashed-node list
*
* @fit: FIT blob
- * @image_noffset: Image node offset (e.g. /images/kernel-1)
+ * @noffset: Offset of the node whose path should be added
* @node_inc: Array of path pointers to fill
* @count: Pointer to current count (updated on return)
* @max_nodes: Maximum entries in @node_inc
@@ -246,23 +243,51 @@ int fit_image_verify_required_sigs(const void *fit, int image_noffset,
* @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)
+static int fit_config_add_node(const void *fit, int noffset, char **node_inc,
+ int *count, int max_nodes, char *buf,
+ int *buf_used, int buf_len)
{
- int noffset, hash_count, ret, len;
+ int ret, len;
if (*count >= max_nodes)
return -ENOSPC;
-
- ret = fdt_get_path(fit, image_noffset, buf + *buf_used,
- buf_len - *buf_used);
+ 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_add_hash() - Add hash nodes for one image to the node list
+ *
+ * Adds the image path, all its hash-* subnode paths, and its cipher and
+ * dm-verity subnode paths (each 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;
+
+ ret = fit_config_add_node(fit, image_noffset, node_inc, count,
+ max_nodes, buf, buf_used, buf_len);
+ if (ret)
+ return ret;
+
/* Add all this image's hash subnodes */
hash_count = 0;
for (noffset = fdt_first_subnode(fit, image_noffset);
@@ -273,15 +298,10 @@ static int fit_config_add_hash(const void *fit, int image_noffset,
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;
+ ret = fit_config_add_node(fit, noffset, node_inc, count,
+ max_nodes, buf, buf_used, buf_len);
+ if (ret)
+ return ret;
hash_count++;
}
@@ -296,26 +316,36 @@ static int fit_config_add_hash(const void *fit, int image_noffset,
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;
+ ret = fit_config_add_node(fit, noffset, node_inc, count,
+ max_nodes, buf, buf_used, buf_len);
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * Add this image's dm-verity node if present. Its roothash is the
+ * only integrity anchor for a dm-verity filesystem image, so it must
+ * be covered by the configuration signature.
+ */
+ noffset = fdt_subnode_offset(fit, image_noffset, FIT_VERITY_NODENAME);
+ if (noffset != -FDT_ERR_NOTFOUND) {
+ if (noffset < 0)
+ return -EIO;
+ ret = fit_config_add_node(fit, noffset, node_inc, count,
+ max_nodes, buf, buf_used, buf_len);
+ if (ret)
+ return ret;
}
return 0;
}
/**
- * fit_config_get_hash_list() - Build the list of nodes to hash
+ * fit_config_get_signed_nodes() - 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.
+ * node paths: root + config + all referenced images with their hash,
+ * cipher and dm-verity subnodes.
*
* Properties known not to be image references (description, compatible,
* default, load-only) are skipped, so any new image type is covered by default.
@@ -328,9 +358,9 @@ static int fit_config_add_hash(const void *fit, int image_noffset,
* @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)
+int fit_config_get_signed_nodes(const void *fit, int conf_noffset,
+ char **node_inc, int max_nodes,
+ char *buf, int buf_len)
{
const char *conf_name;
int image_count;
@@ -470,9 +500,9 @@ static int fit_config_check_sig(const void *fit, int noffset, int conf_noffset,
}
/* 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));
+ count = fit_config_get_signed_nodes(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;