summaryrefslogtreecommitdiff
path: root/drivers/virt
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/virt')
-rw-r--r--drivers/virt/coco/sev-guest/sev-guest.c211
-rw-r--r--drivers/virt/coco/tdx-guest/tdx-guest.c26
-rw-r--r--drivers/virt/coco/tsm.c177
3 files changed, 362 insertions, 52 deletions
diff --git a/drivers/virt/coco/sev-guest/sev-guest.c b/drivers/virt/coco/sev-guest/sev-guest.c
index 654290a8e1ba..f714009b9ff7 100644
--- a/drivers/virt/coco/sev-guest/sev-guest.c
+++ b/drivers/virt/coco/sev-guest/sev-guest.c
@@ -2,7 +2,7 @@
/*
* AMD Secure Encrypted Virtualization (SEV) guest driver interface
*
- * Copyright (C) 2021 Advanced Micro Devices, Inc.
+ * Copyright (C) 2021-2024 Advanced Micro Devices, Inc.
*
* Author: Brijesh Singh <brijesh.singh@amd.com>
*/
@@ -23,6 +23,7 @@
#include <linux/sockptr.h>
#include <linux/cleanup.h>
#include <linux/uuid.h>
+#include <linux/configfs.h>
#include <uapi/linux/sev-guest.h>
#include <uapi/linux/psp-sev.h>
@@ -38,6 +39,8 @@
#define SNP_REQ_MAX_RETRY_DURATION (60*HZ)
#define SNP_REQ_RETRY_DELAY (2*HZ)
+#define SVSM_MAX_RETRIES 3
+
struct snp_guest_crypto {
struct crypto_aead *tfm;
u8 *iv, *authtag;
@@ -70,8 +73,15 @@ struct snp_guest_dev {
u8 *vmpck;
};
-static u32 vmpck_id;
-module_param(vmpck_id, uint, 0444);
+/*
+ * The VMPCK ID represents the key used by the SNP guest to communicate with the
+ * SEV firmware in the AMD Secure Processor (ASP, aka PSP). By default, the key
+ * used will be the key associated with the VMPL at which the guest is running.
+ * Should the default key be wiped (see snp_disable_vmpck()), this parameter
+ * allows for using one of the remaining VMPCKs.
+ */
+static int vmpck_id = -1;
+module_param(vmpck_id, int, 0444);
MODULE_PARM_DESC(vmpck_id, "The VMPCK ID to use when communicating with the PSP.");
/* Mutex to serialize the shared buffer access and command handling. */
@@ -783,6 +793,143 @@ struct snp_msg_cert_entry {
u32 length;
};
+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 svsm_attest_call ac = {};
+ unsigned int retry_count;
+ void *rep, *man, *certs;
+ struct svsm_call call;
+ unsigned int size;
+ bool try_again;
+ void *buffer;
+ u64 call_id;
+ int ret;
+
+ /*
+ * Allocate pages for the request:
+ * - Report blob (4K)
+ * - Manifest blob (4K)
+ * - Certificate blob (16K)
+ *
+ * Above addresses must be 4K aligned
+ */
+ rep_len = SZ_4K;
+ man_len = SZ_4K;
+ certs_len = SEV_FW_BLOB_MAX_SIZE;
+
+ guard(mutex)(&snp_cmd_mutex);
+
+ if (guid_is_null(&desc->service_guid)) {
+ call_id = SVSM_ATTEST_CALL(SVSM_ATTEST_SERVICES);
+ } else {
+ export_guid(ac.service_guid, &desc->service_guid);
+ ac.service_manifest_ver = desc->service_manifest_version;
+
+ call_id = SVSM_ATTEST_CALL(SVSM_ATTEST_SINGLE_SERVICE);
+ }
+
+ retry_count = 0;
+
+retry:
+ memset(&call, 0, sizeof(call));
+
+ size = rep_len + man_len + certs_len;
+ buffer = alloc_pages_exact(size, __GFP_ZERO);
+ if (!buffer)
+ return -ENOMEM;
+
+ rep = buffer;
+ ac.report_buf.pa = __pa(rep);
+ ac.report_buf.len = rep_len;
+
+ man = rep + rep_len;
+ ac.manifest_buf.pa = __pa(man);
+ ac.manifest_buf.len = man_len;
+
+ certs = man + man_len;
+ ac.certificates_buf.pa = __pa(certs);
+ ac.certificates_buf.len = certs_len;
+
+ ac.nonce.pa = __pa(desc->inblob);
+ ac.nonce.len = desc->inblob_len;
+
+ ret = snp_issue_svsm_attest_req(call_id, &call, &ac);
+ if (ret) {
+ free_pages_exact(buffer, size);
+
+ switch (call.rax_out) {
+ case SVSM_ERR_INVALID_PARAMETER:
+ try_again = false;
+
+ if (ac.report_buf.len > rep_len) {
+ rep_len = PAGE_ALIGN(ac.report_buf.len);
+ try_again = true;
+ }
+
+ if (ac.manifest_buf.len > man_len) {
+ man_len = PAGE_ALIGN(ac.manifest_buf.len);
+ try_again = true;
+ }
+
+ if (ac.certificates_buf.len > certs_len) {
+ certs_len = PAGE_ALIGN(ac.certificates_buf.len);
+ try_again = true;
+ }
+
+ /* If one of the buffers wasn't large enough, retry the request */
+ if (try_again && retry_count < SVSM_MAX_RETRIES) {
+ retry_count++;
+ goto retry;
+ }
+
+ return -EINVAL;
+ default:
+ pr_err_ratelimited("SVSM attestation request failed (%d / 0x%llx)\n",
+ ret, call.rax_out);
+ return -EINVAL;
+ }
+ }
+
+ /*
+ * Allocate all the blob memory buffers at once so that the cleanup is
+ * done for errors that occur after the first allocation (i.e. before
+ * using no_free_ptr()).
+ */
+ rep_len = ac.report_buf.len;
+ void *rbuf __free(kvfree) = kvzalloc(rep_len, GFP_KERNEL);
+
+ man_len = ac.manifest_buf.len;
+ void *mbuf __free(kvfree) = kvzalloc(man_len, GFP_KERNEL);
+
+ certs_len = ac.certificates_buf.len;
+ void *cbuf __free(kvfree) = certs_len ? kvzalloc(certs_len, GFP_KERNEL) : NULL;
+
+ if (!rbuf || !mbuf || (certs_len && !cbuf)) {
+ free_pages_exact(buffer, size);
+ return -ENOMEM;
+ }
+
+ memcpy(rbuf, rep, rep_len);
+ report->outblob = no_free_ptr(rbuf);
+ report->outblob_len = rep_len;
+
+ memcpy(mbuf, man, man_len);
+ report->manifestblob = no_free_ptr(mbuf);
+ report->manifestblob_len = man_len;
+
+ if (certs_len) {
+ memcpy(cbuf, certs, certs_len);
+ report->auxblob = no_free_ptr(cbuf);
+ report->auxblob_len = certs_len;
+ }
+
+ free_pages_exact(buffer, size);
+
+ return 0;
+}
+
static int sev_report_new(struct tsm_report *report, void *data)
{
struct snp_msg_cert_entry *cert_table;
@@ -797,6 +944,13 @@ static int sev_report_new(struct tsm_report *report, void *data)
if (desc->inblob_len != SNP_REPORT_USER_DATA_SIZE)
return -EINVAL;
+ if (desc->service_provider) {
+ if (strcmp(desc->service_provider, "svsm"))
+ return -EINVAL;
+
+ return sev_svsm_report_new(report, data);
+ }
+
void *buf __free(kvfree) = kvzalloc(size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
@@ -885,9 +1039,42 @@ static int sev_report_new(struct tsm_report *report, void *data)
return 0;
}
-static const struct tsm_ops sev_tsm_ops = {
+static bool sev_report_attr_visible(int n)
+{
+ switch (n) {
+ case TSM_REPORT_GENERATION:
+ case TSM_REPORT_PROVIDER:
+ case TSM_REPORT_PRIVLEVEL:
+ case TSM_REPORT_PRIVLEVEL_FLOOR:
+ return true;
+ case TSM_REPORT_SERVICE_PROVIDER:
+ case TSM_REPORT_SERVICE_GUID:
+ case TSM_REPORT_SERVICE_MANIFEST_VER:
+ return snp_vmpl;
+ }
+
+ return false;
+}
+
+static bool sev_report_bin_attr_visible(int n)
+{
+ switch (n) {
+ case TSM_REPORT_INBLOB:
+ case TSM_REPORT_OUTBLOB:
+ case TSM_REPORT_AUXBLOB:
+ return true;
+ case TSM_REPORT_MANIFESTBLOB:
+ return snp_vmpl;
+ }
+
+ return false;
+}
+
+static struct tsm_ops sev_tsm_ops = {
.name = KBUILD_MODNAME,
.report_new = sev_report_new,
+ .report_attr_visible = sev_report_attr_visible,
+ .report_bin_attr_visible = sev_report_bin_attr_visible,
};
static void unregister_sev_tsm(void *data)
@@ -923,6 +1110,10 @@ static int __init sev_guest_probe(struct platform_device *pdev)
if (!snp_dev)
goto e_unmap;
+ /* Adjust the default VMPCK key based on the executing VMPL level */
+ if (vmpck_id == -1)
+ vmpck_id = snp_vmpl;
+
ret = -EINVAL;
snp_dev->vmpck = get_vmpck(vmpck_id, secrets, &snp_dev->os_area_msg_seqno);
if (!snp_dev->vmpck) {
@@ -968,7 +1159,10 @@ static int __init sev_guest_probe(struct platform_device *pdev)
snp_dev->input.resp_gpa = __pa(snp_dev->response);
snp_dev->input.data_gpa = __pa(snp_dev->certs_data);
- ret = tsm_register(&sev_tsm_ops, snp_dev, &tsm_report_extra_type);
+ /* Set the privlevel_floor attribute based on the vmpck_id */
+ sev_tsm_ops.privlevel_floor = vmpck_id;
+
+ ret = tsm_register(&sev_tsm_ops, snp_dev);
if (ret)
goto e_free_cert_data;
@@ -1009,8 +1203,13 @@ static void __exit sev_guest_remove(struct platform_device *pdev)
* This driver is meant to be a common SEV guest interface driver and to
* support any SEV guest API. As such, even though it has been introduced
* with the SEV-SNP support, it is named "sev-guest".
+ *
+ * sev_guest_remove() lives in .exit.text. For drivers registered via
+ * module_platform_driver_probe() this is ok because they cannot get unbound
+ * at runtime. So mark the driver struct with __refdata to prevent modpost
+ * triggering a section mismatch warning.
*/
-static struct platform_driver sev_guest_driver = {
+static struct platform_driver sev_guest_driver __refdata = {
.remove_new = __exit_p(sev_guest_remove),
.driver = {
.name = "sev-guest",
diff --git a/drivers/virt/coco/tdx-guest/tdx-guest.c b/drivers/virt/coco/tdx-guest/tdx-guest.c
index 1253bf76b570..2acba56ad42e 100644
--- a/drivers/virt/coco/tdx-guest/tdx-guest.c
+++ b/drivers/virt/coco/tdx-guest/tdx-guest.c
@@ -249,6 +249,28 @@ done:
return ret;
}
+static bool tdx_report_attr_visible(int n)
+{
+ switch (n) {
+ case TSM_REPORT_GENERATION:
+ case TSM_REPORT_PROVIDER:
+ return true;
+ }
+
+ return false;
+}
+
+static bool tdx_report_bin_attr_visible(int n)
+{
+ switch (n) {
+ case TSM_REPORT_INBLOB:
+ case TSM_REPORT_OUTBLOB:
+ return true;
+ }
+
+ return false;
+}
+
static long tdx_guest_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
@@ -281,6 +303,8 @@ MODULE_DEVICE_TABLE(x86cpu, tdx_guest_ids);
static const struct tsm_ops tdx_tsm_ops = {
.name = KBUILD_MODNAME,
.report_new = tdx_report_new,
+ .report_attr_visible = tdx_report_attr_visible,
+ .report_bin_attr_visible = tdx_report_bin_attr_visible,
};
static int __init tdx_guest_init(void)
@@ -301,7 +325,7 @@ static int __init tdx_guest_init(void)
goto free_misc;
}
- ret = tsm_register(&tdx_tsm_ops, NULL, NULL);
+ ret = tsm_register(&tdx_tsm_ops, NULL);
if (ret)
goto free_quote;
diff --git a/drivers/virt/coco/tsm.c b/drivers/virt/coco/tsm.c
index d1c2db83a8ca..9432d4e303f1 100644
--- a/drivers/virt/coco/tsm.c
+++ b/drivers/virt/coco/tsm.c
@@ -14,7 +14,6 @@
static struct tsm_provider {
const struct tsm_ops *ops;
- const struct config_item_type *type;
void *data;
} provider;
static DECLARE_RWSEM(tsm_rwsem);
@@ -35,7 +34,7 @@ static DECLARE_RWSEM(tsm_rwsem);
* 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'.
+ * 'auxblob' and 'manifestblob'.
*/
struct tsm_report_state {
@@ -48,6 +47,7 @@ struct tsm_report_state {
enum tsm_data_select {
TSM_REPORT,
TSM_CERTS,
+ TSM_MANIFEST,
};
static struct tsm_report *to_tsm_report(struct config_item *cfg)
@@ -119,6 +119,74 @@ static ssize_t tsm_report_privlevel_floor_show(struct config_item *cfg,
}
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)
{
@@ -163,6 +231,9 @@ static ssize_t __read_report(struct tsm_report *report, void *buf, size_t count,
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;
@@ -188,7 +259,7 @@ static ssize_t read_cached_report(struct tsm_report *report, void *buf,
/*
* A given TSM backend always fills in ->outblob regardless of
- * whether the report includes an auxblob or not.
+ * whether the report includes an auxblob/manifestblob or not.
*/
if (!report->outblob ||
state->read_generation != state->write_generation)
@@ -224,8 +295,10 @@ static ssize_t tsm_report_read(struct tsm_report *report, void *buf,
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;
@@ -252,34 +325,31 @@ static ssize_t tsm_report_auxblob_read(struct config_item *cfg, void *buf,
}
CONFIGFS_BIN_ATTR_RO(tsm_report_, auxblob, NULL, TSM_OUTBLOB_MAX);
-#define TSM_DEFAULT_ATTRS() \
- &tsm_report_attr_generation, \
- &tsm_report_attr_provider
+static ssize_t tsm_report_manifestblob_read(struct config_item *cfg, void *buf,
+ size_t count)
+{
+ struct tsm_report *report = to_tsm_report(cfg);
-static struct configfs_attribute *tsm_report_attrs[] = {
- TSM_DEFAULT_ATTRS(),
- NULL,
-};
+ return tsm_report_read(report, buf, count, TSM_MANIFEST);
+}
+CONFIGFS_BIN_ATTR_RO(tsm_report_, manifestblob, NULL, TSM_OUTBLOB_MAX);
-static struct configfs_attribute *tsm_report_extra_attrs[] = {
- TSM_DEFAULT_ATTRS(),
- &tsm_report_attr_privlevel,
- &tsm_report_attr_privlevel_floor,
+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,
};
-#define TSM_DEFAULT_BIN_ATTRS() \
- &tsm_report_attr_inblob, \
- &tsm_report_attr_outblob
-
static struct configfs_bin_attribute *tsm_report_bin_attrs[] = {
- TSM_DEFAULT_BIN_ATTRS(),
- NULL,
-};
-
-static struct configfs_bin_attribute *tsm_report_bin_extra_attrs[] = {
- TSM_DEFAULT_BIN_ATTRS(),
- &tsm_report_attr_auxblob,
+ [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,
};
@@ -288,8 +358,10 @@ 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);
}
@@ -297,21 +369,44 @@ static struct configfs_item_operations tsm_report_item_ops = {
.release = tsm_report_item_release,
};
-const struct config_item_type tsm_report_default_type = {
- .ct_owner = THIS_MODULE,
- .ct_bin_attrs = tsm_report_bin_attrs,
- .ct_attrs = tsm_report_attrs,
- .ct_item_ops = &tsm_report_item_ops,
+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,
};
-EXPORT_SYMBOL_GPL(tsm_report_default_type);
-const struct config_item_type tsm_report_extra_type = {
+static const struct config_item_type tsm_report_type = {
.ct_owner = THIS_MODULE,
- .ct_bin_attrs = tsm_report_bin_extra_attrs,
- .ct_attrs = tsm_report_extra_attrs,
+ .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,
};
-EXPORT_SYMBOL_GPL(tsm_report_extra_type);
static struct config_item *tsm_report_make_item(struct config_group *group,
const char *name)
@@ -326,7 +421,7 @@ static struct config_item *tsm_report_make_item(struct config_group *group,
if (!state)
return ERR_PTR(-ENOMEM);
- config_item_init_type_name(&state->cfg, name, provider.type);
+ config_item_init_type_name(&state->cfg, name, &tsm_report_type);
return &state->cfg;
}
@@ -353,16 +448,10 @@ static struct configfs_subsystem tsm_configfs = {
.su_mutex = __MUTEX_INITIALIZER(tsm_configfs.su_mutex),
};
-int tsm_register(const struct tsm_ops *ops, void *priv,
- const struct config_item_type *type)
+int tsm_register(const struct tsm_ops *ops, void *priv)
{
const struct tsm_ops *conflict;
- if (!type)
- type = &tsm_report_default_type;
- if (!(type == &tsm_report_default_type || type == &tsm_report_extra_type))
- return -EINVAL;
-
guard(rwsem_write)(&tsm_rwsem);
conflict = provider.ops;
if (conflict) {
@@ -372,7 +461,6 @@ int tsm_register(const struct tsm_ops *ops, void *priv,
provider.ops = ops;
provider.data = priv;
- provider.type = type;
return 0;
}
EXPORT_SYMBOL_GPL(tsm_register);
@@ -384,7 +472,6 @@ int tsm_unregister(const struct tsm_ops *ops)
return -EBUSY;
provider.ops = NULL;
provider.data = NULL;
- provider.type = NULL;
return 0;
}
EXPORT_SYMBOL_GPL(tsm_unregister);