Skip to content

Commit 88caf54

Browse files
codomaniabonzini
authored andcommitted
KVM: SEV: Provide support for SNP_GUEST_REQUEST NAE event
Version 2 of GHCB specification added support for the SNP Guest Request Message NAE event. The event allows for an SEV-SNP guest to make requests to the SEV-SNP firmware through the hypervisor using the SNP_GUEST_REQUEST API defined in the SEV-SNP firmware specification. This is used by guests primarily to request attestation reports from firmware. There are other request types are available as well, but the specifics of what guest requests are being made generally does not affect how they are handled by the hypervisor, which only serves as a proxy for the guest requests and firmware responses. Implement handling for these events. When an SNP Guest Request is issued, the guest will provide its own request/response pages, which could in theory be passed along directly to firmware. However, these pages would need special care: - Both pages are from shared guest memory, so they need to be protected from migration/etc. occurring while firmware reads/writes to them. At a minimum, this requires elevating the ref counts and potentially needing an explicit pinning of the memory. This places additional restrictions on what type of memory backends userspace can use for shared guest memory since there would be some reliance on using refcounted pages. - The response page needs to be switched to Firmware-owned state before the firmware can write to it, which can lead to potential host RMP #PFs if the guest is misbehaved and hands the host a guest page that KVM is writing to for other reasons (e.g. virtio buffers). Both of these issues can be avoided completely by using separately-allocated bounce pages for both the request/response pages and passing those to firmware instead. So that's the approach taken here. Signed-off-by: Brijesh Singh <[email protected]> Co-developed-by: Alexey Kardashevskiy <[email protected]> Signed-off-by: Alexey Kardashevskiy <[email protected]> Co-developed-by: Ashish Kalra <[email protected]> Signed-off-by: Ashish Kalra <[email protected]> Reviewed-by: Tom Lendacky <[email protected]> Reviewed-by: Liam Merwick <[email protected]> [mdr: ensure FW command failures are indicated to guest, drop extended request handling to be re-written as separate patch, massage commit] Signed-off-by: Michael Roth <[email protected]> Message-ID: <[email protected]> Signed-off-by: Paolo Bonzini <[email protected]>
1 parent b2ec042 commit 88caf54

File tree

3 files changed

+140
-0
lines changed

3 files changed

+140
-0
lines changed

arch/x86/kvm/svm/sev.c

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <linux/misc_cgroup.h>
2020
#include <linux/processor.h>
2121
#include <linux/trace_events.h>
22+
#include <uapi/linux/sev-guest.h>
2223

2324
#include <asm/pkru.h>
2425
#include <asm/trapnr.h>
@@ -326,6 +327,78 @@ static void sev_unbind_asid(struct kvm *kvm, unsigned int handle)
326327
sev_decommission(handle);
327328
}
328329

