Commit aa9f5841 authored by Sean Christopherson's avatar Sean Christopherson Committed by Paolo Bonzini

KVM: SVM: Exit to userspace on ENOMEM/EFAULT GHCB errors

Exit to userspace if setup_vmgexit_scratch() fails due to OOM or because
copying data from guest (userspace) memory failed/faulted.  The OOM
scenario is clearcut, it's userspace's decision as to whether it should
terminate the guest, free memory, etc...

As for -EFAULT, arguably, any guest issue is a violation of the guest's
contract with userspace, and thus userspace needs to decide how to
proceed.  E.g. userspace defines what is RAM vs. MMIO and communicates
that directly to the guest, KVM is not involved in deciding what is/isn't
RAM nor in communicating that information to the guest.  If the scratch
GPA doesn't resolve to a memslot, then the guest is not honoring the
memory configuration as defined by userspace.

And if userspace unmaps an hva for whatever reason, then exiting to
userspace with -EFAULT is absolutely the right thing to do.  KVM's ABI
currently sucks and doesn't provide enough information to act on the
-EFAULT, but that will hopefully be remedied in the future as there are
multiple use cases, e.g. uffd and virtiofs truncation, that shouldn't
require any work in KVM beyond returning -EFAULT with a small amount of
metadata.

KVM could define its ABI such that failure to access the scratch area is
reflected into the guest, i.e. establish a contract with userspace, but
that's undesirable as it limits KVM's options in the future, e.g. in the
potential uffd case any failure on a uaccess needs to kick out to
userspace.  KVM does have several cases where it reflects these errors
into the guest, e.g. kvm_pv_clock_pairing() and Hyper-V emulation, but
KVM would preferably "fix" those instead of propagating the falsehood
that any memory failure is the guest's fault.

Lastly, returning a boolean as an "error" for that a helper that isn't
named accordingly never works out well.

