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,24 +246,504 @@ static void update_fit_checksum(struct fit_table *fit) fit->header.checksum = -result; } +/* + * 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) +{ + return &fit->entries[fit_table_entries(fit)]; +} + +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); + + 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; +} + +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) +{ + 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_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)) { + 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 ?: 2048; + + /* Quickly sanity check a prospective microcode update. */ + if (total_size < sizeof(*mcu_header)) + break; + + /* FIXME: Should the checksum be validated? */ + mcus[num_mcus].offset = current_offset; + mcus[num_mcus].size = total_size; + + /* Proceed to next payload. */ + current_offset += mcus[num_mcus].size; + file_length -= mcus[num_mcus].size; + num_mcus++; + /* Reached limit of FIT entries. */ + if (num_mcus == max_fit_entries) + break; + if (file_length < sizeof(struct microcode_header)) + break; + } + + /* Update how many microcode updates we found. */ + *mcus_found = num_mcus; + + 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, uint64_t mcu_addr) + 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 + * rather from the MCU header, hence we can assign zero here. */ - entry->size_reserved = 0x0000; + 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, - int num_mcus, struct microcode_entry *mcus, + ssize_t num_mcus, + struct microcode_entry *mcus, fit_offset_converter_t offset_helper, uint32_t first_mcu_addr) { @@ -214,113 +768,32 @@ static void add_microcodde_entries(struct fit_table *fit, } } -static void cbfs_file_get_header(struct buffer *buf, struct cbfs_file *file) -{ - 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); -} - -static int fit_header(void *ptr, uint32_t *current_offset, uint32_t *file_length) -{ - struct buffer buf; - struct cbfs_file header; - buf.data = ptr; - buf.size = sizeof(header); - cbfs_file_get_header(&buf, &header); - *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) -{ - int num_mcus; - uint32_t current_offset; - uint32_t file_length; - - fit_header(mcode_file, ¤t_offset, &file_length); - current_offset += (int)((char *)mcode_file - image->buffer.data); - - num_mcus = 0; - while (file_length > sizeof(struct microcode_header)) - { - const struct microcode_header *mcu_header; - - mcu_header = rom_buffer_pointer(&image->buffer, current_offset); - - /* 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; - - /* Quickly sanity check a prospective microcode update. */ - if (total_size < sizeof(*mcu_header)) - break; - - /* FIXME: Should the checksum be validated? */ - mcus[num_mcus].offset = current_offset; - mcus[num_mcus].size = total_size; - - /* Proceed to next payload. */ - current_offset += mcus[num_mcus].size; - file_length -= mcus[num_mcus].size; - num_mcus++; - - /* Reached limit of FIT entries. */ - if (num_mcus == total_entries) - break; - if (file_length < sizeof(struct microcode_header)) - break; - } - - /* Update how many microcode updates we found. */ - *mcus_found = num_mcus; - - return 0; -} - 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 +#include +#include +#include +#include + +#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 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; +}