[PATCH 00/14] RFC: measure FIT signing keys with the TPM

(The fork button on the gitlab instance is greyed out and I don't have privileged to push into a branch of u-boot so trying mail) This is a proof of concept for measured boot without EFI. The idea is to measure the RSA keys contained in the control FDT into PCR#7. The Linux system can then bind it's full disk encryption to that value. Assuming that everything up to and including u-boot is trusted, a devices' original disk content is only accessible then with the same set of trusted keys active. When adding more or different keys (not sure how yet) a device could still be booted with custom software. The most relevant patches are the three ones with exclamation mark in the list below. The others are random fixes Feedback welcome. Ludwig Nussel (14): arm: qemu: fix 64bit initrd_high and fdt_high bootm: treat absent load address as zero vsnprintf: add hex string dump format modifier iminfo: also verify signatures ! qemu: overlay signature nodes ! tpm: allocate tpm event log if missing ! boot: measure FIT signing keys with the TPM imx8mm: add ftpm to device tree if enabled tools: imx8image: implement printing header test: fix build with enabled CONFIG_MEASURED_BOOT sandbox: fix booting host images bootm: add measure subcommand mkimage: define log_err and log_info image-fit-sig: require signatures arch/arm/dts/imx8mm-u-boot.dtsi | 5 + board/emulation/qemu-arm/qemu-arm.c | 30 +++++- board/emulation/qemu-arm/qemu-arm.env | 5 + boot/Kconfig | 8 ++ boot/bootm.c | 93 +++++++++++++++--- boot/image-fdt.c | 65 +++++++++++++ boot/image-fit-sig.c | 15 ++- boot/image-fit.c | 31 ++++++ boot/image-pre-load.c | 3 - cmd/bootm.c | 8 ++ doc/develop/printf.rst | 4 + drivers/block/Makefile | 2 +- drivers/block/host_bootdev.c | 40 ++++++++ fs/sandbox/Makefile | 2 +- .../{host_bootdev.c => sandboxfs_bootdev.c} | 18 ++-- include/image.h | 1 + include/spbuf.h | 28 ++++++ include/tpm-common.h | 1 + include/tpm_tcg2.h | 4 + lib/Makefile | 2 + lib/spbuf.c | 64 ++++++++++++ lib/tpm_tcg2.c | 97 +++++++++++++++---- lib/vsprintf.c | 18 ++++ test/boot/measurement.c | 1 + tools/imx8image.c | 93 +++++++++++++++++- tools/mkimage.h | 2 + 26 files changed, 582 insertions(+), 58 deletions(-) create mode 100644 drivers/block/host_bootdev.c rename fs/sandbox/{host_bootdev.c => sandboxfs_bootdev.c} (65%) create mode 100644 include/spbuf.h create mode 100644 lib/spbuf.c -- 2.34.1

The initrd_high and fdt_high env variables have a special meaning when set to -1 so the width needs to match for the checks to work. Signed-off-by: Ludwig Nussel <ludwig.nussel@siemens.com> --- board/emulation/qemu-arm/qemu-arm.env | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/board/emulation/qemu-arm/qemu-arm.env b/board/emulation/qemu-arm/qemu-arm.env index fb4adef281e..42e0b61164e 100644 --- a/board/emulation/qemu-arm/qemu-arm.env +++ b/board/emulation/qemu-arm/qemu-arm.env @@ -5,8 +5,13 @@ stdin=serial,usbkbd stdout=serial,vidconsole stderr=serial,vidconsole +#ifdef CONFIG_ARM64 +fdt_high=0xffffffffffffffff +initrd_high=0xffffffffffffffff +#else fdt_high=0xffffffff initrd_high=0xffffffff +#endif fdt_addr=0x40000000 scriptaddr=0x40200000 pxefile_addr_r=0x40300000 -- 2.34.1

Explicitly adding a load address of zero for u-boot to pick one seems redundant so just make images work without. Signed-off-by: Ludwig Nussel <ludwig.nussel@siemens.com> --- boot/bootm.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/boot/bootm.c b/boot/bootm.c index 4bdca22ea8c..33ff67aa53d 100644 --- a/boot/bootm.c +++ b/boot/bootm.c @@ -364,9 +364,8 @@ static int bootm_find_os(const char *cmd_name, const char *addr_fit) if (fit_image_get_load(images.fit_hdr_os, images.fit_noffset_os, &images.os.load)) { - puts("Can't get image load address!\n"); - bootstage_error(BOOTSTAGE_ID_FIT_LOADADDR); - return 1; + log_debug("Image has no load address, assume zero\n"); + images.os.load = 0; } break; #endif @@ -416,8 +415,8 @@ static int bootm_find_os(const char *cmd_name, const char *addr_fit) ret = fit_image_get_entry(images.fit_hdr_os, images.fit_noffset_os, &images.ep); if (ret) { - puts("Can't get entry point property!\n"); - return 1; + log_debug("Image has no entry point address, assume zero\n"); + images.ep = 0; } #endif } else if (!ep_found) { -- 2.34.1

Extend the %p format modifier with 'x' and 'X' mode. This allows to to conveniently print ranges of memory without an extra loop or buffer in the calling code e.g. log_info("Checksum %*pX\n", (int)sizeof(buf), buf); Signed-off-by: Ludwig Nussel <ludwig.nussel@siemens.com> --- doc/develop/printf.rst | 4 ++++ lib/vsprintf.c | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/doc/develop/printf.rst b/doc/develop/printf.rst index 99d05061b14..5583baabd48 100644 --- a/doc/develop/printf.rst +++ b/doc/develop/printf.rst @@ -197,3 +197,7 @@ Pointers prints text description of a GUID or if such is not known little endian, lower case, e.g. 'system' for a GUID identifying an EFI system partition. + +%pX, %px + prints hex dump of 'field width' bytes in upper resp lower case + e.g. log_info("%*pX", (int)sizeof(buf), buf); diff --git a/lib/vsprintf.c b/lib/vsprintf.c index c7340a047b2..b1b0c52f4f0 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -276,6 +276,20 @@ static char *string(char *buf, char *end, const char *s, int field_width, return buf; } +static char *hexstring(char *buf, char *end, const uint8_t *s, int field_width, + int precision, int flags) +{ + if (!s) { + s = ""; + field_width = 1; + } + + for (int i = 0; i < field_width; ++i, ++s) + buf = number(buf, end, *s, 16, 2, -1, flags | ZEROPAD); + + return buf; +} + /* U-Boot uses UTF-16 strings in the EFI context only. */ static __maybe_unused char *string16(char *buf, char *end, u16 *s, int field_width, int precision, int flags) @@ -508,6 +522,10 @@ static char *pointer(const char *fmt, char *buf, char *end, void *ptr, return uuid_string(buf, end, ptr, field_width, precision, flags, fmt); #endif + case 'x': + case 'X': + return hexstring(buf, end, (uint8_t *)ptr, field_width, precision, + (*fmt == 'x' ? SMALL : 0)); default: break; } -- 2.34.1

The iminfo command already verifies hashes of images. This change also verifies signatures of configurations if enabled. Signed-off-by: Ludwig Nussel <ludwig.nussel@siemens.com> --- boot/image-fit.c | 31 +++++++++++++++++++++++++++++++ cmd/bootm.c | 7 +++++++ include/image.h | 1 + 3 files changed, 39 insertions(+) diff --git a/boot/image-fit.c b/boot/image-fit.c index 41ab1f552b0..1e5c35aaec4 100644 --- a/boot/image-fit.c +++ b/boot/image-fit.c @@ -1496,6 +1496,37 @@ int fit_all_image_verify(const void *fit) return 1; } +int fit_all_configurations_verify(const void *fit) +{ + int confs_noffset; + int noffset; + int r = 1; + + /* Find images parent node offset */ + confs_noffset = fdt_path_offset(fit, FIT_CONFS_PATH); + if (confs_noffset < 0) { + printf("Can't find configurations parent node '%s' (%s)\n", + FIT_IMAGES_PATH, fdt_strerror(confs_noffset)); + return 0; + } + + /* Process all image subnodes, check hashes for each */ + printf("## Checking signatures for FIT Image at %08lx ...\n", + (ulong)fit); + + fdt_for_each_subnode(noffset, fit, confs_noffset) { + printf("%s ... ", fit_get_name(fit, noffset, NULL)); + if (fit_config_verify(fit, noffset)) { + r = 0; + puts("BAD"); + continue; + } + puts("OK"); + } + + return r; +} + static int fit_image_uncipher(const void *fit, int image_noffset, void **data, size_t *size) { diff --git a/cmd/bootm.c b/cmd/bootm.c index bee683d0580..a2d811a1cbc 100644 --- a/cmd/bootm.c +++ b/cmd/bootm.c @@ -338,6 +338,13 @@ static int image_info(ulong addr) return 1; } + if (CONFIG_IS_ENABLED(FIT_SIGNATURE) && + !fit_all_configurations_verify(hdr)) { + puts("Signature verification failed!\n"); + unmap_sysmem(hdr); + return 1; + } + unmap_sysmem(hdr); return 0; #endif diff --git a/include/image.h b/include/image.h index b695cc39447..aa3d6ab7340 100644 --- a/include/image.h +++ b/include/image.h @@ -1351,6 +1351,7 @@ static inline int fit_config_verify(const void *fit, int conf_noffset) } #endif int fit_all_image_verify(const void *fit); +int fit_all_configurations_verify(const void *fit); int fit_config_decrypt(const void *fit, int conf_noffset); int fit_image_check_os(const void *fit, int noffset, uint8_t os); int fit_image_check_arch(const void *fit, int noffset, uint8_t arch); -- 2.34.1

The keys trusted for FIT signature verification are supposed to be embedded in the device tree built into u-boot. When running in Qemu it's convenient to use the device tree provided by the VM which doesn't know about signatures though. So merge the signature nodes at run time. Needs CONFIG_OF_OMIT_DTB=n CONFIG_OF_LIBFDT_OVERLAY=y Signed-off-by: Ludwig Nussel <ludwig.nussel@siemens.com> --- board/emulation/qemu-arm/qemu-arm.c | 30 ++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/board/emulation/qemu-arm/qemu-arm.c b/board/emulation/qemu-arm/qemu-arm.c index 38f0ec5f2fb..54f891dc37c 100644 --- a/board/emulation/qemu-arm/qemu-arm.c +++ b/board/emulation/qemu-arm/qemu-arm.c @@ -147,7 +147,35 @@ int dram_init_banksize(void) int board_fdt_blob_setup(void **fdtp) { /* QEMU loads a generated DTB for us at the start of RAM. */ - *fdtp = (void *)CFG_SYS_SDRAM_BASE; + void *qemu_fdt = (void *)CFG_SYS_SDRAM_BASE; + + if (CONFIG_IS_ENABLED(FIT_SIGNATURE) && CONFIG_IS_ENABLED(OF_LIBFDT_OVERLAY) && *fdtp) { + int node; + + node = fdt_subnode_offset(*fdtp, 0, FIT_SIG_NODENAME); + if (node > 0) { + int ret; + int nnode; + + log_info("found signature node in previous dt at %p, merging ...\n", *fdtp); + + nnode = fdt_add_subnode(qemu_fdt, 0, FIT_SIG_NODENAME); + if (nnode == -FDT_ERR_EXISTS) { + nnode = fdt_subnode_offset(qemu_fdt, 0, FIT_SIG_NODENAME); + if (nnode == -FDT_ERR_NOTFOUND) + return -FDT_ERR_INTERNAL; + } + + if (nnode < 0) + return nnode; + + ret = fdt_overlay_apply_node(qemu_fdt, nnode, (void *)*fdtp, node); + if (ret < 0) + log_err("Failed to apply overlay: %d\n", ret); + } + } + + *fdtp = qemu_fdt; return 0; } -- 2.34.1

U-Boot might be the first component to start the event log. So allocate and attach it to the device if not initialized. The address is copied into the device tree for the kernel so the event log is accessible from Linux userspace too. TODO: Is devm_kzalloc() the right way to allocate the event log here? Do we need to use lmb instead and mark the area in a specific way so the Linux kernel won't accidentally overwrite the memory before the tpm module reads it? Signed-off-by: Ludwig Nussel <ludwig.nussel@siemens.com> --- boot/image-fdt.c | 65 +++++++++++++++++++++++++++++ include/tpm-common.h | 1 + include/tpm_tcg2.h | 4 ++ lib/tpm_tcg2.c | 97 +++++++++++++++++++++++++++++++++++--------- 4 files changed, 148 insertions(+), 19 deletions(-) diff --git a/boot/image-fdt.c b/boot/image-fdt.c index 3f0ac54f76f..30684decc34 100644 --- a/boot/image-fdt.c +++ b/boot/image-fdt.c @@ -24,6 +24,10 @@ #include <asm/io.h> #include <dm/ofnode.h> #include <tee/optee.h> +#if defined(CONFIG_MEASURED_BOOT) +#include <tpm_tcg2.h> +#include <dm/device.h> +#endif DECLARE_GLOBAL_DATA_PTR; @@ -583,6 +587,65 @@ __weak int arch_fixup_fdt(void *blob) return 0; } +static int copy_tpm_event_log(void *blob) +{ +#if defined(CONFIG_MEASURED_BOOT) + int rc; + struct udevice *dev; + ofnode node; + char path[256]; + + rc = tcg2_platform_get_tpm2(&dev); + if (rc) + return rc; + + struct tcg2_event_log *log = tcg2_platform_get_dev_log(dev); + + if (!log || !log->allocated) { + log_debug("tpm event log not allocated\n"); + return -ENOENT; + } + + node = dev_ofnode(dev); + if (!ofnode_valid(node)) { + log_err("tpm %p has no device tree representation\n", dev); + return -ENOENT; + } + + rc = ofnode_get_path(node, path, sizeof(path)); + if (rc) + return rc; + + if (fdt_path_offset(blob, path) < 0) { + log_warning("kernel device tree lacks %s\n", path); + return 0; + } + + strlcat(path, "/linux,sml-base", sizeof(path)); + if (fdt_path_offset(blob, path) >= 0) { + log_warning("kernel dt already defines an event log address at %s\n", path); + return 0; + } + char *end = strrchr(path, '/'); + *end = 0; + + fdt64_t a = cpu_to_fdt64((u64)virt_to_phys(log->log)); + fdt32_t s = cpu_to_fdt32((u32)log->log_position); + + rc = fdt_find_and_setprop(blob, path, "linux,sml-base", &a, sizeof(a), 1); + if (rc) { + log_err("failed to set sml-base: %d\n", rc); + return rc; + } + rc = fdt_find_and_setprop(blob, path, "linux,sml-size", &s, sizeof(s), 1); + if (rc) { + log_err("failed to set sml-size: %d\n", rc); + return rc; + } +#endif + return 0; +} + int image_setup_libfdt(struct bootm_headers *images, void *blob, bool lmb) { ulong *initrd_start = &images->initrd_start; @@ -625,6 +688,8 @@ int image_setup_libfdt(struct bootm_headers *images, void *blob, bool lmb) goto err; } + copy_tpm_event_log(blob); + /* Store name of configuration node as u-boot,bootconf in /chosen node */ if (images->fit_uname_cfg) fdt_find_and_setprop(blob, "/chosen", "u-boot,bootconf", diff --git a/include/tpm-common.h b/include/tpm-common.h index bfb84a931d1..f02dae679bd 100644 --- a/include/tpm-common.h +++ b/include/tpm-common.h @@ -83,6 +83,7 @@ struct tpm_chip_priv { u32 active_banks[TPM2_NUM_PCR_BANKS]; #endif bool plat_hier_disabled; + void *log; /* struct tcg2_event_log */ }; /** diff --git a/include/tpm_tcg2.h b/include/tpm_tcg2.h index eb6afe49e77..1b40abc4d7b 100644 --- a/include/tpm_tcg2.h +++ b/include/tpm_tcg2.h @@ -162,12 +162,14 @@ struct tcg_efi_spec_id_event { * @log_position: Current entry position * @log_size: Log space available * @found: Boolean indicating if an existing log was discovered + * @allocated: Boolean indicating that the log was allocated by u-boot */ struct tcg2_event_log { u8 *log; u32 log_position; u32 log_size; bool found; + bool allocated; }; /** @@ -345,4 +347,6 @@ void tcg2_platform_startup_error(struct udevice *dev, int rc); */ u32 tcg2_algorithm_to_mask(enum tpm2_algorithms); +struct tcg2_event_log *tcg2_platform_get_dev_log(struct udevice *dev); + #endif /* __TPM_TCG_V2_H */ diff --git a/lib/tpm_tcg2.c b/lib/tpm_tcg2.c index c314b401d0b..0d994120255 100644 --- a/lib/tpm_tcg2.c +++ b/lib/tpm_tcg2.c @@ -20,6 +20,8 @@ #include <linux/unaligned/le_byteshift.h> #include "tpm-utils.h" #include <bloblist.h> +#include <dm/devres.h> +#include <lmb.h> int tcg2_get_pcr_info(struct udevice *dev, u32 *supported_bank, u32 *active_bank, u32 *bank_num) @@ -205,6 +207,9 @@ static int tcg2_log_append_check(struct tcg2_event_log *elog, u32 pcr_index, u32 event_size; u8 *log; + if (!elog->log_size) + return 0; + event_size = size + tcg2_event_get_size(digest_list); if (elog->log_position + event_size > elog->log_size) { printf("%s: log too large: %u + %u > %u\n", __func__, @@ -533,6 +538,14 @@ int tcg2_measure_data(struct udevice *dev, struct tcg2_event_log *elog, struct tpml_digest_values digest_list; int rc; + if (!elog) + elog = tcg2_platform_get_dev_log(dev); + + if (!elog) { + log_err("no event log allocated"); + return -ENOENT; + } + if (data) rc = tcg2_create_digest(dev, data, size, &digest_list); else @@ -555,11 +568,29 @@ int tcg2_log_prepare_buffer(struct udevice *dev, struct tcg2_event_log *elog, int rc; u32 log_active = 0; - elog->log_position = 0; - elog->found = false; + if (elog) { + elog->log_position = 0; + elog->found = false; + elog->allocated = false; + } else if (tcg2_platform_get_dev_log(dev)) { + return -EEXIST; + } rc = tcg2_platform_get_log(dev, (void **)&log.log, &log.log_size); - if (!rc) { + /* no existing event log found and none allocated yet */ + if (rc && !elog) { + /* magic size value. Same as in acpi_table.c */ + log.log_size = 0x10000; + log.log = devm_kzalloc(dev, log.log_size, 0); + if (log.log) { + log.allocated = true; + ignore_existing_log = true; + } else { + log_err("Failed to allocate %u bytes event log\n", log.log_size); + } + } + + if (log.log) { log.log_position = 0; log.found = false; @@ -571,26 +602,40 @@ int tcg2_log_prepare_buffer(struct udevice *dev, struct tcg2_event_log *elog, return rc; } - if (elog->log_size) { - if (log.found) { - if (elog->log_size < log.log_position) - return -ENOBUFS; - - /* - * Copy the discovered log into the user buffer - * if there's enough space. - */ - memcpy(elog->log, log.log, log.log_position); + if (elog) { + if (elog->log_size) { + if (log.found) { + if (elog->log_size < log.log_position) + return -ENOBUFS; + + /* + * Copy the discovered log into the user buffer + * if there's enough space. + */ + memcpy(elog->log, log.log, log.log_position); + } + + unmap_physmem(log.log, MAP_NOCACHE); + } else { + elog->log = log.log; + elog->log_size = log.log_size; } - unmap_physmem(log.log, MAP_NOCACHE); + elog->log_position = log.log_position; + elog->found = log.found; } else { + struct tpm_chip_priv *priv = dev_get_uclass_priv(dev); + + elog = devm_kzalloc(dev, sizeof(struct tcg2_event_log), 0); + elog->log = log.log; elog->log_size = log.log_size; - } + elog->log_position = log.log_position; + elog->found = log.found; + elog->allocated = log.allocated; - elog->log_position = log.log_position; - elog->found = log.found; + priv->log = elog; + } } pcr_allocate: @@ -603,8 +648,10 @@ pcr_allocate: * valid. User's can pass in their own buffer as a fallback if no * memory region is found. */ - if (!elog->found && elog->log_size) - rc = tcg2_log_init(dev, elog); + if (elog) { + if (!elog->found && elog->log_size) + rc = tcg2_log_init(dev, elog); + } return rc; } @@ -618,6 +665,9 @@ int tcg2_measurement_init(struct udevice **dev, struct tcg2_event_log *elog, if (rc) return rc; + if (!elog && tcg2_platform_get_dev_log(*dev)) + return -EEXIST; + rc = tpm_auto_start(*dev); if (rc) return rc; @@ -653,6 +703,15 @@ void tcg2_measurement_term(struct udevice *dev, struct tcg2_event_log *elog, unmap_physmem(elog->log, MAP_NOCACHE); } +struct tcg2_event_log *tcg2_platform_get_dev_log(struct udevice *dev) +{ + struct tpm_chip_priv *priv; + + priv = dev_get_uclass_priv(dev); + + return priv->log; +} + __weak int tcg2_platform_get_log(struct udevice *dev, void **addr, u32 *size) { const __be32 *addr_prop = NULL; -- 2.34.1

