[PATCH 2/6] perf/x86/intel/pt: Stop tracing if VMXON has not yet happened

From: Adrian Hunter

Date: Mon Nov 02 2026 - 02:48:03 EST


On Broadwell, which does not support Intel PT in VMX operation
(IA32_VMX_MISC[14] = 0), VMXON clears IA32_RTIT_CTL.TraceEn and any
subsequent write to IA32_RTIT_CTL causes #GP.

To avoid that, intel_pt_handle_vmx() sets pt->vmx_on so that
pt_config_start() and pt_config_stop() skip writes to
IA32_RTIT_CTL while VMX is active.

However, pt->vmx_on is set before VMXON executes. An NMI in the window
between setting pt->vmx_on and executing VMXON can then observe
vmx_on == true while tracing is still enabled.

In that case pt_config_stop() skips the write that would clear TraceEn,
leaving tracing active. This can subsequently cause:

- intel_pt_interrupt() to reprogram PT output registers while
TraceEn is set, resulting in #GP, or
- cpu_emergency_stop_pt() to leave tracing active during kdump,
allowing tracing to continue into the capture kernel.

If pt->vmx_on is set, read IA32_RTIT_CTL and still clear TraceEn when
it remains enabled. Writing IA32_RTIT_CTL is valid until VMXON
executes, while after VMXON TraceEn is guaranteed clear and cannot be
re-enabled. Consequently no write is attempted while VMX is active.

Example (on a Broadwell):

Instrument x86_vmx_enable_virtualization_cpu() to enlarge the
window e.g.

intel_pt_handle_vmx(1);

+ for (int j = 0; j < 6; j ++) {
+ int cpu = smp_processor_id();
+
+ pr_info("VMX: cpu %d: spinning %d\n", cpu, j);
+ for (int i = 0; i < 10000; i++)
+ cpu_relax();
+ }

r = x86_virt_cpu_vmxon();

Start Intel PT tracing on a CPU:

perf record -e intel_pt//k -C 1 &

Load KVM:

modprobe kvm_intel

Observe kernel messages. Without the fix, there is an unchecked
MSR access:

VMX: cpu 1: spinning 0
VMX: cpu 1: spinning 1
VMX: cpu 1: spinning 2
VMX: cpu 1: spinning 3
unchecked MSR access error: WRMSR to 0x571 ...
Call Trace:
<NMI>
intel_pt_interrupt+0x190/0x490
handle_pmi_common+0x6fc/0xc10
intel_pmu_handle_irq+0x1c9/0x5e0
perf_event_nmi_handler+0x62/0x90
nmi_handle.part.0+0xbd/0x270
default_do_nmi+0x3c/0x120
exc_nmi+0xe3/0x110
<TASK>
x86_vmx_enable_virtualization_cpu+0x88/0xd0
x86_virt_get_ref+0x3f/0x80
kvm_arch_enable_virtualization_cpu+0x1c4/0x820 [kvm]
kvm_online_cpu+0x31/0x50 [kvm]
cpuhp_invoke_callback+0x25c/0xf90
cpuhp_thread_fun+0x298/0x7e0
smpboot_thread_fn+0x28c/0x6d0
kthread+0x2cb/0x3a0
ret_from_fork+0x5ff/0x7f0

Fixes: 1c5ac21a0e9b ("perf/x86/intel/pt: Don't die on VMXON")
Signed-off-by: Adrian Hunter <adrian.hunter@xxxxxxxxx>
Cc: stable@xxxxxxxxxxxxxxx
---
arch/x86/events/intel/pt.c | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/arch/x86/events/intel/pt.c b/arch/x86/events/intel/pt.c
index 49349afee611..36bf039668d7 100644
--- a/arch/x86/events/intel/pt.c
+++ b/arch/x86/events/intel/pt.c
@@ -567,6 +567,15 @@ static void pt_config(struct perf_event *event)
pt_config_enable(event);
}

+static bool pt_trace_enabled(void)
+{
+ u64 ctl;
+
+ rdmsrq(MSR_IA32_RTIT_CTL, ctl);
+
+ return ctl & RTIT_CTL_TRACEEN;
+}
+
static void pt_config_stop(struct perf_event *event)
{
struct pt *pt = this_cpu_ptr(&pt_ctx);
@@ -577,7 +586,12 @@ static void pt_config_stop(struct perf_event *event)
return;

ctl &= ~RTIT_CTL_TRACEEN;
- if (!READ_ONCE(pt->vmx_on))
+ /*
+ * vmx_on is set before VMXON, so tracing may not have stopped yet.
+ * RTIT_CTL can be written until VMXON, and after VMXON TraceEn is
+ * clear, so still write RTIT_CTL if TraceEn is set.
+ */
+ if (!READ_ONCE(pt->vmx_on) || pt_trace_enabled())
wrmsrq(MSR_IA32_RTIT_CTL, ctl);

WRITE_ONCE(event->hw.aux_config, ctl);
--
2.53.0