cbfs: Move stage header into a CBFS attribute

The CBFS stage header is part of the file data (not the header) from
CBFS's point of view, which is problematic for verification: in pre-RAM
environments, there's usually not enough scratch space in CBFS_CACHE to
load the full stage into memory, so it must be directly loaded into its
final destination. However, that destination is decided from reading the
stage header. There's no way we can verify the stage header without
loading the whole file and we can't load the file without trusting the
information in the stage header.

To solve this problem, this patch changes the CBFS stage format to move
the stage header out of the file contents and into a separate CBFS
attribute. Attributes are part of the metadata, so they have already
been verified before the file is loaded.

Since CBFS stages are generally only meant to be used by coreboot itself
and the coreboot build system builds cbfstool and all stages together in
one go, maintaining backwards-compatibility should not be necessary. An
older version of coreboot will build the old version of cbfstool and a
newer version of coreboot will build the new version of cbfstool before
using it to add stages to the final image, thus cbfstool and coreboot's
stage loader should stay in sync. This only causes problems when someone
stashes away a copy of cbfstool somewhere and later uses it to try to
extract stages from a coreboot image built from a different revision...
a debugging use-case that is hopefully rare enough that affected users
can manually deal with finding a matching version of cbfstool.

The SELF (payload) format, on the other hand, is designed to be used for
binaries outside of coreboot that may use independent build systems and
are more likely to be added with a potentially stale copy of cbfstool,
so it would be more problematic to make a similar change for SELFs. It
is not necessary for verification either, since they're usually only
used in post-RAM environments and selfload() already maps SELFs to
CBFS_CACHE before loading them to their final destination anyway (so
they can be hashed at that time).

Signed-off-by: Julius Werner <jwerner@chromium.org>
Change-Id: I8471ad7494b07599e24e82b81e507fcafbad808a
Reviewed-on: https://review.coreboot.org/c/coreboot/+/46484
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Aaron Durbin <adurbin@chromium.org>
diff --git a/src/lib/cbfs.c b/src/lib/cbfs.c
index a274551..fbf4531 100644
--- a/src/lib/cbfs.c
+++ b/src/lib/cbfs.c
@@ -386,11 +386,6 @@
 {
 	union cbfs_mdata mdata;
 	struct region_device rdev;
-	struct cbfs_stage stage;
-	uint8_t *load;
-	void *entry;
-	size_t fsize;
-	size_t foffset;
 	cb_err_t err;
 
 	prog_locate_hook(pstage);
@@ -401,50 +396,41 @@
 	assert(be32toh(mdata.h.type) == CBFS_TYPE_STAGE);
 	pstage->cbfs_type = CBFS_TYPE_STAGE;
 
-	if (rdev_readat(&rdev, &stage, 0, sizeof(stage)) != sizeof(stage))
-		return CB_CBFS_IO;
+	enum cbfs_compression compression = CBFS_COMPRESS_NONE;
+	const struct cbfs_file_attr_compression *cattr = cbfs_find_attr(&mdata,
+				CBFS_FILE_ATTR_TAG_COMPRESSION, sizeof(*cattr));
+	if (cattr)
+		compression = be32toh(cattr->compression);
 
-	fsize = region_device_sz(&rdev);
-	fsize -= sizeof(stage);
-	foffset = 0;
-	foffset += sizeof(stage);
-
-	/* cbfs_stage fields are written in little endian despite the other
-	   cbfs data types being encoded in big endian. */
-	stage.compression = read_le32(&stage.compression);
-	stage.entry = read_le64(&stage.entry);
-	stage.load = read_le64(&stage.load);
-	stage.len = read_le32(&stage.len);
-	stage.memlen = read_le32(&stage.memlen);
-
-	assert(fsize == stage.len);
-
-	load = (void *)(uintptr_t)stage.load;
-	entry = (void *)(uintptr_t)stage.entry;
+	const struct cbfs_file_attr_stageheader *sattr = cbfs_find_attr(&mdata,
+				CBFS_FILE_ATTR_TAG_STAGEHEADER, sizeof(*sattr));
+	if (!sattr)
+		return CB_ERR;
+	prog_set_area(pstage, (void *)(uintptr_t)be64toh(sattr->loadaddr),
+		      be32toh(sattr->memlen));
+	prog_set_entry(pstage, prog_start(pstage) +
+			       be32toh(sattr->entry_offset), NULL);
 
 	/* Hacky way to not load programs over read only media. The stages
 	 * that would hit this path initialize themselves. */
 	if ((ENV_BOOTBLOCK || ENV_SEPARATE_VERSTAGE) &&
 	    !CONFIG(NO_XIP_EARLY_STAGES) && CONFIG(BOOT_DEVICE_MEMORY_MAPPED)) {
-		void *mapping = rdev_mmap(&rdev, foffset, fsize);
+		void *mapping = rdev_mmap_full(&rdev);
 		rdev_munmap(&rdev, mapping);
-		if (mapping == load)
-			goto out;
+		if (mapping == prog_start(pstage))
+			return CB_SUCCESS;
 	}
 
-	fsize = cbfs_stage_load_and_decompress(&rdev, foffset, fsize, load,
-					       stage.memlen, stage.compression);
+	size_t fsize = cbfs_stage_load_and_decompress(&rdev, 0, region_device_sz(&rdev),
+				prog_start(pstage), prog_size(pstage), compression);
 	if (!fsize)
 		return CB_ERR;
 
 	/* Clear area not covered by file. */
-	memset(&load[fsize], 0, stage.memlen - fsize);
+	memset(prog_start(pstage) + fsize, 0, prog_size(pstage) - fsize);
 
-	prog_segment_loaded((uintptr_t)load, stage.memlen, SEG_FINAL);
-
-out:
-	prog_set_area(pstage, load, stage.memlen);
-	prog_set_entry(pstage, entry, NULL);
+	prog_segment_loaded((uintptr_t)prog_start(pstage), prog_size(pstage),
+			    SEG_FINAL);
 
 	return CB_SUCCESS;
 }