330+
/*
331+
* This sets up bounce buffers/firmware pages to handle SNP Guest Request
332+
* messages (e.g. attestation requests). See "SNP Guest Request" in the GHCB
333+
* 2.0 specification for more details.
334+
*
335+
* Technically, when an SNP Guest Request is issued, the guest will provide its
336+
* own request/response pages, which could in theory be passed along directly
337+
* to firmware rather than using bounce pages. However, these pages would need
338+
* special care:
339+
*
340+
* - Both pages are from shared guest memory, so they need to be protected
341+
* from migration/etc. occurring while firmware reads/writes to them. At a
342+
* minimum, this requires elevating the ref counts and potentially needing
343+
* an explicit pinning of the memory. This places additional restrictions
344+
* on what type of memory backends userspace can use for shared guest
345+
* memory since there is some reliance on using refcounted pages.
346+
*
347+
* - The response page needs to be switched to Firmware-owned[1] state
348+
* before the firmware can write to it, which can lead to potential
349+
* host RMP #PFs if the guest is misbehaved and hands the host a
350+
* guest page that KVM might write to for other reasons (e.g. virtio
351+
* buffers/etc.).
352+
*
353+
* Both of these issues can be avoided completely by using separately-allocated
354+
* bounce pages for both the request/response pages and passing those to
355+
* firmware instead. So that's what is being set up here.
356+
*
357+
* Guest requests rely on message sequence numbers to ensure requests are
358+
* issued to firmware in the order the guest issues them, so concurrent guest
359+
* requests generally shouldn't happen. But a misbehaved guest could issue
360+
* concurrent guest requests in theory, so a mutex is used to serialize
361+
* access to the bounce buffers.
362+
*
363+
* [1] See the "Page States" section of the SEV-SNP Firmware ABI for more
364+
* details on Firmware-owned pages, along with "RMP and VMPL Access Checks"
365+
* in the APM for details on the related RMP restrictions.
366+
*/
367+
static int snp_guest_req_init(struct kvm *kvm)
368+
{
369+
struct kvm_sev_info *sev = to_kvm_sev_info(kvm);
370+
struct page *req_page;
371+
372+
req_page = alloc_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO);
373+
if (!req_page)
374+
return -ENOMEM;
375+
376+
sev->guest_resp_buf = snp_alloc_firmware_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO);
377+
if (!sev->guest_resp_buf) {
378+
__free_page(req_page);
379+
return -EIO;
380+
}
381+
382+
sev->guest_req_buf = page_address(req_page);
383+
mutex_init(&sev->guest_req_mutex);
384+
385+
return 0;
386+
}
387+
388+
static void snp_guest_req_cleanup(struct kvm *kvm)
389+
{
390+
struct kvm_sev_info *sev = to_kvm_sev_info(kvm);
391+
392+
if (sev->guest_resp_buf)
393+
snp_free_firmware_page(sev->guest_resp_buf);
394+
395+
if (sev->guest_req_buf)
396+
__free_page(virt_to_page(sev->guest_req_buf));
397+
398+
sev->guest_req_buf = NULL;
399+
sev->guest_resp_buf = NULL;
400+
}
401+
329402
static int __sev_guest_init(struct kvm *kvm, struct kvm_sev_cmd *argp,
330403
struct kvm_sev_init *data,
331404
unsigned long vm_type)
@@ -376,6 +449,10 @@ static int __sev_guest_init(struct kvm *kvm, struct kvm_sev_cmd *argp,
376449
if (ret)
377450
goto e_free;
378451

452+
/* This needs to happen after SEV/SNP firmware initialization. */
453+
if (vm_type == KVM_X86_SNP_VM && snp_guest_req_init(kvm))
454+
goto e_free;
455+
379456
INIT_LIST_HEAD(&sev->regions_list);
380457
INIT_LIST_HEAD(&sev->mirror_vms);
381458
sev->need_init = false;
@@ -2834,6 +2911,8 @@ void sev_vm_destroy(struct kvm *kvm)
28342911
}
28352912

