From fce96cf0443083e37455eff8f78fd240c621dae3 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Mon, 7 Mar 2022 15:33:53 -0600 Subject: virt: Add SEV-SNP guest driver The SEV-SNP specification provides the guest a mechanism to communicate with the PSP without risk from a malicious hypervisor who wishes to read, alter, drop or replay the messages sent. The driver uses snp_issue_guest_request() to issue GHCB SNP_GUEST_REQUEST or SNP_EXT_GUEST_REQUEST NAE events to submit the request to PSP. The PSP requires that all communication should be encrypted using key specified through a struct snp_guest_platform_data descriptor. Userspace can use SNP_GET_REPORT ioctl() to query the guest attestation report. See SEV-SNP spec section Guest Messages for more details. [ bp: Remove the "what" from the commit message, massage. ] Signed-off-by: Brijesh Singh Signed-off-by: Borislav Petkov Link: https://lore.kernel.org/r/20220307213356.2797205-44-brijesh.singh@amd.com --- drivers/virt/Kconfig | 3 + drivers/virt/Makefile | 1 + drivers/virt/coco/sevguest/Kconfig | 14 + drivers/virt/coco/sevguest/Makefile | 2 + drivers/virt/coco/sevguest/sevguest.c | 607 ++++++++++++++++++++++++++++++++++ drivers/virt/coco/sevguest/sevguest.h | 98 ++++++ 6 files changed, 725 insertions(+) create mode 100644 drivers/virt/coco/sevguest/Kconfig create mode 100644 drivers/virt/coco/sevguest/Makefile create mode 100644 drivers/virt/coco/sevguest/sevguest.c create mode 100644 drivers/virt/coco/sevguest/sevguest.h (limited to 'drivers') diff --git a/drivers/virt/Kconfig b/drivers/virt/Kconfig index 121b9293c737..7d3273cfab27 100644 --- a/drivers/virt/Kconfig +++ b/drivers/virt/Kconfig @@ -47,4 +47,7 @@ source "drivers/virt/vboxguest/Kconfig" source "drivers/virt/nitro_enclaves/Kconfig" source "drivers/virt/acrn/Kconfig" + +source "drivers/virt/coco/sevguest/Kconfig" + endif diff --git a/drivers/virt/Makefile b/drivers/virt/Makefile index 108d0ffcc9aa..7b87a7ba1972 100644 --- a/drivers/virt/Makefile +++ b/drivers/virt/Makefile @@ -9,3 +9,4 @@ obj-y += vboxguest/ obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves/ obj-$(CONFIG_ACRN_HSM) += acrn/ +obj-$(CONFIG_SEV_GUEST) += coco/sevguest/ diff --git a/drivers/virt/coco/sevguest/Kconfig b/drivers/virt/coco/sevguest/Kconfig new file mode 100644 index 000000000000..74ca1fe09437 --- /dev/null +++ b/drivers/virt/coco/sevguest/Kconfig @@ -0,0 +1,14 @@ +config SEV_GUEST + tristate "AMD SEV Guest driver" + default m + depends on AMD_MEM_ENCRYPT + select CRYPTO_AEAD2 + select CRYPTO_GCM + help + SEV-SNP firmware provides the guest a mechanism to communicate with + the PSP without risk from a malicious hypervisor who wishes to read, + alter, drop or replay the messages sent. The driver provides + userspace interface to communicate with the PSP to request the + attestation report and more. + + If you choose 'M' here, this module will be called sevguest. diff --git a/drivers/virt/coco/sevguest/Makefile b/drivers/virt/coco/sevguest/Makefile new file mode 100644 index 000000000000..b1ffb2b4177b --- /dev/null +++ b/drivers/virt/coco/sevguest/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_SEV_GUEST) += sevguest.o diff --git a/drivers/virt/coco/sevguest/sevguest.c b/drivers/virt/coco/sevguest/sevguest.c new file mode 100644 index 000000000000..beda93cdeb4f --- /dev/null +++ b/drivers/virt/coco/sevguest/sevguest.c @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AMD Secure Encrypted Virtualization Nested Paging (SEV-SNP) guest request interface + * + * Copyright (C) 2021 Advanced Micro Devices, Inc. + * + * Author: Brijesh Singh + */ + +#define pr_fmt(fmt) "SNP: GUEST: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sevguest.h" + +#define DEVICE_NAME "sev-guest" +#define AAD_LEN 48 +#define MSG_HDR_VER 1 + +struct snp_guest_crypto { + struct crypto_aead *tfm; + u8 *iv, *authtag; + int iv_len, a_len; +}; + +struct snp_guest_dev { + struct device *dev; + struct miscdevice misc; + + struct snp_guest_crypto *crypto; + struct snp_guest_msg *request, *response; + struct snp_secrets_page_layout *layout; + struct snp_req_data input; + u32 *os_area_msg_seqno; + u8 *vmpck; +}; + +static u32 vmpck_id; +module_param(vmpck_id, uint, 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. */ +static DEFINE_MUTEX(snp_cmd_mutex); + +static bool is_vmpck_empty(struct snp_guest_dev *snp_dev) +{ + char zero_key[VMPCK_KEY_LEN] = {0}; + + if (snp_dev->vmpck) + return !memcmp(snp_dev->vmpck, zero_key, VMPCK_KEY_LEN); + + return true; +} + +static void snp_disable_vmpck(struct snp_guest_dev *snp_dev) +{ + memzero_explicit(snp_dev->vmpck, VMPCK_KEY_LEN); + snp_dev->vmpck = NULL; +} + +static inline u64 __snp_get_msg_seqno(struct snp_guest_dev *snp_dev) +{ + u64 count; + + lockdep_assert_held(&snp_cmd_mutex); + + /* Read the current message sequence counter from secrets pages */ + count = *snp_dev->os_area_msg_seqno; + + return count + 1; +} + +/* Return a non-zero on success */ +static u64 snp_get_msg_seqno(struct snp_guest_dev *snp_dev) +{ + u64 count = __snp_get_msg_seqno(snp_dev); + + /* + * The message sequence counter for the SNP guest request is a 64-bit + * value but the version 2 of GHCB specification defines a 32-bit storage + * for it. If the counter exceeds the 32-bit value then return zero. + * The caller should check the return value, but if the caller happens to + * not check the value and use it, then the firmware treats zero as an + * invalid number and will fail the message request. + */ + if (count >= UINT_MAX) { + dev_err(snp_dev->dev, "request message sequence counter overflow\n"); + return 0; + } + + return count; +} + +static void snp_inc_msg_seqno(struct snp_guest_dev *snp_dev) +{ + /* + * The counter is also incremented by the PSP, so increment it by 2 + * and save in secrets page. + */ + *snp_dev->os_area_msg_seqno += 2; +} + +static inline struct snp_guest_dev *to_snp_dev(struct file *file) +{ + struct miscdevice *dev = file->private_data; + + return container_of(dev, struct snp_guest_dev, misc); +} + +static struct snp_guest_crypto *init_crypto(struct snp_guest_dev *snp_dev, u8 *key, size_t keylen) +{ + struct snp_guest_crypto *crypto; + + crypto = kzalloc(sizeof(*crypto), GFP_KERNEL_ACCOUNT); + if (!crypto) + return NULL; + + crypto->tfm = crypto_alloc_aead("gcm(aes)", 0, 0); + if (IS_ERR(crypto->tfm)) + goto e_free; + + if (crypto_aead_setkey(crypto->tfm, key, keylen)) + goto e_free_crypto; + + crypto->iv_len = crypto_aead_ivsize(crypto->tfm); + crypto->iv = kmalloc(crypto->iv_len, GFP_KERNEL_ACCOUNT); + if (!crypto->iv) + goto e_free_crypto; + + if (crypto_aead_authsize(crypto->tfm) > MAX_AUTHTAG_LEN) { + if (crypto_aead_setauthsize(crypto->tfm, MAX_AUTHTAG_LEN)) { + dev_err(snp_dev->dev, "failed to set authsize to %d\n", MAX_AUTHTAG_LEN); + goto e_free_iv; + } + } + + crypto->a_len = crypto_aead_authsize(crypto->tfm); + crypto->authtag = kmalloc(crypto->a_len, GFP_KERNEL_ACCOUNT); + if (!crypto->authtag) + goto e_free_auth; + + return crypto; + +e_free_auth: + kfree(crypto->authtag); +e_free_iv: + kfree(crypto->iv); +e_free_crypto: + crypto_free_aead(crypto->tfm); +e_free: + kfree(crypto); + + return NULL; +} + +static void deinit_crypto(struct snp_guest_crypto *crypto) +{ + crypto_free_aead(crypto->tfm); + kfree(crypto->iv); + kfree(crypto->authtag); + kfree(crypto); +} + +static int enc_dec_message(struct snp_guest_crypto *crypto, struct snp_guest_msg *msg, + u8 *src_buf, u8 *dst_buf, size_t len, bool enc) +{ + struct snp_guest_msg_hdr *hdr = &msg->hdr; + struct scatterlist src[3], dst[3]; + DECLARE_CRYPTO_WAIT(wait); + struct aead_request *req; + int ret; + + req = aead_request_alloc(crypto->tfm, GFP_KERNEL); + if (!req) + return -ENOMEM; + + /* + * AEAD memory operations: + * +------ AAD -------+------- DATA -----+---- AUTHTAG----+ + * | msg header | plaintext | hdr->authtag | + * | bytes 30h - 5Fh | or | | + * | | cipher | | + * +------------------+------------------+----------------+ + */ + sg_init_table(src, 3); + sg_set_buf(&src[0], &hdr->algo, AAD_LEN); + sg_set_buf(&src[1], src_buf, hdr->msg_sz); + sg_set_buf(&src[2], hdr->authtag, crypto->a_len); + + sg_init_table(dst, 3); + sg_set_buf(&dst[0], &hdr->algo, AAD_LEN); + sg_set_buf(&dst[1], dst_buf, hdr->msg_sz); + sg_set_buf(&dst[2], hdr->authtag, crypto->a_len); + + aead_request_set_ad(req, AAD_LEN); + aead_request_set_tfm(req, crypto->tfm); + aead_request_set_callback(req, 0, crypto_req_done, &wait); + + aead_request_set_crypt(req, src, dst, len, crypto->iv); + ret = crypto_wait_req(enc ? crypto_aead_encrypt(req) : crypto_aead_decrypt(req), &wait); + + aead_request_free(req); + return ret; +} + +static int __enc_payload(struct snp_guest_dev *snp_dev, struct snp_guest_msg *msg, + void *plaintext, size_t len) +{ + struct snp_guest_crypto *crypto = snp_dev->crypto; + struct snp_guest_msg_hdr *hdr = &msg->hdr; + + memset(crypto->iv, 0, crypto->iv_len); + memcpy(crypto->iv, &hdr->msg_seqno, sizeof(hdr->msg_seqno)); + + return enc_dec_message(crypto, msg, plaintext, msg->payload, len, true); +} + +static int dec_payload(struct snp_guest_dev *snp_dev, struct snp_guest_msg *msg, + void *plaintext, size_t len) +{ + struct snp_guest_crypto *crypto = snp_dev->crypto; + struct snp_guest_msg_hdr *hdr = &msg->hdr; + + /* Build IV with response buffer sequence number */ + memset(crypto->iv, 0, crypto->iv_len); + memcpy(crypto->iv, &hdr->msg_seqno, sizeof(hdr->msg_seqno)); + + return enc_dec_message(crypto, msg, msg->payload, plaintext, len, false); +} + +static int verify_and_dec_payload(struct snp_guest_dev *snp_dev, void *payload, u32 sz) +{ + struct snp_guest_crypto *crypto = snp_dev->crypto; + struct snp_guest_msg *resp = snp_dev->response; + struct snp_guest_msg *req = snp_dev->request; + struct snp_guest_msg_hdr *req_hdr = &req->hdr; + struct snp_guest_msg_hdr *resp_hdr = &resp->hdr; + + dev_dbg(snp_dev->dev, "response [seqno %lld type %d version %d sz %d]\n", + resp_hdr->msg_seqno, resp_hdr->msg_type, resp_hdr->msg_version, resp_hdr->msg_sz); + + /* Verify that the sequence counter is incremented by 1 */ + if (unlikely(resp_hdr->msg_seqno != (req_hdr->msg_seqno + 1))) + return -EBADMSG; + + /* Verify response message type and version number. */ + if (resp_hdr->msg_type != (req_hdr->msg_type + 1) || + resp_hdr->msg_version != req_hdr->msg_version) + return -EBADMSG; + + /* + * If the message size is greater than our buffer length then return + * an error. + */ + if (unlikely((resp_hdr->msg_sz + crypto->a_len) > sz)) + return -EBADMSG; + + /* Decrypt the payload */ + return dec_payload(snp_dev, resp, payload, resp_hdr->msg_sz + crypto->a_len); +} + +static bool enc_payload(struct snp_guest_dev *snp_dev, u64 seqno, int version, u8 type, + void *payload, size_t sz) +{ + struct snp_guest_msg *req = snp_dev->request; + struct snp_guest_msg_hdr *hdr = &req->hdr; + + memset(req, 0, sizeof(*req)); + + hdr->algo = SNP_AEAD_AES_256_GCM; + hdr->hdr_version = MSG_HDR_VER; + hdr->hdr_sz = sizeof(*hdr); + hdr->msg_type = type; + hdr->msg_version = version; + hdr->msg_seqno = seqno; + hdr->msg_vmpck = vmpck_id; + hdr->msg_sz = sz; + + /* Verify the sequence number is non-zero */ + if (!hdr->msg_seqno) + return -ENOSR; + + dev_dbg(snp_dev->dev, "request [seqno %lld type %d version %d sz %d]\n", + hdr->msg_seqno, hdr->msg_type, hdr->msg_version, hdr->msg_sz); + + return __enc_payload(snp_dev, req, payload, sz); +} + +static int handle_guest_request(struct snp_guest_dev *snp_dev, u64 exit_code, int msg_ver, + u8 type, void *req_buf, size_t req_sz, void *resp_buf, + u32 resp_sz, __u64 *fw_err) +{ + unsigned long err; + u64 seqno; + int rc; + + /* Get message sequence and verify that its a non-zero */ + seqno = snp_get_msg_seqno(snp_dev); + if (!seqno) + return -EIO; + + memset(snp_dev->response, 0, sizeof(struct snp_guest_msg)); + + /* Encrypt the userspace provided payload */ + rc = enc_payload(snp_dev, seqno, msg_ver, type, req_buf, req_sz); + if (rc) + return rc; + + /* Call firmware to process the request */ + rc = snp_issue_guest_request(exit_code, &snp_dev->input, &err); + if (fw_err) + *fw_err = err; + + if (rc) + return rc; + + /* + * The verify_and_dec_payload() will fail only if the hypervisor is + * actively modifying the message header or corrupting the encrypted payload. + * This hints that hypervisor is acting in a bad faith. Disable the VMPCK so that + * the key cannot be used for any communication. The key is disabled to ensure + * that AES-GCM does not use the same IV while encrypting the request payload. + */ + rc = verify_and_dec_payload(snp_dev, resp_buf, resp_sz); + if (rc) { + dev_alert(snp_dev->dev, + "Detected unexpected decode failure, disabling the vmpck_id %d\n", + vmpck_id); + snp_disable_vmpck(snp_dev); + return rc; + } + + /* Increment to new message sequence after payload decryption was successful. */ + snp_inc_msg_seqno(snp_dev); + + return 0; +} + +static int get_report(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg) +{ + struct snp_guest_crypto *crypto = snp_dev->crypto; + struct snp_report_resp *resp; + struct snp_report_req req; + int rc, resp_len; + + lockdep_assert_held(&snp_cmd_mutex); + + if (!arg->req_data || !arg->resp_data) + return -EINVAL; + + if (copy_from_user(&req, (void __user *)arg->req_data, sizeof(req))) + return -EFAULT; + + /* + * The intermediate response buffer is used while decrypting the + * response payload. Make sure that it has enough space to cover the + * authtag. + */ + resp_len = sizeof(resp->data) + crypto->a_len; + resp = kzalloc(resp_len, GFP_KERNEL_ACCOUNT); + if (!resp) + return -ENOMEM; + + rc = handle_guest_request(snp_dev, SVM_VMGEXIT_GUEST_REQUEST, arg->msg_version, + SNP_MSG_REPORT_REQ, &req, sizeof(req), resp->data, + resp_len, &arg->fw_err); + if (rc) + goto e_free; + + if (copy_to_user((void __user *)arg->resp_data, resp, sizeof(*resp))) + rc = -EFAULT; + +e_free: + kfree(resp); + return rc; +} + +static long snp_guest_ioctl(struct file *file, unsigned int ioctl, unsigned long arg) +{ + struct snp_guest_dev *snp_dev = to_snp_dev(file); + void __user *argp = (void __user *)arg; + struct snp_guest_request_ioctl input; + int ret = -ENOTTY; + + if (copy_from_user(&input, argp, sizeof(input))) + return -EFAULT; + + input.fw_err = 0xff; + + /* Message version must be non-zero */ + if (!input.msg_version) + return -EINVAL; + + mutex_lock(&snp_cmd_mutex); + + /* Check if the VMPCK is not empty */ + if (is_vmpck_empty(snp_dev)) { + dev_err_ratelimited(snp_dev->dev, "VMPCK is disabled\n"); + mutex_unlock(&snp_cmd_mutex); + return -ENOTTY; + } + + switch (ioctl) { + case SNP_GET_REPORT: + ret = get_report(snp_dev, &input); + break; + default: + break; + } + + mutex_unlock(&snp_cmd_mutex); + + if (input.fw_err && copy_to_user(argp, &input, sizeof(input))) + return -EFAULT; + + return ret; +} + +static void free_shared_pages(void *buf, size_t sz) +{ + unsigned int npages = PAGE_ALIGN(sz) >> PAGE_SHIFT; + int ret; + + if (!buf) + return; + + ret = set_memory_encrypted((unsigned long)buf, npages); + if (ret) { + WARN_ONCE(ret, "failed to restore encryption mask (leak it)\n"); + return; + } + + __free_pages(virt_to_page(buf), get_order(sz)); +} + +static void *alloc_shared_pages(size_t sz) +{ + unsigned int npages = PAGE_ALIGN(sz) >> PAGE_SHIFT; + struct page *page; + int ret; + + page = alloc_pages(GFP_KERNEL_ACCOUNT, get_order(sz)); + if (IS_ERR(page)) + return NULL; + + ret = set_memory_decrypted((unsigned long)page_address(page), npages); + if (ret) { + pr_err("failed to mark page shared, ret=%d\n", ret); + __free_pages(page, get_order(sz)); + return NULL; + } + + return page_address(page); +} + +static const struct file_operations snp_guest_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = snp_guest_ioctl, +}; + +static u8 *get_vmpck(int id, struct snp_secrets_page_layout *layout, u32 **seqno) +{ + u8 *key = NULL; + + switch (id) { + case 0: + *seqno = &layout->os_area.msg_seqno_0; + key = layout->vmpck0; + break; + case 1: + *seqno = &layout->os_area.msg_seqno_1; + key = layout->vmpck1; + break; + case 2: + *seqno = &layout->os_area.msg_seqno_2; + key = layout->vmpck2; + break; + case 3: + *seqno = &layout->os_area.msg_seqno_3; + key = layout->vmpck3; + break; + default: + break; + } + + return key; +} + +static int __init snp_guest_probe(struct platform_device *pdev) +{ + struct snp_secrets_page_layout *layout; + struct snp_guest_platform_data *data; + struct device *dev = &pdev->dev; + struct snp_guest_dev *snp_dev; + struct miscdevice *misc; + int ret; + + if (!dev->platform_data) + return -ENODEV; + + data = (struct snp_guest_platform_data *)dev->platform_data; + layout = (__force void *)ioremap_encrypted(data->secrets_gpa, PAGE_SIZE); + if (!layout) + return -ENODEV; + + ret = -ENOMEM; + snp_dev = devm_kzalloc(&pdev->dev, sizeof(struct snp_guest_dev), GFP_KERNEL); + if (!snp_dev) + goto e_unmap; + + ret = -EINVAL; + snp_dev->vmpck = get_vmpck(vmpck_id, layout, &snp_dev->os_area_msg_seqno); + if (!snp_dev->vmpck) { + dev_err(dev, "invalid vmpck id %d\n", vmpck_id); + goto e_unmap; + } + + /* Verify that VMPCK is not zero. */ + if (is_vmpck_empty(snp_dev)) { + dev_err(dev, "vmpck id %d is null\n", vmpck_id); + goto e_unmap; + } + + platform_set_drvdata(pdev, snp_dev); + snp_dev->dev = dev; + snp_dev->layout = layout; + + /* Allocate the shared page used for the request and response message. */ + snp_dev->request = alloc_shared_pages(sizeof(struct snp_guest_msg)); + if (!snp_dev->request) + goto e_unmap; + + snp_dev->response = alloc_shared_pages(sizeof(struct snp_guest_msg)); + if (!snp_dev->response) + goto e_free_request; + + ret = -EIO; + snp_dev->crypto = init_crypto(snp_dev, snp_dev->vmpck, VMPCK_KEY_LEN); + if (!snp_dev->crypto) + goto e_free_response; + + misc = &snp_dev->misc; + misc->minor = MISC_DYNAMIC_MINOR; + misc->name = DEVICE_NAME; + misc->fops = &snp_guest_fops; + + /* initial the input address for guest request */ + snp_dev->input.req_gpa = __pa(snp_dev->request); + snp_dev->input.resp_gpa = __pa(snp_dev->response); + + ret = misc_register(misc); + if (ret) + goto e_free_response; + + dev_info(dev, "Initialized SNP guest driver (using vmpck_id %d)\n", vmpck_id); + return 0; + +e_free_response: + free_shared_pages(snp_dev->response, sizeof(struct snp_guest_msg)); +e_free_request: + free_shared_pages(snp_dev->request, sizeof(struct snp_guest_msg)); +e_unmap: + iounmap(layout); + return ret; +} + +static int __exit snp_guest_remove(struct platform_device *pdev) +{ + struct snp_guest_dev *snp_dev = platform_get_drvdata(pdev); + + free_shared_pages(snp_dev->response, sizeof(struct snp_guest_msg)); + free_shared_pages(snp_dev->request, sizeof(struct snp_guest_msg)); + deinit_crypto(snp_dev->crypto); + misc_deregister(&snp_dev->misc); + + return 0; +} + +static struct platform_driver snp_guest_driver = { + .remove = __exit_p(snp_guest_remove), + .driver = { + .name = "snp-guest", + }, +}; + +module_platform_driver_probe(snp_guest_driver, snp_guest_probe); + +MODULE_AUTHOR("Brijesh Singh "); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0.0"); +MODULE_DESCRIPTION("AMD SNP Guest Driver"); diff --git a/drivers/virt/coco/sevguest/sevguest.h b/drivers/virt/coco/sevguest/sevguest.h new file mode 100644 index 000000000000..d39bdd013765 --- /dev/null +++ b/drivers/virt/coco/sevguest/sevguest.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021 Advanced Micro Devices, Inc. + * + * Author: Brijesh Singh + * + * SEV-SNP API spec is available at https://developer.amd.com/sev + */ + +#ifndef __VIRT_SEVGUEST_H__ +#define __VIRT_SEVGUEST_H__ + +#include + +#define MAX_AUTHTAG_LEN 32 + +/* See SNP spec SNP_GUEST_REQUEST section for the structure */ +enum msg_type { + SNP_MSG_TYPE_INVALID = 0, + SNP_MSG_CPUID_REQ, + SNP_MSG_CPUID_RSP, + SNP_MSG_KEY_REQ, + SNP_MSG_KEY_RSP, + SNP_MSG_REPORT_REQ, + SNP_MSG_REPORT_RSP, + SNP_MSG_EXPORT_REQ, + SNP_MSG_EXPORT_RSP, + SNP_MSG_IMPORT_REQ, + SNP_MSG_IMPORT_RSP, + SNP_MSG_ABSORB_REQ, + SNP_MSG_ABSORB_RSP, + SNP_MSG_VMRK_REQ, + SNP_MSG_VMRK_RSP, + + SNP_MSG_TYPE_MAX +}; + +enum aead_algo { + SNP_AEAD_INVALID, + SNP_AEAD_AES_256_GCM, +}; + +struct snp_guest_msg_hdr { + u8 authtag[MAX_AUTHTAG_LEN]; + u64 msg_seqno; + u8 rsvd1[8]; + u8 algo; + u8 hdr_version; + u16 hdr_sz; + u8 msg_type; + u8 msg_version; + u16 msg_sz; + u32 rsvd2; + u8 msg_vmpck; + u8 rsvd3[35]; +} __packed; + +struct snp_guest_msg { + struct snp_guest_msg_hdr hdr; + u8 payload[4000]; +} __packed; + +/* + * The secrets page contains 96-bytes of reserved field that can be used by + * the guest OS. The guest OS uses the area to save the message sequence + * number for each VMPCK. + * + * See the GHCB spec section Secret page layout for the format for this area. + */ +struct secrets_os_area { + u32 msg_seqno_0; + u32 msg_seqno_1; + u32 msg_seqno_2; + u32 msg_seqno_3; + u64 ap_jump_table_pa; + u8 rsvd[40]; + u8 guest_usage[32]; +} __packed; + +#define VMPCK_KEY_LEN 32 + +/* See the SNP spec version 0.9 for secrets page format */ +struct snp_secrets_page_layout { + u32 version; + u32 imien : 1, + rsvd1 : 31; + u32 fms; + u32 rsvd2; + u8 gosvw[16]; + u8 vmpck0[VMPCK_KEY_LEN]; + u8 vmpck1[VMPCK_KEY_LEN]; + u8 vmpck2[VMPCK_KEY_LEN]; + u8 vmpck3[VMPCK_KEY_LEN]; + struct secrets_os_area os_area; + u8 rsvd3[3840]; +} __packed; + +#endif /* __VIRT_SEVGUEST_H__ */ -- cgit v1.2.3 From 68de0b2f938642079c0c853b219bdb88c4dc4d13 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Thu, 24 Feb 2022 10:56:23 -0600 Subject: virt: sevguest: Add support to derive key The SNP_GET_DERIVED_KEY ioctl interface can be used by the SNP guest to ask the firmware to provide a key derived from a root key. The derived key may be used by the guest for any purposes it chooses, such as a sealing key or communicating with the external entities. See SEV-SNP firmware spec for more information. [ bp: No need to memset "req" - it will get overwritten. ] Signed-off-by: Brijesh Singh Signed-off-by: Borislav Petkov Reviewed-by: Liam Merwick Link: https://lore.kernel.org/r/20220307213356.2797205-45-brijesh.singh@amd.com --- Documentation/virt/coco/sevguest.rst | 17 +++++++++++++ drivers/virt/coco/sevguest/sevguest.c | 45 +++++++++++++++++++++++++++++++++++ include/uapi/linux/sev-guest.h | 17 +++++++++++++ 3 files changed, 79 insertions(+) (limited to 'drivers') diff --git a/Documentation/virt/coco/sevguest.rst b/Documentation/virt/coco/sevguest.rst index 3da782e867a3..4135c1431241 100644 --- a/Documentation/virt/coco/sevguest.rst +++ b/Documentation/virt/coco/sevguest.rst @@ -77,6 +77,23 @@ On success, the snp_report_resp.data will contains the report. The report contain the format described in the SEV-SNP specification. See the SEV-SNP specification for further details. +2.2 SNP_GET_DERIVED_KEY +----------------------- +:Technology: sev-snp +:Type: guest ioctl +:Parameters (in): struct snp_derived_key_req +:Returns (out): struct snp_derived_key_resp on success, -negative on error + +The SNP_GET_DERIVED_KEY ioctl can be used to get a key derive from a root key. +The derived key can be used by the guest for any purpose, such as sealing keys +or communicating with external entities. + +The ioctl uses the SNP_GUEST_REQUEST (MSG_KEY_REQ) command provided by the +SEV-SNP firmware to derive the key. See SEV-SNP specification for further details +on the various fields passed in the key derivation request. + +On success, the snp_derived_key_resp.data contains the derived key value. See +the SEV-SNP specification for further details. Reference --------- diff --git a/drivers/virt/coco/sevguest/sevguest.c b/drivers/virt/coco/sevguest/sevguest.c index beda93cdeb4f..393777b72e5e 100644 --- a/drivers/virt/coco/sevguest/sevguest.c +++ b/drivers/virt/coco/sevguest/sevguest.c @@ -391,6 +391,48 @@ e_free: return rc; } +static int get_derived_key(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg) +{ + struct snp_guest_crypto *crypto = snp_dev->crypto; + struct snp_derived_key_resp resp = {0}; + struct snp_derived_key_req req; + int rc, resp_len; + /* Response data is 64 bytes and max authsize for GCM is 16 bytes. */ + u8 buf[64 + 16]; + + lockdep_assert_held(&snp_cmd_mutex); + + if (!arg->req_data || !arg->resp_data) + return -EINVAL; + + /* + * The intermediate response buffer is used while decrypting the + * response payload. Make sure that it has enough space to cover the + * authtag. + */ + resp_len = sizeof(resp.data) + crypto->a_len; + if (sizeof(buf) < resp_len) + return -ENOMEM; + + if (copy_from_user(&req, (void __user *)arg->req_data, sizeof(req))) + return -EFAULT; + + rc = handle_guest_request(snp_dev, SVM_VMGEXIT_GUEST_REQUEST, arg->msg_version, + SNP_MSG_KEY_REQ, &req, sizeof(req), buf, resp_len, + &arg->fw_err); + if (rc) + return rc; + + memcpy(resp.data, buf, sizeof(resp.data)); + if (copy_to_user((void __user *)arg->resp_data, &resp, sizeof(resp))) + rc = -EFAULT; + + /* The response buffer contains the sensitive data, explicitly clear it. */ + memzero_explicit(buf, sizeof(buf)); + memzero_explicit(&resp, sizeof(resp)); + return rc; +} + static long snp_guest_ioctl(struct file *file, unsigned int ioctl, unsigned long arg) { struct snp_guest_dev *snp_dev = to_snp_dev(file); @@ -420,6 +462,9 @@ static long snp_guest_ioctl(struct file *file, unsigned int ioctl, unsigned long case SNP_GET_REPORT: ret = get_report(snp_dev, &input); break; + case SNP_GET_DERIVED_KEY: + ret = get_derived_key(snp_dev, &input); + break; default: break; } diff --git a/include/uapi/linux/sev-guest.h b/include/uapi/linux/sev-guest.h index 38f11d723c68..598367f12064 100644 --- a/include/uapi/linux/sev-guest.h +++ b/include/uapi/linux/sev-guest.h @@ -30,6 +30,20 @@ struct snp_report_resp { __u8 data[4000]; }; +struct snp_derived_key_req { + __u32 root_key_select; + __u32 rsvd; + __u64 guest_field_select; + __u32 vmpl; + __u32 guest_svn; + __u64 tcb_version; +}; + +struct snp_derived_key_resp { + /* response data, see SEV-SNP spec for the format */ + __u8 data[64]; +}; + struct snp_guest_request_ioctl { /* message version number (must be non-zero) */ __u8 msg_version; @@ -47,4 +61,7 @@ struct snp_guest_request_ioctl { /* Get SNP attestation report */ #define SNP_GET_REPORT _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x0, struct snp_guest_request_ioctl) +/* Get a derived key from the root */ +#define SNP_GET_DERIVED_KEY _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x1, struct snp_guest_request_ioctl) + #endif /* __UAPI_LINUX_SEV_GUEST_H_ */ -- cgit v1.2.3 From d80b494f712317493d464a55652698c4d1b7bb0f Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Mon, 7 Mar 2022 15:33:55 -0600 Subject: virt: sevguest: Add support to get extended report Version 2 of GHCB specification defines Non-Automatic-Exit (NAE) to get extended guest report which is similar to the SNP_GET_REPORT ioctl. The main difference is related to the additional data that will be returned. That additional data returned is a certificate blob that can be used by the SNP guest user. The certificate blob layout is defined in the GHCB specification. The driver simply treats the blob as a opaque data and copies it to userspace. [ bp: Massage commit message, cast 1st arg of access_ok() ] Signed-off-by: Brijesh Singh Signed-off-by: Borislav Petkov Link: https://lore.kernel.org/r/20220307213356.2797205-46-brijesh.singh@amd.com --- Documentation/virt/coco/sevguest.rst | 23 +++++++++ drivers/virt/coco/sevguest/sevguest.c | 92 ++++++++++++++++++++++++++++++++++- include/uapi/linux/sev-guest.h | 13 +++++ 3 files changed, 126 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/Documentation/virt/coco/sevguest.rst b/Documentation/virt/coco/sevguest.rst index 4135c1431241..625de22658ec 100644 --- a/Documentation/virt/coco/sevguest.rst +++ b/Documentation/virt/coco/sevguest.rst @@ -95,6 +95,29 @@ on the various fields passed in the key derivation request. On success, the snp_derived_key_resp.data contains the derived key value. See the SEV-SNP specification for further details. + +2.3 SNP_GET_EXT_REPORT +---------------------- +:Technology: sev-snp +:Type: guest ioctl +:Parameters (in/out): struct snp_ext_report_req +:Returns (out): struct snp_report_resp on success, -negative on error + +The SNP_GET_EXT_REPORT ioctl is similar to the SNP_GET_REPORT. The difference is +related to the additional certificate data that is returned with the report. +The certificate data returned is being provided by the hypervisor through the +SNP_SET_EXT_CONFIG. + +The ioctl uses the SNP_GUEST_REQUEST (MSG_REPORT_REQ) command provided by the SEV-SNP +firmware to get the attestation report. + +On success, the snp_ext_report_resp.data will contain the attestation report +and snp_ext_report_req.certs_address will contain the certificate blob. If the +length of the blob is smaller than expected then snp_ext_report_req.certs_len will +be updated with the expected value. + +See GHCB specification for further detail on how to parse the certificate blob. + Reference --------- diff --git a/drivers/virt/coco/sevguest/sevguest.c b/drivers/virt/coco/sevguest/sevguest.c index 393777b72e5e..15afb6ce8d19 100644 --- a/drivers/virt/coco/sevguest/sevguest.c +++ b/drivers/virt/coco/sevguest/sevguest.c @@ -43,6 +43,7 @@ struct snp_guest_dev { struct device *dev; struct miscdevice misc; + void *certs_data; struct snp_guest_crypto *crypto; struct snp_guest_msg *request, *response; struct snp_secrets_page_layout *layout; @@ -433,6 +434,82 @@ static int get_derived_key(struct snp_guest_dev *snp_dev, struct snp_guest_reque return rc; } +static int get_ext_report(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg) +{ + struct snp_guest_crypto *crypto = snp_dev->crypto; + struct snp_ext_report_req req; + struct snp_report_resp *resp; + int ret, npages = 0, resp_len; + + lockdep_assert_held(&snp_cmd_mutex); + + if (!arg->req_data || !arg->resp_data) + return -EINVAL; + + if (copy_from_user(&req, (void __user *)arg->req_data, sizeof(req))) + return -EFAULT; + + /* userspace does not want certificate data */ + if (!req.certs_len || !req.certs_address) + goto cmd; + + if (req.certs_len > SEV_FW_BLOB_MAX_SIZE || + !IS_ALIGNED(req.certs_len, PAGE_SIZE)) + return -EINVAL; + + if (!access_ok((const void __user *)req.certs_address, req.certs_len)) + return -EFAULT; + + /* + * Initialize the intermediate buffer with all zeros. This buffer + * is used in the guest request message to get the certs blob from + * the host. If host does not supply any certs in it, then copy + * zeros to indicate that certificate data was not provided. + */ + memset(snp_dev->certs_data, 0, req.certs_len); + npages = req.certs_len >> PAGE_SHIFT; +cmd: + /* + * The intermediate response buffer is used while decrypting the + * response payload. Make sure that it has enough space to cover the + * authtag. + */ + resp_len = sizeof(resp->data) + crypto->a_len; + resp = kzalloc(resp_len, GFP_KERNEL_ACCOUNT); + if (!resp) + return -ENOMEM; + + snp_dev->input.data_npages = npages; + ret = handle_guest_request(snp_dev, SVM_VMGEXIT_EXT_GUEST_REQUEST, arg->msg_version, + SNP_MSG_REPORT_REQ, &req.data, + sizeof(req.data), resp->data, resp_len, &arg->fw_err); + + /* If certs length is invalid then copy the returned length */ + if (arg->fw_err == SNP_GUEST_REQ_INVALID_LEN) { + req.certs_len = snp_dev->input.data_npages << PAGE_SHIFT; + + if (copy_to_user((void __user *)arg->req_data, &req, sizeof(req))) + ret = -EFAULT; + } + + if (ret) + goto e_free; + + if (npages && + copy_to_user((void __user *)req.certs_address, snp_dev->certs_data, + req.certs_len)) { + ret = -EFAULT; + goto e_free; + } + + if (copy_to_user((void __user *)arg->resp_data, resp, sizeof(*resp))) + ret = -EFAULT; + +e_free: + kfree(resp); + return ret; +} + static long snp_guest_ioctl(struct file *file, unsigned int ioctl, unsigned long arg) { struct snp_guest_dev *snp_dev = to_snp_dev(file); @@ -465,6 +542,9 @@ static long snp_guest_ioctl(struct file *file, unsigned int ioctl, unsigned long case SNP_GET_DERIVED_KEY: ret = get_derived_key(snp_dev, &input); break; + case SNP_GET_EXT_REPORT: + ret = get_ext_report(snp_dev, &input); + break; default: break; } @@ -595,10 +675,14 @@ static int __init snp_guest_probe(struct platform_device *pdev) if (!snp_dev->response) goto e_free_request; + snp_dev->certs_data = alloc_shared_pages(SEV_FW_BLOB_MAX_SIZE); + if (!snp_dev->certs_data) + goto e_free_response; + ret = -EIO; snp_dev->crypto = init_crypto(snp_dev, snp_dev->vmpck, VMPCK_KEY_LEN); if (!snp_dev->crypto) - goto e_free_response; + goto e_free_cert_data; misc = &snp_dev->misc; misc->minor = MISC_DYNAMIC_MINOR; @@ -608,14 +692,17 @@ static int __init snp_guest_probe(struct platform_device *pdev) /* initial the input address for guest request */ snp_dev->input.req_gpa = __pa(snp_dev->request); snp_dev->input.resp_gpa = __pa(snp_dev->response); + snp_dev->input.data_gpa = __pa(snp_dev->certs_data); ret = misc_register(misc); if (ret) - goto e_free_response; + goto e_free_cert_data; dev_info(dev, "Initialized SNP guest driver (using vmpck_id %d)\n", vmpck_id); return 0; +e_free_cert_data: + free_shared_pages(snp_dev->certs_data, SEV_FW_BLOB_MAX_SIZE); e_free_response: free_shared_pages(snp_dev->response, sizeof(struct snp_guest_msg)); e_free_request: @@ -629,6 +716,7 @@ static int __exit snp_guest_remove(struct platform_device *pdev) { struct snp_guest_dev *snp_dev = platform_get_drvdata(pdev); + free_shared_pages(snp_dev->certs_data, SEV_FW_BLOB_MAX_SIZE); free_shared_pages(snp_dev->response, sizeof(struct snp_guest_msg)); free_shared_pages(snp_dev->request, sizeof(struct snp_guest_msg)); deinit_crypto(snp_dev->crypto); diff --git a/include/uapi/linux/sev-guest.h b/include/uapi/linux/sev-guest.h index 598367f12064..256aaeff7e65 100644 --- a/include/uapi/linux/sev-guest.h +++ b/include/uapi/linux/sev-guest.h @@ -56,6 +56,16 @@ struct snp_guest_request_ioctl { __u64 fw_err; }; +struct snp_ext_report_req { + struct snp_report_req data; + + /* where to copy the certificate blob */ + __u64 certs_address; + + /* length of the certificate blob */ + __u32 certs_len; +}; + #define SNP_GUEST_REQ_IOC_TYPE 'S' /* Get SNP attestation report */ @@ -64,4 +74,7 @@ struct snp_guest_request_ioctl { /* Get a derived key from the root */ #define SNP_GET_DERIVED_KEY _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x1, struct snp_guest_request_ioctl) +/* Get SNP extended report as defined in the GHCB specification version 2. */ +#define SNP_GET_EXT_REPORT _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x2, struct snp_guest_request_ioctl) + #endif /* __UAPI_LINUX_SEV_GUEST_H_ */ -- cgit v1.2.3 From e50abbf788c239d529f9ab81e325f8e8f8432c9d Mon Sep 17 00:00:00 2001 From: Yang Yingliang Date: Mon, 11 Apr 2022 19:12:13 +0800 Subject: virt: sevguest: Fix return value check in alloc_shared_pages() If alloc_pages() fails, it returns a NULL pointer. Replace the wrong IS_ERR() check with the proper NULL pointer check. Fixes: fce96cf04430 ("virt: Add SEV-SNP guest driver") Reported-by: Hulk Robot Signed-off-by: Yang Yingliang Signed-off-by: Borislav Petkov Reviewed-by: Brijesh Singh Link: https://lore.kernel.org/r/20220411111213.1477853-1-yangyingliang@huawei.com --- drivers/virt/coco/sevguest/sevguest.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/virt/coco/sevguest/sevguest.c b/drivers/virt/coco/sevguest/sevguest.c index 15afb6ce8d19..aaa6134d1d40 100644 --- a/drivers/virt/coco/sevguest/sevguest.c +++ b/drivers/virt/coco/sevguest/sevguest.c @@ -581,7 +581,7 @@ static void *alloc_shared_pages(size_t sz) int ret; page = alloc_pages(GFP_KERNEL_ACCOUNT, get_order(sz)); - if (IS_ERR(page)) + if (!page) return NULL; ret = set_memory_decrypted((unsigned long)page_address(page), npages); -- cgit v1.2.3 From 101826e02ac6c829bf4e768295e79ae9c37b4b2a Mon Sep 17 00:00:00 2001 From: Haowen Bai Date: Thu, 14 Apr 2022 18:04:17 +0800 Subject: virt: sevguest: Fix bool function returning negative value The function enc_payload() is wrongly declared bool but returns an integer value. Correct it. [ bp: Massage commit message. ] Fixes: fce96cf04430 ("virt: Add SEV-SNP guest driver") Signed-off-by: Haowen Bai Signed-off-by: Borislav Petkov Link: https://lore.kernel.org/r/1649930657-10837-1-git-send-email-baihaowen@meizu.com --- drivers/virt/coco/sevguest/sevguest.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/virt/coco/sevguest/sevguest.c b/drivers/virt/coco/sevguest/sevguest.c index aaa6134d1d40..15f069ec8f0b 100644 --- a/drivers/virt/coco/sevguest/sevguest.c +++ b/drivers/virt/coco/sevguest/sevguest.c @@ -276,7 +276,7 @@ static int verify_and_dec_payload(struct snp_guest_dev *snp_dev, void *payload, return dec_payload(snp_dev, resp, payload, resp_hdr->msg_sz + crypto->a_len); } -static bool enc_payload(struct snp_guest_dev *snp_dev, u64 seqno, int version, u8 type, +static int enc_payload(struct snp_guest_dev *snp_dev, u64 seqno, int version, u8 type, void *payload, size_t sz) { struct snp_guest_msg *req = snp_dev->request; -- cgit v1.2.3 From 2bf93ffbb97e0614cfc431d2ea33b7eae7481eb2 Mon Sep 17 00:00:00 2001 From: Tom Lendacky Date: Wed, 20 Apr 2022 09:14:13 -0500 Subject: virt: sevguest: Change driver name to reflect generic SEV support During patch review, it was decided the SNP guest driver name should not be SEV-SNP specific, but should be generic for use with anything SEV. However, this feedback was missed and the driver name, and many of the driver functions and structures, are SEV-SNP name specific. Rename the driver to "sev-guest" (to match the misc device that is created) and update some of the function and structure names, too. While in the file, adjust the one pr_err() message to be a dev_err() message so that the message, if issued, uses the driver name. Signed-off-by: Tom Lendacky Signed-off-by: Borislav Petkov Link: https://lore.kernel.org/r/307710bb5515c9088a19fd0b930268c7300479b2.1650464054.git.thomas.lendacky@amd.com --- arch/x86/include/asm/sev.h | 2 +- arch/x86/kernel/sev.c | 10 ++++----- drivers/virt/coco/sevguest/sevguest.c | 39 +++++++++++++++++++---------------- 3 files changed, 27 insertions(+), 24 deletions(-) (limited to 'drivers') diff --git a/arch/x86/include/asm/sev.h b/arch/x86/include/asm/sev.h index 9c2d33f1cfee..6e3dda4f82b5 100644 --- a/arch/x86/include/asm/sev.h +++ b/arch/x86/include/asm/sev.h @@ -95,7 +95,7 @@ struct snp_req_data { unsigned int data_npages; }; -struct snp_guest_platform_data { +struct sev_guest_platform_data { u64 secrets_gpa; }; diff --git a/arch/x86/kernel/sev.c b/arch/x86/kernel/sev.c index f01f4550e2c6..2fa87a07ab30 100644 --- a/arch/x86/kernel/sev.c +++ b/arch/x86/kernel/sev.c @@ -2166,8 +2166,8 @@ e_restore_irq: } EXPORT_SYMBOL_GPL(snp_issue_guest_request); -static struct platform_device guest_req_device = { - .name = "snp-guest", +static struct platform_device sev_guest_device = { + .name = "sev-guest", .id = -1, }; @@ -2197,7 +2197,7 @@ static u64 get_secrets_page(void) static int __init snp_init_platform_device(void) { - struct snp_guest_platform_data data; + struct sev_guest_platform_data data; u64 gpa; if (!cc_platform_has(CC_ATTR_GUEST_SEV_SNP)) @@ -2208,10 +2208,10 @@ static int __init snp_init_platform_device(void) return -ENODEV; data.secrets_gpa = gpa; - if (platform_device_add_data(&guest_req_device, &data, sizeof(data))) + if (platform_device_add_data(&sev_guest_device, &data, sizeof(data))) return -ENODEV; - if (platform_device_register(&guest_req_device)) + if (platform_device_register(&sev_guest_device)) return -ENODEV; pr_info("SNP guest platform device initialized.\n"); diff --git a/drivers/virt/coco/sevguest/sevguest.c b/drivers/virt/coco/sevguest/sevguest.c index 15f069ec8f0b..18c3231a816d 100644 --- a/drivers/virt/coco/sevguest/sevguest.c +++ b/drivers/virt/coco/sevguest/sevguest.c @@ -1,14 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * AMD Secure Encrypted Virtualization Nested Paging (SEV-SNP) guest request interface + * AMD Secure Encrypted Virtualization (SEV) guest driver interface * * Copyright (C) 2021 Advanced Micro Devices, Inc. * * Author: Brijesh Singh */ -#define pr_fmt(fmt) "SNP: GUEST: " fmt - #include #include #include @@ -574,7 +572,7 @@ static void free_shared_pages(void *buf, size_t sz) __free_pages(virt_to_page(buf), get_order(sz)); } -static void *alloc_shared_pages(size_t sz) +static void *alloc_shared_pages(struct device *dev, size_t sz) { unsigned int npages = PAGE_ALIGN(sz) >> PAGE_SHIFT; struct page *page; @@ -586,7 +584,7 @@ static void *alloc_shared_pages(size_t sz) ret = set_memory_decrypted((unsigned long)page_address(page), npages); if (ret) { - pr_err("failed to mark page shared, ret=%d\n", ret); + dev_err(dev, "failed to mark page shared, ret=%d\n", ret); __free_pages(page, get_order(sz)); return NULL; } @@ -627,10 +625,10 @@ static u8 *get_vmpck(int id, struct snp_secrets_page_layout *layout, u32 **seqno return key; } -static int __init snp_guest_probe(struct platform_device *pdev) +static int __init sev_guest_probe(struct platform_device *pdev) { struct snp_secrets_page_layout *layout; - struct snp_guest_platform_data *data; + struct sev_guest_platform_data *data; struct device *dev = &pdev->dev; struct snp_guest_dev *snp_dev; struct miscdevice *misc; @@ -639,7 +637,7 @@ static int __init snp_guest_probe(struct platform_device *pdev) if (!dev->platform_data) return -ENODEV; - data = (struct snp_guest_platform_data *)dev->platform_data; + data = (struct sev_guest_platform_data *)dev->platform_data; layout = (__force void *)ioremap_encrypted(data->secrets_gpa, PAGE_SIZE); if (!layout) return -ENODEV; @@ -667,15 +665,15 @@ static int __init snp_guest_probe(struct platform_device *pdev) snp_dev->layout = layout; /* Allocate the shared page used for the request and response message. */ - snp_dev->request = alloc_shared_pages(sizeof(struct snp_guest_msg)); + snp_dev->request = alloc_shared_pages(dev, sizeof(struct snp_guest_msg)); if (!snp_dev->request) goto e_unmap; - snp_dev->response = alloc_shared_pages(sizeof(struct snp_guest_msg)); + snp_dev->response = alloc_shared_pages(dev, sizeof(struct snp_guest_msg)); if (!snp_dev->response) goto e_free_request; - snp_dev->certs_data = alloc_shared_pages(SEV_FW_BLOB_MAX_SIZE); + snp_dev->certs_data = alloc_shared_pages(dev, SEV_FW_BLOB_MAX_SIZE); if (!snp_dev->certs_data) goto e_free_response; @@ -698,7 +696,7 @@ static int __init snp_guest_probe(struct platform_device *pdev) if (ret) goto e_free_cert_data; - dev_info(dev, "Initialized SNP guest driver (using vmpck_id %d)\n", vmpck_id); + dev_info(dev, "Initialized SEV guest driver (using vmpck_id %d)\n", vmpck_id); return 0; e_free_cert_data: @@ -712,7 +710,7 @@ e_unmap: return ret; } -static int __exit snp_guest_remove(struct platform_device *pdev) +static int __exit sev_guest_remove(struct platform_device *pdev) { struct snp_guest_dev *snp_dev = platform_get_drvdata(pdev); @@ -725,16 +723,21 @@ static int __exit snp_guest_remove(struct platform_device *pdev) return 0; } -static struct platform_driver snp_guest_driver = { - .remove = __exit_p(snp_guest_remove), +/* + * This driver is a common SEV guest interface driver and meant to support + * any SEV guest API. As such, even though it has been introduced along with + * the SEV-SNP support, it is named "sev-guest". + */ +static struct platform_driver sev_guest_driver = { + .remove = __exit_p(sev_guest_remove), .driver = { - .name = "snp-guest", + .name = "sev-guest", }, }; -module_platform_driver_probe(snp_guest_driver, snp_guest_probe); +module_platform_driver_probe(sev_guest_driver, sev_guest_probe); MODULE_AUTHOR("Brijesh Singh "); MODULE_LICENSE("GPL"); MODULE_VERSION("1.0.0"); -MODULE_DESCRIPTION("AMD SNP Guest Driver"); +MODULE_DESCRIPTION("AMD SEV Guest Driver"); -- cgit v1.2.3 From d63670d23e60f00210635ca7c62bce27bec55f1b Mon Sep 17 00:00:00 2001 From: Tom Lendacky Date: Wed, 20 Apr 2022 09:14:14 -0500 Subject: virt: sevguest: Rename the sevguest dir and files to sev-guest Rename the drivers/virt/coco/sevguest directory and files to sev-guest so as to match the driver name. [ bp: Rename Documentation/virt/coco/sevguest.rst too, as reported by sfr: https://lore.kernel.org/r/20220427101059.3bf55262@canb.auug.org.au ] Signed-off-by: Tom Lendacky Signed-off-by: Borislav Petkov Link: https://lore.kernel.org/r/2f5c9cb16e3a67599c8e3170f6c72c8712c47d53.1650464054.git.thomas.lendacky@amd.com --- Documentation/virt/coco/sev-guest.rst | 155 +++++++ Documentation/virt/coco/sevguest.rst | 155 ------- Documentation/virt/index.rst | 2 +- drivers/virt/Kconfig | 2 +- drivers/virt/Makefile | 2 +- drivers/virt/coco/sev-guest/Kconfig | 14 + drivers/virt/coco/sev-guest/Makefile | 2 + drivers/virt/coco/sev-guest/sev-guest.c | 743 ++++++++++++++++++++++++++++++++ drivers/virt/coco/sev-guest/sev-guest.h | 98 +++++ drivers/virt/coco/sevguest/Kconfig | 14 - drivers/virt/coco/sevguest/Makefile | 2 - drivers/virt/coco/sevguest/sevguest.c | 743 -------------------------------- drivers/virt/coco/sevguest/sevguest.h | 98 ----- 13 files changed, 1015 insertions(+), 1015 deletions(-) create mode 100644 Documentation/virt/coco/sev-guest.rst delete mode 100644 Documentation/virt/coco/sevguest.rst create mode 100644 drivers/virt/coco/sev-guest/Kconfig create mode 100644 drivers/virt/coco/sev-guest/Makefile create mode 100644 drivers/virt/coco/sev-guest/sev-guest.c create mode 100644 drivers/virt/coco/sev-guest/sev-guest.h delete mode 100644 drivers/virt/coco/sevguest/Kconfig delete mode 100644 drivers/virt/coco/sevguest/Makefile delete mode 100644 drivers/virt/coco/sevguest/sevguest.c delete mode 100644 drivers/virt/coco/sevguest/sevguest.h (limited to 'drivers') diff --git a/Documentation/virt/coco/sev-guest.rst b/Documentation/virt/coco/sev-guest.rst new file mode 100644 index 000000000000..bf593e88cfd9 --- /dev/null +++ b/Documentation/virt/coco/sev-guest.rst @@ -0,0 +1,155 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=================================================================== +The Definitive SEV Guest API Documentation +=================================================================== + +1. General description +====================== + +The SEV API is a set of ioctls that are used by the guest or hypervisor +to get or set a certain aspect of the SEV virtual machine. The ioctls belong +to the following classes: + + - Hypervisor ioctls: These query and set global attributes which affect the + whole SEV firmware. These ioctl are used by platform provisioning tools. + + - Guest ioctls: These query and set attributes of the SEV virtual machine. + +2. API description +================== + +This section describes ioctls that is used for querying the SEV guest report +from the SEV firmware. For each ioctl, the following information is provided +along with a description: + + Technology: + which SEV technology provides this ioctl. SEV, SEV-ES, SEV-SNP or all. + + Type: + hypervisor or guest. The ioctl can be used inside the guest or the + hypervisor. + + Parameters: + what parameters are accepted by the ioctl. + + Returns: + the return value. General error numbers (-ENOMEM, -EINVAL) + are not detailed, but errors with specific meanings are. + +The guest ioctl should be issued on a file descriptor of the /dev/sev-guest device. +The ioctl accepts struct snp_user_guest_request. The input and output structure is +specified through the req_data and resp_data field respectively. If the ioctl fails +to execute due to a firmware error, then fw_err code will be set otherwise the +fw_err will be set to 0x00000000000000ff. + +The firmware checks that the message sequence counter is one greater than +the guests message sequence counter. If guest driver fails to increment message +counter (e.g. counter overflow), then -EIO will be returned. + +:: + + struct snp_guest_request_ioctl { + /* Message version number */ + __u32 msg_version; + + /* Request and response structure address */ + __u64 req_data; + __u64 resp_data; + + /* firmware error code on failure (see psp-sev.h) */ + __u64 fw_err; + }; + +2.1 SNP_GET_REPORT +------------------ + +:Technology: sev-snp +:Type: guest ioctl +:Parameters (in): struct snp_report_req +:Returns (out): struct snp_report_resp on success, -negative on error + +The SNP_GET_REPORT ioctl can be used to query the attestation report from the +SEV-SNP firmware. The ioctl uses the SNP_GUEST_REQUEST (MSG_REPORT_REQ) command +provided by the SEV-SNP firmware to query the attestation report. + +On success, the snp_report_resp.data will contains the report. The report +contain the format described in the SEV-SNP specification. See the SEV-SNP +specification for further details. + +2.2 SNP_GET_DERIVED_KEY +----------------------- +:Technology: sev-snp +:Type: guest ioctl +:Parameters (in): struct snp_derived_key_req +:Returns (out): struct snp_derived_key_resp on success, -negative on error + +The SNP_GET_DERIVED_KEY ioctl can be used to get a key derive from a root key. +The derived key can be used by the guest for any purpose, such as sealing keys +or communicating with external entities. + +The ioctl uses the SNP_GUEST_REQUEST (MSG_KEY_REQ) command provided by the +SEV-SNP firmware to derive the key. See SEV-SNP specification for further details +on the various fields passed in the key derivation request. + +On success, the snp_derived_key_resp.data contains the derived key value. See +the SEV-SNP specification for further details. + + +2.3 SNP_GET_EXT_REPORT +---------------------- +:Technology: sev-snp +:Type: guest ioctl +:Parameters (in/out): struct snp_ext_report_req +:Returns (out): struct snp_report_resp on success, -negative on error + +The SNP_GET_EXT_REPORT ioctl is similar to the SNP_GET_REPORT. The difference is +related to the additional certificate data that is returned with the report. +The certificate data returned is being provided by the hypervisor through the +SNP_SET_EXT_CONFIG. + +The ioctl uses the SNP_GUEST_REQUEST (MSG_REPORT_REQ) command provided by the SEV-SNP +firmware to get the attestation report. + +On success, the snp_ext_report_resp.data will contain the attestation report +and snp_ext_report_req.certs_address will contain the certificate blob. If the +length of the blob is smaller than expected then snp_ext_report_req.certs_len will +be updated with the expected value. + +See GHCB specification for further detail on how to parse the certificate blob. + +3. SEV-SNP CPUID Enforcement +============================ + +SEV-SNP guests can access a special page that contains a table of CPUID values +that have been validated by the PSP as part of the SNP_LAUNCH_UPDATE firmware +command. It provides the following assurances regarding the validity of CPUID +values: + + - Its address is obtained via bootloader/firmware (via CC blob), and those + binaries will be measured as part of the SEV-SNP attestation report. + - Its initial state will be encrypted/pvalidated, so attempts to modify + it during run-time will result in garbage being written, or #VC exceptions + being generated due to changes in validation state if the hypervisor tries + to swap the backing page. + - Attempts to bypass PSP checks by the hypervisor by using a normal page, or + a non-CPUID encrypted page will change the measurement provided by the + SEV-SNP attestation report. + - The CPUID page contents are *not* measured, but attempts to modify the + expected contents of a CPUID page as part of guest initialization will be + gated by the PSP CPUID enforcement policy checks performed on the page + during SNP_LAUNCH_UPDATE, and noticeable later if the guest owner + implements their own checks of the CPUID values. + +It is important to note that this last assurance is only useful if the kernel +has taken care to make use of the SEV-SNP CPUID throughout all stages of boot. +Otherwise, guest owner attestation provides no assurance that the kernel wasn't +fed incorrect values at some point during boot. + + +Reference +--------- + +SEV-SNP and GHCB specification: developer.amd.com/sev + +The driver is based on SEV-SNP firmware spec 0.9 and GHCB spec version 2.0. diff --git a/Documentation/virt/coco/sevguest.rst b/Documentation/virt/coco/sevguest.rst deleted file mode 100644 index bf593e88cfd9..000000000000 --- a/Documentation/virt/coco/sevguest.rst +++ /dev/null @@ -1,155 +0,0 @@ -.. SPDX-License-Identifier: GPL-2.0 - -=================================================================== -The Definitive SEV Guest API Documentation -=================================================================== - -1. General description -====================== - -The SEV API is a set of ioctls that are used by the guest or hypervisor -to get or set a certain aspect of the SEV virtual machine. The ioctls belong -to the following classes: - - - Hypervisor ioctls: These query and set global attributes which affect the - whole SEV firmware. These ioctl are used by platform provisioning tools. - - - Guest ioctls: These query and set attributes of the SEV virtual machine. - -2. API description -================== - -This section describes ioctls that is used for querying the SEV guest report -from the SEV firmware. For each ioctl, the following information is provided -along with a description: - - Technology: - which SEV technology provides this ioctl. SEV, SEV-ES, SEV-SNP or all. - - Type: - hypervisor or guest. The ioctl can be used inside the guest or the - hypervisor. - - Parameters: - what parameters are accepted by the ioctl. - - Returns: - the return value. General error numbers (-ENOMEM, -EINVAL) - are not detailed, but errors with specific meanings are. - -The guest ioctl should be issued on a file descriptor of the /dev/sev-guest device. -The ioctl accepts struct snp_user_guest_request. The input and output structure is -specified through the req_data and resp_data field respectively. If the ioctl fails -to execute due to a firmware error, then fw_err code will be set otherwise the -fw_err will be set to 0x00000000000000ff. - -The firmware checks that the message sequence counter is one greater than -the guests message sequence counter. If guest driver fails to increment message -counter (e.g. counter overflow), then -EIO will be returned. - -:: - - struct snp_guest_request_ioctl { - /* Message version number */ - __u32 msg_version; - - /* Request and response structure address */ - __u64 req_data; - __u64 resp_data; - - /* firmware error code on failure (see psp-sev.h) */ - __u64 fw_err; - }; - -2.1 SNP_GET_REPORT ------------------- - -:Technology: sev-snp -:Type: guest ioctl -:Parameters (in): struct snp_report_req -:Returns (out): struct snp_report_resp on success, -negative on error - -The SNP_GET_REPORT ioctl can be used to query the attestation report from the -SEV-SNP firmware. The ioctl uses the SNP_GUEST_REQUEST (MSG_REPORT_REQ) command -provided by the SEV-SNP firmware to query the attestation report. - -On success, the snp_report_resp.data will contains the report. The report -contain the format described in the SEV-SNP specification. See the SEV-SNP -specification for further details. - -2.2 SNP_GET_DERIVED_KEY ------------------------ -:Technology: sev-snp -:Type: guest ioctl -:Parameters (in): struct snp_derived_key_req -:Returns (out): struct snp_derived_key_resp on success, -negative on error - -The SNP_GET_DERIVED_KEY ioctl can be used to get a key derive from a root key. -The derived key can be used by the guest for any purpose, such as sealing keys -or communicating with external entities. - -The ioctl uses the SNP_GUEST_REQUEST (MSG_KEY_REQ) command provided by the -SEV-SNP firmware to derive the key. See SEV-SNP specification for further details -on the various fields passed in the key derivation request. - -On success, the snp_derived_key_resp.data contains the derived key value. See -the SEV-SNP specification for further details. - - -2.3 SNP_GET_EXT_REPORT ----------------------- -:Technology: sev-snp -:Type: guest ioctl -:Parameters (in/out): struct snp_ext_report_req -:Returns (out): struct snp_report_resp on success, -negative on error - -The SNP_GET_EXT_REPORT ioctl is similar to the SNP_GET_REPORT. The difference is -related to the additional certificate data that is returned with the report. -The certificate data returned is being provided by the hypervisor through the -SNP_SET_EXT_CONFIG. - -The ioctl uses the SNP_GUEST_REQUEST (MSG_REPORT_REQ) command provided by the SEV-SNP -firmware to get the attestation report. - -On success, the snp_ext_report_resp.data will contain the attestation report -and snp_ext_report_req.certs_address will contain the certificate blob. If the -length of the blob is smaller than expected then snp_ext_report_req.certs_len will -be updated with the expected value. - -See GHCB specification for further detail on how to parse the certificate blob. - -3. SEV-SNP CPUID Enforcement -============================ - -SEV-SNP guests can access a special page that contains a table of CPUID values -that have been validated by the PSP as part of the SNP_LAUNCH_UPDATE firmware -command. It provides the following assurances regarding the validity of CPUID -values: - - - Its address is obtained via bootloader/firmware (via CC blob), and those - binaries will be measured as part of the SEV-SNP attestation report. - - Its initial state will be encrypted/pvalidated, so attempts to modify - it during run-time will result in garbage being written, or #VC exceptions - being generated due to changes in validation state if the hypervisor tries - to swap the backing page. - - Attempts to bypass PSP checks by the hypervisor by using a normal page, or - a non-CPUID encrypted page will change the measurement provided by the - SEV-SNP attestation report. - - The CPUID page contents are *not* measured, but attempts to modify the - expected contents of a CPUID page as part of guest initialization will be - gated by the PSP CPUID enforcement policy checks performed on the page - during SNP_LAUNCH_UPDATE, and noticeable later if the guest owner - implements their own checks of the CPUID values. - -It is important to note that this last assurance is only useful if the kernel -has taken care to make use of the SEV-SNP CPUID throughout all stages of boot. -Otherwise, guest owner attestation provides no assurance that the kernel wasn't -fed incorrect values at some point during boot. - - -Reference ---------- - -SEV-SNP and GHCB specification: developer.amd.com/sev - -The driver is based on SEV-SNP firmware spec 0.9 and GHCB spec version 2.0. diff --git a/Documentation/virt/index.rst b/Documentation/virt/index.rst index 40ad0d20032e..492f0920b988 100644 --- a/Documentation/virt/index.rst +++ b/Documentation/virt/index.rst @@ -13,7 +13,7 @@ Linux Virtualization Support guest-halt-polling ne_overview acrn/index - coco/sevguest + coco/sev-guest .. only:: html and subproject diff --git a/drivers/virt/Kconfig b/drivers/virt/Kconfig index 7d3273cfab27..0c1bba7c5c66 100644 --- a/drivers/virt/Kconfig +++ b/drivers/virt/Kconfig @@ -48,6 +48,6 @@ source "drivers/virt/nitro_enclaves/Kconfig" source "drivers/virt/acrn/Kconfig" -source "drivers/virt/coco/sevguest/Kconfig" +source "drivers/virt/coco/sev-guest/Kconfig" endif diff --git a/drivers/virt/Makefile b/drivers/virt/Makefile index 7b87a7ba1972..b2e6e864ebbe 100644 --- a/drivers/virt/Makefile +++ b/drivers/virt/Makefile @@ -9,4 +9,4 @@ obj-y += vboxguest/ obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves/ obj-$(CONFIG_ACRN_HSM) += acrn/ -obj-$(CONFIG_SEV_GUEST) += coco/sevguest/ +obj-$(CONFIG_SEV_GUEST) += coco/sev-guest/ diff --git a/drivers/virt/coco/sev-guest/Kconfig b/drivers/virt/coco/sev-guest/Kconfig new file mode 100644 index 000000000000..f9db0799ae67 --- /dev/null +++ b/drivers/virt/coco/sev-guest/Kconfig @@ -0,0 +1,14 @@ +config SEV_GUEST + tristate "AMD SEV Guest driver" + default m + depends on AMD_MEM_ENCRYPT + select CRYPTO_AEAD2 + select CRYPTO_GCM + help + SEV-SNP firmware provides the guest a mechanism to communicate with + the PSP without risk from a malicious hypervisor who wishes to read, + alter, drop or replay the messages sent. The driver provides + userspace interface to communicate with the PSP to request the + attestation report and more. + + If you choose 'M' here, this module will be called sev-guest. diff --git a/drivers/virt/coco/sev-guest/Makefile b/drivers/virt/coco/sev-guest/Makefile new file mode 100644 index 000000000000..63d67c27723a --- /dev/null +++ b/drivers/virt/coco/sev-guest/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_SEV_GUEST) += sev-guest.o diff --git a/drivers/virt/coco/sev-guest/sev-guest.c b/drivers/virt/coco/sev-guest/sev-guest.c new file mode 100644 index 000000000000..90ce16b6e05f --- /dev/null +++ b/drivers/virt/coco/sev-guest/sev-guest.c @@ -0,0 +1,743 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AMD Secure Encrypted Virtualization (SEV) guest driver interface + * + * Copyright (C) 2021 Advanced Micro Devices, Inc. + * + * Author: Brijesh Singh + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sev-guest.h" + +#define DEVICE_NAME "sev-guest" +#define AAD_LEN 48 +#define MSG_HDR_VER 1 + +struct snp_guest_crypto { + struct crypto_aead *tfm; + u8 *iv, *authtag; + int iv_len, a_len; +}; + +struct snp_guest_dev { + struct device *dev; + struct miscdevice misc; + + void *certs_data; + struct snp_guest_crypto *crypto; + struct snp_guest_msg *request, *response; + struct snp_secrets_page_layout *layout; + struct snp_req_data input; + u32 *os_area_msg_seqno; + u8 *vmpck; +}; + +static u32 vmpck_id; +module_param(vmpck_id, uint, 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. */ +static DEFINE_MUTEX(snp_cmd_mutex); + +static bool is_vmpck_empty(struct snp_guest_dev *snp_dev) +{ + char zero_key[VMPCK_KEY_LEN] = {0}; + + if (snp_dev->vmpck) + return !memcmp(snp_dev->vmpck, zero_key, VMPCK_KEY_LEN); + + return true; +} + +static void snp_disable_vmpck(struct snp_guest_dev *snp_dev) +{ + memzero_explicit(snp_dev->vmpck, VMPCK_KEY_LEN); + snp_dev->vmpck = NULL; +} + +static inline u64 __snp_get_msg_seqno(struct snp_guest_dev *snp_dev) +{ + u64 count; + + lockdep_assert_held(&snp_cmd_mutex); + + /* Read the current message sequence counter from secrets pages */ + count = *snp_dev->os_area_msg_seqno; + + return count + 1; +} + +/* Return a non-zero on success */ +static u64 snp_get_msg_seqno(struct snp_guest_dev *snp_dev) +{ + u64 count = __snp_get_msg_seqno(snp_dev); + + /* + * The message sequence counter for the SNP guest request is a 64-bit + * value but the version 2 of GHCB specification defines a 32-bit storage + * for it. If the counter exceeds the 32-bit value then return zero. + * The caller should check the return value, but if the caller happens to + * not check the value and use it, then the firmware treats zero as an + * invalid number and will fail the message request. + */ + if (count >= UINT_MAX) { + dev_err(snp_dev->dev, "request message sequence counter overflow\n"); + return 0; + } + + return count; +} + +static void snp_inc_msg_seqno(struct snp_guest_dev *snp_dev) +{ + /* + * The counter is also incremented by the PSP, so increment it by 2 + * and save in secrets page. + */ + *snp_dev->os_area_msg_seqno += 2; +} + +static inline struct snp_guest_dev *to_snp_dev(struct file *file) +{ + struct miscdevice *dev = file->private_data; + + return container_of(dev, struct snp_guest_dev, misc); +} + +static struct snp_guest_crypto *init_crypto(struct snp_guest_dev *snp_dev, u8 *key, size_t keylen) +{ + struct snp_guest_crypto *crypto; + + crypto = kzalloc(sizeof(*crypto), GFP_KERNEL_ACCOUNT); + if (!crypto) + return NULL; + + crypto->tfm = crypto_alloc_aead("gcm(aes)", 0, 0); + if (IS_ERR(crypto->tfm)) + goto e_free; + + if (crypto_aead_setkey(crypto->tfm, key, keylen)) + goto e_free_crypto; + + crypto->iv_len = crypto_aead_ivsize(crypto->tfm); + crypto->iv = kmalloc(crypto->iv_len, GFP_KERNEL_ACCOUNT); + if (!crypto->iv) + goto e_free_crypto; + + if (crypto_aead_authsize(crypto->tfm) > MAX_AUTHTAG_LEN) { + if (crypto_aead_setauthsize(crypto->tfm, MAX_AUTHTAG_LEN)) { + dev_err(snp_dev->dev, "failed to set authsize to %d\n", MAX_AUTHTAG_LEN); + goto e_free_iv; + } + } + + crypto->a_len = crypto_aead_authsize(crypto->tfm); + crypto->authtag = kmalloc(crypto->a_len, GFP_KERNEL_ACCOUNT); + if (!crypto->authtag) + goto e_free_auth; + + return crypto; + +e_free_auth: + kfree(crypto->authtag); +e_free_iv: + kfree(crypto->iv); +e_free_crypto: + crypto_free_aead(crypto->tfm); +e_free: + kfree(crypto); + + return NULL; +} + +static void deinit_crypto(struct snp_guest_crypto *crypto) +{ + crypto_free_aead(crypto->tfm); + kfree(crypto->iv); + kfree(crypto->authtag); + kfree(crypto); +} + +static int enc_dec_message(struct snp_guest_crypto *crypto, struct snp_guest_msg *msg, + u8 *src_buf, u8 *dst_buf, size_t len, bool enc) +{ + struct snp_guest_msg_hdr *hdr = &msg->hdr; + struct scatterlist src[3], dst[3]; + DECLARE_CRYPTO_WAIT(wait); + struct aead_request *req; + int ret; + + req = aead_request_alloc(crypto->tfm, GFP_KERNEL); + if (!req) + return -ENOMEM; + + /* + * AEAD memory operations: + * +------ AAD -------+------- DATA -----+---- AUTHTAG----+ + * | msg header | plaintext | hdr->authtag | + * | bytes 30h - 5Fh | or | | + * | | cipher | | + * +------------------+------------------+----------------+ + */ + sg_init_table(src, 3); + sg_set_buf(&src[0], &hdr->algo, AAD_LEN); + sg_set_buf(&src[1], src_buf, hdr->msg_sz); + sg_set_buf(&src[2], hdr->authtag, crypto->a_len); + + sg_init_table(dst, 3); + sg_set_buf(&dst[0], &hdr->algo, AAD_LEN); + sg_set_buf(&dst[1], dst_buf, hdr->msg_sz); + sg_set_buf(&dst[2], hdr->authtag, crypto->a_len); + + aead_request_set_ad(req, AAD_LEN); + aead_request_set_tfm(req, crypto->tfm); + aead_request_set_callback(req, 0, crypto_req_done, &wait); + + aead_request_set_crypt(req, src, dst, len, crypto->iv); + ret = crypto_wait_req(enc ? crypto_aead_encrypt(req) : crypto_aead_decrypt(req), &wait); + + aead_request_free(req); + return ret; +} + +static int __enc_payload(struct snp_guest_dev *snp_dev, struct snp_guest_msg *msg, + void *plaintext, size_t len) +{ + struct snp_guest_crypto *crypto = snp_dev->crypto; + struct snp_guest_msg_hdr *hdr = &msg->hdr; + + memset(crypto->iv, 0, crypto->iv_len); + memcpy(crypto->iv, &hdr->msg_seqno, sizeof(hdr->msg_seqno)); + + return enc_dec_message(crypto, msg, plaintext, msg->payload, len, true); +} + +static int dec_payload(struct snp_guest_dev *snp_dev, struct snp_guest_msg *msg, + void *plaintext, size_t len) +{ + struct snp_guest_crypto *crypto = snp_dev->crypto; + struct snp_guest_msg_hdr *hdr = &msg->hdr; + + /* Build IV with response buffer sequence number */ + memset(crypto->iv, 0, crypto->iv_len); + memcpy(crypto->iv, &hdr->msg_seqno, sizeof(hdr->msg_seqno)); + + return enc_dec_message(crypto, msg, msg->payload, plaintext, len, false); +} + +static int verify_and_dec_payload(struct snp_guest_dev *snp_dev, void *payload, u32 sz) +{ + struct snp_guest_crypto *crypto = snp_dev->crypto; + struct snp_guest_msg *resp = snp_dev->response; + struct snp_guest_msg *req = snp_dev->request; + struct snp_guest_msg_hdr *req_hdr = &req->hdr; + struct snp_guest_msg_hdr *resp_hdr = &resp->hdr; + + dev_dbg(snp_dev->dev, "response [seqno %lld type %d version %d sz %d]\n", + resp_hdr->msg_seqno, resp_hdr->msg_type, resp_hdr->msg_version, resp_hdr->msg_sz); + + /* Verify that the sequence counter is incremented by 1 */ + if (unlikely(resp_hdr->msg_seqno != (req_hdr->msg_seqno + 1))) + return -EBADMSG; + + /* Verify response message type and version number. */ + if (resp_hdr->msg_type != (req_hdr->msg_type + 1) || + resp_hdr->msg_version != req_hdr->msg_version) + return -EBADMSG; + + /* + * If the message size is greater than our buffer length then return + * an error. + */ + if (unlikely((resp_hdr->msg_sz + crypto->a_len) > sz)) + return -EBADMSG; + + /* Decrypt the payload */ + return dec_payload(snp_dev, resp, payload, resp_hdr->msg_sz + crypto->a_len); +} + +static int enc_payload(struct snp_guest_dev *snp_dev, u64 seqno, int version, u8 type, + void *payload, size_t sz) +{ + struct snp_guest_msg *req = snp_dev->request; + struct snp_guest_msg_hdr *hdr = &req->hdr; + + memset(req, 0, sizeof(*req)); + + hdr->algo = SNP_AEAD_AES_256_GCM; + hdr->hdr_version = MSG_HDR_VER; + hdr->hdr_sz = sizeof(*hdr); + hdr->msg_type = type; + hdr->msg_version = version; + hdr->msg_seqno = seqno; + hdr->msg_vmpck = vmpck_id; + hdr->msg_sz = sz; + + /* Verify the sequence number is non-zero */ + if (!hdr->msg_seqno) + return -ENOSR; + + dev_dbg(snp_dev->dev, "request [seqno %lld type %d version %d sz %d]\n", + hdr->msg_seqno, hdr->msg_type, hdr->msg_version, hdr->msg_sz); + + return __enc_payload(snp_dev, req, payload, sz); +} + +static int handle_guest_request(struct snp_guest_dev *snp_dev, u64 exit_code, int msg_ver, + u8 type, void *req_buf, size_t req_sz, void *resp_buf, + u32 resp_sz, __u64 *fw_err) +{ + unsigned long err; + u64 seqno; + int rc; + + /* Get message sequence and verify that its a non-zero */ + seqno = snp_get_msg_seqno(snp_dev); + if (!seqno) + return -EIO; + + memset(snp_dev->response, 0, sizeof(struct snp_guest_msg)); + + /* Encrypt the userspace provided payload */ + rc = enc_payload(snp_dev, seqno, msg_ver, type, req_buf, req_sz); + if (rc) + return rc; + + /* Call firmware to process the request */ + rc = snp_issue_guest_request(exit_code, &snp_dev->input, &err); + if (fw_err) + *fw_err = err; + + if (rc) + return rc; + + /* + * The verify_and_dec_payload() will fail only if the hypervisor is + * actively modifying the message header or corrupting the encrypted payload. + * This hints that hypervisor is acting in a bad faith. Disable the VMPCK so that + * the key cannot be used for any communication. The key is disabled to ensure + * that AES-GCM does not use the same IV while encrypting the request payload. + */ + rc = verify_and_dec_payload(snp_dev, resp_buf, resp_sz); + if (rc) { + dev_alert(snp_dev->dev, + "Detected unexpected decode failure, disabling the vmpck_id %d\n", + vmpck_id); + snp_disable_vmpck(snp_dev); + return rc; + } + + /* Increment to new message sequence after payload decryption was successful. */ + snp_inc_msg_seqno(snp_dev); + + return 0; +} + +static int get_report(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg) +{ + struct snp_guest_crypto *crypto = snp_dev->crypto; + struct snp_report_resp *resp; + struct snp_report_req req; + int rc, resp_len; + + lockdep_assert_held(&snp_cmd_mutex); + + if (!arg->req_data || !arg->resp_data) + return -EINVAL; + + if (copy_from_user(&req, (void __user *)arg->req_data, sizeof(req))) + return -EFAULT; + + /* + * The intermediate response buffer is used while decrypting the + * response payload. Make sure that it has enough space to cover the + * authtag. + */ + resp_len = sizeof(resp->data) + crypto->a_len; + resp = kzalloc(resp_len, GFP_KERNEL_ACCOUNT); + if (!resp) + return -ENOMEM; + + rc = handle_guest_request(snp_dev, SVM_VMGEXIT_GUEST_REQUEST, arg->msg_version, + SNP_MSG_REPORT_REQ, &req, sizeof(req), resp->data, + resp_len, &arg->fw_err); + if (rc) + goto e_free; + + if (copy_to_user((void __user *)arg->resp_data, resp, sizeof(*resp))) + rc = -EFAULT; + +e_free: + kfree(resp); + return rc; +} + +static int get_derived_key(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg) +{ + struct snp_guest_crypto *crypto = snp_dev->crypto; + struct snp_derived_key_resp resp = {0}; + struct snp_derived_key_req req; + int rc, resp_len; + /* Response data is 64 bytes and max authsize for GCM is 16 bytes. */ + u8 buf[64 + 16]; + + lockdep_assert_held(&snp_cmd_mutex); + + if (!arg->req_data || !arg->resp_data) + return -EINVAL; + + /* + * The intermediate response buffer is used while decrypting the + * response payload. Make sure that it has enough space to cover the + * authtag. + */ + resp_len = sizeof(resp.data) + crypto->a_len; + if (sizeof(buf) < resp_len) + return -ENOMEM; + + if (copy_from_user(&req, (void __user *)arg->req_data, sizeof(req))) + return -EFAULT; + + rc = handle_guest_request(snp_dev, SVM_VMGEXIT_GUEST_REQUEST, arg->msg_version, + SNP_MSG_KEY_REQ, &req, sizeof(req), buf, resp_len, + &arg->fw_err); + if (rc) + return rc; + + memcpy(resp.data, buf, sizeof(resp.data)); + if (copy_to_user((void __user *)arg->resp_data, &resp, sizeof(resp))) + rc = -EFAULT; + + /* The response buffer contains the sensitive data, explicitly clear it. */ + memzero_explicit(buf, sizeof(buf)); + memzero_explicit(&resp, sizeof(resp)); + return rc; +} + +static int get_ext_report(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg) +{ + struct snp_guest_crypto *crypto = snp_dev->crypto; + struct snp_ext_report_req req; + struct snp_report_resp *resp; + int ret, npages = 0, resp_len; + + lockdep_assert_held(&snp_cmd_mutex); + + if (!arg->req_data || !arg->resp_data) + return -EINVAL; + + if (copy_from_user(&req, (void __user *)arg->req_data, sizeof(req))) + return -EFAULT; + + /* userspace does not want certificate data */ + if (!req.certs_len || !req.certs_address) + goto cmd; + + if (req.certs_len > SEV_FW_BLOB_MAX_SIZE || + !IS_ALIGNED(req.certs_len, PAGE_SIZE)) + return -EINVAL; + + if (!access_ok((const void __user *)req.certs_address, req.certs_len)) + return -EFAULT; + + /* + * Initialize the intermediate buffer with all zeros. This buffer + * is used in the guest request message to get the certs blob from + * the host. If host does not supply any certs in it, then copy + * zeros to indicate that certificate data was not provided. + */ + memset(snp_dev->certs_data, 0, req.certs_len); + npages = req.certs_len >> PAGE_SHIFT; +cmd: + /* + * The intermediate response buffer is used while decrypting the + * response payload. Make sure that it has enough space to cover the + * authtag. + */ + resp_len = sizeof(resp->data) + crypto->a_len; + resp = kzalloc(resp_len, GFP_KERNEL_ACCOUNT); + if (!resp) + return -ENOMEM; + + snp_dev->input.data_npages = npages; + ret = handle_guest_request(snp_dev, SVM_VMGEXIT_EXT_GUEST_REQUEST, arg->msg_version, + SNP_MSG_REPORT_REQ, &req.data, + sizeof(req.data), resp->data, resp_len, &arg->fw_err); + + /* If certs length is invalid then copy the returned length */ + if (arg->fw_err == SNP_GUEST_REQ_INVALID_LEN) { + req.certs_len = snp_dev->input.data_npages << PAGE_SHIFT; + + if (copy_to_user((void __user *)arg->req_data, &req, sizeof(req))) + ret = -EFAULT; + } + + if (ret) + goto e_free; + + if (npages && + copy_to_user((void __user *)req.certs_address, snp_dev->certs_data, + req.certs_len)) { + ret = -EFAULT; + goto e_free; + } + + if (copy_to_user((void __user *)arg->resp_data, resp, sizeof(*resp))) + ret = -EFAULT; + +e_free: + kfree(resp); + return ret; +} + +static long snp_guest_ioctl(struct file *file, unsigned int ioctl, unsigned long arg) +{ + struct snp_guest_dev *snp_dev = to_snp_dev(file); + void __user *argp = (void __user *)arg; + struct snp_guest_request_ioctl input; + int ret = -ENOTTY; + + if (copy_from_user(&input, argp, sizeof(input))) + return -EFAULT; + + input.fw_err = 0xff; + + /* Message version must be non-zero */ + if (!input.msg_version) + return -EINVAL; + + mutex_lock(&snp_cmd_mutex); + + /* Check if the VMPCK is not empty */ + if (is_vmpck_empty(snp_dev)) { + dev_err_ratelimited(snp_dev->dev, "VMPCK is disabled\n"); + mutex_unlock(&snp_cmd_mutex); + return -ENOTTY; + } + + switch (ioctl) { + case SNP_GET_REPORT: + ret = get_report(snp_dev, &input); + break; + case SNP_GET_DERIVED_KEY: + ret = get_derived_key(snp_dev, &input); + break; + case SNP_GET_EXT_REPORT: + ret = get_ext_report(snp_dev, &input); + break; + default: + break; + } + + mutex_unlock(&snp_cmd_mutex); + + if (input.fw_err && copy_to_user(argp, &input, sizeof(input))) + return -EFAULT; + + return ret; +} + +static void free_shared_pages(void *buf, size_t sz) +{ + unsigned int npages = PAGE_ALIGN(sz) >> PAGE_SHIFT; + int ret; + + if (!buf) + return; + + ret = set_memory_encrypted((unsigned long)buf, npages); + if (ret) { + WARN_ONCE(ret, "failed to restore encryption mask (leak it)\n"); + return; + } + + __free_pages(virt_to_page(buf), get_order(sz)); +} + +static void *alloc_shared_pages(struct device *dev, size_t sz) +{ + unsigned int npages = PAGE_ALIGN(sz) >> PAGE_SHIFT; + struct page *page; + int ret; + + page = alloc_pages(GFP_KERNEL_ACCOUNT, get_order(sz)); + if (!page) + return NULL; + + ret = set_memory_decrypted((unsigned long)page_address(page), npages); + if (ret) { + dev_err(dev, "failed to mark page shared, ret=%d\n", ret); + __free_pages(page, get_order(sz)); + return NULL; + } + + return page_address(page); +} + +static const struct file_operations snp_guest_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = snp_guest_ioctl, +}; + +static u8 *get_vmpck(int id, struct snp_secrets_page_layout *layout, u32 **seqno) +{ + u8 *key = NULL; + + switch (id) { + case 0: + *seqno = &layout->os_area.msg_seqno_0; + key = layout->vmpck0; + break; + case 1: + *seqno = &layout->os_area.msg_seqno_1; + key = layout->vmpck1; + break; + case 2: + *seqno = &layout->os_area.msg_seqno_2; + key = layout->vmpck2; + break; + case 3: + *seqno = &layout->os_area.msg_seqno_3; + key = layout->vmpck3; + break; + default: + break; + } + + return key; +} + +static int __init sev_guest_probe(struct platform_device *pdev) +{ + struct snp_secrets_page_layout *layout; + struct sev_guest_platform_data *data; + struct device *dev = &pdev->dev; + struct snp_guest_dev *snp_dev; + struct miscdevice *misc; + int ret; + + if (!dev->platform_data) + return -ENODEV; + + data = (struct sev_guest_platform_data *)dev->platform_data; + layout = (__force void *)ioremap_encrypted(data->secrets_gpa, PAGE_SIZE); + if (!layout) + return -ENODEV; + + ret = -ENOMEM; + snp_dev = devm_kzalloc(&pdev->dev, sizeof(struct snp_guest_dev), GFP_KERNEL); + if (!snp_dev) + goto e_unmap; + + ret = -EINVAL; + snp_dev->vmpck = get_vmpck(vmpck_id, layout, &snp_dev->os_area_msg_seqno); + if (!snp_dev->vmpck) { + dev_err(dev, "invalid vmpck id %d\n", vmpck_id); + goto e_unmap; + } + + /* Verify that VMPCK is not zero. */ + if (is_vmpck_empty(snp_dev)) { + dev_err(dev, "vmpck id %d is null\n", vmpck_id); + goto e_unmap; + } + + platform_set_drvdata(pdev, snp_dev); + snp_dev->dev = dev; + snp_dev->layout = layout; + + /* Allocate the shared page used for the request and response message. */ + snp_dev->request = alloc_shared_pages(dev, sizeof(struct snp_guest_msg)); + if (!snp_dev->request) + goto e_unmap; + + snp_dev->response = alloc_shared_pages(dev, sizeof(struct snp_guest_msg)); + if (!snp_dev->response) + goto e_free_request; + + snp_dev->certs_data = alloc_shared_pages(dev, SEV_FW_BLOB_MAX_SIZE); + if (!snp_dev->certs_data) + goto e_free_response; + + ret = -EIO; + snp_dev->crypto = init_crypto(snp_dev, snp_dev->vmpck, VMPCK_KEY_LEN); + if (!snp_dev->crypto) + goto e_free_cert_data; + + misc = &snp_dev->misc; + misc->minor = MISC_DYNAMIC_MINOR; + misc->name = DEVICE_NAME; + misc->fops = &snp_guest_fops; + + /* initial the input address for guest request */ + snp_dev->input.req_gpa = __pa(snp_dev->request); + snp_dev->input.resp_gpa = __pa(snp_dev->response); + snp_dev->input.data_gpa = __pa(snp_dev->certs_data); + + ret = misc_register(misc); + if (ret) + goto e_free_cert_data; + + dev_info(dev, "Initialized SEV guest driver (using vmpck_id %d)\n", vmpck_id); + return 0; + +e_free_cert_data: + free_shared_pages(snp_dev->certs_data, SEV_FW_BLOB_MAX_SIZE); +e_free_response: + free_shared_pages(snp_dev->response, sizeof(struct snp_guest_msg)); +e_free_request: + free_shared_pages(snp_dev->request, sizeof(struct snp_guest_msg)); +e_unmap: + iounmap(layout); + return ret; +} + +static int __exit sev_guest_remove(struct platform_device *pdev) +{ + struct snp_guest_dev *snp_dev = platform_get_drvdata(pdev); + + free_shared_pages(snp_dev->certs_data, SEV_FW_BLOB_MAX_SIZE); + free_shared_pages(snp_dev->response, sizeof(struct snp_guest_msg)); + free_shared_pages(snp_dev->request, sizeof(struct snp_guest_msg)); + deinit_crypto(snp_dev->crypto); + misc_deregister(&snp_dev->misc); + + return 0; +} + +/* + * 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". + */ +static struct platform_driver sev_guest_driver = { + .remove = __exit_p(sev_guest_remove), + .driver = { + .name = "sev-guest", + }, +}; + +module_platform_driver_probe(sev_guest_driver, sev_guest_probe); + +MODULE_AUTHOR("Brijesh Singh "); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0.0"); +MODULE_DESCRIPTION("AMD SEV Guest Driver"); diff --git a/drivers/virt/coco/sev-guest/sev-guest.h b/drivers/virt/coco/sev-guest/sev-guest.h new file mode 100644 index 000000000000..d39bdd013765 --- /dev/null +++ b/drivers/virt/coco/sev-guest/sev-guest.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021 Advanced Micro Devices, Inc. + * + * Author: Brijesh Singh + * + * SEV-SNP API spec is available at https://developer.amd.com/sev + */ + +#ifndef __VIRT_SEVGUEST_H__ +#define __VIRT_SEVGUEST_H__ + +#include + +#define MAX_AUTHTAG_LEN 32 + +/* See SNP spec SNP_GUEST_REQUEST section for the structure */ +enum msg_type { + SNP_MSG_TYPE_INVALID = 0, + SNP_MSG_CPUID_REQ, + SNP_MSG_CPUID_RSP, + SNP_MSG_KEY_REQ, + SNP_MSG_KEY_RSP, + SNP_MSG_REPORT_REQ, + SNP_MSG_REPORT_RSP, + SNP_MSG_EXPORT_REQ, + SNP_MSG_EXPORT_RSP, + SNP_MSG_IMPORT_REQ, + SNP_MSG_IMPORT_RSP, + SNP_MSG_ABSORB_REQ, + SNP_MSG_ABSORB_RSP, + SNP_MSG_VMRK_REQ, + SNP_MSG_VMRK_RSP, + + SNP_MSG_TYPE_MAX +}; + +enum aead_algo { + SNP_AEAD_INVALID, + SNP_AEAD_AES_256_GCM, +}; + +struct snp_guest_msg_hdr { + u8 authtag[MAX_AUTHTAG_LEN]; + u64 msg_seqno; + u8 rsvd1[8]; + u8 algo; + u8 hdr_version; + u16 hdr_sz; + u8 msg_type; + u8 msg_version; + u16 msg_sz; + u32 rsvd2; + u8 msg_vmpck; + u8 rsvd3[35]; +} __packed; + +struct snp_guest_msg { + struct snp_guest_msg_hdr hdr; + u8 payload[4000]; +} __packed; + +/* + * The secrets page contains 96-bytes of reserved field that can be used by + * the guest OS. The guest OS uses the area to save the message sequence + * number for each VMPCK. + * + * See the GHCB spec section Secret page layout for the format for this area. + */ +struct secrets_os_area { + u32 msg_seqno_0; + u32 msg_seqno_1; + u32 msg_seqno_2; + u32 msg_seqno_3; + u64 ap_jump_table_pa; + u8 rsvd[40]; + u8 guest_usage[32]; +} __packed; + +#define VMPCK_KEY_LEN 32 + +/* See the SNP spec version 0.9 for secrets page format */ +struct snp_secrets_page_layout { + u32 version; + u32 imien : 1, + rsvd1 : 31; + u32 fms; + u32 rsvd2; + u8 gosvw[16]; + u8 vmpck0[VMPCK_KEY_LEN]; + u8 vmpck1[VMPCK_KEY_LEN]; + u8 vmpck2[VMPCK_KEY_LEN]; + u8 vmpck3[VMPCK_KEY_LEN]; + struct secrets_os_area os_area; + u8 rsvd3[3840]; +} __packed; + +#endif /* __VIRT_SEVGUEST_H__ */ diff --git a/drivers/virt/coco/sevguest/Kconfig b/drivers/virt/coco/sevguest/Kconfig deleted file mode 100644 index 74ca1fe09437..000000000000 --- a/drivers/virt/coco/sevguest/Kconfig +++ /dev/null @@ -1,14 +0,0 @@ -config SEV_GUEST - tristate "AMD SEV Guest driver" - default m - depends on AMD_MEM_ENCRYPT - select CRYPTO_AEAD2 - select CRYPTO_GCM - help - SEV-SNP firmware provides the guest a mechanism to communicate with - the PSP without risk from a malicious hypervisor who wishes to read, - alter, drop or replay the messages sent. The driver provides - userspace interface to communicate with the PSP to request the - attestation report and more. - - If you choose 'M' here, this module will be called sevguest. diff --git a/drivers/virt/coco/sevguest/Makefile b/drivers/virt/coco/sevguest/Makefile deleted file mode 100644 index b1ffb2b4177b..000000000000 --- a/drivers/virt/coco/sevguest/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_SEV_GUEST) += sevguest.o diff --git a/drivers/virt/coco/sevguest/sevguest.c b/drivers/virt/coco/sevguest/sevguest.c deleted file mode 100644 index 18c3231a816d..000000000000 --- a/drivers/virt/coco/sevguest/sevguest.c +++ /dev/null @@ -1,743 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * AMD Secure Encrypted Virtualization (SEV) guest driver interface - * - * Copyright (C) 2021 Advanced Micro Devices, Inc. - * - * Author: Brijesh Singh - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "sevguest.h" - -#define DEVICE_NAME "sev-guest" -#define AAD_LEN 48 -#define MSG_HDR_VER 1 - -struct snp_guest_crypto { - struct crypto_aead *tfm; - u8 *iv, *authtag; - int iv_len, a_len; -}; - -struct snp_guest_dev { - struct device *dev; - struct miscdevice misc; - - void *certs_data; - struct snp_guest_crypto *crypto; - struct snp_guest_msg *request, *response; - struct snp_secrets_page_layout *layout; - struct snp_req_data input; - u32 *os_area_msg_seqno; - u8 *vmpck; -}; - -static u32 vmpck_id; -module_param(vmpck_id, uint, 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. */ -static DEFINE_MUTEX(snp_cmd_mutex); - -static bool is_vmpck_empty(struct snp_guest_dev *snp_dev) -{ - char zero_key[VMPCK_KEY_LEN] = {0}; - - if (snp_dev->vmpck) - return !memcmp(snp_dev->vmpck, zero_key, VMPCK_KEY_LEN); - - return true; -} - -static void snp_disable_vmpck(struct snp_guest_dev *snp_dev) -{ - memzero_explicit(snp_dev->vmpck, VMPCK_KEY_LEN); - snp_dev->vmpck = NULL; -} - -static inline u64 __snp_get_msg_seqno(struct snp_guest_dev *snp_dev) -{ - u64 count; - - lockdep_assert_held(&snp_cmd_mutex); - - /* Read the current message sequence counter from secrets pages */ - count = *snp_dev->os_area_msg_seqno; - - return count + 1; -} - -/* Return a non-zero on success */ -static u64 snp_get_msg_seqno(struct snp_guest_dev *snp_dev) -{ - u64 count = __snp_get_msg_seqno(snp_dev); - - /* - * The message sequence counter for the SNP guest request is a 64-bit - * value but the version 2 of GHCB specification defines a 32-bit storage - * for it. If the counter exceeds the 32-bit value then return zero. - * The caller should check the return value, but if the caller happens to - * not check the value and use it, then the firmware treats zero as an - * invalid number and will fail the message request. - */ - if (count >= UINT_MAX) { - dev_err(snp_dev->dev, "request message sequence counter overflow\n"); - return 0; - } - - return count; -} - -static void snp_inc_msg_seqno(struct snp_guest_dev *snp_dev) -{ - /* - * The counter is also incremented by the PSP, so increment it by 2 - * and save in secrets page. - */ - *snp_dev->os_area_msg_seqno += 2; -} - -static inline struct snp_guest_dev *to_snp_dev(struct file *file) -{ - struct miscdevice *dev = file->private_data; - - return container_of(dev, struct snp_guest_dev, misc); -} - -static struct snp_guest_crypto *init_crypto(struct snp_guest_dev *snp_dev, u8 *key, size_t keylen) -{ - struct snp_guest_crypto *crypto; - - crypto = kzalloc(sizeof(*crypto), GFP_KERNEL_ACCOUNT); - if (!crypto) - return NULL; - - crypto->tfm = crypto_alloc_aead("gcm(aes)", 0, 0); - if (IS_ERR(crypto->tfm)) - goto e_free; - - if (crypto_aead_setkey(crypto->tfm, key, keylen)) - goto e_free_crypto; - - crypto->iv_len = crypto_aead_ivsize(crypto->tfm); - crypto->iv = kmalloc(crypto->iv_len, GFP_KERNEL_ACCOUNT); - if (!crypto->iv) - goto e_free_crypto; - - if (crypto_aead_authsize(crypto->tfm) > MAX_AUTHTAG_LEN) { - if (crypto_aead_setauthsize(crypto->tfm, MAX_AUTHTAG_LEN)) { - dev_err(snp_dev->dev, "failed to set authsize to %d\n", MAX_AUTHTAG_LEN); - goto e_free_iv; - } - } - - crypto->a_len = crypto_aead_authsize(crypto->tfm); - crypto->authtag = kmalloc(crypto->a_len, GFP_KERNEL_ACCOUNT); - if (!crypto->authtag) - goto e_free_auth; - - return crypto; - -e_free_auth: - kfree(crypto->authtag); -e_free_iv: - kfree(crypto->iv); -e_free_crypto: - crypto_free_aead(crypto->tfm); -e_free: - kfree(crypto); - - return NULL; -} - -static void deinit_crypto(struct snp_guest_crypto *crypto) -{ - crypto_free_aead(crypto->tfm); - kfree(crypto->iv); - kfree(crypto->authtag); - kfree(crypto); -} - -static int enc_dec_message(struct snp_guest_crypto *crypto, struct snp_guest_msg *msg, - u8 *src_buf, u8 *dst_buf, size_t len, bool enc) -{ - struct snp_guest_msg_hdr *hdr = &msg->hdr; - struct scatterlist src[3], dst[3]; - DECLARE_CRYPTO_WAIT(wait); - struct aead_request *req; - int ret; - - req = aead_request_alloc(crypto->tfm, GFP_KERNEL); - if (!req) - return -ENOMEM; - - /* - * AEAD memory operations: - * +------ AAD -------+------- DATA -----+---- AUTHTAG----+ - * | msg header | plaintext | hdr->authtag | - * | bytes 30h - 5Fh | or | | - * | | cipher | | - * +------------------+------------------+----------------+ - */ - sg_init_table(src, 3); - sg_set_buf(&src[0], &hdr->algo, AAD_LEN); - sg_set_buf(&src[1], src_buf, hdr->msg_sz); - sg_set_buf(&src[2], hdr->authtag, crypto->a_len); - - sg_init_table(dst, 3); - sg_set_buf(&dst[0], &hdr->algo, AAD_LEN); - sg_set_buf(&dst[1], dst_buf, hdr->msg_sz); - sg_set_buf(&dst[2], hdr->authtag, crypto->a_len); - - aead_request_set_ad(req, AAD_LEN); - aead_request_set_tfm(req, crypto->tfm); - aead_request_set_callback(req, 0, crypto_req_done, &wait); - - aead_request_set_crypt(req, src, dst, len, crypto->iv); - ret = crypto_wait_req(enc ? crypto_aead_encrypt(req) : crypto_aead_decrypt(req), &wait); - - aead_request_free(req); - return ret; -} - -static int __enc_payload(struct snp_guest_dev *snp_dev, struct snp_guest_msg *msg, - void *plaintext, size_t len) -{ - struct snp_guest_crypto *crypto = snp_dev->crypto; - struct snp_guest_msg_hdr *hdr = &msg->hdr; - - memset(crypto->iv, 0, crypto->iv_len); - memcpy(crypto->iv, &hdr->msg_seqno, sizeof(hdr->msg_seqno)); - - return enc_dec_message(crypto, msg, plaintext, msg->payload, len, true); -} - -static int dec_payload(struct snp_guest_dev *snp_dev, struct snp_guest_msg *msg, - void *plaintext, size_t len) -{ - struct snp_guest_crypto *crypto = snp_dev->crypto; - struct snp_guest_msg_hdr *hdr = &msg->hdr; - - /* Build IV with response buffer sequence number */ - memset(crypto->iv, 0, crypto->iv_len); - memcpy(crypto->iv, &hdr->msg_seqno, sizeof(hdr->msg_seqno)); - - return enc_dec_message(crypto, msg, msg->payload, plaintext, len, false); -} - -static int verify_and_dec_payload(struct snp_guest_dev *snp_dev, void *payload, u32 sz) -{ - struct snp_guest_crypto *crypto = snp_dev->crypto; - struct snp_guest_msg *resp = snp_dev->response; - struct snp_guest_msg *req = snp_dev->request; - struct snp_guest_msg_hdr *req_hdr = &req->hdr; - struct snp_guest_msg_hdr *resp_hdr = &resp->hdr; - - dev_dbg(snp_dev->dev, "response [seqno %lld type %d version %d sz %d]\n", - resp_hdr->msg_seqno, resp_hdr->msg_type, resp_hdr->msg_version, resp_hdr->msg_sz); - - /* Verify that the sequence counter is incremented by 1 */ - if (unlikely(resp_hdr->msg_seqno != (req_hdr->msg_seqno + 1))) - return -EBADMSG; - - /* Verify response message type and version number. */ - if (resp_hdr->msg_type != (req_hdr->msg_type + 1) || - resp_hdr->msg_version != req_hdr->msg_version) - return -EBADMSG; - - /* - * If the message size is greater than our buffer length then return - * an error. - */ - if (unlikely((resp_hdr->msg_sz + crypto->a_len) > sz)) - return -EBADMSG; - - /* Decrypt the payload */ - return dec_payload(snp_dev, resp, payload, resp_hdr->msg_sz + crypto->a_len); -} - -static int enc_payload(struct snp_guest_dev *snp_dev, u64 seqno, int version, u8 type, - void *payload, size_t sz) -{ - struct snp_guest_msg *req = snp_dev->request; - struct snp_guest_msg_hdr *hdr = &req->hdr; - - memset(req, 0, sizeof(*req)); - - hdr->algo = SNP_AEAD_AES_256_GCM; - hdr->hdr_version = MSG_HDR_VER; - hdr->hdr_sz = sizeof(*hdr); - hdr->msg_type = type; - hdr->msg_version = version; - hdr->msg_seqno = seqno; - hdr->msg_vmpck = vmpck_id; - hdr->msg_sz = sz; - - /* Verify the sequence number is non-zero */ - if (!hdr->msg_seqno) - return -ENOSR; - - dev_dbg(snp_dev->dev, "request [seqno %lld type %d version %d sz %d]\n", - hdr->msg_seqno, hdr->msg_type, hdr->msg_version, hdr->msg_sz); - - return __enc_payload(snp_dev, req, payload, sz); -} - -static int handle_guest_request(struct snp_guest_dev *snp_dev, u64 exit_code, int msg_ver, - u8 type, void *req_buf, size_t req_sz, void *resp_buf, - u32 resp_sz, __u64 *fw_err) -{ - unsigned long err; - u64 seqno; - int rc; - - /* Get message sequence and verify that its a non-zero */ - seqno = snp_get_msg_seqno(snp_dev); - if (!seqno) - return -EIO; - - memset(snp_dev->response, 0, sizeof(struct snp_guest_msg)); - - /* Encrypt the userspace provided payload */ - rc = enc_payload(snp_dev, seqno, msg_ver, type, req_buf, req_sz); - if (rc) - return rc; - - /* Call firmware to process the request */ - rc = snp_issue_guest_request(exit_code, &snp_dev->input, &err); - if (fw_err) - *fw_err = err; - - if (rc) - return rc; - - /* - * The verify_and_dec_payload() will fail only if the hypervisor is - * actively modifying the message header or corrupting the encrypted payload. - * This hints that hypervisor is acting in a bad faith. Disable the VMPCK so that - * the key cannot be used for any communication. The key is disabled to ensure - * that AES-GCM does not use the same IV while encrypting the request payload. - */ - rc = verify_and_dec_payload(snp_dev, resp_buf, resp_sz); - if (rc) { - dev_alert(snp_dev->dev, - "Detected unexpected decode failure, disabling the vmpck_id %d\n", - vmpck_id); - snp_disable_vmpck(snp_dev); - return rc; - } - - /* Increment to new message sequence after payload decryption was successful. */ - snp_inc_msg_seqno(snp_dev); - - return 0; -} - -static int get_report(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg) -{ - struct snp_guest_crypto *crypto = snp_dev->crypto; - struct snp_report_resp *resp; - struct snp_report_req req; - int rc, resp_len; - - lockdep_assert_held(&snp_cmd_mutex); - - if (!arg->req_data || !arg->resp_data) - return -EINVAL; - - if (copy_from_user(&req, (void __user *)arg->req_data, sizeof(req))) - return -EFAULT; - - /* - * The intermediate response buffer is used while decrypting the - * response payload. Make sure that it has enough space to cover the - * authtag. - */ - resp_len = sizeof(resp->data) + crypto->a_len; - resp = kzalloc(resp_len, GFP_KERNEL_ACCOUNT); - if (!resp) - return -ENOMEM; - - rc = handle_guest_request(snp_dev, SVM_VMGEXIT_GUEST_REQUEST, arg->msg_version, - SNP_MSG_REPORT_REQ, &req, sizeof(req), resp->data, - resp_len, &arg->fw_err); - if (rc) - goto e_free; - - if (copy_to_user((void __user *)arg->resp_data, resp, sizeof(*resp))) - rc = -EFAULT; - -e_free: - kfree(resp); - return rc; -} - -static int get_derived_key(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg) -{ - struct snp_guest_crypto *crypto = snp_dev->crypto; - struct snp_derived_key_resp resp = {0}; - struct snp_derived_key_req req; - int rc, resp_len; - /* Response data is 64 bytes and max authsize for GCM is 16 bytes. */ - u8 buf[64 + 16]; - - lockdep_assert_held(&snp_cmd_mutex); - - if (!arg->req_data || !arg->resp_data) - return -EINVAL; - - /* - * The intermediate response buffer is used while decrypting the - * response payload. Make sure that it has enough space to cover the - * authtag. - */ - resp_len = sizeof(resp.data) + crypto->a_len; - if (sizeof(buf) < resp_len) - return -ENOMEM; - - if (copy_from_user(&req, (void __user *)arg->req_data, sizeof(req))) - return -EFAULT; - - rc = handle_guest_request(snp_dev, SVM_VMGEXIT_GUEST_REQUEST, arg->msg_version, - SNP_MSG_KEY_REQ, &req, sizeof(req), buf, resp_len, - &arg->fw_err); - if (rc) - return rc; - - memcpy(resp.data, buf, sizeof(resp.data)); - if (copy_to_user((void __user *)arg->resp_data, &resp, sizeof(resp))) - rc = -EFAULT; - - /* The response buffer contains the sensitive data, explicitly clear it. */ - memzero_explicit(buf, sizeof(buf)); - memzero_explicit(&resp, sizeof(resp)); - return rc; -} - -static int get_ext_report(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg) -{ - struct snp_guest_crypto *crypto = snp_dev->crypto; - struct snp_ext_report_req req; - struct snp_report_resp *resp; - int ret, npages = 0, resp_len; - - lockdep_assert_held(&snp_cmd_mutex); - - if (!arg->req_data || !arg->resp_data) - return -EINVAL; - - if (copy_from_user(&req, (void __user *)arg->req_data, sizeof(req))) - return -EFAULT; - - /* userspace does not want certificate data */ - if (!req.certs_len || !req.certs_address) - goto cmd; - - if (req.certs_len > SEV_FW_BLOB_MAX_SIZE || - !IS_ALIGNED(req.certs_len, PAGE_SIZE)) - return -EINVAL; - - if (!access_ok((const void __user *)req.certs_address, req.certs_len)) - return -EFAULT; - - /* - * Initialize the intermediate buffer with all zeros. This buffer - * is used in the guest request message to get the certs blob from - * the host. If host does not supply any certs in it, then copy - * zeros to indicate that certificate data was not provided. - */ - memset(snp_dev->certs_data, 0, req.certs_len); - npages = req.certs_len >> PAGE_SHIFT; -cmd: - /* - * The intermediate response buffer is used while decrypting the - * response payload. Make sure that it has enough space to cover the - * authtag. - */ - resp_len = sizeof(resp->data) + crypto->a_len; - resp = kzalloc(resp_len, GFP_KERNEL_ACCOUNT); - if (!resp) - return -ENOMEM; - - snp_dev->input.data_npages = npages; - ret = handle_guest_request(snp_dev, SVM_VMGEXIT_EXT_GUEST_REQUEST, arg->msg_version, - SNP_MSG_REPORT_REQ, &req.data, - sizeof(req.data), resp->data, resp_len, &arg->fw_err); - - /* If certs length is invalid then copy the returned length */ - if (arg->fw_err == SNP_GUEST_REQ_INVALID_LEN) { - req.certs_len = snp_dev->input.data_npages << PAGE_SHIFT; - - if (copy_to_user((void __user *)arg->req_data, &req, sizeof(req))) - ret = -EFAULT; - } - - if (ret) - goto e_free; - - if (npages && - copy_to_user((void __user *)req.certs_address, snp_dev->certs_data, - req.certs_len)) { - ret = -EFAULT; - goto e_free; - } - - if (copy_to_user((void __user *)arg->resp_data, resp, sizeof(*resp))) - ret = -EFAULT; - -e_free: - kfree(resp); - return ret; -} - -static long snp_guest_ioctl(struct file *file, unsigned int ioctl, unsigned long arg) -{ - struct snp_guest_dev *snp_dev = to_snp_dev(file); - void __user *argp = (void __user *)arg; - struct snp_guest_request_ioctl input; - int ret = -ENOTTY; - - if (copy_from_user(&input, argp, sizeof(input))) - return -EFAULT; - - input.fw_err = 0xff; - - /* Message version must be non-zero */ - if (!input.msg_version) - return -EINVAL; - - mutex_lock(&snp_cmd_mutex); - - /* Check if the VMPCK is not empty */ - if (is_vmpck_empty(snp_dev)) { - dev_err_ratelimited(snp_dev->dev, "VMPCK is disabled\n"); - mutex_unlock(&snp_cmd_mutex); - return -ENOTTY; - } - - switch (ioctl) { - case SNP_GET_REPORT: - ret = get_report(snp_dev, &input); - break; - case SNP_GET_DERIVED_KEY: - ret = get_derived_key(snp_dev, &input); - break; - case SNP_GET_EXT_REPORT: - ret = get_ext_report(snp_dev, &input); - break; - default: - break; - } - - mutex_unlock(&snp_cmd_mutex); - - if (input.fw_err && copy_to_user(argp, &input, sizeof(input))) - return -EFAULT; - - return ret; -} - -static void free_shared_pages(void *buf, size_t sz) -{ - unsigned int npages = PAGE_ALIGN(sz) >> PAGE_SHIFT; - int ret; - - if (!buf) - return; - - ret = set_memory_encrypted((unsigned long)buf, npages); - if (ret) { - WARN_ONCE(ret, "failed to restore encryption mask (leak it)\n"); - return; - } - - __free_pages(virt_to_page(buf), get_order(sz)); -} - -static void *alloc_shared_pages(struct device *dev, size_t sz) -{ - unsigned int npages = PAGE_ALIGN(sz) >> PAGE_SHIFT; - struct page *page; - int ret; - - page = alloc_pages(GFP_KERNEL_ACCOUNT, get_order(sz)); - if (!page) - return NULL; - - ret = set_memory_decrypted((unsigned long)page_address(page), npages); - if (ret) { - dev_err(dev, "failed to mark page shared, ret=%d\n", ret); - __free_pages(page, get_order(sz)); - return NULL; - } - - return page_address(page); -} - -static const struct file_operations snp_guest_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = snp_guest_ioctl, -}; - -static u8 *get_vmpck(int id, struct snp_secrets_page_layout *layout, u32 **seqno) -{ - u8 *key = NULL; - - switch (id) { - case 0: - *seqno = &layout->os_area.msg_seqno_0; - key = layout->vmpck0; - break; - case 1: - *seqno = &layout->os_area.msg_seqno_1; - key = layout->vmpck1; - break; - case 2: - *seqno = &layout->os_area.msg_seqno_2; - key = layout->vmpck2; - break; - case 3: - *seqno = &layout->os_area.msg_seqno_3; - key = layout->vmpck3; - break; - default: - break; - } - - return key; -} - -static int __init sev_guest_probe(struct platform_device *pdev) -{ - struct snp_secrets_page_layout *layout; - struct sev_guest_platform_data *data; - struct device *dev = &pdev->dev; - struct snp_guest_dev *snp_dev; - struct miscdevice *misc; - int ret; - - if (!dev->platform_data) - return -ENODEV; - - data = (struct sev_guest_platform_data *)dev->platform_data; - layout = (__force void *)ioremap_encrypted(data->secrets_gpa, PAGE_SIZE); - if (!layout) - return -ENODEV; - - ret = -ENOMEM; - snp_dev = devm_kzalloc(&pdev->dev, sizeof(struct snp_guest_dev), GFP_KERNEL); - if (!snp_dev) - goto e_unmap; - - ret = -EINVAL; - snp_dev->vmpck = get_vmpck(vmpck_id, layout, &snp_dev->os_area_msg_seqno); - if (!snp_dev->vmpck) { - dev_err(dev, "invalid vmpck id %d\n", vmpck_id); - goto e_unmap; - } - - /* Verify that VMPCK is not zero. */ - if (is_vmpck_empty(snp_dev)) { - dev_err(dev, "vmpck id %d is null\n", vmpck_id); - goto e_unmap; - } - - platform_set_drvdata(pdev, snp_dev); - snp_dev->dev = dev; - snp_dev->layout = layout; - - /* Allocate the shared page used for the request and response message. */ - snp_dev->request = alloc_shared_pages(dev, sizeof(struct snp_guest_msg)); - if (!snp_dev->request) - goto e_unmap; - - snp_dev->response = alloc_shared_pages(dev, sizeof(struct snp_guest_msg)); - if (!snp_dev->response) - goto e_free_request; - - snp_dev->certs_data = alloc_shared_pages(dev, SEV_FW_BLOB_MAX_SIZE); - if (!snp_dev->certs_data) - goto e_free_response; - - ret = -EIO; - snp_dev->crypto = init_crypto(snp_dev, snp_dev->vmpck, VMPCK_KEY_LEN); - if (!snp_dev->crypto) - goto e_free_cert_data; - - misc = &snp_dev->misc; - misc->minor = MISC_DYNAMIC_MINOR; - misc->name = DEVICE_NAME; - misc->fops = &snp_guest_fops; - - /* initial the input address for guest request */ - snp_dev->input.req_gpa = __pa(snp_dev->request); - snp_dev->input.resp_gpa = __pa(snp_dev->response); - snp_dev->input.data_gpa = __pa(snp_dev->certs_data); - - ret = misc_register(misc); - if (ret) - goto e_free_cert_data; - - dev_info(dev, "Initialized SEV guest driver (using vmpck_id %d)\n", vmpck_id); - return 0; - -e_free_cert_data: - free_shared_pages(snp_dev->certs_data, SEV_FW_BLOB_MAX_SIZE); -e_free_response: - free_shared_pages(snp_dev->response, sizeof(struct snp_guest_msg)); -e_free_request: - free_shared_pages(snp_dev->request, sizeof(struct snp_guest_msg)); -e_unmap: - iounmap(layout); - return ret; -} - -static int __exit sev_guest_remove(struct platform_device *pdev) -{ - struct snp_guest_dev *snp_dev = platform_get_drvdata(pdev); - - free_shared_pages(snp_dev->certs_data, SEV_FW_BLOB_MAX_SIZE); - free_shared_pages(snp_dev->response, sizeof(struct snp_guest_msg)); - free_shared_pages(snp_dev->request, sizeof(struct snp_guest_msg)); - deinit_crypto(snp_dev->crypto); - misc_deregister(&snp_dev->misc); - - return 0; -} - -/* - * This driver is a common SEV guest interface driver and meant to support - * any SEV guest API. As such, even though it has been introduced along with - * the SEV-SNP support, it is named "sev-guest". - */ -static struct platform_driver sev_guest_driver = { - .remove = __exit_p(sev_guest_remove), - .driver = { - .name = "sev-guest", - }, -}; - -module_platform_driver_probe(sev_guest_driver, sev_guest_probe); - -MODULE_AUTHOR("Brijesh Singh "); -MODULE_LICENSE("GPL"); -MODULE_VERSION("1.0.0"); -MODULE_DESCRIPTION("AMD SEV Guest Driver"); diff --git a/drivers/virt/coco/sevguest/sevguest.h b/drivers/virt/coco/sevguest/sevguest.h deleted file mode 100644 index d39bdd013765..000000000000 --- a/drivers/virt/coco/sevguest/sevguest.h +++ /dev/null @@ -1,98 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) 2021 Advanced Micro Devices, Inc. - * - * Author: Brijesh Singh - * - * SEV-SNP API spec is available at https://developer.amd.com/sev - */ - -#ifndef __VIRT_SEVGUEST_H__ -#define __VIRT_SEVGUEST_H__ - -#include - -#define MAX_AUTHTAG_LEN 32 - -/* See SNP spec SNP_GUEST_REQUEST section for the structure */ -enum msg_type { - SNP_MSG_TYPE_INVALID = 0, - SNP_MSG_CPUID_REQ, - SNP_MSG_CPUID_RSP, - SNP_MSG_KEY_REQ, - SNP_MSG_KEY_RSP, - SNP_MSG_REPORT_REQ, - SNP_MSG_REPORT_RSP, - SNP_MSG_EXPORT_REQ, - SNP_MSG_EXPORT_RSP, - SNP_MSG_IMPORT_REQ, - SNP_MSG_IMPORT_RSP, - SNP_MSG_ABSORB_REQ, - SNP_MSG_ABSORB_RSP, - SNP_MSG_VMRK_REQ, - SNP_MSG_VMRK_RSP, - - SNP_MSG_TYPE_MAX -}; - -enum aead_algo { - SNP_AEAD_INVALID, - SNP_AEAD_AES_256_GCM, -}; - -struct snp_guest_msg_hdr { - u8 authtag[MAX_AUTHTAG_LEN]; - u64 msg_seqno; - u8 rsvd1[8]; - u8 algo; - u8 hdr_version; - u16 hdr_sz; - u8 msg_type; - u8 msg_version; - u16 msg_sz; - u32 rsvd2; - u8 msg_vmpck; - u8 rsvd3[35]; -} __packed; - -struct snp_guest_msg { - struct snp_guest_msg_hdr hdr; - u8 payload[4000]; -} __packed; - -/* - * The secrets page contains 96-bytes of reserved field that can be used by - * the guest OS. The guest OS uses the area to save the message sequence - * number for each VMPCK. - * - * See the GHCB spec section Secret page layout for the format for this area. - */ -struct secrets_os_area { - u32 msg_seqno_0; - u32 msg_seqno_1; - u32 msg_seqno_2; - u32 msg_seqno_3; - u64 ap_jump_table_pa; - u8 rsvd[40]; - u8 guest_usage[32]; -} __packed; - -#define VMPCK_KEY_LEN 32 - -/* See the SNP spec version 0.9 for secrets page format */ -struct snp_secrets_page_layout { - u32 version; - u32 imien : 1, - rsvd1 : 31; - u32 fms; - u32 rsvd2; - u8 gosvw[16]; - u8 vmpck0[VMPCK_KEY_LEN]; - u8 vmpck1[VMPCK_KEY_LEN]; - u8 vmpck2[VMPCK_KEY_LEN]; - u8 vmpck3[VMPCK_KEY_LEN]; - struct secrets_os_area os_area; - u8 rsvd3[3840]; -} __packed; - -#endif /* __VIRT_SEVGUEST_H__ */ -- cgit v1.2.3 From c2106a231c2ba36ff9af50cdf2867b9a5f8150a6 Mon Sep 17 00:00:00 2001 From: Brijesh Singh Date: Fri, 22 Apr 2022 08:56:24 -0500 Subject: x86/sev: Get the AP jump table address from secrets page The GHCB specification section 2.7 states that when SEV-SNP is enabled, a guest should not rely on the hypervisor to provide the address of the AP jump table. Instead, if a guest BIOS wants to provide an AP jump table, it should record the address in the SNP secrets page so the guest operating system can obtain it directly from there. Fix this on the guest kernel side by having SNP guests use the AP jump table address published in the secrets page rather than issuing a GHCB request to get it. [ mroth: - Improve error handling when ioremap()/memremap() return NULL - Don't mix function calls with declarations - Add missing __init - Tweak commit message ] Fixes: 0afb6b660a6b ("x86/sev: Use SEV-SNP AP creation to start secondary CPUs") Signed-off-by: Brijesh Singh Signed-off-by: Michael Roth Signed-off-by: Borislav Petkov Link: https://lore.kernel.org/r/20220422135624.114172-3-michael.roth@amd.com --- arch/x86/include/asm/sev.h | 35 +++++++++++++++ arch/x86/kernel/sev.c | 76 ++++++++++++++++++++++----------- drivers/virt/coco/sev-guest/sev-guest.h | 35 --------------- 3 files changed, 87 insertions(+), 59 deletions(-) (limited to 'drivers') diff --git a/arch/x86/include/asm/sev.h b/arch/x86/include/asm/sev.h index 6e3dda4f82b5..19514524f0f8 100644 --- a/arch/x86/include/asm/sev.h +++ b/arch/x86/include/asm/sev.h @@ -99,6 +99,41 @@ struct sev_guest_platform_data { u64 secrets_gpa; }; +/* + * The secrets page contains 96-bytes of reserved field that can be used by + * the guest OS. The guest OS uses the area to save the message sequence + * number for each VMPCK. + * + * See the GHCB spec section Secret page layout for the format for this area. + */ +struct secrets_os_area { + u32 msg_seqno_0; + u32 msg_seqno_1; + u32 msg_seqno_2; + u32 msg_seqno_3; + u64 ap_jump_table_pa; + u8 rsvd[40]; + u8 guest_usage[32]; +} __packed; + +#define VMPCK_KEY_LEN 32 + +/* See the SNP spec version 0.9 for secrets page format */ +struct snp_secrets_page_layout { + u32 version; + u32 imien : 1, + rsvd1 : 31; + u32 fms; + u32 rsvd2; + u8 gosvw[16]; + u8 vmpck0[VMPCK_KEY_LEN]; + u8 vmpck1[VMPCK_KEY_LEN]; + u8 vmpck2[VMPCK_KEY_LEN]; + u8 vmpck3[VMPCK_KEY_LEN]; + struct secrets_os_area os_area; + u8 rsvd3[3840]; +} __packed; + #ifdef CONFIG_AMD_MEM_ENCRYPT extern struct static_key_false sev_es_enable_key; extern void __sev_es_ist_enter(struct pt_regs *regs); diff --git a/arch/x86/kernel/sev.c b/arch/x86/kernel/sev.c index b7fd1915560d..166375084b1f 100644 --- a/arch/x86/kernel/sev.c +++ b/arch/x86/kernel/sev.c @@ -558,6 +558,55 @@ void noinstr __sev_es_nmi_complete(void) __sev_put_ghcb(&state); } +static u64 __init get_secrets_page(void) +{ + u64 pa_data = boot_params.cc_blob_address; + struct cc_blob_sev_info info; + void *map; + + /* + * The CC blob contains the address of the secrets page, check if the + * blob is present. + */ + if (!pa_data) + return 0; + + map = early_memremap(pa_data, sizeof(info)); + if (!map) { + pr_err("Unable to locate SNP secrets page: failed to map the Confidential Computing blob.\n"); + return 0; + } + memcpy(&info, map, sizeof(info)); + early_memunmap(map, sizeof(info)); + + /* smoke-test the secrets page passed */ + if (!info.secrets_phys || info.secrets_len != PAGE_SIZE) + return 0; + + return info.secrets_phys; +} + +static u64 __init get_snp_jump_table_addr(void) +{ + struct snp_secrets_page_layout *layout; + u64 pa, addr; + + pa = get_secrets_page(); + if (!pa) + return 0; + + layout = (__force void *)ioremap_encrypted(pa, PAGE_SIZE); + if (!layout) { + pr_err("Unable to locate AP jump table address: failed to map the SNP secrets page.\n"); + return 0; + } + + addr = layout->os_area.ap_jump_table_pa; + iounmap(layout); + + return addr; +} + static u64 __init get_jump_table_addr(void) { struct ghcb_state state; @@ -565,6 +614,9 @@ static u64 __init get_jump_table_addr(void) struct ghcb *ghcb; u64 ret = 0; + if (cc_platform_has(CC_ATTR_GUEST_SEV_SNP)) + return get_snp_jump_table_addr(); + local_irq_save(flags); ghcb = __sev_get_ghcb(&state); @@ -2171,30 +2223,6 @@ static struct platform_device sev_guest_device = { .id = -1, }; -static u64 __init get_secrets_page(void) -{ - u64 pa_data = boot_params.cc_blob_address; - struct cc_blob_sev_info info; - void *map; - - /* - * The CC blob contains the address of the secrets page, check if the - * blob is present. - */ - if (!pa_data) - return 0; - - map = early_memremap(pa_data, sizeof(info)); - memcpy(&info, map, sizeof(info)); - early_memunmap(map, sizeof(info)); - - /* smoke-test the secrets page passed */ - if (!info.secrets_phys || info.secrets_len != PAGE_SIZE) - return 0; - - return info.secrets_phys; -} - static int __init snp_init_platform_device(void) { struct sev_guest_platform_data data; diff --git a/drivers/virt/coco/sev-guest/sev-guest.h b/drivers/virt/coco/sev-guest/sev-guest.h index d39bdd013765..21bda26fdb95 100644 --- a/drivers/virt/coco/sev-guest/sev-guest.h +++ b/drivers/virt/coco/sev-guest/sev-guest.h @@ -60,39 +60,4 @@ struct snp_guest_msg { u8 payload[4000]; } __packed; -/* - * The secrets page contains 96-bytes of reserved field that can be used by - * the guest OS. The guest OS uses the area to save the message sequence - * number for each VMPCK. - * - * See the GHCB spec section Secret page layout for the format for this area. - */ -struct secrets_os_area { - u32 msg_seqno_0; - u32 msg_seqno_1; - u32 msg_seqno_2; - u32 msg_seqno_3; - u64 ap_jump_table_pa; - u8 rsvd[40]; - u8 guest_usage[32]; -} __packed; - -#define VMPCK_KEY_LEN 32 - -/* See the SNP spec version 0.9 for secrets page format */ -struct snp_secrets_page_layout { - u32 version; - u32 imien : 1, - rsvd1 : 31; - u32 fms; - u32 rsvd2; - u8 gosvw[16]; - u8 vmpck0[VMPCK_KEY_LEN]; - u8 vmpck1[VMPCK_KEY_LEN]; - u8 vmpck2[VMPCK_KEY_LEN]; - u8 vmpck3[VMPCK_KEY_LEN]; - struct secrets_os_area os_area; - u8 rsvd3[3840]; -} __packed; - #endif /* __VIRT_SEVGUEST_H__ */ -- cgit v1.2.3