[PATCH v4 12/21] KVM: x86/pmu: Mark CondChgd reserved in global_status_rsvd

From: Zide Chen

Date: Thu Nov 12 2026 - 16:18:35 EST


Bit 63 (CondChgd) of IA32_PERF_GLOBAL_STATUS indicates a change to
IA32_MISC_ENABLE[7], which KVM intercepts and keeps read-only for
guests.

In the Intel SDM, there is an asymmetry regarding bit 63: it is valid
in IA32_PERF_GLOBAL_STATUS_{,RESET}, but it is architecturally
reserved in IA32_PERF_GLOBAL_STATUS_SET. Consequently, KVM cannot
restore bit 63 to hardware on vPMU context switch.

Fortunately, because KVM intercepts IA32_MISC_ENABLE and keeps it
read-only for guests, neither hardware nor KVM will ever assert
CondChgd for the guest. It is therefore safe to not virtualize
CondChgd while maintaining architectural consistency.

Mark CondChgd reserved in pmu->global_status_rsvd so that userspace
writes to IA32_PERF_GLOBAL_STATUS, and upcoming emulation of
IA32_PERF_GLOBAL_STATUS_SET, reject bit 63.

Clearing CondChgd via IA32_PERF_GLOBAL_STATUS_RESET (0x390, a.k.a.
GLOBAL_OVF_CTRL) is architecturally valid per the Intel SDM, so
explicitly allow bit 63 to be cleared without #GP.

Additionally, in mediated PMU mode, defensively mask out reserved bits
in intel_mediated_pmu_load() before writing to
IA32_PERF_GLOBAL_STATUS_SET to prevent a host #GP.

Reported-by: Jim Mattson <jmattson@xxxxxxxxxx>
Suggested-by: Yosry Ahmed <yosry@xxxxxxxxxx>
Signed-off-by: Zide Chen <zide.chen@xxxxxxxxx>
---
v4:
- New prep patch to mark CondChgd reserved in global_status_rsvd (suggested
by Yosry), addressing the STATUS_SET bit 63 asymmetry identified by Jim.
(Jim, Yosry)
---
arch/x86/kvm/pmu.c | 8 +++++---
arch/x86/kvm/vmx/pmu_intel.c | 11 +++++++----
2 files changed, 12 insertions(+), 7 deletions(-)

diff --git a/arch/x86/kvm/pmu.c b/arch/x86/kvm/pmu.c
index e1999a6c85d7..7ba1a3b38184 100644
--- a/arch/x86/kvm/pmu.c
+++ b/arch/x86/kvm/pmu.c
@@ -911,10 +911,12 @@ int kvm_pmu_set_msr(struct kvm_vcpu *vcpu, struct msr_data *msr_info)
break;
case MSR_CORE_PERF_GLOBAL_OVF_CTRL:
/*
- * GLOBAL_OVF_CTRL, a.k.a. GLOBAL STATUS_RESET, clears bits in
- * GLOBAL_STATUS, and so the set of reserved bits is the same.
+ * GLOBAL_OVF_CTRL, a.k.a. GLOBAL_STATUS_RESET, clears bits in
+ * GLOBAL_STATUS. CondChgd is not virtualized by KVM and is
+ * reserved in global_status_rsvd, but clearing it via
+ * GLOBAL_OVF_CTRL is architecturally valid per the SDM.
*/
- if (data & pmu->global_status_rsvd)
+ if (data & (pmu->global_status_rsvd & ~GLOBAL_STATUS_COND_CHG))
return 1;
fallthrough;
case MSR_AMD64_PERF_CNTR_GLOBAL_STATUS_CLR:
diff --git a/arch/x86/kvm/vmx/pmu_intel.c b/arch/x86/kvm/vmx/pmu_intel.c
index a240b74ac704..4d05ec50f24f 100644
--- a/arch/x86/kvm/vmx/pmu_intel.c
+++ b/arch/x86/kvm/vmx/pmu_intel.c
@@ -603,7 +603,7 @@ static void intel_pmu_refresh(struct kvm_vcpu *vcpu)
pmu->global_ctrl_rsvd = counter_rsvd;

pmu->global_status_rsvd = pmu->global_ctrl_rsvd
- & ~(GLOBAL_STATUS_BUFFER_OVF | GLOBAL_STATUS_COND_CHG);
+ & ~GLOBAL_STATUS_BUFFER_OVF;
if (vmx_pt_mode_is_host_guest())
pmu->global_status_rsvd &= ~GLOBAL_STATUS_TRACE_TOPAPMI;

@@ -818,14 +818,17 @@ static void intel_pmu_write_global_ctrl(u64 global_ctrl)
static void intel_mediated_pmu_load(struct kvm_vcpu *vcpu)
{
struct kvm_pmu *pmu = vcpu_to_pmu(vcpu);
- u64 global_status, toggle;
+ u64 global_status, toggle, set_bits;

rdmsrq(MSR_CORE_PERF_GLOBAL_STATUS, global_status);
toggle = pmu->global_status ^ global_status;
if (global_status & toggle)
wrmsrq(MSR_CORE_PERF_GLOBAL_OVF_CTRL, global_status & toggle);
- if (pmu->global_status & toggle)
- wrmsrq(MSR_CORE_PERF_GLOBAL_STATUS_SET, pmu->global_status & toggle);
+
+ /* Defensively mask reserved bits to prevent a host #GP. */
+ set_bits = (pmu->global_status & toggle) & ~pmu->global_status_rsvd;
+ if (set_bits)
+ wrmsrq(MSR_CORE_PERF_GLOBAL_STATUS_SET, set_bits);

wrmsrq(MSR_CORE_PERF_FIXED_CTR_CTRL, pmu->fixed_ctr_ctrl_hw);
}
--
2.56.1