and though bugs are the bane of my existence, rest assured the wretched thing will get the best of care here

...
 
Commits (2)
  • Julius Werner's avatar
    cbfstool: Support CONFIG_CBFS_VERIFICATION and metadata hash anchor · 4bfbabdb
    Julius Werner authored
    This patch adds support for the new CONFIG_CBFS_VERIFICATION feature to
    cbfstool. When CBFS verification is enabled, cbfstool must automatically
    add a hash attribute to every CBFS file it adds (with a handful of
    exceptions like bootblock and "header" pseudofiles that are never read
    by coreboot code itself). It must also automatically update the metadata
    hash that is embedded in the bootblock code. It will automatically find
    the metadata hash by scanning the bootblock for its magic number and use
    its presence to auto-detect whether CBFS verification is enabled for an
    image (and which hash algorithm to use).
    Signed-off-by: default avatarJulius Werner <jwerner@chromium.org>
    Change-Id: I61a84add8654f60c683ef213b844a11b145a5cb7
    Reviewed-on: https://review.coreboot.org/c/coreboot/+/41121Reviewed-by: default avatarAngel Pons <th3fanbus@gmail.com>
    Tested-by: default avatarbuild bot (Jenkins) <no-reply@coreboot.org>
    4bfbabdb
  • Julius Werner's avatar
    cbfstool: Add support for platform "fixups" when modifying bootblock · 76dab5f9
    Julius Werner authored
    To support the new CONFIG_CBFS_VERIFICATION feature, cbfstool needs to
    update the metadata hash embedded in the bootblock code every time it
    adds or removes a CBFS file. This can lead to problems on certain
    platforms where the bootblock needs to be specially wrapped in some
    platform-specific data structure so that the platform's masked ROM can
    recognize it. If that data structure contains any form of hash or
    signature of the bootblock code that is checked on every boot, it will
    no longer match if cbfstool modifies it after the fact.
    
    In general, we should always try to disable these kinds of features
    where possible (they're not super useful anyway). But for platforms
    where the hardware simply doesn't allow that, this patch introduces the
    concept of "platform fixups" to cbfstool. Whenever cbfstool finds a
    metadata hash anchor in a CBFS image, it will run all built-in "fixup
    probe" functions on that bootblock to check if it can recognize it as
    the wrapper format for a platform known to have such an issue. If so, it
    will register a corresponding fixup function that will run whenever it
    tries to write back modified data to that bootblock. The function can
    then modify any platform-specific headers as necessary.
    
    As first supported platform, this patch adds a fixup for Qualcomm
    platforms (specifically the header format used by sc7180), which
    recalculates the bootblock body hash originally added by
    util/qualcomm/createxbl.py.
    
    (Note that this feature is not intended to support platform-specific
    signature schemes like BootGuard directly in cbfstool. For anything that
    requires an actual secret key, it should be okay if the user needs to
    run a platform-specific signing tool on the final CBFS image before
    flashing. This feature is intended for the normal unsigned case (which
    on some platforms may be implemented as signing with a well-known key)
    so that on a board that is not "locked down" in any way the normal use
    case of manipulating an image with cbfstool and then directly flashing
    the output file stays working with CONFIG_CBFS_VERIFICATION.)
    Signed-off-by: default avatarJulius Werner <jwerner@chromium.org>
    Change-Id: I02a83a40f1d0009e6f9561ae5d2d9f37a510549a
    Reviewed-on: https://review.coreboot.org/c/coreboot/+/41122Reviewed-by: default avatarAngel Pons <th3fanbus@gmail.com>
    Reviewed-by: default avatarAaron Durbin <adurbin@chromium.org>
    Tested-by: default avatarbuild bot (Jenkins) <no-reply@coreboot.org>
    76dab5f9
......@@ -22,7 +22,9 @@ cbfsobj += elfheaders.o
cbfsobj += rmodule.o
cbfsobj += xdr.o
cbfsobj += partitioned_file.o
cbfsobj += platform_fixups.o
# COMMONLIB
cbfsobj += cbfs_private.o
cbfsobj += fsp_relocate.o
# FMAP
cbfsobj += fmap.o
......@@ -94,6 +96,7 @@ TOOLCFLAGS += -O2
TOOLCPPFLAGS ?= -D_DEFAULT_SOURCE # memccpy() from string.h
TOOLCPPFLAGS += -D_BSD_SOURCE -D_SVID_SOURCE # _DEFAULT_SOURCE for older glibc
TOOLCPPFLAGS += -D_XOPEN_SOURCE=700 # strdup() from string.h
TOOLCPPFLAGS += -D_GNU_SOURCE # memmem() from string.h
TOOLCPPFLAGS += -I$(top)/util/cbfstool/flashmap
TOOLCPPFLAGS += -I$(top)/util/cbfstool
TOOLCPPFLAGS += -I$(objutil)/cbfstool
......@@ -199,6 +202,8 @@ $(objutil)/cbfstool/fmd_scanner.o: TOOLCFLAGS += -Wno-redundant-decls
$(objutil)/cbfstool/fmd_scanner.o: TOOLCFLAGS += -Wno-unused-function
# Tolerate lzma sdk warnings
$(objutil)/cbfstool/LzmaEnc.o: TOOLCFLAGS += -Wno-sign-compare -Wno-cast-qual
# Tolerate commonlib warnings
$(objutil)/cbfstool/cbfs_private.o: TOOLCFLAGS += -Wno-sign-compare
# Tolerate lz4 warnings
$(objutil)/cbfstool/lz4.o: TOOLCFLAGS += -Wno-missing-prototypes
$(objutil)/cbfstool/lz4_wrapper.o: TOOLCFLAGS += -Wno-attributes
......
......@@ -72,4 +72,9 @@ void xdr_segs(struct buffer *output,
void xdr_get_seg(struct cbfs_payload_segment *out,
struct cbfs_payload_segment *in);
/* platform_fixups.c */
typedef int (*platform_fixup_func)(struct buffer *buffer, size_t offset);
platform_fixup_func platform_fixups_probe(struct buffer *buffer, size_t offset,
const char *region_name);
#endif
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _CBFS_GLUE_H_
#define _CBFS_GLUE_H_
#include "cbfs_image.h"
#define CBFS_ENABLE_HASHING 1
typedef const struct cbfs_image *cbfs_dev_t;
static inline ssize_t cbfs_dev_read(cbfs_dev_t dev, void *buffer, size_t offset, size_t size)
{
if (buffer_size(&dev->buffer) < offset ||
buffer_size(&dev->buffer) - offset < size)
return -1;
memcpy(buffer, buffer_get(&dev->buffer) + offset, size);
return size;
}
static inline size_t cbfs_dev_size(cbfs_dev_t dev)
{
return buffer_size(&dev->buffer);
}
#endif /* _CBFS_GLUE_H_ */
......@@ -10,6 +10,7 @@
#define SECTION_NAME_FMAP "FMAP"
#define SECTION_NAME_PRIMARY_CBFS "COREBOOT"
#define SECTION_NAME_BOOTBLOCK "BOOTBLOCK"
#define SECTION_ANNOTATION_CBFS "CBFS"
......
......@@ -14,14 +14,14 @@
#include "cbfs_sections.h"
#include "elfparsing.h"
#include "partitioned_file.h"
#include <commonlib/bsd/cbfs_private.h>
#include <commonlib/bsd/metadata_hash.h>
#include <commonlib/fsp.h>
#include <commonlib/endian.h>
#include <commonlib/helpers.h>
#include <commonlib/region.h>
#include <vboot_host.h>
#define SECTION_WITH_FIT_TABLE "BOOTBLOCK"
struct command {
const char *name;
const char *optstring;
......@@ -116,6 +116,167 @@ static struct param {
.u64val = -1,
};
/*
* This "metadata_hash cache" caches the value and location of the CBFS metadata
* hash embedded in the bootblock when CBFS verification is enabled. The first
* call to get_mh_cache() searches for the cache by scanning the whole bootblock
* for its 8-byte signature, later calls will just return the previously found
* information again. If the cbfs_hash.algo member in the result is
* VB2_HASH_INVALID, that means no metadata hash was found and this image does
* not use CBFS verification.
*/
struct mh_cache {
const char *region;
size_t offset;
struct vb2_hash cbfs_hash;
platform_fixup_func fixup;
bool initialized;
};
static struct mh_cache *get_mh_cache(void)
{
static struct mh_cache mhc;
if (mhc.initialized)
return &mhc;
mhc.initialized = true;
const struct fmap *fmap = partitioned_file_get_fmap(param.image_file);
if (!fmap)
goto no_metadata_hash;
/* Find the bootblock. 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))
goto no_metadata_hash;
mhc.region = SECTION_NAME_BOOTBLOCK;
offset = 0;
size = buffer.size;
} else {
struct cbfs_image cbfs;
struct cbfs_file *bootblock;
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))
goto no_metadata_hash;
bootblock = cbfs_get_entry(&cbfs, "bootblock");
if (!bootblock || ntohl(bootblock->type) != CBFS_TYPE_BOOTBLOCK)
goto no_metadata_hash;
offset = (void *)bootblock + ntohl(bootblock->offset) -
buffer_get(&cbfs.buffer);
size = ntohl(bootblock->len);
}
/* Find and validate the metadata hash anchor inside the bootblock and
record its exact byte offset from the start of the FMAP region. */
struct metadata_hash_anchor *anchor = memmem(buffer_get(&buffer) + offset,
size, METADATA_HASH_ANCHOR_MAGIC, sizeof(anchor->magic));
if (anchor) {
if (!vb2_digest_size(anchor->cbfs_hash.algo)) {
ERROR("Unknown CBFS metadata hash type: %d\n",
anchor->cbfs_hash.algo);
goto no_metadata_hash;
}
mhc.cbfs_hash = anchor->cbfs_hash;
mhc.offset = (void *)anchor - buffer_get(&buffer);
mhc.fixup = platform_fixups_probe(&buffer, mhc.offset,
mhc.region);
return &mhc;
}
no_metadata_hash:
mhc.cbfs_hash.algo = VB2_HASH_INVALID;
return &mhc;
}
static void update_and_info(const char *name, void *dst, void *src, size_t size)
{
if (!memcmp(dst, src, size))
return;
char *src_str = bintohex(src, size);
char *dst_str = bintohex(dst, size);
INFO("Updating %s from %s to %s\n", name, dst_str, src_str);
memcpy(dst, src, size);
free(src_str);
free(dst_str);
}
static int update_anchor(struct mh_cache *mhc, uint8_t *fmap_hash)
{
struct buffer buffer;
if (!partitioned_file_read_region(&buffer, param.image_file,
mhc->region))
return -1;
struct metadata_hash_anchor *anchor = buffer_get(&buffer) + mhc->offset;
/* The metadata hash anchor should always still be where we left it. */
assert(!memcmp(anchor->magic, METADATA_HASH_ANCHOR_MAGIC,
sizeof(anchor->magic)) &&
anchor->cbfs_hash.algo == mhc->cbfs_hash.algo);
update_and_info("CBFS metadata hash", anchor->cbfs_hash.raw,
mhc->cbfs_hash.raw, vb2_digest_size(anchor->cbfs_hash.algo));
if (fmap_hash) {
update_and_info("FMAP hash",
metadata_hash_anchor_fmap_hash(anchor), fmap_hash,
vb2_digest_size(anchor->cbfs_hash.algo));
}
if (mhc->fixup && mhc->fixup(&buffer, mhc->offset) != 0)
return -1;
if (!partitioned_file_write_region(param.image_file, &buffer))
return -1;
return 0;
}
/* This should be called after every time CBFS metadata might have changed. It
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))
return 0; /* Metadata hash only embedded in primary CBFS. */
struct mh_cache *mhc = get_mh_cache();
if (mhc->cbfs_hash.algo == VB2_HASH_INVALID)
return 0;
cb_err_t err = cbfs_walk(cbfs, NULL, NULL, &mhc->cbfs_hash,
CBFS_WALK_WRITEBACK_HASH);
if (err != CB_CBFS_NOT_FOUND) {
ERROR("Unexpected cbfs_walk() error %d\n", err);
return -1;
}
return update_anchor(mhc, NULL);
}
/* This should be called after every time the FMAP or the bootblock itself might
have changed, and will write the new FMAP hash into the metadata hash anchor
in the bootblock if required (usually when the bootblock is first added). */
static int maybe_update_fmap_hash(void)
{
if (strcmp(param.region_name, SECTION_NAME_BOOTBLOCK) &&
strcmp(param.region_name, SECTION_NAME_FMAP) &&
param.type != CBFS_TYPE_BOOTBLOCK)
return 0; /* FMAP and bootblock didn't change. */
struct mh_cache *mhc = get_mh_cache();
if (mhc->cbfs_hash.algo == VB2_HASH_INVALID)
return 0;
uint8_t fmap_hash[VB2_MAX_DIGEST_SIZE];
const struct fmap *fmap = partitioned_file_get_fmap(param.image_file);
if (!fmap || vb2_digest_buffer((const void *)fmap, fmap_size(fmap),
mhc->cbfs_hash.algo, fmap_hash, sizeof(fmap_hash)))
return -1;
return update_anchor(mhc, fmap_hash);
}
static bool region_is_flashmap(const char *region)
{
return partitioned_file_region_check_magic(param.image_file, region,
......@@ -451,13 +612,21 @@ static int cbfs_add_integer_component(const char *name,
header = cbfs_create_file_header(CBFS_TYPE_RAW,
buffer.size, name);
enum vb2_hash_algorithm algo = get_mh_cache()->cbfs_hash.algo;
if (algo != VB2_HASH_INVALID)
if (cbfs_add_file_hash(header, &buffer, algo)) {
ERROR("couldn't add hash for '%s'\n", name);
goto done;
}
if (cbfs_add_entry(&image, &buffer, offset, header, 0) != 0) {
ERROR("Failed to add %llu into ROM image as '%s'.\n",
(long long unsigned)u64val, name);
goto done;
}
ret = 0;
ret = maybe_update_metadata_hash(&image);
done:
free(header);
......@@ -564,6 +733,7 @@ static int cbfs_add_master_header(void)
h->offset = htonl(offset);
h->architecture = htonl(CBFS_ARCHITECTURE_UNKNOWN);
/* Never add a hash attribute to the master header. */
header = cbfs_create_file_header(CBFS_TYPE_CBFSHEADER,
buffer_size(&buffer), name);
if (cbfs_add_entry(&image, &buffer, 0, header, 0) != 0) {
......@@ -594,7 +764,7 @@ static int cbfs_add_master_header(void)
return 1;
}
ret = 0;
ret = maybe_update_metadata_hash(&image);
done:
free(header);
......@@ -692,17 +862,31 @@ static int cbfs_add_component(const char *filename,
if (convert && convert(&buffer, &offset, header) != 0) {
ERROR("Failed to parse file '%s'.\n", filename);
buffer_delete(&buffer);
return 1;
goto error;
}
if (param.hash != VB2_HASH_INVALID)
if (cbfs_add_file_hash(header, &buffer, param.hash) == -1) {
/* Bootblock and CBFS header should never have file hashes. When adding
the bootblock it is important that we *don't* look up the metadata
hash yet (before it is added) or we'll cache an outdated result. */
if (type != CBFS_TYPE_BOOTBLOCK && type != CBFS_TYPE_CBFSHEADER) {
enum vb2_hash_algorithm mh_algo = get_mh_cache()->cbfs_hash.algo;
if (mh_algo != VB2_HASH_INVALID && param.hash != mh_algo) {
if (param.hash == VB2_HASH_INVALID) {
param.hash = mh_algo;
} else {
ERROR("Cannot specify hash %s that's different from metadata hash algorithm %s\n",
vb2_get_hash_algorithm_name(param.hash),
vb2_get_hash_algorithm_name(mh_algo));
goto error;
}
}
if (param.hash != VB2_HASH_INVALID &&
cbfs_add_file_hash(header, &buffer, param.hash) == -1) {
ERROR("couldn't add hash for '%s'\n", name);
free(header);
buffer_delete(&buffer);
return 1;
goto error;
}
}
if (param.autogen_attr) {
/* Add position attribute if assigned */
......@@ -767,14 +951,18 @@ static int cbfs_add_component(const char *filename,
if (cbfs_add_entry(&image, &buffer, offset, header, len_align) != 0) {
ERROR("Failed to add '%s' into ROM image.\n", filename);
free(header);
buffer_delete(&buffer);
return 1;
goto error;
}
free(header);
buffer_delete(&buffer);
return 0;
return maybe_update_metadata_hash(&image) || maybe_update_fmap_hash();
error:
free(header);
buffer_delete(&buffer);
return 1;
}
static int cbfstool_convert_raw(struct buffer *buffer,
......@@ -1118,7 +1306,7 @@ static int cbfs_remove(void)
return 1;
}
return 0;
return maybe_update_metadata_hash(&image);
}
static int cbfs_create(void)
......@@ -1289,6 +1477,33 @@ static int cbfs_print(void)
cbfs_print_directory(&image);
}
if (verbose) {
struct mh_cache *mhc = get_mh_cache();
if (mhc->cbfs_hash.algo == VB2_HASH_INVALID)
return 0;
struct vb2_hash real_hash = { .algo = mhc->cbfs_hash.algo };
cb_err_t err = cbfs_walk(&image, NULL, NULL, &real_hash,
CBFS_WALK_WRITEBACK_HASH);
if (err != CB_CBFS_NOT_FOUND) {
ERROR("Unexpected cbfs_walk() error %d\n", err);
return 1;
}
char *hash_str = bintohex(real_hash.raw,
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 (!memcmp(mhc->cbfs_hash.raw, real_hash.raw,
vb2_digest_size(real_hash.algo)))
printf(":valid");
else
printf(":invalid");
}
printf("\n");
free(hash_str);
}
return 0;
}
......@@ -1387,7 +1602,8 @@ static int cbfs_write(void)
memcpy(param.image_region->data + offset, new_content.data,
new_content.size);
buffer_delete(&new_content);
return 0;
return maybe_update_fmap_hash();
}
static int cbfs_read(void)
......@@ -1692,7 +1908,7 @@ static void usage(char *name)
"Find a place for a file of that size\n"
" layout [-w] "
"List mutable (or, with -w, readable) image regions\n"
" print [-r image,regions] "
" print [-r image,regions] [-k] "
"Show the contents of the ROM\n"
" extract [-r image,regions] [-m ARCH] -n NAME -f FILE [-U] "
"Extracts a file from ROM\n"
......
......@@ -561,6 +561,8 @@ typedef struct
#define PF_W (1 << 1) /* Segment is writable */
#define PF_R (1 << 2) /* Segment is readable */
#define PF_MASKOS 0x0ff00000 /* OS-specific */
#define PF_QC_SG_MASK 0x07000000 /* Qualcomm "segment" types */
#define PF_QC_SG_HASH 0x02000000 /* Qualcomm hash segment */
#define PF_MASKPROC 0xf0000000 /* Processor-specific */
/* Legal values for note segment descriptor types for core files. */
......
/* SPDX-License-Identifier: GPL-2.0-only */
#include "cbfs.h"
#include "cbfs_sections.h"
#include "elfparsing.h"
/*
* NOTE: This currently only implements support for MBN version 6 (as used by sc7180). Support
* for other MBN versions could probably be added but may require more parsing to tell them
* apart, and minor modifications (e.g. different hash algorithm). Add later as needed.
*/
static void *qualcomm_find_hash(struct buffer *in, size_t bb_offset, struct vb2_hash *real_hash)
{
struct buffer elf;
buffer_clone(&elf, in);
/* When buffer_size(&elf) becomes this small, we know we've searched through 32KiB (or
the whole bootblock) without finding anything, so we know we can stop looking. */
size_t search_end_size = MIN(0, buffer_size(in) - 32 * KiB);
/* To identify a Qualcomm image, first we find the GPT header... */
while (buffer_size(&elf) > search_end_size &&
!buffer_check_magic(&elf, "EFI PART", 8))
buffer_seek(&elf, 512);
/* ...then shortly afterwards there's an ELF header... */
while (buffer_size(&elf) > search_end_size &&
!buffer_check_magic(&elf, ELFMAG, 4))
buffer_seek(&elf, 512);
if (buffer_size(&elf) <= search_end_size)
return NULL; /* Doesn't seem to be a Qualcomm image. */
struct parsed_elf pelf;
if (parse_elf(&elf, &pelf, ELF_PARSE_PHDR))
return NULL; /* Not an ELF -- guess not a Qualcomm MBN after all? */
/* Qualcomm stores an array of SHA-384 hashes in a special ELF segment. One special one
to start with, and then one for each segment in order. */
void *bb_hash = NULL;
void *hashtable = NULL;
int i;
int bb_segment = -1;
for (i = 0; i < pelf.ehdr.e_phnum; i++) {
Elf64_Phdr *ph = &pelf.phdr[i];
if ((ph->p_flags & PF_QC_SG_MASK) == PF_QC_SG_HASH) {
if ((int)ph->p_filesz !=
(pelf.ehdr.e_phnum + 1) * VB2_SHA384_DIGEST_SIZE) {
ERROR("fixups: Qualcomm hash segment has wrong size!\n");
goto destroy_elf;
} /* Found the table with the hashes -- store its address. */
hashtable = buffer_get(&elf) + ph->p_offset;
} else if (bb_segment < 0 && ph->p_offset + ph->p_filesz < buffer_size(&elf) &&
buffer_offset(&elf) + ph->p_offset <= bb_offset &&
buffer_offset(&elf) + ph->p_offset + ph->p_filesz > bb_offset) {
bb_segment = i; /* Found the bootblock segment -- store its index. */
}
}
if (!hashtable) /* ELF but no special QC hash segment -- guess not QC after all? */
goto destroy_elf;
if (bb_segment < 0) { /* Can assume it's QC if we found the special segment. */
ERROR("fixups: Cannot find bootblock code in Qualcomm MBN!\n");
goto destroy_elf;
}
/* Pass out the actual hash of the current bootblock segment in |real_hash|. */
if (vb2_hash_calculate(buffer_get(&elf) + pelf.phdr[bb_segment].p_offset,
pelf.phdr[bb_segment].p_filesz, VB2_HASH_SHA384, real_hash)) {
ERROR("fixups: vboot digest error\n");
goto destroy_elf;
} /* Return pointer to where the bootblock hash needs to go in Qualcomm's table. */
bb_hash = hashtable + (bb_segment + 1) * VB2_SHA384_DIGEST_SIZE;
destroy_elf:
parsed_elf_destroy(&pelf);
return bb_hash;
}
static bool qualcomm_probe(struct buffer *buffer, size_t offset)
{
struct vb2_hash real_hash;
void *table_hash = qualcomm_find_hash(buffer, offset, &real_hash);
if (!table_hash)
return false;
if (memcmp(real_hash.raw, table_hash, VB2_SHA384_DIGEST_SIZE)) {
ERROR("fixups: Identified Qualcomm MBN, but existing hash doesn't match!\n");
return false;
}
return true;
}
static int qualcomm_fixup(struct buffer *buffer, size_t offset)
{
struct vb2_hash real_hash;
void *table_hash = qualcomm_find_hash(buffer, offset, &real_hash);
if (!table_hash) {
ERROR("fixups: Cannot find Qualcomm MBN headers anymore!\n");
return -1;
}
memcpy(table_hash, real_hash.raw, VB2_SHA384_DIGEST_SIZE);
INFO("fixups: Updated Qualcomm MBN header bootblock hash.\n");
return 0;
}
platform_fixup_func platform_fixups_probe(struct buffer *buffer, size_t offset,
const char *region_name)
{
if (!strcmp(region_name, SECTION_NAME_BOOTBLOCK)) {
if (qualcomm_probe(buffer, offset))
return qualcomm_fixup;
} else if (!strcmp(region_name, SECTION_NAME_PRIMARY_CBFS)) {
/* TODO: add fixups for primary CBFS bootblock platforms, if needed */
} else {
ERROR("%s called for unexpected FMAP region %s!\n", __func__, region_name);
}
return NULL;
}