CBFS verification: support Top Swap redundancy

Separating the bootblock into two copies (in BOOTBLOCK and TOPSWAP fmap
regions) breaks the CBFS verification as TSPI CRTM knows nothing about
the new regions and looks for bootblock in a hard-coded COREBOOT fmap
region.

Introduce and use cbfs_unverified_area_type_alloc() which is an
extension of cbfs_unverified_area_alloc(), very similar to how
cbfs_ro_type_map() is an extension of cbfs_ro_map().  This allows to
specify a region of the bootblock file and skip verification because
bootblock serves as a container of hashes and is not verified itself.

The branching is done on the state of RTC BUC to always use the current
bootblock.  Somewhat confusingly, the measurement always uses BOOTBLOCK
region because with active Top Swap that's the way to access a
memory-mapped TOPSWAP region.

Makefile.mk now verifies both COREBOOT and COREBOOT_TS regions.
cbfstool needed a few updates as well:
 - recognize both "BOOTBLOCK" and "TOPSWAP" regions
 - recognize both "COREBOOT" and "COREBOOT_TS" regions
 - reset metadata cache before processing each region as cache may now
   be invalid

SMM doesn't link with vboot functions, so cbfs_file_hash_mismatch() has
to skip verification in SMM due to the use of CMOS options backend.

This is a part of the bootblock redundancy feature proposed
on the mailing list:
https://mail.coreboot.org/archives/list/coreboot@coreboot.org/thread/C6JN2PB7K7D67EG7OIKB6BBERZU5YV35/

Tested by successfully booting into Protectli VP6670 with Top Swap and
CBFS Verification features enabled and Top Swap state being toggled.

Change-Id: Ia75e714ae84d8c0ae09b27495e3056313b109999
Signed-off-by: Filip Gołaś <filip.golas@3mdeb.com>
Signed-off-by: Sergii Dmytruk <sergii.dmytruk@3mdeb.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/89691
Reviewed-by: Michał Kopeć <michal.kopec@3mdeb.com>
Reviewed-by: Filip Lewiński <filip.lewinski@3mdeb.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
This commit is contained in:
Filip Gołaś 2025-12-10 16:44:24 +02:00 committed by Matt DeVillier
commit 7c7feca258
7 changed files with 96 additions and 22 deletions

View file

@ -984,6 +984,8 @@ CBFS_REGIONS := COREBOOT,COREBOOT_TS
endif
endif
CBFS_REGION_COUNT := $(words $(subst $(comma),$(spc),$(CBFS_REGIONS)))
# regions-for-file - Returns a cbfstool regions parameter
# $(call regions-for-file,$(filename))
# returns "REGION1,REGION2,..."
@ -1356,8 +1358,8 @@ endif # CONFIG_CPU_INTEL_FIRMWARE_INTERFACE_TABLE
$(CBFSTOOL) $@ layout
@printf " CBFSPRINT $(subst $(obj)/,,$(@))\n\n"
ifeq ($(CONFIG_CBFS_VERIFICATION),y)
line=$$($(CBFSTOOL) $@ print -kv 2>/dev/null | grep -F '[CBFS VERIFICATION (COREBOOT)]') ;\
if ! printf "$$line" | grep -q 'fully valid'; then \
line=$$($(CBFSTOOL) $@ print -kv -r $(CBFS_REGIONS) 2>/dev/null | grep -F '[CBFS VERIFICATION') ;\
if [ "$$(printf "$$line" | grep -c 'fully valid')" -ne $(CBFS_REGION_COUNT) ]; then \
echo "CBFS verification error: $$line" ;\
exit 1 ;\
fi

View file

