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
commit 4d2a7bfad5
4 changed files with 214 additions and 2 deletions

View file

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

View file

@ -5,6 +5,8 @@
* Copyright (C) 2022 Intel Corporation
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
@ -15,14 +17,146 @@
#include <linux/set_memory.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/sockptr.h>
#include <linux/tsm.h>
#include <linux/sizes.h>
#include <linux/tsm-mr.h>
#include <uapi/linux/tdx-guest.h>
#include <asm/cpu_device_id.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
* 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,
};
static const struct attribute_group *tdx_attr_groups[] = {
NULL, /* measurements */
NULL
};
static struct miscdevice tdx_misc_dev = {
.name = KBUILD_MODNAME,
.minor = MISC_DYNAMIC_MINOR,
.fops = &tdx_guest_fops,
.groups = tdx_attr_groups,
};
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))
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);
if (ret)
return ret;
goto deinit_mr;
quote_data = alloc_quote_buf();
if (!quote_data) {
@ -332,6 +476,8 @@ free_quote:
free_quote_buf(quote_data);
free_misc:
misc_deregister(&tdx_misc_dev);
deinit_mr:
tdx_mr_deinit(tdx_attr_groups[0]);
return ret;
}
@ -342,6 +488,7 @@ static void __exit tdx_guest_exit(void)
tsm_unregister(&tdx_tsm_ops);
free_quote_buf(quote_data);
misc_deregister(&tdx_misc_dev);
tdx_mr_deinit(tdx_attr_groups[0]);
}
module_exit(tdx_guest_exit);