EFI measures the secure boot state including DB etc in PCR7. When using FIT signatures we can measure the known RSA keys instead. Short of a DER encoder we can pick the ssh way of serializing RSA keys. Signed-off-by: Ludwig Nussel <ludwig.nussel@siemens.com> --- boot/Kconfig | 8 +++++ boot/bootm.c | 84 +++++++++++++++++++++++++++++++++++++++----- boot/image-fit-sig.c | 3 ++ include/spbuf.h | 28 +++++++++++++++ lib/Makefile | 2 ++ lib/spbuf.c | 64 +++++++++++++++++++++++++++++++++ 6 files changed, 180 insertions(+), 9 deletions(-) create mode 100644 include/spbuf.h create mode 100644 lib/spbuf.c diff --git a/boot/Kconfig b/boot/Kconfig index 2ff6f003738..b2f16ab6b27 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -1051,6 +1051,14 @@ if MEASURED_BOOT through system resets and are the first stage bootloader, then this option should be enabled to ignore any existing data in the event log memory region. + +if FIT_SIGNATURE + config MEASURE_TRUSTED_KEYS + bool "Measure FIT signature keys instead of kernel and initrd" + help + Measure the trusted keys used for signature verification + of fit images instead of kernel, initrd etc checksums +endif # FIT_SIGNATURE endif # MEASURED_BOOT config SYS_BOOTM_LEN diff --git a/boot/bootm.c b/boot/bootm.c index 33ff67aa53d..a99be45ffaa 100644 --- a/boot/bootm.c +++ b/boot/bootm.c @@ -28,6 +28,13 @@ #if defined(CONFIG_CMD_USB) #include <usb.h> #endif +#if defined(CONFIG_MEASURE_TRUSTED_KEYS) +#include <string.h> +#include <spbuf.h> +#ifdef LOG_DEBUG +#include <u-boot/md5.h> +#endif +#endif #else #include "mkimage.h" #endif @@ -908,6 +915,61 @@ int bootm_process_cmdline_env(int flags) return 0; } +static int bootm_measure_keys(struct udevice *dev, const void *key_blob) +{ + int ret = 0; +#if defined(CONFIG_MEASURE_TRUSTED_KEYS) + int noffset; + int sig_node; + + log_info("Measuring keys\n"); + + sig_node = fdt_subnode_offset(key_blob, 0, FIT_SIG_NODENAME); + if (sig_node < 0) { + debug("%s: No signature node found: %s\n", __func__, + fdt_strerror(sig_node)); + return 0; + } + + fdt_for_each_subnode(noffset, key_blob, sig_node) { + const char *keyname = fit_get_name(key_blob, noffset, NULL); + + log_info(" %s\n", keyname); + + u32 nlen = 0; + u32 elen = 0; + const u8 *n = fdt_getprop(key_blob, noffset, "rsa,modulus", &nlen); + const u8 *e = fdt_getprop(key_blob, noffset, "rsa,exponent", &elen); + /* ssh-keygen style digest. dt has + * properties big endian, just as we + * need it for this purpose + */ + if (!n || !e) { + log_err(" Missing modulus and/or exponent\n"); + continue; + } + u8 *p = NULL, *buf = NULL; + + p = spbuf_put_rsa_pubkey(buf, n, nlen, e, elen); + buf = calloc(p - buf, 1); + p = spbuf_put_rsa_pubkey(buf, n, nlen, e, elen); + +#ifdef LOG_DEBUG + char fpr[MD5_SUM_LEN] = {0}; + + md5_wd(buf, p - buf, fpr, CHUNKSZ_MD5); + log_info(" %*pX\n", (int)sizeof(fpr), fpr); +#endif + int r = tcg2_measure_data(dev, NULL, 7, p - buf, + buf, EV_COMPACT_HASH, + strlen(keyname) + 1, (u8 *)keyname); + if (r) + ret = 1; + } +#endif + return ret; +} + int bootm_measure(struct bootm_headers *images) { int ret = 0; @@ -919,7 +981,6 @@ int bootm_measure(struct bootm_headers *images) return ret; if (IS_ENABLED(CONFIG_MEASURED_BOOT)) { - struct tcg2_event_log elog; struct udevice *dev; void *initrd_buf; void *image_buf; @@ -927,15 +988,19 @@ int bootm_measure(struct bootm_headers *images) u32 rd_len; bool ign; - elog.log_size = 0; ign = IS_ENABLED(CONFIG_MEASURE_IGNORE_LOG); - ret = tcg2_measurement_init(&dev, &elog, ign); - if (ret) + ret = tcg2_measurement_init(&dev, NULL, ign); + if (ret && ret != -EEXIST) return ret; + if (CONFIG_IS_ENABLED(MEASURE_TRUSTED_KEYS)) { + ret = bootm_measure_keys(dev, gd_fdt_blob()); + goto measurement_done; + } + image_buf = map_sysmem(images->os.image_start, images->os.image_len); - ret = tcg2_measure_data(dev, &elog, 8, images->os.image_len, + ret = tcg2_measure_data(dev, NULL, 8, images->os.image_len, image_buf, EV_COMPACT_HASH, strlen("linux") + 1, (u8 *)"linux"); if (ret) @@ -943,14 +1008,14 @@ int bootm_measure(struct bootm_headers *images) rd_len = images->rd_end - images->rd_start; initrd_buf = map_sysmem(images->rd_start, rd_len); - ret = tcg2_measure_data(dev, &elog, 9, rd_len, initrd_buf, + ret = tcg2_measure_data(dev, NULL, 9, rd_len, initrd_buf, EV_COMPACT_HASH, strlen("initrd") + 1, (u8 *)"initrd"); if (ret) goto unmap_initrd; if (IS_ENABLED(CONFIG_MEASURE_DEVICETREE)) { - ret = tcg2_measure_data(dev, &elog, 1, images->ft_len, + ret = tcg2_measure_data(dev, NULL, 1, images->ft_len, (u8 *)images->ft_addr, EV_TABLE_OF_DEVICES, strlen("dts") + 1, @@ -962,7 +1027,7 @@ int bootm_measure(struct bootm_headers *images) s = env_get("bootargs"); if (!s) s = ""; - ret = tcg2_measure_data(dev, &elog, 1, strlen(s) + 1, (u8 *)s, + ret = tcg2_measure_data(dev, NULL, 1, strlen(s) + 1, (u8 *)s, EV_PLATFORM_CONFIG_FLAGS, strlen(s) + 1, (u8 *)s); @@ -971,7 +1036,8 @@ unmap_initrd: unmap_image: unmap_sysmem(image_buf); - tcg2_measurement_term(dev, &elog, ret != 0); +measurement_done: + tcg2_measurement_term(dev, NULL, ret != 0); } return ret; diff --git a/boot/image-fit-sig.c b/boot/image-fit-sig.c index f23e9d5d0b0..550c45edbaa 100644 --- a/boot/image-fit-sig.c +++ b/boot/image-fit-sig.c @@ -10,6 +10,9 @@ #include <log.h> #include <malloc.h> #include <asm/global_data.h> +#include <linux/sizes.h> +#include <tpm-v2.h> +#include <tpm_tcg2.h> DECLARE_GLOBAL_DATA_PTR; #endif /* !USE_HOSTCC*/ #include <fdt_region.h> diff --git a/include/spbuf.h b/include/spbuf.h new file mode 100644 index 00000000000..0b58f0d5daa --- /dev/null +++ b/include/spbuf.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * very basic serialization of data using ssh protocol (rfc4251) + * style buffers. Ie numbers are stored big endian. Variable data is + * prefixed with a 32bit length field. + * + * The functions can be called with a NULL argument as buffer to + * only have the required length calculated. + * + * Copyright (c) 2025 Siemens AG + */ + +#ifndef _SPBUF_H +#define _SPBUF_H + +#include <string.h> + +#ifdef USE_HOSTCC +typedef uint8_t u8; +typedef uint32_t u32; +#endif + +u8 *spbuf_put_u32(u8 *buf, u32 num); +u8 *spbuf_put_string(u8 *buf, const char *str); +u8 *spbuf_put_bignum(u8 *buf, const u8 *bn, u32 len); +u8 *spbuf_put_rsa_pubkey(u8 *buf, const u8 *n, u32 nlen, const u8 *e, u32 elen); + +#endif diff --git a/lib/Makefile b/lib/Makefile index 2643bfc867c..1f57a6093f2 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -161,6 +161,8 @@ obj-$(CONFIG_$(PHASE_)SEMIHOSTING) += semihosting.o obj-$(CONFIG_UTHREAD) += uthread.o +obj-$(CONFIG_MEASURE_TRUSTED_KEYS) += spbuf.o + # # Build a fast OID lookup registry from include/linux/oid_registry.h # diff --git a/lib/spbuf.c b/lib/spbuf.c new file mode 100644 index 00000000000..5ff5030db83 --- /dev/null +++ b/lib/spbuf.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 Siemens AG + */ + +#include <spbuf.h> + +u8 *spbuf_put_u32(u8 *buf, u32 num) +{ + if (buf) { + buf[0] = (num >> 24) & 0xff; + buf[1] = (num >> 16) & 0xff; + buf[2] = (num >> 8) & 0xff; + buf[3] = num & 0xff; + } + return buf + 4; +} + +u8 *spbuf_put_string(u8 *buf, const char *str) +{ + u32 len = strlen(str); + u8 *p = spbuf_put_u32(buf, len); + + if (buf) + memcpy(p, str, len); + return p + len; +} + +u8 *spbuf_put_bignum(u8 *buf, const u8 *bn, u32 len) +{ + u8 *p; + int prepend = 0; + /* skip leading zeroes */ + while (!*bn && len) { + ++bn; + --len; + } + if (len && *bn & 0x80) + prepend = 1; + p = spbuf_put_u32(buf, len + prepend); + if (prepend) { + if (buf) + *p = 0; + ++p; + } + if (len && buf) + memcpy(p, bn, len); + + return p + len; +} + +u8 *spbuf_put_rsa_pubkey(u8 *buf, const u8 *n, u32 nlen, const u8 *e, u32 elen) +{ + u8 *p, *len = NULL; + + p = spbuf_put_string(buf, "ssh-rsa"); + if (buf) buf = p; else len = p; + p = spbuf_put_bignum(buf, e, elen); + if (buf) buf = p; else len += (ptrdiff_t)p; + p = spbuf_put_bignum(buf, n, nlen); + if (buf) buf = p; else len += (ptrdiff_t)p; + + return buf ?: len; +} -- 2.34.1