@ -201,7 +201,8 @@ void *_cbfs_alloc(const char *name, cbfs_allocator_t allocator, void *arg,
size_t *size_out, bool force_ro, enum cbfs_type *type);
void *_cbfs_unverified_area_alloc(const char *area, const char *name,
cbfs_allocator_t allocator, void *arg, size_t *size_out);
cbfs_allocator_t allocator, void *arg, size_t *size_out,
enum cbfs_type *type);
struct _cbfs_default_allocator_arg {
void *buf;
@ -242,7 +243,14 @@ static inline void *cbfs_unverified_area_alloc(const char *area, const char *nam
cbfs_allocator_t allocator, void *arg,
size_t *size_out)
{
return _cbfs_unverified_area_alloc(area, name, allocator, arg, size_out);
return _cbfs_unverified_area_alloc(area, name, allocator, arg, size_out, NULL);
}
static inline void *cbfs_unverified_area_type_alloc(const char *area, const char *name,
cbfs_allocator_t allocator, void *arg,
size_t *size_out, enum cbfs_type *type)
{
return _cbfs_unverified_area_alloc(area, name, allocator, arg, size_out, type);
}
static inline void *cbfs_map(const char *name, size_t *size_out)
@ -268,7 +276,7 @@ static inline void *cbfs_ro_type_map(const char *name, size_t *size_out, enum cb
static inline void *cbfs_unverified_area_map(const char *area, const char *name,
size_t *size_out)
{
return _cbfs_unverified_area_alloc(area, name, NULL, NULL, size_out);
return _cbfs_unverified_area_alloc(area, name, NULL, NULL, size_out, NULL);
}
static inline size_t _cbfs_load(const char *name, void *buf, size_t size, bool force_ro,
@ -307,7 +315,7 @@ static inline size_t cbfs_unverified_area_load(const char *area, const char *nam
void *buf, size_t size)
{
struct _cbfs_default_allocator_arg arg = { .buf = buf, .buf_size = size };
if (_cbfs_unverified_area_alloc(area, name, _cbfs_default_allocator, &arg, &size))
if (_cbfs_unverified_area_alloc(area, name, _cbfs_default_allocator, &arg, &size, NULL))
return size;
else
return 0;
@ -341,7 +349,7 @@ static inline void *cbfs_unverified_area_cbmem_alloc(const char *area, const cha
uint32_t cbmem_id, size_t *size_out)
{
return _cbfs_unverified_area_alloc(area, name, _cbfs_cbmem_allocator,
(void *)(uintptr_t)cbmem_id, size_out);
(void *)(uintptr_t)cbmem_id, size_out, NULL);
}
static inline size_t cbfs_get_size(const char *name)

View file

@ -173,7 +173,12 @@ static bool cbfs_file_hash_mismatch(const void *buffer, size_t size,
const struct vb2_hash *hash = NULL;
if (CONFIG(CBFS_VERIFICATION) && !skip_verification) {
/*
* Skipping this block in SMM because vboot library isn't linked to SMM stage. This is
* an issue only if using a CMOS options backend, then SMM refers to an option and tries
* to verify cmos.layout here.
*/
if (CONFIG(CBFS_VERIFICATION) && !ENV_SMM && !skip_verification) {
hash = cbfs_file_hash(mdata);
if (!hash) {
ERROR("'%s' does not have a file hash!\n", mdata->h.filename);
@ -546,7 +551,8 @@ void *_cbfs_alloc(const char *name, cbfs_allocator_t allocator, void *arg,
}
void *_cbfs_unverified_area_alloc(const char *area, const char *name,
cbfs_allocator_t allocator, void *arg, size_t *size_out)
cbfs_allocator_t allocator, void *arg, size_t *size_out,
enum cbfs_type *type)
{
struct region_device area_rdev, file_rdev;
union cbfs_mdata mdata;
@ -562,6 +568,17 @@ void *_cbfs_unverified_area_alloc(const char *area, const char *name,
return NULL;
}
if (type) {
const enum cbfs_type real_type = be32toh(mdata.h.type);
if (*type == CBFS_TYPE_QUERY)
*type = real_type;
else if (*type != real_type) {
ERROR("'%s' type mismatch (is %u, expected %u)\n",
mdata.h.filename, real_type, *type);
return NULL;
}
}
if (rdev_chain(&file_rdev, &area_rdev, data_offset, be32toh(mdata.h.len)))
return NULL;

View file

@ -9,6 +9,10 @@
#include <stdio.h>
#include <string.h>
/* This include is available as <intelblocks/rtc.h> only if CONFIG_SOC_INTEL_COMMON_BLOCK is
set, which is not guaranteed for this file. */
#include <soc/intel/common/block/include/intelblocks/rtc.h>
static int tpm_log_initialized;
static inline int tpm_log_available(void)
{
@ -72,7 +76,28 @@ static tpm_result_t tspi_init_crtm(void)
/* Mapping measures the file. We know we can safely map here because
bootblock-as-a-file is only used on x86, where we don't need cache to map. */
enum cbfs_type type = CBFS_TYPE_BOOTBLOCK;
void *mapping = cbfs_ro_type_map("bootblock", NULL, &type);
void *mapping = NULL;
if (CONFIG(INTEL_TOP_SWAP_SEPARATE_REGIONS)) {
enum ts_config top_swap = get_rtc_buc_top_swap_status();
if (top_swap == TS_ENABLE)
printk(BIOS_INFO,
"CRTM Top Swap: Measuring bootblock in TOPSWAP (will be logged as BOOTBLOCK).\n");
else
printk(BIOS_INFO,
"CRTM Top Swap: Measuring bootblock in BOOTBLOCK.\n");
/*
* Whether Top Swap is active or not, FMAP always refers to the same
* memory ranges but the contents of BOOTBLOCK and TOPSWAP are swapped.
* Hence always using BOOTBLOCK region to access the active bootblock.
*/
mapping = cbfs_unverified_area_type_alloc("BOOTBLOCK", "bootblock",
NULL, NULL, NULL, &type);
} else {
mapping = cbfs_ro_type_map("bootblock", NULL, &type);
}
if (!mapping) {
printk(BIOS_INFO,
"TSPI: Couldn't measure bootblock into CRTM!\n");

View file

@ -10,7 +10,9 @@
#define SECTION_NAME_FMAP "FMAP"
#define SECTION_NAME_PRIMARY_CBFS "COREBOOT"
#define SECTION_NAME_TOPSWAP_CBFS "COREBOOT_TS"
#define SECTION_NAME_BOOTBLOCK "BOOTBLOCK"
#define SECTION_NAME_TOPSWAP "TOPSWAP"
#define SECTION_ANNOTATION_CBFS "CBFS"

View file

@ -135,6 +135,18 @@ struct mh_cache {
bool initialized;
};
static bool is_main_cbfs_region(const char *region_name)
{
return strcmp(region_name, SECTION_NAME_PRIMARY_CBFS) == 0 ||
strcmp(region_name, SECTION_NAME_TOPSWAP_CBFS) == 0;
}
static bool is_topswap_region(const char *region_name)
{
return strcmp(region_name, SECTION_NAME_TOPSWAP_CBFS) == 0 ||
strcmp(region_name, SECTION_NAME_TOPSWAP) == 0;
}
static struct mh_cache *get_mh_cache(void)
{
static struct mh_cache mhc;
@ -148,22 +160,24 @@ static struct mh_cache *get_mh_cache(void)
if (!fmap)
goto no_metadata_hash;
const char *bootblock_region = SECTION_NAME_BOOTBLOCK;
if (is_topswap_region(param.region_name))
bootblock_region = SECTION_NAME_TOPSWAP;
/* Find the metadata_hash container. If there is a "BOOTBLOCK" FMAP section, it's
there. If not, it's a normal file in the primary CBFS section. */
size_t offset, size;
struct buffer buffer;
if (fmap_find_area(fmap, SECTION_NAME_BOOTBLOCK)) {
if (!partitioned_file_read_region(&buffer, param.image_file,
SECTION_NAME_BOOTBLOCK))
if (fmap_find_area(fmap, bootblock_region)) {
if (!partitioned_file_read_region(&buffer, param.image_file, bootblock_region))
goto no_metadata_hash;
mhc.region = SECTION_NAME_BOOTBLOCK;
mhc.region = bootblock_region;
offset = 0;
size = buffer.size;
} else {
struct cbfs_image cbfs;
struct cbfs_file *mh_container;
if (!partitioned_file_read_region(&buffer, param.image_file,
SECTION_NAME_PRIMARY_CBFS))
if (!partitioned_file_read_region(&buffer, param.image_file, SECTION_NAME_PRIMARY_CBFS))
goto no_metadata_hash;
mhc.region = SECTION_NAME_PRIMARY_CBFS;
if (cbfs_image_from_buffer(&cbfs, &buffer, param.headeroffset))
@ -245,7 +259,7 @@ static int update_anchor(struct mh_cache *mhc, uint8_t *fmap_hash)
will recalculate and update the metadata hash in the bootblock if needed. */
static int maybe_update_metadata_hash(struct cbfs_image *cbfs)
{
if (strcmp(param.region_name, SECTION_NAME_PRIMARY_CBFS))
if (!is_main_cbfs_region(param.region_name))
return 0; /* Metadata hash only embedded in primary CBFS. */
struct mh_cache *mhc = get_mh_cache();
@ -268,6 +282,7 @@ static int maybe_update_metadata_hash(struct cbfs_image *cbfs)
static int maybe_update_fmap_hash(void)
{
if (strcmp(param.region_name, SECTION_NAME_BOOTBLOCK) &&
strcmp(param.region_name, SECTION_NAME_TOPSWAP) &&
strcmp(param.region_name, SECTION_NAME_FMAP) &&
param.type != CBFS_TYPE_BOOTBLOCK &&
param.type != CBFS_TYPE_AMDFW)
@ -1603,7 +1618,7 @@ static int cbfs_print(void)
vb2_digest_size(real_hash.algo));
printf("[METADATA HASH]\t%s:%s",
vb2_get_hash_algorithm_name(real_hash.algo), hash_str);
if (!strcmp(param.region_name, SECTION_NAME_PRIMARY_CBFS)) {
if (is_main_cbfs_region(param.region_name)) {
if (!memcmp(mhc->cbfs_hash.raw, real_hash.raw,
vb2_digest_size(real_hash.algo))) {
printf(":valid");
@ -2364,6 +2379,10 @@ int main(int argc, char **argv)
strcpy(region_name_scratch, param.region_name);
param.region_name = strtok(region_name_scratch, ",");
for (unsigned region = 0; region < num_regions; ++region) {
// Reset metadata cache in case region uses anchor from a different
// location.
get_mh_cache()->initialized = false;
if (!param.region_name) {
ERROR("Encountered illegal degenerate region name in -r list\n");
ERROR("The image will be left unmodified.\n");
@ -2371,8 +2390,7 @@ int main(int argc, char **argv)
return 1;
}
if (strcmp(param.region_name, SECTION_NAME_PRIMARY_CBFS)
== 0)
if (is_main_cbfs_region(param.region_name))
seen_primary_cbfs = true;
param.image_region = image_regions + region;

View file

@ -312,12 +312,14 @@ static int mediatek_fixup(struct buffer *buffer, unused size_t offset)
platform_fixup_func platform_fixups_probe(struct buffer *buffer, size_t offset,
const char *region_name)
{
if (!strcmp(region_name, SECTION_NAME_BOOTBLOCK)) {
if (!strcmp(region_name, SECTION_NAME_BOOTBLOCK) ||
!strcmp(region_name, SECTION_NAME_TOPSWAP)) {
if (qualcomm_probe(buffer, offset))
return qualcomm_fixup;
else if (mediatek_probe(buffer))
return mediatek_fixup;
} else if (!strcmp(region_name, SECTION_NAME_PRIMARY_CBFS)) {
} else if (!strcmp(region_name, SECTION_NAME_PRIMARY_CBFS) ||
!strcmp(region_name, SECTION_NAME_TOPSWAP_CBFS)) {
/* TODO: add fixups for primary CBFS bootblock platforms, if needed */
} else {
ERROR("%s called for unexpected FMAP region %s!\n", __func__, region_name);