| /***************************************************************************** |
| * 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 <stddef.h> |
| #include <stdlib.h> |
| |
| #include "types.h" |
| #include "byteorder.h" |
| #include "tpm_drivers.h" |
| #include "string.h" |
| #include "tcgbios.h" |
| #include "tcgbios_int.h" |
| #include "stdio.h" |
| #include "sha.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 = 2, |
| .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); |
| } |
| |
| uint32_t tpm_2hash_ext_log(uint32_t pcrindex, |
| uint32_t eventtype, |
| const char *info, uint32_t infolen, |
| const void *data, uint64_t datalen) |
| { |
| uint32_t ret; |
| |
| ret = tpm_add_measurement_to_log(pcrindex, eventtype, |
| info, infolen, |
| data, datalen); |
| if (!ret) |
| return (uint32_t)-1; // TRUE |
| return 0; // FALSE |
| } |
| |
| /* |
| * 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, |
| (const char *)evt_separator, |
| sizeof(evt_separator), |
| 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; |
| } |
| } |
| } |