mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/4] KVM: x86: Honor userspace MSR filter changes for passed-through LBR MSRs
@ 2026-10-20 23:45 Jim Mattson
  2026-10-20 23:45 ` [PATCH 1/4] KVM: SVM: Always update LBR MSR intercepts on full MSR intercept recalc Jim Mattson
                   ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: Jim Mattson @ 2026-10-20 23:45 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini
  Cc: Jim Mattson, kvm, linux-kernel, Yosry Ahmed, Like Xu

When userspace installs a new MSR filter, KVM raises
KVM_REQ_RECALC_INTERCEPTS to rebuild the MSR intercept bitmaps.  However,
both SVM and VMX cache the desired interception state for the LBR MSRs,
and skip updating the bitmap when the cached state hasn't changed.  The
cached state doesn't account for the filter, so a filter that denies
access to LBR MSRs that are already passed through never takes effect,
i.e. the guest can continue to read and write the MSRs without exiting
to userspace.  Conversely, if a filter forced interception when the
LBRs were passed through, relaxing the filter leaves the MSRs needlessly
intercepted.

On SVM, the bug was introduced by commit 3fa05f96fc08 ("KVM: SVM: Fix
redundant updates of LBR MSR intercepts").  On VMX, it goes all the way
back to commit 9254beaafd12 ("KVM: vmx/pmu: Reduce the overhead of LBR
pass-through or cancellation").  The comment in
vmx_recalc_msr_intercepts() claiming that LBR MSRs "cannot be filtered
by userspace" is at odds with both kvm_msr_allowed() and the
KVM_X86_SET_MSR_FILTER documentation, which exempt only the x2APIC MSRs.
It's also at odds with the filter actually being applied when the LBRs
are first passed through.

Patches 1 and 2 fix SVM and VMX, respectively.  Patch 3 adds a selftests
helper to request permission for a dynamic XSAVE feature without
skipping the test if the feature is unavailable.  Patch 4 adds a test
that primes as many of KVM's passthrough paths as is practical (LBRs,
SPEC_CTRL, XFD, APERF/MPERF, etc.), and then cycles through every
possible transition between "allow all," "deny reads," "deny writes,"
and "deny both," verifying that every denied access (and only denied
accesses) exits to userspace.

Without patch 1, the new test fails on an AMD Genoa host with lbrv=1:
both RDMSR and WRMSR of all four LBR MSRs (0x1db-0x1de) are still passed
through after the first deny-all filter is installed.  Similarly,
without patch 2, the test fails on an Intel Ice Lake host: both RDMSR
and WRMSR of MSR_LBR_SELECT, MSR_LBR_TOS, MSR_LBR_NHM_FROM,
MSR_LBR_NHM_TO, and MSR_LBR_INFO_0 are still passed through after the
first deny-all filter is installed.  With the full series, the test
passes on both hosts.

The selftest is intentionally broader than the LBR MSRs, in the hope of
catching future "skip the update if nothing changed" optimizations that
forget about userspace MSR filters.  Intel PT MSRs aren't covered, as
priming them requires the guest to be actively tracing.

Jim Mattson (4):
  KVM: SVM: Always update LBR MSR intercepts on full MSR intercept
    recalc
  KVM: VMX: Refresh LBR MSR intercepts on full MSR intercept recalc
  KVM: selftests: Add a helper to request XSAVE permission without
    skipping
  KVM: selftests: Verify MSR filter changes apply to passed-through MSRs

 arch/x86/kvm/svm/svm.c                        |  28 +-
 arch/x86/kvm/svm/svm.h                        |   6 +
 arch/x86/kvm/vmx/pmu_intel.c                  |  20 +-
 arch/x86/kvm/vmx/pmu_intel.h                  |   7 +-
 arch/x86/kvm/vmx/vmx.c                        |   6 +-
 arch/x86/kvm/vmx/vmx.h                        |   1 +
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 .../selftests/kvm/include/x86/processor.h     |   4 +
 .../testing/selftests/kvm/lib/x86/processor.c |  33 +-
 .../kvm/x86/msr_filter_update_test.c          | 392 ++++++++++++++++++
 10 files changed, 478 insertions(+), 20 deletions(-)
 create mode 100644 tools/testing/selftests/kvm/x86/msr_filter_update_test.c


base-commit: 24a19b953e2a3d0ea5a8c4951a0ab8f32720c907
-- 
2.56.0.212.g129ede20d9-goog


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/4] KVM: SVM: Always update LBR MSR intercepts on full MSR intercept recalc
  2026-10-20 23:45 [PATCH 0/4] KVM: x86: Honor userspace MSR filter changes for passed-through LBR MSRs Jim Mattson
@ 2026-10-20 23:45 ` Jim Mattson
  2026-10-21  0:09   ` Yosry Ahmed
  2026-10-20 23:45 ` [PATCH 2/4] KVM: VMX: Refresh " Jim Mattson
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 6+ messages in thread
From: Jim Mattson @ 2026-10-20 23:45 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini
  Cc: Jim Mattson, kvm, linux-kernel, Yosry Ahmed, Like Xu, stable

Split the "already up-to-date" check out of the code that writes the LBR
MSR intercepts, and write the intercepts unconditionally from
svm_recalc_msr_intercepts(), so that changes to userspace MSR filters are
reflected in the MSR permissions map.

svm_recalc_msr_intercepts() is invoked in response to
KVM_REQ_RECALC_INTERCEPTS, which is raised on MSR filter updates.  But
svm->lbr_msrs_intercepted tracks only the interception KVM wants, not
what is actually in the bitmap after the filter is applied.  If the LBR
MSRs are passed through when userspace installs a filter that denies
them, the early return leaves them passed through, and the guest can
access them without exiting to userspace.  Conversely, if a filter
forced interception when LBR virtualization was enabled, relaxing the
filter leaves the MSRs needlessly intercepted.  Either way, the bitmap
remains stale until the next change to LBR_CTL.

Full intercept recalculations are rare (e.g. on RESET/INIT, EFER.SVME
toggles, and CPUID, PERF_CAPABILITIES, and MSR filter updates), and
never happen on nested VMRUN or #VMEXIT, so writing the intercepts there
does not reintroduce the redundant per-VMRUN bitmap updates that the
check was added to avoid.  And because svm_recalc_msr_intercepts()
already unconditionally writes the intercepts for many other MSRs (and
thus marks Hyper-V's nested enlightenments dirty), writing the LBR MSR
intercepts there doesn't introduce any new invalidations.

Add a comment to clarify that lbr_msrs_intercepted reflects KVM's
desired interception, not necessarily the state of the bitmap.

Fixes: 3fa05f96fc08 ("KVM: SVM: Fix redundant updates of LBR MSR intercepts")
Cc: stable@vger.kernel.org
Signed-off-by: Jim Mattson <jmattson@google.com>
---
 arch/x86/kvm/svm/svm.c | 28 +++++++++++++++++++++++-----
 arch/x86/kvm/svm/svm.h |  6 ++++++
 2 files changed, 29 insertions(+), 5 deletions(-)

diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index fe0cba4731b2..55b3ee840365 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -721,14 +721,11 @@ void *svm_alloc_permissions_map(unsigned long size, gfp_t gfp_mask)
 	return pm;
 }
 
