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 <brijesh.singh@amd.com> Signed-off-by: Borislav Petkov <bp@suse.de> Link: https://lore.kernel.org/r/20220307213356.2797205-44-brijesh.singh@amd.com
This commit is contained in:
parent
3a45b37538
commit
fce96cf044
9 changed files with 862 additions and 0 deletions
86
Documentation/virt/coco/sevguest.rst
Normal file
86
Documentation/virt/coco/sevguest.rst
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
.. 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.
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
|
@ -13,6 +13,7 @@ Linux Virtualization Support
|
||||||
guest-halt-polling
|
guest-halt-polling
|
||||||
ne_overview
|
ne_overview
|
||||||
acrn/index
|
acrn/index
|
||||||
|
coco/sevguest
|
||||||
|
|
||||||
.. only:: html and subproject
|
.. only:: html and subproject
|
||||||
|
|
||||||
|
|
|
@ -47,4 +47,7 @@ source "drivers/virt/vboxguest/Kconfig"
|
||||||
source "drivers/virt/nitro_enclaves/Kconfig"
|
source "drivers/virt/nitro_enclaves/Kconfig"
|
||||||
|
|
||||||
source "drivers/virt/acrn/Kconfig"
|
source "drivers/virt/acrn/Kconfig"
|
||||||
|
|
||||||
|
source "drivers/virt/coco/sevguest/Kconfig"
|
||||||
|
|
||||||
endif
|
endif
|
||||||
|
|
|
@ -9,3 +9,4 @@ obj-y += vboxguest/
|
||||||
|
|
||||||
obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves/
|
obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves/
|
||||||
obj-$(CONFIG_ACRN_HSM) += acrn/
|
obj-$(CONFIG_ACRN_HSM) += acrn/
|
||||||
|
obj-$(CONFIG_SEV_GUEST) += coco/sevguest/
|
||||||
|
|
14
drivers/virt/coco/sevguest/Kconfig
Normal file
14
drivers/virt/coco/sevguest/Kconfig
Normal file
|
@ -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.
|
2
drivers/virt/coco/sevguest/Makefile
Normal file
2
drivers/virt/coco/sevguest/Makefile
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
obj-$(CONFIG_SEV_GUEST) += sevguest.o
|
607
drivers/virt/coco/sevguest/sevguest.c
Normal file
607
drivers/virt/coco/sevguest/sevguest.c
Normal file
|
@ -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 <brijesh.singh@amd.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) "SNP: GUEST: " fmt
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/miscdevice.h>
|
||||||
|
#include <linux/set_memory.h>
|
||||||
|
#include <linux/fs.h>
|
||||||
|
#include <crypto/aead.h>
|
||||||
|
#include <linux/scatterlist.h>
|
||||||
|
#include <linux/psp-sev.h>
|
||||||
|
#include <uapi/linux/sev-guest.h>
|
||||||
|
#include <uapi/linux/psp-sev.h>
|
||||||
|
|
||||||
|
#include <asm/svm.h>
|
||||||
|
#include <asm/sev.h>
|
||||||
|
|
||||||
|
#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 <brijesh.singh@amd.com>");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_VERSION("1.0.0");
|
||||||
|
MODULE_DESCRIPTION("AMD SNP Guest Driver");
|
98
drivers/virt/coco/sevguest/sevguest.h
Normal file
98
drivers/virt/coco/sevguest/sevguest.h
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 Advanced Micro Devices, Inc.
|
||||||
|
*
|
||||||
|
* Author: Brijesh Singh <brijesh.singh@amd.com>
|
||||||
|
*
|
||||||
|
* SEV-SNP API spec is available at https://developer.amd.com/sev
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __VIRT_SEVGUEST_H__
|
||||||
|
#define __VIRT_SEVGUEST_H__
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
#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__ */
|
50
include/uapi/linux/sev-guest.h
Normal file
50
include/uapi/linux/sev-guest.h
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
|
||||||
|
/*
|
||||||
|
* Userspace interface for AMD SEV and SNP guest driver.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2021 Advanced Micro Devices, Inc.
|
||||||
|
*
|
||||||
|
* Author: Brijesh Singh <brijesh.singh@amd.com>
|
||||||
|
*
|
||||||
|
* SEV API specification is available at: https://developer.amd.com/sev/
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __UAPI_LINUX_SEV_GUEST_H_
|
||||||
|
#define __UAPI_LINUX_SEV_GUEST_H_
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
struct snp_report_req {
|
||||||
|
/* user data that should be included in the report */
|
||||||
|
__u8 user_data[64];
|
||||||
|
|
||||||
|
/* The vmpl level to be included in the report */
|
||||||
|
__u32 vmpl;
|
||||||
|
|
||||||
|
/* Must be zero filled */
|
||||||
|
__u8 rsvd[28];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct snp_report_resp {
|
||||||
|
/* response data, see SEV-SNP spec for the format */
|
||||||
|
__u8 data[4000];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct snp_guest_request_ioctl {
|
||||||
|
/* message version number (must be non-zero) */
|
||||||
|
__u8 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SNP_GUEST_REQ_IOC_TYPE 'S'
|
||||||
|
|
||||||
|
/* Get SNP attestation report */
|
||||||
|
#define SNP_GET_REPORT _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x0, struct snp_guest_request_ioctl)
|
||||||
|
|
||||||
|
#endif /* __UAPI_LINUX_SEV_GUEST_H_ */
|
Loading…
Reference in a new issue