diff --git a/src/commonlib/bsd/include/commonlib/bsd/metadata_hash.h b/src/commonlib/bsd/include/commonlib/bsd/metadata_hash.h new file mode 100644 index 0000000000..d5e54b508e --- /dev/null +++ b/src/commonlib/bsd/include/commonlib/bsd/metadata_hash.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-only */ + +#ifndef _COMMONLIB_BSD_METADATA_HASH_H_ +#define _COMMONLIB_BSD_METADATA_HASH_H_ + +#include +#include + +/* This structure is embedded somewhere in the (uncompressed) bootblock. */ +struct metadata_hash_anchor { + uint8_t magic[8]; + struct vb2_hash cbfs_hash; + /* NOTE: This is just reserving space. sizeof(struct vb2_hash) may change between + configurations/versions and cannot be relied upon, so the FMAP hash must be placed + right after the actual data for the particular CBFS hash algorithm used ends. */ + uint8_t reserved_space_for_fmap_hash[VB2_MAX_DIGEST_SIZE]; +} __packed; + +/* Always use this function to figure out the actual location of the FMAP hash. It always uses + the same algorithm as the CBFS hash. */ +static inline uint8_t *metadata_hash_anchor_fmap_hash(struct metadata_hash_anchor *anchor) +{ + return anchor->cbfs_hash.raw + vb2_digest_size(anchor->cbfs_hash.algo); +} + +/* + * Do not use this constant anywhere else in coreboot code to ensure the bit pattern really only + * appears once in the CBFS image. The only coreboot file allowed to use this is + * src/lib/metadata_anchor.c to define the actual anchor data structure. It is defined here so + * that it can be shared with cbfstool (which may use it freely). + */ +#define DO_NOT_USE_METADATA_HASH_ANCHOR_MAGIC_DO_NOT_USE "\xadMdtHsh\x15" + +#endif /* _COMMONLIB_BSD_MASTER_HASH_H_ */ diff --git a/src/commonlib/include/commonlib/cbmem_id.h b/src/commonlib/include/commonlib/cbmem_id.h index ab7cf63843..6e24545110 100644 --- a/src/commonlib/include/commonlib/cbmem_id.h +++ b/src/commonlib/include/commonlib/cbmem_id.h @@ -25,6 +25,7 @@ #define CBMEM_ID_IGD_OPREGION 0x4f444749 #define CBMEM_ID_IMD_ROOT 0xff4017ff #define CBMEM_ID_IMD_SMALL 0x53a11439 +#define CBMEM_ID_MDATA_HASH 0x6873484D #define CBMEM_ID_MEMINFO 0x494D454D #define CBMEM_ID_MMA_DATA 0x4D4D4144 #define CBMEM_ID_MMC_STATUS 0x4d4d4353 diff --git a/src/include/cbfs.h b/src/include/cbfs.h index 8d4c2209d2..cad01c623d 100644 --- a/src/include/cbfs.h +++ b/src/include/cbfs.h @@ -7,6 +7,7 @@ #include #include #include +#include /*********************************************** * Perform CBFS operations on the boot device. * @@ -74,4 +75,13 @@ void cbfs_boot_device_find_mcache(struct cbfs_boot_device *cbd, uint32_t id); */ const struct cbfs_boot_device *cbfs_get_boot_device(bool force_ro); +/* + * Builds the mcache (if |cbd->mcache| is set) and verifies |metadata_hash| (if + * it is not NULL). If CB_CBFS_CACHE_FULL is returned, the mcache is incomplete + * but still valid and the metadata hash was still verified. Should be called + * once per *boot* (not once per stage) before the first CBFS access. + */ +cb_err_t cbfs_init_boot_device(const struct cbfs_boot_device *cbd, + struct vb2_hash *metadata_hash); + #endif diff --git a/src/include/cbfs_glue.h b/src/include/cbfs_glue.h index ebfbc2e7ae..ffca83ef06 100644 --- a/src/include/cbfs_glue.h +++ b/src/include/cbfs_glue.h @@ -5,8 +5,19 @@ #include #include +#include -#define CBFS_ENABLE_HASHING 0 +/* + * This flag prevents linking hashing functions into stages where they're not required. We don't + * need them at all if verification is disabled. If verification is enabled without TOCTOU + * safety, we only need to verify the metadata hash in the initial stage and can assume it stays + * valid in later stages. If TOCTOU safety is required, we may need them in every stage to + * reverify metadata that had to be reloaded from flash (e.g. because it didn't fit the mcache). + * Note that this only concerns metadata hashing -- file access functions may still link hashing + * routines independently for file data hashing. + */ +#define CBFS_ENABLE_HASHING (CONFIG(CBFS_VERIFICATION) && \ + (CONFIG(TOCTOU_SAFETY) || ENV_INITIAL_STAGE)) #define ERROR(...) printk(BIOS_ERR, "CBFS ERROR: " __VA_ARGS__) #define LOG(...) printk(BIOS_ERR, "CBFS: " __VA_ARGS__) diff --git a/src/include/metadata_hash.h b/src/include/metadata_hash.h new file mode 100644 index 0000000000..2d3b8a86bc --- /dev/null +++ b/src/include/metadata_hash.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* This file is part of the coreboot project. */ + +#ifndef _METADATA_HASH_H_ +#define _METADATA_HASH_H_ + +#include + +/* Verify the an FMAP data structure with the FMAP hash that is stored together with the CBFS + metadata hash in the bootblock's metadata hash anchor (when CBFS verification is enabled). */ +vb2_error_t metadata_hash_verify_fmap(const void *fmap_base, size_t fmap_size); + +#if CONFIG(CBFS_VERIFICATION) +/* Get the (RO) CBFS metadata hash for this CBFS image, which forms the root of trust for CBFS + verification. This function is only available in the bootblock. */ +struct vb2_hash *metadata_hash_get(void); +#else +static inline struct vb2_hash *metadata_hash_get(void) { return NULL; } +#endif + +#endif diff --git a/src/lib/Kconfig.cbfs_verification b/src/lib/Kconfig.cbfs_verification new file mode 100644 index 0000000000..34993458cd --- /dev/null +++ b/src/lib/Kconfig.cbfs_verification @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later +# +# This file is part of the coreboot project. +# +# This file is sourced from src/security/Kconfig for menuconfig convenience. + +#menu "CBFS verification" # TODO: enable once it works + +config CBFS_VERIFICATION + bool # TODO: make user selectable once it works + depends on !COMPRESS_BOOTBLOCK # TODO: figure out decompressor anchor + depends on !VBOOT_STARTS_BEFORE_BOOTBLOCK # this is gonna get tricky... + select VBOOT_LIB + help + Work in progress. Do not use (yet). + +config TOCTOU_SAFETY + bool + depends on CBFS_VERIFICATION + depends on !NO_FMAP_CACHE + depends on !NO_CBFS_MCACHE + help + Work in progress. Not actually TOCTOU safe yet. Do not use. + + Design idea here is that mcache overflows in this mode are only legal + for the RW CBFS, because it's relatively easy to retrieve the RW + metadata hash from persistent vboot context at any time, but the RO + metadata hash is lost after the bootblock is unloaded. This avoids the + need to carry yet another piece forward through the stages. Mcache + overflows are mostly a concern for RW updates (if an update adds more + files than originally planned for), for the RO section it should + always be possible to dimension the mcache correctly beforehand, so + this should be an acceptable limitation. + +config CBFS_HASH_ALGO + int + default 1 if CBFS_HASH_SHA1 + default 2 if CBFS_HASH_SHA256 + default 3 if CBFS_HASH_SHA512 + +choice + prompt "--> hash type" + depends on CBFS_VERIFICATION + default CBFS_HASH_SHA256 + +config CBFS_HASH_SHA1 + bool "SHA-1" + +config CBFS_HASH_SHA256 + bool "SHA-256" + +config CBFS_HASH_SHA512 + bool "SHA-512" + +endchoice + +#endmenu diff --git a/src/lib/Makefile.inc b/src/lib/Makefile.inc index 6cff03dc63..9e601eb055 100644 --- a/src/lib/Makefile.inc +++ b/src/lib/Makefile.inc @@ -47,6 +47,7 @@ bootblock-y += prog_ops.c bootblock-y += cbfs.c bootblock-$(CONFIG_GENERIC_GPIO_LIB) += gpio.c bootblock-y += libgcc.c +bootblock-$(CONFIG_CBFS_VERIFICATION) += metadata_hash.c bootblock-$(CONFIG_GENERIC_UDELAY) += timer.c bootblock-$(CONFIG_COLLECT_TIMESTAMPS) += timestamp.c diff --git a/src/lib/cbfs.c b/src/lib/cbfs.c index beab74ec4d..5df1d8bd85 100644 --- a/src/lib/cbfs.c +++ b/src/lib/cbfs.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -29,8 +30,21 @@ cb_err_t cbfs_boot_lookup(const char *name, bool force_ro, if (!CONFIG(NO_CBFS_MCACHE) && !ENV_SMM) err = cbfs_mcache_lookup(cbd->mcache, cbd->mcache_size, name, mdata, &data_offset); - if (err == CB_CBFS_CACHE_FULL) - err = cbfs_lookup(&cbd->rdev, name, mdata, &data_offset, NULL); + if (err == CB_CBFS_CACHE_FULL) { + struct vb2_hash *metadata_hash = NULL; + if (CONFIG(TOCTOU_SAFETY)) { + if (ENV_SMM) /* Cannot provide TOCTOU safety for SMM */ + dead_code(); + /* We can only reach this for the RW CBFS -- an mcache + overflow in the RO CBFS would have been caught when + building the mcache in cbfs_get_boot_device(). + (Note that TOCTOU_SAFETY implies !NO_CBFS_MCACHE.) */ + assert(cbd == vboot_get_cbfs_boot_device()); + /* TODO: set metadata_hash to RW metadata hash here. */ + } + err = cbfs_lookup(&cbd->rdev, name, mdata, &data_offset, + metadata_hash); + } if (CONFIG(VBOOT_ENABLE_CBFS_FALLBACK) && !force_ro && err == CB_CBFS_NOT_FOUND) { @@ -405,6 +419,26 @@ void cbfs_boot_device_find_mcache(struct cbfs_boot_device *cbd, uint32_t id) } } +cb_err_t cbfs_init_boot_device(const struct cbfs_boot_device *cbd, + struct vb2_hash *metadata_hash) +{ + /* If we have an mcache, mcache_build() will also check mdata hash. */ + if (!CONFIG(NO_CBFS_MCACHE) && !ENV_SMM && cbd->mcache_size > 0) + return cbfs_mcache_build(&cbd->rdev, cbd->mcache, + cbd->mcache_size, metadata_hash); + + /* No mcache and no verification means we have nothing special to do. */ + if (!CONFIG(CBFS_VERIFICATION) || !metadata_hash) + return CB_SUCCESS; + + /* Verification only: use cbfs_walk() without a walker() function to + just run through the CBFS once, will return NOT_FOUND by default. */ + cb_err_t err = cbfs_walk(&cbd->rdev, NULL, NULL, metadata_hash, 0); + if (err == CB_CBFS_NOT_FOUND) + err = CB_SUCCESS; + return err; +} + const struct cbfs_boot_device *cbfs_get_boot_device(bool force_ro) { static struct cbfs_boot_device ro; @@ -426,15 +460,18 @@ const struct cbfs_boot_device *cbfs_get_boot_device(bool force_ro) return &ro; if (fmap_locate_area_as_rdev("COREBOOT", &ro.rdev)) - return NULL; + die("Cannot locate primary CBFS"); cbfs_boot_device_find_mcache(&ro, CBMEM_ID_CBFS_RO_MCACHE); - if (ENV_INITIAL_STAGE && !CONFIG(NO_CBFS_MCACHE)) { - cb_err_t err = cbfs_mcache_build(&ro.rdev, ro.mcache, - ro.mcache_size, NULL); - if (err && err != CB_CBFS_CACHE_FULL) - die("Failed to build RO mcache"); + if (ENV_INITIAL_STAGE) { + cb_err_t err = cbfs_init_boot_device(&ro, metadata_hash_get()); + if (err == CB_CBFS_HASH_MISMATCH) + die("RO CBFS metadata hash verification failure"); + else if (CONFIG(TOCTOU_SAFETY) && err == CB_CBFS_CACHE_FULL) + die("RO mcache overflow breaks TOCTOU safety!\n"); + else if (err && err != CB_CBFS_CACHE_FULL) + die("RO CBFS initialization error: %d", err); } return &ro; diff --git a/src/lib/fmap.c b/src/lib/fmap.c index 377123afdc..2abe138cdd 100644 --- a/src/lib/fmap.c +++ b/src/lib/fmap.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -27,9 +28,20 @@ uint64_t get_fmap_flash_offset(void) return FMAP_OFFSET; } -static int check_signature(const struct fmap *fmap) +static int verify_fmap(const struct fmap *fmap) { - return memcmp(fmap->signature, FMAP_SIGNATURE, sizeof(fmap->signature)); + if (memcmp(fmap->signature, FMAP_SIGNATURE, sizeof(fmap->signature))) + return -1; + + static bool done = false; + if (!CONFIG(CBFS_VERIFICATION) || !ENV_INITIAL_STAGE || done) + return 0; /* Only need to check hash in first stage. */ + + if (metadata_hash_verify_fmap(fmap, FMAP_SIZE) != VB2_SUCCESS) + return -1; + + done = true; + return 0; } static void report(const struct fmap *fmap) @@ -63,10 +75,12 @@ static void setup_preram_cache(struct mem_region_device *cache_mrdev) if (!(ENV_INITIAL_STAGE)) { /* NOTE: This assumes that the first stage will make at least one FMAP access (usually from finding CBFS). */ - if (!check_signature(fmap)) + if (!verify_fmap(fmap)) goto register_cache; printk(BIOS_ERR, "ERROR: FMAP cache corrupted?!\n"); + if (CONFIG(TOCTOU_SAFETY)) + die("TOCTOU safety relies on FMAP cache"); } /* In case we fail below, make sure the cache is invalid. */ @@ -80,7 +94,7 @@ static void setup_preram_cache(struct mem_region_device *cache_mrdev) /* memlayout statically guarantees that the FMAP_CACHE is big enough. */ if (rdev_readat(boot_rdev, fmap, FMAP_OFFSET, FMAP_SIZE) != FMAP_SIZE) return; - if (check_signature(fmap)) + if (verify_fmap(fmap)) return; report(fmap); @@ -111,8 +125,9 @@ static int find_fmap_directory(struct region_device *fmrd) if (fmap == NULL) return -1; - if (check_signature(fmap)) { - printk(BIOS_DEBUG, "No FMAP found at %zx offset.\n", offset); + if (verify_fmap(fmap)) { + printk(BIOS_ERR, "FMAP missing or corrupted at offset 0x%zx!\n", + offset); rdev_munmap(boot, fmap); return -1; } diff --git a/src/lib/metadata_hash.c b/src/lib/metadata_hash.c new file mode 100644 index 0000000000..f296cf58a5 --- /dev/null +++ b/src/lib/metadata_hash.c @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* This file is part of the coreboot project. */ + +#include +#include +#include +#include + +__attribute__((used, section(".metadata_hash_anchor"))) +static struct metadata_hash_anchor metadata_hash_anchor = { + /* This is the only place in all of coreboot where we actually need to use this. */ + .magic = DO_NOT_USE_METADATA_HASH_ANCHOR_MAGIC_DO_NOT_USE, + .cbfs_hash = { .algo = CONFIG_CBFS_HASH_ALGO } +}; + +struct vb2_hash *metadata_hash_get(void) +{ + return &metadata_hash_anchor.cbfs_hash; +} + +vb2_error_t metadata_hash_verify_fmap(const void *fmap_buffer, size_t fmap_size) +{ + struct vb2_hash hash = { .algo = metadata_hash_anchor.cbfs_hash.algo }; + memcpy(hash.raw, metadata_hash_anchor_fmap_hash(&metadata_hash_anchor), + vb2_digest_size(hash.algo)); + return vb2_hash_verify(fmap_buffer, fmap_size, &hash); +} diff --git a/src/lib/program.ld b/src/lib/program.ld index 3b6aa2ecba..94ba409ced 100644 --- a/src/lib/program.ld +++ b/src/lib/program.ld @@ -18,6 +18,7 @@ #if !ENV_X86 && (ENV_DECOMPRESSOR || ENV_BOOTBLOCK && !CONFIG(COMPRESS_BOOTBLOCK)) KEEP(*(.id)); #endif + KEEP(*(.metadata_hash_anchor)); *(.text); *(.text.*); diff --git a/src/security/Kconfig b/src/security/Kconfig index 54d38fb5c2..abbd0b86b8 100644 --- a/src/security/Kconfig +++ b/src/security/Kconfig @@ -1,5 +1,9 @@ # SPDX-License-Identifier: GPL-2.0-only +# These features are implemented in src/lib/cbfs.c, but they are security +# features so sort them in here for menuconfig. +source "src/lib/Kconfig.cbfs_verification" + source "src/security/vboot/Kconfig" source "src/security/tpm/Kconfig" source "src/security/memory/Kconfig" diff --git a/src/security/vboot/vboot_loader.c b/src/security/vboot/vboot_loader.c index 9c6e56e9af..56a0664328 100644 --- a/src/security/vboot/vboot_loader.c +++ b/src/security/vboot/vboot_loader.c @@ -25,18 +25,17 @@ _Static_assert(!CONFIG(VBOOT_RETURN_FROM_VERSTAGE) || int vboot_executed; -static void build_rw_mcache(void) +static void after_verstage(void) { - if (CONFIG(NO_CBFS_MCACHE)) - return; + vboot_executed = 1; /* Mark verstage execution complete. */ const struct cbfs_boot_device *cbd = vboot_get_cbfs_boot_device(); - if (!cbd) /* Don't build RW mcache in recovery mode. */ + if (!cbd) /* Can't initialize RW CBFS in recovery mode. */ return; - cb_err_t err = cbfs_mcache_build(&cbd->rdev, cbd->mcache, - cbd->mcache_size, NULL); - if (err && err != CB_CBFS_CACHE_FULL) - die("Failed to build RW mcache."); /* TODO: -> recovery? */ + + cb_err_t err = cbfs_init_boot_device(cbd, NULL); /* TODO: RW hash */ + if (err && err != CB_CBFS_CACHE_FULL) /* TODO: -> recovery? */ + die("RW CBFS initialization failure: %d", err); } void vboot_run_logic(void) @@ -44,8 +43,7 @@ void vboot_run_logic(void) if (verification_should_run()) { /* Note: this path is not used for VBOOT_RETURN_FROM_VERSTAGE */ verstage_main(); - vboot_executed = 1; - build_rw_mcache(); + after_verstage(); } else if (verstage_should_load()) { struct cbfsf file; struct prog verstage = @@ -72,8 +70,7 @@ void vboot_run_logic(void) if (!CONFIG(VBOOT_RETURN_FROM_VERSTAGE)) return; - vboot_executed = 1; - build_rw_mcache(); + after_verstage(); } } diff --git a/util/cbfstool/cbfs.h b/util/cbfstool/cbfs.h index 9b4d7ae316..7f07751a8f 100644 --- a/util/cbfstool/cbfs.h +++ b/util/cbfstool/cbfs.h @@ -11,6 +11,11 @@ #define CBFS_HEADPTR_ADDR_X86 0xFFFFFFFC +/* cbfstool is allowed to use this constant freely since it's not part of the + CBFS image, so make an alias for the name that's a little less aggressive. */ +#define METADATA_HASH_ANCHOR_MAGIC \ + DO_NOT_USE_METADATA_HASH_ANCHOR_MAGIC_DO_NOT_USE + struct typedesc_t { uint32_t type; const char *name;