-static void svm_recalc_lbr_msr_intercepts(struct kvm_vcpu *vcpu)
+static void svm_set_lbr_msr_intercepts(struct kvm_vcpu *vcpu)
 {
 	struct vcpu_svm *svm = to_svm(vcpu);
 	bool intercept = !(svm->vmcb->control.misc_ctl2 & SVM_MISC2_ENABLE_V_LBR);
 
-	if (intercept == svm->lbr_msrs_intercepted)
-		return;
-
 	svm_set_intercept_for_msr(vcpu, MSR_IA32_LASTBRANCHFROMIP, MSR_TYPE_RW, intercept);
 	svm_set_intercept_for_msr(vcpu, MSR_IA32_LASTBRANCHTOIP, MSR_TYPE_RW, intercept);
 	svm_set_intercept_for_msr(vcpu, MSR_IA32_LASTINTFROMIP, MSR_TYPE_RW, intercept);
@@ -740,6 +737,22 @@ static void svm_recalc_lbr_msr_intercepts(struct kvm_vcpu *vcpu)
 	svm->lbr_msrs_intercepted = intercept;
 }
 
+static void svm_recalc_lbr_msr_intercepts(struct kvm_vcpu *vcpu)
+{
+	struct vcpu_svm *svm = to_svm(vcpu);
+	bool intercept = !(svm->vmcb->control.misc_ctl2 & SVM_MISC2_ENABLE_V_LBR);
+
+	/*
+	 * Skip redundant updates from svm_update_lbrv(), e.g. on every nested
+	 * VMRUN and #VMEXIT.  Note, svm_recalc_msr_intercepts() updates the
+	 * bitmap unconditionally, as userspace MSR filters may have changed.
+	 */
+	if (intercept == svm->lbr_msrs_intercepted)
+		return;
+
+	svm_set_lbr_msr_intercepts(vcpu);
+}
+
 void svm_vcpu_free_msrpm(void *msrpm)
 {
 	__free_pages(virt_to_page(msrpm), get_order(MSRPM_SIZE));
@@ -795,8 +808,13 @@ static void svm_recalc_msr_intercepts(struct kvm_vcpu *vcpu)
 	svm_disable_intercept_for_msr(vcpu, MSR_SYSCALL_MASK, MSR_TYPE_RW);
 #endif
 
+	/*
+	 * Unconditionally refresh the LBR MSR intercepts, as userspace MSR
+	 * filters may have changed, in which case the bitmap may be stale even
+	 * if KVM's desired interception hasn't changed.
+	 */
 	if (lbrv)
-		svm_recalc_lbr_msr_intercepts(vcpu);
+		svm_set_lbr_msr_intercepts(vcpu);
 
 	if (cpu_feature_enabled(X86_FEATURE_IBPB))
 		svm_set_intercept_for_msr(vcpu, MSR_IA32_PRED_CMD, MSR_TYPE_W,
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index 84f19026d3e8..62e08d6d4706 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -364,6 +364,12 @@ struct vcpu_svm {
 
 	bool avic_irq_window;
 	bool x2avic_msrs_intercepted;
+
+	/*
+	 * True if KVM wants the LBR MSRs to be intercepted.  Note, userspace MSR
+	 * filters may force interception regardless, i.e. this doesn't
+	 * necessarily reflect the state of the MSR bitmap.
+	 */
 	bool lbr_msrs_intercepted;
 
 	/* Guest GIF value, used when vGIF is not enabled */
-- 
2.56.0.212.g129ede20d9-goog


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 2/4] KVM: VMX: Refresh LBR MSR intercepts on full MSR intercept recalc
  2026-10-20 23:45 [PATCH 0/4] KVM: x86: Honor userspace MSR filter changes for passed-through LBR MSRs Jim Mattson
  2026-10-20 23:45 ` [PATCH 1/4] KVM: SVM: Always update LBR MSR intercepts on full MSR intercept recalc Jim Mattson
@ 2026-10-20 23:45 ` Jim Mattson
  2026-10-20 23:45 ` [PATCH 3/4] KVM: selftests: Add a helper to request XSAVE permission without skipping Jim Mattson
  2026-10-20 23:45 ` [PATCH 4/4] KVM: selftests: Verify MSR filter changes apply to passed-through MSRs Jim Mattson
  3 siblings, 0 replies; 6+ messages in thread
From: Jim Mattson @ 2026-10-20 23:45 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini
  Cc: Jim Mattson, kvm, linux-kernel, Yosry Ahmed, Like Xu, stable

Re-apply the LBR MSR intercepts in vmx_recalc_msr_intercepts() if the
LBRs are currently passed through, so that changes to userspace MSR
filters are reflected in the MSR bitmap.  Unlike SVM, which can simply
rewrite the intercepts in either direction, leave the bitmap alone if
the LBRs aren't passed through, as a filter can only force interception,
never prevent it, i.e. the bitmap is already correct.  Note, clearing
msr_passthrough to force vmx_passthrough_lbr_msrs() to re-apply the
intercepts on the next VM-Enter isn't an option, as doing so would
leave stale passthrough bits behind if the guest LBR event is no longer
active.

vmx_recalc_msr_intercepts() is invoked in response to
KVM_REQ_RECALC_INTERCEPTS, which is raised on MSR filter updates, but it
skips the LBR MSRs, and vmx_enable_lbr_msrs_passthrough() does nothing
if lbr_desc->msr_passthrough is already set.  If the LBR MSRs are passed
through when userspace installs a filter that denies them, they remain
passed through, and the guest can access them without exiting to
userspace.  Conversely, if a filter forced interception when the guest
LBR event became active, relaxing the filter leaves the MSRs needlessly
intercepted.  Either way, the bitmap remains stale until the guest LBR
event is torn down and recreated.

Only x2APIC MSRs are exempt from userspace filtering (see
kvm_msr_allowed() and the KVM_X86_SET_MSR_FILTER documentation), and the
filter is honored when LBR passthrough is first enabled, so KVM's
behavior shouldn't depend on whether the filter is set before or after
the guest enables LBRs.  Drop the LBR MSRs from the comment claiming
that they can't be filtered by userspace, and clarify that
lbr_desc.msr_passthrough tracks KVM's desired interception, which may
not match the MSR bitmap when userspace filters are in play.

Fixes: 9254beaafd12 ("KVM: vmx/pmu: Reduce the overhead of LBR pass-through or cancellation")
Cc: stable@vger.kernel.org
Signed-off-by: Jim Mattson <jmattson@google.com>
---
 arch/x86/kvm/vmx/pmu_intel.c | 20 +++++++++++++++++++-
 arch/x86/kvm/vmx/pmu_intel.h |  7 ++++++-
 arch/x86/kvm/vmx/vmx.c       |  6 ++++--
 arch/x86/kvm/vmx/vmx.h       |  1 +
 4 files changed, 30 insertions(+), 4 deletions(-)

diff --git a/arch/x86/kvm/vmx/pmu_intel.c b/arch/x86/kvm/vmx/pmu_intel.c
index 70a8c4816135..b945213f0b68 100644
--- a/arch/x86/kvm/vmx/pmu_intel.c
+++ b/arch/x86/kvm/vmx/pmu_intel.c
@@ -517,7 +517,8 @@ static void intel_pmu_refresh(struct kvm_vcpu *vcpu)
 	memset(&lbr_desc->records, 0, sizeof(lbr_desc->records));
 
 	/*
-	 * Setting passthrough of LBR MSRs is done only in the VM-Entry loop,
+	 * LBR MSRs are initially passed through only in the VM-Entry loop (a
+	 * full MSR intercept recalc merely refreshes an existing passthrough),
 	 * and PMU refresh is disallowed after the vCPU has run, i.e. this code
 	 * should never be reached while KVM is passing through MSRs.
 	 */
@@ -704,6 +705,23 @@ static inline void vmx_enable_lbr_msrs_passthrough(struct kvm_vcpu *vcpu)
 	lbr_desc->msr_passthrough = true;
 }
 
+/*
+ * Refresh the LBR MSR intercepts if the LBRs are passed through, so that
+ * changes to userspace MSR filters are reflected in the MSR bitmap.  There's
+ * nothing to do if the LBRs are intercepted, as a filter can only force
+ * interception, never prevent it.
+ */
+void vmx_recalc_lbr_msr_intercepts(struct kvm_vcpu *vcpu)
+{
+	struct lbr_desc *lbr_desc = vcpu_to_lbr_desc(vcpu);
+
+	if (WARN_ON_ONCE(!lbr_desc))
+		return;
+
+	if (lbr_desc->msr_passthrough)
+		vmx_update_intercept_for_lbr_msrs(vcpu, false);
+}
+
 /*
  * Higher priority host perf events (e.g. cpu pinned) could reclaim the
  * pmu resources (e.g. LBR) that were assigned to the guest. This is
diff --git a/arch/x86/kvm/vmx/pmu_intel.h b/arch/x86/kvm/vmx/pmu_intel.h
index 5d9357640aa1..b553f63e19a6 100644
--- a/arch/x86/kvm/vmx/pmu_intel.h
+++ b/arch/x86/kvm/vmx/pmu_intel.h
@@ -34,7 +34,12 @@ struct lbr_desc {
 	 */
 	struct perf_event *event;
 
-	/* True if LBRs are marked as not intercepted in the MSR bitmap */
+	/*
+	 * True if KVM wants the LBR MSRs to be passed through, i.e. not
+	 * intercepted.  Note, userspace MSR filters may force interception of
+	 * some or all LBR MSRs regardless, i.e. this doesn't necessarily
+	 * reflect the state of the MSR bitmap.
+	 */
 	bool msr_passthrough;
 };
 
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 612ab07d4100..1c190a6303a3 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -4347,11 +4347,13 @@ static void vmx_recalc_msr_intercepts(struct kvm_vcpu *vcpu)
 		vmx_set_intercept_for_msr(vcpu, MSR_IA32_S_CET, MSR_TYPE_RW, intercept);
 	}
 