Fixes: ad5b3532 ("KVM: SVM: Do not terminate SEV-ES guests on GHCB validation failure")
Cc: Alper Gun <alpergun@google.com>
Cc: Peter Gonda <pgonda@google.com>
Signed-off-by: default avatarSean Christopherson <seanjc@google.com>
Message-Id: <20220225205209.3881130-1-seanjc@google.com>
Signed-off-by: default avatarPaolo Bonzini <pbonzini@redhat.com>
parent 5d6a3221
...@@ -2377,7 +2377,7 @@ static void sev_es_sync_from_ghcb(struct vcpu_svm *svm) ...@@ -2377,7 +2377,7 @@ static void sev_es_sync_from_ghcb(struct vcpu_svm *svm)
memset(ghcb->save.valid_bitmap, 0, sizeof(ghcb->save.valid_bitmap)); memset(ghcb->save.valid_bitmap, 0, sizeof(ghcb->save.valid_bitmap));
} }
static bool sev_es_validate_vmgexit(struct vcpu_svm *svm) static int sev_es_validate_vmgexit(struct vcpu_svm *svm)
{ {
struct kvm_vcpu *vcpu; struct kvm_vcpu *vcpu;
struct ghcb *ghcb; struct ghcb *ghcb;
...@@ -2482,7 +2482,7 @@ static bool sev_es_validate_vmgexit(struct vcpu_svm *svm) ...@@ -2482,7 +2482,7 @@ static bool sev_es_validate_vmgexit(struct vcpu_svm *svm)
goto vmgexit_err; goto vmgexit_err;
} }
return true; return 0;
vmgexit_err: vmgexit_err:
vcpu = &svm->vcpu; vcpu = &svm->vcpu;
...@@ -2505,7 +2505,8 @@ static bool sev_es_validate_vmgexit(struct vcpu_svm *svm) ...@@ -2505,7 +2505,8 @@ static bool sev_es_validate_vmgexit(struct vcpu_svm *svm)
ghcb_set_sw_exit_info_1(ghcb, 2); ghcb_set_sw_exit_info_1(ghcb, 2);
ghcb_set_sw_exit_info_2(ghcb, reason); ghcb_set_sw_exit_info_2(ghcb, reason);
return false; /* Resume the guest to "return" the error code. */
return 1;
} }
void sev_es_unmap_ghcb(struct vcpu_svm *svm) void sev_es_unmap_ghcb(struct vcpu_svm *svm)
...@@ -2564,7 +2565,7 @@ void pre_sev_run(struct vcpu_svm *svm, int cpu) ...@@ -2564,7 +2565,7 @@ void pre_sev_run(struct vcpu_svm *svm, int cpu)
} }
#define GHCB_SCRATCH_AREA_LIMIT (16ULL * PAGE_SIZE) #define GHCB_SCRATCH_AREA_LIMIT (16ULL * PAGE_SIZE)
static bool setup_vmgexit_scratch(struct vcpu_svm *svm, bool sync, u64 len) static int setup_vmgexit_scratch(struct vcpu_svm *svm, bool sync, u64 len)
{ {
struct vmcb_control_area *control = &svm->vmcb->control; struct vmcb_control_area *control = &svm->vmcb->control;
struct ghcb *ghcb = svm->sev_es.ghcb; struct ghcb *ghcb = svm->sev_es.ghcb;
...@@ -2617,14 +2618,14 @@ static bool setup_vmgexit_scratch(struct vcpu_svm *svm, bool sync, u64 len) ...@@ -2617,14 +2618,14 @@ static bool setup_vmgexit_scratch(struct vcpu_svm *svm, bool sync, u64 len)
} }
scratch_va = kvzalloc(len, GFP_KERNEL_ACCOUNT); scratch_va = kvzalloc(len, GFP_KERNEL_ACCOUNT);
if (!scratch_va) if (!scratch_va)
goto e_scratch; return -ENOMEM;
if (kvm_read_guest(svm->vcpu.kvm, scratch_gpa_beg, scratch_va, len)) { if (kvm_read_guest(svm->vcpu.kvm, scratch_gpa_beg, scratch_va, len)) {
/* Unable to copy scratch area from guest */ /* Unable to copy scratch area from guest */
pr_err("vmgexit: kvm_read_guest for scratch area failed\n"); pr_err("vmgexit: kvm_read_guest for scratch area failed\n");
kvfree(scratch_va); kvfree(scratch_va);
goto e_scratch; return -EFAULT;
} }
/* /*
...@@ -2640,13 +2641,13 @@ static bool setup_vmgexit_scratch(struct vcpu_svm *svm, bool sync, u64 len) ...@@ -2640,13 +2641,13 @@ static bool setup_vmgexit_scratch(struct vcpu_svm *svm, bool sync, u64 len)
svm->sev_es.ghcb_sa = scratch_va; svm->sev_es.ghcb_sa = scratch_va;
svm->sev_es.ghcb_sa_len = len; svm->sev_es.ghcb_sa_len = len;
return true; return 0;
e_scratch: e_scratch:
ghcb_set_sw_exit_info_1(ghcb, 2); ghcb_set_sw_exit_info_1(ghcb, 2);
ghcb_set_sw_exit_info_2(ghcb, GHCB_ERR_INVALID_SCRATCH_AREA); ghcb_set_sw_exit_info_2(ghcb, GHCB_ERR_INVALID_SCRATCH_AREA);
return false; return 1;
} }
static void set_ghcb_msr_bits(struct vcpu_svm *svm, u64 value, u64 mask, static void set_ghcb_msr_bits(struct vcpu_svm *svm, u64 value, u64 mask,
...@@ -2784,17 +2785,18 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu) ...@@ -2784,17 +2785,18 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu)
exit_code = ghcb_get_sw_exit_code(ghcb); exit_code = ghcb_get_sw_exit_code(ghcb);
if (!sev_es_validate_vmgexit(svm)) ret = sev_es_validate_vmgexit(svm);
return 1; if (ret)
return ret;
sev_es_sync_from_ghcb(svm); sev_es_sync_from_ghcb(svm);
ghcb_set_sw_exit_info_1(ghcb, 0); ghcb_set_sw_exit_info_1(ghcb, 0);
ghcb_set_sw_exit_info_2(ghcb, 0); ghcb_set_sw_exit_info_2(ghcb, 0);
ret = 1;
switch (exit_code) { switch (exit_code) {
case SVM_VMGEXIT_MMIO_READ: case SVM_VMGEXIT_MMIO_READ:
if (!setup_vmgexit_scratch(svm, true, control->exit_info_2)) ret = setup_vmgexit_scratch(svm, true, control->exit_info_2);
if (ret)
break; break;
ret = kvm_sev_es_mmio_read(vcpu, ret = kvm_sev_es_mmio_read(vcpu,
...@@ -2803,7 +2805,8 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu) ...@@ -2803,7 +2805,8 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu)
svm->sev_es.ghcb_sa); svm->sev_es.ghcb_sa);
break; break;
case SVM_VMGEXIT_MMIO_WRITE: case SVM_VMGEXIT_MMIO_WRITE:
if (!setup_vmgexit_scratch(svm, false, control->exit_info_2)) ret = setup_vmgexit_scratch(svm, false, control->exit_info_2);
if (ret)
break; break;
ret = kvm_sev_es_mmio_write(vcpu, ret = kvm_sev_es_mmio_write(vcpu,
...@@ -2836,6 +2839,7 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu) ...@@ -2836,6 +2839,7 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu)
ghcb_set_sw_exit_info_2(ghcb, GHCB_ERR_INVALID_INPUT); ghcb_set_sw_exit_info_2(ghcb, GHCB_ERR_INVALID_INPUT);
} }
ret = 1;
break; break;
} }
case SVM_VMGEXIT_UNSUPPORTED_EVENT: case SVM_VMGEXIT_UNSUPPORTED_EVENT:
...@@ -2855,6 +2859,7 @@ int sev_es_string_io(struct vcpu_svm *svm, int size, unsigned int port, int in) ...@@ -2855,6 +2859,7 @@ int sev_es_string_io(struct vcpu_svm *svm, int size, unsigned int port, int in)
{ {
int count; int count;
int bytes; int bytes;
int r;
if (svm->vmcb->control.exit_info_2 > INT_MAX) if (svm->vmcb->control.exit_info_2 > INT_MAX)
return -EINVAL; return -EINVAL;
...@@ -2863,8 +2868,9 @@ int sev_es_string_io(struct vcpu_svm *svm, int size, unsigned int port, int in) ...@@ -2863,8 +2868,9 @@ int sev_es_string_io(struct vcpu_svm *svm, int size, unsigned int port, int in)
if (unlikely(check_mul_overflow(count, size, &bytes))) if (unlikely(check_mul_overflow(count, size, &bytes)))
return -EINVAL; return -EINVAL;
if (!setup_vmgexit_scratch(svm, in, bytes)) r = setup_vmgexit_scratch(svm, in, bytes);
return 1; if (r)
return r;
return kvm_sev_es_string_io(&svm->vcpu, size, port, svm->sev_es.ghcb_sa, return kvm_sev_es_string_io(&svm->vcpu, size, port, svm->sev_es.ghcb_sa,
count, in); count, in);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment