[PATCH 8/9] KVM: x86, SVM: do not clobber guest DR6 on KVM_EXIT_DEBUG

From: Paolo Bonzini
Date: Wed May 06 2020 - 07:10:56 EST


On Intel, #DB exceptions transmit the DR6 value via the exit qualification
field of the VMCS, and the exit qualification only contains the description
of the precise event that caused a vmexit.

On AMD, instead the DR6 field of the VMCB is filled in as if the #DB exception
was to be injected into the guest. This has two effects when guest debugging
is in use:

* the guest DR6 is clobbered

* the kvm_run->debug.arch.dr6 field can accumulate more debug events, rather
than just the last one that happened.

Fortunately, if guest debugging is in use we debug register reads and writes
are always intercepted. Now that the guest DR6 is always synchronized with
vcpu->arch.dr6, we can just run the guest with an all-zero DR6 while guest
debugging is enabled, and restore the guest value when it is disabled. This
fixes both problems.

A testcase for the second issue is added in the next patch.

Signed-off-by: Paolo Bonzini <pbonzini@xxxxxxxxxx>
---
arch/x86/kvm/svm/svm.c | 2 ++
arch/x86/kvm/x86.c | 12 ++++++++----
arch/x86/kvm/x86.h | 2 ++
3 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index f03bffafd9e6..29dc7311dbb1 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -1750,6 +1750,8 @@ static int db_interception(struct vcpu_svm *svm)
kvm_run->exit_reason = KVM_EXIT_DEBUG;
kvm_run->debug.arch.dr6 = svm->vmcb->save.dr6;
kvm_run->debug.arch.dr7 = svm->vmcb->save.dr7;
+ /* This restores DR6 to all zeros. */
+ kvm_update_dr6(vcpu);
kvm_run->debug.arch.pc =
svm->vmcb->save.cs.base + svm->vmcb->save.rip;
kvm_run->debug.arch.exception = DB_VECTOR;
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index f4254d716b10..1b5e0fc346bb 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -104,7 +104,6 @@ static u64 __read_mostly cr4_reserved_bits = CR4_RESERVED_BITS;
KVM_X2APIC_API_DISABLE_BROADCAST_QUIRK)

static void update_cr8_intercept(struct kvm_vcpu *vcpu);
-static void kvm_update_dr6(struct kvm_vcpu *vcpu);
static void process_nmi(struct kvm_vcpu *vcpu);
static void enter_smm(struct kvm_vcpu *vcpu);
static void __kvm_set_rflags(struct kvm_vcpu *vcpu, unsigned long rflags);
@@ -1048,10 +1047,14 @@ static void kvm_update_dr0123(struct kvm_vcpu *vcpu)
}
}

-static void kvm_update_dr6(struct kvm_vcpu *vcpu)
+void kvm_update_dr6(struct kvm_vcpu *vcpu)
{
- if (!(vcpu->guest_debug & KVM_GUESTDBG_USE_HW_BP) &&
- kvm_x86_ops.set_dr6)
+ if (!kvm_x86_ops.set_dr6)
+ return;
+
+ if (vcpu->guest_debug & KVM_GUESTDBG_USE_HW_BP)
+ kvm_x86_ops.set_dr6(vcpu, DR6_FIXED_1 | DR6_RTM);
+ else
kvm_x86_ops.set_dr6(vcpu, vcpu->arch.dr6);
}

@@ -9154,6 +9157,7 @@ int kvm_arch_vcpu_ioctl_set_guest_debug(struct kvm_vcpu *vcpu,
for (i = 0; i < KVM_NR_DB_REGS; i++)
vcpu->arch.eff_db[i] = vcpu->arch.db[i];
}
+ kvm_update_dr6(vcpu);
kvm_update_dr7(vcpu);

if (vcpu->guest_debug & KVM_GUESTDBG_SINGLESTEP)
diff --git a/arch/x86/kvm/x86.h b/arch/x86/kvm/x86.h
index b968acc0516f..a4c950ad4d60 100644
--- a/arch/x86/kvm/x86.h
+++ b/arch/x86/kvm/x86.h
@@ -240,6 +240,8 @@ static inline bool kvm_vcpu_latch_init(struct kvm_vcpu *vcpu)
return is_smm(vcpu) || kvm_x86_ops.apic_init_signal_blocked(vcpu);
}

+void kvm_update_dr6(struct kvm_vcpu *vcpu);
+
void kvm_set_pending_timer(struct kvm_vcpu *vcpu);
void kvm_inject_realmode_interrupt(struct kvm_vcpu *vcpu, int irq, int inc_eip);

--
2.18.2