+	vmx_recalc_lbr_msr_intercepts(vcpu);
+
 	vmx_recalc_pmu_msr_intercepts(vcpu);
 
 	/*
-	 * x2APIC and LBR MSR intercepts are modified on-demand and cannot be
-	 * filtered by userspace.
+	 * x2APIC intercepts are modified on-demand and cannot be filtered by
+	 * userspace.
 	 */
 }
 
diff --git a/arch/x86/kvm/vmx/vmx.h b/arch/x86/kvm/vmx/vmx.h
index dc8517f15bc4..ec6104b66009 100644
--- a/arch/x86/kvm/vmx/vmx.h
+++ b/arch/x86/kvm/vmx/vmx.h
@@ -667,6 +667,7 @@ static __always_inline struct vcpu_vmx *to_vmx(struct kvm_vcpu *vcpu)
 void intel_pmu_cross_mapped_check(struct kvm_pmu *pmu);
 int intel_pmu_create_guest_lbr_event(struct kvm_vcpu *vcpu);
 void vmx_passthrough_lbr_msrs(struct kvm_vcpu *vcpu);
+void vmx_recalc_lbr_msr_intercepts(struct kvm_vcpu *vcpu);
 
 struct vmcs *alloc_vmcs_cpu(bool shadow, int cpu, gfp_t flags);
 void free_vmcs(struct vmcs *vmcs);
-- 
2.56.0.212.g129ede20d9-goog


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 3/4] KVM: selftests: Add a helper to request XSAVE permission without skipping
  2026-10-20 23:45 [PATCH 0/4] KVM: x86: Honor userspace MSR filter changes for passed-through LBR MSRs Jim Mattson
  2026-10-20 23:45 ` [PATCH 1/4] KVM: SVM: Always update LBR MSR intercepts on full MSR intercept recalc Jim Mattson
  2026-10-20 23:45 ` [PATCH 2/4] KVM: VMX: Refresh " Jim Mattson
@ 2026-10-20 23:45 ` Jim Mattson
  2026-10-20 23:45 ` [PATCH 4/4] KVM: selftests: Verify MSR filter changes apply to passed-through MSRs Jim Mattson
  3 siblings, 0 replies; 6+ messages in thread
From: Jim Mattson @ 2026-10-20 23:45 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini
  Cc: Jim Mattson, kvm, linux-kernel, Yosry Ahmed, Like Xu

