An image node may carry an 'image-data' property naming another image node under /images whose binary data it shares. This lets several configurations load the same payload at different addresses without storing it twice, as described in the 'Shared image data' section of the Flat Image Tree specification [1].
Resolve the references in mkimage after any external data has been imported: the target's data is copied into the referencing node, so the later ciphering, hashing and signing steps see a normal image node. The property itself is retained in the resolved FIT as a record of the sharing relationship. When mkimage re-processes a FIT that already contains resolved references, the node's data must match the target's data exactly. A reference is rejected when the target does not exist, when the target itself carries an image-data property (chained references), when the node's existing data disagrees with the target, or when the node mixes image-data with external data properties. Since encryption happens per node, sharing encrypted data is only accepted when both nodes use the same algorithm and key together with the same iv-name-hint or, when neither uses a hint, the same explicit iv; anything else, including the independent random IV generated when a node provides no IV at all, would produce different ciphertexts. [1] https://github.com/open-source-firmware/flat-image-tree Signed-off-by: Aristo Chen <[email protected]> --- include/image.h | 1 + tools/fit_image.c | 286 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 287 insertions(+) diff --git a/include/image.h b/include/image.h index 11af6f1d522..4d621815f59 100644 --- a/include/image.h +++ b/include/image.h @@ -1180,6 +1180,7 @@ int booti_setup(ulong image, ulong *relocated_addr, ulong *size, #define FIT_DATA_POSITION_PROP "data-position" #define FIT_DATA_OFFSET_PROP "data-offset" #define FIT_DATA_SIZE_PROP "data-size" +#define FIT_IMAGE_DATA_PROP "image-data" #define FIT_TIMESTAMP_PROP "timestamp" #define FIT_DESC_PROP "description" #define FIT_ARCH_PROP "arch" diff --git a/tools/fit_image.c b/tools/fit_image.c index f7d11904472..de9f73ace3f 100644 --- a/tools/fit_image.c +++ b/tools/fit_image.c @@ -1169,6 +1169,287 @@ err: return ret; } +/** + * fit_props_equal() - Check whether a property is present and equal on + * two nodes + */ +static bool fit_props_equal(const void *fdt, int node_a, int node_b, + const char *prop) +{ + const void *a, *b; + int len_a, len_b; + + a = fdt_getprop(fdt, node_a, prop, &len_a); + b = fdt_getprop(fdt, node_b, prop, &len_b); + + return a && b && len_a == len_b && !memcmp(a, b, len_a); +} + +/** + * fit_check_shared_cipher() - Check cipher settings of images sharing data + * + * Two image nodes can only share binary data if their cipher settings + * produce identical ciphertext: either neither is encrypted, or both + * use the same algorithm, key and IV. The IV must be shared explicitly, + * through the same iv-name-hint or, when neither node uses a hint, the + * same iv value; anything else, including the independent random IV + * generated when a node provides no IV at all, would make the + * ciphertexts differ. A hint drives the encryption whenever it is + * present, so in that case the hints themselves have to match. + */ +static int fit_check_shared_cipher(const void *fdt, int node, int target, + const char *name, const char *ref) +{ + int ncipher, tcipher; + + ncipher = fdt_subnode_offset(fdt, node, FIT_CIPHER_NODENAME); + tcipher = fdt_subnode_offset(fdt, target, FIT_CIPHER_NODENAME); + if (ncipher < 0 && tcipher < 0) + return 0; + + if ((ncipher < 0) != (tcipher < 0)) { + fprintf(stderr, + "Error: image '%s' shares data with image '%s' but only one of them is encrypted\n", + name, ref); + return -EINVAL; + } + if (!fit_props_equal(fdt, ncipher, tcipher, FIT_ALGO_PROP) || + !fit_props_equal(fdt, ncipher, tcipher, FIT_KEY_HINT)) { + fprintf(stderr, + "Error: image '%s' shares data with image '%s' but their cipher settings differ\n", + name, ref); + return -EINVAL; + } + if ((fdt_getprop(fdt, node, "data-size-unciphered", NULL) || + fdt_getprop(fdt, target, "data-size-unciphered", NULL)) && + !fit_props_equal(fdt, node, target, "data-size-unciphered")) { + fprintf(stderr, + "Error: image '%s' shares data with image '%s' but data-size-unciphered differs\n", + name, ref); + return -EINVAL; + } + + if (fdt_getprop(fdt, ncipher, "iv-name-hint", NULL) || + fdt_getprop(fdt, tcipher, "iv-name-hint", NULL)) { + if (!fit_props_equal(fdt, ncipher, tcipher, "iv-name-hint")) { + fprintf(stderr, + "Error: image '%s' shares encrypted data with image '%s'; both must use the same iv-name-hint\n", + name, ref); + return -EINVAL; + } + return 0; + } + if (!fit_props_equal(fdt, ncipher, tcipher, "iv")) { + fprintf(stderr, + "Error: image '%s' shares encrypted data with image '%s'; both must use the same iv-name-hint or iv\n", + name, ref); + return -EINVAL; + } + + return 0; +} + +/** + * fit_resolve_image_data() - Resolve image-data references between images + * + * An image node may carry an 'image-data' property naming another image + * node under /images whose binary data it shares. Copy the referenced + * node's data into the referencing node so that later steps (ciphering, + * hashing, signing and external-data extraction) see a normal image + * node. The property itself is retained in the FIT as a record of the + * sharing relationship. + * + * If the referencing node already has data (for example when mkimage + * re-processes a FIT it produced earlier), the data must match the + * target's data exactly; anything else means the FIT is malformed. + */ +static int fit_resolve_image_data(struct image_tool_params *params, + const char *fname) +{ + void *fdt = NULL, *old_fdt; + int new_size, size, extra; + int fd; + struct stat sbuf; + bool found = false; + int ret; + int images; + int node; + + fd = mmap_fdt(params->cmdname, fname, 0, &old_fdt, &sbuf, false, false); + if (fd < 0) + return -EIO; + + images = fdt_path_offset(old_fdt, FIT_IMAGES_PATH); + if (images < 0) { + debug("%s: Cannot find /images node: %d\n", __func__, images); + ret = -EINVAL; + goto err_munmap; + } + + /* + * Work out roughly how much larger the FIT will get, and skip the + * rewrite in the common case of no references. References that do + * not resolve are left for the loop below to report. + */ + extra = 0; + fdt_for_each_subnode(node, old_fdt, images) { + const char *ref; + int target, len; + + ref = fdt_getprop(old_fdt, node, FIT_IMAGE_DATA_PROP, NULL); + if (!ref) + continue; + found = true; + target = fdt_subnode_offset(old_fdt, images, ref); + if (target >= 0 && + fdt_getprop(old_fdt, target, FIT_DATA_PROP, &len)) + extra += len + 64; + } + if (!found) { + munmap(old_fdt, sbuf.st_size); + close(fd); + return 0; + } + + /* Allocate space to hold the enlarged FIT */ + size = sbuf.st_size + extra + 1024; + fdt = calloc(1, size); + if (!fdt) { + fprintf(stderr, "%s: Failed to allocate memory (%d bytes)\n", + __func__, size); + ret = -ENOMEM; + goto err_munmap; + } + ret = fdt_open_into(old_fdt, fdt, size); + if (ret) { + debug("%s: Failed to expand FIT: %s\n", __func__, + fdt_strerror(ret)); + ret = -EINVAL; + goto err_munmap; + } + + images = fdt_path_offset(fdt, FIT_IMAGES_PATH); + fdt_for_each_subnode(node, fdt, images) { + const char *name = fdt_get_name(fdt, node, NULL); + const char *ref, *data, *tdata; + void *copy; + int target, len, tlen; + + ref = fdt_getprop(fdt, node, FIT_IMAGE_DATA_PROP, NULL); + if (!ref) + continue; + + target = fdt_subnode_offset(fdt, images, ref); + if (target < 0) { + fprintf(stderr, + "Error: image '%s': image-data references undefined image '%s'\n", + name, ref); + ret = -EINVAL; + goto err_munmap; + } + if (fdt_getprop(fdt, target, FIT_IMAGE_DATA_PROP, NULL)) { + fprintf(stderr, + "Error: image '%s': image-data target '%s' has an image-data property itself; chained references are not permitted\n", + name, ref); + ret = -EINVAL; + goto err_munmap; + } + tdata = fdt_getprop(fdt, target, FIT_DATA_PROP, &tlen); + if (!tdata) { + fprintf(stderr, + "Error: image '%s': image-data target '%s' has no data\n", + name, ref); + ret = -EINVAL; + goto err_munmap; + } + + ret = fit_check_shared_cipher(fdt, node, target, name, ref); + if (ret) + goto err_munmap; + + data = fdt_getprop(fdt, node, FIT_DATA_PROP, &len); + if (data) { + if (len != tlen || memcmp(data, tdata, len)) { + fprintf(stderr, + "Error: image '%s': data does not match image-data target '%s'\n", + name, ref); + ret = -EINVAL; + goto err_munmap; + } + /* Already resolved */ + continue; + } + if (fdt_getprop(fdt, node, FIT_DATA_OFFSET_PROP, NULL) || + fdt_getprop(fdt, node, FIT_DATA_POSITION_PROP, NULL) || + fdt_getprop(fdt, node, FIT_DATA_SIZE_PROP, NULL)) { + fprintf(stderr, + "Error: image '%s': image-data must not be combined with external data properties\n", + name); + ret = -EINVAL; + goto err_munmap; + } + + /* + * Copy via a temporary buffer since fdt_setprop() moves the + * tree, including the target's data, while it works + */ + copy = malloc(tlen); + if (!copy) { + fprintf(stderr, + "%s: Failed to allocate memory (%d bytes)\n", + __func__, tlen); + ret = -ENOMEM; + goto err_munmap; + } + memcpy(copy, tdata, tlen); + ret = fdt_setprop(fdt, node, FIT_DATA_PROP, copy, tlen); + free(copy); + if (ret) { + debug("%s: Failed to write property: %s\n", __func__, + fdt_strerror(ret)); + ret = -EINVAL; + goto err_munmap; + } + debug("Resolved image-data of '%s' from '%s', size %x\n", + name, ref, tlen); + } + + munmap(old_fdt, sbuf.st_size); + + /* Close the old fd so we can re-use it. */ + close(fd); + + fdt_pack(fdt); + + new_size = fdt_totalsize(fdt); + debug("Size expanded from %x to %x\n", (int)sbuf.st_size, new_size); + + fd = open(fname, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0666); + if (fd < 0) { + fprintf(stderr, "%s: Can't open %s: %s\n", + params->cmdname, fname, strerror(errno)); + ret = -EIO; + goto err; + } + if (write(fd, fdt, new_size) != new_size) { + debug("%s: Failed to write FIT to file %s\n", __func__, + strerror(errno)); + ret = -EIO; + goto err; + } + + free(fdt); + close(fd); + return 0; + +err_munmap: + munmap(old_fdt, sbuf.st_size); +err: + free(fdt); + close(fd); + return ret; +} + /** * fit_handle_file - main FIT file processing function * @@ -1234,6 +1515,11 @@ static int fit_handle_file(struct image_tool_params *params) if (ret) goto err_system; + /* Resolve image-data references between images, if any */ + ret = fit_resolve_image_data(params, tmpfile); + if (ret) + goto err_system; + /* * Copy the tmpfile to bakfile, then in the following loop * we copy bakfile to tmpfile. So we always start from the -- 2.43.0
