Merge tag 'tsm-for-6.16' of git://git.kernel.org/pub/scm/linux/kernel/git/devsec/tsm

Pull trusted security manager (TSM) updates from Dan Williams:

 - Add a general sysfs scheme for publishing "Measurement" values
   provided by the architecture's TEE Security Manager. Use it to
   publish TDX "Runtime Measurement Registers" ("RTMRs") that either
   maintain a hash of stored values (similar to a TPM PCR) or provide
   statically provisioned data. These measurements are validated by a
   relying party.

 - Reorganize the drivers/virt/coco/ directory for "host" and "guest"
   shared infrastructure.

 - Fix a configfs-tsm-report unregister bug

 - With CONFIG_TSM_MEASUREMENTS joining CONFIG_TSM_REPORTS and in
   anticipation of more shared "TSM" infrastructure arriving, rename the
   maintainer entry to "TRUSTED SECURITY MODULE (TSM) INFRASTRUCTURE".

* tag 'tsm-for-6.16' of git://git.kernel.org/pub/scm/linux/kernel/git/devsec/tsm:
  tsm-mr: Fix init breakage after bin_attrs constification by scoping non-const pointers to init phase
  sample/tsm-mr: Fix missing static for sample_report
  virt: tdx-guest: Transition to scoped_cond_guard for mutex operations
  virt: tdx-guest: Refactor and streamline TDREPORT generation
  virt: tdx-guest: Expose TDX MRs as sysfs attributes
  x86/tdx: tdx_mcall_get_report0: Return -EBUSY on TDCALL_OPERAND_BUSY error
  x86/tdx: Add tdx_mcall_extend_rtmr() interface
  tsm-mr: Add tsm-mr sample code
  tsm-mr: Add TVM Measurement Register support
  configfs-tsm-report: Fix NULL dereference of tsm_ops
  coco/guest: Move shared guest CC infrastructure to drivers/virt/coco/guest/
  configfs-tsm: Namespace TSM report symbols
This commit is contained in:
Linus Torvalds
2025-05-29 21:21:11 -07:00
26 changed files with 974 additions and 137 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

@@ -0,0 +1,12 @@
.. SPDX-License-Identifier: GPL-2.0
======================
Confidential Computing
======================
.. toctree::
:maxdepth: 1
measurement-registers
.. only:: subproject and html

View File

@@ -0,0 +1,12 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: <isonum.txt>
=====================
Measurement Registers
=====================
.. kernel-doc:: include/linux/tsm-mr.h
:internal:
.. kernel-doc:: drivers/virt/coco/guest/tsm-mr.c
:export:

View File

@@ -81,6 +81,7 @@ Subsystem-specific APIs
acpi/index
backlight/lp855x-driver.rst
clk
coco/index
console
crypto/index
dmaengine/index

View File

@@ -24981,13 +24981,15 @@ M: David Lechner <dlechner@baylibre.com>
S: Maintained
F: Documentation/devicetree/bindings/trigger-source/pwm-trigger.yaml
TRUSTED SECURITY MODULE (TSM) ATTESTATION REPORTS
TRUSTED SECURITY MODULE (TSM) INFRASTRUCTURE
M: Dan Williams <dan.j.williams@intel.com>
L: linux-coco@lists.linux.dev
S: Maintained
F: Documentation/ABI/testing/configfs-tsm
F: drivers/virt/coco/tsm.c
F: include/linux/tsm.h
F: Documentation/ABI/testing/configfs-tsm-report
F: Documentation/driver-api/coco/
F: drivers/virt/coco/guest/
F: include/linux/tsm*.h
F: samples/tsm-mr/
TRUSTED SERVICES TEE DRIVER
M: Balint Dobszay <balint.dobszay@arm.com>
@@ -26673,6 +26675,7 @@ L: x86@kernel.org
L: linux-coco@lists.linux.dev
S: Supported
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/coco/tdx/
F: arch/x86/include/asm/shared/tdx.h

View File

@@ -36,6 +36,7 @@
/* TDX Module call error codes */
#define TDCALL_RETURN_CODE(a) ((a) >> 32)
#define TDCALL_INVALID_OPERAND 0xc0000100
#define TDCALL_OPERAND_BUSY 0x80000200
#define TDREPORT_SUBTYPE_0 0
@@ -109,12 +110,13 @@ static inline u64 tdg_vm_wr(u64 field, u64 value, u64 mask)
* 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.
* 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.
* Return 0 on success, -ENXIO for invalid operands, -EBUSY for busy operation,
* or -EIO on other TDCALL failures.
*/
int tdx_mcall_get_report0(u8 *reportdata, u8 *tdreport)
{
@@ -128,7 +130,9 @@ int tdx_mcall_get_report0(u8 *reportdata, u8 *tdreport)
ret = __tdcall(TDG_MR_REPORT, &args);
if (ret) {
if (TDCALL_RETURN_CODE(ret) == TDCALL_INVALID_OPERAND)
return -EINVAL;
return -ENXIO;
else if (TDCALL_RETURN_CODE(ret) == TDCALL_OPERAND_BUSY)
return -EBUSY;
return -EIO;
}
@@ -136,6 +140,42 @@ int tdx_mcall_get_report0(u8 *reportdata, u8 *tdreport)
}
EXPORT_SYMBOL_GPL(tdx_mcall_get_report0);
/**
* tdx_mcall_extend_rtmr() - Wrapper to extend RTMR registers using
* TDG.MR.RTMR.EXTEND TDCALL.
* @index: Index of RTMR register to be extended.
* @data: Address of the input buffer with RTMR register extend data.
*
* Refer to section titled "TDG.MR.RTMR.EXTEND leaf" in the TDX Module v1.0
* specification for more information on TDG.MR.RTMR.EXTEND TDCALL.
*
* It is used in the TDX guest driver module to allow user to extend the RTMR
* registers.
*
* Return 0 on success, -ENXIO for invalid operands, -EBUSY for busy operation,
* or -EIO on other TDCALL failures.
*/
int tdx_mcall_extend_rtmr(u8 index, u8 *data)
{
struct tdx_module_args args = {
.rcx = virt_to_phys(data),
.rdx = index,
};
u64 ret;
ret = __tdcall(TDG_MR_RTMR_EXTEND, &args);
if (ret) {
if (TDCALL_RETURN_CODE(ret) == TDCALL_INVALID_OPERAND)
return -ENXIO;
if (TDCALL_RETURN_CODE(ret) == TDCALL_OPERAND_BUSY)
return -EBUSY;
return -EIO;
}
return 0;
}
EXPORT_SYMBOL_GPL(tdx_mcall_extend_rtmr);
/**
* tdx_hcall_get_quote() - Wrapper to request TD Quote using GetQuote
* hypercall.

View File

@@ -13,6 +13,7 @@
/* TDX module Call Leaf IDs */
#define TDG_VP_VMCALL 0
#define TDG_VP_INFO 1
#define TDG_MR_RTMR_EXTEND 2
#define TDG_VP_VEINFO_GET 3
#define TDG_MR_REPORT 4
#define TDG_MEM_PAGE_ACCEPT 6

