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>
This commit is contained in:
Julius Werner 2020-10-15 17:37:57 -07:00 committed by Patrick Georgi
parent 2e973942bc
commit 81dc20e744
9 changed files with 205 additions and 331 deletions

View File

@ -20,7 +20,8 @@ enum cbfs_type {
CBFS_TYPE_NULL = 0xffffffff, CBFS_TYPE_NULL = 0xffffffff,
CBFS_TYPE_BOOTBLOCK = 0x01, CBFS_TYPE_BOOTBLOCK = 0x01,
CBFS_TYPE_CBFSHEADER = 0x02, CBFS_TYPE_CBFSHEADER = 0x02,
CBFS_TYPE_STAGE = 0x10, CBFS_TYPE_LEGACY_STAGE = 0x10,
CBFS_TYPE_STAGE = 0x11,
CBFS_TYPE_SELF = 0x20, CBFS_TYPE_SELF = 0x20,
CBFS_TYPE_FIT = 0x21, CBFS_TYPE_FIT = 0x21,
CBFS_TYPE_OPTIONROM = 0x30, CBFS_TYPE_OPTIONROM = 0x30,
@ -131,6 +132,7 @@ enum cbfs_file_attr_tag {
CBFS_FILE_ATTR_TAG_ALIGNMENT = 0x42434c41, /* BE: 'BCLA' */ CBFS_FILE_ATTR_TAG_ALIGNMENT = 0x42434c41, /* BE: 'BCLA' */
CBFS_FILE_ATTR_TAG_IBB = 0x32494242, /* BE: '2IBB' */ CBFS_FILE_ATTR_TAG_IBB = 0x32494242, /* BE: '2IBB' */
CBFS_FILE_ATTR_TAG_PADDING = 0x47444150, /* BE: 'GNDP' */ CBFS_FILE_ATTR_TAG_PADDING = 0x47444150, /* BE: 'GNDP' */
CBFS_FILE_ATTR_TAG_STAGEHEADER = 0x53746748, /* BE: 'StgH' */
}; };
struct cbfs_file_attr_compression { struct cbfs_file_attr_compression {
@ -160,22 +162,20 @@ struct cbfs_file_attr_align {
uint32_t alignment; uint32_t alignment;
} __packed; } __packed;
struct cbfs_file_attr_stageheader {
uint32_t tag;
uint32_t len;
uint64_t loadaddr; /* Memory address to load the code to. */
uint32_t entry_offset; /* Offset of entry point from loadaddr. */
uint32_t memlen; /* Total length (including BSS) in memory. */
} __packed;
/*** Component sub-headers ***/ /*** Component sub-headers ***/
/* Following are component sub-headers for the "standard" /* Following are component sub-headers for the "standard"
component types */ component types */
/** This is the sub-header for stage components. Stages are
loaded by coreboot during the normal boot process */
struct cbfs_stage {
uint32_t compression; /** Compression type */
uint64_t entry; /** entry point */
uint64_t load; /** Where to load in memory */
uint32_t len; /** length of data to load */
uint32_t memlen; /** total length of object in memory */
} __packed;
/** this is the sub-header for payload components. Payloads /** this is the sub-header for payload components. Payloads
are loaded by coreboot at the end of the boot process */ are loaded by coreboot at the end of the boot process */

View File

@ -386,11 +386,6 @@ cb_err_t cbfs_prog_stage_load(struct prog *pstage)
{ {
union cbfs_mdata mdata; union cbfs_mdata mdata;
struct region_device rdev; struct region_device rdev;
struct cbfs_stage stage;
uint8_t *load;
void *entry;
size_t fsize;
size_t foffset;
cb_err_t err; cb_err_t err;
prog_locate_hook(pstage); prog_locate_hook(pstage);
@ -401,50 +396,41 @@ cb_err_t cbfs_prog_stage_load(struct prog *pstage)
assert(be32toh(mdata.h.type) == CBFS_TYPE_STAGE); assert(be32toh(mdata.h.type) == CBFS_TYPE_STAGE);
pstage->cbfs_type = CBFS_TYPE_STAGE; pstage->cbfs_type = CBFS_TYPE_STAGE;
if (rdev_readat(&rdev, &stage, 0, sizeof(stage)) != sizeof(stage)) enum cbfs_compression compression = CBFS_COMPRESS_NONE;
return CB_CBFS_IO; 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); const struct cbfs_file_attr_stageheader *sattr = cbfs_find_attr(&mdata,
fsize -= sizeof(stage); CBFS_FILE_ATTR_TAG_STAGEHEADER, sizeof(*sattr));
foffset = 0; if (!sattr)
foffset += sizeof(stage); return CB_ERR;
prog_set_area(pstage, (void *)(uintptr_t)be64toh(sattr->loadaddr),
/* cbfs_stage fields are written in little endian despite the other be32toh(sattr->memlen));
cbfs data types being encoded in big endian. */ prog_set_entry(pstage, prog_start(pstage) +
stage.compression = read_le32(&stage.compression); be32toh(sattr->entry_offset), NULL);
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;
/* Hacky way to not load programs over read only media. The stages /* Hacky way to not load programs over read only media. The stages
* that would hit this path initialize themselves. */ * that would hit this path initialize themselves. */
if ((ENV_BOOTBLOCK || ENV_SEPARATE_VERSTAGE) && if ((ENV_BOOTBLOCK || ENV_SEPARATE_VERSTAGE) &&
!CONFIG(NO_XIP_EARLY_STAGES) && CONFIG(BOOT_DEVICE_MEMORY_MAPPED)) { !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); rdev_munmap(&rdev, mapping);
if (mapping == load) if (mapping == prog_start(pstage))
goto out; return CB_SUCCESS;
} }
fsize = cbfs_stage_load_and_decompress(&rdev, foffset, fsize, load, size_t fsize = cbfs_stage_load_and_decompress(&rdev, 0, region_device_sz(&rdev),
stage.memlen, stage.compression); prog_start(pstage), prog_size(pstage), compression);
if (!fsize) if (!fsize)
return CB_ERR; return CB_ERR;
/* Clear area not covered by file. */ /* 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); prog_segment_loaded((uintptr_t)prog_start(pstage), prog_size(pstage),
SEG_FINAL);
out:
prog_set_area(pstage, load, stage.memlen);
prog_set_entry(pstage, entry, NULL);
return CB_SUCCESS; return CB_SUCCESS;
} }

View File

@ -2,7 +2,6 @@
#include <assert.h> #include <assert.h>
#include <cbmem.h> #include <cbmem.h>
#include <cbfs.h> #include <cbfs.h>
#include <cbfs_private.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -13,6 +12,8 @@
/* Change this define to get more verbose debugging for module loading. */ /* Change this define to get more verbose debugging for module loading. */
#define PK_ADJ_LEVEL BIOS_NEVER #define PK_ADJ_LEVEL BIOS_NEVER
const size_t region_alignment = MIN_UNSAFE(DYN_CBMEM_ALIGN_SIZE, 4096);
static inline int rmodule_is_loaded(const struct rmodule *module) static inline int rmodule_is_loaded(const struct rmodule *module)
{ {
return module->location != NULL; return module->location != NULL;
@ -190,20 +191,26 @@ int rmodule_load(void *base, struct rmodule *module)
return 0; return 0;
} }
int rmodule_calc_region(unsigned int region_alignment, size_t rmodule_size, static void *rmodule_cbfs_allocator(void *rsl_arg, size_t unused,
size_t *region_size, int *load_offset) union cbfs_mdata *mdata)
{ {
/* region_alignment must be a power of 2. */ struct rmod_stage_load *rsl = rsl_arg;
if (region_alignment & (region_alignment - 1))
BUG();
if (region_alignment < 4096) assert(IS_POWER_OF_2(region_alignment) &&
region_alignment = 4096; region_alignment >= sizeof(struct rmodule_header));
/* Sanity check rmodule_header size. The code below assumes it is less /* The CBFS core just passes us the decompressed size of the file, but
* than the minimum alignment required. */ we need to know the memlen of the binary image. We need to find and
if (region_alignment < sizeof(struct rmodule_header)) parse the stage header explicitly. */
BUG(); const struct cbfs_file_attr_stageheader *sattr = cbfs_find_attr(mdata,
CBFS_FILE_ATTR_TAG_STAGEHEADER, sizeof(*sattr));
if (!sattr) {
printk(BIOS_ERR, "rmodule '%s' has no stage header!\n",
rsl->prog->name);
return NULL;
}
const size_t memlen = be32toh(sattr->memlen);
/* Place the rmodule according to alignment. The rmodule files /* Place the rmodule according to alignment. The rmodule files
* themselves are packed as a header and a payload, however the rmodule * themselves are packed as a header and a payload, however the rmodule
@ -215,7 +222,7 @@ int rmodule_calc_region(unsigned int region_alignment, size_t rmodule_size,
* to place the rmodule so that the program falls on the aligned * to place the rmodule so that the program falls on the aligned
* address with the header just before it. Therefore, we need at least * address with the header just before it. Therefore, we need at least
* a page to account for the size of the header. */ * a page to account for the size of the header. */
*region_size = ALIGN(rmodule_size + region_alignment, 4096); size_t region_size = ALIGN(memlen + region_alignment, 4096);
/* The program starts immediately after the header. However, /* The program starts immediately after the header. However,
* it needs to be aligned to a 4KiB boundary. Therefore, adjust the * it needs to be aligned to a 4KiB boundary. Therefore, adjust the
* program location so that the program lands on a page boundary. The * program location so that the program lands on a page boundary. The
@ -231,22 +238,17 @@ int rmodule_calc_region(unsigned int region_alignment, size_t rmodule_size,
* | >= 0 bytes from alignment | * | >= 0 bytes from alignment |
* +--------------------------------+ region_alignment * +--------------------------------+ region_alignment
*/ */
*load_offset = region_alignment;
return region_alignment - sizeof(struct rmodule_header); uint8_t *stage_region = cbmem_add(rsl->cbmem_id, region_size);
if (stage_region == NULL)
return NULL;
return stage_region + region_alignment - sizeof(struct rmodule_header);
} }
int rmodule_stage_load(struct rmod_stage_load *rsl) int rmodule_stage_load(struct rmod_stage_load *rsl)
{ {
struct rmodule rmod_stage; struct rmodule rmod_stage;
size_t region_size;
char *stage_region;
int rmodule_offset;
int load_offset;
struct cbfs_stage stage;
void *rmod_loc;
struct region_device rdev;
union cbfs_mdata mdata;
if (rsl->prog == NULL || prog_name(rsl->prog) == NULL) if (rsl->prog == NULL || prog_name(rsl->prog) == NULL)
return -1; return -1;
@ -254,37 +256,15 @@ int rmodule_stage_load(struct rmod_stage_load *rsl)
if (prog_locate_hook(rsl->prog)) if (prog_locate_hook(rsl->prog))
return -1; return -1;
if (cbfs_boot_lookup(prog_name(rsl->prog), false, &mdata, &rdev) != CB_SUCCESS) void *rmod_loc = cbfs_alloc(prog_name(rsl->prog),
return -1; rmodule_cbfs_allocator, rsl, NULL);
if (!rmod_loc)
assert(be32toh(mdata.h.type) == CBFS_TYPE_STAGE);
rsl->prog->cbfs_type = CBFS_TYPE_STAGE;
if (rdev_readat(&rdev, &stage, 0, sizeof(stage)) != sizeof(stage))
return -1;
rmodule_offset =
rmodule_calc_region(DYN_CBMEM_ALIGN_SIZE,
stage.memlen, &region_size, &load_offset);
stage_region = cbmem_add(rsl->cbmem_id, region_size);
if (stage_region == NULL)
return -1;
rmod_loc = &stage_region[rmodule_offset];
printk(BIOS_INFO, "Decompressing stage %s @ %p (%d bytes)\n",
prog_name(rsl->prog), rmod_loc, stage.memlen);
if (!cbfs_load_and_decompress(&rdev, sizeof(stage), stage.len, rmod_loc,
stage.memlen, stage.compression))
return -1; return -1;
if (rmodule_parse(rmod_loc, &rmod_stage)) if (rmodule_parse(rmod_loc, &rmod_stage))
return -1; return -1;
if (rmodule_load(&stage_region[load_offset], &rmod_stage)) if (rmodule_load(rmod_loc + sizeof(struct rmodule_header), &rmod_stage))
return -1; return -1;
prog_set_area(rsl->prog, rmod_stage.location, prog_set_area(rsl->prog, rmod_stage.location,

View File

@ -29,14 +29,8 @@ static int agesa_locate_raw_file(const char *name, struct region_device *rdev)
static int agesa_locate_stage_file_early(const char *name, static int agesa_locate_stage_file_early(const char *name,
struct region_device *rdev) struct region_device *rdev)
{ {
const size_t metadata_sz = sizeof(struct cbfs_stage);
if (agesa_locate_file(name, rdev, CBFS_TYPE_STAGE)) if (agesa_locate_file(name, rdev, CBFS_TYPE_STAGE))
return -1; return -1;
/* Peel off the cbfs stage metadata. */
return rdev_chain(rdev, rdev, metadata_sz,
region_device_sz(rdev) - metadata_sz);
} }
static int agesa_locate_stage_file_ramstage(const char *name, static int agesa_locate_stage_file_ramstage(const char *name,

View File

@ -10,8 +10,6 @@
#include "cbfs.h" #include "cbfs.h"
#include "rmodule.h" #include "rmodule.h"
#include <commonlib/bsd/compression.h>
/* Checks if program segment contains the ignored section */ /* Checks if program segment contains the ignored section */
static int is_phdr_ignored(Elf64_Phdr *phdr, Elf64_Shdr *shdr) static int is_phdr_ignored(Elf64_Phdr *phdr, Elf64_Shdr *shdr)
{ {
@ -73,19 +71,20 @@ static Elf64_Shdr *find_ignored_section_header(struct parsed_elf *pelf,
return NULL; return NULL;
} }
static void fill_cbfs_stage(struct buffer *outheader, enum cbfs_compression algo, static int fill_cbfs_stageheader(struct cbfs_file_attr_stageheader *stageheader,
uint64_t entry, uint64_t loadaddr, uint64_t entry, uint64_t loadaddr,
uint32_t filesize, uint32_t memsize) uint32_t memsize)
{ {
/* N.B. The original plan was that SELF data was B.E. if (entry - loadaddr >= memsize) {
* but: this is all L.E. ERROR("stage entry point out of bounds!\n");
* Maybe we should just change the spec. return -1;
*/ }
xdr_le.put32(outheader, algo);
xdr_le.put64(outheader, entry); stageheader->loadaddr = htonll(loadaddr);
xdr_le.put64(outheader, loadaddr); stageheader->memlen = htonl(memsize);
xdr_le.put32(outheader, filesize); stageheader->entry_offset = htonl(entry - loadaddr);
xdr_le.put32(outheader, memsize);
return 0;
} }
/* returns size of result, or -1 if error. /* returns size of result, or -1 if error.
@ -93,25 +92,20 @@ static void fill_cbfs_stage(struct buffer *outheader, enum cbfs_compression algo
* works for all elf files, not just the restricted set. * works for all elf files, not just the restricted set.
*/ */
int parse_elf_to_stage(const struct buffer *input, struct buffer *output, int parse_elf_to_stage(const struct buffer *input, struct buffer *output,
enum cbfs_compression algo, const char *ignore_section) const char *ignore_section,
struct cbfs_file_attr_stageheader *stageheader)
{ {
struct parsed_elf pelf; struct parsed_elf pelf;
Elf64_Phdr *phdr; Elf64_Phdr *phdr;
Elf64_Ehdr *ehdr; Elf64_Ehdr *ehdr;
Elf64_Shdr *shdr_ignored; Elf64_Shdr *shdr_ignored;
Elf64_Addr virt_to_phys; Elf64_Addr virt_to_phys;
char *buffer;
struct buffer outheader;
int ret = -1; int ret = -1;
int headers; int headers;
int i, outlen; int i;
uint64_t data_start, data_end, mem_end; uint64_t data_start, data_end, mem_end;
comp_func_ptr compress = compression_function(algo);
if (!compress)
return -1;
int flags = ELF_PARSE_PHDR | ELF_PARSE_SHDR | ELF_PARSE_STRTAB; int flags = ELF_PARSE_PHDR | ELF_PARSE_SHDR | ELF_PARSE_STRTAB;
if (parse_elf(input, &pelf, flags)) { if (parse_elf(input, &pelf, flags)) {
@ -178,15 +172,13 @@ int parse_elf_to_stage(const struct buffer *input, struct buffer *output,
exit(1); exit(1);
} }
/* allocate an intermediate buffer for the data */ if (buffer_create(output, data_end - data_start, input->name) != 0) {
buffer = calloc(data_end - data_start, 1);
if (buffer == NULL) {
ERROR("Unable to allocate memory: %m\n"); ERROR("Unable to allocate memory: %m\n");
goto err; goto err;
} }
memset(output->data, 0, output->size);
/* Copy the file data into the buffer */ /* Copy the file data into the output buffer */
for (i = 0; i < headers; i++) { for (i = 0; i < headers; i++) {
if (phdr[i].p_type != PT_LOAD) if (phdr[i].p_type != PT_LOAD)
@ -207,90 +199,17 @@ int parse_elf_to_stage(const struct buffer *input, struct buffer *output,
ERROR("Underflow copying out the segment." ERROR("Underflow copying out the segment."
"File has %zu bytes left, segment end is %zu\n", "File has %zu bytes left, segment end is %zu\n",
input->size, (size_t)(phdr[i].p_offset + phdr[i].p_filesz)); input->size, (size_t)(phdr[i].p_offset + phdr[i].p_filesz));
free(buffer);
goto err; goto err;
} }
memcpy(buffer + (phdr[i].p_paddr - data_start), memcpy(&output->data[phdr[i].p_paddr - data_start],
&input->data[phdr[i].p_offset], &input->data[phdr[i].p_offset],
phdr[i].p_filesz); phdr[i].p_filesz);
} }
/* Now make the output buffer */
if (buffer_create(output, sizeof(struct cbfs_stage) + data_end - data_start,
input->name) != 0) {
ERROR("Unable to allocate memory: %m\n");
free(buffer);
goto err;
}
memset(output->data, 0, output->size);
/* Compress the data, at which point we'll know information
* to fill out the header. This seems backward but it works because
* - the output header is a known size (not always true in many xdr's)
* - we do need to know the compressed output size first
* If compression fails or makes the data bigger, we'll warn about it
* and use the original data.
*/
if (compress(buffer, data_end - data_start,
(output->data + sizeof(struct cbfs_stage)),
&outlen) < 0 || (unsigned)outlen > data_end - data_start) {
WARN("Compression failed or would make the data bigger "
"- disabled.\n");
memcpy(output->data + sizeof(struct cbfs_stage),
buffer, data_end - data_start);
outlen = data_end - data_start;
algo = CBFS_COMPRESS_NONE;
}
/* Check for enough BSS scratch space to decompress LZ4 in-place. */
if (algo == CBFS_COMPRESS_LZ4) {
size_t result;
size_t memlen = mem_end - data_start;
size_t compressed_size = outlen;
char *compare_buffer = malloc(memlen);
char *start = compare_buffer + memlen - compressed_size;
if (compare_buffer == NULL) {
ERROR("Can't allocate memory!\n");
free(buffer);
goto err;
}
memcpy(start, output->data + sizeof(struct cbfs_stage),
compressed_size);
result = ulz4fn(start, compressed_size, compare_buffer, memlen);
if (result == 0) {
ERROR("Not enough scratch space to decompress LZ4 in-place -- increase BSS size or disable compression!\n");
free(compare_buffer);
free(buffer);
goto err;
}
if (result != data_end - data_start ||
memcmp(compare_buffer, buffer, data_end - data_start)) {
ERROR("LZ4 compression BUG! Report to mailing list.\n");
free(compare_buffer);
free(buffer);
goto err;
}
free(compare_buffer);
}
free(buffer);
/* Set up for output marshaling. */
outheader.data = output->data;
outheader.size = 0;
/* coreboot expects entry point to be physical address. Thus, adjust the /* coreboot expects entry point to be physical address. Thus, adjust the
* entry point accordingly. entry point accordingly. */
*/ ret = fill_cbfs_stageheader(stageheader, ehdr->e_entry + virt_to_phys,
fill_cbfs_stage(&outheader, algo, ehdr->e_entry + virt_to_phys, data_start, mem_end - data_start);
data_start, outlen, mem_end - data_start);
output->size = sizeof(struct cbfs_stage) + outlen;
ret = 0;
err: err:
parsed_elf_destroy(&pelf); parsed_elf_destroy(&pelf);
return ret; return ret;
@ -341,13 +260,13 @@ static int rmod_filter(struct reloc_filter *f, const Elf64_Rela *r)
} }
int parse_elf_to_xip_stage(const struct buffer *input, struct buffer *output, int parse_elf_to_xip_stage(const struct buffer *input, struct buffer *output,
uint32_t *location, const char *ignore_section) uint32_t *location, const char *ignore_section,
struct cbfs_file_attr_stageheader *stageheader)
{ {
struct xip_context xipctx; struct xip_context xipctx;
struct rmod_context *rmodctx; struct rmod_context *rmodctx;
struct reloc_filter filter; struct reloc_filter filter;
struct parsed_elf *pelf; struct parsed_elf *pelf;
size_t output_sz;
uint32_t adjustment; uint32_t adjustment;
struct buffer binput; struct buffer binput;
struct buffer boutput; struct buffer boutput;
@ -381,13 +300,12 @@ int parse_elf_to_xip_stage(const struct buffer *input, struct buffer *output,
if (rmodule_collect_relocations(rmodctx, &filter)) if (rmodule_collect_relocations(rmodctx, &filter))
goto out; goto out;
output_sz = sizeof(struct cbfs_stage) + pelf->phdr->p_filesz; if (buffer_create(output, pelf->phdr->p_filesz, input->name) != 0) {
if (buffer_create(output, output_sz, input->name) != 0) {
ERROR("Unable to allocate memory: %m\n"); ERROR("Unable to allocate memory: %m\n");
goto out; goto out;
} }
buffer_clone(&boutput, output); buffer_clone(&boutput, output);
memset(buffer_get(&boutput), 0, output_sz); memset(buffer_get(&boutput), 0, pelf->phdr->p_filesz);
buffer_set_size(&boutput, 0); buffer_set_size(&boutput, 0);
/* Single loadable segment. The entire segment moves to final /* Single loadable segment. The entire segment moves to final
@ -395,17 +313,16 @@ int parse_elf_to_xip_stage(const struct buffer *input, struct buffer *output,
adjustment = *location - pelf->phdr->p_vaddr; adjustment = *location - pelf->phdr->p_vaddr;
DEBUG("Relocation adjustment: %08x\n", adjustment); DEBUG("Relocation adjustment: %08x\n", adjustment);
fill_cbfs_stage(&boutput, CBFS_COMPRESS_NONE, fill_cbfs_stageheader(stageheader,
(uint32_t)pelf->ehdr.e_entry + adjustment, (uint32_t)pelf->ehdr.e_entry + adjustment,
(uint32_t)pelf->phdr->p_vaddr + adjustment, (uint32_t)pelf->phdr->p_vaddr + adjustment,
pelf->phdr->p_filesz, pelf->phdr->p_memsz); pelf->phdr->p_memsz);
/* Need an adjustable buffer. */ /* Need an adjustable buffer. */
buffer_clone(&binput, input); buffer_clone(&binput, input);
buffer_seek(&binput, pelf->phdr->p_offset); buffer_seek(&binput, pelf->phdr->p_offset);
bputs(&boutput, buffer_get(&binput), pelf->phdr->p_filesz); bputs(&boutput, buffer_get(&binput), pelf->phdr->p_filesz);
buffer_clone(&boutput, output); buffer_clone(&boutput, output);
buffer_seek(&boutput, sizeof(struct cbfs_stage));
/* Make adjustments to all the relocations within the program. */ /* Make adjustments to all the relocations within the program. */
for (i = 0; i < rmodctx->nrelocs; i++) { for (i = 0; i < rmodctx->nrelocs; i++) {
@ -431,8 +348,6 @@ int parse_elf_to_xip_stage(const struct buffer *input, struct buffer *output,
xdr_le.put32(&out, val + adjustment); xdr_le.put32(&out, val + adjustment);
} }
/* Need to back up the location to include cbfs stage metadata. */
*location -= sizeof(struct cbfs_stage);
ret = 0; ret = 0;
out: out:

View File

@ -824,69 +824,6 @@ struct cbfs_file *cbfs_get_entry(struct cbfs_image *image, const char *name)
return NULL; return NULL;
} }
static int cbfs_stage_decompress(struct cbfs_stage *stage, struct buffer *buff)
{
struct buffer reader;
char *orig_buffer;
char *new_buffer;
size_t new_buff_sz;
decomp_func_ptr decompress;
buffer_clone(&reader, buff);
/* The stage metadata is in little endian. */
stage->compression = xdr_le.get32(&reader);
stage->entry = xdr_le.get64(&reader);
stage->load = xdr_le.get64(&reader);
stage->len = xdr_le.get32(&reader);
stage->memlen = xdr_le.get32(&reader);
/* Create a buffer just with the uncompressed program now that the
* struct cbfs_stage has been peeled off. */
if (stage->compression == CBFS_COMPRESS_NONE) {
new_buff_sz = buffer_size(buff) - sizeof(struct cbfs_stage);
orig_buffer = buffer_get(buff);
new_buffer = calloc(1, new_buff_sz);
memcpy(new_buffer, orig_buffer + sizeof(struct cbfs_stage),
new_buff_sz);
buffer_init(buff, buff->name, new_buffer, new_buff_sz);
free(orig_buffer);
return 0;
}
decompress = decompression_function(stage->compression);
if (decompress == NULL)
return -1;
orig_buffer = buffer_get(buff);
/* This can be too big of a buffer needed, but there's no current
* field indicating decompressed size of data. */
new_buff_sz = stage->memlen;
new_buffer = calloc(1, new_buff_sz);
if (decompress(orig_buffer + sizeof(struct cbfs_stage),
(int)(buffer_size(buff) - sizeof(struct cbfs_stage)),
new_buffer, (int)new_buff_sz, &new_buff_sz)) {
ERROR("Couldn't decompress stage.\n");
free(new_buffer);
return -1;
}
/* Include correct size for full stage info. */
buffer_init(buff, buff->name, new_buffer, new_buff_sz);
/* True decompressed size is just the data size -- no metadata. */
stage->len = new_buff_sz;
/* Stage is not compressed. */
stage->compression = CBFS_COMPRESS_NONE;
free(orig_buffer);
return 0;
}
static int cbfs_payload_decompress(struct cbfs_payload_segment *segments, static int cbfs_payload_decompress(struct cbfs_payload_segment *segments,
struct buffer *buff, int num_seg) struct buffer *buff, int num_seg)
{ {
@ -1020,11 +957,11 @@ static int init_elf_from_arch(Elf64_Ehdr *ehdr, uint32_t cbfs_arch)
return 0; return 0;
} }
static int cbfs_stage_make_elf(struct buffer *buff, uint32_t arch) static int cbfs_stage_make_elf(struct buffer *buff, uint32_t arch,
struct cbfs_file *entry)
{ {
Elf64_Ehdr ehdr; Elf64_Ehdr ehdr;
Elf64_Shdr shdr; Elf64_Shdr shdr;
struct cbfs_stage stage;
struct elf_writer *ew; struct elf_writer *ew;
struct buffer elf_out; struct buffer elf_out;
size_t empty_sz; size_t empty_sz;
@ -1035,16 +972,23 @@ static int cbfs_stage_make_elf(struct buffer *buff, uint32_t arch)
return -1; return -1;
} }
if (cbfs_stage_decompress(&stage, buff)) { struct cbfs_file_attr_stageheader *stage = NULL;
ERROR("Failed to decompress stage.\n"); for (struct cbfs_file_attribute *attr = cbfs_file_first_attr(entry);
attr != NULL; attr = cbfs_file_next_attr(entry, attr)) {
if (ntohl(attr->tag) == CBFS_FILE_ATTR_TAG_STAGEHEADER) {
stage = (struct cbfs_file_attr_stageheader *)attr;
break;
}
}
if (stage == NULL) {
ERROR("Stage header not found for %s\n", entry->filename);
return -1; return -1;
} }
if (init_elf_from_arch(&ehdr, arch)) if (init_elf_from_arch(&ehdr, arch))
return -1; return -1;
ehdr.e_entry = stage.entry;
/* Attempt rmodule translation first. */ /* Attempt rmodule translation first. */
rmod_ret = rmodule_stage_to_elf(&ehdr, buff); rmod_ret = rmodule_stage_to_elf(&ehdr, buff);
@ -1056,6 +1000,8 @@ static int cbfs_stage_make_elf(struct buffer *buff, uint32_t arch)
/* Rmodule couldn't do anything with the data. Continue on with SELF. */ /* Rmodule couldn't do anything with the data. Continue on with SELF. */
ehdr.e_entry = ntohll(stage->loadaddr) + ntohl(stage->entry_offset);
ew = elf_writer_init(&ehdr); ew = elf_writer_init(&ehdr);
if (ew == NULL) { if (ew == NULL) {
ERROR("Unable to init ELF writer.\n"); ERROR("Unable to init ELF writer.\n");
@ -1065,9 +1011,9 @@ static int cbfs_stage_make_elf(struct buffer *buff, uint32_t arch)
memset(&shdr, 0, sizeof(shdr)); memset(&shdr, 0, sizeof(shdr));
shdr.sh_type = SHT_PROGBITS; shdr.sh_type = SHT_PROGBITS;
shdr.sh_flags = SHF_WRITE | SHF_ALLOC | SHF_EXECINSTR; shdr.sh_flags = SHF_WRITE | SHF_ALLOC | SHF_EXECINSTR;
shdr.sh_addr = stage.load; shdr.sh_addr = ntohll(stage->loadaddr);
shdr.sh_size = stage.len; shdr.sh_size = buffer_size(buff);
empty_sz = stage.memlen - stage.len; empty_sz = ntohl(stage->memlen) - buffer_size(buff);
if (elf_writer_add_section(ew, &shdr, buff, ".program")) { if (elf_writer_add_section(ew, &shdr, buff, ".program")) {
ERROR("Unable to add ELF section: .program\n"); ERROR("Unable to add ELF section: .program\n");
@ -1082,7 +1028,7 @@ static int cbfs_stage_make_elf(struct buffer *buff, uint32_t arch)
memset(&shdr, 0, sizeof(shdr)); memset(&shdr, 0, sizeof(shdr));
shdr.sh_type = SHT_NOBITS; shdr.sh_type = SHT_NOBITS;
shdr.sh_flags = SHF_WRITE | SHF_ALLOC; shdr.sh_flags = SHF_WRITE | SHF_ALLOC;
shdr.sh_addr = stage.load + stage.len; shdr.sh_addr = ntohl(stage->loadaddr) + buffer_size(buff);
shdr.sh_size = empty_sz; shdr.sh_size = empty_sz;
if (elf_writer_add_section(ew, &shdr, &b, ".empty")) { if (elf_writer_add_section(ew, &shdr, &b, ".empty")) {
ERROR("Unable to add ELF section: .empty\n"); ERROR("Unable to add ELF section: .empty\n");
@ -1106,7 +1052,8 @@ static int cbfs_stage_make_elf(struct buffer *buff, uint32_t arch)
return 0; return 0;
} }
static int cbfs_payload_make_elf(struct buffer *buff, uint32_t arch) static int cbfs_payload_make_elf(struct buffer *buff, uint32_t arch,
unused struct cbfs_file *entry)
{ {
Elf64_Ehdr ehdr; Elf64_Ehdr ehdr;
Elf64_Shdr shdr; Elf64_Shdr shdr;
@ -1258,7 +1205,7 @@ static int cbfs_payload_make_elf(struct buffer *buff, uint32_t arch)
} }
if (elf_writer_serialize(ew, &elf_out)) { if (elf_writer_serialize(ew, &elf_out)) {
ERROR("Unable to create ELF file from stage.\n"); ERROR("Unable to create ELF file from payload.\n");
goto out; goto out;
} }
@ -1320,13 +1267,13 @@ int cbfs_export_entry(struct cbfs_image *image, const char *entry_name,
} }
/* /*
* The stage metadata is never compressed proper for cbfs_stage * We want to export stages and payloads as ELFs, not with coreboot's
* files. The contents of the stage data can be though. Therefore * custom stage/SELF binary formats, so we need to do extra processing
* one has to do a second pass for stages to potentially decompress * to turn them back into an ELF.
* the stage data to make it more meaningful.
*/ */
if (do_processing) { if (do_processing) {
int (*make_elf)(struct buffer *, uint32_t) = NULL; int (*make_elf)(struct buffer *, uint32_t,
struct cbfs_file *) = NULL;
switch (ntohl(entry->type)) { switch (ntohl(entry->type)) {
case CBFS_TYPE_STAGE: case CBFS_TYPE_STAGE:
make_elf = cbfs_stage_make_elf; make_elf = cbfs_stage_make_elf;
@ -1335,7 +1282,7 @@ int cbfs_export_entry(struct cbfs_image *image, const char *entry_name,
make_elf = cbfs_payload_make_elf; make_elf = cbfs_payload_make_elf;
break; break;
} }
if (make_elf && make_elf(&buffer, arch)) { if (make_elf && make_elf(&buffer, arch, entry)) {
ERROR("Failed to write %s into %s.\n", ERROR("Failed to write %s into %s.\n",
entry_name, filename); entry_name, filename);
buffer_delete(&buffer); buffer_delete(&buffer);
@ -1387,17 +1334,29 @@ int cbfs_print_header_info(struct cbfs_image *image)
return 0; return 0;
} }
static int cbfs_print_stage_info(struct cbfs_stage *stage, FILE* fp) static int cbfs_print_stage_info(struct cbfs_file *entry, FILE* fp)
{ {
struct cbfs_file_attr_stageheader *stage = NULL;
for (struct cbfs_file_attribute *attr = cbfs_file_first_attr(entry);
attr != NULL; attr = cbfs_file_next_attr(entry, attr)) {
if (ntohl(attr->tag) == CBFS_FILE_ATTR_TAG_STAGEHEADER) {
stage = (struct cbfs_file_attr_stageheader *)attr;
break;
}
}
if (stage == NULL) {
fprintf(fp, " ERROR: stage header not found!\n");
return -1;
}
fprintf(fp, fprintf(fp,
" %s compression, entry: 0x%" PRIx64 ", load: 0x%" PRIx64 ", " " entry: 0x%" PRIx64 ", load: 0x%" PRIx64 ", "
"length: %d/%d\n", "memlen: %d\n",
lookup_name_by_type(types_cbfs_compression, ntohll(stage->loadaddr) + ntohl(stage->entry_offset),
stage->compression, "(unknown)"), ntohll(stage->loadaddr),
stage->entry, ntohl(stage->memlen));
stage->load,
stage->len,
stage->memlen);
return 0; return 0;
} }
@ -1519,8 +1478,7 @@ int cbfs_print_entry_info(struct cbfs_image *image, struct cbfs_file *entry,
/* note the components of the subheader may be in host order ... */ /* note the components of the subheader may be in host order ... */
switch (ntohl(entry->type)) { switch (ntohl(entry->type)) {
case CBFS_TYPE_STAGE: case CBFS_TYPE_STAGE:
cbfs_print_stage_info((struct cbfs_stage *) cbfs_print_stage_info(entry, fp);
CBFS_SUBHEADER(entry), fp);
break; break;
case CBFS_TYPE_SELF: case CBFS_TYPE_SELF:

View File

@ -14,7 +14,9 @@
#include "cbfs_sections.h" #include "cbfs_sections.h"
#include "elfparsing.h" #include "elfparsing.h"
#include "partitioned_file.h" #include "partitioned_file.h"
#include "lz4/lib/xxhash.h"
#include <commonlib/bsd/cbfs_private.h> #include <commonlib/bsd/cbfs_private.h>
#include <commonlib/bsd/compression.h>
#include <commonlib/bsd/metadata_hash.h> #include <commonlib/bsd/metadata_hash.h>
#include <commonlib/fsp.h> #include <commonlib/fsp.h>
#include <commonlib/endian.h> #include <commonlib/endian.h>
@ -911,15 +913,6 @@ static int cbfs_add_component(const char *filename,
sizeof(struct cbfs_file_attr_position)); sizeof(struct cbfs_file_attr_position));
if (attrs == NULL) if (attrs == NULL)
goto error; goto error;
/* If we add a stage or a payload, we need to take */
/* care about the additional metadata that is added */
/* to the cbfs file and therefore set the position */
/* the real beginning of the data. */
if (type == CBFS_TYPE_STAGE)
attrs->position = htonl(offset - sizeof(struct cbfs_stage));
else if (type == CBFS_TYPE_SELF)
attrs->position = htonl(offset - sizeof(struct cbfs_payload));
else
attrs->position = htonl(offset); attrs->position = htonl(offset);
} }
/* Add alignment attribute if used */ /* Add alignment attribute if used */
@ -1118,11 +1111,18 @@ static int cbfstool_convert_mkstage(struct buffer *buffer, uint32_t *offset,
* stages that would actually fit once compressed. * stages that would actually fit once compressed.
*/ */
if ((param.alignment || param.stage_xip) && if ((param.alignment || param.stage_xip) &&
do_cbfs_locate(offset, sizeof(struct cbfs_stage), data_size)) { do_cbfs_locate(offset, sizeof(struct cbfs_file_attr_stageheader),
data_size)) {
ERROR("Could not find location for stage.\n"); ERROR("Could not find location for stage.\n");
return 1; return 1;
} }
struct cbfs_file_attr_stageheader *stageheader = (void *)
cbfs_add_file_attr(header, CBFS_FILE_ATTR_TAG_STAGEHEADER,
sizeof(struct cbfs_file_attr_stageheader));
if (!stageheader)
return -1;
if (param.stage_xip) { if (param.stage_xip) {
/* /*
* Ensure the address is a memory mapped one. This assumes * Ensure the address is a memory mapped one. This assumes
@ -1132,19 +1132,57 @@ static int cbfstool_convert_mkstage(struct buffer *buffer, uint32_t *offset,
*offset = convert_addr_space(param.image_region, *offset); *offset = convert_addr_space(param.image_region, *offset);
ret = parse_elf_to_xip_stage(buffer, &output, offset, ret = parse_elf_to_xip_stage(buffer, &output, offset,
param.ignore_section); param.ignore_section,
stageheader);
} else { } else {
ret = parse_elf_to_stage(buffer, &output, param.compression, ret = parse_elf_to_stage(buffer, &output, param.ignore_section,
param.ignore_section); stageheader);
} }
if (ret != 0) if (ret != 0)
return -1; return -1;
/* Store a hash of original uncompressed stage to compare later. */
size_t decmp_size = buffer_size(&output);
uint32_t decmp_hash = XXH32(buffer_get(&output), decmp_size, 0);
/* Chain to base conversion routine to handle compression. */
ret = cbfstool_convert_raw(&output, offset, header);
if (ret != 0)
goto fail;
/* Special care must be taken for LZ4-compressed stages that the BSS is
large enough to provide scratch space for in-place decompression. */
if (!param.precompression && param.compression == CBFS_COMPRESS_LZ4) {
size_t memlen = ntohl(stageheader->memlen);
size_t compressed_size = buffer_size(&output);
uint8_t *compare_buffer = malloc(memlen);
uint8_t *start = compare_buffer + memlen - compressed_size;
if (!compare_buffer) {
ERROR("Out of memory\n");
goto fail;
}
memcpy(start, buffer_get(&output), compressed_size);
ret = ulz4fn(start, compressed_size, compare_buffer, memlen);
if (ret == 0) {
ERROR("Not enough scratch space to decompress LZ4 in-place -- increase BSS size or disable compression!\n");
free(compare_buffer);
goto fail;
} else if (ret != (int)decmp_size ||
decmp_hash != XXH32(compare_buffer, decmp_size, 0)) {
ERROR("LZ4 compression BUG! Report to mailing list.\n");
free(compare_buffer);
goto fail;
}
free(compare_buffer);
}
buffer_delete(buffer); buffer_delete(buffer);
// Direct assign, no dupe. buffer_clone(buffer, &output);
memcpy(buffer, &output, sizeof(*buffer));
header->len = htonl(output.size);
return 0; return 0;
fail:
buffer_delete(&output);
return -1;
} }
static int cbfstool_convert_mkpayload(struct buffer *buffer, static int cbfstool_convert_mkpayload(struct buffer *buffer,

View File

@ -174,10 +174,12 @@ int parse_flat_binary_to_payload(const struct buffer *input,
enum cbfs_compression algo); enum cbfs_compression algo);
/* cbfs-mkstage.c */ /* cbfs-mkstage.c */
int parse_elf_to_stage(const struct buffer *input, struct buffer *output, int parse_elf_to_stage(const struct buffer *input, struct buffer *output,
enum cbfs_compression algo, const char *ignore_section); const char *ignore_section,
struct cbfs_file_attr_stageheader *stageheader);
/* location is TOP aligned. */ /* location is TOP aligned. */
int parse_elf_to_xip_stage(const struct buffer *input, struct buffer *output, int parse_elf_to_xip_stage(const struct buffer *input, struct buffer *output,
uint32_t *location, const char *ignore_section); uint32_t *location, const char *ignore_section,
struct cbfs_file_attr_stageheader *stageheader);
void print_supported_architectures(void); void print_supported_architectures(void);
void print_supported_filetypes(void); void print_supported_filetypes(void);

View File

@ -498,8 +498,9 @@ write_elf(const struct rmod_context *ctx, const struct buffer *in,
/* Program contents. */ /* Program contents. */
buffer_splice(&program, in, ctx->phdr->p_offset, ctx->phdr->p_filesz); buffer_splice(&program, in, ctx->phdr->p_offset, ctx->phdr->p_filesz);
/* Create ELF writer with modified entry point. */ /* Create ELF writer. Set entry point to 0 to match section offsets. */
memcpy(&ehdr, &ctx->pelf.ehdr, sizeof(ehdr)); memcpy(&ehdr, &ctx->pelf.ehdr, sizeof(ehdr));
ehdr.e_entry = 0;
ew = elf_writer_init(&ehdr); ew = elf_writer_init(&ehdr);
if (ew == NULL) { if (ew == NULL) {