[RFC PATCH 4/4] x86: Add early PCR extend support for Secure Launch

From: Daniel P. Smith
Date: Sat Oct 31 2020 - 12:46:53 EST


Access to PCR extend functionality is needed early in the compressed
kernel so that Secure Launch can measure items into the DRTM PCRs
before these items are used. The items include the boot parameters and
associated information, the kernel command line, any external initrd
and the OS-MLE TXT heap structure.

NOTE: for the RFC, early_pcr_extend.c is built unconditionally in the
Makefile. In the full Secure Launch patch set it is conditionally built
if CONFIG_SECURE_LAUNCH is defined.

Signed-off-by: Daniel P. Smith <dpsmith@xxxxxxxxxxxxxxxxxxxx>
Signed-off-by: Ross Philipson <ross.philipson@xxxxxxxxxx>
---
arch/x86/boot/compressed/Makefile | 2 +
arch/x86/boot/compressed/early_pcr_extend.c | 311 ++++++++++++++++++++++++++++
arch/x86/boot/compressed/early_pcr_extend.h | 92 ++++++++
3 files changed, 405 insertions(+)
create mode 100644 arch/x86/boot/compressed/early_pcr_extend.c
create mode 100644 arch/x86/boot/compressed/early_pcr_extend.h

diff --git a/arch/x86/boot/compressed/Makefile b/arch/x86/boot/compressed/Makefile
index 5a828fde7a42..8f0b29dce9da 100644
--- a/arch/x86/boot/compressed/Makefile
+++ b/arch/x86/boot/compressed/Makefile
@@ -93,6 +93,8 @@ vmlinux-objs-$(CONFIG_ACPI) += $(obj)/acpi.o
vmlinux-objs-$(CONFIG_EFI_MIXED) += $(obj)/efi_thunk_$(BITS).o
efi-obj-$(CONFIG_EFI_STUB) = $(objtree)/drivers/firmware/efi/libstub/lib.a

