KVM: x86: clear vcpu->run->hypercall.ret before exiting for KVM_EXIT_HYPERCALL
authorPaolo Bonzini <pbonzini@redhat.com>
Fri, 13 Dec 2024 19:36:25 +0000 (14:36 -0500)
committerPaolo Bonzini <pbonzini@redhat.com>
Sun, 22 Dec 2024 18:00:25 +0000 (13:00 -0500)
QEMU up to 9.2.0 is assuming that vcpu->run->hypercall.ret is 0 on exit and
it never modifies it when processing KVM_EXIT_HYPERCALL.  Make this explicit
in the code, to avoid breakage when KVM starts modifying that field.

This in principle is not a good idea... It would have been much better if
KVM had set the field to -KVM_ENOSYS from the beginning, so that a dumb
userspace that does nothing on KVM_EXIT_HYPERCALL would tell the guest it
does not support KVM_HC_MAP_GPA_RANGE.  However, breaking userspace is
a Very Bad Thing, as everybody should know.

Reported-by: Binbin Wu <binbin.wu@linux.intel.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
arch/x86/kvm/svm/sev.c
arch/x86/kvm/x86.c

index 943bd074a5d37212a1fdf1f01e42f1c8f0e416c6..9ffb0fb5aacd393b2d22a2600cd372d9b66a31b3 100644 (file)
@@ -3634,6 +3634,13 @@ static int snp_begin_psc_msr(struct vcpu_svm *svm, u64 ghcb_msr)
 
        vcpu->run->exit_reason = KVM_EXIT_HYPERCALL;
        vcpu->run->hypercall.nr = KVM_HC_MAP_GPA_RANGE;
+       /*
+        * In principle this should have been -KVM_ENOSYS, but userspace (QEMU <=9.2)
+        * assumed that vcpu->run->hypercall.ret is never changed by KVM and thus that
+        * it was always zero on KVM_EXIT_HYPERCALL.  Since KVM is now overwriting
+        * vcpu->run->hypercall.ret, ensuring that it is zero to not break QEMU.
+        */
+       vcpu->run->hypercall.ret = 0;
        vcpu->run->hypercall.args[0] = gpa;
        vcpu->run->hypercall.args[1] = 1;
        vcpu->run->hypercall.args[2] = (op == SNP_PAGE_STATE_PRIVATE)
@@ -3797,6 +3804,13 @@ next_range:
        case VMGEXIT_PSC_OP_SHARED:
                vcpu->run->exit_reason = KVM_EXIT_HYPERCALL;
                vcpu->run->hypercall.nr = KVM_HC_MAP_GPA_RANGE;
+               /*
+                * In principle this should have been -KVM_ENOSYS, but userspace (QEMU <=9.2)
+                * assumed that vcpu->run->hypercall.ret is never changed by KVM and thus that
+                * it was always zero on KVM_EXIT_HYPERCALL.  Since KVM is now overwriting
+                * vcpu->run->hypercall.ret, ensuring that it is zero to not break QEMU.
+                */
+               vcpu->run->hypercall.ret = 0;
                vcpu->run->hypercall.args[0] = gfn_to_gpa(gfn);
                vcpu->run->hypercall.args[1] = npages;
                vcpu->run->hypercall.args[2] = entry_start.operation == VMGEXIT_PSC_OP_PRIVATE
index 1b04092ec76aa4be07bad196eef7848f1396d638..615cba6fe46634b5cd35b07e11657b4940820a36 100644 (file)
@@ -10052,6 +10052,13 @@ unsigned long __kvm_emulate_hypercall(struct kvm_vcpu *vcpu, unsigned long nr,
 
                vcpu->run->exit_reason        = KVM_EXIT_HYPERCALL;
                vcpu->run->hypercall.nr       = KVM_HC_MAP_GPA_RANGE;
+               /*
+                * In principle this should have been -KVM_ENOSYS, but userspace (QEMU <=9.2)
+                * assumed that vcpu->run->hypercall.ret is never changed by KVM and thus that
+                * it was always zero on KVM_EXIT_HYPERCALL.  Since KVM is now overwriting
+                * vcpu->run->hypercall.ret, ensuring that it is zero to not break QEMU.
+                */
+               vcpu->run->hypercall.ret = 0;
                vcpu->run->hypercall.args[0]  = gpa;
                vcpu->run->hypercall.args[1]  = npages;
                vcpu->run->hypercall.args[2]  = attrs;