Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

virt: tdx-guest: Expose TDX MRs as sysfs attributes

Expose the most commonly used TDX MRs (Measurement Registers) as sysfs
attributes. Use the ioctl() interface of /dev/tdx_guest to request a full
TDREPORT for access to other TD measurements.

Directory structure of TDX MRs inside a TDVM is as follows:

/sys/class/misc/tdx_guest
└── measurements
├── mrconfigid
├── mrowner
├── mrownerconfig
├── mrtd:sha384
├── rtmr0:sha384
├── rtmr1:sha384
├── rtmr2:sha384
└── rtmr3:sha384

Read the file/attribute to retrieve the current value of an MR. Write to
the file/attribute (if writable) to extend the corresponding RTMR. Refer to
Documentation/ABI/testing/sysfs-devices-virtual-misc-tdx_guest for more
information.

Signed-off-by: Cedric Xing <cedric.xing@intel.com>
Acked-by: Dionna Amalie Glaze <dionnaglaze@google.com>
[djbw: fixup exit order]
Link: https://patch.msgid.link/20250508010606.4129953-1-dan.j.williams@intel.com
Signed-off-by: Dan Williams <dan.j.williams@intel.com>

authored by

Cedric Xing and committed by
Dan Williams
4d2a7bfa 2748566d

+214 -2
+63
Documentation/ABI/testing/sysfs-devices-virtual-misc-tdx_guest
··· 1 + What: /sys/devices/virtual/misc/tdx_guest/measurements/MRNAME[:HASH] 2 + Date: April, 2025 3 + KernelVersion: v6.16 4 + Contact: linux-coco@lists.linux.dev 5 + Description: 6 + Value of a TDX measurement register (MR). MRNAME and HASH above 7 + are placeholders. The optional suffix :HASH is used for MRs 8 + that have associated hash algorithms. See below for a complete 9 + list of TDX MRs exposed via sysfs. Refer to Intel TDX Module 10 + ABI Specification for the definition of TDREPORT and the full 11 + list of TDX measurements. 12 + 13 + Intel TDX Module ABI Specification can be found at: 14 + https://www.intel.com/content/www/us/en/developer/tools/trust-domain-extensions/documentation.html#architecture 15 + 16 + See also: 17 + https://docs.kernel.org/driver-api/coco/measurement-registers.html 18 + 19 + What: /sys/devices/virtual/misc/tdx_guest/measurements/mrconfigid 20 + Date: April, 2025 21 + KernelVersion: v6.16 22 + Contact: linux-coco@lists.linux.dev 23 + Description: 24 + (RO) MRCONFIGID - 48-byte immutable storage typically used for 25 + software-defined ID for non-owner-defined configuration of the 26 + guest TD – e.g., run-time or OS configuration. 27 + 28 + What: /sys/devices/virtual/misc/tdx_guest/measurements/mrowner 29 + Date: April, 2025 30 + KernelVersion: v6.16 31 + Contact: linux-coco@lists.linux.dev 32 + Description: 33 + (RO) MROWNER - 48-byte immutable storage typically used for 34 + software-defined ID for the guest TD’s owner. 35 + 36 + What: /sys/devices/virtual/misc/tdx_guest/measurements/mrownerconfig 37 + Date: April, 2025 38 + KernelVersion: v6.16 39 + Contact: linux-coco@lists.linux.dev 40 + Description: 41 + (RO) MROWNERCONFIG - 48-byte immutable storage typically used 42 + for software-defined ID for owner-defined configuration of the 43 + guest TD – e.g., specific to the workload rather than the 44 + run-time or OS. 45 + 46 + What: /sys/devices/virtual/misc/tdx_guest/measurements/mrtd:sha384 47 + Date: April, 2025 48 + KernelVersion: v6.16 49 + Contact: linux-coco@lists.linux.dev 50 + Description: 51 + (RO) MRTD - Measurement of the initial contents of the TD. 52 + 53 + What: /sys/devices/virtual/misc/tdx_guest/measurements/rtmr[0123]:sha384 54 + Date: April, 2025 55 + KernelVersion: v6.16 56 + Contact: linux-coco@lists.linux.dev 57 + Description: 58 + (RW) RTMR[0123] - 4 Run-Time extendable Measurement Registers. 59 + Read from any of these returns the current value of the 60 + corresponding RTMR. Write extends the written buffer to the 61 + RTMR. All writes must start at offset 0 and be 48 bytes in 62 + size. Partial writes will result in EINVAL returned by the 63 + write() syscall.
+1
MAINTAINERS
··· 26321 26321 L: linux-coco@lists.linux.dev 26322 26322 S: Supported 26323 26323 T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git x86/tdx 26324 + F: Documentation/ABI/testing/sysfs-devices-virtual-misc-tdx_guest 26324 26325 F: arch/x86/boot/compressed/tdx* 26325 26326 F: arch/x86/coco/tdx/ 26326 26327 F: arch/x86/include/asm/shared/tdx.h
+1
drivers/virt/coco/tdx-guest/Kconfig
··· 2 2 tristate "TDX Guest driver" 3 3 depends on INTEL_TDX_GUEST 4 4 select TSM_REPORTS 5 + select TSM_MEASUREMENTS 5 6 help 6 7 The driver provides userspace interface to communicate with 7 8 the TDX module to request the TDX guest details like attestation
+149 -2
drivers/virt/coco/tdx-guest/tdx-guest.c
··· 5 5 * Copyright (C) 2022 Intel Corporation 6 6 */ 7 7 8 + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 9 + 8 10 #include <linux/kernel.h> 9 11 #include <linux/miscdevice.h> 10 12 #include <linux/mm.h> ··· 17 15 #include <linux/set_memory.h> 18 16 #include <linux/io.h> 19 17 #include <linux/delay.h> 18 + #include <linux/sockptr.h> 20 19 #include <linux/tsm.h> 21 - #include <linux/sizes.h> 20 + #include <linux/tsm-mr.h> 22 21 23 22 #include <uapi/linux/tdx-guest.h> 24 23 25 24 #include <asm/cpu_device_id.h> 26 25 #include <asm/tdx.h> 26 + 27 + /* TDREPORT buffer */ 28 + static u8 *tdx_report_buf; 29 + 30 + /* Lock to serialize TDG.MR.REPORT and TDG.MR.RTMR.EXTEND TDCALLs */ 31 + static DEFINE_MUTEX(mr_lock); 32 + 33 + /* TDREPORT fields */ 34 + enum { 35 + TDREPORT_reportdata = 128, 36 + TDREPORT_tee_tcb_info = 256, 37 + TDREPORT_tdinfo = TDREPORT_tee_tcb_info + 256, 38 + TDREPORT_attributes = TDREPORT_tdinfo, 39 + TDREPORT_xfam = TDREPORT_attributes + sizeof(u64), 40 + TDREPORT_mrtd = TDREPORT_xfam + sizeof(u64), 41 + TDREPORT_mrconfigid = TDREPORT_mrtd + SHA384_DIGEST_SIZE, 42 + TDREPORT_mrowner = TDREPORT_mrconfigid + SHA384_DIGEST_SIZE, 43 + TDREPORT_mrownerconfig = TDREPORT_mrowner + SHA384_DIGEST_SIZE, 44 + TDREPORT_rtmr0 = TDREPORT_mrownerconfig + SHA384_DIGEST_SIZE, 45 + TDREPORT_rtmr1 = TDREPORT_rtmr0 + SHA384_DIGEST_SIZE, 46 + TDREPORT_rtmr2 = TDREPORT_rtmr1 + SHA384_DIGEST_SIZE, 47 + TDREPORT_rtmr3 = TDREPORT_rtmr2 + SHA384_DIGEST_SIZE, 48 + TDREPORT_servtd_hash = TDREPORT_rtmr3 + SHA384_DIGEST_SIZE, 49 + }; 50 + 51 + static int tdx_do_report(sockptr_t data, sockptr_t tdreport) 52 + { 53 + scoped_cond_guard(mutex_intr, return -EINTR, &mr_lock) { 54 + u8 *reportdata = tdx_report_buf + TDREPORT_reportdata; 55 + int ret; 56 + 57 + if (!sockptr_is_null(data) && 58 + copy_from_sockptr(reportdata, data, TDX_REPORTDATA_LEN)) 59 + return -EFAULT; 60 + 61 + ret = tdx_mcall_get_report0(reportdata, tdx_report_buf); 62 + if (WARN_ONCE(ret, "tdx_mcall_get_report0() failed: %d", ret)) 63 + return ret; 64 + 65 + if (!sockptr_is_null(tdreport) && 66 + copy_to_sockptr(tdreport, tdx_report_buf, TDX_REPORT_LEN)) 67 + return -EFAULT; 68 + } 69 + return 0; 70 + } 71 + 72 + static int tdx_do_extend(u8 mr_ind, const u8 *data) 73 + { 74 + scoped_cond_guard(mutex_intr, return -EINTR, &mr_lock) { 75 + /* 76 + * TDX requires @extend_buf to be 64-byte aligned. 77 + * It's safe to use REPORTDATA buffer for that purpose because 78 + * tdx_mr_report/extend_lock() are mutually exclusive. 79 + */ 80 + u8 *extend_buf = tdx_report_buf + TDREPORT_reportdata; 81 + int ret; 82 + 83 + memcpy(extend_buf, data, SHA384_DIGEST_SIZE); 84 + 85 + ret = tdx_mcall_extend_rtmr(mr_ind, extend_buf); 86 + if (WARN_ONCE(ret, "tdx_mcall_extend_rtmr(%u) failed: %d", mr_ind, ret)) 87 + return ret; 88 + } 89 + return 0; 90 + } 91 + 92 + #define TDX_MR_(r) .mr_value = (void *)TDREPORT_##r, TSM_MR_(r, SHA384) 93 + static struct tsm_measurement_register tdx_mrs[] = { 94 + { TDX_MR_(rtmr0) | TSM_MR_F_RTMR }, 95 + { TDX_MR_(rtmr1) | TSM_MR_F_RTMR }, 96 + { TDX_MR_(rtmr2) | TSM_MR_F_RTMR }, 97 + { TDX_MR_(rtmr3) | TSM_MR_F_RTMR }, 98 + { TDX_MR_(mrtd) }, 99 + { TDX_MR_(mrconfigid) | TSM_MR_F_NOHASH }, 100 + { TDX_MR_(mrowner) | TSM_MR_F_NOHASH }, 101 + { TDX_MR_(mrownerconfig) | TSM_MR_F_NOHASH }, 102 + }; 103 + #undef TDX_MR_ 104 + 105 + static int tdx_mr_refresh(const struct tsm_measurements *tm) 106 + { 107 + return tdx_do_report(KERNEL_SOCKPTR(NULL), KERNEL_SOCKPTR(NULL)); 108 + } 109 + 110 + static int tdx_mr_extend(const struct tsm_measurements *tm, 111 + const struct tsm_measurement_register *mr, 112 + const u8 *data) 113 + { 114 + return tdx_do_extend(mr - tm->mrs, data); 115 + } 116 + 117 + static struct tsm_measurements tdx_measurements = { 118 + .mrs = tdx_mrs, 119 + .nr_mrs = ARRAY_SIZE(tdx_mrs), 120 + .refresh = tdx_mr_refresh, 121 + .write = tdx_mr_extend, 122 + }; 123 + 124 + static const struct attribute_group *tdx_mr_init(void) 125 + { 126 + const struct attribute_group *g; 127 + int rc; 128 + 129 + u8 *buf __free(kfree) = kzalloc(TDX_REPORT_LEN, GFP_KERNEL); 130 + if (!buf) 131 + return ERR_PTR(-ENOMEM); 132 + 133 + tdx_report_buf = buf; 134 + rc = tdx_mr_refresh(&tdx_measurements); 135 + if (rc) 136 + return ERR_PTR(rc); 137 + 138 + /* 139 + * @mr_value was initialized with the offset only, while the base 140 + * address is being added here. 141 + */ 142 + for (size_t i = 0; i < ARRAY_SIZE(tdx_mrs); ++i) 143 + *(long *)&tdx_mrs[i].mr_value += (long)buf; 144 + 145 + g = tsm_mr_create_attribute_group(&tdx_measurements); 146 + if (!IS_ERR(g)) 147 + tdx_report_buf = no_free_ptr(buf); 148 + 149 + return g; 150 + } 151 + 152 + static void tdx_mr_deinit(const struct attribute_group *mr_grp) 153 + { 154 + tsm_mr_free_attribute_group(mr_grp); 155 + kfree(tdx_report_buf); 156 + } 27 157 28 158 /* 29 159 * Intel's SGX QE implementation generally uses Quote size less ··· 419 285 .unlocked_ioctl = tdx_guest_ioctl, 420 286 }; 421 287 288 + static const struct attribute_group *tdx_attr_groups[] = { 289 + NULL, /* measurements */ 290 + NULL 291 + }; 292 + 422 293 static struct miscdevice tdx_misc_dev = { 423 294 .name = KBUILD_MODNAME, 424 295 .minor = MISC_DYNAMIC_MINOR, 425 296 .fops = &tdx_guest_fops, 297 + .groups = tdx_attr_groups, 426 298 }; 427 299 428 300 static const struct x86_cpu_id tdx_guest_ids[] = { ··· 451 311 if (!x86_match_cpu(tdx_guest_ids)) 452 312 return -ENODEV; 453 313 314 + tdx_attr_groups[0] = tdx_mr_init(); 315 + if (IS_ERR(tdx_attr_groups[0])) 316 + return PTR_ERR(tdx_attr_groups[0]); 317 + 454 318 ret = misc_register(&tdx_misc_dev); 455 319 if (ret) 456 - return ret; 320 + goto deinit_mr; 457 321 458 322 quote_data = alloc_quote_buf(); 459 323 if (!quote_data) { ··· 476 332 free_quote_buf(quote_data); 477 333 free_misc: 478 334 misc_deregister(&tdx_misc_dev); 335 + deinit_mr: 336 + tdx_mr_deinit(tdx_attr_groups[0]); 479 337 480 338 return ret; 481 339 } ··· 488 342 tsm_unregister(&tdx_tsm_ops); 489 343 free_quote_buf(quote_data); 490 344 misc_deregister(&tdx_misc_dev); 345 + tdx_mr_deinit(tdx_attr_groups[0]); 491 346 } 492 347 module_exit(tdx_guest_exit); 493 348