+vmlinux-objs-y += $(obj)/early_pcr_extend.o
+
# The compressed kernel is built with -fPIC/-fPIE so that a boot loader
# can place it anywhere in memory and it will still run. However, since
# it is executed as-is without any ELF relocation processing performed
diff --git a/arch/x86/boot/compressed/early_pcr_extend.c b/arch/x86/boot/compressed/early_pcr_extend.c
new file mode 100644
index 000000000000..94ee3cc9814e
--- /dev/null
+++ b/arch/x86/boot/compressed/early_pcr_extend.c
@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 Apertus Solutions, LLC
+ *
+ * Author(s):
+ * Daniel P. Smith <dpsmith@xxxxxxxxxxxxxxxxxxxx>
+ *
+ * The code in this file is based on the article "Writing a TPM Device Driver"
+ * published on http://ptgmedia.pearsoncmg.com.
+ *
+ * The scope of the TPM functionality here is solely to allow DRTM PCRs to be
+ * extended early in the compressed kernel.
+ */
+
+#include <linux/types.h>
+#include <linux/bits.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <asm/byteorder.h>
+#include <asm/io.h>
+
+#define COMPRESSED_KERNEL
+#include <crypto/sha.h>
+#include <linux/tpm_buffer.h>
+#include <linux/tpm_command.h>
+#include <linux/tpm_core.h>
+#include "../../../../drivers/char/tpm/tpm_tis_defs.h"
+#include "early_pcr_extend.h"
+
+#define tpm_read8(f) readb((void *)(u64)(TPM_MMIO_BASE | f))
+#define tpm_write8(v, f) writeb(v, (void *)(u64)(TPM_MMIO_BASE | f))
+#define tpm_read32(f) readl((void *)(u64)(TPM_MMIO_BASE | f));
+
+static struct tpm tpm;
+static u8 locality = TPM_NO_LOCALITY;
+
+static void tpm_io_delay(void)
+{
+ /* This is the default delay type in native_io_delay */
+ asm volatile ("outb %al, $0x80");
+}
+
+static void tpm_udelay(int loops)
+{
+ while (loops--)
+ tpm_io_delay(); /* Approximately 1 us */
+}
+
+static u32 burst_wait(void)
+{
+ u32 count = 0;
+
+ while (count == 0) {
+ count = tpm_read8(TPM_STS(locality) + 1);
+ count += tpm_read8(TPM_STS(locality) + 2) << 8;
+
+ /* Wait for FIFO to drain */
+ if (count == 0)
+ tpm_udelay(TPM_BURST_MIN_DELAY);
+ }
+
+ return count;
+}
+
+static void tis_relinquish_locality(void)
+{
+ if (locality < TPM_MAX_LOCALITY)
+ tpm_write8(TPM_ACCESS_ACTIVE_LOCALITY, TPM_ACCESS(locality));
+
+ locality = TPM_NO_LOCALITY;
+}
+
+static u8 tis_request_locality(u8 l)
+{
+ if (l > TPM_MAX_LOCALITY)
+ return TPM_NO_LOCALITY;
+
+ if (l == locality)
+ return locality;
+
+ tis_relinquish_locality();
+
+ tpm_write8(TPM_ACCESS_REQUEST_USE, TPM_ACCESS(l));
+
+ /* wait for locality to be granted */
+ if (tpm_read8(TPM_ACCESS(l)) & TPM_ACCESS_ACTIVE_LOCALITY)
+ locality = l;
+
+ return locality;
+}
+
+static size_t tis_send(struct tpm_buf *buf)
+{
+ u8 status, *buf_ptr;
+ u32 length, count = 0, burstcnt = 0;
+
+ if (locality > TPM_MAX_LOCALITY)
+ return 0;
+
+ for (status = 0; (status & TPM_STS_COMMAND_READY) == 0; ) {
+ tpm_write8(TPM_STS_COMMAND_READY, TPM_STS(locality));
+ status = tpm_read8(TPM_STS(locality));
+ }
+
+ buf_ptr = buf->data;
+ length = tpm_buf_length(buf);
+
+ /* send all but the last byte */
+ while (count < (length - 1)) {
+ burstcnt = burst_wait();
+ for (; burstcnt > 0 && count < (length - 1); burstcnt--) {
+ tpm_write8(buf_ptr[count], TPM_DATA_FIFO(locality));
+ count++;
+ }
+
+ /* check for overflow */
+ for (status = 0; (status & TPM_STS_VALID) == 0; )
+ status = tpm_read8(TPM_STS(locality));
+
+ if ((status & TPM_STS_DATA_EXPECT) == 0)
+ return 0;
+ }
+
+ /* write last byte */
+ tpm_write8(buf_ptr[length - 1], TPM_DATA_FIFO(locality));
+ count++;
+
+ /* make sure it stuck */
+ for (status = 0; (status & TPM_STS_VALID) == 0; )
+ status = tpm_read8(TPM_STS(locality));
+
+ if ((status & TPM_STS_DATA_EXPECT) != 0)
+ return 0;
+
+ /* go and do it */
+ tpm_write8(TPM_STS_GO, TPM_STS(locality));
+
+ return (size_t)count;
+}
+
+static u8 tis_init(struct tpm *t)
+{
+ locality = TPM_NO_LOCALITY;
+
+ if (tis_request_locality(0) != 0)
+ return 0;
+
+ t->vendor = tpm_read32(TPM_DID_VID(0));
+ if ((t->vendor & 0xFFFF) == 0xFFFF)
+ return 0;
+
+ return 1;
+}
+
+static u16 tpm_alg_size(u16 alg_id)
+{
+ if (alg_id == TPM_ALG_SHA1)
+ return SHA1_DIGEST_SIZE;
+ else if (alg_id == TPM_ALG_SHA256)
+ return SHA256_DIGEST_SIZE;
+ else if (alg_id == TPM_ALG_SHA512)
+ return SHA512_DIGEST_SIZE;
+
+ return 0;
+}
+
+static int tpm1_pcr_extend(struct tpm *t, u32 pcr, struct tpm_digest *d)
+{
+ struct tpm_buf buf;
+ int ret;
+
+ ret = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_EXTEND);
+ if (ret)
+ return ret;
+
+ tpm_buf_append_u32(&buf, pcr);
+ tpm_buf_append(&buf, d->digest, tpm_alg_size(TPM_ALG_SHA1));
+
+ if (tpm_buf_length(&buf) != tis_send(&buf))
+ ret = -EAGAIN;
+
+ return ret;
+}
+
+static int tpm2_extend_pcr(struct tpm *t, u32 pcr, struct tpm_digest *digest)
+{
+ struct tpm_buf buf;
+ u8 auth_area[NULL_AUTH_SIZE] = {0};
+ u32 *handle;
+ int ret;
+
+ ret = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_PCR_EXTEND);
+ if (ret)
+ return ret;
+
+ tpm_buf_append_u32(&buf, pcr);
+
+ /*
+ * The handle, the first element, is the
+ * only non-zero value in a NULL auth
+ */
+ handle = (u32 *)&auth_area;
+ *handle = cpu_to_be32(TPM2_RS_PW);
+
+ tpm_buf_append_u32(&buf, NULL_AUTH_SIZE);
+ tpm_buf_append(&buf, (const unsigned char *)&auth_area,
+ NULL_AUTH_SIZE);
+
+ tpm_buf_append_u32(&buf, 1);
+
+ tpm_buf_append_u16(&buf, digest->alg_id);
+ tpm_buf_append(&buf, (const unsigned char *)digest->digest,
+ tpm_alg_size(digest->alg_id));
+
+ if (tpm_buf_length(&buf) != tis_send(&buf))
+ ret = -EAGAIN;
+
+ return ret;
+}
+
+static void find_interface_and_family(struct tpm *t)
+{
+ struct tpm_interface_id intf_id;
+ struct tpm_intf_capability intf_cap;
+
+ /* Sort out whether if it is 1.2 */
+ intf_cap.val = tpm_read32(TPM_INTF_CAPABILITY_0);
+ if ((intf_cap.interface_version == TPM12_TIS_INTF_12) ||
+ (intf_cap.interface_version == TPM12_TIS_INTF_13)) {
+ t->family = TPM12;
+ t->intf = TPM_TIS;
+ return;
+ }
+
+ /* Assume that it is 2.0 and TIS */
+ t->family = TPM20;
+ t->intf = TPM_TIS;
+
+ /* Check if the interface is CRB */
+ intf_id.val = tpm_read32(TPM_INTERFACE_ID_0);
+ if (intf_id.interface_type == TPM_CRB_INTF_ACTIVE)
+ t->intf = TPM_CRB;
+}
+
+struct tpm *enable_tpm(void)
+{
+ struct tpm *t = &tpm;
+
+ find_interface_and_family(t);
+
+ switch (t->intf) {
+ case TPM_TIS:
+ if (!tis_init(t))
+ return NULL;
+ break;
+ case TPM_CRB:
+ return NULL;
+ }
+
+ return t;
+}
+
+u8 tpm_request_locality(u8 l)
+{
+ return tis_request_locality(l);
+}
+
+int tpm_extend_pcr(struct tpm *t, u32 pcr, u16 algo,
+ u8 *digest)
+{
+ int ret = -EINVAL;
+
+ if (t->family == TPM12) {
+ struct tpm_digest d;
+
+ if (algo != TPM_ALG_SHA1)
+ return -EINVAL;
+
+ memcpy((void *)d.digest, digest, SHA1_DIGEST_SIZE);
+
+ ret = tpm1_pcr_extend(t, pcr, &d);
+ } else if (t->family == TPM20) {
+ struct tpm_digest *d;
+ u8 buf[MAX_TPM_EXTEND_SIZE];
+
+ d = (struct tpm_digest *) buf;
+ d->alg_id = algo;
+ switch (algo) {
+ case TPM_ALG_SHA1:
+ memcpy(d->digest, digest, SHA1_DIGEST_SIZE);
+ break;
+ case TPM_ALG_SHA256:
+ memcpy(d->digest, digest, SHA256_DIGEST_SIZE);
+ break;
+ case TPM_ALG_SHA512:
+ memcpy(d->digest, digest, SHA512_DIGEST_SIZE);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = tpm2_extend_pcr(t, pcr, d);
+ }
+
+ return ret;
+}
+
+void free_tpm(void)
+{
+ tis_relinquish_locality();
+}
diff --git a/arch/x86/boot/compressed/early_pcr_extend.h b/arch/x86/boot/compressed/early_pcr_extend.h
new file mode 100644
index 000000000000..bcd6d57d8c56
--- /dev/null
+++ b/arch/x86/boot/compressed/early_pcr_extend.h
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2020 Apertus Solutions, LLC
+ *
+ * Author(s):
+ * Daniel P. Smith <dpsmith@xxxxxxxxxxxxxxxxxxxx>
+ *
+ */
+
+#ifndef BOOT_COMPRESSED_EARLY_PCR_EXTEND_H
+#define BOOT_COMPRESSED_EARLY_PCR_EXTEND_H
+
+#define TPM_MMIO_BASE 0xFED40000
+#define TPM_MAX_LOCALITY 4
+#define TPM_NO_LOCALITY 0xFF
+#define TPM_BURST_MIN_DELAY 100 /* 100us */
+#define TPM_ORD_PCR_EXTEND 20
+#define NULL_AUTH_SIZE 9
+#define MAX_TPM_EXTEND_SIZE 68 /* TPM2 SHA512 is the largest */
+
+#define TPM_INTERFACE_ID_0 0x30
+#define TPM_TIS_INTF_ACTIVE 0x00
+#define TPM_CRB_INTF_ACTIVE 0x01
+
+struct tpm_interface_id {
+ union {
+ u32 val;
+ struct {
+ u32 interface_type:4;
+ u32 interface_version:4;
+ u32 cap_locality:1;
+ u32 reserved1:4;
+ u32 cap_tis:1;
+ u32 cap_crb:1;
+ u32 cap_if_res:2;
+ u32 interface_selector:2;
+ u32 intf_sel_lock:1;
+ u32 reserved2:4;
+ u32 reserved3:8;
+ };
+ };
+} __packed;
+
+#define TPM_INTF_CAPABILITY_0 0x14
+#define TPM12_TIS_INTF_12 0x00
+#define TPM12_TIS_INTF_13 0x02
+#define TPM20_TIS_INTF_13 0x03
+
+struct tpm_intf_capability {
+ union {
+ u32 val;
+ struct {
+ u32 data_avail_int_support:1;
+ u32 sts_valid_int_support:1;
+ u32 locality_change_int_support:1;
+ u32 interrupt_level_high:1;
+ u32 interrupt_level_low:1;
+ u32 interrupt_edge_rising:1;
+ u32 interrupt_edge_falling:1;
+ u32 command_ready_int_support:1;
+ u32 burst_count_static:1;
+ u32 data_transfer_size_support:2;
+ u32 reserved1:17;
+ u32 interface_version:3;
+ u32 reserved2:1;
+ };
+ };
+} __packed;
+
+enum tpm_hw_intf {
+ TPM_TIS,
+ TPM_CRB
+};
+
+enum tpm_family {
+ TPM12,
+ TPM20
+};
+
+struct tpm {
+ u32 vendor;
+ enum tpm_family family;
+ enum tpm_hw_intf intf;
+};
+
+extern struct tpm *enable_tpm(void);
+extern u8 tpm_request_locality(u8 l);
+extern int tpm_extend_pcr(struct tpm *t, u32 pcr, u16 algo,
+ u8 *digest);
+extern void free_tpm(void);
+
+#endif
--
2.11.0