[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [PATCH 11/21] x86/tpm.c: code for early hashing and extending PCRs (for TPM1.2)
From: Krystian Hebel <krystian.hebel@xxxxxxxxx> This file is built twice: for early 32b mode without paging to measure MBI and for 64b code to measure dom0 kernel and initramfs. Since MBI is small, the first case uses TPM to do the hashing. Kernel and initramfs on the other hand are too big, sending them to the TPM would take multiple minutes. Signed-off-by: Krystian Hebel <krystian.hebel@xxxxxxxxx> Signed-off-by: Sergii Dmytruk <sergii.dmytruk@xxxxxxxxx> --- xen/arch/x86/Makefile | 1 + xen/arch/x86/boot/Makefile | 7 +- xen/arch/x86/boot/head.S | 3 + xen/arch/x86/include/asm/slaunch.h | 14 + xen/arch/x86/include/asm/tpm.h | 19 ++ xen/arch/x86/slaunch.c | 7 +- xen/arch/x86/tpm.c | 437 +++++++++++++++++++++++++++++ 7 files changed, 486 insertions(+), 2 deletions(-) create mode 100644 xen/arch/x86/include/asm/tpm.h create mode 100644 xen/arch/x86/tpm.c diff --git a/xen/arch/x86/Makefile b/xen/arch/x86/Makefile index cae548f7e9..7d1027a50f 100644 --- a/xen/arch/x86/Makefile +++ b/xen/arch/x86/Makefile @@ -67,6 +67,7 @@ obj-y += spec_ctrl.o obj-y += srat.o obj-y += string.o obj-y += time.o +obj-y += tpm.o obj-y += traps-setup.o obj-y += traps.o obj-$(CONFIG_INTEL) += tsx.o diff --git a/xen/arch/x86/boot/Makefile b/xen/arch/x86/boot/Makefile index d0015f7d19..ab37ab1fb7 100644 --- a/xen/arch/x86/boot/Makefile +++ b/xen/arch/x86/boot/Makefile @@ -6,6 +6,7 @@ obj32 := cmdline.32.o obj32 += reloc.32.o obj32 += reloc-trampoline.32.o obj32 += slaunch_early.32.o +obj32 += tpm_early.32.o obj64 := reloc-trampoline.o @@ -31,6 +32,10 @@ $(obj)/%.32.o: $(src)/%.c FORCE $(obj)/slaunch_early.32.o: XEN_CFLAGS += -D__EARLY_SLAUNCH__ +$(obj)/tpm_early.32.o: XEN_CFLAGS += -D__EARLY_SLAUNCH__ +$(obj)/tpm_early.32.o: $(src)/../tpm.c FORCE + $(call if_changed_rule,cc_o_c) + orphan-handling-$(call ld-option,--orphan-handling=error) := --orphan-handling=error LDFLAGS_DIRECT-$(call ld-option,--warn-rwx-segments) := --no-warn-rwx-segments LDFLAGS_DIRECT += $(LDFLAGS_DIRECT-y) @@ -84,7 +89,7 @@ cmd_combine = \ --bin1 $(obj)/built-in-32.base.bin \ --bin2 $(obj)/built-in-32.offset.bin \ --map $(obj)/built-in-32.base.map \ - --exports cmdline_parse_early,reloc,reloc_trampoline32,slaunch_early_init \ + --exports cmdline_parse_early,reloc,reloc_trampoline32,slaunch_early_init,tpm_extend_mbi \ --output $@ targets += built-in-32.S diff --git a/xen/arch/x86/boot/head.S b/xen/arch/x86/boot/head.S index e522a36305..0b7903070a 100644 --- a/xen/arch/x86/boot/head.S +++ b/xen/arch/x86/boot/head.S @@ -527,6 +527,9 @@ __start: /* Store MBI address in EBX where MB2 code expects it. */ mov %eax, %ebx + /* tpm_extend_mbi(mbi/eax, slrt/edx) using fastcall. */ + call tpm_extend_mbi + /* Move magic number expected by Multiboot 2 to EAX and fall through. */ movl $MULTIBOOT2_BOOTLOADER_MAGIC, %eax diff --git a/xen/arch/x86/include/asm/slaunch.h b/xen/arch/x86/include/asm/slaunch.h index 78d3c8bf37..b9b50f20c6 100644 --- a/xen/arch/x86/include/asm/slaunch.h +++ b/xen/arch/x86/include/asm/slaunch.h @@ -10,6 +10,20 @@ #include <xen/slr_table.h> #include <xen/types.h> +#define DRTM_LOC 2 +#define DRTM_CODE_PCR 17 +#define DRTM_DATA_PCR 18 + +/* + * Secure Launch event log entry types. The TXT specification defines the base + * event value as 0x400 for DRTM values, use it regardless of the DRTM for + * consistency. + */ +#define DLE_EVTYPE_BASE 0x400 +#define DLE_EVTYPE_SLAUNCH (DLE_EVTYPE_BASE + 0x102) +#define DLE_EVTYPE_SLAUNCH_START (DLE_EVTYPE_BASE + 0x103) +#define DLE_EVTYPE_SLAUNCH_END (DLE_EVTYPE_BASE + 0x104) + extern bool slaunch_active; /* diff --git a/xen/arch/x86/include/asm/tpm.h b/xen/arch/x86/include/asm/tpm.h new file mode 100644 index 0000000000..d46eba673c --- /dev/null +++ b/xen/arch/x86/include/asm/tpm.h @@ -0,0 +1,19 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (c) 2022-2025 3mdeb Sp. z o.o. All rights reserved. + */ + +#ifndef _ASM_X86_TPM_H_ +#define _ASM_X86_TPM_H_ + +#include <xen/types.h> + +#define TPM_TIS_BASE 0xFED40000 +#define TPM_TIS_SIZE 0x00010000 + +void tpm_hash_extend(unsigned loc, unsigned pcr, const uint8_t *buf, + unsigned size, uint32_t type, const uint8_t *log_data, + unsigned log_data_size); + +#endif /* _ASM_X86_TPM_H_ */ diff --git a/xen/arch/x86/slaunch.c b/xen/arch/x86/slaunch.c index 20e277cc5c..7b13b0a852 100644 --- a/xen/arch/x86/slaunch.c +++ b/xen/arch/x86/slaunch.c @@ -13,6 +13,7 @@ #include <asm/intel_txt.h> #include <asm/page.h> #include <asm/slaunch.h> +#include <asm/tpm.h> /* * These variables are assigned to by the code near Xen's entry point. @@ -65,16 +66,20 @@ struct slr_table *__init slaunch_get_slrt(void) void __init slaunch_map_mem_regions(void) { + int rc; void *evt_log_addr; uint32_t evt_log_size; + rc = slaunch_map_l2(TPM_TIS_BASE, TPM_TIS_SIZE); + BUG_ON(rc != 0); + /* Vendor-specific part. */ txt_map_mem_regions(); find_evt_log(slaunch_get_slrt(), &evt_log_addr, &evt_log_size); if ( evt_log_addr != NULL ) { - int rc = slaunch_map_l2((uintptr_t)evt_log_addr, evt_log_size); + rc = slaunch_map_l2((uintptr_t)evt_log_addr, evt_log_size); BUG_ON(rc != 0); } } diff --git a/xen/arch/x86/tpm.c b/xen/arch/x86/tpm.c new file mode 100644 index 0000000000..8cf836d0df --- /dev/null +++ b/xen/arch/x86/tpm.c @@ -0,0 +1,437 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (c) 2022-2025 3mdeb Sp. z o.o. All rights reserved. + */ + +#include <xen/sha1.h> +#include <xen/types.h> +#include <asm/intel_txt.h> +#include <asm/slaunch.h> +#include <asm/tpm.h> + +#ifdef __EARLY_SLAUNCH__ + +#ifdef __va +#error "__va defined in non-paged mode!" +#endif + +#define __va(x) _p(x) + +/* Implementation of slaunch_get_slrt() for early TPM code. */ +static uint32_t slrt_location; +struct slr_table *slaunch_get_slrt(void) +{ + return __va(slrt_location); +} + +/* + * The code is being compiled as a standalone binary without linking to any + * other part of Xen. Providing implementation of builtin functions in this + * case is necessary if compiler chooses to not use an inline builtin. + */ +void *memcpy(void *dest, const void *src, size_t n) +{ + const uint8_t *s = src; + uint8_t *d = dest; + + while ( n-- ) + *d++ = *s++; + + return dest; +} + +#else /* __EARLY_SLAUNCH__ */ + +#include <xen/mm.h> +#include <xen/pfn.h> + +#endif /* __EARLY_SLAUNCH__ */ + +#define TPM_LOC_REG(loc, reg) (0x1000 * (loc) + (reg)) + +#define TPM_ACCESS_(x) TPM_LOC_REG(x, 0x00) +#define ACCESS_REQUEST_USE (1 << 1) +#define ACCESS_ACTIVE_LOCALITY (1 << 5) +#define TPM_INTF_CAPABILITY_(x) TPM_LOC_REG(x, 0x14) +#define INTF_VERSION_MASK 0x70000000 +#define TPM_STS_(x) TPM_LOC_REG(x, 0x18) +#define TPM_FAMILY_MASK 0x0C000000 +#define STS_DATA_AVAIL (1 << 4) +#define STS_TPM_GO (1 << 5) +#define STS_COMMAND_READY (1 << 6) +#define STS_VALID (1 << 7) +#define TPM_DATA_FIFO_(x) TPM_LOC_REG(x, 0x24) + +#define swap16(x) __builtin_bswap16(x) +#define swap32(x) __builtin_bswap32(x) +#define memcpy(d, s, n) __builtin_memcpy(d, s, n) + +static inline volatile uint32_t tis_read32(unsigned reg) +{ + return *(volatile uint32_t *)__va(TPM_TIS_BASE + reg); +} + +static inline volatile uint8_t tis_read8(unsigned reg) +{ + return *(volatile uint8_t *)__va(TPM_TIS_BASE + reg); +} + +static inline void tis_write8(unsigned reg, uint8_t val) +{ + *(volatile uint8_t *)__va(TPM_TIS_BASE + reg) = val; +} + +static inline void request_locality(unsigned loc) +{ + tis_write8(TPM_ACCESS_(loc), ACCESS_REQUEST_USE); + /* Check that locality was actually activated. */ + while ( (tis_read8(TPM_ACCESS_(loc)) & ACCESS_ACTIVE_LOCALITY) == 0 ); +} + +static inline void relinquish_locality(unsigned loc) +{ + tis_write8(TPM_ACCESS_(loc), ACCESS_ACTIVE_LOCALITY); +} + +static void send_cmd(unsigned loc, uint8_t *buf, unsigned i_size, + unsigned *o_size) +{ + /* + * Value of "data available" bit counts only when "valid" field is set as + * well. + */ + const unsigned data_avail = STS_VALID | STS_DATA_AVAIL; + + unsigned i; + + /* Make sure TPM can accept a command. */ + if ( (tis_read8(TPM_STS_(loc)) & STS_COMMAND_READY) == 0 ) + { + /* Abort current command. */ + tis_write8(TPM_STS_(loc), STS_COMMAND_READY); + /* Wait until TPM is ready for a new one. */ + while ( (tis_read8(TPM_STS_(loc)) & STS_COMMAND_READY) == 0 ); + } + + for ( i = 0; i < i_size; i++ ) + tis_write8(TPM_DATA_FIFO_(loc), buf[i]); + + tis_write8(TPM_STS_(loc), STS_TPM_GO); + + /* Wait for the first byte of response. */ + while ( (tis_read8(TPM_STS_(loc)) & data_avail) != data_avail); + + for ( i = 0; i < *o_size && tis_read8(TPM_STS_(loc)) & data_avail; i++ ) + buf[i] = tis_read8(TPM_DATA_FIFO_(loc)); + + if ( i < *o_size ) + *o_size = i; + + tis_write8(TPM_STS_(loc), STS_COMMAND_READY); +} + +static inline bool is_tpm12(void) +{ + /* + * If one of these conditions is true: + * - INTF_CAPABILITY_x.interfaceVersion is 0 (TIS <= 1.21) + * - INTF_CAPABILITY_x.interfaceVersion is 2 (TIS == 1.3) + * - STS_x.tpmFamily is 0 + * we're dealing with TPM1.2. + */ + uint32_t intf_version = tis_read32(TPM_INTF_CAPABILITY_(0)) + & INTF_VERSION_MASK; + return (intf_version == 0x00000000 || intf_version == 0x20000000 || + (tis_read32(TPM_STS_(0)) & TPM_FAMILY_MASK) == 0); +} + +/****************************** TPM1.2 specific *******************************/ +#define TPM_ORD_Extend 0x00000014 +#define TPM_ORD_SHA1Start 0x000000A0 +#define TPM_ORD_SHA1Update 0x000000A1 +#define TPM_ORD_SHA1CompleteExtend 0x000000A3 + +#define TPM_TAG_RQU_COMMAND 0x00C1 +#define TPM_TAG_RSP_COMMAND 0x00C4 + +/* All fields of following structs are big endian. */ +struct tpm_cmd_hdr { + uint16_t tag; + uint32_t paramSize; + uint32_t ordinal; +} __packed; + +struct tpm_rsp_hdr { + uint16_t tag; + uint32_t paramSize; + uint32_t returnCode; +} __packed; + +struct extend_cmd { + struct tpm_cmd_hdr h; + uint32_t pcrNum; + uint8_t inDigest[SHA1_DIGEST_SIZE]; +} __packed; + +struct extend_rsp { + struct tpm_rsp_hdr h; + uint8_t outDigest[SHA1_DIGEST_SIZE]; +} __packed; + +struct sha1_start_cmd { + struct tpm_cmd_hdr h; +} __packed; + +struct sha1_start_rsp { + struct tpm_rsp_hdr h; + uint32_t maxNumBytes; +} __packed; + +struct sha1_update_cmd { + struct tpm_cmd_hdr h; + uint32_t numBytes; /* Must be a multiple of 64 */ + uint8_t hashData[]; +} __packed; + +struct sha1_update_rsp { + struct tpm_rsp_hdr h; +} __packed; + +struct sha1_complete_extend_cmd { + struct tpm_cmd_hdr h; + uint32_t pcrNum; + uint32_t hashDataSize; /* 0-64, inclusive */ + uint8_t hashData[]; +} __packed; + +struct sha1_complete_extend_rsp { + struct tpm_rsp_hdr h; + uint8_t hashValue[SHA1_DIGEST_SIZE]; + uint8_t outDigest[SHA1_DIGEST_SIZE]; +} __packed; + +struct TPM12_PCREvent { + uint32_t PCRIndex; + uint32_t Type; + uint8_t Digest[SHA1_DIGEST_SIZE]; + uint32_t Size; + uint8_t Data[]; +}; + +struct txt_ev_log_container_12 { + char Signature[20]; /* "TXT Event Container", null-terminated */ + uint8_t Reserved[12]; + uint8_t ContainerVerMajor; + uint8_t ContainerVerMinor; + uint8_t PCREventVerMajor; + uint8_t PCREventVerMinor; + uint32_t ContainerSize; /* Allocated size */ + uint32_t PCREventsOffset; + uint32_t NextEventOffset; + struct TPM12_PCREvent PCREvents[]; +}; + +#ifdef __EARLY_SLAUNCH__ +/* + * TPM1.2 is required to support commands of up to 1101 bytes, vendors rarely + * go above that. Limit maximum size of block of data to be hashed to 1024. + */ +#define MAX_HASH_BLOCK 1024 +#define CMD_RSP_BUF_SIZE (sizeof(struct sha1_update_cmd) + MAX_HASH_BLOCK) + +union cmd_rsp { + struct sha1_start_cmd start_c; + struct sha1_start_rsp start_r; + struct sha1_update_cmd update_c; + struct sha1_update_rsp update_r; + struct sha1_complete_extend_cmd finish_c; + struct sha1_complete_extend_rsp finish_r; + uint8_t buf[CMD_RSP_BUF_SIZE]; +}; + +/* Returns true on success. */ +static bool tpm12_hash_extend(unsigned loc, const uint8_t *buf, unsigned size, + unsigned pcr, uint8_t *out_digest) +{ + union cmd_rsp cmd_rsp; + unsigned max_bytes = MAX_HASH_BLOCK; + unsigned o_size = sizeof(cmd_rsp); + bool success = false; + + request_locality(loc); + + cmd_rsp.start_c = (struct sha1_start_cmd) { + .h.tag = swap16(TPM_TAG_RQU_COMMAND), + .h.paramSize = swap32(sizeof(struct sha1_start_cmd)), + .h.ordinal = swap32(TPM_ORD_SHA1Start), + }; + + send_cmd(loc, cmd_rsp.buf, sizeof(struct sha1_start_cmd), &o_size); + if ( o_size < sizeof(struct sha1_start_rsp) ) + goto error; + + if ( max_bytes > swap32(cmd_rsp.start_r.maxNumBytes) ) + max_bytes = swap32(cmd_rsp.start_r.maxNumBytes); + + while ( size > 64 ) + { + if ( size < max_bytes ) + max_bytes = size & ~(64 - 1); + + o_size = sizeof(cmd_rsp); + + cmd_rsp.update_c = (struct sha1_update_cmd){ + .h.tag = swap16(TPM_TAG_RQU_COMMAND), + .h.paramSize = swap32(sizeof(struct sha1_update_cmd) + max_bytes), + .h.ordinal = swap32(TPM_ORD_SHA1Update), + .numBytes = swap32(max_bytes), + }; + memcpy(cmd_rsp.update_c.hashData, buf, max_bytes); + + send_cmd(loc, cmd_rsp.buf, sizeof(struct sha1_update_cmd) + max_bytes, + &o_size); + if ( o_size < sizeof(struct sha1_update_rsp) ) + goto error; + + size -= max_bytes; + buf += max_bytes; + } + + o_size = sizeof(cmd_rsp); + + cmd_rsp.finish_c = (struct sha1_complete_extend_cmd) { + .h.tag = swap16(TPM_TAG_RQU_COMMAND), + .h.paramSize = swap32(sizeof(struct sha1_complete_extend_cmd) + size), + .h.ordinal = swap32(TPM_ORD_SHA1CompleteExtend), + .pcrNum = swap32(pcr), + .hashDataSize = swap32(size), + }; + memcpy(cmd_rsp.finish_c.hashData, buf, size); + + send_cmd(loc, cmd_rsp.buf, sizeof(struct sha1_complete_extend_cmd) + size, + &o_size); + if ( o_size < sizeof(struct sha1_complete_extend_rsp) ) + goto error; + + if ( out_digest != NULL ) + memcpy(out_digest, cmd_rsp.finish_r.hashValue, SHA1_DIGEST_SIZE); + + success = true; + +error: + relinquish_locality(loc); + return success; +} + +#else + +union cmd_rsp { + struct extend_cmd extend_c; + struct extend_rsp extend_r; +}; + +/* Returns true on success. */ +static bool tpm12_hash_extend(unsigned loc, const uint8_t *buf, unsigned size, + unsigned pcr, uint8_t *out_digest) +{ + union cmd_rsp cmd_rsp; + unsigned o_size = sizeof(cmd_rsp); + + sha1_hash(buf, size, out_digest); + + request_locality(loc); + + cmd_rsp.extend_c = (struct extend_cmd) { + .h.tag = swap16(TPM_TAG_RQU_COMMAND), + .h.paramSize = swap32(sizeof(struct extend_cmd)), + .h.ordinal = swap32(TPM_ORD_Extend), + .pcrNum = swap32(pcr), + }; + + memcpy(cmd_rsp.extend_c.inDigest, out_digest, SHA1_DIGEST_SIZE); + + send_cmd(loc, (uint8_t *)&cmd_rsp, sizeof(struct extend_cmd), &o_size); + + relinquish_locality(loc); + + return (o_size >= sizeof(struct extend_rsp)); +} + +#endif /* __EARLY_SLAUNCH__ */ + +static void *create_log_event12(struct txt_ev_log_container_12 *evt_log, + uint32_t evt_log_size, uint32_t pcr, + uint32_t type, const uint8_t *data, + unsigned data_size) +{ + struct TPM12_PCREvent *new_entry; + + new_entry = (void *)(((uint8_t *)evt_log) + evt_log->NextEventOffset); + + /* + * Check if there is enough space left for new entry. + * Note: it is possible to introduce a gap in event log if entry with big + * data_size is followed by another entry with smaller data. Maybe we should + * cap the event log size in such case? + */ + if ( evt_log->NextEventOffset + sizeof(struct TPM12_PCREvent) + data_size + > evt_log_size ) + return NULL; + + evt_log->NextEventOffset += sizeof(struct TPM12_PCREvent) + data_size; + + new_entry->PCRIndex = pcr; + new_entry->Type = type; + new_entry->Size = data_size; + + if ( data && data_size > 0 ) + memcpy(new_entry->Data, data, data_size); + + return new_entry->Digest; +} + +/************************** end of TPM1.2 specific ****************************/ + +void tpm_hash_extend(unsigned loc, unsigned pcr, const uint8_t *buf, + unsigned size, uint32_t type, const uint8_t *log_data, + unsigned log_data_size) +{ + void *evt_log_addr; + uint32_t evt_log_size; + + find_evt_log(slaunch_get_slrt(), &evt_log_addr, &evt_log_size); + evt_log_addr = __va((uintptr_t)evt_log_addr); + + if ( is_tpm12() ) + { + uint8_t sha1_digest[SHA1_DIGEST_SIZE]; + + struct txt_ev_log_container_12 *evt_log = evt_log_addr; + void *entry_digest = create_log_event12(evt_log, evt_log_size, pcr, + type, log_data, log_data_size); + + /* We still need to write computed hash somewhere. */ + if ( entry_digest == NULL ) + entry_digest = sha1_digest; + + if ( !tpm12_hash_extend(loc, buf, size, pcr, entry_digest) ) + { +#ifndef __EARLY_SLAUNCH__ + printk(XENLOG_ERR "Extending PCR%u failed\n", pcr); +#endif + } + } +} + +#ifdef __EARLY_SLAUNCH__ +void tpm_extend_mbi(uint32_t *mbi, uint32_t slrt_pa) +{ + /* Need this to implement slaunch_get_slrt() for early TPM code. */ + slrt_location = slrt_pa; + + /* MBI starts with uint32_t total_size. */ + tpm_hash_extend(DRTM_LOC, DRTM_DATA_PCR, (uint8_t *)mbi, *mbi, + DLE_EVTYPE_SLAUNCH, NULL, 0); +} +#endif -- 2.49.0
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |