/* Firmware Interface Table support */ /* SPDX-License-Identifier: GPL-2.0-only */ #include <inttypes.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "fit.h" /* FIXME: This code assumes it is being executed on a little endian machine. */ #define FIT_POINTER_LOCATION 0xffffffc0 #define FIT_TABLE_LOWEST_ADDRESS ((uint32_t)(-(16 << 20))) #define FIT_ENTRY_CHECKSUM_VALID 0x80 #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; struct fit_table { struct fit_entry header; struct fit_entry entries[]; } __packed; struct microcode_header { uint32_t version; uint32_t revision; uint32_t date; uint32_t processor_signature; uint32_t checksum; uint32_t loader_revision; uint32_t processor_flags; uint32_t data_size; uint32_t total_size; uint8_t reserved[12]; } __packed; struct microcode_entry { int offset; int size; }; static inline void *rom_buffer_pointer(struct buffer *buffer, int offset) { return &buffer->data[offset]; } 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, 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, const int size_bytes) { int size = fit_entry_size_bytes(entry); size += size_bytes; fit_entry_update_size(entry, size); } static inline int fit_entry_type(struct fit_entry *entry) { return entry->type_checksum_valid & ~FIT_ENTRY_CHECKSUM_VALID; } /* * Get an offset from a host pointer. 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 int ptr_to_offset(fit_offset_converter_t helper, const struct buffer *region, uint32_t host_ptr) { return helper(region, -host_ptr); } /* * 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); } /* * 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 algorithm, 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))) return 0; if (table->header.version != FIT_HEADER_VERSION) return 0; if (fit_entry_type(&table->header) != FIT_TYPE_HEADER) return 0; /* 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; } /* * Update the FIT checksum. * To be called after modifiying the table. */ static void update_fit_checksum(struct fit_table *fit) { int size_bytes; uint8_t *buffer; uint8_t result; int i; if (!fit) return; fit->header.checksum = 0; size_bytes = fit_entry_size_bytes(&fit->header); result = 0; buffer = (void *)fit; for (i = 0; i < size_bytes; i++) result += buffer[i]; 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) { ERROR("Couldn't find microcode blob.\n"); 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) || total_size > file_length) break; if (num_mcus == max_fit_entries) { ERROR("Maximum of FIT entries reached.\n"); return 1; } /* 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++; 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, 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)); } /* * There can be zero or one FIT_TYPE_BOOT_POLICY entries * * The caller has to provide valid arguments as those aren't verified. */ static void update_fit_boot_policy_entry(struct fit_table *fit, struct fit_entry *entry, uint64_t boot_policy_addr, uint32_t boot_policy_size) { entry->address = boot_policy_addr; entry->type_checksum_valid = FIT_TYPE_BOOT_POLICY; entry->size_reserved = boot_policy_size; 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_KEY_MANIFEST entries * * The caller has to provide valid arguments as those aren't verified. */ static void update_fit_key_manifest_entry(struct fit_table *fit, struct fit_entry *entry, uint64_t key_manifest_addr, uint32_t key_manifest_size) { entry->address = key_manifest_addr; entry->type_checksum_valid = FIT_TYPE_KEY_MANIFEST; entry->size_reserved = key_manifest_size; entry->version = FIT_TXT_VERSION; 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)) { 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; } static uint32_t *get_fit_ptr(struct buffer *bootblock, fit_offset_converter_t offset_fn, uint32_t topswap_size) { return rom_buffer_pointer(bootblock, ptr_to_offset(offset_fn, bootblock, FIT_POINTER_LOCATION - topswap_size)); } /* Set the FIT pointer to a FIT table. */ int set_fit_pointer(struct buffer *bootblock, const uint32_t fit_address, fit_offset_converter_t offset_fn, uint32_t topswap_size) { struct fit_table *fit; uint32_t *fit_pointer = get_fit_ptr(bootblock, offset_fn, topswap_size); fit = rom_buffer_pointer(bootblock, ptr_to_offset(offset_fn, bootblock, fit_address)); if (fit_address < FIT_TABLE_LOWEST_ADDRESS) { ERROR("FIT must be reside in the top 16MiB.\n"); return 1; } if (!fit_table_verified(fit)) { ERROR("FIT not found at address.\n"); return 1; } fit_pointer[0] = fit_address; fit_pointer[1] = 0; 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 = get_fit_ptr(bootblock, offset_fn, topswap_size); /* 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; } 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: case FIT_TYPE_KEY_MANIFEST: case FIT_TYPE_BOOT_POLICY: return 1; case FIT_TYPE_TPM_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; case FIT_TYPE_KEY_MANIFEST: update_fit_key_manifest_entry(fit, entry, offset, len); break; case FIT_TYPE_BOOT_POLICY: update_fit_boot_policy_entry(fit, entry, offset, len); 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; }