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>
This commit is contained in:
Cedric Xing 2025-05-07 18:06:06 -07:00 committed by Dan Williams
parent 2748566da8
commit 4d2a7bfad5
4 changed files with 214 additions and 2 deletions

View File

@ -0,0 +1,63 @@
What: /sys/devices/virtual/misc/tdx_guest/measurements/MRNAME[:HASH]
Date: April, 2025
KernelVersion: v6.16
Contact: linux-coco@lists.linux.dev
Description:
Value of a TDX measurement register (MR). MRNAME and HASH above
are placeholders. The optional suffix :HASH is used for MRs
that have associated hash algorithms. See below for a complete
list of TDX MRs exposed via sysfs. Refer to Intel TDX Module
ABI Specification for the definition of TDREPORT and the full
list of TDX measurements.
Intel TDX Module ABI Specification can be found at:
https://www.intel.com/content/www/us/en/developer/tools/trust-domain-extensions/documentation.html#architecture
See also:
https://docs.kernel.org/driver-api/coco/measurement-registers.html
What: /sys/devices/virtual/misc/tdx_guest/measurements/mrconfigid
Date: April, 2025
KernelVersion: v6.16
Contact: linux-coco@lists.linux.dev
Description:
(RO) MRCONFIGID - 48-byte immutable storage typically used for
software-defined ID for non-owner-defined configuration of the
guest TD e.g., run-time or OS configuration.
What: /sys/devices/virtual/misc/tdx_guest/measurements/mrowner
Date: April, 2025
KernelVersion: v6.16
Contact: linux-coco@lists.linux.dev
Description:
(RO) MROWNER - 48-byte immutable storage typically used for
software-defined ID for the guest TDs owner.
What: /sys/devices/virtual/misc/tdx_guest/measurements/mrownerconfig
Date: April, 2025
KernelVersion: v6.16
Contact: linux-coco@lists.linux.dev
Description:
(RO) MROWNERCONFIG - 48-byte immutable storage typically used
for software-defined ID for owner-defined configuration of the
guest TD e.g., specific to the workload rather than the
run-time or OS.
What: /sys/devices/virtual/misc/tdx_guest/measurements/mrtd:sha384
Date: April, 2025
KernelVersion: v6.16
Contact: linux-coco@lists.linux.dev
Description:
(RO) MRTD - Measurement of the initial contents of the TD.
What: /sys/devices/virtual/misc/tdx_guest/measurements/rtmr[0123]:sha384
Date: April, 2025
KernelVersion: v6.16
Contact: linux-coco@lists.linux.dev
Description:
(RW) RTMR[0123] - 4 Run-Time extendable Measurement Registers.
Read from any of these returns the current value of the
corresponding RTMR. Write extends the written buffer to the
RTMR. All writes must start at offset 0 and be 48 bytes in
size. Partial writes will result in EINVAL returned by the
write() syscall.

View File

@ -26321,6 +26321,7 @@ L: x86@kernel.org
L: linux-coco@lists.linux.dev L: linux-coco@lists.linux.dev
S: Supported S: Supported
T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git x86/tdx T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git x86/tdx
F: Documentation/ABI/testing/sysfs-devices-virtual-misc-tdx_guest
F: arch/x86/boot/compressed/tdx* F: arch/x86/boot/compressed/tdx*
F: arch/x86/coco/tdx/ F: arch/x86/coco/tdx/
F: arch/x86/include/asm/shared/tdx.h F: arch/x86/include/asm/shared/tdx.h

View File

@ -2,6 +2,7 @@ config TDX_GUEST_DRIVER
tristate "TDX Guest driver" tristate "TDX Guest driver"
depends on INTEL_TDX_GUEST depends on INTEL_TDX_GUEST
select TSM_REPORTS select TSM_REPORTS
select TSM_MEASUREMENTS
help help
The driver provides userspace interface to communicate with The driver provides userspace interface to communicate with
the TDX module to request the TDX guest details like attestation the TDX module to request the TDX guest details like attestation

View File

