From 7515f45c165269b72ee739e6fc26cc2ef928fc1b Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Mon, 3 Mar 2025 23:14:26 -0800 Subject: coco/guest: Move shared guest CC infrastructure to drivers/virt/coco/guest/ In preparation for creating a new drivers/virt/coco/host/ directory to house shared host driver infrastructure for confidential computing, move configfs-tsm to a guest/ sub-directory. The tsm.ko module is renamed to tsm_reports.ko. The old tsm.ko module was only ever demand loaded by kernel internal dependencies, so it should not affect existing userspace module install scripts. The new drivers/virt/coco/guest/ is also a preparatory landing spot for new / optional TSM Report mechanics like a TCB stability enumeration / watchdog mechanism. To be added later. Cc: Wu Hao Cc: Yilun Xu Cc: Samuel Ortiz Cc: Tom Lendacky Reviewed-by: Alexey Kardashevskiy Reviewed-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/174107246641.1288555.208426916259466774.stgit@dwillia2-xfh.jf.intel.com Signed-off-by: Dan Williams --- drivers/virt/coco/Kconfig | 6 +- drivers/virt/coco/Makefile | 2 +- drivers/virt/coco/guest/Kconfig | 7 + drivers/virt/coco/guest/Makefile | 3 + drivers/virt/coco/guest/report.c | 512 +++++++++++++++++++++++++++++++++++++++ drivers/virt/coco/tsm.c | 512 --------------------------------------- 6 files changed, 525 insertions(+), 517 deletions(-) create mode 100644 drivers/virt/coco/guest/Kconfig create mode 100644 drivers/virt/coco/guest/Makefile create mode 100644 drivers/virt/coco/guest/report.c delete mode 100644 drivers/virt/coco/tsm.c (limited to 'drivers/virt') diff --git a/drivers/virt/coco/Kconfig b/drivers/virt/coco/Kconfig index ff869d883d95..819a97e8ba99 100644 --- a/drivers/virt/coco/Kconfig +++ b/drivers/virt/coco/Kconfig @@ -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" diff --git a/drivers/virt/coco/Makefile b/drivers/virt/coco/Makefile index c3d07cfc087e..885c9ef4e9fc 100644 --- a/drivers/virt/coco/Makefile +++ b/drivers/virt/coco/Makefile @@ -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_REPORTS) += guest/ diff --git a/drivers/virt/coco/guest/Kconfig b/drivers/virt/coco/guest/Kconfig new file mode 100644 index 000000000000..ed9bafbdd854 --- /dev/null +++ b/drivers/virt/coco/guest/Kconfig @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Confidential computing shared guest collateral +# +config TSM_REPORTS + select CONFIGFS_FS + tristate diff --git a/drivers/virt/coco/guest/Makefile b/drivers/virt/coco/guest/Makefile new file mode 100644 index 000000000000..b3b217af77cf --- /dev/null +++ b/drivers/virt/coco/guest/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_TSM_REPORTS) += tsm_report.o +tsm_report-y := report.o diff --git a/drivers/virt/coco/guest/report.c b/drivers/virt/coco/guest/report.c new file mode 100644 index 000000000000..bcb515b50c68 --- /dev/null +++ b/drivers/virt/coco/guest/report.c @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright(c) 2023 Intel Corporation. All rights reserved. */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +static struct tsm_provider { + const struct tsm_report_ops *ops; + void *data; +} provider; +static DECLARE_RWSEM(tsm_rwsem); + +/** + * DOC: Trusted Security Module (TSM) Attestation Report Interface + * + * The TSM report interface is a common provider of blobs that facilitate + * attestation of a TVM (confidential computing guest) by an attestation + * service. A TSM report combines a user-defined blob (likely a public-key with + * a nonce for a key-exchange protocol) with a signed attestation report. That + * combined blob is then used to obtain secrets provided by an agent that can + * validate the attestation report. The expectation is that this interface is + * invoked infrequently, however configfs allows for multiple agents to + * own their own report generation instances to generate reports as + * often as needed. + * + * The attestation report format is TSM provider specific, when / if a standard + * materializes that can be published instead of the vendor layout. Until then + * the 'provider' attribute indicates the format of 'outblob', and optionally + * 'auxblob' and 'manifestblob'. + */ + +struct tsm_report_state { + struct tsm_report report; + unsigned long write_generation; + unsigned long read_generation; + struct config_item cfg; +}; + +enum tsm_data_select { + TSM_REPORT, + TSM_CERTS, + TSM_MANIFEST, +}; + +static struct tsm_report *to_tsm_report(struct config_item *cfg) +{ + struct tsm_report_state *state = + container_of(cfg, struct tsm_report_state, cfg); + + return &state->report; +} + +static struct tsm_report_state *to_state(struct tsm_report *report) +{ + return container_of(report, struct tsm_report_state, report); +} + +static int try_advance_write_generation(struct tsm_report *report) +{ + struct tsm_report_state *state = to_state(report); + + lockdep_assert_held_write(&tsm_rwsem); + + /* + * Malicious or broken userspace has written enough times for + * read_generation == write_generation by modular arithmetic without an + * interim read. Stop accepting updates until the current report + * configuration is read. + */ + if (state->write_generation == state->read_generation - 1) + return -EBUSY; + state->write_generation++; + return 0; +} + +static ssize_t tsm_report_privlevel_store(struct config_item *cfg, + const char *buf, size_t len) +{ + struct tsm_report *report = to_tsm_report(cfg); + unsigned int val; + int rc; + + rc = kstrtouint(buf, 0, &val); + if (rc) + return rc; + + /* + * 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_REPORT_PRIVLEVEL_MAX) + return -EINVAL; + + guard(rwsem_write)(&tsm_rwsem); + rc = try_advance_write_generation(report); + if (rc) + return rc; + report->desc.privlevel = val; + + return len; +} +CONFIGFS_ATTR_WO(tsm_report_, privlevel); + +static ssize_t tsm_report_privlevel_floor_show(struct config_item *cfg, + char *buf) +{ + guard(rwsem_read)(&tsm_rwsem); + return sysfs_emit(buf, "%u\n", provider.ops->privlevel_floor); +} +CONFIGFS_ATTR_RO(tsm_report_, privlevel_floor); + +static ssize_t tsm_report_service_provider_store(struct config_item *cfg, + const char *buf, size_t len) +{ + struct tsm_report *report = to_tsm_report(cfg); + size_t sp_len; + char *sp; + int rc; + + guard(rwsem_write)(&tsm_rwsem); + rc = try_advance_write_generation(report); + if (rc) + return rc; + + sp_len = (buf[len - 1] != '\n') ? len : len - 1; + + sp = kstrndup(buf, sp_len, GFP_KERNEL); + if (!sp) + return -ENOMEM; + kfree(report->desc.service_provider); + + report->desc.service_provider = sp; + + return len; +} +CONFIGFS_ATTR_WO(tsm_report_, service_provider); + +static ssize_t tsm_report_service_guid_store(struct config_item *cfg, + const char *buf, size_t len) +{ + struct tsm_report *report = to_tsm_report(cfg); + int rc; + + guard(rwsem_write)(&tsm_rwsem); + rc = try_advance_write_generation(report); + if (rc) + return rc; + + report->desc.service_guid = guid_null; + + rc = guid_parse(buf, &report->desc.service_guid); + if (rc) + return rc; + + return len; +} +CONFIGFS_ATTR_WO(tsm_report_, service_guid); + +static ssize_t tsm_report_service_manifest_version_store(struct config_item *cfg, + const char *buf, size_t len) +{ + struct tsm_report *report = to_tsm_report(cfg); + unsigned int val; + int rc; + + rc = kstrtouint(buf, 0, &val); + if (rc) + return rc; + + guard(rwsem_write)(&tsm_rwsem); + rc = try_advance_write_generation(report); + if (rc) + return rc; + report->desc.service_manifest_version = val; + + return len; +} +CONFIGFS_ATTR_WO(tsm_report_, service_manifest_version); + +static ssize_t tsm_report_inblob_write(struct config_item *cfg, + const void *buf, size_t count) +{ + struct tsm_report *report = to_tsm_report(cfg); + int rc; + + guard(rwsem_write)(&tsm_rwsem); + rc = try_advance_write_generation(report); + if (rc) + return rc; + + report->desc.inblob_len = count; + memcpy(report->desc.inblob, buf, count); + return count; +} +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) +{ + struct tsm_report *report = to_tsm_report(cfg); + struct tsm_report_state *state = to_state(report); + + guard(rwsem_read)(&tsm_rwsem); + return sysfs_emit(buf, "%lu\n", state->write_generation); +} +CONFIGFS_ATTR_RO(tsm_report_, generation); + +static ssize_t tsm_report_provider_show(struct config_item *cfg, char *buf) +{ + guard(rwsem_read)(&tsm_rwsem); + return sysfs_emit(buf, "%s\n", provider.ops->name); +} +CONFIGFS_ATTR_RO(tsm_report_, provider); + +static ssize_t __read_report(struct tsm_report *report, void *buf, size_t count, + enum tsm_data_select select) +{ + loff_t offset = 0; + ssize_t len; + u8 *out; + + if (select == TSM_REPORT) { + out = report->outblob; + len = report->outblob_len; + } else if (select == TSM_MANIFEST) { + out = report->manifestblob; + len = report->manifestblob_len; + } else { + out = report->auxblob; + len = report->auxblob_len; + } + + /* + * Recall that a NULL @buf is configfs requesting the size of + * the buffer. + */ + if (!buf) + return len; + return memory_read_from_buffer(buf, count, &offset, out, len); +} + +static ssize_t read_cached_report(struct tsm_report *report, void *buf, + size_t count, enum tsm_data_select select) +{ + struct tsm_report_state *state = to_state(report); + + guard(rwsem_read)(&tsm_rwsem); + if (!report->desc.inblob_len) + return -EINVAL; + + /* + * A given TSM backend always fills in ->outblob regardless of + * whether the report includes an auxblob/manifestblob or not. + */ + if (!report->outblob || + state->read_generation != state->write_generation) + return -EWOULDBLOCK; + + return __read_report(report, buf, count, select); +} + +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_report_ops *ops; + ssize_t rc; + + /* try to read from the existing report if present and valid... */ + rc = read_cached_report(report, buf, count, select); + if (rc >= 0 || rc != -EWOULDBLOCK) + return rc; + + /* slow path, report may need to be regenerated... */ + guard(rwsem_write)(&tsm_rwsem); + ops = provider.ops; + if (!ops) + return -ENOTTY; + if (!report->desc.inblob_len) + return -EINVAL; + + /* did another thread already generate this report? */ + if (report->outblob && + state->read_generation == state->write_generation) + goto out; + + kvfree(report->outblob); + kvfree(report->auxblob); + kvfree(report->manifestblob); + report->outblob = NULL; + report->auxblob = NULL; + report->manifestblob = NULL; + rc = ops->report_new(report, provider.data); + if (rc < 0) + return rc; + state->read_generation = state->write_generation; +out: + return __read_report(report, buf, count, select); +} + +static ssize_t tsm_report_outblob_read(struct config_item *cfg, void *buf, + size_t count) +{ + struct tsm_report *report = to_tsm_report(cfg); + + return tsm_report_read(report, buf, count, TSM_REPORT); +} +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) +{ + struct tsm_report *report = to_tsm_report(cfg); + + return tsm_report_read(report, buf, count, TSM_CERTS); +} +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) +{ + struct tsm_report *report = to_tsm_report(cfg); + + return tsm_report_read(report, buf, count, TSM_MANIFEST); +} +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, + [TSM_REPORT_PROVIDER] = &tsm_report_attr_provider, + [TSM_REPORT_PRIVLEVEL] = &tsm_report_attr_privlevel, + [TSM_REPORT_PRIVLEVEL_FLOOR] = &tsm_report_attr_privlevel_floor, + [TSM_REPORT_SERVICE_PROVIDER] = &tsm_report_attr_service_provider, + [TSM_REPORT_SERVICE_GUID] = &tsm_report_attr_service_guid, + [TSM_REPORT_SERVICE_MANIFEST_VER] = &tsm_report_attr_service_manifest_version, + NULL, +}; + +static struct configfs_bin_attribute *tsm_report_bin_attrs[] = { + [TSM_REPORT_INBLOB] = &tsm_report_attr_inblob, + [TSM_REPORT_OUTBLOB] = &tsm_report_attr_outblob, + [TSM_REPORT_AUXBLOB] = &tsm_report_attr_auxblob, + [TSM_REPORT_MANIFESTBLOB] = &tsm_report_attr_manifestblob, + NULL, +}; + +static void tsm_report_item_release(struct config_item *cfg) +{ + struct tsm_report *report = to_tsm_report(cfg); + struct tsm_report_state *state = to_state(report); + + kvfree(report->manifestblob); + kvfree(report->auxblob); + kvfree(report->outblob); + kfree(report->desc.service_provider); + kfree(state); +} + +static struct configfs_item_operations tsm_report_item_ops = { + .release = tsm_report_item_release, +}; + +static bool tsm_report_is_visible(struct config_item *item, + struct configfs_attribute *attr, int n) +{ + guard(rwsem_read)(&tsm_rwsem); + if (!provider.ops) + return false; + + if (!provider.ops->report_attr_visible) + return true; + + return provider.ops->report_attr_visible(n); +} + +static bool tsm_report_is_bin_visible(struct config_item *item, + struct configfs_bin_attribute *attr, int n) +{ + guard(rwsem_read)(&tsm_rwsem); + if (!provider.ops) + return false; + + if (!provider.ops->report_bin_attr_visible) + return true; + + return provider.ops->report_bin_attr_visible(n); +} + +static struct configfs_group_operations tsm_report_attr_group_ops = { + .is_visible = tsm_report_is_visible, + .is_bin_visible = tsm_report_is_bin_visible, +}; + +static const struct config_item_type tsm_report_type = { + .ct_owner = THIS_MODULE, + .ct_bin_attrs = tsm_report_bin_attrs, + .ct_attrs = tsm_report_attrs, + .ct_item_ops = &tsm_report_item_ops, + .ct_group_ops = &tsm_report_attr_group_ops, +}; + +static struct config_item *tsm_report_make_item(struct config_group *group, + const char *name) +{ + struct tsm_report_state *state; + + guard(rwsem_read)(&tsm_rwsem); + if (!provider.ops) + return ERR_PTR(-ENXIO); + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return ERR_PTR(-ENOMEM); + + config_item_init_type_name(&state->cfg, name, &tsm_report_type); + return &state->cfg; +} + +static struct configfs_group_operations tsm_report_group_ops = { + .make_item = tsm_report_make_item, +}; + +static const struct config_item_type tsm_reports_type = { + .ct_owner = THIS_MODULE, + .ct_group_ops = &tsm_report_group_ops, +}; + +static const struct config_item_type tsm_root_group_type = { + .ct_owner = THIS_MODULE, +}; + +static struct configfs_subsystem tsm_configfs = { + .su_group = { + .cg_item = { + .ci_namebuf = "tsm", + .ci_type = &tsm_root_group_type, + }, + }, + .su_mutex = __MUTEX_INITIALIZER(tsm_configfs.su_mutex), +}; + +int tsm_report_register(const struct tsm_report_ops *ops, void *priv) +{ + const struct tsm_report_ops *conflict; + + guard(rwsem_write)(&tsm_rwsem); + conflict = provider.ops; + if (conflict) { + pr_err("\"%s\" ops already registered\n", conflict->name); + return -EBUSY; + } + + provider.ops = ops; + provider.data = priv; + return 0; +} +EXPORT_SYMBOL_GPL(tsm_report_register); + +int tsm_report_unregister(const struct tsm_report_ops *ops) +{ + guard(rwsem_write)(&tsm_rwsem); + if (ops != provider.ops) + return -EBUSY; + provider.ops = NULL; + provider.data = NULL; + return 0; +} +EXPORT_SYMBOL_GPL(tsm_report_unregister); + +static struct config_group *tsm_report_group; + +static int __init tsm_report_init(void) +{ + struct config_group *root = &tsm_configfs.su_group; + struct config_group *tsm; + int rc; + + config_group_init(root); + rc = configfs_register_subsystem(&tsm_configfs); + if (rc) + return rc; + + tsm = configfs_register_default_group(root, "report", + &tsm_reports_type); + if (IS_ERR(tsm)) { + configfs_unregister_subsystem(&tsm_configfs); + return PTR_ERR(tsm); + } + tsm_report_group = tsm; + + return 0; +} +module_init(tsm_report_init); + +static void __exit tsm_report_exit(void) +{ + configfs_unregister_default_group(tsm_report_group); + configfs_unregister_subsystem(&tsm_configfs); +} +module_exit(tsm_report_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Provide Trusted Security Module attestation reports via configfs"); diff --git a/drivers/virt/coco/tsm.c b/drivers/virt/coco/tsm.c deleted file mode 100644 index bcb515b50c68..000000000000 --- a/drivers/virt/coco/tsm.c +++ /dev/null @@ -1,512 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* Copyright(c) 2023 Intel Corporation. All rights reserved. */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include - -static struct tsm_provider { - const struct tsm_report_ops *ops; - void *data; -} provider; -static DECLARE_RWSEM(tsm_rwsem); - -/** - * DOC: Trusted Security Module (TSM) Attestation Report Interface - * - * The TSM report interface is a common provider of blobs that facilitate - * attestation of a TVM (confidential computing guest) by an attestation - * service. A TSM report combines a user-defined blob (likely a public-key with - * a nonce for a key-exchange protocol) with a signed attestation report. That - * combined blob is then used to obtain secrets provided by an agent that can - * validate the attestation report. The expectation is that this interface is - * invoked infrequently, however configfs allows for multiple agents to - * own their own report generation instances to generate reports as - * often as needed. - * - * The attestation report format is TSM provider specific, when / if a standard - * materializes that can be published instead of the vendor layout. Until then - * the 'provider' attribute indicates the format of 'outblob', and optionally - * 'auxblob' and 'manifestblob'. - */ - -struct tsm_report_state { - struct tsm_report report; - unsigned long write_generation; - unsigned long read_generation; - struct config_item cfg; -}; - -enum tsm_data_select { - TSM_REPORT, - TSM_CERTS, - TSM_MANIFEST, -}; - -static struct tsm_report *to_tsm_report(struct config_item *cfg) -{ - struct tsm_report_state *state = - container_of(cfg, struct tsm_report_state, cfg); - - return &state->report; -} - -static struct tsm_report_state *to_state(struct tsm_report *report) -{ - return container_of(report, struct tsm_report_state, report); -} - -static int try_advance_write_generation(struct tsm_report *report) -{ - struct tsm_report_state *state = to_state(report); - - lockdep_assert_held_write(&tsm_rwsem); - - /* - * Malicious or broken userspace has written enough times for - * read_generation == write_generation by modular arithmetic without an - * interim read. Stop accepting updates until the current report - * configuration is read. - */ - if (state->write_generation == state->read_generation - 1) - return -EBUSY; - state->write_generation++; - return 0; -} - -static ssize_t tsm_report_privlevel_store(struct config_item *cfg, - const char *buf, size_t len) -{ - struct tsm_report *report = to_tsm_report(cfg); - unsigned int val; - int rc; - - rc = kstrtouint(buf, 0, &val); - if (rc) - return rc; - - /* - * 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_REPORT_PRIVLEVEL_MAX) - return -EINVAL; - - guard(rwsem_write)(&tsm_rwsem); - rc = try_advance_write_generation(report); - if (rc) - return rc; - report->desc.privlevel = val; - - return len; -} -CONFIGFS_ATTR_WO(tsm_report_, privlevel); - -static ssize_t tsm_report_privlevel_floor_show(struct config_item *cfg, - char *buf) -{ - guard(rwsem_read)(&tsm_rwsem); - return sysfs_emit(buf, "%u\n", provider.ops->privlevel_floor); -} -CONFIGFS_ATTR_RO(tsm_report_, privlevel_floor); - -static ssize_t tsm_report_service_provider_store(struct config_item *cfg, - const char *buf, size_t len) -{ - struct tsm_report *report = to_tsm_report(cfg); - size_t sp_len; - char *sp; - int rc; - - guard(rwsem_write)(&tsm_rwsem); - rc = try_advance_write_generation(report); - if (rc) - return rc; - - sp_len = (buf[len - 1] != '\n') ? len : len - 1; - - sp = kstrndup(buf, sp_len, GFP_KERNEL); - if (!sp) - return -ENOMEM; - kfree(report->desc.service_provider); - - report->desc.service_provider = sp; - - return len; -} -CONFIGFS_ATTR_WO(tsm_report_, service_provider); - -static ssize_t tsm_report_service_guid_store(struct config_item *cfg, - const char *buf, size_t len) -{ - struct tsm_report *report = to_tsm_report(cfg); - int rc; - - guard(rwsem_write)(&tsm_rwsem); - rc = try_advance_write_generation(report); - if (rc) - return rc; - - report->desc.service_guid = guid_null; - - rc = guid_parse(buf, &report->desc.service_guid); - if (rc) - return rc; - - return len; -} -CONFIGFS_ATTR_WO(tsm_report_, service_guid); - -static ssize_t tsm_report_service_manifest_version_store(struct config_item *cfg, - const char *buf, size_t len) -{ - struct tsm_report *report = to_tsm_report(cfg); - unsigned int val; - int rc; - - rc = kstrtouint(buf, 0, &val); - if (rc) - return rc; - - guard(rwsem_write)(&tsm_rwsem); - rc = try_advance_write_generation(report); - if (rc) - return rc; - report->desc.service_manifest_version = val; - - return len; -} -CONFIGFS_ATTR_WO(tsm_report_, service_manifest_version); - -static ssize_t tsm_report_inblob_write(struct config_item *cfg, - const void *buf, size_t count) -{ - struct tsm_report *report = to_tsm_report(cfg); - int rc; - - guard(rwsem_write)(&tsm_rwsem); - rc = try_advance_write_generation(report); - if (rc) - return rc; - - report->desc.inblob_len = count; - memcpy(report->desc.inblob, buf, count); - return count; -} -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) -{ - struct tsm_report *report = to_tsm_report(cfg); - struct tsm_report_state *state = to_state(report); - - guard(rwsem_read)(&tsm_rwsem); - return sysfs_emit(buf, "%lu\n", state->write_generation); -} -CONFIGFS_ATTR_RO(tsm_report_, generation); - -static ssize_t tsm_report_provider_show(struct config_item *cfg, char *buf) -{ - guard(rwsem_read)(&tsm_rwsem); - return sysfs_emit(buf, "%s\n", provider.ops->name); -} -CONFIGFS_ATTR_RO(tsm_report_, provider); - -static ssize_t __read_report(struct tsm_report *report, void *buf, size_t count, - enum tsm_data_select select) -{ - loff_t offset = 0; - ssize_t len; - u8 *out; - - if (select == TSM_REPORT) { - out = report->outblob; - len = report->outblob_len; - } else if (select == TSM_MANIFEST) { - out = report->manifestblob; - len = report->manifestblob_len; - } else { - out = report->auxblob; - len = report->auxblob_len; - } - - /* - * Recall that a NULL @buf is configfs requesting the size of - * the buffer. - */ - if (!buf) - return len; - return memory_read_from_buffer(buf, count, &offset, out, len); -} - -static ssize_t read_cached_report(struct tsm_report *report, void *buf, - size_t count, enum tsm_data_select select) -{ - struct tsm_report_state *state = to_state(report); - - guard(rwsem_read)(&tsm_rwsem); - if (!report->desc.inblob_len) - return -EINVAL; - - /* - * A given TSM backend always fills in ->outblob regardless of - * whether the report includes an auxblob/manifestblob or not. - */ - if (!report->outblob || - state->read_generation != state->write_generation) - return -EWOULDBLOCK; - - return __read_report(report, buf, count, select); -} - -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_report_ops *ops; - ssize_t rc; - - /* try to read from the existing report if present and valid... */ - rc = read_cached_report(report, buf, count, select); - if (rc >= 0 || rc != -EWOULDBLOCK) - return rc; - - /* slow path, report may need to be regenerated... */ - guard(rwsem_write)(&tsm_rwsem); - ops = provider.ops; - if (!ops) - return -ENOTTY; - if (!report->desc.inblob_len) - return -EINVAL; - - /* did another thread already generate this report? */ - if (report->outblob && - state->read_generation == state->write_generation) - goto out; - - kvfree(report->outblob); - kvfree(report->auxblob); - kvfree(report->manifestblob); - report->outblob = NULL; - report->auxblob = NULL; - report->manifestblob = NULL; - rc = ops->report_new(report, provider.data); - if (rc < 0) - return rc; - state->read_generation = state->write_generation; -out: - return __read_report(report, buf, count, select); -} - -static ssize_t tsm_report_outblob_read(struct config_item *cfg, void *buf, - size_t count) -{ - struct tsm_report *report = to_tsm_report(cfg); - - return tsm_report_read(report, buf, count, TSM_REPORT); -} -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) -{ - struct tsm_report *report = to_tsm_report(cfg); - - return tsm_report_read(report, buf, count, TSM_CERTS); -} -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) -{ - struct tsm_report *report = to_tsm_report(cfg); - - return tsm_report_read(report, buf, count, TSM_MANIFEST); -} -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, - [TSM_REPORT_PROVIDER] = &tsm_report_attr_provider, - [TSM_REPORT_PRIVLEVEL] = &tsm_report_attr_privlevel, - [TSM_REPORT_PRIVLEVEL_FLOOR] = &tsm_report_attr_privlevel_floor, - [TSM_REPORT_SERVICE_PROVIDER] = &tsm_report_attr_service_provider, - [TSM_REPORT_SERVICE_GUID] = &tsm_report_attr_service_guid, - [TSM_REPORT_SERVICE_MANIFEST_VER] = &tsm_report_attr_service_manifest_version, - NULL, -}; - -static struct configfs_bin_attribute *tsm_report_bin_attrs[] = { - [TSM_REPORT_INBLOB] = &tsm_report_attr_inblob, - [TSM_REPORT_OUTBLOB] = &tsm_report_attr_outblob, - [TSM_REPORT_AUXBLOB] = &tsm_report_attr_auxblob, - [TSM_REPORT_MANIFESTBLOB] = &tsm_report_attr_manifestblob, - NULL, -}; - -static void tsm_report_item_release(struct config_item *cfg) -{ - struct tsm_report *report = to_tsm_report(cfg); - struct tsm_report_state *state = to_state(report); - - kvfree(report->manifestblob); - kvfree(report->auxblob); - kvfree(report->outblob); - kfree(report->desc.service_provider); - kfree(state); -} - -static struct configfs_item_operations tsm_report_item_ops = { - .release = tsm_report_item_release, -}; - -static bool tsm_report_is_visible(struct config_item *item, - struct configfs_attribute *attr, int n) -{ - guard(rwsem_read)(&tsm_rwsem); - if (!provider.ops) - return false; - - if (!provider.ops->report_attr_visible) - return true; - - return provider.ops->report_attr_visible(n); -} - -static bool tsm_report_is_bin_visible(struct config_item *item, - struct configfs_bin_attribute *attr, int n) -{ - guard(rwsem_read)(&tsm_rwsem); - if (!provider.ops) - return false; - - if (!provider.ops->report_bin_attr_visible) - return true; - - return provider.ops->report_bin_attr_visible(n); -} - -static struct configfs_group_operations tsm_report_attr_group_ops = { - .is_visible = tsm_report_is_visible, - .is_bin_visible = tsm_report_is_bin_visible, -}; - -static const struct config_item_type tsm_report_type = { - .ct_owner = THIS_MODULE, - .ct_bin_attrs = tsm_report_bin_attrs, - .ct_attrs = tsm_report_attrs, - .ct_item_ops = &tsm_report_item_ops, - .ct_group_ops = &tsm_report_attr_group_ops, -}; - -static struct config_item *tsm_report_make_item(struct config_group *group, - const char *name) -{ - struct tsm_report_state *state; - - guard(rwsem_read)(&tsm_rwsem); - if (!provider.ops) - return ERR_PTR(-ENXIO); - - state = kzalloc(sizeof(*state), GFP_KERNEL); - if (!state) - return ERR_PTR(-ENOMEM); - - config_item_init_type_name(&state->cfg, name, &tsm_report_type); - return &state->cfg; -} - -static struct configfs_group_operations tsm_report_group_ops = { - .make_item = tsm_report_make_item, -}; - -static const struct config_item_type tsm_reports_type = { - .ct_owner = THIS_MODULE, - .ct_group_ops = &tsm_report_group_ops, -}; - -static const struct config_item_type tsm_root_group_type = { - .ct_owner = THIS_MODULE, -}; - -static struct configfs_subsystem tsm_configfs = { - .su_group = { - .cg_item = { - .ci_namebuf = "tsm", - .ci_type = &tsm_root_group_type, - }, - }, - .su_mutex = __MUTEX_INITIALIZER(tsm_configfs.su_mutex), -}; - -int tsm_report_register(const struct tsm_report_ops *ops, void *priv) -{ - const struct tsm_report_ops *conflict; - - guard(rwsem_write)(&tsm_rwsem); - conflict = provider.ops; - if (conflict) { - pr_err("\"%s\" ops already registered\n", conflict->name); - return -EBUSY; - } - - provider.ops = ops; - provider.data = priv; - return 0; -} -EXPORT_SYMBOL_GPL(tsm_report_register); - -int tsm_report_unregister(const struct tsm_report_ops *ops) -{ - guard(rwsem_write)(&tsm_rwsem); - if (ops != provider.ops) - return -EBUSY; - provider.ops = NULL; - provider.data = NULL; - return 0; -} -EXPORT_SYMBOL_GPL(tsm_report_unregister); - -static struct config_group *tsm_report_group; - -static int __init tsm_report_init(void) -{ - struct config_group *root = &tsm_configfs.su_group; - struct config_group *tsm; - int rc; - - config_group_init(root); - rc = configfs_register_subsystem(&tsm_configfs); - if (rc) - return rc; - - tsm = configfs_register_default_group(root, "report", - &tsm_reports_type); - if (IS_ERR(tsm)) { - configfs_unregister_subsystem(&tsm_configfs); - return PTR_ERR(tsm); - } - tsm_report_group = tsm; - - return 0; -} -module_init(tsm_report_init); - -static void __exit tsm_report_exit(void) -{ - configfs_unregister_default_group(tsm_report_group); - configfs_unregister_subsystem(&tsm_configfs); -} -module_exit(tsm_report_exit); - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("Provide Trusted Security Module attestation reports via configfs"); -- cgit v1.2.3