Split the core of __vm_xsave_require_permission() into a new helper,
vm_xsave_request_permission(), which returns false instead of skipping
the test if KVM doesn't support the requested dynamic XSAVE feature, or
if the request for permission is denied.  This allows tests to opt into
dynamic features opportunistically, e.g. to exercise additional KVM
paths when AMX is available, while retaining the sanity check that the
request is made before KVM_GET_SUPPORTED_CPUID is first queried.

Like vm_xsave_require_permission(), wrap the new helper in a macro that
stringifies the feature mask for use in assertion messages.

Use __kvm_device_attr_get() instead of open-coding KVM_GET_DEVICE_ATTR.

No functional change intended for existing users, other than collapsing
the reasons for skipping into a single message.

Signed-off-by: Jim Mattson <jmattson@google.com>
---
 .../selftests/kvm/include/x86/processor.h     |  4 +++
 .../testing/selftests/kvm/lib/x86/processor.c | 33 ++++++++++++-------
 2 files changed, 26 insertions(+), 11 deletions(-)

diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h
index 6e6f70035508..6e9db81187c3 100644
--- a/tools/testing/selftests/kvm/include/x86/processor.h
+++ b/tools/testing/selftests/kvm/include/x86/processor.h
@@ -1530,8 +1530,12 @@ static inline void cli(void)
 	asm volatile ("cli");
 }
 
+bool __vm_xsave_request_permission(u64 xfeature, const char *name);
 void __vm_xsave_require_permission(u64 xfeature, const char *name);
 
+#define vm_xsave_request_permission(xfeature)	\
+	__vm_xsave_request_permission(xfeature, #xfeature)
+
 #define vm_xsave_require_permission(xfeature)	\
 	__vm_xsave_require_permission(xfeature, #xfeature)
 
diff --git a/tools/testing/selftests/kvm/lib/x86/processor.c b/tools/testing/selftests/kvm/lib/x86/processor.c
index d31fa81ea075..12092e59b390 100644
--- a/tools/testing/selftests/kvm/lib/x86/processor.c
+++ b/tools/testing/selftests/kvm/lib/x86/processor.c
@@ -975,16 +975,18 @@ u64 kvm_get_feature_msr(u64 msr_index)
 	return buffer.entry.data;
 }
 
-void __vm_xsave_require_permission(u64 xfeature, const char *name)
+/*
+ * Request permission for guests to use the dynamic XSAVE feature @xfeature.
+ * Returns false, without skipping the test, if KVM doesn't support the
+ * feature or if the request is denied.  Must be called before
+ * KVM_GET_SUPPORTED_CPUID is first queried, as KVM enumerates dynamic
+ * features only if userspace has permission to use them.
+ */
+bool __vm_xsave_request_permission(u64 xfeature, const char *name)
 {
 	int kvm_fd;
 	u64 bitmask;
 	long rc;
-	struct kvm_device_attr attr = {
-		.group = 0,
-		.attr = KVM_X86_XCOMP_GUEST_SUPP,
-		.addr = (unsigned long) &bitmask,
-	};
 
 	TEST_ASSERT(!kvm_supported_cpuid,
 		    "kvm_get_supported_cpuid() cannot be used before ARCH_REQ_XCOMP_GUEST_PERM");
@@ -993,24 +995,33 @@ void __vm_xsave_require_permission(u64 xfeature, const char *name)
 		    "Dynamic XFeatures must be enabled one at a time");
 
 	kvm_fd = open_kvm_dev_path_or_exit();
-	rc = __kvm_ioctl(kvm_fd, KVM_GET_DEVICE_ATTR, &attr);
+	rc = __kvm_device_attr_get(kvm_fd, 0, KVM_X86_XCOMP_GUEST_SUPP, &bitmask);
 	close(kvm_fd);
 
 	if (rc == -1 && (errno == ENXIO || errno == EINVAL))
-		__TEST_REQUIRE(0, "KVM_X86_XCOMP_GUEST_SUPP not supported");
+		return false;
 
 	TEST_ASSERT(rc == 0, "KVM_GET_DEVICE_ATTR(0, KVM_X86_XCOMP_GUEST_SUPP) error: %ld", rc);
 
-	__TEST_REQUIRE(bitmask & xfeature,
-		       "Required XSAVE feature '%s' not supported", name);
+	if (!(bitmask & xfeature))
+		return false;
 
-	TEST_REQUIRE(!syscall(SYS_arch_prctl, ARCH_REQ_XCOMP_GUEST_PERM, ilog2(xfeature)));
+	if (syscall(SYS_arch_prctl, ARCH_REQ_XCOMP_GUEST_PERM, ilog2(xfeature)))
+		return false;
 
 	rc = syscall(SYS_arch_prctl, ARCH_GET_XCOMP_GUEST_PERM, &bitmask);
 	TEST_ASSERT(rc == 0, "prctl(ARCH_GET_XCOMP_GUEST_PERM) error: %ld", rc);
 	TEST_ASSERT(bitmask & xfeature,
 		    "'%s' (0x%lx) not permitted after prctl(ARCH_REQ_XCOMP_GUEST_PERM) permitted=0x%lx",
 		    name, xfeature, bitmask);
+	return true;
+}
+
+void __vm_xsave_require_permission(u64 xfeature, const char *name)
+{
+	__TEST_REQUIRE(__vm_xsave_request_permission(xfeature, name),
+		       "Dynamic XSAVE feature '%s' not supported or not permitted",
+		       name);
 }
 
 void vcpu_init_cpuid(struct kvm_vcpu *vcpu, const struct kvm_cpuid2 *cpuid)
-- 
2.56.0.212.g129ede20d9-goog


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 4/4] KVM: selftests: Verify MSR filter changes apply to passed-through MSRs
  2026-10-20 23:45 [PATCH 0/4] KVM: x86: Honor userspace MSR filter changes for passed-through LBR MSRs Jim Mattson
                   ` (2 preceding siblings ...)
  2026-10-20 23:45 ` [PATCH 3/4] KVM: selftests: Add a helper to request XSAVE permission without skipping Jim Mattson
