diff --git a/Documentation/virt/coco/tdx-guest.rst b/Documentation/virt/coco/tdx-guest.rst new file mode 100644 index 000000000000..46e316db6bb4 --- /dev/null +++ b/Documentation/virt/coco/tdx-guest.rst @@ -0,0 +1,52 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=================================================================== +TDX Guest API Documentation +=================================================================== + +1. General description +====================== + +The TDX guest driver exposes IOCTL interfaces via the /dev/tdx-guest misc +device to allow userspace to get certain TDX guest-specific details. + +2. API description +================== + +In this section, for each supported IOCTL, the following information is +provided along with a generic description. + +:Input parameters: Parameters passed to the IOCTL and related details. +:Output: Details about output data and return value (with details about + the non common error values). + +2.1 TDX_CMD_GET_REPORT0 +----------------------- + +:Input parameters: struct tdx_report_req +:Output: Upon successful execution, TDREPORT data is copied to + tdx_report_req.tdreport and return 0. Return -EINVAL for invalid + operands, -EIO on TDCALL failure or standard error number on other + common failures. + +The TDX_CMD_GET_REPORT0 IOCTL can be used by the attestation software to get +the TDREPORT0 (a.k.a. TDREPORT subtype 0) from the TDX module using +TDCALL[TDG.MR.REPORT]. + +A subtype index is added at the end of this IOCTL CMD to uniquely identify the +subtype-specific TDREPORT request. Although the subtype option is mentioned in +the TDX Module v1.0 specification, section titled "TDG.MR.REPORT", it is not +currently used, and it expects this value to be 0. So to keep the IOCTL +implementation simple, the subtype option was not included as part of the input +ABI. However, in the future, if the TDX Module supports more than one subtype, +a new IOCTL CMD will be created to handle it. To keep the IOCTL naming +consistent, a subtype index is added as part of the IOCTL CMD. + +Reference +--------- + +TDX reference material is collected here: + +https://www.intel.com/content/www/us/en/developer/articles/technical/intel-trust-domain-extensions.html + +The driver is based on TDX module specification v1.0 and TDX GHCI specification v1.0. diff --git a/Documentation/virt/index.rst b/Documentation/virt/index.rst index 2f1cffa87b1b..56e003ff28ff 100644 --- a/Documentation/virt/index.rst +++ b/Documentation/virt/index.rst @@ -14,6 +14,7 @@ Linux Virtualization Support ne_overview acrn/index coco/sev-guest + coco/tdx-guest hyperv/index .. only:: html and subproject diff --git a/Documentation/x86/tdx.rst b/Documentation/x86/tdx.rst index b8fa4329e1a5..dc8d9fd2c3f7 100644 --- a/Documentation/x86/tdx.rst +++ b/Documentation/x86/tdx.rst @@ -210,6 +210,49 @@ converted to shared on boot. For coherent DMA allocation, the DMA buffer gets converted on the allocation. Check force_dma_unencrypted() for details. +Attestation +=========== + +Attestation is used to verify the TDX guest trustworthiness to other +entities before provisioning secrets to the guest. For example, a key +server may want to use attestation to verify that the guest is the +desired one before releasing the encryption keys to mount the encrypted +rootfs or a secondary drive. + +The TDX module records the state of the TDX guest in various stages of +the guest boot process using the build time measurement register (MRTD) +and runtime measurement registers (RTMR). Measurements related to the +guest initial configuration and firmware image are recorded in the MRTD +register. Measurements related to initial state, kernel image, firmware +image, command line options, initrd, ACPI tables, etc are recorded in +RTMR registers. For more details, as an example, please refer to TDX +Virtual Firmware design specification, section titled "TD Measurement". +At TDX guest runtime, the attestation process is used to attest to these +measurements. + +The attestation process consists of two steps: TDREPORT generation and +Quote generation. + +TDX guest uses TDCALL[TDG.MR.REPORT] to get the TDREPORT (TDREPORT_STRUCT) +from the TDX module. TDREPORT is a fixed-size data structure generated by +the TDX module which contains guest-specific information (such as build +and boot measurements), platform security version, and the MAC to protect +the integrity of the TDREPORT. A user-provided 64-Byte REPORTDATA is used +as input and included in the TDREPORT. Typically it can be some nonce +provided by attestation service so the TDREPORT can be verified uniquely. +More details about the TDREPORT can be found in Intel TDX Module +specification, section titled "TDG.MR.REPORT Leaf". + +After getting the TDREPORT, the second step of the attestation process +is to send it to the Quoting Enclave (QE) to generate the Quote. TDREPORT +by design can only be verified on the local platform as the MAC key is +bound to the platform. To support remote verification of the TDREPORT, +TDX leverages Intel SGX Quoting Enclave to verify the TDREPORT locally +and convert it to a remotely verifiable Quote. Method of sending TDREPORT +to QE is implementation specific. Attestation software can choose +whatever communication channel available (i.e. vsock or TCP/IP) to +send the TDREPORT to QE and receive the Quote. + References ========== diff --git a/arch/x86/coco/tdx/tdx.c b/arch/x86/coco/tdx/tdx.c index b8998cf0508a..cfd4c95b9f04 100644 --- a/arch/x86/coco/tdx/tdx.c +++ b/arch/x86/coco/tdx/tdx.c @@ -5,6 +5,8 @@ #define pr_fmt(fmt) "tdx: " fmt #include +#include +#include #include #include #include @@ -15,6 +17,7 @@ /* TDX module Call Leaf IDs */ #define TDX_GET_INFO 1 #define TDX_GET_VEINFO 3 +#define TDX_GET_REPORT 4 #define TDX_ACCEPT_PAGE 6 /* TDX hypercall Leaf IDs */ @@ -36,6 +39,12 @@ #define ATTR_SEPT_VE_DISABLE BIT(28) +/* TDX Module call error codes */ +#define TDCALL_RETURN_CODE(a) ((a) >> 32) +#define TDCALL_INVALID_OPERAND 0xc0000100 + +#define TDREPORT_SUBTYPE_0 0 + /* * Wrapper for standard use of __tdx_hypercall with no output aside from * return code. @@ -100,6 +109,37 @@ static inline void tdx_module_call(u64 fn, u64 rcx, u64 rdx, u64 r8, u64 r9, panic("TDCALL %lld failed (Buggy TDX module!)\n", fn); } +/** + * tdx_mcall_get_report0() - Wrapper to get TDREPORT0 (a.k.a. TDREPORT + * subtype 0) using TDG.MR.REPORT TDCALL. + * @reportdata: Address of the input buffer which contains user-defined + * REPORTDATA to be included into TDREPORT. + * @tdreport: Address of the output buffer to store TDREPORT. + * + * Refer to section titled "TDG.MR.REPORT leaf" in the TDX Module + * v1.0 specification for more information on TDG.MR.REPORT TDCALL. + * It is used in the TDX guest driver module to get the TDREPORT0. + * + * Return 0 on success, -EINVAL for invalid operands, or -EIO on + * other TDCALL failures. + */ +int tdx_mcall_get_report0(u8 *reportdata, u8 *tdreport) +{ + u64 ret; + + ret = __tdx_module_call(TDX_GET_REPORT, virt_to_phys(tdreport), + virt_to_phys(reportdata), TDREPORT_SUBTYPE_0, + 0, NULL); + if (ret) { + if (TDCALL_RETURN_CODE(ret) == TDCALL_INVALID_OPERAND) + return -EINVAL; + return -EIO; + } + + return 0; +} +EXPORT_SYMBOL_GPL(tdx_mcall_get_report0); + static void tdx_parse_tdinfo(u64 *cc_mask) { struct tdx_module_output out; diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h index 020c81a7c729..28d889c9aa16 100644 --- a/arch/x86/include/asm/tdx.h +++ b/arch/x86/include/asm/tdx.h @@ -67,6 +67,8 @@ void tdx_safe_halt(void); bool tdx_early_handle_ve(struct pt_regs *regs); +int tdx_mcall_get_report0(u8 *reportdata, u8 *tdreport); + #else static inline void tdx_early_init(void) { }; diff --git a/drivers/virt/Kconfig b/drivers/virt/Kconfig index 87ef258cec64..f79ab13a5c28 100644 --- a/drivers/virt/Kconfig +++ b/drivers/virt/Kconfig @@ -52,4 +52,6 @@ source "drivers/virt/coco/efi_secret/Kconfig" source "drivers/virt/coco/sev-guest/Kconfig" +source "drivers/virt/coco/tdx-guest/Kconfig" + endif diff --git a/drivers/virt/Makefile b/drivers/virt/Makefile index 093674e05c40..e9aa6fc96fab 100644 --- a/drivers/virt/Makefile +++ b/drivers/virt/Makefile @@ -11,3 +11,4 @@ obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves/ obj-$(CONFIG_ACRN_HSM) += acrn/ obj-$(CONFIG_EFI_SECRET) += coco/efi_secret/ obj-$(CONFIG_SEV_GUEST) += coco/sev-guest/ +obj-$(CONFIG_INTEL_TDX_GUEST) += coco/tdx-guest/ diff --git a/drivers/virt/coco/tdx-guest/Kconfig b/drivers/virt/coco/tdx-guest/Kconfig new file mode 100644 index 000000000000..14246fc2fb02 --- /dev/null +++ b/drivers/virt/coco/tdx-guest/Kconfig @@ -0,0 +1,10 @@ +config TDX_GUEST_DRIVER + tristate "TDX Guest driver" + depends on INTEL_TDX_GUEST + help + The driver provides userspace interface to communicate with + the TDX module to request the TDX guest details like attestation + report. + + To compile this driver as module, choose M here. The module will + be called tdx-guest. diff --git a/drivers/virt/coco/tdx-guest/Makefile b/drivers/virt/coco/tdx-guest/Makefile new file mode 100644 index 000000000000..775cb463f9c8 --- /dev/null +++ b/drivers/virt/coco/tdx-guest/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_TDX_GUEST_DRIVER) += tdx-guest.o diff --git a/drivers/virt/coco/tdx-guest/tdx-guest.c b/drivers/virt/coco/tdx-guest/tdx-guest.c new file mode 100644 index 000000000000..5e44a0fa69bd --- /dev/null +++ b/drivers/virt/coco/tdx-guest/tdx-guest.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TDX guest user interface driver + * + * Copyright (C) 2022 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +static long tdx_get_report0(struct tdx_report_req __user *req) +{ + u8 *reportdata, *tdreport; + long ret; + + reportdata = kmalloc(TDX_REPORTDATA_LEN, GFP_KERNEL); + if (!reportdata) + return -ENOMEM; + + tdreport = kzalloc(TDX_REPORT_LEN, GFP_KERNEL); + if (!tdreport) { + ret = -ENOMEM; + goto out; + } + + if (copy_from_user(reportdata, req->reportdata, TDX_REPORTDATA_LEN)) { + ret = -EFAULT; + goto out; + } + + /* Generate TDREPORT0 using "TDG.MR.REPORT" TDCALL */ + ret = tdx_mcall_get_report0(reportdata, tdreport); + if (ret) + goto out; + + if (copy_to_user(req->tdreport, tdreport, TDX_REPORT_LEN)) + ret = -EFAULT; + +out: + kfree(reportdata); + kfree(tdreport); + + return ret; +} + +static long tdx_guest_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + switch (cmd) { + case TDX_CMD_GET_REPORT0: + return tdx_get_report0((struct tdx_report_req __user *)arg); + default: + return -ENOTTY; + } +} + +static const struct file_operations tdx_guest_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = tdx_guest_ioctl, + .llseek = no_llseek, +}; + +static struct miscdevice tdx_misc_dev = { + .name = KBUILD_MODNAME, + .minor = MISC_DYNAMIC_MINOR, + .fops = &tdx_guest_fops, +}; + +static const struct x86_cpu_id tdx_guest_ids[] = { + X86_MATCH_FEATURE(X86_FEATURE_TDX_GUEST, NULL), + {} +}; +MODULE_DEVICE_TABLE(x86cpu, tdx_guest_ids); + +static int __init tdx_guest_init(void) +{ + if (!x86_match_cpu(tdx_guest_ids)) + return -ENODEV; + + return misc_register(&tdx_misc_dev); +} +module_init(tdx_guest_init); + +static void __exit tdx_guest_exit(void) +{ + misc_deregister(&tdx_misc_dev); +} +module_exit(tdx_guest_exit); + +MODULE_AUTHOR("Kuppuswamy Sathyanarayanan "); +MODULE_DESCRIPTION("TDX Guest Driver"); +MODULE_LICENSE("GPL"); diff --git a/include/uapi/linux/tdx-guest.h b/include/uapi/linux/tdx-guest.h new file mode 100644 index 000000000000..a6a2098c08ff --- /dev/null +++ b/include/uapi/linux/tdx-guest.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Userspace interface for TDX guest driver + * + * Copyright (C) 2022 Intel Corporation + */ + +#ifndef _UAPI_LINUX_TDX_GUEST_H_ +#define _UAPI_LINUX_TDX_GUEST_H_ + +#include +#include + +/* Length of the REPORTDATA used in TDG.MR.REPORT TDCALL */ +#define TDX_REPORTDATA_LEN 64 + +/* Length of TDREPORT used in TDG.MR.REPORT TDCALL */ +#define TDX_REPORT_LEN 1024 + +/** + * struct tdx_report_req - Request struct for TDX_CMD_GET_REPORT0 IOCTL. + * + * @reportdata: User buffer with REPORTDATA to be included into TDREPORT. + * Typically it can be some nonce provided by attestation + * service, so the generated TDREPORT can be uniquely verified. + * @tdreport: User buffer to store TDREPORT output from TDCALL[TDG.MR.REPORT]. + */ +struct tdx_report_req { + __u8 reportdata[TDX_REPORTDATA_LEN]; + __u8 tdreport[TDX_REPORT_LEN]; +}; + +/* + * TDX_CMD_GET_REPORT0 - Get TDREPORT0 (a.k.a. TDREPORT subtype 0) using + * TDCALL[TDG.MR.REPORT] + * + * Return 0 on success, -EIO on TDCALL execution failure, and + * standard errno on other general error cases. + */ +#define TDX_CMD_GET_REPORT0 _IOWR('T', 1, struct tdx_report_req) + +#endif /* _UAPI_LINUX_TDX_GUEST_H_ */ diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index f07aef7c592c..bc63c69e4a5e 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -74,6 +74,7 @@ TARGETS += sync TARGETS += syscall_user_dispatch TARGETS += sysctl TARGETS += tc-testing +TARGETS += tdx TARGETS += timens ifneq (1, $(quicktest)) TARGETS += timers diff --git a/tools/testing/selftests/tdx/Makefile b/tools/testing/selftests/tdx/Makefile new file mode 100644 index 000000000000..8dd43517cd55 --- /dev/null +++ b/tools/testing/selftests/tdx/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 + +CFLAGS += -O3 -Wl,-no-as-needed -Wall -static + +TEST_GEN_PROGS := tdx_guest_test + +include ../lib.mk diff --git a/tools/testing/selftests/tdx/config b/tools/testing/selftests/tdx/config new file mode 100644 index 000000000000..aa1edc829ab6 --- /dev/null +++ b/tools/testing/selftests/tdx/config @@ -0,0 +1 @@ +CONFIG_TDX_GUEST_DRIVER=y diff --git a/tools/testing/selftests/tdx/tdx_guest_test.c b/tools/testing/selftests/tdx/tdx_guest_test.c new file mode 100644 index 000000000000..2a2afd856798 --- /dev/null +++ b/tools/testing/selftests/tdx/tdx_guest_test.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test TDX guest features + * + * Copyright (C) 2022 Intel Corporation. + * + * Author: Kuppuswamy Sathyanarayanan + */ + +#include + +#include +#include + +#include "../kselftest_harness.h" +#include "../../../../include/uapi/linux/tdx-guest.h" + +#define TDX_GUEST_DEVNAME "/dev/tdx_guest" +#define HEX_DUMP_SIZE 8 +#define DEBUG 0 + +/** + * struct tdreport_type - Type header of TDREPORT_STRUCT. + * @type: Type of the TDREPORT (0 - SGX, 81 - TDX, rest are reserved) + * @sub_type: Subtype of the TDREPORT (Default value is 0). + * @version: TDREPORT version (Default value is 0). + * @reserved: Added for future extension. + * + * More details can be found in TDX v1.0 module specification, sec + * titled "REPORTTYPE". + */ +struct tdreport_type { + __u8 type; + __u8 sub_type; + __u8 version; + __u8 reserved; +}; + +/** + * struct reportmac - TDX guest report data, MAC and TEE hashes. + * @type: TDREPORT type header. + * @reserved1: Reserved for future extension. + * @cpu_svn: CPU security version. + * @tee_tcb_info_hash: SHA384 hash of TEE TCB INFO. + * @tee_td_info_hash: SHA384 hash of TDINFO_STRUCT. + * @reportdata: User defined unique data passed in TDG.MR.REPORT request. + * @reserved2: Reserved for future extension. + * @mac: CPU MAC ID. + * + * It is MAC-protected and contains hashes of the remainder of the + * report structure along with user provided report data. More details can + * be found in TDX v1.0 Module specification, sec titled "REPORTMACSTRUCT" + */ +struct reportmac { + struct tdreport_type type; + __u8 reserved1[12]; + __u8 cpu_svn[16]; + __u8 tee_tcb_info_hash[48]; + __u8 tee_td_info_hash[48]; + __u8 reportdata[64]; + __u8 reserved2[32]; + __u8 mac[32]; +}; + +/** + * struct td_info - TDX guest measurements and configuration. + * @attr: TDX Guest attributes (like debug, spet_disable, etc). + * @xfam: Extended features allowed mask. + * @mrtd: Build time measurement register. + * @mrconfigid: Software-defined ID for non-owner-defined configuration + * of the guest - e.g., run-time or OS configuration. + * @mrowner: Software-defined ID for the guest owner. + * @mrownerconfig: Software-defined ID for owner-defined configuration of + * the guest - e.g., specific to the workload. + * @rtmr: Run time measurement registers. + * @reserved: Added for future extension. + * + * It contains the measurements and initial configuration of the TDX guest + * that was locked at initialization and a set of measurement registers + * that are run-time extendable. More details can be found in TDX v1.0 + * Module specification, sec titled "TDINFO_STRUCT". + */ +struct td_info { + __u8 attr[8]; + __u64 xfam; + __u64 mrtd[6]; + __u64 mrconfigid[6]; + __u64 mrowner[6]; + __u64 mrownerconfig[6]; + __u64 rtmr[24]; + __u64 reserved[14]; +}; + +/* + * struct tdreport - Output of TDCALL[TDG.MR.REPORT]. + * @reportmac: Mac protected header of size 256 bytes. + * @tee_tcb_info: Additional attestable elements in the TCB are not + * reflected in the reportmac. + * @reserved: Added for future extension. + * @tdinfo: Measurements and configuration data of size 512 bytes. + * + * More details can be found in TDX v1.0 Module specification, sec + * titled "TDREPORT_STRUCT". + */ +struct tdreport { + struct reportmac reportmac; + __u8 tee_tcb_info[239]; + __u8 reserved[17]; + struct td_info tdinfo; +}; + +static void print_array_hex(const char *title, const char *prefix_str, + const void *buf, int len) +{ + int i, j, line_len, rowsize = HEX_DUMP_SIZE; + const __u8 *ptr = buf; + + printf("\t\t%s", title); + + for (j = 0; j < len; j += rowsize) { + line_len = rowsize < (len - j) ? rowsize : (len - j); + printf("%s%.8x:", prefix_str, j); + for (i = 0; i < line_len; i++) + printf(" %.2x", ptr[j + i]); + printf("\n"); + } + + printf("\n"); +} + +TEST(verify_report) +{ + struct tdx_report_req req; + struct tdreport *tdreport; + int devfd, i; + + devfd = open(TDX_GUEST_DEVNAME, O_RDWR | O_SYNC); + ASSERT_LT(0, devfd); + + /* Generate sample report data */ + for (i = 0; i < TDX_REPORTDATA_LEN; i++) + req.reportdata[i] = i; + + /* Get TDREPORT */ + ASSERT_EQ(0, ioctl(devfd, TDX_CMD_GET_REPORT0, &req)); + + if (DEBUG) { + print_array_hex("\n\t\tTDX report data\n", "", + req.reportdata, sizeof(req.reportdata)); + + print_array_hex("\n\t\tTDX tdreport data\n", "", + req.tdreport, sizeof(req.tdreport)); + } + + /* Make sure TDREPORT data includes the REPORTDATA passed */ + tdreport = (struct tdreport *)req.tdreport; + ASSERT_EQ(0, memcmp(&tdreport->reportmac.reportdata[0], + req.reportdata, sizeof(req.reportdata))); + + ASSERT_EQ(0, close(devfd)); +} + +TEST_HARNESS_MAIN