* [PATCH v2 0/2] KVM: x86/mmu: fix cross-address-space MMU metadata checks
@ 2026-07-21 10:35 Jinu Kim
2026-07-21 10:35 ` [PATCH v2 1/2] KVM: x86/mmu: Check write tracking in all address spaces Jinu Kim
2026-07-21 10:35 ` [PATCH v2 2/2] KVM: x86/mmu: Check all address spaces before skipping unsync Jinu Kim
0 siblings, 2 replies; 4+ messages in thread
From: Jinu Kim @ 2026-07-21 10:35 UTC (permalink / raw)
To: Sean Christopherson; +Cc: Paolo Bonzini, kvm, linux-kernel
The x86 shadow MMU keeps write-tracking and hugepage metadata per address
space, but shares the shadow-page hash across all address spaces. With SMM,
checking only the supplied memslot can therefore miss metadata for the same
GFN in the other address space.
For write tracking, this can allow an upper-level shadow page to become
unsync. Synchronizing the page later can treat a non-leaf SPTE as a leaf and
reach the BUG in pte_list_remove(). The observed nested-VMX failure was:
WARNING: mmu_try_to_unsync_pages
WARNING: ept_sync_spte
kernel BUG at arch/x86/kvm/mmu/mmu.c:1104
RIP: pte_list_remove.isra.0
Patch 1 makes kvm_gfn_is_write_tracked() check both x86 address spaces so
that all callers honor write tracking regardless of the active address
space.
Patch 2 makes the hugepage fast path in mmu_try_to_unsync_pages() check both
address spaces before skipping the shadow-page lookup. It is kept separate
because the hugepage optimization was added much more recently than the
write-tracking bug.
The series is based on commit
6bc96b971766fbbbbdd9fb2642cedacaf02da957.
Testing on Intel VMX:
- Patch 1 alone completed the reproducer without a KVM warning or fault;
vmx_pf_exception_test passed all 75 checks.
- Patch 2 alone built and loaded successfully; vmx_pse_test passed all 92
checks.
- The full series completed the reproducer without a KVM warning or fault;
vmx_pse_test passed all 92 checks with EPT enabled and all 92 checks with
EPT disabled.
- bzImage, kvm.ko, kvm-intel.ko, and irqbypass.ko built successfully with
CONFIG_KVM_WERROR=y.
Changes from v1:
- Split the write-tracking and hugepage fixes for independent backporting.
- Move the cross-address-space write-tracking check into
kvm_gfn_is_write_tracked() so that all callers are covered.
- Check the supplied memslot before consulting the other address space.
Jinu Kim (2):
KVM: x86/mmu: Check write tracking in all address spaces
KVM: x86/mmu: Check all address spaces before skipping unsync
arch/x86/kvm/mmu/mmu.c | 21 ++++++++++++++++++++-
arch/x86/kvm/mmu/page_track.c | 26 +++++++++++++++++++++++---
2 files changed, 43 insertions(+), 4 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2 1/2] KVM: x86/mmu: Check write tracking in all address spaces
2026-07-21 10:35 [PATCH v2 0/2] KVM: x86/mmu: fix cross-address-space MMU metadata checks Jinu Kim
@ 2026-07-21 10:35 ` Jinu Kim
2026-07-21 10:35 ` [PATCH v2 2/2] KVM: x86/mmu: Check all address spaces before skipping unsync Jinu Kim
1 sibling, 0 replies; 4+ messages in thread
From: Jinu Kim @ 2026-07-21 10:35 UTC (permalink / raw)
To: Sean Christopherson; +Cc: Paolo Bonzini, kvm, linux-kernel
kvm_gfn_is_write_tracked() checks only the supplied memslot, but page
tracking is per-address-space and shadow pages are shared across all
address spaces. With SMM, a GFN can therefore be write-tracked in one
address space and appear untracked through the other.
Check the supplied slot first, then the slot for the other address space.
This ensures all callers honor write tracking regardless of the active
address space. In particular, it prevents mmu_try_to_unsync_pages() from
marking an upper-level shadow page unsync and eventually triggering the
BUG in pte_list_remove().
Fixes: 699023e23965 ("KVM: x86: add SMM to the MMU role, support SMRAM address space")
Assisted-by: Codex:GPT-5
Signed-off-by: Jinu Kim <kimjw04271234@gmail.com>
---
arch/x86/kvm/mmu/page_track.c | 26 +++++++++++++++++++++++---
1 file changed, 23 insertions(+), 3 deletions(-)
diff --git a/arch/x86/kvm/mmu/page_track.c b/arch/x86/kvm/mmu/page_track.c
index 1b17b12393a8c..ff93591be1adb 100644
--- a/arch/x86/kvm/mmu/page_track.c
+++ b/arch/x86/kvm/mmu/page_track.c
@@ -133,10 +133,22 @@ void __kvm_write_track_remove_gfn(struct kvm *kvm,
/*
* check if the corresponding access on the specified guest page is tracked.
*/
+static bool __kvm_gfn_is_write_tracked(const struct kvm_memory_slot *slot,
+ gfn_t gfn)
+{
+ int index;
+
+ if (!slot)
+ return false;
+
+ index = gfn_to_index(gfn, slot->base_gfn, PG_LEVEL_4K);
+ return !!READ_ONCE(slot->arch.gfn_write_track[index]);
+}
+
bool kvm_gfn_is_write_tracked(struct kvm *kvm,
const struct kvm_memory_slot *slot, gfn_t gfn)
{
- int index;
+ const struct kvm_memory_slot *other_slot;
if (!slot)
return false;
@@ -144,8 +156,16 @@ bool kvm_gfn_is_write_tracked(struct kvm *kvm,
if (!kvm_page_track_write_tracking_enabled(kvm))
return false;
- index = gfn_to_index(gfn, slot->base_gfn, PG_LEVEL_4K);
- return !!READ_ONCE(slot->arch.gfn_write_track[index]);
+ BUILD_BUG_ON(KVM_MAX_NR_ADDRESS_SPACES > 2);
+
+ if (__kvm_gfn_is_write_tracked(slot, gfn))
+ return true;
+
+ if (kvm_arch_nr_memslot_as_ids(kvm) == 1)
+ return false;
+
+ other_slot = __gfn_to_memslot(__kvm_memslots(kvm, slot->as_id ^ 1), gfn);
+ return __kvm_gfn_is_write_tracked(other_slot, gfn);
}
#ifdef CONFIG_KVM_EXTERNAL_WRITE_TRACKING
base-commit: 6bc96b971766fbbbbdd9fb2642cedacaf02da957
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2 2/2] KVM: x86/mmu: Check all address spaces before skipping unsync
2026-07-21 10:35 [PATCH v2 0/2] KVM: x86/mmu: fix cross-address-space MMU metadata checks Jinu Kim
2026-07-21 10:35 ` [PATCH v2 1/2] KVM: x86/mmu: Check write tracking in all address spaces Jinu Kim
@ 2026-07-21 10:35 ` Jinu Kim
2026-07-28 16:18 ` Paolo Bonzini
1 sibling, 1 reply; 4+ messages in thread
From: Jinu Kim @ 2026-07-21 10:35 UTC (permalink / raw)
To: Sean Christopherson; +Cc: Paolo Bonzini, kvm, linux-kernel
mmu_try_to_unsync_pages() skips the shadow-page lookup when the
supplied memslot allows a hugepage, because a shadow page would disallow
hugepages. But hugepage metadata is per-address-space while shadow pages
are shared across all address spaces. With SMM, the other address space
can therefore have a shadow page even when the supplied memslot allows a
hugepage.
Check the corresponding memslot in the other address space before
taking the fast path. Skip the shadow-page lookup only when all address
spaces allow a hugepage.
Fixes: b3ae3ceb5569 ("KVM: x86/mmu: KVM: x86/mmu: Skip unsync when large pages are allowed")
Assisted-by: Codex:GPT-5
Signed-off-by: Jinu Kim <kimjw04271234@gmail.com>
---
arch/x86/kvm/mmu/mmu.c | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 7757f0ad65c6f..1db6750992546 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -725,6 +725,25 @@ static struct kvm_lpage_info *lpage_info_slot(gfn_t gfn,
return &slot->arch.lpage_info[level - 2][idx];
}
+static bool kvm_gfn_is_lpage_allowed(struct kvm *kvm,
+ const struct kvm_memory_slot *slot,
+ gfn_t gfn, int level)
+{
+ const struct kvm_memory_slot *other_slot;
+
+ BUILD_BUG_ON(KVM_MAX_NR_ADDRESS_SPACES > 2);
+
+ if (lpage_info_slot(gfn, slot, level)->disallow_lpage)
+ return false;
+
+ if (kvm_arch_nr_memslot_as_ids(kvm) == 1)
+ return true;
+
+ other_slot = __gfn_to_memslot(__kvm_memslots(kvm, slot->as_id ^ 1), gfn);
+ return !other_slot ||
+ !lpage_info_slot(gfn, other_slot, level)->disallow_lpage;
+}
+
/*
* The most significant bit in disallow_lpage tracks whether or not memory
* attributes are mixed, i.e. not identical for all gfns at the current level.
@@ -2972,7 +2991,7 @@ int mmu_try_to_unsync_pages(struct kvm *kvm, const struct kvm_memory_slot *slot,
* write-protected (see above), thus if the gfn can be mapped with a
* hugepage and isn't write-tracked, it can't have a shadow page.
*/
- if (!lpage_info_slot(gfn, slot, PG_LEVEL_2M)->disallow_lpage)
+ if (kvm_gfn_is_lpage_allowed(kvm, slot, gfn, PG_LEVEL_2M))
return 0;
/*
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v2 2/2] KVM: x86/mmu: Check all address spaces before skipping unsync
2026-07-21 10:35 ` [PATCH v2 2/2] KVM: x86/mmu: Check all address spaces before skipping unsync Jinu Kim
@ 2026-07-28 16:18 ` Paolo Bonzini
0 siblings, 0 replies; 4+ messages in thread
From: Paolo Bonzini @ 2026-07-28 16:18 UTC (permalink / raw)
To: Jinu Kim, Sean Christopherson; +Cc: kvm, linux-kernel
On 7/21/26 12:35, Jinu Kim wrote:
> diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
> index 7757f0ad65c6f..1db6750992546 100644
> --- a/arch/x86/kvm/mmu/mmu.c
> +++ b/arch/x86/kvm/mmu/mmu.c
> @@ -725,6 +725,25 @@ static struct kvm_lpage_info *lpage_info_slot(gfn_t gfn,
> return &slot->arch.lpage_info[level - 2][idx];
> }
>
> +static bool kvm_gfn_is_lpage_allowed(struct kvm *kvm,
> + const struct kvm_memory_slot *slot,
> + gfn_t gfn, int level)
> +{
> + const struct kvm_memory_slot *other_slot;
> +
> + BUILD_BUG_ON(KVM_MAX_NR_ADDRESS_SPACES > 2);
> +
> + if (lpage_info_slot(gfn, slot, level)->disallow_lpage)
> + return false;
> +
> + if (kvm_arch_nr_memslot_as_ids(kvm) == 1)
> + return true;
> +
> + other_slot = __gfn_to_memslot(__kvm_memslots(kvm, slot->as_id ^ 1), gfn);
> + return !other_slot ||
> + !lpage_info_slot(gfn, other_slot, level)->disallow_lpage;
I have a slight preference for checking "> 1", for example:
if (kvm_arch_nr_memslot_as_ids(kvm) > 1) {
other_slot = __gfn_to_memslot(__kvm_memslots(kvm, slot->as_id ^ 1), gfn);
if (other_slot && lpage_info_slot(gfn, other_slot, level)->disallow_lpage)
return false;
}
return true;
I made the change and queued the two patches, thanks!
Paolo
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-07-28 16:18 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-21 10:35 [PATCH v2 0/2] KVM: x86/mmu: fix cross-address-space MMU metadata checks Jinu Kim
2026-07-21 10:35 ` [PATCH v2 1/2] KVM: x86/mmu: Check write tracking in all address spaces Jinu Kim
2026-07-21 10:35 ` [PATCH v2 2/2] KVM: x86/mmu: Check all address spaces before skipping unsync Jinu Kim
2026-07-28 16:18 ` Paolo Bonzini
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®