@ 2026-10-20 23:45 ` Jim Mattson
  3 siblings, 0 replies; 6+ messages in thread
From: Jim Mattson @ 2026-10-20 23:45 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini
  Cc: Jim Mattson, kvm, linux-kernel, Yosry Ahmed, Like Xu

Add a test to verify that userspace MSR filter changes take effect for
MSRs that KVM has already passed through to the guest.  KVM tracks the
desired interception state for some MSRs to avoid redundant bitmap
updates, and such optimizations can easily cause KVM to skip refreshing
the MSR bitmaps when only the filter has changed, leaving MSRs passed
through despite userspace denying access.  This is a regression test
for the SVM and VMX LBR MSR fixes that precede it.

Prime as many passthrough paths as is practical: disable APERF/MPERF
and C-state exits if allowed, request AMX permission so that the guest
can write XFD, and have the guest enable LBRs, set SPEC_CTRL, and set
XFD.  Then cycle through a sequence of filters that allow everything, or
deny reads, writes, or both for every MSR of interest, and verify that
every denied access (and only denied accesses) exits to userspace.  The
sequence covers every possible transition between the four filter
states, to catch stale state tracking from any prior state.

Add a new test rather than extending userspace_msr_exit_test, as the
existing test focuses on the semantics of individual filters, whereas
this test needs to prime KVM's on-demand passthrough paths before
installing filters, and then cycle through many filters on a single
vCPU.

Signed-off-by: Jim Mattson <jmattson@google.com>
---
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 .../kvm/x86/msr_filter_update_test.c          | 392 ++++++++++++++++++
 2 files changed, 393 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/x86/msr_filter_update_test.c

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index f43549a56f0b..9cefaf64809d 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -92,6 +92,7 @@ TEST_GEN_PROGS_x86 += x86/kvm_clock_test
 TEST_GEN_PROGS_x86 += x86/kvm_pv_test
 TEST_GEN_PROGS_x86 += x86/kvm_buslock_test
 TEST_GEN_PROGS_x86 += x86/monitor_mwait_test
+TEST_GEN_PROGS_x86 += x86/msr_filter_update_test
 TEST_GEN_PROGS_x86 += x86/msrs_test
 TEST_GEN_PROGS_x86 += x86/nested_close_kvm_test
 TEST_GEN_PROGS_x86 += x86/nested_dirty_log_test
diff --git a/tools/testing/selftests/kvm/x86/msr_filter_update_test.c b/tools/testing/selftests/kvm/x86/msr_filter_update_test.c
new file mode 100644
index 000000000000..9b9ba3fa398c
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/msr_filter_update_test.c
@@ -0,0 +1,392 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026, Google LLC.
+ *
+ * Verify that changes to userspace MSR filters take effect for MSRs that KVM
+ * has already passed through to the guest, i.e. that KVM refreshes its MSR
+ * bitmaps on filter changes even if its desired interception hasn't changed.
+ *
+ * Intel PT MSRs aren't covered, as KVM passes them through only while the
+ * guest is tracing (RTIT_CTL.TraceEn=1), which requires valid output setup.
+ */
+#include "kvm_util.h"
+#include "processor.h"
+
+#define ACC_R	KVM_MSR_FILTER_READ
+#define ACC_W	KVM_MSR_FILTER_WRITE
+#define ACC_RW	(ACC_R | ACC_W)
+
+struct test_msr {
+	u32 index;
+	const char *name;
+	/* Accesses to perform; never write MSRs where doing so is unsafe. */
+	u8 access;
+	/* Value to write for write-only MSRs. */
+	u64 write_val;
+};
+
+/* Stringify in the outer macros, before the MSR name is macro-expanded. */
+#define __MSR(msr, str, acc, val)					\
+	{ .index = msr, .name = str, .access = acc, .write_val = val }
+#define MSR_R(msr)		__MSR(msr, #msr, ACC_R, 0)
+#define MSR_W(msr, val)		__MSR(msr, #msr, ACC_W, val)
+#define MSR_RW(msr)		__MSR(msr, #msr, ACC_RW, 0)
+
+/*
+ * MSRs that KVM may pass through to the guest on Intel and/or AMD.  MSRs that
+ * don't exist on the host, or that KVM doesn't support for the guest, are
+ * still useful: denied accesses must exit to userspace regardless of whether
+ * KVM would have emulated or rejected them.
+ */
+static const struct test_msr msrs[] = {
+	MSR_R(MSR_IA32_TSC),
+	MSR_RW(MSR_IA32_SPEC_CTRL),
+	MSR_W(MSR_IA32_PRED_CMD, PRED_CMD_IBPB),
+	MSR_W(MSR_IA32_FLUSH_CMD, L1D_FLUSH),
+	MSR_RW(MSR_IA32_SYSENTER_CS),
+	MSR_RW(MSR_IA32_SYSENTER_ESP),
+	MSR_RW(MSR_IA32_SYSENTER_EIP),
+	MSR_R(MSR_IA32_APERF),
+	MSR_R(MSR_IA32_MPERF),
+	MSR_R(MSR_CORE_C1_RES),
+	MSR_R(MSR_CORE_C3_RESIDENCY),
+	MSR_R(MSR_CORE_C6_RESIDENCY),
+	MSR_R(MSR_CORE_C7_RESIDENCY),
+	MSR_RW(MSR_IA32_XFD),
+	MSR_RW(MSR_IA32_XFD_ERR),
+
+	/* LBRs: AMD LBR virtualization, and Intel's legacy LBR stack. */
+	MSR_RW(MSR_IA32_DEBUGCTLMSR),
+	MSR_RW(MSR_IA32_LASTBRANCHFROMIP),
+	MSR_RW(MSR_IA32_LASTBRANCHTOIP),
+	MSR_RW(MSR_IA32_LASTINTFROMIP),
+	MSR_RW(MSR_IA32_LASTINTTOIP),
+	MSR_RW(MSR_LBR_SELECT),
+	MSR_RW(MSR_LBR_TOS),
+	MSR_RW(MSR_LBR_CORE_FROM),
+	MSR_RW(MSR_LBR_CORE_TO),
+	MSR_RW(MSR_LBR_NHM_FROM),
+	MSR_RW(MSR_LBR_NHM_TO),
+	MSR_RW(MSR_LBR_INFO_0),
+
+	/* CET. */
+	MSR_RW(MSR_IA32_U_CET),
+	MSR_RW(MSR_IA32_S_CET),
+	MSR_RW(MSR_IA32_PL0_SSP),
+	MSR_RW(MSR_IA32_PL1_SSP),
+	MSR_RW(MSR_IA32_PL2_SSP),
+	MSR_RW(MSR_IA32_PL3_SSP),
+
+	/* Mediated PMU, Intel. */
+	MSR_RW(MSR_IA32_PERFCTR0),
+	MSR_RW(MSR_IA32_PMC0),
+	MSR_RW(MSR_CORE_PERF_FIXED_CTR0),
+	MSR_R(MSR_CORE_PERF_GLOBAL_STATUS),
+	MSR_RW(MSR_CORE_PERF_GLOBAL_CTRL),
+	MSR_W(MSR_CORE_PERF_GLOBAL_OVF_CTRL, 0),
+
+	/* Mediated PMU, AMD. */
+	MSR_RW(MSR_K7_PERFCTR0),
+	MSR_RW(MSR_F15H_PERF_CTR),
+	MSR_R(MSR_AMD64_PERF_CNTR_GLOBAL_STATUS),
+	MSR_RW(MSR_AMD64_PERF_CNTR_GLOBAL_CTL),
+	MSR_W(MSR_AMD64_PERF_CNTR_GLOBAL_STATUS_CLR, 0),
+	MSR_W(MSR_AMD64_PERF_CNTR_GLOBAL_STATUS_SET, 0),
+
+	MSR_RW(MSR_STAR),
+	MSR_RW(MSR_LSTAR),
+	MSR_RW(MSR_CSTAR),
+	MSR_RW(MSR_SYSCALL_MASK),
+	MSR_RW(MSR_FS_BASE),
+	MSR_RW(MSR_GS_BASE),
+	MSR_RW(MSR_KERNEL_GS_BASE),
+};
+
+#define NR_MSRS ARRAY_SIZE(msrs)
+
+/*
+ * Accesses denied by the filter in each phase.  The sequence covers every
+ * possible transition between the four filter states (allow all, deny reads,
+ * deny writes, deny both), to catch KVM failing to propagate filter changes
+ * from any prior state.
+ */
+static const u8 phases[] = {
+	0, ACC_RW, 0, ACC_R, 0, ACC_W, ACC_R, ACC_RW, ACC_W, ACC_RW, ACC_R,
+	ACC_W, 0,
+};
+
+/* MSR filter ranges, which must cover every MSR in msrs[]. */
+static const u32 filter_bases[] = { 0x00000000, 0xc0000000, 0xc0010000 };
+#define FILTER_NR_MSRS	(KVM_MSR_FILTER_MAX_BITMAP_SIZE * BITS_PER_BYTE)
+
+/* All zeroes, i.e. deny everything in the range. */
+static u8 deny_bitmap[KVM_MSR_FILTER_MAX_BITMAP_SIZE];
+
+/* Guest-only: the last value read from each MSR with no filter in place. */
+static u64 guest_vals[NR_MSRS];
+static bool guest_val_valid[NR_MSRS];
+
+/* Host-only: userspace exits observed during the current phase. */
+static int nr_read_exits[NR_MSRS];
+static int nr_write_exits[NR_MSRS];
+
+/*
+ * Get KVM to pass through as many MSRs as possible.  Failures are expected
+ * and ignored, e.g. if the relevant feature isn't supported.
+ */
+static void guest_prime_passthrough(void)
+{
+	static const u64 spec_ctrl_vals[] = {
+		SPEC_CTRL_IBRS, SPEC_CTRL_STIBP, SPEC_CTRL_SSBD,
+	};
+	int i;
+
+	/*
+	 * Enabling LBRs activates LBR virtualization on AMD, and creates a
+	 * guest LBR perf event (and thus passes through the LBR stack) on
+	 * Intel.
+	 */
+	wrmsr_safe(MSR_IA32_DEBUGCTLMSR, DEBUGCTLMSR_LBR);
+
+	/* KVM passes through SPEC_CTRL once the guest sets a non-zero value. */
+	for (i = 0; i < ARRAY_SIZE(spec_ctrl_vals); i++) {
+		if (!wrmsr_safe(MSR_IA32_SPEC_CTRL, spec_ctrl_vals[i]))
+			break;
+	}
+
+	/* VMX passes through XFD once the guest sets a non-zero value. */
+	wrmsr_safe(MSR_IA32_XFD, XFEATURE_MASK_XTILE_DATA);
+}
+
+static void guest_access_msrs(u8 deny)
+{
+	u64 val;
+	int i;
+
+	for (i = 0; i < NR_MSRS; i++) {
+		const struct test_msr *msr = &msrs[i];
+
+		/*
+		 * Denied reads are handled by userspace, so only record the
+		 * value if the read is allowed (and doesn't #GP).
+		 */
+		if (msr->access & ACC_R) {
+			if (!rdmsr_safe(msr->index, &val) && !(deny & ACC_R)) {
+				guest_vals[i] = val;
+				guest_val_valid[i] = true;
+			}
+		}
+
+		if (!(msr->access & ACC_W))
+			continue;
+
+		if (!(msr->access & ACC_R))
+			val = msr->write_val;
+		else if (guest_val_valid[i])
+			val = guest_vals[i];
+		else if (deny & ACC_W)
+			val = 0;
+		else
+			continue;
+
+		/*
+		 * Writes that are allowed only ever write the MSR's current
+		 * (or a benign) value.  Denied writes are dropped by userspace.
+		 */
+		wrmsr_safe(msr->index, val);
+	}
+}
+
+static void guest_code(void)
+{
+	int i;
+
+	guest_prime_passthrough();
+
+	for (i = 0; i < ARRAY_SIZE(phases); i++) {
+		guest_access_msrs(phases[i]);
+		GUEST_SYNC(i);
+	}
+
+	GUEST_DONE();
+}
+
+static void set_msr_filter(struct kvm_vm *vm, u8 deny)
+{
+	struct kvm_msr_filter filter = {
+		.flags = KVM_MSR_FILTER_DEFAULT_ALLOW,
+	};
+	int i;
+
+	/* No ranges, i.e. disable filtering, if nothing is denied. */
+	if (deny) {
+		for (i = 0; i < ARRAY_SIZE(filter_bases); i++) {
+			filter.ranges[i].flags = deny;
+			filter.ranges[i].base = filter_bases[i];
+			filter.ranges[i].nmsrs = FILTER_NR_MSRS;
+			filter.ranges[i].bitmap = deny_bitmap;
+		}
+	}
+
+	vm_ioctl(vm, KVM_X86_SET_MSR_FILTER, &filter);
+}
+
+static int find_msr(u32 index)
+{
+	int i;
+
+	for (i = 0; i < NR_MSRS; i++) {
+		if (msrs[i].index == index)
+			return i;
+	}
+
+	TEST_FAIL("Unexpected userspace exit for MSR 0x%x", index);
+	return -1;
+}
+
+static void handle_msr_exit(struct kvm_vcpu *vcpu)
+{
+	struct kvm_run *run = vcpu->run;
+	int i = find_msr(run->msr.index);
+
+	TEST_ASSERT(run->msr.reason == KVM_MSR_EXIT_REASON_FILTER,
+		    "Wanted KVM_MSR_EXIT_REASON_FILTER for %s, got reason %u",
+		    msrs[i].name, run->msr.reason);
+
+	if (run->exit_reason == KVM_EXIT_X86_RDMSR) {
+		nr_read_exits[i]++;
+		run->msr.data = 0;
+	} else {
+		nr_write_exits[i]++;
+	}
+	run->msr.error = 0;
+}
+
+static int check_exits(const char *type, const char *name, int nr_exits,
+		       bool denied, int phase)
+{
+	int expected = denied;
+
+	if (nr_exits == expected)
+		return 0;
+
+	pr_info("Phase %d: %s %s: wanted %d userspace exit(s), got %d\n",
+		phase, name, type, expected, nr_exits);
+	return 1;
+}
+
+static void verify_phase(int phase)
+{
+	u8 deny = phases[phase];
+	int i, nr_failures = 0;
+
+	for (i = 0; i < NR_MSRS; i++) {
+		nr_failures += check_exits("RDMSR", msrs[i].name,
+					   nr_read_exits[i],
+					   msrs[i].access & deny & ACC_R, phase);
+		nr_failures += check_exits("WRMSR", msrs[i].name,
+					   nr_write_exits[i],
+					   msrs[i].access & deny & ACC_W, phase);
+	}
+
+	TEST_ASSERT(!nr_failures,
+		    "Phase %d (deny =%s%s): %d MSR filter failure(s), see above",
+		    phase, deny & ACC_R ? " RDMSR" : "",
+		    deny & ACC_W ? " WRMSR" : "", nr_failures);
+}
+
+static void run_phase(struct kvm_vcpu *vcpu, int phase)
+{
+	struct ucall uc;
+
+	memset(nr_read_exits, 0, sizeof(nr_read_exits));
+	memset(nr_write_exits, 0, sizeof(nr_write_exits));
+
+	set_msr_filter(vcpu->vm, phases[phase]);
+
+	for (;;) {
+		vcpu_run(vcpu);
+
+		if (vcpu->run->exit_reason == KVM_EXIT_X86_RDMSR ||
+		    vcpu->run->exit_reason == KVM_EXIT_X86_WRMSR) {
+			handle_msr_exit(vcpu);
+			continue;
+		}
+
+		TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+
+		switch (get_ucall(vcpu, &uc)) {
+		case UCALL_SYNC:
+			TEST_ASSERT_EQ(uc.args[1], phase);
+			verify_phase(phase);
+			return;
+		case UCALL_ABORT:
+			REPORT_GUEST_ASSERT(uc);
+		default:
+			TEST_FAIL("Unexpected ucall %lu", uc.cmd);
+		}
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	u64 disable_exits;
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	struct ucall uc;
+	int i, j;
+
+	TEST_REQUIRE(kvm_has_cap(KVM_CAP_X86_USER_SPACE_MSR));
+	TEST_REQUIRE(kvm_has_cap(KVM_CAP_X86_MSR_FILTER));
+
+	for (i = 0; i < NR_MSRS; i++) {
+		for (j = 0; j < i; j++)
+			TEST_ASSERT(msrs[i].index != msrs[j].index,
+				    "%s (0x%x) is listed more than once",
+				    msrs[i].name, msrs[i].index);
+
+		for (j = 0; j < ARRAY_SIZE(filter_bases); j++) {
+			if (msrs[i].index - filter_bases[j] < FILTER_NR_MSRS)
+				break;
+		}
+		TEST_ASSERT(j < ARRAY_SIZE(filter_bases),
+			    "%s (0x%x) isn't covered by the MSR filter",
+			    msrs[i].name, msrs[i].index);
+	}
+
+	/*
+	 * Request permission to expose AMX to the guest, if possible, so that
+	 * the guest can set XFD.  Ignore failure, as KVM omits AMX (and thus
+	 * support for non-zero XFD values) from the supported CPUID if
+	 * permission isn't granted, and priming XFD is done on a best effort
+	 * basis.
+	 */
+	vm_xsave_request_permission(XFEATURE_MASK_XTILE_DATA);
+
+	vm = vm_create(1);
+
+	/*
+	 * Pass through APERF/MPERF and the C-state residency MSRs if allowed.
+	 * This must be done before creating vCPUs.
+	 */
+	disable_exits = vm_check_cap(vm, KVM_CAP_X86_DISABLE_EXITS) &
+			(KVM_X86_DISABLE_EXITS_APERFMPERF |
+			 KVM_X86_DISABLE_EXITS_CSTATE);
+	if (disable_exits)
+		vm_enable_cap(vm, KVM_CAP_X86_DISABLE_EXITS, disable_exits);
+
+	/* Userspace exits must be enabled before installing any filters. */
+	vm_enable_cap(vm, KVM_CAP_X86_USER_SPACE_MSR, KVM_MSR_EXIT_REASON_FILTER);
+
+	vcpu = vm_vcpu_add(vm, 0, guest_code);
+
+	for (i = 0; i < ARRAY_SIZE(phases); i++)
+		run_phase(vcpu, i);
+
+	vcpu_run(vcpu);
+	TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+	TEST_ASSERT_EQ(get_ucall(vcpu, &uc), UCALL_DONE);
+
+	kvm_vm_free(vm);
+	return 0;
+}
-- 
2.56.0.212.g129ede20d9-goog


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/4] KVM: SVM: Always update LBR MSR intercepts on full MSR intercept recalc
  2026-10-20 23:45 ` [PATCH 1/4] KVM: SVM: Always update LBR MSR intercepts on full MSR intercept recalc Jim Mattson
