diff mbox series

[v4,24/33] tpm2: Rework the logging and implement tpm20_extend()

Message ID 20191211202728.127996-25-stefanb@linux.vnet.ibm.com
State Superseded
Headers show
Series Add vTPM support to SLOF | expand

Commit Message

Stefan Berger Dec. 11, 2019, 8:27 p.m. UTC
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
---
 lib/libtpm/tcgbios.c     | 363 ++++++++++++++++++++++++++++++++++++---
 lib/libtpm/tcgbios_int.h |  69 ++++++++
 2 files changed, 406 insertions(+), 26 deletions(-)
diff mbox series

Patch

diff --git a/lib/libtpm/tcgbios.c b/lib/libtpm/tcgbios.c
index 3f1dca8..c8759cb 100644
--- a/lib/libtpm/tcgbios.c
+++ b/lib/libtpm/tcgbios.c
@@ -88,6 +88,17 @@  static inline uint32_t cpu_to_log32(uint32_t val)
 	return 0;
 }
 
+static inline uint16_t cpu_to_log16(uint16_t val)
+{
+	switch (TPM_version) {
+	case TPM_VERSION_1_2:
+		return cpu_to_be16(val);
+	case TPM_VERSION_2:
+		return cpu_to_le16(val);
+	}
+	return 0;
+}
+
 static inline bool tpm_log_is_be(void)
 {
 	return TPM_version == TPM_VERSION_1_2;
@@ -104,9 +115,144 @@  static void probe_tpm(void)
 	tpm_state.tpm_working = tpm_state.tpm_found;
 }
 
+
+/****************************************************************
+ * Digest formatting
+ ****************************************************************/
+
 static uint32_t tpm20_pcr_selection_size;
 static struct tpml_pcr_selection *tpm20_pcr_selection;
 
+/* A 'struct tpm_log_entry' is a local data structure containing a
+ * 'tpm_log_header' followed by space for the maximum supported
+ * digest.  (The digest is a sha1 hash on tpm1.2 or a series of
+ * tpm2_digest_value structs on tpm2.0)
+ */
+struct tpm_log_entry {
+	struct tpm_log_header hdr;
+	uint8_t pad[sizeof(struct tpm2_digest_values)
+	   + 5 * sizeof(struct tpm2_digest_value)
+	   + SHA1_BUFSIZE + SHA256_BUFSIZE + SHA384_BUFSIZE
+	   + SHA512_BUFSIZE + SM3_256_BUFSIZE];
+} __attribute__((packed));
+
+static int
+tpm20_get_hash_buffersize(uint16_t hashAlg)
+{
+	switch (hashAlg) {
+	case TPM2_ALG_SHA1:
+		return SHA1_BUFSIZE;
+	case TPM2_ALG_SHA256:
+		return SHA256_BUFSIZE;
+	case TPM2_ALG_SHA384:
+		return SHA384_BUFSIZE;
+	case TPM2_ALG_SHA512:
+		return SHA512_BUFSIZE;
+	case TPM2_ALG_SM3_256:
+		return SM3_256_BUFSIZE;
+	default:
+		return -1;
+	}
+}
+
+/*
+ * Build the TPM2 tpm2_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 sha1 hash in the area of the sha256
+ * 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 *sha1,
+			      bool bigEndian)
+{
+	struct tpms_pcr_selection *sel;
+	void *nsel, *end;
+	void *dest = le->hdr.digest + sizeof(struct tpm2_digest_values);
+	uint32_t count;
+	struct tpm2_digest_value *v;
+	struct tpm2_digest_values *vs;
+
+	if (!tpm20_pcr_selection)
+		return -1;
+
+	sel = tpm20_pcr_selection->selections;
+	end = (void *)tpm20_pcr_selection + tpm20_pcr_selection_size;
+
+	for (count = 0; count < be32_to_cpu(tpm20_pcr_selection->count); count++) {
+		int hsize;
+		uint8_t sizeOfSelect = sel->sizeOfSelect;
+
+		nsel = (void*)sel + sizeof(*sel) + sizeOfSelect;
+		if (nsel > end)
+			break;
+
+		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, sha1, hsize > SHA1_BUFSIZE ? SHA1_BUFSIZE : hsize);
+
+		dest += sizeof(*v) + hsize;
+		sel = nsel;
+	}
+
+	if (sel != end) {
+		dprintf("Malformed pcr selection structure fron TPM\n");
+		return -1;
+	}
+
+	vs = (void*)le->hdr.digest;
+	if (bigEndian)
+		vs->count = cpu_to_be32(count);
+	else
+		vs->count = cpu_to_le32(count);
+
+	return dest - (void*)le->hdr.digest;
+}
+
+static int tpm12_build_digest(struct tpm_log_entry *le, const uint8_t *sha1)
+{
+	// On TPM 1.2 the digest contains just the SHA1 hash
+	memcpy(le->hdr.digest, sha1, SHA1_BUFSIZE);
+	return SHA1_BUFSIZE;
+}
+
+static int
+tpm_build_digest(struct tpm_log_entry *le, const uint8_t *sha1, bool bigEndian)
+{
+	switch (TPM_version) {
+	case TPM_VERSION_1_2:
+		return tpm12_build_digest(le, sha1);
+	case TPM_VERSION_2:
+		return tpm20_build_digest(le, sha1, bigEndian);
+	}
+	return -1;
+}
+
 /****************************************************************
  * TPM hardware command wrappers
  ****************************************************************/
