blob: 3b2e76d87d8430d45e7e0539c97a6adc2643dcab [file] [log] [blame]
/*****************************************************************************
* 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;
void (*hashfunc)(const uint8_t *data, uint32_t length, uint8_t *hash);
} hash_parameters[] = {
{
.hashalg = TPM2_ALG_SHA1,
.hashalg_flag = TPM2_ALG_SHA1_FLAG,
.hash_buffersize = SHA1_BUFSIZE,
.name = "SHA1",
.hashfunc = sha1,
}, {
.hashalg = TPM2_ALG_SHA256,
.hashalg_flag = TPM2_ALG_SHA256_FLAG,
.hash_buffersize = SHA256_BUFSIZE,
.name = "SHA256",
.hashfunc = sha256,
}, {
.hashalg = TPM2_ALG_SHA384,
.hashalg_flag = TPM2_ALG_SHA384_FLAG,
.hash_buffersize = SHA384_BUFSIZE,
.name = "SHA384",
.hashfunc = sha384,
}, {
.hashalg = TPM2_ALG_SHA512,
.hashalg_flag = TPM2_ALG_SHA512_FLAG,
.hash_buffersize = SHA512_BUFSIZE,
.name = "SHA512",
.hashfunc = 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;
}
static void tpm2_hash_data(uint16_t hashAlg,
const uint8_t *data, uint32_t data_len,
uint8_t *hash)
{
unsigned i;
for (i = 0; i < ARRAY_SIZE(hash_parameters); i++) {
if (hash_parameters[i].hashalg == hashAlg) {
if (hash_parameters[i].hashfunc) {
hash_parameters[i].hashfunc(data, data_len,
hash);
} else {
memset(hash, 0xff,
hash_parameters[i].hash_buffersize);
}
}
}
}
/*
* 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
* hashdata: the data to hash
* hashdata_len: the length of the hashdata
* 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 *hashdata, uint32_t hashdata_len,
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));
tpm2_hash_data(be16_to_cpu(sel->hashAlg), hashdata, hashdata_len,
v->hash);
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);
tpm20_hierarchycontrol(TPM2_RH_PLATFORM, 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)
{
struct tpm_log_entry le = {
.hdr.pcrindex = cpu_to_log32(pcrindex),
.hdr.eventtype = cpu_to_log32(eventtype),
};
int digest_len;
int ret;
digest_len = tpm20_build_digest(&le, hashdata, hashdatalen, 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, hashdata, hashdatalen, 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, i;
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";
#define _TT(a, x) a##x
#define _T(a, x) _TT(a, x)
unsigned short ucs2_version[] = _T(L, RELEASE);
dprintf("Measure S-CRTM Version: addr = %p, length = %d\n",
ucs2_version, ucs2_length);
for (i = 0; i < ARRAY_SIZE(ucs2_version); ++i)
ucs2_version[i] = cpu_to_le16(ucs2_version[i]);
rc = tpm_add_measurement_to_log(0, EV_S_CRTM_VERSION,
(char *)ucs2_version,
sizeof(ucs2_version),
(uint8_t *)ucs2_version,
sizeof(ucs2_version));
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;
}
}
}