Signed-off-by: Ludwig Nussel <ludwig.nussel@siemens.com> --- arch/arm/dts/imx8mm-u-boot.dtsi | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/dts/imx8mm-u-boot.dtsi b/arch/arm/dts/imx8mm-u-boot.dtsi index 59453dc36d3..cd9e8257c93 100644 --- a/arch/arm/dts/imx8mm-u-boot.dtsi +++ b/arch/arm/dts/imx8mm-u-boot.dtsi @@ -15,6 +15,11 @@ }; }; #endif +#ifdef CONFIG_TPM2_FTPM_TEE + tpm@0 { + compatible = "microsoft,ftpm"; + }; +#endif }; #ifdef CONFIG_FSL_CAAM -- 2.34.1

So far the tool could only assemble imx8 images but not show anything about the content Signed-off-by: Ludwig Nussel <ludwig.nussel@siemens.com> --- tools/imx8image.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/tools/imx8image.c b/tools/imx8image.c index 5a76643c06e..758bd570455 100644 --- a/tools/imx8image.c +++ b/tools/imx8image.c @@ -33,8 +33,97 @@ static void imx8image_set_header(void *ptr, struct stat *sbuf, int ifd, { } +static int imx8image_verify_header(unsigned char *ptr, int image_size, + struct image_tool_params *params) +{ + flash_header_v3_t *header = (flash_header_v3_t *) ptr; + + if (header->tag == IVT_HEADER_TAG_B0 && + header->version == IVT_VERSION_B0 && + header->num_images) + return 0; + + return 1; +} + +static void imx8image_print_flash_header_v3(const flash_header_v3_t *header, + struct image_tool_params *params) +{ + static const char * const img_type_table[8] = { + "?", "CSF", "SCD", "EXEC", "DATA", "DCD_DDR", "SECO", "SENTINEL" }; + static const char * const core_table[8] = { + "?", "SC", "CM4_0", "CM4_1", "CA35/CA53", "CA72", "SECO", "?" }; + + printf(" Version: 0x%02x\n" + " Length: 0x%04x\n" + " Tag: 0x%02x\n" + " Flags: 0x%08x\n" + " SW Version: 0x%04x\n" + " Fuse Version 0x%02x\n" + " Num Images: %4u\n" + " Sig Blk Offset: %4u\n", + header->version, + le16_to_cpu(header->length), + header->tag, + header->flags, + header->sw_version, + header->fuse_version, + header->num_images, + header->sig_blk_offset + ); + + for (int i = 0; i < header->num_images; ++i) { + const boot_img_t* h = &header->img[i]; + printf(" Image #%d\n" + " Offset: 0x%08x\n" + " Size: 0x%08x\n" + " Dst: 0x%016" PRIx64 "\n" + " Entry: 0x%016" PRIx64 "\n" + " HAB Flags: 0x%08x\n" + " Image Type: %10s\n" + " Core: %10s\n" + " Meta: 0x%08x\n" + " Hash: 0x%02x%02x%02x..\n" + " IV: 0x%02x%02x%02x..\n", + i, + le32_to_cpu(h->offset), + le32_to_cpu(h->size), + le64_to_cpu(h->dst), + le64_to_cpu(h->entry), + le32_to_cpu(h->hab_flags), + img_type_table[h->hab_flags & 0x7], + core_table[(h->hab_flags >> BOOT_IMG_FLAGS_CORE_SHIFT) & BOOT_IMG_FLAGS_CORE_MASK], + le32_to_cpu(h->meta), + h->hash[0], h->hash[1], h->hash[2], + h->iv[0], h->iv[1], h->iv[2] + ); + } + printf(" Signature\n" + " Version: 0x%02x\n" + " Length: 0x%04x\n" + " Tag: 0x%02x\n" + " SRK Offset: 0x%04x\n" + " Cert Offset: 0x%04x\n" + " Blob Offset: 0x%04x\n" + " Sig Offset: 0x%04x\n", + header->sig_blk_hdr.version, + header->sig_blk_hdr.length, + header->sig_blk_hdr.tag, + header->sig_blk_hdr.srk_table_offset, + header->sig_blk_hdr.cert_offset, + header->sig_blk_hdr.blob_offset, + header->sig_blk_hdr.signature_offset + ); +} + static void imx8image_print_header(const void *ptr, struct image_tool_params *params) { + printf("Container #0:\n"); + imx8image_print_flash_header_v3(ptr, params); + if (imx8image_verify_header((void *)ptr + 0x400, 0, 0) == 0) { + printf("Container #1:\n"); + imx8image_print_flash_header_v3(ptr + 0x400, params); + } } static int imx8image_check_image_types(uint8_t type) @@ -571,7 +660,7 @@ static void set_image_hash(boot_img_t *img, char *filename, uint32_t hash_type) } for (i = 0; i < strlen(hash) / 2; i++) { - ret = sscanf(hash + 2 * i, "%02hhx", &img->hash[i]); + ret = sscanf(hash + 2 * i, "%02x", &img->hash[i]); if (ret < 0) { fprintf(stderr, "Failed sscanf hash: %d\n", ret); exit(EXIT_FAILURE); @@ -1191,7 +1280,7 @@ U_BOOT_IMAGE_TYPE( 0, NULL, imx8image_check_params, - NULL, + imx8image_verify_header, imx8image_print_header, imx8image_set_header, NULL, -- 2.34.1

Missing include statement makes build failf CONFIG_MEASURED_BOOT is enabled Signed-off-by: Ludwig Nussel <ludwig.nussel@siemens.com> --- test/boot/measurement.c | 1 + 1 file changed, 1 insertion(+) diff --git a/test/boot/measurement.c b/test/boot/measurement.c index 71f503f1567..c2a48ab2c41 100644 --- a/test/boot/measurement.c +++ b/test/boot/measurement.c @@ -12,6 +12,7 @@ #include <test/test.h> #include <test/ut.h> #include <asm/io.h> +#include <mapmem.h> #define MEASUREMENT_TEST(_name, _flags) \ UNIT_TEST(_name, _flags, measurement) -- 2.34.1

Attached host images need to be booted with the u-boot built in fs drivers rather than sandboxfs Signed-off-by: Ludwig Nussel <ludwig.nussel@siemens.com> --- drivers/block/Makefile | 2 +- drivers/block/host_bootdev.c | 40 +++++++++++++++++++ fs/sandbox/Makefile | 2 +- .../{host_bootdev.c => sandboxfs_bootdev.c} | 18 ++++----- 4 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 drivers/block/host_bootdev.c rename fs/sandbox/{host_bootdev.c => sandboxfs_bootdev.c} (65%) diff --git a/drivers/block/Makefile b/drivers/block/Makefile index f5a9d8637a3..acad2488040 100644 --- a/drivers/block/Makefile +++ b/drivers/block/Makefile @@ -13,7 +13,7 @@ ifndef CONFIG_XPL_BUILD obj-$(CONFIG_IDE) += ide.o obj-$(CONFIG_RKMTD) += rkmtd.o endif -obj-$(CONFIG_SANDBOX) += sandbox.o host-uclass.o host_dev.o +obj-$(CONFIG_SANDBOX) += sandbox.o host-uclass.o host_dev.o host_bootdev.o obj-$(CONFIG_$(PHASE_)BLOCK_CACHE) += blkcache.o obj-$(CONFIG_$(PHASE_)BLKMAP) += blkmap.o obj-$(CONFIG_$(PHASE_)BLKMAP) += blkmap_helper.o diff --git a/drivers/block/host_bootdev.c b/drivers/block/host_bootdev.c new file mode 100644 index 00000000000..fae35f091bd --- /dev/null +++ b/drivers/block/host_bootdev.c @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootdev for host + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ +#include <bootdev.h> +#include <dm.h> + +static int host_bootdev_bind(struct udevice *dev) +{ + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); + + ucp->prio = BOOTDEVP_2_INTERNAL_FAST; + + return 0; +} + +struct bootdev_ops host_bootdev_ops = { +}; + +static const struct udevice_id host_bootdev_ids[] = { + { .compatible = "u-boot,bootdev-host" }, + { } +}; + +U_BOOT_DRIVER(host_bootdev) = { + .name = "host_bootdev", + .id = UCLASS_BOOTDEV, + .ops = &host_bootdev_ops, + .bind = host_bootdev_bind, + .of_match = host_bootdev_ids, +}; + +BOOTDEV_HUNTER(host_bootdev_hunter) = { + .prio = BOOTDEVP_2_INTERNAL_FAST, + .uclass = UCLASS_HOST, + .drv = DM_DRIVER_REF(host_bootdev), +}; diff --git a/fs/sandbox/Makefile b/fs/sandbox/Makefile index 54ad842d1fe..97926a7f184 100644 --- a/fs/sandbox/Makefile +++ b/fs/sandbox/Makefile @@ -9,4 +9,4 @@ # Pavel Bartusek, Sysgo Real-Time Solutions AG, pba@sysgo.de obj-y := sandboxfs.o -obj-$(CONFIG_$(PHASE_)BOOTSTD) += host_bootdev.o +obj-$(CONFIG_$(PHASE_)BOOTSTD) += sandboxfs_bootdev.o diff --git a/fs/sandbox/host_bootdev.c b/fs/sandbox/sandboxfs_bootdev.c similarity index 65% rename from fs/sandbox/host_bootdev.c rename to fs/sandbox/sandboxfs_bootdev.c index 3f74972a9f8..82eb124d55f 100644 --- a/fs/sandbox/host_bootdev.c +++ b/fs/sandbox/sandboxfs_bootdev.c @@ -12,7 +12,7 @@ #include <dm.h> #include <fs.h> -static int host_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, +static int sandbox_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, struct bootflow *bflow) { int ret; @@ -38,18 +38,18 @@ static int host_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, return 0; } -struct bootdev_ops host_bootdev_ops = { - .get_bootflow = host_get_bootflow, +struct bootdev_ops sandbox_bootdev_ops = { + .get_bootflow = sandbox_get_bootflow, }; -static const struct udevice_id host_bootdev_ids[] = { - { .compatible = "sandbox,bootdev-host" }, +static const struct udevice_id sandbox_bootdev_ids[] = { + { .compatible = "sandbox,bootdev-sandbox" }, { } }; -U_BOOT_DRIVER(host_bootdev) = { - .name = "host_bootdev", +U_BOOT_DRIVER(sandbox_bootdev) = { + .name = "sandbox_bootdev", .id = UCLASS_BOOTDEV, - .ops = &host_bootdev_ops, - .of_match = host_bootdev_ids, + .ops = &sandbox_bootdev_ops, + .of_match = sandbox_bootdev_ids, }; -- 2.34.1

Measuring bootm is a separate step that needs an extra subcommand to trigger Signed-off-by: Ludwig Nussel <ludwig.nussel@siemens.com> --- cmd/bootm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/bootm.c b/cmd/bootm.c index a2d811a1cbc..7b5cf5093d9 100644 --- a/cmd/bootm.c +++ b/cmd/bootm.c @@ -41,6 +41,7 @@ static int do_imls(struct cmd_tbl *cmdtp, int flag, int argc, * function pointer */ static struct cmd_tbl cmd_bootm_sub[] = { U_BOOT_CMD_MKENT(start, 0, 1, (void *)BOOTM_STATE_START, "", ""), + U_BOOT_CMD_MKENT(measure, 0, 1, (void *)BOOTM_STATE_MEASURE, "", ""), U_BOOT_CMD_MKENT(loados, 0, 1, (void *)BOOTM_STATE_LOADOS, "", ""), #ifdef CONFIG_CMD_BOOTM_PRE_LOAD U_BOOT_CMD_MKENT(preload, 0, 1, (void *)BOOTM_STATE_PRE_LOAD, "", ""), -- 2.34.1

Signed-off-by: Ludwig Nussel <ludwig.nussel@siemens.com> --- boot/image-pre-load.c | 3 --- tools/mkimage.h | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/boot/image-pre-load.c b/boot/image-pre-load.c index 2f851ebb28c..cd319ddb777 100644 --- a/boot/image-pre-load.c +++ b/boot/image-pre-load.c @@ -35,9 +35,6 @@ ulong image_load_offset; */ static struct image_sig_info *host_info; -#define log_info(fmt, args...) printf(fmt, ##args) -#define log_err(fmt, args...) printf(fmt, ##args) - void image_pre_load_sig_set_info(struct image_sig_info *info) { host_info = info; diff --git a/tools/mkimage.h b/tools/mkimage.h index 5d6bcc9301a..62fbfcacbb3 100644 --- a/tools/mkimage.h +++ b/tools/mkimage.h @@ -31,6 +31,8 @@ #endif /* MKIMAGE_DEBUG */ #define log_debug(fmt, args...) debug(fmt, ##args) +#define log_info(fmt, args...) printf(fmt, ##args) +#define log_err(fmt, args...) fprintf(stderr, fmt, ##args) static inline void *map_sysmem(ulong paddr, unsigned long len) { -- 2.34.1

Signature nodes in the device tree are mandatory if u-boot is compiled with signature verification. Allowing signature verification to pass if those nodes are missing would leave the system fail open. Signed-off-by: Ludwig Nussel <ludwig.nussel@siemens.com> --- boot/image-fit-sig.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/boot/image-fit-sig.c b/boot/image-fit-sig.c index 550c45edbaa..5ea354ae75b 100644 --- a/boot/image-fit-sig.c +++ b/boot/image-fit-sig.c @@ -481,7 +481,6 @@ static int fit_config_verify_required_keys(const void *fit, int conf_noffset, int noffset; int key_node; int verified = 0; - int reqd_sigs = 0; bool reqd_policy_all = true; const char *reqd_mode; @@ -502,9 +501,8 @@ static int fit_config_verify_required_keys(const void *fit, int conf_noffset, /* Work out what we need to verify */ key_node = fdt_subnode_offset(key_blob, 0, FIT_SIG_NODENAME); if (key_node < 0) { - debug("%s: No signature node found: %s\n", __func__, - fdt_strerror(key_node)); - return 0; + log_err("No signature node found: %s\n", fdt_strerror(key_node)); + return -EPERM; } /* Get required-mode policy property from DTB */ @@ -531,8 +529,6 @@ static int fit_config_verify_required_keys(const void *fit, int conf_noffset, if (!required || strcmp(required, "conf")) continue; - reqd_sigs++; - ret = fit_config_verify_key(fit, conf_noffset, key_blob, noffset); if (ret) { @@ -548,8 +544,8 @@ static int fit_config_verify_required_keys(const void *fit, int conf_noffset, } } - if (reqd_sigs && !verified) { - printf("Failed to verify 'any' of the required signature(s)\n"); + if (!verified) { + log_err("Failed to verify 'any' of the required signature(s)\n"); return -EPERM; } -- 2.34.1
participants (1)
-
Ludwig Nussel