@ -5,6 +5,8 @@
* Copyright (C) 2022 Intel Corporation * Copyright (C) 2022 Intel Corporation
*/ */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/miscdevice.h> #include <linux/miscdevice.h>
#include <linux/mm.h> #include <linux/mm.h>
@ -15,14 +17,146 @@
#include <linux/set_memory.h> #include <linux/set_memory.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/sockptr.h>
#include <linux/tsm.h> #include <linux/tsm.h>
#include <linux/sizes.h> #include <linux/tsm-mr.h>
#include <uapi/linux/tdx-guest.h> #include <uapi/linux/tdx-guest.h>
#include <asm/cpu_device_id.h> #include <asm/cpu_device_id.h>
#include <asm/tdx.h> #include <asm/tdx.h>
/* TDREPORT buffer */
static u8 *tdx_report_buf;
/* Lock to serialize TDG.MR.REPORT and TDG.MR.RTMR.EXTEND TDCALLs */
static DEFINE_MUTEX(mr_lock);
/* TDREPORT fields */
enum {
TDREPORT_reportdata = 128,
TDREPORT_tee_tcb_info = 256,
TDREPORT_tdinfo = TDREPORT_tee_tcb_info + 256,
TDREPORT_attributes = TDREPORT_tdinfo,
TDREPORT_xfam = TDREPORT_attributes + sizeof(u64),
TDREPORT_mrtd = TDREPORT_xfam + sizeof(u64),
TDREPORT_mrconfigid = TDREPORT_mrtd + SHA384_DIGEST_SIZE,
TDREPORT_mrowner = TDREPORT_mrconfigid + SHA384_DIGEST_SIZE,
TDREPORT_mrownerconfig = TDREPORT_mrowner + SHA384_DIGEST_SIZE,
TDREPORT_rtmr0 = TDREPORT_mrownerconfig + SHA384_DIGEST_SIZE,
TDREPORT_rtmr1 = TDREPORT_rtmr0 + SHA384_DIGEST_SIZE,
TDREPORT_rtmr2 = TDREPORT_rtmr1 + SHA384_DIGEST_SIZE,
TDREPORT_rtmr3 = TDREPORT_rtmr2 + SHA384_DIGEST_SIZE,
TDREPORT_servtd_hash = TDREPORT_rtmr3 + SHA384_DIGEST_SIZE,
};
static int tdx_do_report(sockptr_t data, sockptr_t tdreport)
{
scoped_cond_guard(mutex_intr, return -EINTR, &mr_lock) {
u8 *reportdata = tdx_report_buf + TDREPORT_reportdata;
int ret;
if (!sockptr_is_null(data) &&
copy_from_sockptr(reportdata, data, TDX_REPORTDATA_LEN))
return -EFAULT;
ret = tdx_mcall_get_report0(reportdata, tdx_report_buf);
if (WARN_ONCE(ret, "tdx_mcall_get_report0() failed: %d", ret))
return ret;
if (!sockptr_is_null(tdreport) &&
copy_to_sockptr(tdreport, tdx_report_buf, TDX_REPORT_LEN))
return -EFAULT;
}
return 0;
}
static int tdx_do_extend(u8 mr_ind, const u8 *data)
{
scoped_cond_guard(mutex_intr, return -EINTR, &mr_lock) {
/*
* TDX requires @extend_buf to be 64-byte aligned.
* It's safe to use REPORTDATA buffer for that purpose because
* tdx_mr_report/extend_lock() are mutually exclusive.
*/
u8 *extend_buf = tdx_report_buf + TDREPORT_reportdata;
int ret;
memcpy(extend_buf, data, SHA384_DIGEST_SIZE);
ret = tdx_mcall_extend_rtmr(mr_ind, extend_buf);
if (WARN_ONCE(ret, "tdx_mcall_extend_rtmr(%u) failed: %d", mr_ind, ret))
return ret;
}
return 0;
}
#define TDX_MR_(r) .mr_value = (void *)TDREPORT_##r, TSM_MR_(r, SHA384)
static struct tsm_measurement_register tdx_mrs[] = {
{ TDX_MR_(rtmr0) | TSM_MR_F_RTMR },
{ TDX_MR_(rtmr1) | TSM_MR_F_RTMR },
{ TDX_MR_(rtmr2) | TSM_MR_F_RTMR },
{ TDX_MR_(rtmr3) | TSM_MR_F_RTMR },
{ TDX_MR_(mrtd) },
{ TDX_MR_(mrconfigid) | TSM_MR_F_NOHASH },
{ TDX_MR_(mrowner) | TSM_MR_F_NOHASH },
{ TDX_MR_(mrownerconfig) | TSM_MR_F_NOHASH },
};
#undef TDX_MR_
static int tdx_mr_refresh(const struct tsm_measurements *tm)
{
return tdx_do_report(KERNEL_SOCKPTR(NULL), KERNEL_SOCKPTR(NULL));
}
static int tdx_mr_extend(const struct tsm_measurements *tm,
const struct tsm_measurement_register *mr,
const u8 *data)
{
return tdx_do_extend(mr - tm->mrs, data);
}
static struct tsm_measurements tdx_measurements = {
.mrs = tdx_mrs,
.nr_mrs = ARRAY_SIZE(tdx_mrs),
.refresh = tdx_mr_refresh,
.write = tdx_mr_extend,
};
static const struct attribute_group *tdx_mr_init(void)
{
const struct attribute_group *g;
int rc;
u8 *buf __free(kfree) = kzalloc(TDX_REPORT_LEN, GFP_KERNEL);
if (!buf)
return ERR_PTR(-ENOMEM);
tdx_report_buf = buf;
rc = tdx_mr_refresh(&tdx_measurements);
if (rc)
return ERR_PTR(rc);
/*
* @mr_value was initialized with the offset only, while the base
* address is being added here.
*/
for (size_t i = 0; i < ARRAY_SIZE(tdx_mrs); ++i)
*(long *)&tdx_mrs[i].mr_value += (long)buf;
g = tsm_mr_create_attribute_group(&tdx_measurements);
if (!IS_ERR(g))
tdx_report_buf = no_free_ptr(buf);
return g;
}
static void tdx_mr_deinit(const struct attribute_group *mr_grp)
{
tsm_mr_free_attribute_group(mr_grp);
kfree(tdx_report_buf);
}
/* /*
* Intel's SGX QE implementation generally uses Quote size less * Intel's SGX QE implementation generally uses Quote size less
* than 8K (2K Quote data + ~5K of certificate blob). * than 8K (2K Quote data + ~5K of certificate blob).
@ -285,10 +419,16 @@ static const struct file_operations tdx_guest_fops = {
.unlocked_ioctl = tdx_guest_ioctl, .unlocked_ioctl = tdx_guest_ioctl,
}; };
static const struct attribute_group *tdx_attr_groups[] = {
NULL, /* measurements */
NULL
};
static struct miscdevice tdx_misc_dev = { static struct miscdevice tdx_misc_dev = {
.name = KBUILD_MODNAME, .name = KBUILD_MODNAME,
.minor = MISC_DYNAMIC_MINOR, .minor = MISC_DYNAMIC_MINOR,
.fops = &tdx_guest_fops, .fops = &tdx_guest_fops,
.groups = tdx_attr_groups,
}; };
static const struct x86_cpu_id tdx_guest_ids[] = { static const struct x86_cpu_id tdx_guest_ids[] = {
@ -311,9 +451,13 @@ static int __init tdx_guest_init(void)
if (!x86_match_cpu(tdx_guest_ids)) if (!x86_match_cpu(tdx_guest_ids))
return -ENODEV; return -ENODEV;
tdx_attr_groups[0] = tdx_mr_init();
if (IS_ERR(tdx_attr_groups[0]))
return PTR_ERR(tdx_attr_groups[0]);
ret = misc_register(&tdx_misc_dev); ret = misc_register(&tdx_misc_dev);
if (ret) if (ret)
return ret; goto deinit_mr;
quote_data = alloc_quote_buf(); quote_data = alloc_quote_buf();
if (!quote_data) { if (!quote_data) {
@ -332,6 +476,8 @@ free_quote:
free_quote_buf(quote_data); free_quote_buf(quote_data);
free_misc: free_misc:
misc_deregister(&tdx_misc_dev); misc_deregister(&tdx_misc_dev);
deinit_mr:
tdx_mr_deinit(tdx_attr_groups[0]);
return ret; return ret;
} }
@ -342,6 +488,7 @@ static void __exit tdx_guest_exit(void)
tsm_unregister(&tdx_tsm_ops); tsm_unregister(&tdx_tsm_ops);
free_quote_buf(quote_data); free_quote_buf(quote_data);
misc_deregister(&tdx_misc_dev); misc_deregister(&tdx_misc_dev);
tdx_mr_deinit(tdx_attr_groups[0]);
} }
module_exit(tdx_guest_exit); module_exit(tdx_guest_exit);