Re: [RFC PATCH 4/4] x86: Add early PCR extend support for Secure Launch
From: Jarkko Sakkinen
Date: Mon Nov 02 2020 - 07:19:57 EST
On Sat, Oct 31, 2020 at 12:51:22PM -0400, Daniel P. Smith wrote:
> 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>
We don't want two copies of TIS implementation and TPM helper functions.
This is a problem that the patch set must resolve in order to be
acceptable for the mainline.
/Jarkko
> ---
> 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
>