mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-12-27 11:06:41 -05:00
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:
@@ -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 TD’s 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.
|
||||
12
Documentation/driver-api/coco/index.rst
Normal file
12
Documentation/driver-api/coco/index.rst
Normal file
@@ -0,0 +1,12 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
======================
|
||||
Confidential Computing
|
||||
======================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
measurement-registers
|
||||
|
||||
.. only:: subproject and html
|
||||
12
Documentation/driver-api/coco/measurement-registers.rst
Normal file
12
Documentation/driver-api/coco/measurement-registers.rst
Normal 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:
|
||||
@@ -81,6 +81,7 @@ Subsystem-specific APIs
|
||||
acpi/index
|
||||
backlight/lp855x-driver.rst
|
||||
clk
|
||||
coco/index
|
||||
console
|
||||
crypto/index
|
||||
dmaengine/index
|
||||
|
||||
11
MAINTAINERS
11
MAINTAINERS
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
17
drivers/virt/coco/guest/Kconfig
Normal file
17
drivers/virt/coco/guest/Kconfig
Normal 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
|
||||
4
drivers/virt/coco/guest/Makefile
Normal file
4
drivers/virt/coco/guest/Makefile
Normal 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
|
||||
@@ -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");
|
||||
251
drivers/virt/coco/guest/tsm-mr.c
Normal file
251
drivers/virt/coco/guest/tsm-mr.c
Normal 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);
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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("e_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("e_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, "e_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
89
include/linux/tsm-mr.h
Normal 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
|
||||
@@ -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 */
|
||||
|
||||
80
include/trace/events/tsm_mr.h
Normal file
80
include/trace/events/tsm_mr.h
Normal 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>
|
||||
@@ -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
|
||||
|
||||
@@ -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
2
samples/tsm-mr/Makefile
Normal file
@@ -0,0 +1,2 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
obj-$(CONFIG_SAMPLE_TSM_MR) += tsm_mr_sample.o
|
||||
131
samples/tsm-mr/tsm_mr_sample.c
Normal file
131
samples/tsm-mr/tsm_mr_sample.c
Normal 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");
|
||||
Reference in New Issue
Block a user