View File

@@ -68,6 +68,8 @@ bool tdx_early_handle_ve(struct pt_regs *regs);
int tdx_mcall_get_report0(u8 *reportdata, u8 *tdreport);
int tdx_mcall_extend_rtmr(u8 index, u8 *data);
u64 tdx_hcall_get_quote(u8 *buf, size_t size);
void __init tdx_dump_attributes(u64 td_attr);

View File

@@ -3,10 +3,6 @@
# Confidential computing related collateral
#
config TSM_REPORTS
select CONFIGFS_FS
tristate
source "drivers/virt/coco/efi_secret/Kconfig"
source "drivers/virt/coco/pkvm-guest/Kconfig"
@@ -16,3 +12,5 @@ source "drivers/virt/coco/sev-guest/Kconfig"
source "drivers/virt/coco/tdx-guest/Kconfig"
source "drivers/virt/coco/arm-cca-guest/Kconfig"
source "drivers/virt/coco/guest/Kconfig"

View File

@@ -2,9 +2,9 @@
#
# Confidential computing related collateral
#
obj-$(CONFIG_TSM_REPORTS) += tsm.o
obj-$(CONFIG_EFI_SECRET) += efi_secret/
obj-$(CONFIG_ARM_PKVM_GUEST) += pkvm-guest/
obj-$(CONFIG_SEV_GUEST) += sev-guest/
obj-$(CONFIG_INTEL_TDX_GUEST) += tdx-guest/
obj-$(CONFIG_ARM_CCA_GUEST) += arm-cca-guest/
obj-$(CONFIG_TSM_GUEST) += guest/

View File

@@ -96,7 +96,7 @@ static int arm_cca_report_new(struct tsm_report *report, void *data)
struct arm_cca_token_info info;
void *buf;
u8 *token __free(kvfree) = NULL;
struct tsm_desc *desc = &report->desc;
struct tsm_report_desc *desc = &report->desc;
if (desc->inblob_len < 32 || desc->inblob_len > 64)
return -EINVAL;
@@ -181,7 +181,7 @@ static int arm_cca_report_new(struct tsm_report *report, void *data)
return ret;
}
static const struct tsm_ops arm_cca_tsm_ops = {
static const struct tsm_report_ops arm_cca_tsm_ops = {
.name = KBUILD_MODNAME,
.report_new = arm_cca_report_new,
};
@@ -202,7 +202,7 @@ static int __init arm_cca_guest_init(void)
if (!is_realm_world())
return -ENODEV;
ret = tsm_register(&arm_cca_tsm_ops, NULL);
ret = tsm_report_register(&arm_cca_tsm_ops, NULL);
if (ret < 0)
pr_err("Error %d registering with TSM\n", ret);
@@ -216,7 +216,7 @@ module_init(arm_cca_guest_init);
*/
static void __exit arm_cca_guest_exit(void)
{
tsm_unregister(&arm_cca_tsm_ops);
tsm_report_unregister(&arm_cca_tsm_ops);
}
module_exit(arm_cca_guest_exit);

View File

@@ -0,0 +1,17 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Confidential computing shared guest collateral
#
config TSM_GUEST
bool
config TSM_REPORTS
select TSM_GUEST
select CONFIGFS_FS
tristate
config TSM_MEASUREMENTS
select TSM_GUEST
select CRYPTO_HASH_INFO
select CRYPTO
bool

View File

@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_TSM_REPORTS) += tsm_report.o
tsm_report-y := report.o
obj-$(CONFIG_TSM_MEASUREMENTS) += tsm-mr.o

View File

