mirror of
https://source.denx.de/u-boot/u-boot.git
synced 2026-06-02 09:46:37 +03:00
Merge patch series "fit: dm-verity support"
Daniel Golle <daniel@makrotopia.org> says:
This series adds dm-verity support to U-Boot's FIT image infrastructure.
It is the first logical subset of the larger OpenWrt boot method series
posted as an RFC in February 2026 [1], extracted here for independent
review and merging.
OpenWrt's firmware model embeds a read-only squashfs or erofs root
filesystem directly inside a uImage.FIT container as a FILESYSTEM-type
loadable FIT image. At boot the kernel maps this sub-image directly from
the underlying block device via the fitblk driver (/dev/fit0, /dev/fit1,
...), the goal is that the bootloader never even copies it to RAM.
dm-verity enables the kernel to verify the integrity of those mapped
filesystems at read time, with a Merkle hash tree stored contiguously in
the same sub-image just after the data. Two kernel command-line
parameters are required:
dm-mod.create= -- the device-mapper target table for the verity device
dm-mod.waitfor= -- a comma-separated list of block devices to wait for
before dm-init sets up the targets (needed when fitblk
probes late, e.g. because it depends on NVMEM
calibration data)
The FIT dm-verity node schema was upstreamed into the flat-image-tree
specification [2], which this implementation tries to follow exactly.
The runtime feature is guarded behind CONFIG_FIT_VERITY. If not
enabled the resulting binary size remains unchanged. If enabled the
binary size increases by about 3kB.
[1] previous submissions:
RFC: https://www.mail-archive.com/u-boot@lists.denx.de/msg565945.html
v1: https://www.mail-archive.com/u-boot@lists.denx.de/msg569472.html
v2: https://www.mail-archive.com/u-boot@lists.denx.de/msg570599.html
v3: https://www.mail-archive.com/u-boot@lists.denx.de/msg573223.html
v4: https://www.mail-archive.com/u-boot@lists.denx.de/msg574000.html
[2] flat-image-tree dm-verity node spec:
795fd5fd7f
Link: https://lore.kernel.org/r/cover.1778887196.git.daniel@makrotopia.org
This commit is contained in:
20
boot/Kconfig
20
boot/Kconfig
@@ -142,6 +142,26 @@ config FIT_CIPHER
|
||||
Enable the feature of data ciphering/unciphering in the tool mkimage
|
||||
and in the u-boot support of the FIT image.
|
||||
|
||||
config FIT_VERITY
|
||||
bool "dm-verity boot parameter generation from FIT metadata"
|
||||
depends on FIT && OF_LIBFDT
|
||||
help
|
||||
When a FIT configuration contains loadable sub-images of type
|
||||
IH_TYPE_FILESYSTEM with a dm-verity subnode, this option enables
|
||||
building the dm-mod.create= and dm-mod.waitfor= kernel
|
||||
command-line parameters from the verity metadata
|
||||
(data-block-size, hash-block-size, num-data-blocks,
|
||||
hash-start-block, algorithm, digest, salt) stored in the FIT.
|
||||
|
||||
The generated parameters reference /dev/fitN block devices that
|
||||
Linux's uImage.FIT block driver assigns to loadable sub-images.
|
||||
|
||||
During FIT parsing (BOOTM_STATE_FINDOTHER), verity cmdline
|
||||
fragments are stored in struct bootm_headers and automatically
|
||||
appended to the bootargs environment variable during
|
||||
BOOTM_STATE_OS_PREP. This works from both the bootm command
|
||||
and BOOTSTD bootmeths.
|
||||
|
||||
config FIT_VERBOSE
|
||||
bool "Show verbose messages when FIT images fail"
|
||||
help
|
||||
|
||||
13
boot/bootm.c
13
boot/bootm.c
@@ -243,6 +243,13 @@ static int boot_get_kernel(const char *addr_fit, struct bootm_headers *images,
|
||||
|
||||
static int bootm_start(void)
|
||||
{
|
||||
/*
|
||||
* Free dm-verity allocations from a prior boot attempt before
|
||||
* zeroing the structure. The pointers are guaranteed to be valid
|
||||
* or NULL: .bss is zero-initialised, and memset() below zeroes
|
||||
* them again after every boot.
|
||||
*/
|
||||
fit_verity_free(&images);
|
||||
memset((void *)&images, 0, sizeof(images));
|
||||
images.verify = env_get_yesno("verify");
|
||||
|
||||
@@ -1071,6 +1078,12 @@ int bootm_run_states(struct bootm_info *bmi, int states)
|
||||
/* For Linux OS do all substitutions at console processing */
|
||||
if (images->os.os == IH_OS_LINUX)
|
||||
flags = BOOTM_CL_ALL;
|
||||
ret = fit_verity_apply_bootargs(images);
|
||||
if (ret) {
|
||||
printf("dm-verity bootargs failed (err=%d)\n", ret);
|
||||
ret = CMD_RET_FAILURE;
|
||||
goto err;
|
||||
}
|
||||
ret = bootm_process_cmdline_env(flags);
|
||||
if (ret) {
|
||||
printf("Cmdline setup failed (err=%d)\n", ret);
|
||||
|
||||
@@ -810,6 +810,11 @@ int boot_get_loadable(struct bootm_headers *images)
|
||||
|
||||
fit_loadable_process(img_type, img_data, img_len);
|
||||
}
|
||||
|
||||
fit_img_result = fit_verity_build_cmdline(buf, conf_noffset,
|
||||
images);
|
||||
if (fit_img_result < 0)
|
||||
return fit_img_result;
|
||||
break;
|
||||
default:
|
||||
printf("The given image format is not supported (corrupt?)\n");
|
||||
|
||||
337
boot/image-fit.c
337
boot/image-fit.c
@@ -21,8 +21,11 @@
|
||||
extern void *aligned_alloc(size_t alignment, size_t size);
|
||||
#else
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <env.h>
|
||||
#include <errno.h>
|
||||
#include <hexdump.h>
|
||||
#include <log.h>
|
||||
#include <mapmem.h>
|
||||
#include <asm/io.h>
|
||||
@@ -235,6 +238,39 @@ static void fit_image_print_data(const void *fit, int noffset, const char *p,
|
||||
}
|
||||
}
|
||||
|
||||
static __maybe_unused void fit_image_print_dm_verity(const void *fit,
|
||||
int noffset,
|
||||
const char *p)
|
||||
{
|
||||
#if defined(USE_HOSTCC) || CONFIG_IS_ENABLED(FIT_VERITY)
|
||||
const char *algo;
|
||||
const uint8_t *bin;
|
||||
int len, i;
|
||||
|
||||
algo = fdt_getprop(fit, noffset, FIT_VERITY_ALGO_PROP, NULL);
|
||||
if (algo)
|
||||
printf("%s Verity algo: %s\n", p, algo);
|
||||
|
||||
bin = fdt_getprop(fit, noffset, FIT_VERITY_DIGEST_PROP,
|
||||
&len);
|
||||
if (bin && len > 0) {
|
||||
printf("%s Verity hash: ", p);
|
||||
for (i = 0; i < len; i++)
|
||||
printf("%02x", bin[i]);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
bin = fdt_getprop(fit, noffset, FIT_VERITY_SALT_PROP,
|
||||
&len);
|
||||
if (bin && len > 0) {
|
||||
printf("%s Verity salt: ", p);
|
||||
for (i = 0; i < len; i++)
|
||||
printf("%02x", bin[i]);
|
||||
printf("\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* fit_image_print_verification_data() - prints out the hash/signature details
|
||||
* @fit: pointer to the FIT format image header
|
||||
@@ -263,6 +299,11 @@ static void fit_image_print_verification_data(const void *fit, int noffset,
|
||||
strlen(FIT_SIG_NODENAME))) {
|
||||
fit_image_print_data(fit, noffset, p, "Sign");
|
||||
}
|
||||
#if defined(USE_HOSTCC) || CONFIG_IS_ENABLED(FIT_VERITY)
|
||||
else if (!strcmp(name, FIT_VERITY_NODENAME)) {
|
||||
fit_image_print_dm_verity(fit, noffset, p);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2589,3 +2630,299 @@ out:
|
||||
return fdt_noffset;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(USE_HOSTCC) && CONFIG_IS_ENABLED(FIT_VERITY)
|
||||
|
||||
static const char *const verity_opt_props[] = {
|
||||
FIT_VERITY_OPT_RESTART,
|
||||
FIT_VERITY_OPT_PANIC,
|
||||
FIT_VERITY_OPT_RERR,
|
||||
FIT_VERITY_OPT_PERR,
|
||||
FIT_VERITY_OPT_ONCE,
|
||||
};
|
||||
|
||||
/**
|
||||
* fit_verity_build_target() - build one dm-verity target specification
|
||||
* @fit: pointer to the FIT blob
|
||||
* @img_noffset: image node offset containing the dm-verity subnode
|
||||
* @loadable_idx: index of this loadable (for /dev/fitN)
|
||||
* @uname: unit name of the image
|
||||
* @separator: true if a ";" prefix is needed (not the first target)
|
||||
* @buf: output buffer, or NULL to measure only
|
||||
* @bufsize: size of @buf (ignored when @buf is NULL)
|
||||
*
|
||||
* Parses all dm-verity properties from the image's ``dm-verity`` child
|
||||
* node and writes (or measures) a dm target specification string of the
|
||||
* form used by the ``dm-mod.create`` kernel parameter.
|
||||
*
|
||||
* Return: number of characters that would be written (excluding '\0'),
|
||||
* or -ve errno on error (e.g. missing mandatory property)
|
||||
*/
|
||||
static int fit_verity_build_target(const void *fit, int img_noffset,
|
||||
int loadable_idx, const char *uname,
|
||||
bool separator, char *buf, int bufsize)
|
||||
{
|
||||
const char *algorithm;
|
||||
const u8 *digest_raw, *salt_raw;
|
||||
const fdt32_t *val;
|
||||
char *digest_hex = NULL, *salt_hex = NULL, *opt_buf = NULL;
|
||||
int verity_node;
|
||||
unsigned int data_block_size, hash_block_size;
|
||||
int num_data_blocks, hash_start_block;
|
||||
u64 data_sectors;
|
||||
int digest_len, salt_len;
|
||||
int opt_count, opt_off, opt_buf_size;
|
||||
int len;
|
||||
int i;
|
||||
|
||||
verity_node = fdt_subnode_offset(fit, img_noffset, FIT_VERITY_NODENAME);
|
||||
if (verity_node < 0)
|
||||
return -ENOENT;
|
||||
|
||||
/* Mandatory u32 properties */
|
||||
val = fdt_getprop(fit, verity_node, FIT_VERITY_DBS_PROP, NULL);
|
||||
if (!val)
|
||||
return -EINVAL;
|
||||
data_block_size = fdt32_to_cpu(*val);
|
||||
|
||||
val = fdt_getprop(fit, verity_node, FIT_VERITY_HBS_PROP, NULL);
|
||||
if (!val)
|
||||
return -EINVAL;
|
||||
hash_block_size = fdt32_to_cpu(*val);
|
||||
|
||||
val = fdt_getprop(fit, verity_node, FIT_VERITY_NBLK_PROP, NULL);
|
||||
if (!val)
|
||||
return -EINVAL;
|
||||
num_data_blocks = fdt32_to_cpu(*val);
|
||||
|
||||
val = fdt_getprop(fit, verity_node, FIT_VERITY_HBLK_PROP, NULL);
|
||||
if (!val)
|
||||
return -EINVAL;
|
||||
hash_start_block = fdt32_to_cpu(*val);
|
||||
|
||||
if (data_block_size < 512U || !is_power_of_2(data_block_size) ||
|
||||
hash_block_size < 512U || !is_power_of_2(hash_block_size) ||
|
||||
!num_data_blocks)
|
||||
return -EINVAL;
|
||||
|
||||
/* Mandatory string */
|
||||
algorithm = fdt_getprop(fit, verity_node, FIT_VERITY_ALGO_PROP, NULL);
|
||||
if (!algorithm)
|
||||
return -EINVAL;
|
||||
|
||||
/* Mandatory byte arrays */
|
||||
digest_raw = fdt_getprop(fit, verity_node, FIT_VERITY_DIGEST_PROP,
|
||||
&digest_len);
|
||||
if (!digest_raw || digest_len <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
salt_raw = fdt_getprop(fit, verity_node, FIT_VERITY_SALT_PROP,
|
||||
&salt_len);
|
||||
if (!salt_raw || salt_len <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* Hex-encode digest and salt into dynamically sized buffers */
|
||||
digest_hex = malloc(digest_len * 2 + 1);
|
||||
salt_hex = malloc(salt_len * 2 + 1);
|
||||
if (!digest_hex || !salt_hex) {
|
||||
len = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
*bin2hex(digest_hex, digest_raw, digest_len) = '\0';
|
||||
*bin2hex(salt_hex, salt_raw, salt_len) = '\0';
|
||||
|
||||
data_sectors = (u64)num_data_blocks * ((u64)data_block_size / 512);
|
||||
|
||||
/* Compute space needed for optional boolean properties */
|
||||
opt_buf_size = 1; /* NUL terminator */
|
||||
for (i = 0; i < ARRAY_SIZE(verity_opt_props); i++)
|
||||
opt_buf_size += strlen(verity_opt_props[i]) + 1;
|
||||
opt_buf = malloc(opt_buf_size);
|
||||
if (!opt_buf) {
|
||||
len = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Collect optional boolean properties */
|
||||
opt_count = 0;
|
||||
opt_off = 0;
|
||||
opt_buf[0] = '\0';
|
||||
for (i = 0; i < ARRAY_SIZE(verity_opt_props); i++) {
|
||||
if (fdt_getprop(fit, verity_node,
|
||||
verity_opt_props[i], NULL)) {
|
||||
const char *s = verity_opt_props[i];
|
||||
int slen = strlen(s);
|
||||
|
||||
if (opt_off)
|
||||
opt_buf[opt_off++] = ' ';
|
||||
/* Copy with hyphen-to-underscore conversion */
|
||||
while (slen-- > 0) {
|
||||
opt_buf[opt_off++] =
|
||||
(*s == '-') ? '_' : *s;
|
||||
s++;
|
||||
}
|
||||
opt_buf[opt_off] = '\0';
|
||||
opt_count++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Emit (or measure) the target spec */
|
||||
len = snprintf(buf, buf ? bufsize : 0,
|
||||
"%s%s,,, ro,0 %llu verity 1 /dev/fit%d /dev/fit%d %u %u %d %d %s %s %s",
|
||||
separator ? ";" : "", uname,
|
||||
(unsigned long long)data_sectors, loadable_idx, loadable_idx,
|
||||
data_block_size, hash_block_size,
|
||||
num_data_blocks, hash_start_block,
|
||||
algorithm, digest_hex, salt_hex);
|
||||
if (opt_count) {
|
||||
int extra = snprintf(buf ? buf + len : NULL,
|
||||
buf ? bufsize - len : 0,
|
||||
" %d %s", opt_count, opt_buf);
|
||||
len += extra;
|
||||
}
|
||||
|
||||
out:
|
||||
free(digest_hex);
|
||||
free(salt_hex);
|
||||
free(opt_buf);
|
||||
return len;
|
||||
}
|
||||
|
||||
int fit_verity_build_cmdline(const void *fit, int conf_noffset,
|
||||
struct bootm_headers *images)
|
||||
{
|
||||
int images_noffset;
|
||||
int dm_create_len = 0, dm_waitfor_len = 0;
|
||||
char *dm_create = NULL, *dm_waitfor = NULL;
|
||||
const char *uname;
|
||||
int loadable_idx;
|
||||
int found = 0;
|
||||
int ret = 0;
|
||||
|
||||
images_noffset = fdt_path_offset(fit, FIT_IMAGES_PATH);
|
||||
if (images_noffset < 0)
|
||||
return 0;
|
||||
|
||||
for (loadable_idx = 0;
|
||||
(uname = fdt_stringlist_get(fit, conf_noffset,
|
||||
FIT_LOADABLE_PROP,
|
||||
loadable_idx, NULL));
|
||||
loadable_idx++) {
|
||||
int img_noffset, need;
|
||||
u8 img_type;
|
||||
char *tmp;
|
||||
|
||||
img_noffset = fdt_subnode_offset(fit, images_noffset, uname);
|
||||
if (img_noffset < 0)
|
||||
continue;
|
||||
|
||||
if (fit_image_get_type(fit, img_noffset, &img_type) ||
|
||||
img_type != IH_TYPE_FILESYSTEM)
|
||||
continue;
|
||||
|
||||
/* Measure first, then allocate and write */
|
||||
need = fit_verity_build_target(fit, img_noffset,
|
||||
loadable_idx, uname,
|
||||
found > 0, NULL, 0);
|
||||
if (need == -ENOENT)
|
||||
continue; /* no dm-verity subnode -- fine */
|
||||
if (need < 0) {
|
||||
log_err("FIT: broken dm-verity metadata in '%s'\n",
|
||||
uname);
|
||||
ret = need;
|
||||
goto err;
|
||||
}
|
||||
|
||||
tmp = realloc(dm_create, dm_create_len + need + 1);
|
||||
if (!tmp) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
dm_create = tmp;
|
||||
fit_verity_build_target(fit, img_noffset, loadable_idx,
|
||||
uname, found > 0,
|
||||
dm_create + dm_create_len,
|
||||
need + 1);
|
||||
dm_create_len += need;
|
||||
|
||||
/* Grow dm_waitfor buffer */
|
||||
need = snprintf(NULL, 0, "%s/dev/fit%d",
|
||||
dm_waitfor_len ? "," : "",
|
||||
loadable_idx);
|
||||
tmp = realloc(dm_waitfor, dm_waitfor_len + need + 1);
|
||||
if (!tmp) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
dm_waitfor = tmp;
|
||||
sprintf(dm_waitfor + dm_waitfor_len, "%s/dev/fit%d",
|
||||
dm_waitfor_len ? "," : "",
|
||||
loadable_idx);
|
||||
dm_waitfor_len += need;
|
||||
|
||||
found++;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
/* Transfer ownership to the bootm_headers */
|
||||
images->dm_mod_create = dm_create;
|
||||
images->dm_mod_waitfor = dm_waitfor;
|
||||
} else {
|
||||
free(dm_create);
|
||||
free(dm_waitfor);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
free(dm_create);
|
||||
free(dm_waitfor);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* fmt used by both the measurement and the actual write of bootargs.
|
||||
* Shared to guarantee they stay in sync.
|
||||
*/
|
||||
#define VERITY_BOOTARGS_FMT "%s%sdm-mod.create=\"%s\" dm-mod.waitfor=\"%s\""
|
||||
|
||||
int fit_verity_apply_bootargs(const struct bootm_headers *images)
|
||||
{
|
||||
const char *existing;
|
||||
char *newargs;
|
||||
int len;
|
||||
|
||||
if (!images->dm_mod_create)
|
||||
return 0;
|
||||
|
||||
existing = env_get("bootargs");
|
||||
if (!existing)
|
||||
existing = "";
|
||||
|
||||
/* Measure */
|
||||
len = snprintf(NULL, 0, VERITY_BOOTARGS_FMT,
|
||||
existing, existing[0] ? " " : "",
|
||||
images->dm_mod_create, images->dm_mod_waitfor);
|
||||
|
||||
newargs = malloc(len + 1);
|
||||
if (!newargs)
|
||||
return -ENOMEM;
|
||||
|
||||
snprintf(newargs, len + 1, VERITY_BOOTARGS_FMT,
|
||||
existing, existing[0] ? " " : "",
|
||||
images->dm_mod_create, images->dm_mod_waitfor);
|
||||
|
||||
env_set("bootargs", newargs);
|
||||
free(newargs);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void fit_verity_free(struct bootm_headers *images)
|
||||
{
|
||||
free(images->dm_mod_create);
|
||||
free(images->dm_mod_waitfor);
|
||||
images->dm_mod_create = NULL;
|
||||
images->dm_mod_waitfor = NULL;
|
||||
}
|
||||
#endif /* FIT_VERITY */
|
||||
|
||||
@@ -18,6 +18,7 @@ CONFIG_EFI_RT_VOLATILE_STORE=y
|
||||
CONFIG_BUTTON_CMD=y
|
||||
CONFIG_FIT=y
|
||||
CONFIG_FIT_SIGNATURE=y
|
||||
CONFIG_FIT_VERITY=y
|
||||
CONFIG_FIT_VERBOSE=y
|
||||
CONFIG_LEGACY_IMAGE_FORMAT=y
|
||||
CONFIG_BOOTSTAGE=y
|
||||
|
||||
@@ -22,6 +22,7 @@ CONFIG_BUTTON_CMD=y
|
||||
CONFIG_FIT=y
|
||||
CONFIG_FIT_SIGNATURE=y
|
||||
CONFIG_FIT_CIPHER=y
|
||||
CONFIG_FIT_VERITY=y
|
||||
CONFIG_FIT_VERBOSE=y
|
||||
CONFIG_BOOTMETH_ANDROID=y
|
||||
CONFIG_BOOTMETH_RAUC=y
|
||||
|
||||
@@ -14,6 +14,7 @@ CONFIG_EFI_CAPSULE_CRT_FILE="board/sandbox/capsule_pub_key_good.crt"
|
||||
CONFIG_BUTTON_CMD=y
|
||||
CONFIG_FIT=y
|
||||
CONFIG_FIT_SIGNATURE=y
|
||||
CONFIG_FIT_VERITY=y
|
||||
CONFIG_FIT_VERBOSE=y
|
||||
CONFIG_LEGACY_IMAGE_FORMAT=y
|
||||
CONFIG_BOOTSTAGE=y
|
||||
|
||||
304
doc/usage/fit/dm-verity.rst
Normal file
304
doc/usage/fit/dm-verity.rst
Normal file
@@ -0,0 +1,304 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
FIT dm-verity Boot Parameters
|
||||
=============================
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Linux's dm-verity device-mapper target provides transparent integrity
|
||||
checking of block devices using a Merkle tree. It is commonly used to
|
||||
protect read-only root filesystems such as SquashFS images.
|
||||
|
||||
When a FIT image packages the root filesystem as a loadable sub-image of
|
||||
type ``filesystem`` (``IH_TYPE_FILESYSTEM``), the verity metadata can be
|
||||
stored alongside the image data in a ``dm-verity`` subnode. U-Boot reads
|
||||
this metadata at boot time and generates the kernel command-line parameters
|
||||
that Linux needs to activate the verity target, eliminating the need for
|
||||
an initramfs or userspace helper to set up dm-verity.
|
||||
|
||||
This feature is enabled by ``CONFIG_FIT_VERITY`` (see ``boot/Kconfig``).
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
* **Linux uImage.FIT block driver** – the kernel must include the FIT block
|
||||
driver that exposes loadable sub-images as ``/dev/fit0``, ``/dev/fit1``,
|
||||
etc. The driver assigns device numbers in the order loadables appear in
|
||||
the FIT configuration.
|
||||
|
||||
* **dm-verity support in the kernel** – ``CONFIG_DM_VERITY`` must be
|
||||
enabled so the kernel can process the ``dm-mod.create=`` parameter.
|
||||
|
||||
* **CONFIG_FIT_VERITY** enabled in U-Boot.
|
||||
|
||||
How it works
|
||||
------------
|
||||
|
||||
The implementation is split into a **build** phase and an **apply** phase,
|
||||
both of which run automatically within the ``bootm`` state machine. No boot
|
||||
method needs to call verity functions explicitly.
|
||||
|
||||
**Build phase** (``BOOTM_STATE_FINDOTHER`` → ``boot_get_loadable()``)
|
||||
|
||||
1. After all loadable sub-images have been loaded,
|
||||
``fit_verity_build_cmdline()`` iterates the configuration's
|
||||
``loadables`` list.
|
||||
|
||||
2. For each loadable that is an ``IH_TYPE_FILESYSTEM`` image **and**
|
||||
contains a ``dm-verity`` child node, a dm-verity target specification is
|
||||
built by the helper ``fit_verity_build_target()``.
|
||||
|
||||
3. The dm-verity target references ``/dev/fitN``, where *N* is the
|
||||
zero-based index of the loadable in the configuration. This matches the
|
||||
numbering used by the Linux FIT block driver.
|
||||
|
||||
4. The resulting fragments are stored in ``struct bootm_headers``:
|
||||
|
||||
``images->dm_mod_create``
|
||||
The full dm-verity target table. Multiple targets are separated by ``;``.
|
||||
|
||||
``images->dm_mod_waitfor``
|
||||
Comma-separated list of ``/dev/fitN`` devices so the kernel waits for
|
||||
the underlying FIT block devices to appear before activating
|
||||
device-mapper.
|
||||
|
||||
**Apply phase** (``BOOTM_STATE_OS_PREP``)
|
||||
|
||||
5. Just before ``bootm_process_cmdline_env()`` processes the ``bootargs``
|
||||
environment variable, ``fit_verity_apply_bootargs()`` appends the
|
||||
``dm-mod.create=`` and ``dm-mod.waitfor=`` parameters.
|
||||
|
||||
**Bootmeth integration**
|
||||
|
||||
Because the fragments are stored in ``struct bootm_headers``, a boot
|
||||
method can check ``fit_verity_active(images)`` between bootm state
|
||||
invocations. A typical pattern splits ``bootm_run_states()`` into two
|
||||
calls -- one for ``START|FINDOS|FINDOTHER|LOADOS`` and one for
|
||||
``OS_PREP|OS_GO`` -- and inspects ``fit_verity_active()`` in
|
||||
between to decide whether to add a ``root=`` parameter pointing at the
|
||||
dm-verity device.
|
||||
|
||||
FIT image source (.its) example
|
||||
-------------------------------
|
||||
|
||||
Below is a minimal ``.its`` file showing a kernel and a dm-verity-protected
|
||||
root filesystem packaged as a FIT. Only the three user-provided properties
|
||||
(``algo``, ``data-block-size``, ``hash-block-size``) are included; ``mkimage``
|
||||
computes and fills in ``digest``, ``salt``, ``num-data-blocks``, and
|
||||
``hash-start-block`` automatically (see `Generating verity metadata`_ below)::
|
||||
|
||||
/dts-v1/;
|
||||
|
||||
/ {
|
||||
description = "Kernel + dm-verity rootfs";
|
||||
#address-cells = <1>;
|
||||
|
||||
images {
|
||||
kernel {
|
||||
description = "Linux kernel";
|
||||
data = /incbin/("./Image.gz");
|
||||
type = "kernel";
|
||||
arch = "arm64";
|
||||
os = "linux";
|
||||
compression = "gzip";
|
||||
load = <0x44000000>;
|
||||
entry = <0x44000000>;
|
||||
hash-1 {
|
||||
algo = "sha256";
|
||||
};
|
||||
};
|
||||
|
||||
fdt {
|
||||
description = "Device tree blob";
|
||||
data = /incbin/("./board.dtb");
|
||||
type = "flat_dt";
|
||||
arch = "arm64";
|
||||
compression = "none";
|
||||
hash-1 {
|
||||
algo = "sha256";
|
||||
};
|
||||
};
|
||||
|
||||
rootfs {
|
||||
description = "SquashFS root filesystem";
|
||||
data = /incbin/("./rootfs.squashfs");
|
||||
type = "filesystem";
|
||||
arch = "arm64";
|
||||
compression = "none";
|
||||
hash-1 {
|
||||
algo = "sha256";
|
||||
};
|
||||
|
||||
dm-verity {
|
||||
algo = "sha256";
|
||||
data-block-size = <4096>;
|
||||
hash-block-size = <4096>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
configurations {
|
||||
default = "config-1";
|
||||
config-1 {
|
||||
description = "Boot with dm-verity rootfs";
|
||||
kernel = "kernel";
|
||||
fdt = "fdt";
|
||||
loadables = "rootfs";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
With this configuration U-Boot produces a kernel command line similar to::
|
||||
|
||||
dm-mod.create="rootfs,,, ro,0 <data_sectors> verity 1 \
|
||||
/dev/fit0 /dev/fit0 4096 4096 3762 3762 sha256 \
|
||||
8e6791637f93cbb81fc45299e203cbe85ca2e47a38f5051bddeece92d7b1c9f9 \
|
||||
aa7b11f8db8fe2e5bfd4eca1d18a22b5de7ea39d2e1b93bb7272ce0c6ca3cc8e" \
|
||||
dm-mod.waitfor=/dev/fit0
|
||||
|
||||
dm-verity subnode properties
|
||||
----------------------------
|
||||
|
||||
User-provided properties (required in the ``.its``):
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
:widths: 20 15 65
|
||||
|
||||
* - Property
|
||||
- Type
|
||||
- Description
|
||||
* - ``algo``
|
||||
- string
|
||||
- Hash algorithm name, e.g. ``"sha256"``.
|
||||
* - ``data-block-size``
|
||||
- u32
|
||||
- Data block size in bytes (>= 512, typically 4096).
|
||||
* - ``hash-block-size``
|
||||
- u32
|
||||
- Hash block size in bytes (>= 512, typically 4096).
|
||||
|
||||
Computed properties (filled in by ``mkimage``):
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
:widths: 20 15 65
|
||||
|
||||
* - Property
|
||||
- Type
|
||||
- Description
|
||||
* - ``num-data-blocks``
|
||||
- u32
|
||||
- Number of data blocks in the filesystem image (computed from the
|
||||
image size and ``data-block-size``).
|
||||
* - ``hash-start-block``
|
||||
- u32
|
||||
- Offset in ``hash-block-size``-sized blocks from the start of the
|
||||
sub-image to the root block of the hash tree.
|
||||
* - ``digest``
|
||||
- byte array
|
||||
- Root hash of the Merkle tree, stored as raw bytes. Length must match
|
||||
the output size of ``algo``.
|
||||
* - ``salt``
|
||||
- byte array
|
||||
- Salt used when computing the Merkle tree, stored as raw bytes.
|
||||
|
||||
These values are the same ones produced by ``veritysetup format`` and can
|
||||
typically be obtained from its output.
|
||||
The ``digest`` and ``salt`` byte arrays correspond to the hex-encoded
|
||||
``Root hash`` and ``Salt`` printed by ``veritysetup format``.
|
||||
|
||||
Optional boolean properties (when present, they are collected and appended
|
||||
as dm-verity optional parameters with hyphens converted to underscores):
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
:widths: 30 70
|
||||
|
||||
* - Property
|
||||
- Description
|
||||
* - ``restart-on-corruption``
|
||||
- Restart the system on data corruption.
|
||||
* - ``panic-on-corruption``
|
||||
- Panic the system on data corruption.
|
||||
* - ``restart-on-error``
|
||||
- Restart the system on I/O error.
|
||||
* - ``panic-on-error``
|
||||
- Panic the system on I/O error.
|
||||
* - ``check-at-most-once``
|
||||
- Verify data blocks only on first read.
|
||||
|
||||
|
||||
Generating verity metadata
|
||||
--------------------------
|
||||
|
||||
``mkimage`` automates the entire process. When it encounters a
|
||||
``dm-verity`` subnode, it:
|
||||
|
||||
1. Writes the embedded image data to a temporary file.
|
||||
2. Runs ``veritysetup format`` with the user-supplied algorithm and
|
||||
block sizes.
|
||||
3. Parses ``Root hash`` and ``Salt`` from ``veritysetup`` stdout.
|
||||
4. Reads the expanded content (original data + Merkle hash tree) back
|
||||
into an in-memory buffer and removes the temporary file. The
|
||||
external-data section written to the .itb file uses this buffer in
|
||||
place of the original ``data`` property.
|
||||
5. Writes the computed ``digest``, ``salt``, ``num-data-blocks``, and
|
||||
``hash-start-block`` properties into the ``dm-verity`` subnode.
|
||||
|
||||
Images with ``dm-verity`` subnodes **must** use external data layout
|
||||
(``mkimage -E``). ``mkimage`` will abort with an error if ``-E`` is
|
||||
not specified.
|
||||
|
||||
Usage::
|
||||
|
||||
# Create the filesystem image
|
||||
mksquashfs rootfs/ rootfs.squashfs -comp xz
|
||||
|
||||
# Build the FIT (dm-verity is computed automatically); align each
|
||||
# external-data section to the block size of the underlying storage
|
||||
# (see the alignment note below).
|
||||
mkimage -E -B 0x1000 -f image.its image.itb
|
||||
|
||||
``veritysetup`` (from the cryptsetup_ package) must be installed on
|
||||
the build host.
|
||||
|
||||
.. _cryptsetup: https://gitlab.com/cryptsetup/cryptsetup
|
||||
|
||||
.. note::
|
||||
|
||||
``veritysetup format`` is invoked with ``--no-superblock``, so no
|
||||
on-disk superblock is written between the data and hash regions.
|
||||
The Merkle hash tree is appended directly to the image data within
|
||||
the FIT external data section. ``hash-start-block`` is therefore
|
||||
computed as ``data_size / hash-block-size`` (the offset of the hash
|
||||
region in units of ``hash-block-size``). When ``data-block-size``
|
||||
equals ``hash-block-size`` this happens to equal ``num-data-blocks``.
|
||||
|
||||
.. note::
|
||||
|
||||
The Linux ``fitblk`` driver currently requires each ``filesystem``
|
||||
sub-image to start and end on block boundaries of the underlying
|
||||
block device (typically 512 bytes, sometimes 4 KiB for eMMC or NVMe
|
||||
with 4 KiB native sectors). Use ``mkimage -B <align>`` to pad
|
||||
external-data sections to that boundary; ``-B 0x1000`` is a safe
|
||||
default for the storage in common use.
|
||||
|
||||
This alignment requirement comes from the kernel-side ``fitblk``
|
||||
driver to avoid unaligned-access fix-up overhead in block I/O, and
|
||||
is **independent** of the dm-verity ``data-block-size`` and
|
||||
``hash-block-size`` properties -- those describe the block sizes
|
||||
used by the device-mapper verity target itself, not storage
|
||||
alignment.
|
||||
|
||||
Kconfig
|
||||
-------
|
||||
|
||||
``CONFIG_FIT_VERITY``
|
||||
Depends on ``CONFIG_FIT`` and ``CONFIG_OF_LIBFDT``.
|
||||
When enabled, ``fit_verity_build_cmdline()`` and
|
||||
``fit_verity_apply_bootargs()`` are compiled into the boot path.
|
||||
When disabled, the functions are static inlines returning 0, so there
|
||||
is no code-size impact. Works with both the ``bootm`` command and
|
||||
BOOTSTD boot methods.
|
||||
@@ -11,6 +11,7 @@ images that it reads and boots. Documentation about FIT is available in
|
||||
:maxdepth: 1
|
||||
|
||||
beaglebone_vboot
|
||||
dm-verity
|
||||
howto
|
||||
kernel_fdt
|
||||
kernel_fdts_compressed
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
#ifndef HEXDUMP_H
|
||||
#define HEXDUMP_H
|
||||
|
||||
#ifdef USE_HOSTCC
|
||||
#include <ctype.h>
|
||||
#else
|
||||
#include <linux/ctype.h>
|
||||
#endif
|
||||
#include <linux/types.h>
|
||||
|
||||
enum dump_prefix_t {
|
||||
@@ -20,7 +24,7 @@ extern const char hex_asc[];
|
||||
#define hex_asc_lo(x) hex_asc[((x) & 0x0f)]
|
||||
#define hex_asc_hi(x) hex_asc[((x) & 0xf0) >> 4]
|
||||
|
||||
static inline char *hex_byte_pack(char *buf, u8 byte)
|
||||
static inline char *hex_byte_pack(char *buf, uint8_t byte)
|
||||
{
|
||||
*buf++ = hex_asc_hi(byte);
|
||||
*buf++ = hex_asc_lo(byte);
|
||||
@@ -52,7 +56,7 @@ static inline int hex_to_bin(char ch)
|
||||
*
|
||||
* Return 0 on success, -1 in case of bad input.
|
||||
*/
|
||||
static inline int hex2bin(u8 *dst, const char *src, size_t count)
|
||||
static inline int hex2bin(uint8_t *dst, const char *src, size_t count)
|
||||
{
|
||||
while (count--) {
|
||||
int hi = hex_to_bin(*src++);
|
||||
|
||||
115
include/image.h
115
include/image.h
@@ -396,7 +396,19 @@ struct bootm_headers {
|
||||
ulong cmdline_start;
|
||||
ulong cmdline_end;
|
||||
struct bd_info *kbd;
|
||||
#endif
|
||||
|
||||
#if CONFIG_IS_ENABLED(FIT_VERITY)
|
||||
/*
|
||||
* dm-verity kernel command-line fragments, populated during FIT
|
||||
* parsing by fit_verity_build_cmdline(). Bootmeths can check
|
||||
* fit_verity_active() between bootm states, and
|
||||
* fit_verity_apply_bootargs() appends these to the "bootargs"
|
||||
* env var during BOOTM_STATE_OS_PREP.
|
||||
*/
|
||||
char *dm_mod_create;
|
||||
char *dm_mod_waitfor;
|
||||
#endif /* FIT_VERITY */
|
||||
#endif /* !USE_HOSTCC */
|
||||
|
||||
int verify; /* env_get("verify")[0] != 'n' */
|
||||
|
||||
@@ -756,6 +768,72 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
|
||||
int arch, int image_ph_type, int bootstage_id,
|
||||
enum fit_load_op load_op, ulong *datap, ulong *lenp);
|
||||
|
||||
#if !defined(USE_HOSTCC) && CONFIG_IS_ENABLED(FIT_VERITY)
|
||||
/**
|
||||
* fit_verity_build_cmdline() - build dm-verity cmdline from FIT metadata
|
||||
* @fit: pointer to the FIT blob
|
||||
* @conf_noffset: configuration node offset in @fit
|
||||
* @images: bootm headers; dm_mod_create / dm_mod_waitfor are
|
||||
* populated on success
|
||||
*
|
||||
* Called automatically from boot_get_loadable() during FIT parsing.
|
||||
* For each IH_TYPE_FILESYSTEM loadable with a dm-verity subnode,
|
||||
* builds the corresponding dm target specification.
|
||||
*
|
||||
* Return: 0 on success, -ve errno on error
|
||||
*/
|
||||
int fit_verity_build_cmdline(const void *fit, int conf_noffset,
|
||||
struct bootm_headers *images);
|
||||
|
||||
/**
|
||||
* fit_verity_apply_bootargs() - append dm-verity params to bootargs env
|
||||
* @images: bootm headers with dm-verity cmdline fragments
|
||||
*
|
||||
* Called from BOOTM_STATE_OS_PREP before bootm_process_cmdline_env().
|
||||
*
|
||||
* Return: 0 on success, -ve errno on error
|
||||
*/
|
||||
int fit_verity_apply_bootargs(const struct bootm_headers *images);
|
||||
|
||||
/**
|
||||
* fit_verity_active() - check whether dm-verity targets were found
|
||||
* @images: bootm headers
|
||||
*
|
||||
* Return: true if at least one dm-verity target was built
|
||||
*/
|
||||
static inline bool fit_verity_active(const struct bootm_headers *images)
|
||||
{
|
||||
return !!images->dm_mod_create;
|
||||
}
|
||||
|
||||
/**
|
||||
* fit_verity_free() - free dm-verity cmdline allocations
|
||||
* @images: bootm headers
|
||||
*/
|
||||
void fit_verity_free(struct bootm_headers *images);
|
||||
|
||||
#else /* !FIT_VERITY */
|
||||
|
||||
static inline int fit_verity_build_cmdline(const void *fit, int conf_noffset,
|
||||
struct bootm_headers *images)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int fit_verity_apply_bootargs(const struct bootm_headers *images)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline bool fit_verity_active(const struct bootm_headers *images)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline void fit_verity_free(struct bootm_headers *images) {}
|
||||
|
||||
#endif /* FIT_VERITY */
|
||||
|
||||
/**
|
||||
* image_locate_script() - Locate the raw script in an image
|
||||
*
|
||||
@@ -1079,6 +1157,23 @@ int booti_setup(ulong image, ulong *relocated_addr, ulong *size,
|
||||
#define FIT_CIPHER_NODENAME "cipher"
|
||||
#define FIT_ALGO_PROP "algo"
|
||||
|
||||
/* dm-verity node */
|
||||
#define FIT_VERITY_NODENAME "dm-verity"
|
||||
#define FIT_VERITY_ALGO_PROP "algo"
|
||||
#define FIT_VERITY_DBS_PROP "data-block-size"
|
||||
#define FIT_VERITY_HBS_PROP "hash-block-size"
|
||||
#define FIT_VERITY_NBLK_PROP "num-data-blocks"
|
||||
#define FIT_VERITY_HBLK_PROP "hash-start-block"
|
||||
#define FIT_VERITY_DIGEST_PROP "digest"
|
||||
#define FIT_VERITY_SALT_PROP "salt"
|
||||
|
||||
/* dm-verity error-handling modes (optional boolean property names) */
|
||||
#define FIT_VERITY_OPT_RESTART "restart-on-corruption"
|
||||
#define FIT_VERITY_OPT_PANIC "panic-on-corruption"
|
||||
#define FIT_VERITY_OPT_RERR "restart-on-error"
|
||||
#define FIT_VERITY_OPT_PERR "panic-on-error"
|
||||
#define FIT_VERITY_OPT_ONCE "check-at-most-once"
|
||||
|
||||
/* image node */
|
||||
#define FIT_DATA_PROP "data"
|
||||
#define FIT_DATA_POSITION_PROP "data-position"
|
||||
@@ -1332,6 +1427,24 @@ int fit_add_verification_data(const char *keydir, const char *keyfile,
|
||||
const char *cmdname, const char *algo_name,
|
||||
struct image_summary *summary);
|
||||
|
||||
#ifdef USE_HOSTCC
|
||||
/**
|
||||
* fit_verity_get_expanded() - look up the cached dm-verity expanded buffer
|
||||
*
|
||||
* After mkimage has run veritysetup on a FILESYSTEM image, the original
|
||||
* data concatenated with the Merkle hash tree is cached in memory keyed
|
||||
* by image name. fit_extract_data() retrieves it to write the external
|
||||
* data section without having to re-read a temporary file from disk.
|
||||
*
|
||||
* @name: image unit name (FDT node name under /images)
|
||||
* @data: output -- pointer to cached buffer (do NOT free; lifetime
|
||||
* ends when mkimage exits)
|
||||
* @size: output -- size of @data in bytes
|
||||
* Return: 0 if a cache entry exists for @name, -ENOENT otherwise
|
||||
*/
|
||||
int fit_verity_get_expanded(const char *name, const void **data, size_t *size);
|
||||
#endif /* USE_HOSTCC */
|
||||
|
||||
/**
|
||||
* fit_image_verify_with_data() - Verify an image with given data
|
||||
*
|
||||
|
||||
@@ -15,6 +15,7 @@ endif
|
||||
ifdef CONFIG_SANDBOX
|
||||
obj-$(CONFIG_$(PHASE_)CMDLINE) += bootm.o
|
||||
endif
|
||||
obj-$(CONFIG_$(PHASE_)FIT_VERITY) += fit_verity.o
|
||||
obj-$(CONFIG_MEASURED_BOOT) += measurement.o
|
||||
|
||||
ifdef CONFIG_OF_LIVE
|
||||
|
||||
306
test/boot/fit_verity.c
Normal file
306
test/boot/fit_verity.c
Normal file
@@ -0,0 +1,306 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Tests for FIT dm-verity cmdline generation
|
||||
*
|
||||
* Copyright 2026 Daniel Golle <daniel@makrotopia.org>
|
||||
*/
|
||||
|
||||
#include <image.h>
|
||||
#include <test/test.h>
|
||||
#include <test/ut.h>
|
||||
|
||||
#define FIT_VERITY_TEST(_name, _flags) UNIT_TEST(_name, _flags, fit_verity)
|
||||
|
||||
/* FIT blob buffer size — generous to avoid FDT_ERR_NOSPACE */
|
||||
#define FIT_BUF_SIZE 4096
|
||||
|
||||
/* Test digest (32 bytes = sha256) */
|
||||
static const u8 test_digest[32] = {
|
||||
0x8e, 0x67, 0x91, 0x63, 0x7f, 0x93, 0xcb, 0xb8,
|
||||
0x1f, 0xc4, 0x52, 0x99, 0xe2, 0x03, 0xcb, 0xe8,
|
||||
0x5c, 0xa2, 0xe4, 0x7a, 0x38, 0xf5, 0x05, 0x1b,
|
||||
0xdd, 0xee, 0xce, 0x92, 0xd7, 0xb1, 0xc9, 0xf9,
|
||||
};
|
||||
|
||||
/* Test salt (32 bytes) */
|
||||
static const u8 test_salt[32] = {
|
||||
0xaa, 0x7b, 0x11, 0xf8, 0xdb, 0x8f, 0xe2, 0xe5,
|
||||
0xbf, 0xd4, 0xec, 0xa1, 0xd1, 0x8a, 0x22, 0xb5,
|
||||
0xde, 0x7e, 0xa3, 0x9d, 0x2e, 0x1b, 0x93, 0xbb,
|
||||
0x72, 0x72, 0xce, 0x0c, 0x6c, 0xa3, 0xcc, 0x8e,
|
||||
};
|
||||
|
||||
/**
|
||||
* build_verity_fit() - construct a minimal FIT blob with dm-verity metadata
|
||||
* @buf: output buffer (at least FIT_BUF_SIZE bytes)
|
||||
* @num_loadables: number of filesystem loadables to create (1 or 2)
|
||||
*
|
||||
* Builds a FIT blob containing:
|
||||
* - /images/rootfsN with type="filesystem" and a dm-verity subnode
|
||||
* - /configurations/conf-1 referencing the loadable(s)
|
||||
*
|
||||
* Return: configuration node offset, or -ve on error
|
||||
*/
|
||||
static int build_verity_fit(void *buf, int num_loadables)
|
||||
{
|
||||
int images_node, conf_node, confs_node, img_node, verity_node;
|
||||
fdt32_t val;
|
||||
int ret, i;
|
||||
char name[32];
|
||||
/*
|
||||
* Build the loadables string list. FDT stringlists are concatenated
|
||||
* NUL-terminated strings. E.g. "rootfs0\0rootfs1\0"
|
||||
*/
|
||||
char loadables[128];
|
||||
int loadables_len = 0;
|
||||
|
||||
ret = fdt_create_empty_tree(buf, FIT_BUF_SIZE);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* /images */
|
||||
images_node = fdt_add_subnode(buf, 0, "images");
|
||||
if (images_node < 0)
|
||||
return images_node;
|
||||
|
||||
for (i = 0; i < num_loadables; i++) {
|
||||
snprintf(name, sizeof(name), "rootfs%d", i);
|
||||
|
||||
img_node = fdt_add_subnode(buf, images_node, name);
|
||||
if (img_node < 0)
|
||||
return img_node;
|
||||
|
||||
ret = fdt_setprop_string(buf, img_node, FIT_TYPE_PROP,
|
||||
"filesystem");
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
verity_node = fdt_add_subnode(buf, img_node,
|
||||
FIT_VERITY_NODENAME);
|
||||
if (verity_node < 0)
|
||||
return verity_node;
|
||||
|
||||
ret = fdt_setprop_string(buf, verity_node,
|
||||
FIT_VERITY_ALGO_PROP, "sha256");
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val = cpu_to_fdt32(4096);
|
||||
ret = fdt_setprop(buf, verity_node, FIT_VERITY_DBS_PROP,
|
||||
&val, sizeof(val));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = fdt_setprop(buf, verity_node, FIT_VERITY_HBS_PROP,
|
||||
&val, sizeof(val));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val = cpu_to_fdt32(100);
|
||||
ret = fdt_setprop(buf, verity_node, FIT_VERITY_NBLK_PROP,
|
||||
&val, sizeof(val));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val = cpu_to_fdt32(100);
|
||||
ret = fdt_setprop(buf, verity_node, FIT_VERITY_HBLK_PROP,
|
||||
&val, sizeof(val));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = fdt_setprop(buf, verity_node, FIT_VERITY_DIGEST_PROP,
|
||||
test_digest, sizeof(test_digest));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = fdt_setprop(buf, verity_node, FIT_VERITY_SALT_PROP,
|
||||
test_salt, sizeof(test_salt));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Append to loadables stringlist */
|
||||
loadables_len += snprintf(loadables + loadables_len,
|
||||
sizeof(loadables) - loadables_len,
|
||||
"%s", name) + 1;
|
||||
}
|
||||
|
||||
/* /configurations/conf-1 */
|
||||
confs_node = fdt_add_subnode(buf, 0, "configurations");
|
||||
if (confs_node < 0)
|
||||
return confs_node;
|
||||
|
||||
conf_node = fdt_add_subnode(buf, confs_node, "conf-1");
|
||||
if (conf_node < 0)
|
||||
return conf_node;
|
||||
|
||||
ret = fdt_setprop(buf, conf_node, FIT_LOADABLE_PROP,
|
||||
loadables, loadables_len);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return conf_node;
|
||||
}
|
||||
|
||||
/* Test: single dm-verity loadable produces correct cmdline fragments */
|
||||
static int fit_verity_test_single(struct unit_test_state *uts)
|
||||
{
|
||||
char buf[FIT_BUF_SIZE];
|
||||
struct bootm_headers images;
|
||||
int conf_noffset;
|
||||
|
||||
conf_noffset = build_verity_fit(buf, 1);
|
||||
ut_assert(conf_noffset >= 0);
|
||||
|
||||
memset(&images, 0, sizeof(images));
|
||||
ut_assertok(fit_verity_build_cmdline(buf, conf_noffset, &images));
|
||||
|
||||
/* dm_mod_create should contain the target spec for rootfs0 */
|
||||
ut_assertnonnull(images.dm_mod_create);
|
||||
ut_assert(strstr(images.dm_mod_create, "rootfs0,,,"));
|
||||
ut_assert(strstr(images.dm_mod_create, "verity 1"));
|
||||
ut_assert(strstr(images.dm_mod_create, "/dev/fit0"));
|
||||
ut_assert(strstr(images.dm_mod_create, "4096 4096 100 100"));
|
||||
ut_assert(strstr(images.dm_mod_create, "sha256"));
|
||||
/* Check hex-encoded digest prefix */
|
||||
ut_assert(strstr(images.dm_mod_create, "8e6791637f93cbb8"));
|
||||
/* Check hex-encoded salt prefix */
|
||||
ut_assert(strstr(images.dm_mod_create, "aa7b11f8db8fe2e5"));
|
||||
|
||||
/* dm_mod_waitfor should reference /dev/fit0 */
|
||||
ut_assertnonnull(images.dm_mod_waitfor);
|
||||
ut_asserteq_str("/dev/fit0", images.dm_mod_waitfor);
|
||||
|
||||
fit_verity_free(&images);
|
||||
ut_assertnull(images.dm_mod_create);
|
||||
ut_assertnull(images.dm_mod_waitfor);
|
||||
|
||||
return 0;
|
||||
}
|
||||
FIT_VERITY_TEST(fit_verity_test_single, 0);
|
||||
|
||||
/* Test: FIT with no dm-verity subnode returns 0, pointers stay NULL */
|
||||
static int fit_verity_test_no_verity(struct unit_test_state *uts)
|
||||
{
|
||||
char buf[FIT_BUF_SIZE];
|
||||
struct bootm_headers images;
|
||||
int conf_node, images_node, img_node, confs_node;
|
||||
int ret;
|
||||
|
||||
ret = fdt_create_empty_tree(buf, FIT_BUF_SIZE);
|
||||
ut_assertok(ret);
|
||||
|
||||
images_node = fdt_add_subnode(buf, 0, "images");
|
||||
ut_assert(images_node >= 0);
|
||||
|
||||
img_node = fdt_add_subnode(buf, images_node, "rootfs");
|
||||
ut_assert(img_node >= 0);
|
||||
ut_assertok(fdt_setprop_string(buf, img_node, FIT_TYPE_PROP,
|
||||
"filesystem"));
|
||||
/* No dm-verity subnode */
|
||||
|
||||
confs_node = fdt_add_subnode(buf, 0, "configurations");
|
||||
ut_assert(confs_node >= 0);
|
||||
conf_node = fdt_add_subnode(buf, confs_node, "conf-1");
|
||||
ut_assert(conf_node >= 0);
|
||||
ut_assertok(fdt_setprop_string(buf, conf_node, FIT_LOADABLE_PROP,
|
||||
"rootfs"));
|
||||
|
||||
memset(&images, 0, sizeof(images));
|
||||
ut_asserteq(0, fit_verity_build_cmdline(buf, conf_node, &images));
|
||||
ut_assertnull(images.dm_mod_create);
|
||||
ut_assertnull(images.dm_mod_waitfor);
|
||||
|
||||
return 0;
|
||||
}
|
||||
FIT_VERITY_TEST(fit_verity_test_no_verity, 0);
|
||||
|
||||
/* Test: two dm-verity loadables produce combined cmdline */
|
||||
static int fit_verity_test_two_loadables(struct unit_test_state *uts)
|
||||
{
|
||||
char buf[FIT_BUF_SIZE];
|
||||
struct bootm_headers images;
|
||||
int conf_noffset;
|
||||
|
||||
conf_noffset = build_verity_fit(buf, 2);
|
||||
ut_assert(conf_noffset >= 0);
|
||||
|
||||
memset(&images, 0, sizeof(images));
|
||||
ut_assertok(fit_verity_build_cmdline(buf, conf_noffset, &images));
|
||||
|
||||
/* Both targets should appear, separated by ";" */
|
||||
ut_assertnonnull(images.dm_mod_create);
|
||||
ut_assert(strstr(images.dm_mod_create, "rootfs0,,,"));
|
||||
ut_assert(strstr(images.dm_mod_create, ";rootfs1,,,"));
|
||||
ut_assert(strstr(images.dm_mod_create, "/dev/fit0"));
|
||||
ut_assert(strstr(images.dm_mod_create, "/dev/fit1"));
|
||||
|
||||
/* dm_mod_waitfor should list both devices */
|
||||
ut_assertnonnull(images.dm_mod_waitfor);
|
||||
ut_assert(strstr(images.dm_mod_waitfor, "/dev/fit0"));
|
||||
ut_assert(strstr(images.dm_mod_waitfor, "/dev/fit1"));
|
||||
|
||||
fit_verity_free(&images);
|
||||
return 0;
|
||||
}
|
||||
FIT_VERITY_TEST(fit_verity_test_two_loadables, 0);
|
||||
|
||||
/* Test: invalid block size (not power of two) returns -EINVAL */
|
||||
static int fit_verity_test_bad_blocksize(struct unit_test_state *uts)
|
||||
{
|
||||
char buf[FIT_BUF_SIZE];
|
||||
struct bootm_headers images;
|
||||
int images_node, conf_node, confs_node, img_node, verity_node;
|
||||
fdt32_t val;
|
||||
int ret;
|
||||
|
||||
ret = fdt_create_empty_tree(buf, FIT_BUF_SIZE);
|
||||
ut_assertok(ret);
|
||||
|
||||
images_node = fdt_add_subnode(buf, 0, "images");
|
||||
ut_assert(images_node >= 0);
|
||||
|
||||
img_node = fdt_add_subnode(buf, images_node, "rootfs");
|
||||
ut_assert(img_node >= 0);
|
||||
ut_assertok(fdt_setprop_string(buf, img_node, FIT_TYPE_PROP,
|
||||
"filesystem"));
|
||||
|
||||
verity_node = fdt_add_subnode(buf, img_node, FIT_VERITY_NODENAME);
|
||||
ut_assert(verity_node >= 0);
|
||||
|
||||
ut_assertok(fdt_setprop_string(buf, verity_node,
|
||||
FIT_VERITY_ALGO_PROP, "sha256"));
|
||||
|
||||
/* 3000 is not a power of two */
|
||||
val = cpu_to_fdt32(3000);
|
||||
ut_assertok(fdt_setprop(buf, verity_node, FIT_VERITY_DBS_PROP,
|
||||
&val, sizeof(val)));
|
||||
val = cpu_to_fdt32(4096);
|
||||
ut_assertok(fdt_setprop(buf, verity_node, FIT_VERITY_HBS_PROP,
|
||||
&val, sizeof(val)));
|
||||
|
||||
val = cpu_to_fdt32(100);
|
||||
ut_assertok(fdt_setprop(buf, verity_node, FIT_VERITY_NBLK_PROP,
|
||||
&val, sizeof(val)));
|
||||
ut_assertok(fdt_setprop(buf, verity_node, FIT_VERITY_HBLK_PROP,
|
||||
&val, sizeof(val)));
|
||||
|
||||
ut_assertok(fdt_setprop(buf, verity_node, FIT_VERITY_DIGEST_PROP,
|
||||
test_digest, sizeof(test_digest)));
|
||||
ut_assertok(fdt_setprop(buf, verity_node, FIT_VERITY_SALT_PROP,
|
||||
test_salt, sizeof(test_salt)));
|
||||
|
||||
confs_node = fdt_add_subnode(buf, 0, "configurations");
|
||||
ut_assert(confs_node >= 0);
|
||||
conf_node = fdt_add_subnode(buf, confs_node, "conf-1");
|
||||
ut_assert(conf_node >= 0);
|
||||
ut_assertok(fdt_setprop_string(buf, conf_node, FIT_LOADABLE_PROP,
|
||||
"rootfs"));
|
||||
|
||||
memset(&images, 0, sizeof(images));
|
||||
ut_asserteq(-EINVAL, fit_verity_build_cmdline(buf, conf_node, &images));
|
||||
ut_assertnull(images.dm_mod_create);
|
||||
ut_assertnull(images.dm_mod_waitfor);
|
||||
|
||||
return 0;
|
||||
}
|
||||
FIT_VERITY_TEST(fit_verity_test_bad_blocksize, 0);
|
||||
@@ -59,6 +59,7 @@ SUITE_DECL(env);
|
||||
SUITE_DECL(exit);
|
||||
SUITE_DECL(fdt);
|
||||
SUITE_DECL(fdt_overlay);
|
||||
SUITE_DECL(fit_verity);
|
||||
SUITE_DECL(font);
|
||||
SUITE_DECL(hush);
|
||||
SUITE_DECL(lib);
|
||||
@@ -86,6 +87,7 @@ static struct suite suites[] = {
|
||||
SUITE(exit, "shell exit and variables"),
|
||||
SUITE(fdt, "fdt command"),
|
||||
SUITE(fdt_overlay, "device tree overlays"),
|
||||
SUITE(fit_verity, "FIT dm-verity cmdline generation"),
|
||||
SUITE(font, "font command"),
|
||||
SUITE(hush, "hush behaviour"),
|
||||
SUITE(lib, "library functions"),
|
||||
|
||||
175
test/py/tests/test_fit_verity.py
Normal file
175
test/py/tests/test_fit_verity.py
Normal file
@@ -0,0 +1,175 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
#
|
||||
# Copyright 2026 Daniel Golle <daniel@makrotopia.org>
|
||||
|
||||
"""
|
||||
Test mkimage dm-verity Merkle-tree generation
|
||||
|
||||
Build a minimal .its with a dm-verity subnode (user-provided properties only),
|
||||
run mkimage -E, and verify that the computed properties (digest, salt,
|
||||
num-data-blocks, hash-start-block) are written into the resulting FIT.
|
||||
The computed digest is then re-verified by running ``veritysetup verify``
|
||||
against the external data section of the .itb.
|
||||
|
||||
This test does not run the sandbox. It only exercises the host tool 'mkimage'.
|
||||
Requires 'veritysetup' from the cryptsetup package on the build host.
|
||||
"""
|
||||
|
||||
import os
|
||||
import struct
|
||||
import pytest
|
||||
import utils
|
||||
|
||||
ITS_TEMPLATE = """\
|
||||
/dts-v1/;
|
||||
|
||||
/ {
|
||||
description = "dm-verity test";
|
||||
#address-cells = <1>;
|
||||
|
||||
images {
|
||||
rootfs {
|
||||
description = "test filesystem";
|
||||
data = /incbin/("./rootfs.bin");
|
||||
type = "filesystem";
|
||||
arch = "sandbox";
|
||||
compression = "none";
|
||||
|
||||
dm-verity {
|
||||
algo = "sha256";
|
||||
data-block-size = <%d>;
|
||||
hash-block-size = <%d>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
configurations {
|
||||
default = "conf-1";
|
||||
conf-1 {
|
||||
description = "test config";
|
||||
loadables = "rootfs";
|
||||
};
|
||||
};
|
||||
};
|
||||
"""
|
||||
|
||||
def _fdt_totalsize(path):
|
||||
"""Read the totalsize field from an FDT header (offset 4, big-endian u32)."""
|
||||
with open(path, 'rb') as f:
|
||||
magic, totalsize = struct.unpack('>II', f.read(8))
|
||||
assert magic == 0xd00dfeed, f'not an FDT: magic={magic:#x}'
|
||||
return totalsize
|
||||
|
||||
|
||||
def _run_round_trip(ubman, tempdir, data_block_size, hash_block_size):
|
||||
"""Build a FIT with dm-verity, verify written properties, re-verify with veritysetup."""
|
||||
mkimage = ubman.config.build_dir + '/tools/mkimage'
|
||||
|
||||
rootfs_file = os.path.join(tempdir, 'rootfs.bin')
|
||||
its_file = os.path.join(tempdir, 'image.its')
|
||||
fit_file = os.path.join(tempdir, 'image.itb')
|
||||
|
||||
# 64 data blocks of 0xa5
|
||||
num_blocks = 64
|
||||
data_size = data_block_size * num_blocks
|
||||
with open(rootfs_file, 'wb') as f:
|
||||
f.write(bytes([0xa5]) * data_size)
|
||||
|
||||
with open(its_file, 'w') as f:
|
||||
f.write(ITS_TEMPLATE % (data_block_size, hash_block_size))
|
||||
|
||||
dtc_args = f'-I dts -O dtb -i {tempdir}'
|
||||
utils.run_and_log(ubman,
|
||||
[mkimage, '-E', '-D', dtc_args, '-f', its_file, fit_file])
|
||||
|
||||
def fdt_get(node, prop):
|
||||
val = utils.run_and_log(ubman, f'fdtget {fit_file} {node} {prop}')
|
||||
return val.strip()
|
||||
|
||||
def fdt_get_hex(node, prop):
|
||||
val = utils.run_and_log(ubman, f'fdtget -tbx {fit_file} {node} {prop}')
|
||||
return ''.join(b.zfill(2) for b in val.strip().split())
|
||||
|
||||
verity_path = '/images/rootfs/dm-verity'
|
||||
|
||||
assert fdt_get(verity_path, 'algo') == 'sha256'
|
||||
assert int(fdt_get(verity_path, 'data-block-size')) == data_block_size
|
||||
assert int(fdt_get(verity_path, 'hash-block-size')) == hash_block_size
|
||||
|
||||
nblk = int(fdt_get(verity_path, 'num-data-blocks'))
|
||||
assert nblk == num_blocks, f'num-data-blocks {nblk} != {num_blocks}'
|
||||
|
||||
hblk = int(fdt_get(verity_path, 'hash-start-block'))
|
||||
# With --no-superblock, hash-start-block = data_size / hash-block-size
|
||||
assert hblk == data_size // hash_block_size, \
|
||||
f'hash-start-block {hblk} != {data_size // hash_block_size}'
|
||||
|
||||
digest = fdt_get_hex(verity_path, 'digest')
|
||||
assert len(digest) == 64 and digest != '0' * 64
|
||||
salt = fdt_get_hex(verity_path, 'salt')
|
||||
assert len(salt) == 64
|
||||
|
||||
# Re-verify the digest with veritysetup against the .itb's external data.
|
||||
# With -E, image data sits after the FIT FDT at (fdt_totalsize + data-offset).
|
||||
data_offset = int(fdt_get('/images/rootfs', 'data-offset'))
|
||||
data_size_full = int(fdt_get('/images/rootfs', 'data-size'))
|
||||
ext_pos = _fdt_totalsize(fit_file) + data_offset
|
||||
expanded = os.path.join(tempdir, 'expanded.bin')
|
||||
with open(fit_file, 'rb') as src, open(expanded, 'wb') as dst:
|
||||
src.seek(ext_pos)
|
||||
dst.write(src.read(data_size_full))
|
||||
|
||||
utils.run_and_log(ubman, [
|
||||
'veritysetup', 'verify', expanded, expanded, digest,
|
||||
'--no-superblock',
|
||||
f'--data-block-size={data_block_size}',
|
||||
f'--hash-block-size={hash_block_size}',
|
||||
f'--data-blocks={nblk}',
|
||||
'--hash=sha256',
|
||||
f'--salt={salt}',
|
||||
f'--hash-offset={data_size}',
|
||||
])
|
||||
|
||||
|
||||
@pytest.mark.requiredtool('dtc')
|
||||
@pytest.mark.requiredtool('fdtget')
|
||||
@pytest.mark.requiredtool('veritysetup')
|
||||
@pytest.mark.parametrize('data_block_size,hash_block_size,subdir', [
|
||||
(4096, 4096, 'verity-equal'),
|
||||
(4096, 1024, 'verity-unequal'),
|
||||
])
|
||||
def test_mkimage_verity(ubman, data_block_size, hash_block_size, subdir):
|
||||
"""mkimage writes correct dm-verity properties and the digest verifies.
|
||||
|
||||
Run with matching and mismatched block sizes so the
|
||||
``hash-start-block != num-data-blocks`` path is exercised.
|
||||
"""
|
||||
tempdir = os.path.join(ubman.config.result_dir, subdir)
|
||||
os.makedirs(tempdir, exist_ok=True)
|
||||
_run_round_trip(ubman, tempdir, data_block_size, hash_block_size)
|
||||
|
||||
|
||||
@pytest.mark.requiredtool('dtc')
|
||||
@pytest.mark.requiredtool('veritysetup')
|
||||
def test_mkimage_verity_requires_external(ubman):
|
||||
"""mkimage rejects dm-verity without -E with the expected diagnostic."""
|
||||
|
||||
mkimage = ubman.config.build_dir + '/tools/mkimage'
|
||||
tempdir = os.path.join(ubman.config.result_dir, 'verity_no_ext')
|
||||
os.makedirs(tempdir, exist_ok=True)
|
||||
|
||||
rootfs_file = os.path.join(tempdir, 'rootfs.bin')
|
||||
its_file = os.path.join(tempdir, 'image.its')
|
||||
fit_file = os.path.join(tempdir, 'image.itb')
|
||||
|
||||
with open(rootfs_file, 'wb') as f:
|
||||
f.write(bytes([0xa5]) * 4096 * 8)
|
||||
|
||||
with open(its_file, 'w') as f:
|
||||
f.write(ITS_TEMPLATE % (4096, 4096))
|
||||
|
||||
dtc_args = f'-I dts -O dtb -i {tempdir}'
|
||||
utils.run_and_log_expect_exception(
|
||||
ubman,
|
||||
[mkimage, '-D', dtc_args, '-f', its_file, fit_file],
|
||||
1, 'dm-verity requires external data')
|
||||
@@ -40,10 +40,10 @@ static int fit_estimate_hash_sig_size(struct image_tool_params *params, const ch
|
||||
return -EIO;
|
||||
|
||||
/*
|
||||
* Walk the FIT image, looking for nodes named hash* and
|
||||
* signature*. Since the interesting nodes are subnodes of an
|
||||
* image or configuration node, we are only interested in
|
||||
* those at depth exactly 3.
|
||||
* Walk the FIT image, looking for nodes named hash*,
|
||||
* signature*, and dm-verity. Since the interesting nodes are
|
||||
* subnodes of an image or configuration node, we are only
|
||||
* interested in those at depth exactly 3.
|
||||
*
|
||||
* The estimate for a hash node is based on a sha512 digest
|
||||
* being 64 bytes, with another 64 bytes added to account for
|
||||
@@ -55,6 +55,10 @@ static int fit_estimate_hash_sig_size(struct image_tool_params *params, const ch
|
||||
* account for fdt overhead and the various other properties
|
||||
* (hashed-nodes etc.) that will also be filled in.
|
||||
*
|
||||
* For a dm-verity node the small metadata properties (digest,
|
||||
* salt, two u32s and a temp-file path) are written into the
|
||||
* FDT by fit_image_process_verity().
|
||||
*
|
||||
* One could try to be more precise in the estimates by
|
||||
* looking at the "algo" property and, in the case of
|
||||
* configuration signatures, the sign-images property. Also,
|
||||
@@ -76,6 +80,18 @@ static int fit_estimate_hash_sig_size(struct image_tool_params *params, const ch
|
||||
|
||||
if (signing && !strncmp(name, FIT_SIG_NODENAME, strlen(FIT_SIG_NODENAME)))
|
||||
estimate += 1024;
|
||||
|
||||
if (!strcmp(name, FIT_VERITY_NODENAME)) {
|
||||
if (!params->external_data) {
|
||||
fprintf(stderr,
|
||||
"%s: dm-verity requires external data (-E)\n",
|
||||
params->cmdname);
|
||||
munmap(fdt, sbuf.st_size);
|
||||
close(fd);
|
||||
return -EINVAL;
|
||||
}
|
||||
estimate += 256;
|
||||
}
|
||||
}
|
||||
|
||||
munmap(fdt, sbuf.st_size);
|
||||
@@ -470,6 +486,41 @@ static int fit_write_images(struct image_tool_params *params, char *fdt)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* fit_copy_image_data() - copy image data, using cached verity expansion
|
||||
* @fdt: FIT blob
|
||||
* @node: image node offset
|
||||
* @buf: destination buffer
|
||||
* @buf_ptr: write offset within @buf
|
||||
* @data: embedded image data (used when no dm-verity expansion exists)
|
||||
* @lenp: in/out: on entry, length of @data; on exit, bytes written
|
||||
*
|
||||
* When fit_image_process_verity() has run, the expanded image data
|
||||
* (original + hash tree) is cached in memory. Look it up by image name
|
||||
* and copy from the cached buffer rather than the embedded ``data``
|
||||
* property; fall back to @data otherwise.
|
||||
*
|
||||
* Return: 0 on success
|
||||
*/
|
||||
static int fit_copy_image_data(void *fdt, int node, void *buf,
|
||||
int buf_ptr, const void *data, int *lenp)
|
||||
{
|
||||
const char *image_name = fdt_get_name(fdt, node, NULL);
|
||||
const void *vdata;
|
||||
size_t vsize;
|
||||
|
||||
if (image_name &&
|
||||
!fit_verity_get_expanded(image_name, &vdata, &vsize)) {
|
||||
memcpy(buf + buf_ptr, vdata, vsize);
|
||||
*lenp = vsize;
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(buf + buf_ptr, data, *lenp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* fit_write_configs() - Write out a list of configurations to the FIT
|
||||
*
|
||||
@@ -653,6 +704,8 @@ static int fit_extract_data(struct image_tool_params *params, const char *fname)
|
||||
int node;
|
||||
int align_size = 0;
|
||||
int len = 0;
|
||||
int verity_extra = 0;
|
||||
int orig_len;
|
||||
|
||||
fd = mmap_fdt(params->cmdname, fname, 0, &fdt, &sbuf, false, false);
|
||||
if (fd < 0)
|
||||
@@ -686,11 +739,34 @@ static int fit_extract_data(struct image_tool_params *params, const char *fname)
|
||||
align_size += 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* When dm-verity is active the external data for an image is
|
||||
* larger than the embedded data property (original + hash tree).
|
||||
* Walk images once more and consult the in-memory cache for the
|
||||
* actual expanded size.
|
||||
*/
|
||||
fdt_for_each_subnode(node, fdt, images) {
|
||||
const char *image_name;
|
||||
const void *vdata;
|
||||
size_t vsize;
|
||||
|
||||
orig_len = 0;
|
||||
if (fdt_subnode_offset(fdt, node, FIT_VERITY_NODENAME) < 0)
|
||||
continue;
|
||||
image_name = fdt_get_name(fdt, node, NULL);
|
||||
if (!image_name ||
|
||||
fit_verity_get_expanded(image_name, &vdata, &vsize))
|
||||
continue;
|
||||
fdt_getprop(fdt, node, FIT_DATA_PROP, &orig_len);
|
||||
if ((int)vsize > orig_len)
|
||||
verity_extra += (int)vsize - orig_len;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate space to hold the image data we will extract,
|
||||
* extral space allocate for image alignment to prevent overflow.
|
||||
*/
|
||||
buf = calloc(1, fit_size + align_size);
|
||||
buf = calloc(1, fit_size + align_size + verity_extra);
|
||||
if (!buf) {
|
||||
ret = -ENOMEM;
|
||||
goto err_munmap;
|
||||
@@ -721,7 +797,10 @@ static int fit_extract_data(struct image_tool_params *params, const char *fname)
|
||||
data = fdt_getprop(fdt, node, FIT_DATA_PROP, &len);
|
||||
if (!data)
|
||||
continue;
|
||||
memcpy(buf + buf_ptr, data, len);
|
||||
|
||||
ret = fit_copy_image_data(fdt, node, buf, buf_ptr, data, &len);
|
||||
if (ret)
|
||||
goto err_munmap;
|
||||
debug("Extracting data size %x\n", len);
|
||||
|
||||
ret = fdt_delprop(fdt, node, FIT_DATA_PROP);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user