/***************************************************************************** * Copyright (c) 2015-2020 IBM Corporation * All rights reserved. * This program and the accompanying materials * are made available under the terms of the BSD License * which accompanies this distribution, and is available at * http://www.opensource.org/licenses/bsd-license.php * * Contributors: * IBM Corporation - initial implementation * Stefan Berger, stefanb@linux.ibm.com * Kevin O'Connor, kevin@koconnor.net *****************************************************************************/ /* * Implementation of the TPM BIOS extension according to the specification * described in the IBM VTPM Firmware document and the TCG Specification * that can be found here under the following link: * https://trustedcomputinggroup.org/resource/pc-client-work-group-specific-implementation-specification-for-conventional-bios/ */ #include #include #include "types.h" #include "byteorder.h" #include "tpm_drivers.h" #include "string.h" #include "tcgbios.h" #include "tcgbios_int.h" #include "stdio.h" #include "sha256.h" #include "helpers.h" #include "version.h" #include "OF.h" #include "libelf.h" #undef TCGBIOS_DEBUG //#define TCGBIOS_DEBUG #ifdef TCGBIOS_DEBUG #define dprintf(_x ...) do { printf("TCGBIOS: " _x); } while(0) #else #define dprintf(_x ...) #endif static struct { unsigned tpm_probed:1; unsigned tpm_found:1; unsigned tpm_working:1; /* base address of the log area */ uint8_t *log_base; /* size of the logging area */ size_t log_area_size; /* where to write the next log entry to */ uint8_t *log_area_next_entry; /* PCR selection as received from TPM */ uint32_t tpm20_pcr_selection_size; struct tpml_pcr_selection *tpm20_pcr_selection; } tpm_state; #define TPM2_ALG_SHA1_FLAG (1 << 0) #define TPM2_ALG_SHA256_FLAG (1 << 1) #define TPM2_ALG_SHA384_FLAG (1 << 2) #define TPM2_ALG_SHA512_FLAG (1 << 3) #define TPM2_ALG_SM3_256_FLAG (1 << 4) #define TPM2_ALG_SHA3_256_FLAG (1 << 5) #define TPM2_ALG_SHA3_384_FLAG (1 << 6) #define TPM2_ALG_SHA3_512_FLAG (1 << 7) static const uint8_t ZeroGuid[16] = { 0 }; static UEFI_GPT_DATA *uefi_gpt_data; static size_t uefi_gpt_data_size; /* * TPM 2 logs are written in little endian format. */ static inline uint32_t log32_to_cpu(uint32_t val) { return le32_to_cpu(val); } static inline uint32_t cpu_to_log32(uint32_t val) { return cpu_to_le32(val); } static inline uint16_t cpu_to_log16(uint16_t val) { return cpu_to_le16(val); } /******************************************************** Extensions for TCG-enabled BIOS *******************************************************/ static void probe_tpm(void) { tpm_state.tpm_probed = true; tpm_state.tpm_found = spapr_is_vtpm_present(); tpm_state.tpm_working = tpm_state.tpm_found; } /**************************************************************** * Digest formatting ****************************************************************/ /* A 'struct tpm_log_entry' is a local data structure containing a * 'TCG_PCR_EVENT2_Header' followed by space for the maximum supported * digest. The digest is a series of TPMT_HA structs on tpm2.0. */ struct tpm_log_entry { TCG_PCR_EVENT2_Header hdr; uint8_t pad[sizeof(struct TPML_DIGEST_VALUES) + 8 * sizeof(struct TPMT_HA) + SHA1_BUFSIZE + SHA256_BUFSIZE + SHA384_BUFSIZE + SHA512_BUFSIZE + SM3_256_BUFSIZE + SHA3_256_BUFSIZE + SHA3_384_BUFSIZE + SHA3_512_BUFSIZE]; } __attribute__((packed)); static const struct hash_parameters { uint16_t hashalg; uint8_t hashalg_flag; uint8_t hash_buffersize; const char *name; } hash_parameters[] = { { .hashalg = TPM2_ALG_SHA1, .hashalg_flag = TPM2_ALG_SHA1_FLAG, .hash_buffersize = SHA1_BUFSIZE, .name = "SHA1", }, { .hashalg = TPM2_ALG_SHA256, .hashalg_flag = TPM2_ALG_SHA256_FLAG, .hash_buffersize = SHA256_BUFSIZE, .name = "SHA256", }, { .hashalg = TPM2_ALG_SHA384, .hashalg_flag = TPM2_ALG_SHA384_FLAG, .hash_buffersize = SHA384_BUFSIZE, .name = "SHA384", }, { .hashalg = TPM2_ALG_SHA512, .hashalg_flag = TPM2_ALG_SHA512_FLAG, .hash_buffersize = SHA512_BUFSIZE, .name = "SHA512", }, { .hashalg = TPM2_ALG_SM3_256, .hashalg_flag = TPM2_ALG_SM3_256_FLAG, .hash_buffersize = SM3_256_BUFSIZE, .name = "SM3-256", }, { .hashalg = TPM2_ALG_SHA3_256, .hashalg_flag = TPM2_ALG_SHA3_256_FLAG, .hash_buffersize = SHA3_256_BUFSIZE, .name = "SHA3-256", }, { .hashalg = TPM2_ALG_SHA3_384, .hashalg_flag = TPM2_ALG_SHA3_384_FLAG, .hash_buffersize = SHA3_384_BUFSIZE, .name = "SHA3-384", }, { .hashalg = TPM2_ALG_SHA3_512, .hashalg_flag = TPM2_ALG_SHA3_512_FLAG, .hash_buffersize = SHA3_512_BUFSIZE, .name = "SHA3-512", } }; static const struct hash_parameters *tpm20_find_by_hashalg(uint16_t hashAlg) { unsigned i; for (i = 0; i < ARRAY_SIZE(hash_parameters); i++) { if (hash_parameters[i].hashalg == hashAlg) return &hash_parameters[i]; } return NULL; } static const struct hash_parameters * tpm20_find_by_hashalg_flag(uint16_t hashalg_flag) { unsigned i; for (i = 0; i < ARRAY_SIZE(hash_parameters); i++) { if (hash_parameters[i].hashalg_flag == hashalg_flag) return &hash_parameters[i]; } return NULL; } static inline int tpm20_get_hash_buffersize(uint16_t hashAlg) { const struct hash_parameters *hp = tpm20_find_by_hashalg(hashAlg); if (hp) return hp->hash_buffersize; return -1; } static inline uint8_t tpm20_hashalg_to_flag(uint16_t hashAlg) { const struct hash_parameters *hp = tpm20_find_by_hashalg(hashAlg); if (hp) return hp->hashalg_flag; return 0; } static uint16_t tpm20_hashalg_flag_to_hashalg(uint8_t hashalg_flag) { const struct hash_parameters *hp; hp = tpm20_find_by_hashalg_flag(hashalg_flag); if (hp) return hp->hashalg; return 0; } static const char * tpm20_hashalg_flag_to_name(uint8_t hashalg_flag) { const struct hash_parameters *hp; hp = tpm20_find_by_hashalg_flag(hashalg_flag); if (hp) return hp->name; return NULL; } /* * Build the TPM2 TPML_DIGEST_VALUES data structure from the given hash. * Follow the PCR bank configuration of the TPM and write the same hash * in either truncated or zero-padded form in the areas of all the other * hashes. For example, write the sha256 hash in the area of the sha384 * hash and fill the remaining bytes with zeros. Or truncate the sha256 * hash when writing it in the area of the sha1 hash. * * le: the log entry to build the digest in * sha1: the sha1 hash value to use * bigEndian: whether to build in big endian format for the TPM or log * little endian for the log (TPM 2.0) * * Returns the digest size; -1 on fatal error */ static int tpm20_build_digest(struct tpm_log_entry *le, const uint8_t *sha256, bool bigEndian) { struct tpms_pcr_selection *sel; void *nsel, *end; void *dest = le->hdr.digests + sizeof(struct TPML_DIGEST_VALUES); uint32_t count, numAlgs; struct TPMT_HA *v; struct TPML_DIGEST_VALUES *vs; sel = tpm_state.tpm20_pcr_selection->selections; end = (void *)tpm_state.tpm20_pcr_selection + tpm_state.tpm20_pcr_selection_size; for (count = 0, numAlgs = 0; count < be32_to_cpu(tpm_state.tpm20_pcr_selection->count); count++) { int hsize; uint8_t sizeOfSelect = sel->sizeOfSelect; nsel = (void*)sel + sizeof(*sel) + sizeOfSelect; if (nsel > end) break; /* PCR 0-7 unused ? -- skip */ if (!sizeOfSelect || sel->pcrSelect[0] == 0) { sel = nsel; continue; } hsize = tpm20_get_hash_buffersize(be16_to_cpu(sel->hashAlg)); if (hsize < 0) { dprintf("TPM is using an unsupported hash: %d\n", be16_to_cpu(sel->hashAlg)); return -1; } /* buffer size sanity check before writing */ v = dest; if (dest + sizeof(*v) + hsize > (void*)le + sizeof(*le)) { dprintf("tpm_log_entry is too small\n"); return -1; } if (bigEndian) v->hashAlg = sel->hashAlg; else v->hashAlg = cpu_to_le16(be16_to_cpu(sel->hashAlg)); memset(v->hash, 0, hsize); memcpy(v->hash, sha256, hsize < SHA256_BUFSIZE ? hsize : SHA256_BUFSIZE); dest += sizeof(*v) + hsize; sel = nsel; numAlgs++; } if (sel != end) { dprintf("Malformed pcr selection structure fron TPM\n"); return -1; } vs = (void*)le->hdr.digests; if (bigEndian) vs->count = cpu_to_be32(numAlgs); else vs->count = cpu_to_le32(numAlgs); return dest - (void*)le->hdr.digests; } /**************************************************************** * TPM hardware command wrappers ****************************************************************/ /* Helper function for sending TPM commands that take a single * optional parameter (0, 1, or 2 bytes) and have no special response. */ static int tpm_simple_cmd(uint8_t locty, uint32_t ordinal, int param_size, uint16_t param, enum tpm_duration_type to_t) { struct { struct tpm_req_header trqh; uint16_t param; } __attribute__((packed)) req = { .trqh.totlen = cpu_to_be32(sizeof(req.trqh) + param_size), .trqh.tag = cpu_to_be16(TPM2_ST_NO_SESSIONS), .trqh.ordinal = cpu_to_be32(ordinal), }; uint8_t obuffer[64]; struct tpm_rsp_header *trsh = (void *)obuffer; uint32_t obuffer_len = sizeof(obuffer); int ret; switch (param_size) { case 2: req.param = cpu_to_be16(param); break; case 1: *(uint8_t *)&req.param = param; break; } memset(obuffer, 0, sizeof(obuffer)); ret = spapr_transmit(locty, &req.trqh, obuffer, &obuffer_len, to_t); ret = ret ? -1 : (int) be32_to_cpu(trsh->errcode); dprintf("Return from tpm_simple_cmd(%x, %x) = %x\n", ordinal, param, ret); return ret; } static int tpm20_getcapability(uint32_t capability, uint32_t property, uint32_t count, struct tpm_rsp_header *rsp, uint32_t rsize) { struct tpm2_req_getcapability trg = { .hdr.tag = cpu_to_be16(TPM2_ST_NO_SESSIONS), .hdr.totlen = cpu_to_be32(sizeof(trg)), .hdr.ordinal = cpu_to_be32(TPM2_CC_GetCapability), .capability = cpu_to_be32(capability), .property = cpu_to_be32(property), .propertycount = cpu_to_be32(count), }; uint32_t resp_size = rsize; int ret; ret = spapr_transmit(0, &trg.hdr, rsp, &resp_size, TPM_DURATION_TYPE_SHORT); ret = (ret || rsize < be32_to_cpu(rsp->totlen)) ? -1 : (int) be32_to_cpu(rsp->errcode); dprintf("TCGBIOS: Return value from sending TPM2_CC_GetCapability = 0x%08x\n", ret); return ret; } static int tpm20_get_pcrbanks(void) { uint8_t buffer[128]; uint32_t size; struct tpm2_res_getcapability *trg = (struct tpm2_res_getcapability *)&buffer; uint32_t resplen; int ret; ret = tpm20_getcapability(TPM2_CAP_PCRS, 0, 8, &trg->hdr, sizeof(buffer)); if (ret) return ret; /* defend against (broken) TPM sending packets that are too short */ resplen = be32_to_cpu(trg->hdr.totlen); if (resplen <= offset_of(struct tpm2_res_getcapability, data)) return -1; size = resplen - offset_of(struct tpm2_res_getcapability, data); /* we need a valid tpml_pcr_selection up to and including sizeOfSelect*/ if (size < offset_of(struct tpml_pcr_selection, selections) + offset_of(struct tpms_pcr_selection, pcrSelect)) return -1; tpm_state.tpm20_pcr_selection = SLOF_alloc_mem(size); if (tpm_state.tpm20_pcr_selection) { memcpy(tpm_state.tpm20_pcr_selection, &trg->data, size); tpm_state.tpm20_pcr_selection_size = size; } else { printf("TCGBIOS: Failed to allocated %u bytes.\n", size); return -1; } return 0; } static int tpm20_extend(struct tpm_log_entry *le, int digest_len) { struct tpm2_req_extend tmp_tre = { .hdr.tag = cpu_to_be16(TPM2_ST_SESSIONS), .hdr.totlen = cpu_to_be32(0), .hdr.ordinal = cpu_to_be32(TPM2_CC_PCR_Extend), .pcrindex = cpu_to_be32(log32_to_cpu(le->hdr.pcrindex)), .authblocksize = cpu_to_be32(sizeof(tmp_tre.authblock)), .authblock = { .handle = cpu_to_be32(TPM2_RS_PW), .noncesize = cpu_to_be16(0), .contsession = TPM2_YES, .pwdsize = cpu_to_be16(0), }, }; uint8_t buffer[sizeof(tmp_tre) + sizeof(le->pad)]; struct tpm2_req_extend *tre = (struct tpm2_req_extend *)buffer; struct tpm_rsp_header rsp; uint32_t resp_length = sizeof(rsp); int ret; memcpy(tre, &tmp_tre, sizeof(tmp_tre)); memcpy(&tre->digest[0], le->hdr.digests, digest_len); tre->hdr.totlen = cpu_to_be32(sizeof(tmp_tre) + digest_len); ret = spapr_transmit(0, &tre->hdr, &rsp, &resp_length, TPM_DURATION_TYPE_SHORT); if (ret || resp_length != sizeof(rsp) || rsp.errcode) return -1; return 0; } static int tpm20_stirrandom(void) { struct tpm2_req_stirrandom stir = { .hdr.tag = cpu_to_be16(TPM2_ST_NO_SESSIONS), .hdr.totlen = cpu_to_be32(sizeof(stir)), .hdr.ordinal = cpu_to_be32(TPM2_CC_StirRandom), .size = cpu_to_be16(sizeof(stir.stir)), .stir = rand(), }; struct tpm_rsp_header rsp; uint32_t resp_length = sizeof(rsp); int ret = spapr_transmit(0, &stir.hdr, &rsp, &resp_length, TPM_DURATION_TYPE_SHORT); if (ret || resp_length != sizeof(rsp) || rsp.errcode) ret = -1; dprintf("TCGBIOS: Return value from sending TPM2_CC_StirRandom = 0x%08x\n", ret); return ret; } static int tpm20_getrandom(uint8_t *buf, uint16_t buf_len) { struct tpm2_res_getrandom rsp; struct tpm2_req_getrandom trgr = { .hdr.tag = cpu_to_be16(TPM2_ST_NO_SESSIONS), .hdr.totlen = cpu_to_be32(sizeof(trgr)), .hdr.ordinal = cpu_to_be32(TPM2_CC_GetRandom), .bytesRequested = cpu_to_be16(buf_len), }; uint32_t resp_length = sizeof(rsp); int ret; if (buf_len > sizeof(rsp.rnd.buffer)) return -1; ret = spapr_transmit(0, &trgr.hdr, &rsp, &resp_length, TPM_DURATION_TYPE_MEDIUM); if (ret || resp_length != sizeof(rsp) || rsp.hdr.errcode) ret = -1; else memcpy(buf, rsp.rnd.buffer, buf_len); dprintf("TCGBIOS: Return value from sending TPM2_CC_GetRandom = 0x%08x\n", ret); return ret; } static int tpm20_hierarchychangeauth(uint8_t auth[20]) { struct tpm2_req_hierarchychangeauth trhca = { .hdr.tag = cpu_to_be16(TPM2_ST_SESSIONS), .hdr.totlen = cpu_to_be32(sizeof(trhca)), .hdr.ordinal = cpu_to_be32(TPM2_CC_HierarchyChangeAuth), .authhandle = cpu_to_be32(TPM2_RH_PLATFORM), .authblocksize = cpu_to_be32(sizeof(trhca.authblock)), .authblock = { .handle = cpu_to_be32(TPM2_RS_PW), .noncesize = cpu_to_be16(0), .contsession = TPM2_YES, .pwdsize = cpu_to_be16(0), }, .newAuth = { .size = cpu_to_be16(sizeof(trhca.newAuth.buffer)), }, }; struct tpm_rsp_header rsp; uint32_t resp_length = sizeof(rsp); int ret; memcpy(trhca.newAuth.buffer, auth, sizeof(trhca.newAuth.buffer)); ret = spapr_transmit(0, &trhca.hdr, &rsp, &resp_length, TPM_DURATION_TYPE_MEDIUM); if (ret || resp_length != sizeof(rsp) || rsp.errcode) ret = -1; dprintf("TCGBIOS: Return value from sending TPM2_CC_HierarchyChangeAuth = 0x%08x\n", ret); return ret; } static int tpm20_hierarchycontrol(uint32_t hierarchy, uint8_t state) { struct tpm2_req_hierarchycontrol trh = { .hdr.tag = cpu_to_be16(TPM2_ST_SESSIONS), .hdr.totlen = cpu_to_be32(sizeof(trh)), .hdr.ordinal = cpu_to_be32(TPM2_CC_HierarchyControl), .authhandle = cpu_to_be32(TPM2_RH_PLATFORM), .authblocksize = cpu_to_be32(sizeof(trh.authblock)), .authblock = { .handle = cpu_to_be32(TPM2_RS_PW), .noncesize = cpu_to_be16(0), .contsession = TPM2_YES, .pwdsize = cpu_to_be16(0), }, .enable = cpu_to_be32(hierarchy), .state = state, }; struct tpm_rsp_header rsp; uint32_t resp_length = sizeof(rsp); int ret; ret = spapr_transmit(0, &trh.hdr, &rsp, &resp_length, TPM_DURATION_TYPE_MEDIUM); if (ret || resp_length != sizeof(rsp) || rsp.errcode) ret = -1; dprintf("TCGBIOS: Return value from sending TPM2_CC_HierarchyControl = 0x%08x\n", ret); return ret; } /**************************************************************** * Setup and Measurements ****************************************************************/ bool tpm_is_working(void) { if (!tpm_state.tpm_probed) probe_tpm(); return tpm_state.tpm_working; } static void tpm_set_failure(void) { tpm20_hierarchycontrol(TPM2_RH_ENDORSEMENT, TPM2_NO); tpm20_hierarchycontrol(TPM2_RH_OWNER, TPM2_NO); tpm_state.tpm_working = false; } /* * Extend the OFDT log with the given entry by copying the * entry data into the log. * * @pcpes: Pointer to the structure to be copied into the log * @event: The event to be appended to 'pcpes' * @event_length: The length of the event * * Returns 0 on success, an error code otherwise. */ static uint32_t tpm_log_event_long(TCG_PCR_EVENT2_Header *entry, int digest_len, const void *event, uint32_t event_length) { size_t size, logsize; void *dest; TCG_PCR_EVENT2_Trailer *t; dprintf("log base address = %p, next entry = %p\n", tpm_state.log_base, tpm_state.log_area_next_entry); if (tpm_state.log_area_next_entry == NULL) return TCGBIOS_LOGOVERFLOW; size = sizeof(*entry) + digest_len + sizeof(TCG_PCR_EVENT2_Trailer) + event_length; logsize = (tpm_state.log_area_next_entry + size - tpm_state.log_base); if (logsize > tpm_state.log_area_size) { dprintf("TCGBIOS: LOG OVERFLOW: size = %zu\n", size); return TCGBIOS_LOGOVERFLOW; } dest = tpm_state.log_area_next_entry; memcpy(dest, entry, sizeof(*entry) + digest_len); t = dest + sizeof(*entry) + digest_len; t->eventdatasize = cpu_to_log32(event_length); if (event_length) memcpy(t->event, event, event_length); tpm_state.log_area_next_entry += size; return 0; } /* Add an entry at the start of the log describing digest formats */ static int tpm20_write_EfiSpecIdEventStruct(void) { struct { struct TCG_EfiSpecIdEventStruct hdr; uint32_t pad[sizeof(struct tpm_log_entry) + sizeof(uint8_t)]; } event = { .hdr.signature = "Spec ID Event03", .hdr.platformClass = TPM_TCPA_ACPI_CLASS_CLIENT, .hdr.specVersionMinor = 0, .hdr.specVersionMajor = 2, .hdr.specErrata = 0, .hdr.uintnSize = 2, }; struct tpms_pcr_selection *sel; void *nsel, *end; unsigned event_size; uint8_t *vendorInfoSize; struct tpm_log_entry le = { .hdr.eventtype = cpu_to_log32(EV_NO_ACTION), }; uint32_t count, numAlgs; sel = tpm_state.tpm20_pcr_selection->selections; end = (void*)tpm_state.tpm20_pcr_selection + tpm_state.tpm20_pcr_selection_size; for (count = 0, numAlgs = 0; count < be32_to_cpu(tpm_state.tpm20_pcr_selection->count); count++) { int hsize; uint8_t sizeOfSelect = sel->sizeOfSelect; nsel = (void*)sel + sizeof(*sel) + sizeOfSelect; if (nsel > end) break; /* PCR 0-7 unused ? -- skip */ if (!sizeOfSelect || sel->pcrSelect[0] == 0) { sel = nsel; continue; } hsize = tpm20_get_hash_buffersize(be16_to_cpu(sel->hashAlg)); if (hsize < 0) { dprintf("TPM is using an unsupported hash: %d\n", be16_to_cpu(sel->hashAlg)); return -1; } event_size = offset_of(struct TCG_EfiSpecIdEventStruct, digestSizes[count+1]); if (event_size > sizeof(event) - sizeof(uint8_t)) { dprintf("EfiSpecIdEventStruct pad too small\n"); return -1; } event.hdr.digestSizes[numAlgs].algorithmId = cpu_to_log16(be16_to_cpu(sel->hashAlg)); event.hdr.digestSizes[numAlgs].digestSize = cpu_to_log16(hsize); numAlgs++; sel = nsel; } if (sel != end) { dprintf("Malformed pcr selection structure fron TPM\n"); return -1; } event.hdr.numberOfAlgorithms = cpu_to_log32(numAlgs); event_size = offset_of(struct TCG_EfiSpecIdEventStruct, digestSizes[numAlgs]); vendorInfoSize = (void*)&event + event_size; *vendorInfoSize = 0; event_size += sizeof(*vendorInfoSize); return tpm_log_event_long(&le.hdr, SHA1_BUFSIZE, &event, event_size); } static int tpm20_startup(void) { int ret; ret = tpm_simple_cmd(0, TPM2_CC_Startup, 2, TPM2_SU_CLEAR, TPM_DURATION_TYPE_SHORT); dprintf("TCGBIOS: Return value from sending TPM2_CC_Startup(SU_CLEAR) = 0x%08x\n", ret); if (ret) goto err_exit; ret = tpm_simple_cmd(0, TPM2_CC_SelfTest, 1, TPM2_YES, TPM_DURATION_TYPE_LONG); dprintf("TCGBIOS: Return value from sending TPM2_CC_SELF_TEST = 0x%08x\n", ret); if (ret) goto err_exit; ret = tpm20_get_pcrbanks(); if (ret) goto err_exit; /* the log parameters will be passed from Forth layer */ return 0; err_exit: dprintf("TCGBIOS: TPM malfunctioning (line %d).\n", __LINE__); tpm_set_failure(); return -1; } uint32_t tpm_start(void) { probe_tpm(); if (!tpm_is_working()) { dprintf("%s: Machine does not have a working TPM\n", __func__); return TCGBIOS_FATAL_COM_ERROR; } return tpm20_startup(); } void tpm_finalize(void) { spapr_vtpm_finalize(); } static void tpm20_prepboot(void) { uint8_t auth[20]; int ret; ret = tpm20_stirrandom(); if (ret) goto err_exit; ret = tpm20_getrandom(&auth[0], sizeof(auth)); if (ret) goto err_exit; ret = tpm20_hierarchychangeauth(auth); if (ret) goto err_exit; return; err_exit: dprintf("TCGBIOS: TPM malfunctioning (line %d).\n", __LINE__); tpm_set_failure(); } /* * Prepare TPM for boot; this function has to be called before * the firmware transitions to the boot loader. */ uint32_t tpm_leave_firmware(void) { tpm20_prepboot(); return 0; } /**************************************************************** * Forth interface ****************************************************************/ void tpm_set_log_parameters(void *addr, size_t size) { int ret; dprintf("Log is at 0x%llx; size is %zu bytes\n", (uint64_t)addr, size); tpm_state.log_base = addr; tpm_state.log_area_next_entry = addr; tpm_state.log_area_size = size; ret = tpm20_write_EfiSpecIdEventStruct(); if (ret) tpm_set_failure(); } uint32_t tpm_get_logsize(void) { uint32_t logsize = tpm_state.log_area_next_entry - tpm_state.log_base; dprintf("log size: %u\n", logsize); return logsize; } /* * Add a measurement to the log; * * Input parameters: * @pcrindex : PCR to extend * @event_type : type of event * @info : pointer to info (i.e., string) to be added to the log as-is * @info_length: length of the info * @hashdata : pointer to data to be hashed * @hashdata_length: length of the data * */ static uint32_t tpm_add_measurement_to_log(uint32_t pcrindex, uint32_t eventtype, const char *info, uint32_t infolen, const uint8_t *hashdata, uint32_t hashdatalen) { uint8_t hash[SHA256_BUFSIZE]; struct tpm_log_entry le = { .hdr.pcrindex = cpu_to_log32(pcrindex), .hdr.eventtype = cpu_to_log32(eventtype), }; int digest_len; int ret; sha256(hashdata, hashdatalen, hash); digest_len = tpm20_build_digest(&le, hash, true); if (digest_len < 0) return TCGBIOS_GENERAL_ERROR; ret = tpm20_extend(&le, digest_len); if (ret) { tpm_set_failure(); return TCGBIOS_COMMAND_ERROR; } tpm20_build_digest(&le, hash, false); return tpm_log_event_long(&le.hdr, digest_len, info, infolen); } /* * Measure the contents of a buffer into the given PCR and log it with the * given eventtype. If is_elf is true, try to determine the size of the * ELF file in the buffer and use its size rather than the much larger data * buffer it is held in. In case of failure to detect the ELF file size, * log an error. * * Input parameters: * @pcrindex : PCR to extend * @eventtype : type of event * @data: the buffer to measure * @datalen: length of the buffer * @desc: The description to log * @desclen: The length of the description * @is_elf: Whether data buffer holds an ELF file and we should determine * the original file size. * * Returns 0 on success, an error code otherwise. */ uint32_t tpm_hash_log_extend_event_buffer(uint32_t pcrindex, uint32_t eventtype, const void *data, uint64_t datalen, const char *desc, uint32_t desclen, bool is_elf) { long len; char buf[256]; if (is_elf) { len = elf_get_file_size(data, datalen); if (len > 0) { datalen = len; } else { snprintf(buf, sizeof(buf), "BAD ELF FILE: %s", desc); return tpm_add_measurement_to_log(pcrindex, eventtype, buf, strlen(buf), (uint8_t *)buf, strlen(buf)); } } return tpm_add_measurement_to_log(pcrindex, eventtype, desc, desclen, data, datalen); } /* * Add an EV_ACTION measurement to the list of measurements */ static uint32_t tpm_add_action(uint32_t pcrIndex, const char *string) { uint32_t len = strlen(string); return tpm_add_measurement_to_log(pcrIndex, EV_ACTION, string, len, (uint8_t *)string, len); } /* * Add event separators for a range of PCRs */ uint32_t tpm_add_event_separators(uint32_t start_pcr, uint32_t end_pcr) { static const uint8_t evt_separator[] = {0xff,0xff,0xff,0xff}; uint32_t pcrIndex; int rc; if (!tpm_is_working()) return TCGBIOS_GENERAL_ERROR; if (start_pcr >= 24 || start_pcr > end_pcr) return TCGBIOS_INVALID_INPUT_PARA; /* event separators need to be extended and logged for PCRs 0-7 */ for (pcrIndex = start_pcr; pcrIndex <= end_pcr; pcrIndex++) { rc = tpm_add_measurement_to_log(pcrIndex, EV_SEPARATOR, NULL, 0, evt_separator, sizeof(evt_separator)); if (rc) return rc; } return 0; } uint32_t tpm_measure_bcv_mbr(uint32_t bootdrv, const uint8_t *addr, uint32_t length) { uint32_t rc; const char *string; if (!tpm_is_working()) return TCGBIOS_GENERAL_ERROR; if (length < 0x200) return TCGBIOS_INVALID_INPUT_PARA; string = "Booting BCV device 00h (Floppy)"; if (bootdrv == BCV_DEVICE_HDD) string = "Booting BCV device 80h (HDD)"; rc = tpm_add_action(4, string); if (rc) return rc; /* * equivalent to: dd if=/dev/hda ibs=1 count=440 | sha256sum */ string = "MBR"; rc = tpm_add_measurement_to_log(4, EV_IPL, string, strlen(string), addr, 0x1b8); if (rc) return rc; /* * equivalent to: dd if=/dev/hda ibs=1 count=72 skip=440 | sha256sum */ string = "MBR PARTITION TABLE"; return tpm_add_measurement_to_log(5, EV_IPL_PARTITION_DATA, string, strlen(string), addr + 0x1b8, 0x48); } /* * This is the first function to call when measuring a GPT table. * It allocates memory for the data to log which are 'measured' later on. */ void tpm_gpt_set_lba1(const uint8_t *addr, uint32_t length) { if (!tpm_is_working()) return; SLOF_free_mem(uefi_gpt_data, uefi_gpt_data_size); uefi_gpt_data_size = sizeof(UEFI_GPT_DATA); uefi_gpt_data = SLOF_alloc_mem(uefi_gpt_data_size); if (!uefi_gpt_data) return; memcpy(&uefi_gpt_data->EfiPartitionHeader, addr, MIN(sizeof(uefi_gpt_data->EfiPartitionHeader), length)); uefi_gpt_data->NumberOfPartitions = 0; } /* * This function adds a GPT entry to the data to measure. It must * be called after tpm_gpt_set_lba1. */ void tpm_gpt_add_entry(const uint8_t *addr, uint32_t length) { size_t sz; UEFI_PARTITION_ENTRY *upe = (void *)addr; void *tmp; if (!tpm_is_working() || !uefi_gpt_data || length < sizeof(*upe) || !memcmp(upe->partTypeGuid, ZeroGuid, sizeof(ZeroGuid))) return; sz = offset_of(UEFI_GPT_DATA, Partitions) + (uefi_gpt_data->NumberOfPartitions + 1) * sizeof(UEFI_PARTITION_ENTRY); if (sz > uefi_gpt_data_size) { tmp = SLOF_alloc_mem(sz); if (!tmp) goto err_no_mem; memcpy(tmp, uefi_gpt_data, uefi_gpt_data_size); SLOF_free_mem(uefi_gpt_data, uefi_gpt_data_size); uefi_gpt_data = tmp; uefi_gpt_data_size = sz; } memcpy(&uefi_gpt_data->Partitions[uefi_gpt_data->NumberOfPartitions], addr, sizeof(UEFI_PARTITION_ENTRY)); uefi_gpt_data->NumberOfPartitions++; return; err_no_mem: SLOF_free_mem(uefi_gpt_data, uefi_gpt_data_size); uefi_gpt_data_size = 0; uefi_gpt_data = NULL; } /* * tpm_measure_gpt finally measures the GPT table and adds an entry * to the log. */ uint32_t tpm_measure_gpt(void) { size_t sz; if (!tpm_is_working()) return TCGBIOS_GENERAL_ERROR; sz = offset_of(UEFI_GPT_DATA, Partitions) + uefi_gpt_data->NumberOfPartitions * sizeof(UEFI_PARTITION_ENTRY); return tpm_add_measurement_to_log(5, EV_EFI_GPT_EVENT, (const char *)uefi_gpt_data, sz, (const uint8_t *)uefi_gpt_data, sz); } uint32_t tpm_measure_scrtm(void) { uint32_t rc; char *version_start = strstr((char *)&print_version, "FW Version"); char *version_end; uint32_t version_length; char *slof_text_start = (char *)&_slof_text; uint32_t slof_text_length = (long)&_slof_text_end - (long)&_slof_text; const char *scrtm = "S-CRTM Contents"; version_end = strchr(version_start, '\r'); version_length = version_end - version_start; dprintf("Measure S-CRTM Version: addr = %p, length = %d\n", version_start, version_length); rc = tpm_add_measurement_to_log(0, EV_S_CRTM_VERSION, version_start, version_length, (uint8_t *)version_start, version_length); if (rc) return rc; dprintf("Measure S-CRTM Content (text): start = %p, length = %d\n", slof_text_start, slof_text_length); rc = tpm_add_measurement_to_log(0, EV_S_CRTM_CONTENTS, scrtm, strlen(scrtm), (uint8_t *)slof_text_start, slof_text_length); return rc; } /* * tpm_driver_get_failure_reason: Function for interfacing with the firmware * API */ uint32_t tpm_driver_get_failure_reason(void) { /* do not check for a working TPM here */ if (!tpm_state.tpm_found) return VTPM_DRV_STATE_INVALID; return spapr_vtpm_get_error(); } /* * tpm_driver_set_failure_reason: Function for interfacing with the firmware * API */ void tpm_driver_set_failure_reason(uint32_t errcode) { if (!tpm_state.tpm_found) return; spapr_vtpm_set_error(errcode); } /**************************************************************** * TPM Configuration Menu ****************************************************************/ static int tpm20_get_suppt_pcrbanks(uint8_t *suppt_pcrbanks, uint8_t *active_pcrbanks) { struct tpms_pcr_selection *sel; void *end; *suppt_pcrbanks = 0; *active_pcrbanks = 0; sel = tpm_state.tpm20_pcr_selection->selections; end = (void*)tpm_state.tpm20_pcr_selection + tpm_state.tpm20_pcr_selection_size; while (1) { uint16_t hashalg; uint8_t hashalg_flag; unsigned i; uint8_t sizeOfSelect = sel->sizeOfSelect; void *nsel = (void*)sel + sizeof(*sel) + sizeOfSelect; if (nsel > end) return 0; hashalg = be16_to_cpu(sel->hashAlg); hashalg_flag = tpm20_hashalg_to_flag(hashalg); *suppt_pcrbanks |= hashalg_flag; for (i = 0; i < sizeOfSelect; i++) { if (sel->pcrSelect[i]) { *active_pcrbanks |= hashalg_flag; break; } } sel = nsel; } } static int tpm20_set_pcrbanks(uint32_t active_banks) { struct tpm2_req_pcr_allocate trpa = { .hdr.tag = cpu_to_be16(TPM2_ST_SESSIONS), .hdr.ordinal = cpu_to_be32(TPM2_CC_PCR_Allocate), .authhandle = cpu_to_be32(TPM2_RH_PLATFORM), .authblocksize = cpu_to_be32(sizeof(trpa.authblock)), .authblock = { .handle = cpu_to_be32(TPM2_RS_PW), .noncesize = cpu_to_be16(0), .contsession = TPM2_YES, .pwdsize = cpu_to_be16(0), }, }; struct tpms_pcr_selection3 { uint16_t hashAlg; uint8_t sizeOfSelect; uint8_t pcrSelect[3]; } tps[ARRAY_SIZE(trpa.tpms_pcr_selections)]; int i = 0; uint8_t hashalg_flag = TPM2_ALG_SHA1_FLAG; uint8_t dontcare, suppt_banks; struct tpm_rsp_header rsp; uint32_t resp_length = sizeof(rsp); uint16_t hashalg; int ret; tpm20_get_suppt_pcrbanks(&suppt_banks, &dontcare); while (hashalg_flag) { if ((hashalg_flag & suppt_banks)) { hashalg = tpm20_hashalg_flag_to_hashalg(hashalg_flag); if (hashalg) { uint8_t mask = 0; tps[i].hashAlg = cpu_to_be16(hashalg); tps[i].sizeOfSelect = 3; if (active_banks & hashalg_flag) mask = 0xff; tps[i].pcrSelect[0] = mask; tps[i].pcrSelect[1] = mask; tps[i].pcrSelect[2] = mask; i++; } } hashalg_flag <<= 1; } trpa.count = cpu_to_be32(i); memcpy(trpa.tpms_pcr_selections, tps, i * sizeof(tps[0])); trpa.hdr.totlen = cpu_to_be32(offset_of(struct tpm2_req_pcr_allocate, tpms_pcr_selections) + i * sizeof(tps[0])); ret = spapr_transmit(0, &trpa.hdr, &rsp, &resp_length, TPM_DURATION_TYPE_SHORT); ret = ret ? -1 : (int) be32_to_cpu(rsp.errcode); return ret; } static int tpm20_activate_pcrbanks(uint32_t active_banks) { int ret; ret = tpm20_set_pcrbanks(active_banks); if (!ret) ret = tpm_simple_cmd(0, TPM2_CC_Shutdown, 2, TPM2_SU_CLEAR, TPM_DURATION_TYPE_SHORT); if (!ret) SLOF_reset(); return ret; } static int tpm20_clearcontrol(uint8_t disable) { struct tpm2_req_clearcontrol trc = { .hdr.tag = cpu_to_be16(TPM2_ST_SESSIONS), .hdr.totlen = cpu_to_be32(sizeof(trc)), .hdr.ordinal = cpu_to_be32(TPM2_CC_ClearControl), .authhandle = cpu_to_be32(TPM2_RH_PLATFORM), .authblocksize = cpu_to_be32(sizeof(trc.authblock)), .authblock = { .handle = cpu_to_be32(TPM2_RS_PW), .noncesize = cpu_to_be16(0), .contsession = TPM2_YES, .pwdsize = cpu_to_be16(0), }, .disable = disable, }; struct tpm_rsp_header rsp; uint32_t resp_length = sizeof(rsp); int ret; ret = spapr_transmit(0, &trc.hdr, &rsp, &resp_length, TPM_DURATION_TYPE_SHORT); if (ret || resp_length != sizeof(rsp) || rsp.errcode) ret = -1; dprintf("TCGBIOS: Return value from sending TPM2_CC_ClearControl = 0x%08x\n", ret); return ret; } static int tpm20_clear(void) { struct tpm2_req_clear trq = { .hdr.tag = cpu_to_be16(TPM2_ST_SESSIONS), .hdr.totlen = cpu_to_be32(sizeof(trq)), .hdr.ordinal = cpu_to_be32(TPM2_CC_Clear), .authhandle = cpu_to_be32(TPM2_RH_PLATFORM), .authblocksize = cpu_to_be32(sizeof(trq.authblock)), .authblock = { .handle = cpu_to_be32(TPM2_RS_PW), .noncesize = cpu_to_be16(0), .contsession = TPM2_YES, .pwdsize = cpu_to_be16(0), }, }; struct tpm_rsp_header rsp; uint32_t resp_length = sizeof(rsp); int ret; ret = spapr_transmit(0, &trq.hdr, &rsp, &resp_length, TPM_DURATION_TYPE_MEDIUM); if (ret || resp_length != sizeof(rsp) || rsp.errcode) ret = -1; dprintf("TCGBIOS: Return value from sending TPM2_CC_Clear = 0x%08x\n", ret); return ret; } static int tpm20_menu_change_active_pcrbanks(void) { uint8_t active_banks, suppt_banks, activate_banks; tpm20_get_suppt_pcrbanks(&suppt_banks, &active_banks); activate_banks = active_banks; while (1) { uint8_t hashalg_flag = TPM2_ALG_SHA1_FLAG; uint8_t i = 0; uint8_t flagnum; int show = 0; printf("\nToggle active PCR banks by pressing number key\n\n"); while (hashalg_flag) { uint8_t flag = hashalg_flag & suppt_banks; const char *hashname = tpm20_hashalg_flag_to_name(flag); i++; if (hashname) { printf(" %d: %s", i, hashname); if (activate_banks & hashalg_flag) printf(" (enabled)"); printf("\n"); } hashalg_flag <<= 1; } printf("\n" "ESC: return to previous menu without changes\n"); if (activate_banks) printf("a : activate selection\n"); while (!show) { int key_code = SLOF_get_keystroke(); switch (key_code) { case ~0: continue; case 27: /* ESC */ printf("\n"); return -1; case '1' ... '5': /* keys 1 .. 5 */ flagnum = key_code - '0'; if (flagnum > i) continue; if (suppt_banks & (1 << (flagnum - 1))) { activate_banks ^= 1 << (flagnum - 1); show = 1; } break; case 'a': /* a */ if (activate_banks) tpm20_activate_pcrbanks(activate_banks); } } } } void tpm20_menu(void) { int key_code; int waitkey; int ret; for (;;) { printf("1. Clear TPM\n"); printf("2. Change active PCR banks\n"); printf("\nIf not change is desired or if this menu was reached by " "mistake, press ESC to\ncontinue the boot.\n"); waitkey = 1; while (waitkey) { key_code = SLOF_get_keystroke(); switch (key_code) { case 27: // ESC return; case '1': ret = tpm20_clearcontrol(false); if (!ret) ret = tpm20_clear(); if (ret) printf("An error occurred clearing " "the TPM: 0x%x\n", ret); break; case '2': tpm20_menu_change_active_pcrbanks(); waitkey = 0; continue; default: continue; } waitkey = 0; } } }