@ 2026-10-21  0:09   ` Yosry Ahmed
  0 siblings, 0 replies; 6+ messages in thread
From: Yosry Ahmed @ 2026-10-21  0:09 UTC (permalink / raw)
  To: Jim Mattson
  Cc: Sean Christopherson, Paolo Bonzini, kvm, linux-kernel, Like Xu, stable

On Tue, Oct 20, 2026 at 4:45 PM Jim Mattson <jmattson@google.com> wrote:
>
> Split the "already up-to-date" check out of the code that writes the LBR
> MSR intercepts, and write the intercepts unconditionally from
> svm_recalc_msr_intercepts(), so that changes to userspace MSR filters are
> reflected in the MSR permissions map.
>
> svm_recalc_msr_intercepts() is invoked in response to
> KVM_REQ_RECALC_INTERCEPTS, which is raised on MSR filter updates.  But
> svm->lbr_msrs_intercepted tracks only the interception KVM wants, not
> what is actually in the bitmap after the filter is applied.  If the LBR
> MSRs are passed through when userspace installs a filter that denies
> them, the early return leaves them passed through, and the guest can
> access them without exiting to userspace.  Conversely, if a filter
> forced interception when LBR virtualization was enabled, relaxing the
> filter leaves the MSRs needlessly intercepted.  Either way, the bitmap
> remains stale until the next change to LBR_CTL.
>
> Full intercept recalculations are rare (e.g. on RESET/INIT, EFER.SVME
> toggles, and CPUID, PERF_CAPABILITIES, and MSR filter updates), and
> never happen on nested VMRUN or #VMEXIT, so writing the intercepts there
> does not reintroduce the redundant per-VMRUN bitmap updates that the
> check was added to avoid.  And because svm_recalc_msr_intercepts()
> already unconditionally writes the intercepts for many other MSRs (and
> thus marks Hyper-V's nested enlightenments dirty), writing the LBR MSR
> intercepts there doesn't introduce any new invalidations.
>
> Add a comment to clarify that lbr_msrs_intercepted reflects KVM's
> desired interception, not necessarily the state of the bitmap.
>
> Fixes: 3fa05f96fc08 ("KVM: SVM: Fix redundant updates of LBR MSR intercepts")
> Cc: stable@vger.kernel.org
> Signed-off-by: Jim Mattson <jmattson@google.com>
> ---
>  arch/x86/kvm/svm/svm.c | 28 +++++++++++++++++++++++-----
>  arch/x86/kvm/svm/svm.h |  6 ++++++
>  2 files changed, 29 insertions(+), 5 deletions(-)
>
> diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
> index fe0cba4731b2..55b3ee840365 100644
> --- a/arch/x86/kvm/svm/svm.c
> +++ b/arch/x86/kvm/svm/svm.c
> @@ -721,14 +721,11 @@ void *svm_alloc_permissions_map(unsigned long size, gfp_t gfp_mask)
>         return pm;
>  }
>
> -static void svm_recalc_lbr_msr_intercepts(struct kvm_vcpu *vcpu)
> +static void svm_set_lbr_msr_intercepts(struct kvm_vcpu *vcpu)
>  {
>         struct vcpu_svm *svm = to_svm(vcpu);
>         bool intercept = !(svm->vmcb->control.misc_ctl2 & SVM_MISC2_ENABLE_V_LBR);
>
> -       if (intercept == svm->lbr_msrs_intercepted)
> -               return;
> -
>         svm_set_intercept_for_msr(vcpu, MSR_IA32_LASTBRANCHFROMIP, MSR_TYPE_RW, intercept);
>         svm_set_intercept_for_msr(vcpu, MSR_IA32_LASTBRANCHTOIP, MSR_TYPE_RW, intercept);
>         svm_set_intercept_for_msr(vcpu, MSR_IA32_LASTINTFROMIP, MSR_TYPE_RW, intercept);
> @@ -740,6 +737,22 @@ static void svm_recalc_lbr_msr_intercepts(struct kvm_vcpu *vcpu)
>         svm->lbr_msrs_intercepted = intercept;

Hmm I was going to ask if we can set svm->lbr_msrs_intercepted based
on whether or not svm_set_intercept_for_msr() actually set the
intercept, so that it also captures intercepts due to MSR filtering. I
assume that we didn't do this because MSR filters can result in
different interception status for different LBR MSRs, and we don't
want to track them individually?

Hmm even if we do track them individually (e.g. as a bitmap), it would
add a bit of complexity here because we'll need to plumb that into
svm_set_intercept_for_msr(), right?

I suppose if we go with "lbr_msrs_intercepted just means KVM wants to
intercept", then we should probably rename it accordingly?

>  }
>
> +static void svm_recalc_lbr_msr_intercepts(struct kvm_vcpu *vcpu)
> +{
> +       struct vcpu_svm *svm = to_svm(vcpu);
> +       bool intercept = !(svm->vmcb->control.misc_ctl2 & SVM_MISC2_ENABLE_V_LBR);
> +
> +       /*
> +        * Skip redundant updates from svm_update_lbrv(), e.g. on every nested
> +        * VMRUN and #VMEXIT.  Note, svm_recalc_msr_intercepts() updates the
> +        * bitmap unconditionally, as userspace MSR filters may have changed.
> +        */
> +       if (intercept == svm->lbr_msrs_intercepted)
> +               return;
> +
> +       svm_set_lbr_msr_intercepts(vcpu);
> +}
> +
>  void svm_vcpu_free_msrpm(void *msrpm)
>  {
>         __free_pages(virt_to_page(msrpm), get_order(MSRPM_SIZE));
> @@ -795,8 +808,13 @@ static void svm_recalc_msr_intercepts(struct kvm_vcpu *vcpu)
>         svm_disable_intercept_for_msr(vcpu, MSR_SYSCALL_MASK, MSR_TYPE_RW);
>  #endif
>
> +       /*
> +        * Unconditionally refresh the LBR MSR intercepts, as userspace MSR
> +        * filters may have changed, in which case the bitmap may be stale even
> +        * if KVM's desired interception hasn't changed.
> +        */
>         if (lbrv)
> -               svm_recalc_lbr_msr_intercepts(vcpu);
> +               svm_set_lbr_msr_intercepts(vcpu);
>
>         if (cpu_feature_enabled(X86_FEATURE_IBPB))
>                 svm_set_intercept_for_msr(vcpu, MSR_IA32_PRED_CMD, MSR_TYPE_W,
> diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
> index 84f19026d3e8..62e08d6d4706 100644
> --- a/arch/x86/kvm/svm/svm.h
> +++ b/arch/x86/kvm/svm/svm.h
> @@ -364,6 +364,12 @@ struct vcpu_svm {
>
>         bool avic_irq_window;
>         bool x2avic_msrs_intercepted;
> +
> +       /*
> +        * True if KVM wants the LBR MSRs to be intercepted.  Note, userspace MSR
> +        * filters may force interception regardless, i.e. this doesn't
> +        * necessarily reflect the state of the MSR bitmap.
> +        */
>         bool lbr_msrs_intercepted;
>
>         /* Guest GIF value, used when vGIF is not enabled */
> --
> 2.56.0.212.g129ede20d9-goog
>

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-10-21  0:10 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-20 23:45 [PATCH 0/4] KVM: x86: Honor userspace MSR filter changes for passed-through LBR MSRs Jim Mattson
2026-10-20 23:45 ` [PATCH 1/4] KVM: SVM: Always update LBR MSR intercepts on full MSR intercept recalc Jim Mattson
2026-10-21  0:09   ` Yosry Ahmed
2026-10-20 23:45 ` [PATCH 2/4] KVM: VMX: Refresh " Jim Mattson
2026-10-20 23:45 ` [PATCH 3/4] KVM: selftests: Add a helper to request XSAVE permission without skipping Jim Mattson
2026-10-20 23:45 ` [PATCH 4/4] KVM: selftests: Verify MSR filter changes apply to passed-through MSRs Jim Mattson

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®