summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/commonlib/bsd/include/commonlib/bsd/metadata_hash.h34
-rw-r--r--src/commonlib/include/commonlib/cbmem_id.h1
-rw-r--r--src/include/cbfs.h10
-rw-r--r--src/include/cbfs_glue.h15
-rw-r--r--src/include/metadata_hash.h21
-rw-r--r--src/lib/Kconfig.cbfs_verification57
-rw-r--r--src/lib/Makefile.inc1
-rw-r--r--src/lib/cbfs.c53
-rw-r--r--src/lib/fmap.c27
-rw-r--r--src/lib/metadata_hash.c27
-rw-r--r--src/lib/program.ld1
-rw-r--r--src/security/Kconfig4
-rw-r--r--src/security/vboot/vboot_loader.c21
-rw-r--r--util/cbfstool/cbfs.h5
14 files changed, 249 insertions, 28 deletions
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 <stdint.h>
+#include <vb2_sha.h>
+
+/* 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 <commonlib/cbfs.h>
#include <program_loading.h>
#include <types.h>
+#include <vb2_sha.h>
/***********************************************
* 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 <commonlib/region.h>
#include <console/console.h>
-
-#define CBFS_ENABLE_HASHING 0
+#include <rules.h>
+
+/*
+ * 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 <commonlib/bsd/metadata_hash.h>
+
+/* 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 <console/console.h>
#include <fmap.h>
#include <lib.h>
+#include <metadata_hash.h>
#include <security/tpm/tspi/crtm.h>
#include <security/vboot/vboot_common.h>
#include <stdlib.h>
@@ -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 <cbmem.h>
#include <console/console.h>
#include <fmap.h>
+#include <metadata_hash.h>
#include <stddef.h>
#include <string.h>
#include <symbols.h>
@@ -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 <assert.h>
+#include <cbmem.h>
+#include <metadata_hash.h>
+#include <symbols.h>
+
+__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;