28362913
if (sev_snp_guest(kvm)) {
2914+
snp_guest_req_cleanup(kvm);
2915+
28372916
/*
28382917
* Decomission handles unbinding of the ASID. If it fails for
28392918
* some unexpected reason, just leak the ASID.
@@ -3299,6 +3378,13 @@ static int sev_es_validate_vmgexit(struct vcpu_svm *svm)
32993378
if (!sev_snp_guest(vcpu->kvm) || !kvm_ghcb_sw_scratch_is_valid(svm))
33003379
goto vmgexit_err;
33013380
break;
3381+
case SVM_VMGEXIT_GUEST_REQUEST:
3382+
if (!sev_snp_guest(vcpu->kvm) ||
3383+
!PAGE_ALIGNED(control->exit_info_1) ||
3384+
!PAGE_ALIGNED(control->exit_info_2) ||
3385+
control->exit_info_1 == control->exit_info_2)
3386+
goto vmgexit_err;
3387+
break;
33023388
default:
33033389
reason = GHCB_ERR_INVALID_EVENT;
33043390
goto vmgexit_err;
@@ -3917,6 +4003,51 @@ static int sev_snp_ap_creation(struct vcpu_svm *svm)
39174003
return ret;
39184004
}
39194005

4006+
static int snp_handle_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t resp_gpa)
4007+
{
4008+
struct sev_data_snp_guest_request data = {0};
4009+
struct kvm *kvm = svm->vcpu.kvm;
4010+
struct kvm_sev_info *sev = to_kvm_sev_info(kvm);
4011+
sev_ret_code fw_err = 0;
4012+
int ret;
4013+
4014+
if (!sev_snp_guest(kvm))
4015+
return -EINVAL;
4016+
4017+
mutex_lock(&sev->guest_req_mutex);
4018+
4019+
if (kvm_read_guest(kvm, req_gpa, sev->guest_req_buf, PAGE_SIZE)) {
4020+
ret = -EIO;
4021+
goto out_unlock;
4022+
}
4023+
4024+
data.gctx_paddr = __psp_pa(sev->snp_context);
4025+
data.req_paddr = __psp_pa(sev->guest_req_buf);
4026+
data.res_paddr = __psp_pa(sev->guest_resp_buf);
4027+
4028+
/*
4029+
* Firmware failures are propagated on to guest, but any other failure
4030+
* condition along the way should be reported to userspace. E.g. if
4031+
* the PSP is dead and commands are timing out.
4032+
*/
4033+
ret = sev_issue_cmd(kvm, SEV_CMD_SNP_GUEST_REQUEST, &data, &fw_err);
4034+
if (ret && !fw_err)
4035+
goto out_unlock;
4036+
4037+
if (kvm_write_guest(kvm, resp_gpa, sev->guest_resp_buf, PAGE_SIZE)) {
4038+
ret = -EIO;
4039+
goto out_unlock;
4040+
}
4041+
4042+
ghcb_set_sw_exit_info_2(svm->sev_es.ghcb, SNP_GUEST_ERR(0, fw_err));
4043+
4044+
ret = 1; /* resume guest */
4045+
4046+
out_unlock:
4047+
mutex_unlock(&sev->guest_req_mutex);
4048+
return ret;
4049+
}
4050+
39204051
static int sev_handle_vmgexit_msr_protocol(struct vcpu_svm *svm)
39214052
{
39224053
struct vmcb_control_area *control = &svm->vmcb->control;
@@ -4191,6 +4322,9 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu)
41914322

41924323
ret = 1;
41934324
break;
4325+
case SVM_VMGEXIT_GUEST_REQUEST:
4326+
ret = snp_handle_guest_req(svm, control->exit_info_1, control->exit_info_2);
4327+
break;
41944328
case SVM_VMGEXIT_UNSUPPORTED_EVENT:
41954329
vcpu_unimpl(vcpu,
41964330
"vmgexit: unsupported event - exit_info_1=%#llx, exit_info_2=%#llx\n",

arch/x86/kvm/svm/svm.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ struct kvm_sev_info {
9494
struct misc_cg *misc_cg; /* For misc cgroup accounting */
9595
atomic_t migration_in_progress;
9696
void *snp_context; /* SNP guest context page */
97+
void *guest_req_buf; /* Bounce buffer for SNP Guest Request input */
98+
void *guest_resp_buf; /* Bounce buffer for SNP Guest Request output */
99+
struct mutex guest_req_mutex; /* Must acquire before using bounce buffers */
97100
};
98101

99102
struct kvm_svm {

include/uapi/linux/sev-guest.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ struct snp_ext_report_req {
8989
#define SNP_GUEST_FW_ERR_MASK GENMASK_ULL(31, 0)
9090
#define SNP_GUEST_VMM_ERR_SHIFT 32
9191
#define SNP_GUEST_VMM_ERR(x) (((u64)x) << SNP_GUEST_VMM_ERR_SHIFT)
92+
#define SNP_GUEST_FW_ERR(x) ((x) & SNP_GUEST_FW_ERR_MASK)
93+
#define SNP_GUEST_ERR(vmm_err, fw_err) (SNP_GUEST_VMM_ERR(vmm_err) | \
94+
SNP_GUEST_FW_ERR(fw_err))
9295

9396
#define SNP_GUEST_VMM_ERR_INVALID_LEN 1
9497
#define SNP_GUEST_VMM_ERR_BUSY 2

0 commit comments

Comments
 (0)