diff options
author | Philipp Deppenwiese <zaolin.daisuki@gmail.com> | 2018-11-20 13:54:49 +0100 |
---|---|---|
committer | Philipp Deppenwiese <zaolin.daisuki@gmail.com> | 2019-06-24 09:42:31 +0000 |
commit | 5ada0023d191eb6af1de6581f1b770b75b5554c4 (patch) | |
tree | 69e2b78e5230c3a8d5dd5692399db6c081b10113 /util/cbfstool | |
parent | 42c44c2f8391a3b56acf9215044d2b2787061738 (diff) | |
download | coreboot-5ada0023d191eb6af1de6581f1b770b75b5554c4.tar.xz |
cbfstool: Add ifittool
Add the IntelFirmwareInterfaceTable-tool to modify the FIT.
As cbfstool is overloaded with arguments, introduce a new tool
to only modify FIT, which brings it's own command line syntax.
Provide clean interface to:
* Clear FIT
* Add entry to CBFS file
* Add entry to REGION
* Delete entries
* Add support for types other than 1
* Add support to dump current table
* Add support for top-swap
* Sort entries by type
Most code is reused from existing cbfstool and functionality of cbfstool
is kept. It will be removed once the make system uses only ifittool.
Based on "Intel Trusted Execution Technology (Intel TXT) LAB Handout"
and https://github.com/slimbootloader/slimbootloader .
Change-Id: I0fe8cd70611d58823aca1147d5b830722ed72bd5
Signed-off-by: Patrick Rudolph <patrick.rudolph@9elements.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/31493
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Philipp Deppenwiese <zaolin.daisuki@gmail.com>
Diffstat (limited to 'util/cbfstool')
-rw-r--r-- | util/cbfstool/Makefile | 8 | ||||
-rw-r--r-- | util/cbfstool/Makefile.inc | 33 | ||||
-rw-r--r-- | util/cbfstool/fit.c | 707 | ||||
-rw-r--r-- | util/cbfstool/fit.h | 60 | ||||
-rw-r--r-- | util/cbfstool/ifittool.c | 431 |
5 files changed, 1110 insertions, 129 deletions
diff --git a/util/cbfstool/Makefile b/util/cbfstool/Makefile index d7137e907b..d5321f6959 100644 --- a/util/cbfstool/Makefile +++ b/util/cbfstool/Makefile @@ -12,7 +12,7 @@ OBJCOPY ?= objcopy VBOOT_SOURCE ?= $(top)/3rdparty/vboot .PHONY: all -all: cbfstool fmaptool rmodtool ifwitool cbfs-compression-tool +all: cbfstool ifittool fmaptool rmodtool ifwitool cbfs-compression-tool cbfstool: $(objutil)/cbfstool/cbfstool @@ -22,15 +22,18 @@ rmodtool: $(objutil)/cbfstool/rmodtool ifwitool: $(objutil)/cbfstool/ifwitool +ifittool: $(objutil)/cbfstool/ifittool + cbfs-compression-tool: $(objutil)/cbfstool/cbfs-compression-tool -.PHONY: clean cbfstool fmaptool rmodtool ifwitool cbfs-compression-tool +.PHONY: clean cbfstool ifittool fmaptool rmodtool ifwitool cbfs-compression-tool clean: $(RM) fmd_parser.c fmd_parser.h fmd_scanner.c fmd_scanner.h $(RM) $(objutil)/cbfstool/cbfstool $(cbfsobj) $(RM) $(objutil)/cbfstool/fmaptool $(fmapobj) $(RM) $(objutil)/cbfstool/rmodtool $(rmodobj) $(RM) $(objutil)/cbfstool/ifwitool $(ifwiobj) + $(RM) $(objutil)/cbfstool/ifittool $(ifitobj) $(RM) $(objutil)/cbfstool/cbfs-compression-tool $(cbfscompobj) linux_trampoline.c: linux_trampoline.S @@ -49,6 +52,7 @@ install: all $(INSTALL) fmaptool $(DESTDIR)$(BINDIR) $(INSTALL) rmodtool $(DESTDIR)$(BINDIR) $(INSTALL) ifwitool $(DESTDIR)$(BINDIR) + $(INSTALL) ifittool $(DESTDIR)$(BINDIR) $(INSTALL) cbfs-compression-tool $(DESTDIR)$(BINDIR) ifneq ($(V),1) diff --git a/util/cbfstool/Makefile.inc b/util/cbfstool/Makefile.inc index 79285207b0..0340c3eecb 100644 --- a/util/cbfstool/Makefile.inc +++ b/util/cbfstool/Makefile.inc @@ -66,6 +66,35 @@ ifwiobj := ifwiobj += ifwitool.o ifwiobj += common.o +ifitobj := +ifitobj += ifittool.o +ifitobj += common.o +ifitobj += fit.o +ifitobj += cbfs_image.o +# Make it link .... +ifitobj += xdr.o +ifitobj += elfheaders.o +ifitobj += partitioned_file.o +ifitobj += cbfs-mkstage.o +ifitobj += cbfs-mkpayload.o +ifitobj += rmodule.o +# COMMONLIB +ifitobj += cbfs.o +ifitobj += mem_pool.o +ifitobj += region.o +# CRYPTOLIB +ifitobj += 2sha_utility.o +ifitobj += 2sha1.o +ifitobj += 2sha256.o +ifitobj += 2sha512.o +# FMAP +ifitobj += fmap.o +ifitobj += kv_pair.o +ifitobj += valstr.o +# compression algorithms +ifitobj += $(compressionobj) + + cbfscompobj := cbfscompobj += $(compressionobj) cbfscompobj += cbfscomptool.o @@ -149,6 +178,10 @@ $(objutil)/cbfstool/ifwitool: $(addprefix $(objutil)/cbfstool/,$(ifwiobj)) printf " HOSTCC $(subst $(objutil)/,,$(@)) (link)\n" $(HOSTCC) $(TOOLLDFLAGS) -o $@ $(addprefix $(objutil)/cbfstool/,$(ifwiobj)) +$(objutil)/cbfstool/ifittool: $(addprefix $(objutil)/cbfstool/,$(ifitobj)) + printf " HOSTCC $(subst $(objutil)/,,$(@)) (link)\n" + $(HOSTCC) $(TOOLLDFLAGS) -o $@ $(addprefix $(objutil)/cbfstool/,$(ifitobj)) + $(objutil)/cbfstool/cbfs-compression-tool: $(addprefix $(objutil)/cbfstool/,$(cbfscompobj)) printf " HOSTCC $(subst $(objutil)/,,$(@)) (link)\n" $(HOSTCC) $(TOOLLDFLAGS) -o $@ $(addprefix $(objutil)/cbfstool/,$(cbfscompobj)) diff --git a/util/cbfstool/fit.c b/util/cbfstool/fit.c index aeb1755032..86dde4d23d 100644 --- a/util/cbfstool/fit.c +++ b/util/cbfstool/fit.c @@ -2,6 +2,8 @@ * Firmware Interface Table support. * * Copyright (C) 2012 Google Inc. + * Copyright (C) 2019 9elements Agency GmbH + * Copyright (C) 2019 Facebook Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,17 +27,42 @@ #define FIT_POINTER_LOCATION 0xffffffc0 #define FIT_TABLE_LOWEST_ADDRESS ((uint32_t)(-(16 << 20))) #define FIT_ENTRY_CHECKSUM_VALID 0x80 -#define FIT_TYPE_HEADER 0x0 -#define FIT_HEADER_VERSION 0x0100 -#define FIT_HEADER_ADDRESS "_FIT_ " -#define FIT_TYPE_MICROCODE 0x1 -#define FIT_MICROCODE_VERSION 0x0100 +#define FIT_HEADER_VERSION 0x0100 +#define FIT_HEADER_ADDRESS "_FIT_ " +#define FIT_MICROCODE_VERSION 0x0100 +#define FIT_TXT_VERSION 0x0100 + +#define FIT_SIZE_ALIGNMENT 16 struct fit_entry { + /** + * Address is the base address of the firmware component + * must be aligned on 16 byte boundary + */ uint64_t address; + /** + * Size is the span of the component in multiple of 16 bytes + * Bits [24:31] are reserved and must be set to 0 + */ uint32_t size_reserved; + /** + * Component's version number in binary coded decimal (BCD) format. + * For the FIT header entry, the value in this field will indicate the + * revision number of the FIT data structure. The upper byte of the + * revision field indicates the major revision and the lower byte + * indicates the minor revision. + */ uint16_t version; + /** + * FIT types 0x00 to 0x7F + * Bit 7 (C_V) indicates whether component has valid checksum. + */ uint8_t type_checksum_valid; + /** + * Component's checksum. The modulo sum of all the bytes in the + * component and the value in this field (Chksum) must add up to zero. + * This field is only valid if the C_V flag is non-zero. + */ uint8_t checksum; } __packed; @@ -67,20 +94,20 @@ static inline void *rom_buffer_pointer(struct buffer *buffer, int offset) return &buffer->data[offset]; } -static inline int fit_entry_size_bytes(struct fit_entry *entry) +static inline size_t fit_entry_size_bytes(const struct fit_entry *entry) { return (entry->size_reserved & 0xffffff) << 4; } static inline void fit_entry_update_size(struct fit_entry *entry, - int size_bytes) + const int size_bytes) { /* Size is multiples of 16 bytes. */ entry->size_reserved = (size_bytes >> 4) & 0xffffff; } static inline void fit_entry_add_size(struct fit_entry *entry, - int size_bytes) + const int size_bytes) { int size = fit_entry_size_bytes(entry); size += size_bytes; @@ -114,8 +141,70 @@ static inline uint32_t offset_to_ptr(fit_offset_converter_t helper, return -helper(region, offset); } +/* + * Return the number of FIT entries. + */ +static inline size_t fit_table_entries(const struct fit_table *fit) +{ + if (!fit) + return 0; + + return (fit_entry_size_bytes(&fit->header) / FIT_SIZE_ALIGNMENT) - 1; +} + +/* + * Return the number of unused entries. + */ +static inline size_t fit_free_space(struct fit_table *fit, + const size_t max_entries) +{ + if (!fit) + return 0; + + return max_entries - fit_table_entries(fit); +} + +/* + * Sort entries by type and fill gaps (entries with type unused). + * To be called after adding or deleting entries. + * + * This one is critical, as mentioned in Chapter 1.2.1 "FIT Ordering Rules" + * "Firmware Interface Table BIOS Specification". + * + * We need to use a stable sorting algortihm, as the order of + * FIT_TYPE_BIOS_STARTUP matter for measurements. + */ +static void sort_fit_table(struct fit_table *fit) +{ + struct fit_entry tmp; + size_t i, j; + int swapped; + + /* Bubble sort entries */ + for (j = 0; j < fit_table_entries(fit) - 1; j++) { + swapped = 0; + for (i = 0; i < fit_table_entries(fit) - j - 1; i++) { + if (fit->entries[i].type_checksum_valid <= + fit->entries[i + 1].type_checksum_valid) + continue; + /* SWAP entries */ + memcpy(&tmp, &fit->entries[i], sizeof(tmp)); + memcpy(&fit->entries[i], &fit->entries[i + 1], + sizeof(fit->entries[i])); + memcpy(&fit->entries[i + 1], &tmp, + sizeof(fit->entries[i + 1])); + swapped = 1; + } + if (!swapped) + break; + } +} + static int fit_table_verified(struct fit_table *table) { + if (!table) + return 0; + /* Check that the address field has the proper signature. */ if (strncmp((const char *)&table->header.address, FIT_HEADER_ADDRESS, sizeof(table->header.address))) @@ -127,35 +216,17 @@ static int fit_table_verified(struct fit_table *table) if (fit_entry_type(&table->header) != FIT_TYPE_HEADER) return 0; - /* Assume that the FIT table only contains the header */ - if (fit_entry_size_bytes(&table->header) != sizeof(struct fit_entry)) + /* Assume that the FIT table contains at least the header */ + if (fit_entry_size_bytes(&table->header) < sizeof(struct fit_entry)) return 0; return 1; } -static struct fit_table *locate_fit_table(fit_offset_converter_t offset_helper, - struct buffer *buffer) -{ - struct fit_table *table; - uint32_t *fit_pointer; - - fit_pointer = rom_buffer_pointer(buffer, - ptr_to_offset(offset_helper, buffer, - FIT_POINTER_LOCATION)); - - /* Ensure pointer is below 4GiB and within 16MiB of 4GiB */ - if (fit_pointer[1] != 0 || fit_pointer[0] < FIT_TABLE_LOWEST_ADDRESS) - return NULL; - - table = rom_buffer_pointer(buffer, - ptr_to_offset(offset_helper, buffer, *fit_pointer)); - if (!fit_table_verified(table)) - return NULL; - else - return table; -} - +/* + * Update the FIT checksum. + * To be called after modifiying the table. + */ static void update_fit_checksum(struct fit_table *fit) { int size_bytes; @@ -163,6 +234,9 @@ static void update_fit_checksum(struct fit_table *fit) uint8_t result; int i; + if (!fit) + return; + fit->header.checksum = 0; size_bytes = fit_entry_size_bytes(&fit->header); result = 0; @@ -172,92 +246,68 @@ static void update_fit_checksum(struct fit_table *fit) fit->header.checksum = -result; } -static void update_fit_ucode_entry(struct fit_table *fit, - struct fit_entry *entry, uint64_t mcu_addr) -{ - entry->address = mcu_addr; - /* - * While loading MCU, its size is not referred from FIT and - * rather from the MCU header, hence we can assign zero here - */ - entry->size_reserved = 0x0000; - entry->type_checksum_valid = FIT_TYPE_MICROCODE; - entry->version = FIT_MICROCODE_VERSION; - entry->checksum = 0; - fit_entry_add_size(&fit->header, sizeof(struct fit_entry)); -} - -static void add_microcodde_entries(struct fit_table *fit, - const struct cbfs_image *image, - int num_mcus, struct microcode_entry *mcus, - fit_offset_converter_t offset_helper, - uint32_t first_mcu_addr) -{ - int i = 0; - /* - * Check if an entry has to be forced into the FIT at index 0. - * first_mcu_addr is an address (in ROM) that will point to a - * microcode patch. - */ - if (first_mcu_addr) { - struct fit_entry *entry = &fit->entries[0]; - update_fit_ucode_entry(fit, entry, first_mcu_addr); - i = 1; - } - - struct microcode_entry *mcu = &mcus[0]; - for (; i < num_mcus; i++) { - struct fit_entry *entry = &fit->entries[i]; - update_fit_ucode_entry(fit, entry, offset_to_ptr(offset_helper, - &image->buffer, mcu->offset)); - mcu++; - } -} - -static void cbfs_file_get_header(struct buffer *buf, struct cbfs_file *file) +/* + * Return a pointer to the next free entry. + * Caller must take care if enough space is available. + */ +static struct fit_entry *get_next_free_entry(struct fit_table *fit) { - bgets(buf, &file->magic, sizeof(file->magic)); - file->len = xdr_be.get32(buf); - file->type = xdr_be.get32(buf); - file->attributes_offset = xdr_be.get32(buf); - file->offset = xdr_be.get32(buf); + return &fit->entries[fit_table_entries(fit)]; } -static int fit_header(void *ptr, uint32_t *current_offset, uint32_t *file_length) +static void fit_location_from_cbfs_header(uint32_t *current_offset, + uint32_t *file_length, void *ptr) { struct buffer buf; struct cbfs_file header; + memset(&buf, 0, sizeof(buf)); + buf.data = ptr; buf.size = sizeof(header); - cbfs_file_get_header(&buf, &header); + + bgets(&buf, header.magic, sizeof(header.magic)); + header.len = xdr_be.get32(&buf); + header.type = xdr_be.get32(&buf); + header.attributes_offset = xdr_be.get32(&buf); + header.offset = xdr_be.get32(&buf); + *current_offset = header.offset; *file_length = header.len; - return 0; } -static int parse_microcode_blob(struct cbfs_image *image, - struct cbfs_file *mcode_file, - struct microcode_entry *mcus, - int total_entries, int *mcus_found) +static int +parse_microcode_blob(struct cbfs_image *image, + const char *blob_name, + size_t *mcus_found, + struct microcode_entry *mcus, + const size_t max_fit_entries) { - int num_mcus; + size_t num_mcus; uint32_t current_offset; uint32_t file_length; + struct cbfs_file *mcode_file; + + mcode_file = cbfs_get_entry(image, blob_name); + if (!mcode_file) + return 1; - fit_header(mcode_file, ¤t_offset, &file_length); - current_offset += (int)((char *)mcode_file - image->buffer.data); + fit_location_from_cbfs_header(¤t_offset, &file_length, + mcode_file); + current_offset += cbfs_get_entry_addr(image, mcode_file); num_mcus = 0; - while (file_length > sizeof(struct microcode_header)) - { + while (file_length > sizeof(struct microcode_header)) { const struct microcode_header *mcu_header; mcu_header = rom_buffer_pointer(&image->buffer, current_offset); + if (!mcu_header) { + ERROR("Couldn't parse microcode header.\n"); + return 1; + } /* Newer microcode updates include a size field, whereas older * containers set it at 0 and are exactly 2048 bytes long */ - uint32_t total_size = mcu_header->total_size - ? mcu_header->total_size : 2048; + uint32_t total_size = mcu_header->total_size ?: 2048; /* Quickly sanity check a prospective microcode update. */ if (total_size < sizeof(*mcu_header)) @@ -271,9 +321,8 @@ static int parse_microcode_blob(struct cbfs_image *image, current_offset += mcus[num_mcus].size; file_length -= mcus[num_mcus].size; num_mcus++; - /* Reached limit of FIT entries. */ - if (num_mcus == total_entries) + if (num_mcus == max_fit_entries) break; if (file_length < sizeof(struct microcode_header)) break; @@ -285,42 +334,466 @@ static int parse_microcode_blob(struct cbfs_image *image, return 0; } +/* There can be zero or more FIT_TYPE_MICROCODE entries */ +static void update_fit_ucode_entry(struct fit_table *fit, + struct fit_entry *entry, + const uint64_t mcu_addr) +{ + entry->address = mcu_addr; + /* + * While loading MCU, its size is not referred from FIT and + * rather from the MCU header, hence we can assign zero here. + */ + entry->size_reserved = 0; + entry->type_checksum_valid = FIT_TYPE_MICROCODE; + entry->version = FIT_MICROCODE_VERSION; + entry->checksum = 0; + fit_entry_add_size(&fit->header, sizeof(struct fit_entry)); +} + +/* + * There can be zero or one FIT_TYPE_BIOS_ACM entry per table. + * In case there's a FIT_TYPE_BIOS_ACM entry, at least one + * FIT_TYPE_BIOS_STARTUP entry must exist. + * + * The caller has to provide valid arguments as those aren't verfied. + */ +static void update_fit_bios_acm_entry(struct fit_table *fit, + struct fit_entry *entry, + const uint64_t acm_addr) +{ + entry->address = acm_addr; + /* + * The Address field points to a BIOS ACM. The Address field points to + * the first byte of the AC module header. When BIOS ACM is loaded in + * Authenticated Code RAM, one MTRR base/limit pair is used to map it. + */ + entry->size_reserved = 0; + entry->type_checksum_valid = FIT_TYPE_BIOS_ACM; + entry->version = FIT_TXT_VERSION; + entry->checksum = 0; + fit_entry_add_size(&fit->header, sizeof(struct fit_entry)); +} + +/* + * In case there's a FIT_TYPE_BIOS_ACM entry, at least one + * FIT_TYPE_BIOS_STARTUP entry must exist. + * + * The caller has to provide valid arguments as those aren't verfied. + */ +static void update_fit_bios_startup_entry(struct fit_table *fit, + struct fit_entry *entry, + const uint64_t sm_addr, + const uint32_t sm_size) +{ + entry->address = sm_addr; + assert(sm_size % 16 == 0); + /* + * BIOS Startup code is defined as the code that gets control at the + * reset vector and continues the chain of trust in TCG-compliant + * fashion. In addition, this code may also configure memory and SMRAM. + */ + fit_entry_update_size(entry, sm_size); + entry->type_checksum_valid = FIT_TYPE_BIOS_STARTUP; + entry->version = FIT_TXT_VERSION; + entry->checksum = 0; + fit_entry_add_size(&fit->header, sizeof(struct fit_entry)); +} + +/* + * There can be zero or one FIT_TYPE_BIOS_POLICY Record in the FIT. + * If the platform uses the hash comparison method and employs a + * failsafe bootblock, one FIT_TYPE_BIOS_POLICY entry is needed to + * contain the failsafe hash. + * If the platform uses the Signature verification method, one + * FIT_TYPE_BIOS_POLICY entry is needed. In this case, the entry + * contains the OEM key, hash of the BIOS and signature over the hash + * using the OEM key. + * In all other cases, the FIT_TYPE_BIOS_POLICY record is not required. + * + * The caller has to provide valid arguments as those aren't verfied. + */ +static void update_fit_bios_policy_entry(struct fit_table *fit, + struct fit_entry *entry, + const uint64_t lcp_policy_addr, + const uint32_t lcp_policy_size) +{ + entry->address = lcp_policy_addr; + fit_entry_update_size(entry, lcp_policy_size); + entry->type_checksum_valid = FIT_TYPE_BIOS_POLICY; + entry->version = FIT_TXT_VERSION; + entry->checksum = 0; + fit_entry_add_size(&fit->header, sizeof(struct fit_entry)); +} + +/* + * There can be zero or one FIT_TYPE_TXT_POLICY entries + * + * The caller has to provide valid arguments as those aren't verfied. + */ +static void update_fit_txt_policy_entry(struct fit_table *fit, + struct fit_entry *entry, + uint64_t txt_policy_addr) +{ + entry->address = txt_policy_addr; + /* + * Points to the flag indicating if TXT is enabled on this platform. + * If not present, TXT is not disabled by FIT. + */ + entry->size_reserved = 0; + entry->type_checksum_valid = FIT_TYPE_TXT_POLICY; + entry->version = 0x1; + entry->checksum = 0; + fit_entry_add_size(&fit->header, sizeof(struct fit_entry)); +} + +/* Special case for ucode CBFS file, as it might contain more than one ucode */ +int fit_add_microcode_file(struct fit_table *fit, + struct cbfs_image *image, + const char *blob_name, + fit_offset_converter_t offset_helper, + const size_t max_fit_entries) +{ + struct microcode_entry *mcus; + + size_t i; + size_t mcus_found; + + mcus = malloc(sizeof(*mcus) * max_fit_entries); + if (!mcus) { + ERROR("Couldn't allocate memory for microcode entries.\n"); + return 1; + } + + if (parse_microcode_blob(image, blob_name, &mcus_found, mcus, + max_fit_entries)) { + ERROR("Couldn't parse microcode blob.\n"); + free(mcus); + return 1; + } + + if (mcus_found > fit_free_space(fit, max_fit_entries)) { + ERROR("Maximum of FIT entries reached.\n"); + free(mcus); + return 1; + } + + for (i = 0; i < mcus_found; i++) { + if (fit_add_entry(fit, + offset_to_ptr(offset_helper, &image->buffer, + mcus[i].offset), + 0, + FIT_TYPE_MICROCODE, + max_fit_entries)) { + + free(mcus); + return 1; + } + } + + free(mcus); + return 0; +} + +/* + * Return a pointer to the active FIT. + */ +struct fit_table *fit_get_table(struct buffer *bootblock, + fit_offset_converter_t offset_fn, + uint32_t topswap_size) +{ + struct fit_table *fit; + uint32_t *fit_pointer; + + fit_pointer = rom_buffer_pointer(bootblock, + ptr_to_offset(offset_fn, bootblock, + FIT_POINTER_LOCATION)); + + /* Ensure pointer is below 4GiB and within 16MiB of 4GiB */ + if (fit_pointer[1] != 0 || fit_pointer[0] < FIT_TABLE_LOWEST_ADDRESS) { + ERROR("FIT not found.\n"); + return NULL; + } + + fit = rom_buffer_pointer(bootblock, + ptr_to_offset(offset_fn, bootblock, *fit_pointer)); + if (!fit_table_verified(fit)) { + ERROR("FIT not found.\n"); + return NULL; + } + + if (topswap_size) { + struct fit_table *fit2 = (struct fit_table *)((uintptr_t)fit - + topswap_size); + if (!fit_table_verified(fit2)) { + ERROR("second FIT is invalid\n"); + return NULL; + } + fit = fit2; + } + + DEBUG("Operating on table (0x%x)\n", *fit_pointer - topswap_size); + + return fit; +} + +/* + * Dump the current FIT in human readable format to stdout. + */ +int fit_dump(struct fit_table *fit) +{ + size_t i; + + if (!fit) + return 1; + + printf("\n"); + printf(" FIT table:\n"); + + if (fit_table_entries(fit) < 1) { + printf(" empty\n\n"); + return 0; + } + + printf(" %-6s %-20s %-16s %-8s\n", "Index", "Type", "Addr", "Size"); + + for (i = 0; i < fit_table_entries(fit); i++) { + const char *name; + + switch (fit->entries[i].type_checksum_valid) { + case FIT_TYPE_MICROCODE: + name = "Microcode"; + break; + case FIT_TYPE_BIOS_ACM: + name = "BIOS ACM"; + break; + case FIT_TYPE_BIOS_STARTUP: + name = "BIOS Startup Module"; + break; + case FIT_TYPE_TPM_POLICY: + name = "TPM Policy"; + break; + case FIT_TYPE_BIOS_POLICY: + name = "BIOS Policy"; + break; + case FIT_TYPE_TXT_POLICY: + name = "TXT Policy"; + break; + case FIT_TYPE_KEY_MANIFEST: + name = "Key Manifest"; + break; + case FIT_TYPE_BOOT_POLICY: + name = "Boot Policy"; + break; + case FIT_TYPE_CSE_SECURE_BOOT: + name = "CSE SecureBoot"; + break; + case FIT_TYPE_TXTSX_POLICY: + name = "TXTSX policy"; + break; + case FIT_TYPE_JMP_DEBUG_POLICY: + name = "JMP debug policy"; + break; + case FIT_TYPE_UNUSED: + name = "unused"; + break; + default: + name = "unknown"; + } + + printf(" %6zd %-20s 0x%08"PRIx64" 0x%08zx\n", i, name, + fit->entries[i].address, + fit_entry_size_bytes(&fit->entries[i])); + } + printf("\n"); + return 0; +} + +/* + * Remove all entries from table. + */ +int fit_clear_table(struct fit_table *fit) +{ + if (!fit) + return 1; + + memset(fit->entries, 0, + sizeof(struct fit_entry) * fit_table_entries(fit)); + + /* Reset entry counter in header */ + fit_entry_update_size(&fit->header, sizeof(fit->header)); + + update_fit_checksum(fit); + + return 0; +} + +/* + * Returns true if the FIT type is know and can be added to the table. + */ +int fit_is_supported_type(const enum fit_type type) +{ + switch (type) { + case FIT_TYPE_MICROCODE: + case FIT_TYPE_BIOS_ACM: + case FIT_TYPE_BIOS_STARTUP: + case FIT_TYPE_BIOS_POLICY: + case FIT_TYPE_TXT_POLICY: + return 1; + case FIT_TYPE_TPM_POLICY: + case FIT_TYPE_KEY_MANIFEST: + case FIT_TYPE_BOOT_POLICY: + default: + return 0; + } +} + +/* + * Adds an known entry to the FIT. + * len is optional for same types and might be zero. + * offset is an absolute address in 32-bit protected mode address space. + */ +int fit_add_entry(struct fit_table *fit, + const uint32_t offset, + const uint32_t len, + const enum fit_type type, + const size_t max_fit_entries) +{ + struct fit_entry *entry; + + if (!fit) { + ERROR("Internal error."); + return 1; + } + + if (fit_free_space(fit, max_fit_entries) < 1) { + ERROR("No space left in FIT."); + return 1; + } + + if (!fit_is_supported_type(type)) { + ERROR("Unsupported FIT type %u\n", type); + return 1; + } + + DEBUG("Adding new entry type %u at offset %zd\n", type, + fit_table_entries(fit)); + + entry = get_next_free_entry(fit); + + switch (type) { + case FIT_TYPE_MICROCODE: + update_fit_ucode_entry(fit, entry, offset); + break; + case FIT_TYPE_BIOS_ACM: + update_fit_bios_acm_entry(fit, entry, offset); + break; + case FIT_TYPE_BIOS_STARTUP: + update_fit_bios_startup_entry(fit, entry, offset, len); + break; + case FIT_TYPE_BIOS_POLICY: + update_fit_bios_policy_entry(fit, entry, offset, len); + break; + case FIT_TYPE_TXT_POLICY: + update_fit_txt_policy_entry(fit, entry, offset); + break; + default: + return 1; + } + + sort_fit_table(fit); + + update_fit_checksum(fit); + + return 0; +} + +/* + * Delete one entry from table. + */ +int fit_delete_entry(struct fit_table *fit, + const size_t idx) +{ + if (!fit) { + ERROR("Internal error."); + return 1; + } + + if (idx >= fit_table_entries(fit)) { + ERROR("Index out of range."); + return 1; + } + + memset(&fit->entries[idx], 0, sizeof(struct fit_entry)); + + fit->entries[idx].type_checksum_valid = FIT_TYPE_UNUSED; + + sort_fit_table(fit); + + /* The unused entry is now the last one */ + fit_entry_add_size(&fit->header, -(int)sizeof(struct fit_entry)); + + update_fit_checksum(fit); + + return 0; +} + +/* Legacy code. TODO: Remove once ifittool is merged. */ + +static void add_microcodde_entries(struct fit_table *fit, + const struct cbfs_image *image, + ssize_t num_mcus, + struct microcode_entry *mcus, + fit_offset_converter_t offset_helper, + uint32_t first_mcu_addr) +{ + int i = 0; + /* + * Check if an entry has to be forced into the FIT at index 0. + * first_mcu_addr is an address (in ROM) that will point to a + * microcode patch. + */ + if (first_mcu_addr) { + struct fit_entry *entry = &fit->entries[0]; + update_fit_ucode_entry(fit, entry, first_mcu_addr); + i = 1; + } + + struct microcode_entry *mcu = &mcus[0]; + for (; i < num_mcus; i++) { + struct fit_entry *entry = &fit->entries[i]; + update_fit_ucode_entry(fit, entry, offset_to_ptr(offset_helper, + &image->buffer, mcu->offset)); + mcu++; + } +} + int fit_update_table(struct buffer *bootblock, struct cbfs_image *image, - const char *microcode_blob_name, int empty_entries, + const char *microcode_blob_name, + unsigned int empty_entries, fit_offset_converter_t offset_fn, uint32_t topswap_size, - uint32_t first_mcu_addr) + uint32_t first_mcu_addr) { struct fit_table *fit, *fit2; - struct cbfs_file *mcode_file; struct microcode_entry *mcus; - int mcus_found; + size_t mcus_found; int ret = 0; - // struct rom_image image = { .rom = rom, .size = romsize, }; - - fit = locate_fit_table(offset_fn, bootblock); + fit = fit_get_table(bootblock, offset_fn, 0); if (!fit) { ERROR("FIT not found.\n"); return 1; } - mcode_file = cbfs_get_entry(image, microcode_blob_name); - if (!mcode_file) { - ERROR("File '%s' not found in CBFS.\n", - microcode_blob_name); - return 1; - } - mcus = malloc(sizeof(*mcus) * empty_entries); - if (!mcus) { - ERROR("Couldn't allocate memory for microcode update entries.\n"); + ERROR("Couldn't allocate memory for microcode entries.\n"); return 1; } - if (parse_microcode_blob(image, mcode_file, mcus, empty_entries, - &mcus_found)) { + if (parse_microcode_blob(image, microcode_blob_name, &mcus_found, + mcus, empty_entries)) { ERROR("Couldn't parse microcode blob.\n"); ret = 1; goto out; @@ -333,7 +806,7 @@ int fit_update_table(struct buffer *bootblock, struct cbfs_image *image, /* A second fit is exactly topswap size away from the bottom one */ if (topswap_size) { - fit2 = (struct fit_table *)((uintptr_t)fit - topswap_size); + fit2 = fit_get_table(bootblock, offset_fn, topswap_size); if (!fit_table_verified(fit2)) { ERROR("second FIT is invalid\n"); @@ -343,8 +816,8 @@ int fit_update_table(struct buffer *bootblock, struct cbfs_image *image, /* Check if we have room for first entry */ if (first_mcu_addr) { if (mcus_found >= empty_entries) { - ERROR("No room, blob mcus = %d, total entries = %d\n", - mcus_found, empty_entries); + ERROR("No room, blob mcus = %zd, total entries" + " = %d\n", mcus_found, empty_entries); ret = 1; goto out; } diff --git a/util/cbfstool/fit.h b/util/cbfstool/fit.h index 42b3b4722a..e5872ab599 100644 --- a/util/cbfstool/fit.h +++ b/util/cbfstool/fit.h @@ -2,6 +2,8 @@ * Firmware Interface Table support. * * Copyright (C) 2012 Google Inc. + * Copyright (C) 2019 9elements Agency GmbH + * Copyright (C) 2019 Facebook Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,6 +21,26 @@ #include "cbfs_image.h" #include "common.h" +/** + * Based on "Intel Trusted Execution Technology (Intel TXT) LAB Handout" and + * https://github.com/slimbootloader/slimbootloader/ + */ +enum fit_type { + FIT_TYPE_HEADER = 0, + FIT_TYPE_MICROCODE = 1, + FIT_TYPE_BIOS_ACM = 2, + FIT_TYPE_BIOS_STARTUP = 7, + FIT_TYPE_TPM_POLICY = 8, + FIT_TYPE_BIOS_POLICY = 9, + FIT_TYPE_TXT_POLICY = 0xa, + FIT_TYPE_KEY_MANIFEST = 0xb, + FIT_TYPE_BOOT_POLICY = 0xc, + FIT_TYPE_CSE_SECURE_BOOT = 0x10, + FIT_TYPE_TXTSX_POLICY = 0x2d, + FIT_TYPE_JMP_DEBUG_POLICY = 0x2f, + FIT_TYPE_UNUSED = 127, +}; + /* * Converts between offsets from the start of the specified image region and * "top-aligned" offsets from the top of the entire flash image. Should work in @@ -28,15 +50,33 @@ typedef unsigned (*fit_offset_converter_t)(const struct buffer *region, unsigned offset); -/* - * populate FIT with the MCUs prepsent in the blob provided. - * - * first_mcu_addr is an address (in ROM) that will point to a - * microcode patch. When provided, it will be forced as the first - * MCU entry into the FIT located in the topswap bootblock. - */ +struct fit_table; + +struct fit_table *fit_get_table(struct buffer *bootblock, + fit_offset_converter_t offset_fn, + uint32_t topswap_size); +int fit_dump(struct fit_table *fit); +int fit_clear_table(struct fit_table *fit); +int fit_is_supported_type(const enum fit_type type); +int fit_add_entry(struct fit_table *fit, + const uint32_t offset, + const uint32_t len, + const enum fit_type type, + const size_t max_fit_entries); +int fit_delete_entry(struct fit_table *fit, + const size_t idx); + +int fit_add_microcode_file(struct fit_table *fit, + struct cbfs_image *image, + const char *blob_name, + fit_offset_converter_t offset_helper, + const size_t max_fit_entries); + +/* Legacy code */ int fit_update_table(struct buffer *bootblock, struct cbfs_image *image, - const char *microcode_blob_name, int empty_entries, - fit_offset_converter_t offset_fn, - uint32_t topswap_size, uint32_t first_mcu_addr); + const char *microcode_blob_name, + unsigned int empty_entries, + fit_offset_converter_t offset_fn, uint32_t topswap_size, + uint32_t first_mcu_addr); + #endif diff --git a/util/cbfstool/ifittool.c b/util/cbfstool/ifittool.c new file mode 100644 index 0000000000..a83fd96715 --- /dev/null +++ b/util/cbfstool/ifittool.c @@ -0,0 +1,431 @@ +/* + * cbfstool, CLI utility for creating rmodules + * + * Copyright (C) 2019 9elements Agency GmbH + * Copyright (C) 2019 Facebook Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <getopt.h> + +#include "common.h" +#include "cbfs_image.h" +#include "partitioned_file.h" +#include "fit.h" + +/* Global variables */ +partitioned_file_t *image_file; + +static const char *optstring = "H:j:f:r:d:t:n:s:caDvh?"; +static struct option long_options[] = { + {"file", required_argument, 0, 'f' }, + {"region", required_argument, 0, 'r' }, + {"add-cbfs-entry", no_argument, 0, 'a' }, + {"add-region", no_argument, 0, 'A' }, + {"del-entry", required_argument, 0, 'd' }, + {"clear-table", no_argument, 0, 'c' }, + {"fit-type", required_argument, 0, 't' }, + {"cbfs-filename", required_argument, 0, 'n' }, + {"max-table-size", required_argument, 0, 's' }, + {"topswap-size", required_argument, 0, 'j' }, + {"dump", no_argument, 0, 'D' }, + {"verbose", no_argument, 0, 'v' }, + {"help", no_argument, 0, 'h' }, + {"header-offset", required_argument, 0, 'H' }, + {NULL, 0, 0, 0 } +}; + +static void usage(const char *name) +{ + printf( + "ifittool: utility for modifying Intel Firmware Interface Table\n\n" + "USAGE: %s [-h] [-H] [-v] [-D] [-c] <-f|--file name> <-s|--max-table-size size> <-r|--region fmap region> OPERATION\n" + "\tOPERATION:\n" + "\t\t-a|--add-entry : Add a CBFS file as new entry to FIT\n" + "\t\t-A|--add-region : Add region as new entry to FIT (for microcodes)\n" + "\t\t-d|--del-entry number : Delete existing <number> entry\n" + "\t\t-t|--fit-type : Type of new entry\n" + "\t\t-n|--name : The CBFS filename or region to add to table\n" + "\tOPTIONAL ARGUMENTS:\n" + "\t\t-h|--help : Display this text\n" + "\t\t-H|--header-offset : Do not search for header, use this offset\n" + "\t\t-v|--verbose : Be verbose\n" + "\t\t-D|--dump : Dump FIT table (at end of operation)\n" + "\t\t-c|--clear-table : Remove all existing entries (do not update)\n" + "\t\t-j|--topswap-size : Use second FIT table if non zero\n" + "\tREQUIRED ARGUMENTS:\n" + "\t\t-f|--file name : The file containing the CBFS\n" + "\t\t-s|--max-table-size : The number of possible FIT entries in table\n" + "\t\t-r|--region : The FMAP region to operate on\n" + , name); +} + +static int is_valid_topswap(size_t topswap_size) +{ + switch (topswap_size) { + case (64 * KiB): + case (128 * KiB): + case (256 * KiB): + case (512 * KiB): + case (1 * MiB): + break; + default: + ERROR("Invalid topswap_size %zd\n", topswap_size); + ERROR("topswap can be 64K|128K|256K|512K|1M\n"); + return 0; + } + return 1; +} + +/* + * Converts between offsets from the start of the specified image region and + * "top-aligned" offsets from the top of the entire boot media. See comment + * below for convert_to_from_top_aligned() about forming addresses. + */ +static unsigned int convert_to_from_absolute_top_aligned( + const struct buffer *region, unsigned int offset) +{ + assert(region); + + size_t image_size = partitioned_file_total_size(image_file); + + return image_size - region->offset - offset; +} + +/* + * Converts between offsets from the start of the specified image region and + * "top-aligned" offsets from the top of the image region. Works in either + * direction: pass in one type of offset and receive the other type. + * N.B. A top-aligned offset is always a positive number, and should not be + * confused with a top-aligned *address*, which is its arithmetic inverse. */ +static unsigned int convert_to_from_top_aligned(const struct buffer *region, + unsigned int offset) +{ + assert(region); + + /* Cover the situation where a negative base address is given by the + * user. Callers of this function negate it, so it'll be a positive + * number smaller than the region. + */ + if ((offset > 0) && (offset < region->size)) + return region->size - offset; + + return convert_to_from_absolute_top_aligned(region, offset); +} + +/* + * Get a pointer from an offset. This function assumes the ROM is located + * in the host address space at [4G - romsize -> 4G). It also assume all + * pointers have values within this address range. + */ +static inline uint32_t offset_to_ptr(fit_offset_converter_t helper, + const struct buffer *region, int offset) +{ + return -helper(region, offset); +} + +enum fit_operation { + NO_OP = 0, + ADD_CBFS_OP, + ADD_REGI_OP, + ADD_ADDR_OP, + DEL_OP +}; + +int main(int argc, char *argv[]) +{ + int c; + const char *input_file = NULL; + const char *name = NULL; + const char *region_name = NULL; + enum fit_operation op = NO_OP; + bool dump = false, clear_table = false; + size_t max_table_size = 0; + size_t table_entry = 0; + uint32_t addr = 0; + size_t topswap_size = 0; + enum fit_type fit_type = 0; + uint32_t headeroffset = ~0u; + + verbose = 0; + + if (argc < 4) { + usage(argv[0]); + return 1; + } + + while (1) { + int optindex = 0; + char *suffix = NULL; + + c = getopt_long(argc, argv, optstring, long_options, &optindex); + + if (c == -1) + break; + + switch (c) { + case 'h': + usage(argv[0]); + return 1; + case 'a': + if (op != NO_OP) { + ERROR("specified multiple actions at once\n"); + usage(argv[0]); + return 1; + } + op = ADD_CBFS_OP; + break; + case 'A': + if (op != NO_OP) { + ERROR("specified multiple actions at once\n"); + usage(argv[0]); + return 1; + } + op = ADD_REGI_OP; + break; + case 'x': + if (op != NO_OP) { + ERROR("specified multiple actions at once\n"); + usage(argv[0]); + return 1; + } + op = ADD_ADDR_OP; + addr = atoll(optarg); + break; + case 'c': + clear_table = true; + break; + case 'd': + if (op != NO_OP) { + ERROR("specified multiple actions at once\n"); + usage(argv[0]); + return 1; + } + op = DEL_OP; + table_entry = atoi(optarg); + break; + case 'D': + dump = true; + break; + case 'f': + input_file = optarg; + break; + case 'H': + headeroffset = strtoul(optarg, &suffix, 0); + if (!*optarg || (suffix && *suffix)) { + ERROR("Invalid header offset '%s'.\n", optarg); + return 1; + } + break; + case 'j': + topswap_size = atoi(optarg); + if (!is_valid_topswap(topswap_size)) + return 1; + break; + case 'n': + name = optarg; + break; + case 'r': + region_name = optarg; + break; + case 's': + max_table_size = atoi(optarg); + break; + case 't': + fit_type = atoi(optarg); + break; + case 'v': + verbose++; + break; + default: + break; + } + } + + if (input_file == NULL) { + ERROR("No input file given\n"); + usage(argv[0]); + return 1; + } + + if (op == ADD_CBFS_OP || op == ADD_REGI_OP) { + if (fit_type == 0) { + ERROR("Adding FIT entry, but no type given\n"); + usage(argv[0]); + return 1; + } else if (name == NULL) { + ERROR("Adding FIT entry, but no name set\n"); + usage(argv[0]); + return 1; + } else if (max_table_size == 0) { + ERROR("Maximum table size not given\n"); + usage(argv[0]); + return 1; + } + } + if (op == ADD_ADDR_OP) { + if (fit_type == 0) { + ERROR("Adding FIT entry, but no type given\n"); + usage(argv[0]); + return 1; + } else if (max_table_size == 0) { + ERROR("Maximum table size not given\n"); + usage(argv[0]); + return 1; + } + } + + if (!region_name) { + ERROR("Region not given\n"); + usage(argv[0]); + return 1; + } + + image_file = partitioned_file_reopen(input_file, + op != NO_OP || clear_table); + + struct buffer image_region; + + if (!partitioned_file_read_region(&image_region, image_file, + region_name)) { + partitioned_file_close(image_file); + ERROR("The image will be left unmodified.\n"); + return 1; + } + + struct buffer bootblock; + // The bootblock is part of the CBFS on x86 + buffer_clone(&bootblock, &image_region); + + struct cbfs_image image; + if (cbfs_image_from_buffer(&image, &image_region, headeroffset)) { + partitioned_file_close(image_file); + return 1; + } + + struct fit_table *fit = fit_get_table(&bootblock, + convert_to_from_top_aligned, + topswap_size); + if (!fit) { + partitioned_file_close(image_file); + ERROR("FIT not found.\n"); + return 1; + } + + if (clear_table) { + if (fit_clear_table(fit)) { + partitioned_file_close(image_file); + ERROR("Failed to clear table.\n"); + return 1; + } + } + + switch (op) { + case ADD_REGI_OP: + { + struct buffer region; + addr = 0; + + if (partitioned_file_read_region(®ion, image_file, name)) { + addr = -convert_to_from_top_aligned(®ion, 0); + } else { + partitioned_file_close(image_file); + return 1; + } + + if (fit_add_entry(fit, addr, 0, fit_type, max_table_size)) { + partitioned_file_close(image_file); + ERROR("Adding type %u FIT entry\n", fit_type); + return 1; + } + break; + } + case ADD_CBFS_OP: + { + if (fit_type == FIT_TYPE_MICROCODE) { + if (fit_add_microcode_file(fit, &image, name, + convert_to_from_top_aligned, + max_table_size)) { + return 1; + } + } else { + uint32_t offset, len; + struct cbfs_file *cbfs_file; + + cbfs_file = cbfs_get_entry(&image, name); + if (!cbfs_file) { + partitioned_file_close(image_file); + ERROR("%s not found in CBFS.\n", name); + return 1; + } + + len = ntohl(cbfs_file->len); + offset = offset_to_ptr(convert_to_from_top_aligned, + &image.buffer, + cbfs_get_entry_addr(&image, cbfs_file) + + ntohl(cbfs_file->offset)); + + + if (fit_add_entry(fit, offset, len, fit_type, + max_table_size)) { + partitioned_file_close(image_file); + ERROR("Adding type %u FIT entry\n", fit_type); + return 1; + } + } + break; + } + case ADD_ADDR_OP: + { + if (fit_add_entry(fit, addr, 0, fit_type, max_table_size)) { + partitioned_file_close(image_file); + ERROR("Adding type %u FIT entry\n", fit_type); + return 1; + } + } + break; + case DEL_OP: + { + if (fit_delete_entry(fit, table_entry)) { + partitioned_file_close(image_file); + ERROR("Deleting FIT entry %zu failed\n", table_entry); + return 1; + } + break; + } + case NO_OP: + default: + break; + } + + if (op != NO_OP || clear_table) { + if (!partitioned_file_write_region(image_file, &bootblock)) { + ERROR("Failed to write changes to disk.\n"); + partitioned_file_close(image_file); + return 1; + } + } + + if (dump) { + if (fit_dump(fit)) { + partitioned_file_close(image_file); + return 1; + } + } + + partitioned_file_close(image_file); + + return 0; +} |