From: Masami Hiramatsu (Google) <[email protected]>

When KVM enters a guest OS, host hardware breakpoints are disabled
before running the guest. However, an NMI can occur while executing
in guest mode or during guest transition, where
arch_install_hw_breakpoint() can be invoked from NMI context.

If arch_install_hw_breakpoint() is executed while the CPU is in guest
mode or during the VM entry transition window, hardware debug registers
can be modified with host breakpoint settings, leaking host breakpoints
into the guest OS or clobbering the guest's debug registers.

To prevent this without introducing ad-hoc per-CPU flags, introduce an
.in_guest callback to struct perf_guest_info_callbacks and an inline
helper perf_guest_in_guest(). In KVM, implement .in_guest by checking
whether the current vCPU is in IN_GUEST_MODE or EXITING_GUEST_MODE.
In arch_install_hw_breakpoint(), check perf_guest_in_guest() and return
-EBUSY if the CPU is running or transitioning to/from a guest.

Fixes: f85d40160691 ("KVM: X86: Disable hardware breakpoints unconditionally 
before kvm_x86->run()")
Assisted-by: Antigravity:gemini-3.8-flash
Signed-off-by: Masami Hiramatsu (Google) <[email protected]>
---
Changes in v17:
 - Drop changes to local_db_save() and local_db_restore().
 - Drop cpu_dr_in_guest per-CPU flag.
 - Introduce .in_guest callback in perf_guest_info_callbacks and
   perf_guest_in_guest() helper to query vcpu->mode.
   (avoid breaking perf_guest_state() users)
 - In arch_install_hw_breakpoint(), check perf_guest_in_guest()
Changes in v16:
 - Newly added.
---
 arch/x86/kernel/hw_breakpoint.c |    3 +++
 include/linux/perf_event.h      |    9 ++++++++-
 kernel/events/core.c            |    5 +++++
 virt/kvm/kvm_main.c             |   12 ++++++++++++
 4 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/arch/x86/kernel/hw_breakpoint.c b/arch/x86/kernel/hw_breakpoint.c
index f846c15f21ca..0473a5c95856 100644
--- a/arch/x86/kernel/hw_breakpoint.c
+++ b/arch/x86/kernel/hw_breakpoint.c
@@ -102,6 +102,9 @@ int arch_install_hw_breakpoint(struct perf_event *bp)
 
        lockdep_assert_irqs_disabled();
 
+       if (perf_guest_in_guest())
+               return -EBUSY;
+
        for (i = 0; i < HBP_NUM; i++) {
                struct perf_event **slot = this_cpu_ptr(&bp_per_reg[i]);
 
diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h
index 5842552294c1..194e69270460 100644
--- a/include/linux/perf_event.h
+++ b/include/linux/perf_event.h
@@ -1677,8 +1677,8 @@ struct perf_guest_info_callbacks {
        unsigned int                    (*state)(void);
        unsigned long                   (*get_ip)(void);
        unsigned int                    (*handle_intel_pt_intr)(void);
-
        void                            (*handle_mediated_pmi)(void);
+       bool                            (*in_guest)(void);
 };
 
 #ifdef CONFIG_GUEST_PERF_EVENTS
@@ -1689,6 +1689,7 @@ DECLARE_STATIC_CALL(__perf_guest_state, 
*perf_guest_cbs->state);
 DECLARE_STATIC_CALL(__perf_guest_get_ip, *perf_guest_cbs->get_ip);
 DECLARE_STATIC_CALL(__perf_guest_handle_intel_pt_intr, 
*perf_guest_cbs->handle_intel_pt_intr);
 DECLARE_STATIC_CALL(__perf_guest_handle_mediated_pmi, 
*perf_guest_cbs->handle_mediated_pmi);
+DECLARE_STATIC_CALL(__perf_guest_in_guest, *perf_guest_cbs->in_guest);
 
 static inline unsigned int perf_guest_state(void)
 {
@@ -1710,6 +1711,11 @@ static inline void perf_guest_handle_mediated_pmi(void)
        static_call(__perf_guest_handle_mediated_pmi)();
 }
 
+static inline bool perf_guest_in_guest(void)
+{
+       return static_call(__perf_guest_in_guest)();
+}
+
 extern void perf_register_guest_info_callbacks(struct 
perf_guest_info_callbacks *cbs);
 extern void perf_unregister_guest_info_callbacks(struct 
perf_guest_info_callbacks *cbs);
 
@@ -1718,6 +1724,7 @@ extern void perf_unregister_guest_info_callbacks(struct 
perf_guest_info_callback
 static inline unsigned int perf_guest_state(void)               { return 0; }
 static inline unsigned long perf_guest_get_ip(void)             { return 0; }
 static inline unsigned int perf_guest_handle_intel_pt_intr(void) { return 0; }
+static inline bool perf_guest_in_guest(void)                    { return 
false; }
 
 #endif /* !CONFIG_GUEST_PERF_EVENTS */
 
diff --git a/kernel/events/core.c b/kernel/events/core.c
index a6c8e38a3110..c5dead7c5f71 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -7749,6 +7749,7 @@ DEFINE_STATIC_CALL_RET0(__perf_guest_state, 
*perf_guest_cbs->state);
 DEFINE_STATIC_CALL_RET0(__perf_guest_get_ip, *perf_guest_cbs->get_ip);
 DEFINE_STATIC_CALL_RET0(__perf_guest_handle_intel_pt_intr, 
*perf_guest_cbs->handle_intel_pt_intr);
 DEFINE_STATIC_CALL_RET0(__perf_guest_handle_mediated_pmi, 
*perf_guest_cbs->handle_mediated_pmi);
+DEFINE_STATIC_CALL_RET0(__perf_guest_in_guest, *perf_guest_cbs->in_guest);
 
 void perf_register_guest_info_callbacks(struct perf_guest_info_callbacks *cbs)
 {
@@ -7767,6 +7768,9 @@ void perf_register_guest_info_callbacks(struct 
perf_guest_info_callbacks *cbs)
        if (cbs->handle_mediated_pmi)
                static_call_update(__perf_guest_handle_mediated_pmi,
                                   cbs->handle_mediated_pmi);
+
+       if (cbs->in_guest)
+               static_call_update(__perf_guest_in_guest, cbs->in_guest);
 }
 EXPORT_SYMBOL_GPL(perf_register_guest_info_callbacks);
 
@@ -7780,6 +7784,7 @@ void perf_unregister_guest_info_callbacks(struct 
perf_guest_info_callbacks *cbs)
        static_call_update(__perf_guest_get_ip, (void *)&__static_call_return0);
        static_call_update(__perf_guest_handle_intel_pt_intr, (void 
*)&__static_call_return0);
        static_call_update(__perf_guest_handle_mediated_pmi, (void 
*)&__static_call_return0);
+       static_call_update(__perf_guest_in_guest, (void 
*)&__static_call_return0);
        synchronize_rcu();
 }
 EXPORT_SYMBOL_GPL(perf_unregister_guest_info_callbacks);
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 65eb26a0520d..7779d500fe77 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -6476,11 +6476,23 @@ static unsigned long kvm_guest_get_ip(void)
        return kvm_arch_vcpu_get_ip(vcpu);
 }
 
+static bool kvm_guest_in_guest(void)
+{
+       struct kvm_vcpu *vcpu = kvm_get_running_vcpu();
+
+       if (!vcpu)
+               return false;
+
+       return READ_ONCE(vcpu->mode) == IN_GUEST_MODE ||
+              READ_ONCE(vcpu->mode) == EXITING_GUEST_MODE;
+}
+
 static struct perf_guest_info_callbacks kvm_guest_cbs = {
        .state                  = kvm_guest_state,
        .get_ip                 = kvm_guest_get_ip,
        .handle_intel_pt_intr   = NULL,
        .handle_mediated_pmi    = NULL,
+       .in_guest               = kvm_guest_in_guest,
 };
 
 void __kvm_register_perf_callbacks(unsigned int (*pt_intr_handler)(void),


Reply via email to