summaryrefslogtreecommitdiff
path: root/tools/image-host.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/image-host.c')
-rw-r--r--tools/image-host.c414
1 files changed, 411 insertions, 3 deletions
diff --git a/tools/image-host.c b/tools/image-host.c
index 8b550af0dc1..8f1e7be4066 100644
--- a/tools/image-host.c
+++ b/tools/image-host.c
@@ -12,8 +12,12 @@
#include <bootm.h>
#include <fdt_region.h>
#include <image.h>
+#include <hexdump.h>
#include <version.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
#if CONFIG_IS_ENABLED(FIT_SIGNATURE)
#include <openssl/pem.h>
#include <openssl/evp.h>
@@ -626,6 +630,376 @@ int fit_image_cipher_data(const char *keydir, void *keydest,
image_noffset, cipher_node_offset, data, size, cmdname);
}
+/*
+ * In-memory cache of dm-verity expanded buffers (original data followed
+ * by the Merkle hash tree), keyed by image unit name. Populated by
+ * fit_image_process_verity() and consumed by fit_extract_data() /
+ * fit_copy_image_data() so that the expanded content never leaves the
+ * mkimage process address space.
+ */
+struct fit_verity_blob {
+ char *name;
+ void *data;
+ size_t size;
+ struct fit_verity_blob *next;
+};
+
+static struct fit_verity_blob *fit_verity_blobs;
+
+/* Stash a malloc'd expanded buffer; takes ownership of @data on success. */
+static int fit_verity_stash(const char *name, void *data, size_t size)
+{
+ struct fit_verity_blob *b;
+
+ b = calloc(1, sizeof(*b));
+ if (!b)
+ return -ENOMEM;
+ b->name = strdup(name);
+ if (!b->name) {
+ free(b);
+ return -ENOMEM;
+ }
+ b->data = data;
+ b->size = size;
+ b->next = fit_verity_blobs;
+ fit_verity_blobs = b;
+
+ return 0;
+}
+
+int fit_verity_get_expanded(const char *name, const void **data, size_t *size)
+{
+ struct fit_verity_blob *b;
+
+ for (b = fit_verity_blobs; b; b = b->next) {
+ if (!strcmp(b->name, name)) {
+ *data = b->data;
+ *size = b->size;
+ return 0;
+ }
+ }
+
+ return -ENOENT;
+}
+
+/**
+ * fit_image_process_verity() - Run veritysetup and fill dm-verity properties
+ *
+ * Extracts the embedded image data to a temporary file, runs
+ * ``veritysetup format`` to generate the Merkle hash tree (appended to the
+ * same file), parses Root hash / Salt from its stdout, and writes the
+ * computed properties (digest, salt, num-data-blocks, hash-start-block)
+ * back into the FIT dm-verity subnode.
+ *
+ * The expanded data (original data + hash tree) is read back into a
+ * malloc'd buffer and stashed in an in-memory cache keyed by @image_name
+ * via fit_verity_stash(). The same buffer is returned through
+ * @expanded_data / @expanded_size so that hash and signature subnodes
+ * can be computed over the complete image; the returned pointer is a
+ * *view* of the cached buffer and must not be freed by the caller.
+ * fit_extract_data() later retrieves the same buffer via
+ * fit_verity_get_expanded() to write the external data section.
+ *
+ * @fit: FIT blob (read-write)
+ * @image_name: image unit name (for diagnostics)
+ * @verity_noffset: dm-verity subnode offset
+ * @data: embedded image data
+ * @data_size: size of @data in bytes
+ * @expanded_data: output -- malloc'd buffer with expanded content
+ * @expanded_size: output -- size of @expanded_data
+ * Return: 0 on success, -ve on error (-ENOSPC when the FIT blob is full)
+ */
+static int fit_image_process_verity(void *fit, const char *image_name,
+ int verity_noffset,
+ const void *data, size_t data_size,
+ void **expanded_data, size_t *expanded_size)
+{
+ const char *algo_prop;
+ char algo[64];
+ const fdt32_t *val;
+ unsigned int data_block_size, hash_block_size;
+ uint32_t num_data_blocks;
+ size_t hash_offset;
+ uint32_t hash_start_block;
+ char tmpfile[] = "/tmp/mkimage-verity-XXXXXX";
+ char dbs_arg[32], hbs_arg[32], algo_arg[80], hoff_arg[40];
+ int pipefd[2];
+ pid_t pid;
+ FILE *fp;
+ char line[256];
+ char *colon, *value, *end;
+ char root_hash_hex[256] = {0};
+ char salt_hex[256] = {0};
+ uint8_t digest_bin[FIT_MAX_HASH_LEN];
+ uint8_t salt_bin[FIT_MAX_HASH_LEN];
+ int digest_len = 0, salt_len = 0;
+ void *expanded = NULL;
+ struct stat st;
+ int fd, ret;
+
+ *expanded_data = NULL;
+ *expanded_size = 0;
+
+ algo_prop = fdt_getprop(fit, verity_noffset, FIT_VERITY_ALGO_PROP,
+ NULL);
+ if (!algo_prop) {
+ fprintf(stderr,
+ "Missing '%s' in dm-verity node of '%s'\n",
+ FIT_VERITY_ALGO_PROP, image_name);
+ return -EINVAL;
+ }
+ /* Local copy -- the FDT pointer goes stale after fdt_setprop(). */
+ snprintf(algo, sizeof(algo), "%s", algo_prop);
+
+ val = fdt_getprop(fit, verity_noffset, FIT_VERITY_DBS_PROP, NULL);
+ if (!val) {
+ fprintf(stderr,
+ "Missing '%s' in dm-verity node of '%s'\n",
+ FIT_VERITY_DBS_PROP, image_name);
+ return -EINVAL;
+ }
+ data_block_size = fdt32_to_cpu(*val);
+
+ val = fdt_getprop(fit, verity_noffset, FIT_VERITY_HBS_PROP, NULL);
+ if (!val) {
+ fprintf(stderr,
+ "Missing '%s' in dm-verity node of '%s'\n",
+ FIT_VERITY_HBS_PROP, image_name);
+ return -EINVAL;
+ }
+ hash_block_size = fdt32_to_cpu(*val);
+
+ if (data_block_size < 512 || (data_block_size & (data_block_size - 1)) ||
+ hash_block_size < 512 || (hash_block_size & (hash_block_size - 1))) {
+ fprintf(stderr,
+ "Block sizes must be >= 512 and a power of two in dm-verity node of '%s'\n",
+ image_name);
+ return -EINVAL;
+ }
+
+ if (data_size % data_block_size) {
+ fprintf(stderr,
+ "Image '%s' size %zu not a multiple of data-block-size %d\n",
+ image_name, data_size, data_block_size);
+ return -EINVAL;
+ }
+
+ if (data_size / data_block_size > UINT32_MAX ||
+ data_size / hash_block_size > UINT32_MAX) {
+ fprintf(stderr,
+ "Image '%s' too large for dm-verity (> 2^32 blocks)\n",
+ image_name);
+ return -EINVAL;
+ }
+ num_data_blocks = data_size / data_block_size;
+ hash_offset = data_size;
+
+ fd = mkstemp(tmpfile);
+ if (fd < 0) {
+ fprintf(stderr, "Can't create temp file: %s\n",
+ strerror(errno));
+ return -EIO;
+ }
+
+ if (write(fd, data, data_size) != (ssize_t)data_size) {
+ fprintf(stderr, "Can't write temp file: %s\n",
+ strerror(errno));
+ ret = -EIO;
+ goto err_unlink;
+ }
+ close(fd);
+ fd = -1;
+
+ /*
+ * Invoke veritysetup via fork/execvp -- no shell, so each argument
+ * goes verbatim to the binary and the algo string cannot inject
+ * additional commands no matter how crafted the .its is.
+ */
+ snprintf(algo_arg, sizeof(algo_arg), "--hash=%s", algo);
+ snprintf(dbs_arg, sizeof(dbs_arg), "--data-block-size=%u",
+ data_block_size);
+ snprintf(hbs_arg, sizeof(hbs_arg), "--hash-block-size=%u",
+ hash_block_size);
+ snprintf(hoff_arg, sizeof(hoff_arg), "--hash-offset=%zu", hash_offset);
+
+ if (pipe(pipefd) < 0) {
+ fprintf(stderr, "Can't create pipe: %s\n", strerror(errno));
+ ret = -EIO;
+ goto err_unlink;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ fprintf(stderr, "Can't fork: %s\n", strerror(errno));
+ close(pipefd[0]);
+ close(pipefd[1]);
+ ret = -EIO;
+ goto err_unlink;
+ }
+
+ if (pid == 0) {
+ /* child: redirect stdout+stderr to pipe write-end, then exec */
+ char *argv[] = {
+ "veritysetup", "format", tmpfile, tmpfile,
+ "--no-superblock", algo_arg, dbs_arg, hbs_arg,
+ hoff_arg, NULL,
+ };
+
+ close(pipefd[0]);
+ if (dup2(pipefd[1], STDOUT_FILENO) < 0 ||
+ dup2(pipefd[1], STDERR_FILENO) < 0)
+ _exit(127);
+ close(pipefd[1]);
+ execvp(argv[0], argv);
+ fprintf(stderr, "Can't exec veritysetup: %s\n",
+ strerror(errno));
+ _exit(127);
+ }
+
+ /* parent: parse key: value lines from veritysetup stdout */
+ close(pipefd[1]);
+ fp = fdopen(pipefd[0], "r");
+ if (!fp) {
+ fprintf(stderr, "Can't fdopen veritysetup pipe: %s\n",
+ strerror(errno));
+ close(pipefd[0]);
+ waitpid(pid, NULL, 0);
+ ret = -EIO;
+ goto err_unlink;
+ }
+
+ while (fgets(line, sizeof(line), fp)) {
+ colon = strchr(line, ':');
+ if (!colon)
+ continue;
+ value = colon + 1;
+ while (*value == ' ' || *value == '\t')
+ value++;
+ end = value + strlen(value) - 1;
+ while (end > value && (*end == '\n' || *end == '\r' ||
+ *end == ' '))
+ *end-- = '\0';
+
+ if (!strncmp(line, "Root hash:", 10))
+ snprintf(root_hash_hex, sizeof(root_hash_hex),
+ "%s", value);
+ else if (!strncmp(line, "Salt:", 5))
+ snprintf(salt_hex, sizeof(salt_hex), "%s", value);
+ }
+ fclose(fp);
+
+ if (waitpid(pid, &ret, 0) < 0 || !WIFEXITED(ret) ||
+ WEXITSTATUS(ret) != 0) {
+ fprintf(stderr, "veritysetup failed for '%s'\n", image_name);
+ ret = -EIO;
+ goto err_unlink;
+ }
+
+ if (!root_hash_hex[0] || !salt_hex[0]) {
+ fprintf(stderr, "Failed to parse veritysetup output for '%s'\n",
+ image_name);
+ ret = -EIO;
+ goto err_unlink;
+ }
+
+ digest_len = strlen(root_hash_hex) / 2;
+ salt_len = strlen(salt_hex) / 2;
+
+ if (digest_len > (int)sizeof(digest_bin) ||
+ salt_len > (int)sizeof(salt_bin)) {
+ fprintf(stderr, "Hash/salt too long for '%s'\n", image_name);
+ ret = -EINVAL;
+ goto err_unlink;
+ }
+
+ if (hex2bin(digest_bin, root_hash_hex, digest_len) ||
+ hex2bin(salt_bin, salt_hex, salt_len)) {
+ fprintf(stderr, "Invalid hex in veritysetup output for '%s'\n",
+ image_name);
+ ret = -EINVAL;
+ goto err_unlink;
+ }
+
+ if (stat(tmpfile, &st)) {
+ fprintf(stderr, "Can't stat temp file: %s\n",
+ strerror(errno));
+ ret = -EIO;
+ goto err_unlink;
+ }
+
+ expanded = malloc(st.st_size);
+ if (!expanded) {
+ ret = -ENOMEM;
+ goto err_unlink;
+ }
+
+ fd = open(tmpfile, O_RDONLY);
+ if (fd < 0 || read(fd, expanded, st.st_size) != st.st_size) {
+ fprintf(stderr, "Can't read back temp file: %s\n",
+ strerror(errno));
+ ret = -EIO;
+ goto err_free;
+ }
+ close(fd);
+ fd = -1;
+
+ /* Temp file is no longer needed -- expanded buffer lives in memory. */
+ unlink(tmpfile);
+
+ /* hash tree starts immediately after data (no superblock) */
+ hash_start_block = hash_offset / hash_block_size;
+
+ ret = fdt_setprop(fit, verity_noffset, FIT_VERITY_DIGEST_PROP,
+ digest_bin, digest_len);
+ if (ret) {
+ ret = (ret == -FDT_ERR_NOSPACE) ? -ENOSPC : -EIO;
+ goto err_free;
+ }
+
+ ret = fdt_setprop(fit, verity_noffset, FIT_VERITY_SALT_PROP,
+ salt_bin, salt_len);
+ if (ret) {
+ ret = (ret == -FDT_ERR_NOSPACE) ? -ENOSPC : -EIO;
+ goto err_free;
+ }
+
+ ret = fdt_setprop_u32(fit, verity_noffset, FIT_VERITY_NBLK_PROP,
+ num_data_blocks);
+ if (ret) {
+ ret = (ret == -FDT_ERR_NOSPACE) ? -ENOSPC : -EIO;
+ goto err_free;
+ }
+
+ ret = fdt_setprop_u32(fit, verity_noffset, FIT_VERITY_HBLK_PROP,
+ hash_start_block);
+ if (ret) {
+ ret = (ret == -FDT_ERR_NOSPACE) ? -ENOSPC : -EIO;
+ goto err_free;
+ }
+
+ /*
+ * Stash the expanded buffer in the in-process cache; fit_extract_data()
+ * looks it up via fit_verity_get_expanded() to populate the external
+ * data section. On success the cache takes ownership of @expanded.
+ */
+ ret = fit_verity_stash(image_name, expanded, st.st_size);
+ if (ret)
+ goto err_free;
+
+ *expanded_data = expanded;
+ *expanded_size = st.st_size;
+
+ return 0;
+
+err_free:
+ free(expanded);
+err_unlink:
+ if (fd >= 0)
+ close(fd);
+ unlink(tmpfile);
+ return ret;
+}
+
/**
* fit_image_add_verification_data() - calculate/set verig. data for image node
*
@@ -652,6 +1026,8 @@ int fit_image_cipher_data(const char *keydir, void *keydest,
*
* For signature details, please see doc/usage/fit/signature.rst
*
+ * For dm-verity details, please see doc/usage/fit/dm-verity.rst
+ *
* @keydir Directory containing *.key and *.crt files (or NULL)
* @keydest FDT Blob to write public keys into (NULL if none)
* @fit: Pointer to the FIT format image header
@@ -667,9 +1043,16 @@ int fit_image_add_verification_data(const char *keydir, const char *keyfile,
const char *cmdname, const char* algo_name)
{
const char *image_name;
+ const char *node_name;
const void *data;
size_t size;
+ /*
+ * View pointer into the dm-verity cache (owned by image-host.c).
+ * Do not free; the cache lives until mkimage exits.
+ */
+ void *verity_data = NULL;
int noffset;
+ int ret;
/* Get image data and data length */
if (fit_image_get_emb_data(fit, image_noffset, &data, &size)) {
@@ -679,13 +1062,38 @@ int fit_image_add_verification_data(const char *keydir, const char *keyfile,
image_name = fit_get_name(fit, image_noffset, NULL);
- /* Process all hash subnodes of the component image node */
+ /*
+ * Pass 1 -- dm-verity: run veritysetup to produce the Merkle
+ * hash tree and fill in computed metadata. The expanded
+ * content (original data + hash tree) is returned in
+ * verity_data so that pass 2 hashes the complete image.
+ */
for (noffset = fdt_first_subnode(fit, image_noffset);
noffset >= 0;
noffset = fdt_next_subnode(fit, noffset)) {
- const char *node_name;
- int ret = 0;
+ if (!strcmp(fit_get_name(fit, noffset, NULL),
+ FIT_VERITY_NODENAME)) {
+ ret = fit_image_process_verity(fit, image_name,
+ noffset,
+ data, size,
+ &verity_data,
+ &size);
+ if (ret)
+ return ret;
+ if (verity_data)
+ data = verity_data;
+ break;
+ }
+ }
+ /*
+ * Pass 2 -- hashes and signatures: compute over the (possibly
+ * expanded) image data.
+ */
+ for (noffset = fdt_first_subnode(fit, image_noffset);
+ noffset >= 0;
+ noffset = fdt_next_subnode(fit, noffset)) {
+ ret = 0;
/*
* Check subnode name, must be equal to "hash" or "signature".
* Multiple hash nodes require unique unit node