@@ -295,19 +441,19 @@  static void tpm20_set_timeouts(void)
  * @hash: sha1 hash (20 bytes) to extend PCR with
  * @pcrindex: the PCR to extend [ 0..23 ]
  */
-static int tpm_extend(uint8_t *hash, uint32_t pcrindex)
+static int tpm12_extend(struct tpm_log_entry *le, int digest_len)
 {
 	struct tpm_req_extend tre = {
 		.hdr.tag = cpu_to_be16(TPM_TAG_RQU_CMD),
 		.hdr.totlen = cpu_to_be32(sizeof(tre)),
 		.hdr.ordinal = cpu_to_be32(TPM_ORD_EXTEND),
-		.pcrindex = cpu_to_be32(pcrindex),
+		.pcrindex = cpu_to_be32(log32_to_cpu(le->hdr.pcrindex)),
 	};
 	struct tpm_rsp_extend rsp;
 	uint32_t resp_length = sizeof(rsp);
 	int ret;
 
-	memcpy(tre.digest, hash, sizeof(tre.digest));
+	memcpy(tre.digest, le->hdr.digest, sizeof(tre.digest));
 
 	ret = tpmhw_transmit(0, &tre.hdr, &rsp, &resp_length,
 			     TPM_DURATION_TYPE_SHORT);
@@ -321,6 +467,50 @@  static int tpm_extend(uint8_t *hash, uint32_t pcrindex)
 	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;
+
+	memcpy(tre, &tmp_tre, sizeof(tmp_tre));
+	memcpy(&tre->digest[0], le->hdr.digest, digest_len);
+
+	tre->hdr.totlen = cpu_to_be32(sizeof(tmp_tre) + digest_len);
+
+	struct tpm_rsp_header rsp;
+	uint32_t resp_length = sizeof(rsp);
+	int ret = tpmhw_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 tpm_extend(struct tpm_log_entry *le, int digest_len)
+{
+	switch (TPM_version) {
+	case TPM_VERSION_1_2:
+		return tpm12_extend(le, digest_len);
+	case TPM_VERSION_2:
+		return tpm20_extend(le, digest_len);
+	}
+	return -1;
+}
+
 static int tpm20_hierarchycontrol(uint32_t hierarchy, uint8_t state)
 {
 	/* we will try to deactivate the TPM now - ignoring all errors */
@@ -391,10 +581,12 @@  static void tpm_set_failure(void)
  *
  * Returns 0 on success, an error code otherwise.
  */
-static uint32_t tpm_log_event_long(struct pcpes *pcpes,
+static uint32_t tpm_log_event_long(struct tpm_log_header *entry,
+				   int digest_len,
 				   const void *event, uint32_t event_length)
 {
-	uint32_t size;
+	uint32_t size, logsize;
+	void *dest;
 
 	dprintf("log base address = %p, next entry = %p\n",
 		tpm_state.log_base, tpm_state.log_area_next_entry);
@@ -402,20 +594,21 @@  static uint32_t tpm_log_event_long(struct pcpes *pcpes,
 	if (tpm_state.log_area_next_entry == NULL)
 		return TCGBIOS_LOGOVERFLOW;
 
-	size = offset_of(struct pcpes, event) + event_length;
-
-	if ((tpm_state.log_area_next_entry + size - tpm_state.log_base) >
-	     tpm_state.log_area_size) {
-		dprintf("LOG OVERFLOW: size = %d\n", size);
+	size = sizeof(*entry) + digest_len +
+	       sizeof(struct tpm_log_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 = %u\n", size);
 		return TCGBIOS_LOGOVERFLOW;
 	}
 
-	pcpes->eventdatasize = cpu_to_log32(event_length);
-
-	memcpy(tpm_state.log_area_next_entry, pcpes,
-	       offset_of(struct pcpes, event));
-	memcpy(tpm_state.log_area_next_entry + offset_of(struct pcpes, event),
-	       event, event_length);
+	dest = tpm_state.log_area_next_entry;
+	memcpy(dest, entry, sizeof(*entry) + digest_len);
+	struct tpm_log_trailer *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;
 
@@ -426,11 +619,97 @@  bool tpm_log_event(struct pcpes *pcpes)
 {
 	const char *event = NULL;
 	uint32_t event_length = log32_to_cpu(pcpes->eventdatasize);
+	struct tpm_log_entry le = {
+		.hdr.pcrindex = pcpes->pcrindex,
+		.hdr.eventtype = pcpes->eventtype,
+	};
+	int digest_len, ret;
 
 	if (event_length)
 		event = (void *)pcpes + offset_of(struct pcpes, event);
 
-	return (tpm_log_event_long(pcpes, event, event_length) == 0);
+	digest_len = tpm_build_digest(&le, pcpes->digest, tpm_log_is_be());
+	if (digest_len < 0)
+		return false;
+
+	ret = tpm_log_event_long(&le.hdr, digest_len, event, event_length);
+	if (ret)
+		return false;
+	return true;
+}
+
+/* Add an entry at the start of the log describing digest formats
+ */
+static int tpm20_write_EfiSpecIdEventStruct(void)
+{
+	if (!tpm20_pcr_selection)
+		return -1;
+
+	struct {
+		struct TCG_EfiSpecIdEventStruct hdr;
+		uint32_t pad[256];
+	} 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 = tpm20_pcr_selection->selections;
+	void *nsel, *end = (void*)tpm20_pcr_selection + tpm20_pcr_selection_size;
+	int event_size;
+	uint32_t *vendorInfoSize;
+	struct tpm_log_entry le = {
+		.hdr.eventtype = cpu_to_log32(EV_NO_ACTION),
+	};
+	uint32_t count;
+
+	for (count = 0;
+	     count < be32_to_cpu(tpm20_pcr_selection->count);
+	     count++) {
+		int hsize;
+		uint8_t sizeOfSelect = sel->sizeOfSelect;
+
+		nsel = (void*)sel + sizeof(*sel) + sizeOfSelect;
+		if (nsel > end)
+			break;
+
+		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(uint32_t)) {
+			dprintf("EfiSpecIdEventStruct pad too small\n");
+			return -1;
+		}
+
+		event.hdr.digestSizes[count].algorithmId =
+			cpu_to_log16(be16_to_cpu(sel->hashAlg));
+		event.hdr.digestSizes[count].digestSize = cpu_to_log16(hsize);
+
+		sel = nsel;
+	}
+
+	if (sel != end) {
+		dprintf("Malformed pcr selection structure fron TPM\n");
+		return -1;
+	}
+
+	event.hdr.numberOfAlgorithms = cpu_to_log32(count);
+	event_size = offset_of(struct TCG_EfiSpecIdEventStruct,
+			       digestSizes[count]);
+	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 tpm12_assert_physical_presence(void)
@@ -520,6 +799,8 @@  static int tpm20_startup(void)
 	if (ret)
 		goto err_exit;
 
+	/* the log parameters will be passed from Forth layer */
+
 	return 0;
 
 err_exit:
@@ -580,11 +861,20 @@  uint32_t tpm_unassert_physical_presence(void)
 
 void tpm_set_log_parameters(void *addr, unsigned int size)
 {
+	int ret;
+
 	dprintf("Log is at 0x%llx; size is %u bytes\n",
 		(uint64_t)addr, size);
 	tpm_state.log_base = addr;
 	tpm_state.log_area_next_entry = addr;
 	tpm_state.log_area_size = size;
+
+	switch (TPM_version) {
+	case TPM_VERSION_2:
+		ret = tpm20_write_EfiSpecIdEventStruct();
+		if (ret)
+			tpm_set_failure();
+	}
 }
 
 uint32_t tpm_get_logsize(void)
@@ -611,18 +901,29 @@  static uint32_t hash_log_extend(struct pcpes *pcpes,
 				bool extend)
 {
 	int ret;
+	struct tpm_log_entry le;
+	int digest_len;
 
 	if (log32_to_cpu(pcpes->pcrindex) >= 24)
 		return TCGBIOS_INVALID_INPUT_PARA;
 	if (hashdata)
 		tpm_hash_all(hashdata, hashdata_length, pcpes->digest);
 
+	le = (struct tpm_log_entry) {
+		.hdr.pcrindex = pcpes->pcrindex,
+		.hdr.eventtype = pcpes->eventtype,
+	};
+	digest_len = tpm_build_digest(&le, pcpes->digest, true);
+	if (digest_len < 0)
+		return TCGBIOS_GENERAL_ERROR;
+
 	if (extend) {
-		ret = tpm_extend(pcpes->digest, log32_to_cpu(pcpes->pcrindex));
+		ret = tpm_extend(&le, digest_len);
 		if (ret)
 			return TCGBIOS_COMMAND_ERROR;
 	}
-	ret = tpm_log_event_long(pcpes, event, event_length);
+	tpm_build_digest(&le, pcpes->digest, tpm_log_is_be());
+	ret = tpm_log_event_long(&le.hdr, digest_len, event, event_length);
 	if (ret)
 		return TCGBIOS_LOGOVERFLOW;
 	return 0;
@@ -647,14 +948,24 @@  static uint32_t tpm_add_measurement_to_log(uint32_t pcrindex,
 					   const uint8_t *hashdata,
 					   uint32_t hashdatalen)
 {
-	struct pcpes pcpes;
-
-	pcpes.pcrindex	= cpu_to_log32(pcrindex);
-	pcpes.eventtype = cpu_to_log32(eventtype);
-	memset(&pcpes.digest, 0, sizeof(pcpes.digest));
+	uint8_t hash[SHA1_BUFSIZE];
+	struct tpm_log_entry le = {
+		.hdr.pcrindex = cpu_to_log32(pcrindex),
+		.hdr.eventtype = cpu_to_log32(eventtype),
+	};
+	int digest_len;
 
-	return hash_log_extend(&pcpes, hashdata, hashdatalen,
-			       info, infolen, true);
+	sha1(hashdata, hashdatalen, hash);
+	digest_len = tpm_build_digest(&le, hash, true);
+	if (digest_len < 0)
+		return TCGBIOS_GENERAL_ERROR;
+	int ret = tpm_extend(&le, digest_len);
+	if (ret) {
+		tpm_set_failure();
+		return TCGBIOS_COMMAND_ERROR;
+	}
+	tpm_build_digest(&le, hash, tpm_log_is_be());
+	return tpm_log_event_long(&le.hdr, digest_len, info, infolen);
 }
 
 /*
diff --git a/lib/libtpm/tcgbios_int.h b/lib/libtpm/tcgbios_int.h
index 581424f..3aab7ed 100644
--- a/lib/libtpm/tcgbios_int.h
+++ b/lib/libtpm/tcgbios_int.h
@@ -56,6 +56,7 @@ 
 
 /* event types */
 #define EV_POST_CODE                     1
+#define EV_NO_ACTION                     3
 #define EV_SEPARATOR                     4
 #define EV_ACTION                        5
 #define EV_EVENT_TAG                     6
@@ -65,6 +66,59 @@ 
 #define EV_IPL_PARTITION_DATA           14
 
 #define SHA1_BUFSIZE                    20
+#define SHA256_BUFSIZE                  32
+#define SHA384_BUFSIZE                  48
+#define SHA512_BUFSIZE                  64
+#define SM3_256_BUFSIZE                 32
+
+struct tpm2_digest_value {
+	uint16_t hashAlg;
+	uint8_t hash[0]; /* size depends on hashAlg */
+} __attribute__((packed));
+
+struct tpm2_digest_values {
+	uint32_t count;
+	struct tpm2_digest_value digest[0];
+} __attribute__((packed));
+
+/* Each entry in the TPM log contains: a tpm_log_header, a variable
+ * length digest, a tpm_log_trailer, and a variable length event.  The
+ * 'digest' matches what is sent to the TPM hardware via the Extend
+ * command.  On TPM1.2 the digest is a SHA1 hash; on TPM2.0 the digest
+ * contains a tpm2_digest_values struct followed by a variable number
+ * of tpm2_digest_value structs (as specified by the hardware via the
+ * TPM2_CAP_PCRS request).
+ */
+struct tpm_log_header {
+	uint32_t pcrindex;
+	uint32_t eventtype;
+	uint8_t digest[0];
+} __attribute__((packed));
+
+struct tpm_log_trailer {
+	uint32_t eventdatasize;
+	uint8_t event[0];
+} __attribute__((packed));
+
+struct TCG_EfiSpecIdEventStruct {
+	uint8_t signature[16];
+	uint32_t platformClass;
+	uint8_t specVersionMinor;
+	uint8_t specVersionMajor;
+	uint8_t specErrata;
+	uint8_t uintnSize;
+	uint32_t numberOfAlgorithms;
+	struct TCG_EfiSpecIdEventAlgorithmSize {
+		uint16_t algorithmId;
+		uint16_t digestSize;
+	} digestSizes[0];
+	/*
+	uint8_t vendorInfoSize;
+	uint8_t vendorInfo[0];
+	*/
+} __attribute__((packed));
+
+#define TPM_TCPA_ACPI_CLASS_CLIENT 0
 
 /* Input and Output blocks for the TCG BIOS commands */
 
@@ -210,6 +264,12 @@  struct tpm_rsp_getcap_buffersize {
 #define TPM2_RH_ENDORSEMENT         0x4000000b
 #define TPM2_RH_PLATFORM            0x4000000c
 
+#define TPM2_ALG_SHA1               0x0004
+#define TPM2_ALG_SHA256             0x000b
+#define TPM2_ALG_SHA384             0x000c
+#define TPM2_ALG_SHA512             0x000d
+#define TPM2_ALG_SM3_256            0x0012
+
 /* TPM 2 command tags */
 #define TPM2_ST_NO_SESSIONS         0x8001
 #define TPM2_ST_SESSIONS            0x8002
@@ -219,6 +279,7 @@  struct tpm_rsp_getcap_buffersize {
 #define TPM2_CC_SelfTest            0x143
 #define TPM2_CC_Startup             0x144
 #define TPM2_CC_GetCapability       0x17a
+#define TPM2_CC_PCR_Extend          0x182
 
 /* TPM 2 Capabilities */
 #define TPM2_CAP_PCRS               0x00000005
@@ -232,6 +293,14 @@  struct tpm2_authblock {
 	uint16_t pwdsize;    /* always 0 */
 } __attribute__((packed));
 
+struct tpm2_req_extend {
+	struct tpm_req_header hdr;
+	uint32_t pcrindex;
+	uint32_t authblocksize;
+	struct tpm2_authblock authblock;
+	uint8_t digest[0];
+} __attribute__((packed));
+
 struct tpm2_req_hierarchycontrol {
 	struct tpm_req_header hdr;
 	uint32_t authhandle;