]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
KVM: nSVM: Stop leaking single-stepping on VMRUN into L2
authorYosry Ahmed <yosry@kernel.org>
Wed, 27 May 2026 23:46:55 +0000 (23:46 +0000)
committerSean Christopherson <seanjc@google.com>
Thu, 28 May 2026 01:45:48 +0000 (18:45 -0700)
According to the APM, TF on VMRUN causes a #DB after VMRUN completes on
the _host_ side. However, KVM injects a #DB in L2 context instead (or
exits to userspace if KVM_GUESTDBG_SINGLESTEP is set) in
kvm_skip_emulated_instruction().

Avoid single-step handling on VMRUN by open-coding the rest of
kvm_skip_emulated_instruction() in nested_svm_vmrun(). This doesn't look
pretty, but following changes will need to open-code
kvm_pmu_instruction_retired() anyway, and will cleanup the code.  This
ignores TF on VMRUN instead of injecting a spurious exception into
L2. Document this virtualization hole with a FIXME.

Note that a failed VMRUN would have been correctly single-stepped, but
now TF is always ignored for consistency and simplicity purposes.  VMX
does not support TF on a successful VMLAUNCH/VMRESUME, so it's unlikely
that single-stepping VMRUN properly is important, especially if it's
only for failed VMRUNs.

Fixes: c8e16b78c614 ("x86: KVM: svm: eliminate hardcoded RIP advancement from vmrun_interception()")
Signed-off-by: Yosry Ahmed <yosry@kernel.org>
Link: https://patch.msgid.link/20260527234711.4175166-2-yosry@kernel.org
Signed-off-by: Sean Christopherson <seanjc@google.com>
arch/x86/kvm/svm/nested.c
arch/x86/kvm/svm/svm.c
arch/x86/kvm/svm/svm.h

index b326420ac8830ec9b7cefc96b5e6fd6bd5ca92ec..a7ed06d88697b7f82efcc3db51ee9d91e01ad9bb 100644 (file)
@@ -30,6 +30,7 @@
 #include "lapic.h"
 #include "svm.h"
 #include "hyperv.h"
+#include "pmu.h"
 
 #define CC KVM_NESTED_VMENTER_CONSISTENCY_CHECK
 
@@ -1140,11 +1141,22 @@ int nested_svm_vmrun(struct kvm_vcpu *vcpu)
                        return kvm_handle_memory_failure(vcpu, X86EMUL_IO_NEEDED, NULL);
 
                /* Advance RIP past VMRUN as part of the nested #VMEXIT. */
-               return kvm_skip_emulated_instruction(vcpu);
+               if (!svm_skip_emulated_instruction(vcpu))
+                       return 0;
+
+               kvm_pmu_instruction_retired(vcpu);
+               return 1;
        }
 
-       /* At this point, VMRUN is guaranteed to not fault; advance RIP. */
-       ret = kvm_skip_emulated_instruction(vcpu);
+       /*
+        * At this point, VMRUN is guaranteed to not fault; advance RIP.
+        *
+        * FIXME: If TF is set on VMRUN should inject a #DB (or handle guest
+        * debugging) right after #VMEXIT, right now it's just ignored.
+        */
+       ret = svm_skip_emulated_instruction(vcpu);
+       if (ret)
+               kvm_pmu_instruction_retired(vcpu);
 
        /*
         * Since vmcb01 is not in use, we can use it to store some of the L1
index 62d374ceffc1f7d97c636140a2ba7dd042fe1bf4..ab1014986b91923824f7f71608d5c052b6a2ea6f 100644 (file)
@@ -333,7 +333,7 @@ done:
        return 1;
 }
 
-static int svm_skip_emulated_instruction(struct kvm_vcpu *vcpu)
+int svm_skip_emulated_instruction(struct kvm_vcpu *vcpu)
 {
        return __svm_skip_emulated_instruction(vcpu, EMULTYPE_SKIP, true);
 }
index 2b6733dffd76ffd096980bb9b1d00d12df6a54f6..e5d9984ef6320f48fb5fc3fd495e0a3e6a7d4cb7 100644 (file)
@@ -832,6 +832,8 @@ static inline void svm_enable_intercept_for_msr(struct kvm_vcpu *vcpu,
        svm_set_intercept_for_msr(vcpu, msr, type, true);
 }
 
+int svm_skip_emulated_instruction(struct kvm_vcpu *vcpu);
+
 /* nested.c */
 
 #define NESTED_EXIT_HOST       0       /* Exit handled on host level */