@@ -13,8 +13,9 @@
#include <linux/configfs.h>
static struct tsm_provider {
const struct tsm_ops *ops;
const struct tsm_report_ops *ops;
void *data;
atomic_t count;
} provider;
static DECLARE_RWSEM(tsm_rwsem);
@@ -92,16 +93,19 @@ static ssize_t tsm_report_privlevel_store(struct config_item *cfg,
if (rc)
return rc;
guard(rwsem_write)(&tsm_rwsem);
if (!provider.ops)
return -ENXIO;
/*
* The valid privilege levels that a TSM might accept, if it accepts a
* privilege level setting at all, are a max of TSM_PRIVLEVEL_MAX (see
* SEV-SNP GHCB) and a minimum of a TSM selected floor value no less
* than 0.
*/
if (provider.ops->privlevel_floor > val || val > TSM_PRIVLEVEL_MAX)
if (provider.ops->privlevel_floor > val || val > TSM_REPORT_PRIVLEVEL_MAX)
return -EINVAL;
guard(rwsem_write)(&tsm_rwsem);
rc = try_advance_write_generation(report);
if (rc)
return rc;
@@ -115,6 +119,10 @@ static ssize_t tsm_report_privlevel_floor_show(struct config_item *cfg,
char *buf)
{
guard(rwsem_read)(&tsm_rwsem);
if (!provider.ops)
return -ENXIO;
return sysfs_emit(buf, "%u\n", provider.ops->privlevel_floor);
}
CONFIGFS_ATTR_RO(tsm_report_, privlevel_floor);
@@ -202,7 +210,7 @@ static ssize_t tsm_report_inblob_write(struct config_item *cfg,
memcpy(report->desc.inblob, buf, count);
return count;
}
CONFIGFS_BIN_ATTR_WO(tsm_report_, inblob, NULL, TSM_INBLOB_MAX);
CONFIGFS_BIN_ATTR_WO(tsm_report_, inblob, NULL, TSM_REPORT_INBLOB_MAX);
static ssize_t tsm_report_generation_show(struct config_item *cfg, char *buf)
{
@@ -217,6 +225,9 @@ CONFIGFS_ATTR_RO(tsm_report_, generation);
static ssize_t tsm_report_provider_show(struct config_item *cfg, char *buf)
{
guard(rwsem_read)(&tsm_rwsem);
if (!provider.ops)
return -ENXIO;
return sysfs_emit(buf, "%s\n", provider.ops->name);
}
CONFIGFS_ATTR_RO(tsm_report_, provider);
@@ -272,7 +283,7 @@ static ssize_t tsm_report_read(struct tsm_report *report, void *buf,
size_t count, enum tsm_data_select select)
{
struct tsm_report_state *state = to_state(report);
const struct tsm_ops *ops;
const struct tsm_report_ops *ops;
ssize_t rc;
/* try to read from the existing report if present and valid... */
@@ -284,7 +295,7 @@ static ssize_t tsm_report_read(struct tsm_report *report, void *buf,
guard(rwsem_write)(&tsm_rwsem);
ops = provider.ops;
if (!ops)
return -ENOTTY;
return -ENXIO;
if (!report->desc.inblob_len)
return -EINVAL;
@@ -314,7 +325,7 @@ static ssize_t tsm_report_outblob_read(struct config_item *cfg, void *buf,
return tsm_report_read(report, buf, count, TSM_REPORT);
}
CONFIGFS_BIN_ATTR_RO(tsm_report_, outblob, NULL, TSM_OUTBLOB_MAX);
CONFIGFS_BIN_ATTR_RO(tsm_report_, outblob, NULL, TSM_REPORT_OUTBLOB_MAX);
static ssize_t tsm_report_auxblob_read(struct config_item *cfg, void *buf,
size_t count)
@@ -323,7 +334,7 @@ static ssize_t tsm_report_auxblob_read(struct config_item *cfg, void *buf,
return tsm_report_read(report, buf, count, TSM_CERTS);
}
CONFIGFS_BIN_ATTR_RO(tsm_report_, auxblob, NULL, TSM_OUTBLOB_MAX);
CONFIGFS_BIN_ATTR_RO(tsm_report_, auxblob, NULL, TSM_REPORT_OUTBLOB_MAX);
static ssize_t tsm_report_manifestblob_read(struct config_item *cfg, void *buf,
size_t count)
@@ -332,7 +343,7 @@ static ssize_t tsm_report_manifestblob_read(struct config_item *cfg, void *buf,
return tsm_report_read(report, buf, count, TSM_MANIFEST);
}
CONFIGFS_BIN_ATTR_RO(tsm_report_, manifestblob, NULL, TSM_OUTBLOB_MAX);
CONFIGFS_BIN_ATTR_RO(tsm_report_, manifestblob, NULL, TSM_REPORT_OUTBLOB_MAX);
static struct configfs_attribute *tsm_report_attrs[] = {
[TSM_REPORT_GENERATION] = &tsm_report_attr_generation,
@@ -421,12 +432,20 @@ static struct config_item *tsm_report_make_item(struct config_group *group,
if (!state)
return ERR_PTR(-ENOMEM);
atomic_inc(&provider.count);
config_item_init_type_name(&state->cfg, name, &tsm_report_type);
return &state->cfg;
}
static void tsm_report_drop_item(struct config_group *group, struct config_item *item)
{
config_item_put(item);
atomic_dec(&provider.count);
}
static struct configfs_group_operations tsm_report_group_ops = {
.make_item = tsm_report_make_item,
.drop_item = tsm_report_drop_item,
};
static const struct config_item_type tsm_reports_type = {
@@ -448,9 +467,9 @@ static struct configfs_subsystem tsm_configfs = {
.su_mutex = __MUTEX_INITIALIZER(tsm_configfs.su_mutex),
};
int tsm_register(const struct tsm_ops *ops, void *priv)
int tsm_report_register(const struct tsm_report_ops *ops, void *priv)
{
const struct tsm_ops *conflict;
const struct tsm_report_ops *conflict;
guard(rwsem_write)(&tsm_rwsem);
conflict = provider.ops;
@@ -459,26 +478,34 @@ int tsm_register(const struct tsm_ops *ops, void *priv)
return -EBUSY;
}
if (atomic_read(&provider.count)) {
pr_err("configfs/tsm/report not empty\n");
return -EBUSY;
}
provider.ops = ops;
provider.data = priv;
return 0;
}
EXPORT_SYMBOL_GPL(tsm_register);
EXPORT_SYMBOL_GPL(tsm_report_register);
int tsm_unregister(const struct tsm_ops *ops)
int tsm_report_unregister(const struct tsm_report_ops *ops)
{
guard(rwsem_write)(&tsm_rwsem);
if (ops != provider.ops)
return -EBUSY;
if (atomic_read(&provider.count))
pr_warn("\"%s\" unregistered with items present in configfs/tsm/report\n",
provider.ops->name);
provider.ops = NULL;
provider.data = NULL;
return 0;
}
EXPORT_SYMBOL_GPL(tsm_unregister);
EXPORT_SYMBOL_GPL(tsm_report_unregister);
static struct config_group *tsm_report_group;
static int __init tsm_init(void)
static int __init tsm_report_init(void)
{
struct config_group *root = &tsm_configfs.su_group;
struct config_group *tsm;
@@ -499,14 +526,14 @@ static int __init tsm_init(void)
return 0;
}
module_init(tsm_init);
module_init(tsm_report_init);
static void __exit tsm_exit(void)
static void __exit tsm_report_exit(void)
{
configfs_unregister_default_group(tsm_report_group);
configfs_unregister_subsystem(&tsm_configfs);
}
module_exit(tsm_exit);
module_exit(tsm_report_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Provide Trusted Security Module attestation reports via configfs");

View File

@@ -0,0 +1,251 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2024-2025 Intel Corporation. All rights reserved. */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#define CREATE_TRACE_POINTS
#include <trace/events/tsm_mr.h>
/*
* struct tm_context - contains everything necessary to implement sysfs
* attributes for MRs.
* @rwsem: protects the MR cache from concurrent access.
* @agrp: contains all MR attributes created by tsm_mr_create_attribute_group().
* @tm: input to tsm_mr_create_attribute_group() containing MR definitions/ops.
* @in_sync: %true if MR cache is up-to-date.
* @mrs: array of &struct bin_attribute, one for each MR.
*
* This internal structure contains everything needed to implement
* tm_digest_read() and tm_digest_write().
*
* Given tm->refresh() is potentially expensive, tm_digest_read() caches MR
* values and calls tm->refresh() only when necessary. Only live MRs (i.e., with
* %TSM_MR_F_LIVE set) can trigger tm->refresh(), while others are assumed to
* retain their values from the last tm->write(). @in_sync tracks if there have
* been tm->write() calls since the last tm->refresh(). That is, tm->refresh()
* will be called only when a live MR is being read and the cache is stale
* (@in_sync is %false).
*
* tm_digest_write() sets @in_sync to %false and calls tm->write(), whose
* semantics is arch and MR specific. Most (if not all) writable MRs support the
* extension semantics (i.e., tm->write() extends the input buffer into the MR).
*/
struct tm_context {
struct rw_semaphore rwsem;
struct attribute_group agrp;
const struct tsm_measurements *tm;
bool in_sync;
struct bin_attribute mrs[];
};
static ssize_t tm_digest_read(struct file *filp, struct kobject *kobj,
const struct bin_attribute *attr, char *buffer,
loff_t off, size_t count)
{
struct tm_context *ctx;
const struct tsm_measurement_register *mr;
int rc;
ctx = attr->private;
rc = down_read_interruptible(&ctx->rwsem);
if (rc)
return rc;
mr = &ctx->tm->mrs[attr - ctx->mrs];
/*
* @ctx->in_sync indicates if the MR cache is stale. It is a global
* instead of a per-MR flag for simplicity, as most (if not all) archs
* allow reading all MRs in oneshot.
*
* ctx->refresh() is necessary only for LIVE MRs, while others retain
* their values from their respective last ctx->write().
*/
if ((mr->mr_flags & TSM_MR_F_LIVE) && !ctx->in_sync) {
up_read(&ctx->rwsem);
rc = down_write_killable(&ctx->rwsem);
if (rc)
return rc;
if (!ctx->in_sync) {
rc = ctx->tm->refresh(ctx->tm);
ctx->in_sync = !rc;
trace_tsm_mr_refresh(mr, rc);
}
downgrade_write(&ctx->rwsem);
}
memcpy(buffer, mr->mr_value + off, count);
trace_tsm_mr_read(mr);
up_read(&ctx->rwsem);
return rc ?: count;
}
static ssize_t tm_digest_write(struct file *filp, struct kobject *kobj,
const struct bin_attribute *attr, char *buffer,
loff_t off, size_t count)
{
struct tm_context *ctx;
const struct tsm_measurement_register *mr;
ssize_t rc;
/* partial writes are not supported */
if (off != 0 || count != attr->size)
return -EINVAL;
ctx = attr->private;
mr = &ctx->tm->mrs[attr - ctx->mrs];
rc = down_write_killable(&ctx->rwsem);
if (rc)
return rc;
rc = ctx->tm->write(ctx->tm, mr, buffer);
/* mark MR cache stale */
if (!rc) {
ctx->in_sync = false;
trace_tsm_mr_write(mr, buffer);
}
up_write(&ctx->rwsem);
return rc ?: count;
}
/**
* tsm_mr_create_attribute_group() - creates an attribute group for measurement
* registers (MRs)
* @tm: pointer to &struct tsm_measurements containing the MR definitions.
*
* This function creates attributes corresponding to the MR definitions
* provided by @tm->mrs.
*
* The created attributes will reference @tm and its members. The caller must
* not free @tm until after tsm_mr_free_attribute_group() is called.
*
* Context: Process context. May sleep due to memory allocation.
*
* Return:
* * On success, the pointer to a an attribute group is returned; otherwise
* * %-EINVAL - Invalid MR definitions.
* * %-ENOMEM - Out of memory.
*/
const struct attribute_group *
tsm_mr_create_attribute_group(const struct tsm_measurements *tm)
{
size_t nlen;
if (!tm || !tm->mrs)
return ERR_PTR(-EINVAL);
/* aggregated length of all MR names */
nlen = 0;
for (size_t i = 0; i < tm->nr_mrs; ++i) {
if ((tm->mrs[i].mr_flags & TSM_MR_F_LIVE) && !tm->refresh)
return ERR_PTR(-EINVAL);
if ((tm->mrs[i].mr_flags & TSM_MR_F_WRITABLE) && !tm->write)
return ERR_PTR(-EINVAL);
if (!tm->mrs[i].mr_name)
return ERR_PTR(-EINVAL);
if (tm->mrs[i].mr_flags & TSM_MR_F_NOHASH)
continue;
if (tm->mrs[i].mr_hash >= HASH_ALGO__LAST)
return ERR_PTR(-EINVAL);
/* MR sysfs attribute names have the form of MRNAME:HASH */
nlen += strlen(tm->mrs[i].mr_name) + 1 +
strlen(hash_algo_name[tm->mrs[i].mr_hash]) + 1;
}
/*
* @attrs and the MR name strings are combined into a single allocation
* so that we don't have to free MR names one-by-one in
* tsm_mr_free_attribute_group()
*/
const struct bin_attribute **attrs __free(kfree) =
kzalloc(sizeof(*attrs) * (tm->nr_mrs + 1) + nlen, GFP_KERNEL);
struct tm_context *ctx __free(kfree) =
kzalloc(struct_size(ctx, mrs, tm->nr_mrs), GFP_KERNEL);
char *name, *end;
if (!ctx || !attrs)
return ERR_PTR(-ENOMEM);
/* @attrs is followed immediately by MR name strings */
name = (char *)&attrs[tm->nr_mrs + 1];
end = name + nlen;
for (size_t i = 0; i < tm->nr_mrs; ++i) {
struct bin_attribute *bap = &ctx->mrs[i];
sysfs_bin_attr_init(bap);
if (tm->mrs[i].mr_flags & TSM_MR_F_NOHASH)
bap->attr.name = tm->mrs[i].mr_name;
else if (name < end) {
bap->attr.name = name;
name += snprintf(name, end - name, "%s:%s",
tm->mrs[i].mr_name,
hash_algo_name[tm->mrs[i].mr_hash]);
++name;
} else
return ERR_PTR(-EINVAL);
/* check for duplicated MR definitions */
for (size_t j = 0; j < i; ++j)
if (!strcmp(bap->attr.name, attrs[j]->attr.name))
return ERR_PTR(-EINVAL);
if (tm->mrs[i].mr_flags & TSM_MR_F_READABLE) {
bap->attr.mode |= 0444;
bap->read_new = tm_digest_read;
}
if (tm->mrs[i].mr_flags & TSM_MR_F_WRITABLE) {
bap->attr.mode |= 0200;
bap->write_new = tm_digest_write;
}
bap->size = tm->mrs[i].mr_size;
bap->private = ctx;
attrs[i] = bap;
}
if (name != end)
return ERR_PTR(-EINVAL);
init_rwsem(&ctx->rwsem);
ctx->agrp.name = "measurements";
ctx->agrp.bin_attrs_new = no_free_ptr(attrs);
ctx->tm = tm;
return &no_free_ptr(ctx)->agrp;
}
EXPORT_SYMBOL_GPL(tsm_mr_create_attribute_group);
/**
* tsm_mr_free_attribute_group() - frees the attribute group returned by
* tsm_mr_create_attribute_group()
* @attr_grp: attribute group returned by tsm_mr_create_attribute_group()
*
* Context: Process context.
*/
void tsm_mr_free_attribute_group(const struct attribute_group *attr_grp)
{
if (!IS_ERR_OR_NULL(attr_grp)) {
kfree(attr_grp->bin_attrs_new);
kfree(container_of(attr_grp, struct tm_context, agrp));
}
}
EXPORT_SYMBOL_GPL(tsm_mr_free_attribute_group);

View File

@@ -346,7 +346,7 @@ struct snp_msg_cert_entry {
static int sev_svsm_report_new(struct tsm_report *report, void *data)
{
unsigned int rep_len, man_len, certs_len;
struct tsm_desc *desc = &report->desc;
struct tsm_report_desc *desc = &report->desc;
struct svsm_attest_call ac = {};
unsigned int retry_count;
void *rep, *man, *certs;
@@ -481,7 +481,7 @@ static int sev_svsm_report_new(struct tsm_report *report, void *data)
static int sev_report_new(struct tsm_report *report, void *data)
{
struct snp_msg_cert_entry *cert_table;
struct tsm_desc *desc = &report->desc;
struct tsm_report_desc *desc = &report->desc;
struct snp_guest_dev *snp_dev = data;
struct snp_msg_report_resp_hdr hdr;
const u32 report_size = SZ_4K;
@@ -610,7 +610,7 @@ static bool sev_report_bin_attr_visible(int n)
return false;
}
static struct tsm_ops sev_tsm_ops = {
static struct tsm_report_ops sev_tsm_report_ops = {
.name = KBUILD_MODNAME,
.report_new = sev_report_new,
.report_attr_visible = sev_report_attr_visible,
@@ -619,7 +619,7 @@ static struct tsm_ops sev_tsm_ops = {
static void unregister_sev_tsm(void *data)
{
tsm_unregister(&sev_tsm_ops);
tsm_report_unregister(&sev_tsm_report_ops);
}
static int __init sev_guest_probe(struct platform_device *pdev)
@@ -656,9 +656,9 @@ static int __init sev_guest_probe(struct platform_device *pdev)
misc->fops = &snp_guest_fops;
/* Set the privlevel_floor attribute based on the vmpck_id */
sev_tsm_ops.privlevel_floor = mdesc->vmpck_id;
sev_tsm_report_ops.privlevel_floor = mdesc->vmpck_id;
ret = tsm_register(&sev_tsm_ops, snp_dev);
ret = tsm_report_register(&sev_tsm_report_ops, snp_dev);
if (ret)
goto e_msg_init;

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).
@@ -68,37 +202,8 @@ static u32 getquote_timeout = 30;
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;
return tdx_do_report(USER_SOCKPTR(req->reportdata),
USER_SOCKPTR(req->tdreport));
}
static void free_quote_buf(void *buf)
@@ -157,53 +262,24 @@ static int wait_for_quote_completion(struct tdx_quote_buf *quote_buf, u32 timeou
return (i == timeout) ? -ETIMEDOUT : 0;
}
static int tdx_report_new(struct tsm_report *report, void *data)
static int tdx_report_new_locked(struct tsm_report *report, void *data)
{
u8 *buf, *reportdata = NULL, *tdreport = NULL;
u8 *buf;
struct tdx_quote_buf *quote_buf = quote_data;
struct tsm_desc *desc = &report->desc;
struct tsm_report_desc *desc = &report->desc;
int ret;
u64 err;
/* TODO: switch to guard(mutex_intr) */
if (mutex_lock_interruptible(&quote_lock))
return -EINTR;
/*
* If the previous request is timedout or interrupted, and the
* Quote buf status is still in GET_QUOTE_IN_FLIGHT (owned by
* VMM), don't permit any new request.
*/
if (quote_buf->status == GET_QUOTE_IN_FLIGHT) {
ret = -EBUSY;
goto done;
}
if (quote_buf->status == GET_QUOTE_IN_FLIGHT)
return -EBUSY;
if (desc->inblob_len != TDX_REPORTDATA_LEN) {
ret = -EINVAL;
goto done;
}
reportdata = kmalloc(TDX_REPORTDATA_LEN, GFP_KERNEL);
if (!reportdata) {
ret = -ENOMEM;
goto done;
}
tdreport = kzalloc(TDX_REPORT_LEN, GFP_KERNEL);
if (!tdreport) {
ret = -ENOMEM;
goto done;
}
memcpy(reportdata, desc->inblob, desc->inblob_len);
/* Generate TDREPORT0 using "TDG.MR.REPORT" TDCALL */
ret = tdx_mcall_get_report0(reportdata, tdreport);
if (ret) {
pr_err("GetReport call failed\n");
goto done;
}
if (desc->inblob_len != TDX_REPORTDATA_LEN)
return -EINVAL;
memset(quote_data, 0, GET_QUOTE_BUF_SIZE);
@@ -211,26 +287,26 @@ static int tdx_report_new(struct tsm_report *report, void *data)
quote_buf->version = GET_QUOTE_CMD_VER;
quote_buf->in_len = TDX_REPORT_LEN;
memcpy(quote_buf->data, tdreport, TDX_REPORT_LEN);
ret = tdx_do_report(KERNEL_SOCKPTR(desc->inblob),
KERNEL_SOCKPTR(quote_buf->data));
if (ret)
return ret;
err = tdx_hcall_get_quote(quote_data, GET_QUOTE_BUF_SIZE);
if (err) {
pr_err("GetQuote hypercall failed, status:%llx\n", err);
ret = -EIO;
goto done;
return -EIO;
}
ret = wait_for_quote_completion(quote_buf, getquote_timeout);
if (ret) {
pr_err("GetQuote request timedout\n");
goto done;
return ret;
}
buf = kvmemdup(quote_buf->data, quote_buf->out_len, GFP_KERNEL);
if (!buf) {
ret = -ENOMEM;
goto done;
}
if (!buf)
return -ENOMEM;
report->outblob = buf;
report->outblob_len = quote_buf->out_len;
@@ -239,14 +315,16 @@ static int tdx_report_new(struct tsm_report *report, void *data)
* TODO: parse the PEM-formatted cert chain out of the quote buffer when
* provided
*/
done:
mutex_unlock(&quote_lock);
kfree(reportdata);
kfree(tdreport);
return ret;
}
static int tdx_report_new(struct tsm_report *report, void *data)
{
scoped_cond_guard(mutex_intr, return -EINTR, &quote_lock)
return tdx_report_new_locked(report, data);
}
static bool tdx_report_attr_visible(int n)
{
switch (n) {
@@ -285,10 +363,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[] = {
@@ -297,7 +381,7 @@ static const struct x86_cpu_id tdx_guest_ids[] = {
};
MODULE_DEVICE_TABLE(x86cpu, tdx_guest_ids);
static const struct tsm_ops tdx_tsm_ops = {
static const struct tsm_report_ops tdx_tsm_ops = {
.name = KBUILD_MODNAME,
.report_new = tdx_report_new,
.report_attr_visible = tdx_report_attr_visible,
@@ -311,9 +395,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) {
@@ -322,7 +410,7 @@ static int __init tdx_guest_init(void)
goto free_misc;
}
ret = tsm_register(&tdx_tsm_ops, NULL);
ret = tsm_report_register(&tdx_tsm_ops, NULL);
if (ret)
goto free_quote;
@@ -332,6 +420,8 @@ static int __init tdx_guest_init(void)
free_quote_buf(quote_data);
free_misc:
misc_deregister(&tdx_misc_dev);
deinit_mr:
tdx_mr_deinit(tdx_attr_groups[0]);
return ret;
}
@@ -339,9 +429,10 @@ module_init(tdx_guest_init);
static void __exit tdx_guest_exit(void)
{
tsm_unregister(&tdx_tsm_ops);
tsm_report_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);

89
include/linux/tsm-mr.h Normal file
View File

@@ -0,0 +1,89 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __TSM_MR_H
#define __TSM_MR_H
#include <crypto/hash_info.h>
/**
* struct tsm_measurement_register - describes an architectural measurement
* register (MR)
* @mr_name: name of the MR
* @mr_value: buffer containing the current value of the MR
* @mr_size: size of the MR - typically the digest size of @mr_hash
* @mr_flags: bitwise OR of one or more flags, detailed below
* @mr_hash: optional hash identifier defined in include/uapi/linux/hash_info.h.
*
* A CC guest driver encloses an array of this structure in struct
* tsm_measurements to detail the measurement facility supported by the
* underlying CC hardware.
*
* @mr_name and @mr_value must stay valid until this structure is no longer in
* use.
*
* @mr_flags is the bitwise-OR of zero or more of the flags below.
*
* * %TSM_MR_F_READABLE - the sysfs attribute corresponding to this MR is readable.
* * %TSM_MR_F_WRITABLE - the sysfs attribute corresponding to this MR is writable.
* The semantics is typically to extend the MR but could vary depending on the
* architecture and the MR.
* * %TSM_MR_F_LIVE - this MR's value may differ from the last value written, so
* must be read back from the underlying CC hardware/firmware.
* * %TSM_MR_F_RTMR - bitwise-OR of %TSM_MR_F_LIVE and %TSM_MR_F_WRITABLE.
* * %TSM_MR_F_NOHASH - this MR does NOT have an associated hash algorithm.
* @mr_hash will be ignored when this flag is set.
*/
struct tsm_measurement_register {
const char *mr_name;
void *mr_value;
u32 mr_size;
u32 mr_flags;
enum hash_algo mr_hash;
};
#define TSM_MR_F_NOHASH 1
#define TSM_MR_F_WRITABLE 2
#define TSM_MR_F_READABLE 4
#define TSM_MR_F_LIVE 8
#define TSM_MR_F_RTMR (TSM_MR_F_LIVE | TSM_MR_F_WRITABLE)
#define TSM_MR_(mr, hash) \
.mr_name = #mr, .mr_size = hash##_DIGEST_SIZE, \
.mr_hash = HASH_ALGO_##hash, .mr_flags = TSM_MR_F_READABLE
/**
* struct tsm_measurements - defines the CC architecture specific measurement
* facility and methods for updating measurement registers (MRs)
* @mrs: Array of MR definitions.
* @nr_mrs: Number of elements in @mrs.
* @refresh: Callback function to load/sync all MRs from TVM hardware/firmware
* into the kernel cache.
* @write: Callback function to write to the MR specified by the parameter @mr.
* Typically, writing to an MR extends the input buffer to that MR.
*
* The @refresh callback is invoked when an MR with %TSM_MR_F_LIVE set is being
* read and the cache is stale. It must reload all MRs with %TSM_MR_F_LIVE set.
* The function parameter @tm is a pointer pointing back to this structure.
*
* The @write callback is invoked whenever an MR is being written. It takes two
* additional parameters besides @tm:
*
* * @mr - points to the MR (an element of @tm->mrs) being written.
* * @data - contains the bytes to write and whose size is @mr->mr_size.
*
* Both @refresh and @write should return 0 on success and an appropriate error
* code on failure.
*/
struct tsm_measurements {
const struct tsm_measurement_register *mrs;
size_t nr_mrs;
int (*refresh)(const struct tsm_measurements *tm);
int (*write)(const struct tsm_measurements *tm,
const struct tsm_measurement_register *mr, const u8 *data);
};
const struct attribute_group *
tsm_mr_create_attribute_group(const struct tsm_measurements *tm);
void tsm_mr_free_attribute_group(const struct attribute_group *attr_grp);
#endif

View File

@@ -6,17 +6,17 @@
#include <linux/types.h>
#include <linux/uuid.h>
#define TSM_INBLOB_MAX 64
#define TSM_OUTBLOB_MAX SZ_32K
#define TSM_REPORT_INBLOB_MAX 64
#define TSM_REPORT_OUTBLOB_MAX SZ_32K
/*
* Privilege level is a nested permission concept to allow confidential
* guests to partition address space, 4-levels are supported.
*/
#define TSM_PRIVLEVEL_MAX 3
#define TSM_REPORT_PRIVLEVEL_MAX 3
/**
* struct tsm_desc - option descriptor for generating tsm report blobs
* struct tsm_report_desc - option descriptor for generating tsm report blobs
* @privlevel: optional privilege level to associate with @outblob
* @inblob_len: sizeof @inblob
* @inblob: arbitrary input data
@@ -24,10 +24,10 @@
* @service_guid: optional service-provider service guid to attest
* @service_manifest_version: optional service-provider service manifest version requested
*/
struct tsm_desc {
struct tsm_report_desc {
unsigned int privlevel;
size_t inblob_len;
u8 inblob[TSM_INBLOB_MAX];
u8 inblob[TSM_REPORT_INBLOB_MAX];
char *service_provider;
guid_t service_guid;
unsigned int service_manifest_version;
@@ -44,7 +44,7 @@ struct tsm_desc {
* @manifestblob: (optional) manifest data associated with the report
*/
struct tsm_report {
struct tsm_desc desc;
struct tsm_report_desc desc;
size_t outblob_len;
u8 *outblob;
size_t auxblob_len;
@@ -88,7 +88,7 @@ enum tsm_bin_attr_index {
};
/**
* struct tsm_ops - attributes and operations for tsm instances
* struct tsm_report_ops - attributes and operations for tsm_report instances
* @name: tsm id reflected in /sys/kernel/config/tsm/report/$report/provider
* @privlevel_floor: convey base privlevel for nested scenarios
* @report_new: Populate @report with the report blob and auxblob
@@ -99,7 +99,7 @@ enum tsm_bin_attr_index {
* Implementation specific ops, only one is expected to be registered at
* a time i.e. only one of "sev-guest", "tdx-guest", etc.
*/
struct tsm_ops {
struct tsm_report_ops {
const char *name;
unsigned int privlevel_floor;
int (*report_new)(struct tsm_report *report, void *data);
@@ -107,6 +107,6 @@ struct tsm_ops {
bool (*report_bin_attr_visible)(int n);
};
int tsm_register(const struct tsm_ops *ops, void *priv);
int tsm_unregister(const struct tsm_ops *ops);
int tsm_report_register(const struct tsm_report_ops *ops, void *priv);
int tsm_report_unregister(const struct tsm_report_ops *ops);
#endif /* __TSM_H */

View File

@@ -0,0 +1,80 @@
/* SPDX-License-Identifier: GPL-2.0 */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM tsm_mr
#if !defined(_TRACE_TSM_MR_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_TSM_MR_H
#include <linux/tracepoint.h>
#include <linux/tsm-mr.h>
TRACE_EVENT(tsm_mr_read,
TP_PROTO(const struct tsm_measurement_register *mr),
TP_ARGS(mr),
TP_STRUCT__entry(
__string(mr, mr->mr_name)
__string(hash, mr->mr_flags & TSM_MR_F_NOHASH ?
"data" : hash_algo_name[mr->mr_hash])
__dynamic_array(u8, d, mr->mr_size)
),
TP_fast_assign(
__assign_str(mr);
__assign_str(hash);
memcpy(__get_dynamic_array(d), mr->mr_value, __get_dynamic_array_len(d));
),
TP_printk("[%s] %s:%s", __get_str(mr), __get_str(hash),
__print_hex_str(__get_dynamic_array(d), __get_dynamic_array_len(d)))
);
TRACE_EVENT(tsm_mr_refresh,
TP_PROTO(const struct tsm_measurement_register *mr, int rc),
TP_ARGS(mr, rc),
TP_STRUCT__entry(
__string(mr, mr->mr_name)
__field(int, rc)
),
TP_fast_assign(
__assign_str(mr);
__entry->rc = rc;
),
TP_printk("[%s] %s:%d", __get_str(mr),
__entry->rc ? "failed" : "succeeded", __entry->rc)
);
TRACE_EVENT(tsm_mr_write,
TP_PROTO(const struct tsm_measurement_register *mr, const u8 *data),
TP_ARGS(mr, data),
TP_STRUCT__entry(
__string(mr, mr->mr_name)
__string(hash, mr->mr_flags & TSM_MR_F_NOHASH ?
"data" : hash_algo_name[mr->mr_hash])
__dynamic_array(u8, d, mr->mr_size)
),
TP_fast_assign(
__assign_str(mr);
__assign_str(hash);
memcpy(__get_dynamic_array(d), data, __get_dynamic_array_len(d));
),
TP_printk("[%s] %s:%s", __get_str(mr), __get_str(hash),
__print_hex_str(__get_dynamic_array(d), __get_dynamic_array_len(d)))
);
#endif
/* This part must be outside protection */
#include <trace/define_trace.h>

View File

@@ -184,6 +184,17 @@ config SAMPLE_TIMER
bool "Timer sample"
depends on CC_CAN_LINK && HEADERS_INSTALL
config SAMPLE_TSM_MR
tristate "TSM measurement sample"
select TSM_MEASUREMENTS
select VIRT_DRIVERS
help
Build a sample module that emulates MRs (Measurement Registers) and
exposes them to user mode applications through the TSM sysfs
interface (/sys/class/misc/tsm_mr_sample/emulated_mr/).
The module name will be tsm-mr-sample when built as a module.
config SAMPLE_UHID
bool "UHID sample"
depends on CC_CAN_LINK && HEADERS_INSTALL

View File

@@ -43,3 +43,4 @@ obj-$(CONFIG_SAMPLES_RUST) += rust/
obj-$(CONFIG_SAMPLE_DAMON_WSSE) += damon/
obj-$(CONFIG_SAMPLE_DAMON_PRCL) += damon/
obj-$(CONFIG_SAMPLE_HUNG_TASK) += hung_task/
obj-$(CONFIG_SAMPLE_TSM_MR) += tsm-mr/

2
samples/tsm-mr/Makefile Normal file
View File

@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_SAMPLE_TSM_MR) += tsm_mr_sample.o

View File

@@ -0,0 +1,131 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2024-2005 Intel Corporation. All rights reserved. */
#define pr_fmt(x) KBUILD_MODNAME ": " x
#include <linux/module.h>
#include <linux/tsm-mr.h>
#include <linux/miscdevice.h>
#include <crypto/hash.h>
static struct {
u8 static_mr[SHA384_DIGEST_SIZE];
u8 config_mr[SHA512_DIGEST_SIZE];
u8 rtmr0[SHA256_DIGEST_SIZE];
u8 rtmr1[SHA384_DIGEST_SIZE];
u8 report_digest[SHA512_DIGEST_SIZE];
} sample_report = {
.static_mr = "static_mr",
.config_mr = "config_mr",
.rtmr0 = "rtmr0",
.rtmr1 = "rtmr1",
};
static int sample_report_refresh(const struct tsm_measurements *tm)
{
struct crypto_shash *tfm;
int rc;
tfm = crypto_alloc_shash(hash_algo_name[HASH_ALGO_SHA512], 0, 0);
if (IS_ERR(tfm)) {
pr_err("crypto_alloc_shash failed: %ld\n", PTR_ERR(tfm));
return PTR_ERR(tfm);
}
rc = crypto_shash_tfm_digest(tfm, (u8 *)&sample_report,
offsetof(typeof(sample_report),
report_digest),
sample_report.report_digest);
crypto_free_shash(tfm);
if (rc)
pr_err("crypto_shash_tfm_digest failed: %d\n", rc);
return rc;
}
static int sample_report_extend_mr(const struct tsm_measurements *tm,
const struct tsm_measurement_register *mr,
const u8 *data)
{
SHASH_DESC_ON_STACK(desc, 0);
int rc;
desc->tfm = crypto_alloc_shash(hash_algo_name[mr->mr_hash], 0, 0);
if (IS_ERR(desc->tfm)) {
pr_err("crypto_alloc_shash failed: %ld\n", PTR_ERR(desc->tfm));
return PTR_ERR(desc->tfm);
}
rc = crypto_shash_init(desc);
if (!rc)
rc = crypto_shash_update(desc, mr->mr_value, mr->mr_size);
if (!rc)
rc = crypto_shash_finup(desc, data, mr->mr_size, mr->mr_value);
crypto_free_shash(desc->tfm);
if (rc)
pr_err("SHA calculation failed: %d\n", rc);
return rc;
}
#define MR_(mr, hash) .mr_value = &sample_report.mr, TSM_MR_(mr, hash)
static const struct tsm_measurement_register sample_mrs[] = {
/* static MR, read-only */
{ MR_(static_mr, SHA384) },
/* config MR, read-only */
{ MR_(config_mr, SHA512) | TSM_MR_F_NOHASH },
/* RTMR, direct extension prohibited */
{ MR_(rtmr0, SHA256) | TSM_MR_F_LIVE },
/* RTMR, direct extension allowed */
{ MR_(rtmr1, SHA384) | TSM_MR_F_RTMR },
/* RTMR, crypto agile, alaised to rtmr0 and rtmr1, respectively */
{ .mr_value = &sample_report.rtmr0,
TSM_MR_(rtmr_crypto_agile, SHA256) | TSM_MR_F_RTMR },
{ .mr_value = &sample_report.rtmr1,
TSM_MR_(rtmr_crypto_agile, SHA384) | TSM_MR_F_RTMR },
/* sha512 digest of the whole structure */
{ MR_(report_digest, SHA512) | TSM_MR_F_LIVE },
};
#undef MR_
static struct tsm_measurements sample_tm = {
.mrs = sample_mrs,
.nr_mrs = ARRAY_SIZE(sample_mrs),
.refresh = sample_report_refresh,
.write = sample_report_extend_mr,
};
static const struct attribute_group *sample_groups[] = {
NULL,
NULL,
};
static struct miscdevice sample_misc_dev = {
.name = KBUILD_MODNAME,
.minor = MISC_DYNAMIC_MINOR,
.groups = sample_groups,
};
static int __init tsm_mr_sample_init(void)
{
int rc;
sample_groups[0] = tsm_mr_create_attribute_group(&sample_tm);
if (IS_ERR(sample_groups[0]))
return PTR_ERR(sample_groups[0]);
rc = misc_register(&sample_misc_dev);
if (rc)
tsm_mr_free_attribute_group(sample_groups[0]);
return rc;
}
static void __exit tsm_mr_sample_exit(void)
{
misc_deregister(&sample_misc_dev);
tsm_mr_free_attribute_group(sample_groups[0]);
}
module_init(tsm_mr_sample_init);
module_exit(tsm_mr_sample_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Sample module using tsm-mr to expose emulated MRs");