Skip to content

Commit 51e2c75

Browse files
KVM: x86: Fix shadow paging use-after-free due to unexpected GFN
jira VULN-186742 cve CVE-2026-46113 commit-author Sean Christopherson <seanjc@google.com> commit - commit-source-sha 0cb2af2ea66ad95873455d8a1d0f6f4e13fef645 commit-source https://lore.kernel.org/all/20260626112634.1778506-1-pbonzini@redhat.com/ upstream-diff | This is a HAND-PORT, not a cherry-pick. Upstream commit 0cb2af2 fixes kvm_mmu_get_child_sp() by adding a spte_to_child_sp(*sptep)->gfn == gfn check to its -EEXIST reuse guard. That function (and spte_to_child_sp/kvm_mmu_child_role, the whole get_child_sp/get_shadow_page split) does NOT exist on this 4.18-based tree, which predates the ~6.2 KVM MMU refactor and still uses kvm_mmu_get_page() + link_shadow_page(). On this tree the interior-SPTE reuse happens in the fault-path walkers __direct_map() and FNAME(fetch): after drop_large_spte(), a present non-large SPTE is reused as-is (skipping the kvm_mmu_get_page() alloc path that would validate gfn), with no re-check that the referenced child shadow page still maps the expected gfn. The equivalent guard is therefore applied at each of the three reuse sites via a shared helper kvm_mmu_child_sp_matches(); on mismatch the stale child SPTE is zapped (mmu_page_zap_pte + remote flush) so the correct child is (re)allocated. kvm_mmu_child_role() factors out kvm_mmu_get_page()'s role derivation so the guard and the allocator cannot drift. Functionally equivalent to upstream. Commit 0cb2af2 ("KVM: x86: Fix shadow paging use-after-free due to unexpected GFN") fixes a shadow paging mismatch between the stored and computed GFNs. The bug can be triggered by changing a PDE mapping from outside the guest and then deleting a memslot: rmap_remove() misses entries whose leaf SPTE gfn does not match the gfn of the struct kvm_mmu_page. The freed shadow page's rmap entry survives, and later walks of that gfn (dirty logging, MMU notifier invalidation, ...) dereference an sptep in the freed page, causing the use-after-free. Fixes: 2032a93 ("KVM: MMU: Don't allocate gfns page for direct mmu pages") Reported-by: Hyunwoo Kim <imv4bel@gmail.com> Signed-off-by: Shreeya Patel <spatel@ciq.com>
1 parent cbbe7c6 commit 51e2c75

2 files changed

Lines changed: 109 additions & 12 deletions

File tree

arch/x86/kvm/mmu/mmu.c

Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ static struct percpu_counter kvm_total_used_mmu_pages;
188188
static void mmu_spte_set(u64 *sptep, u64 spte);
189189
static union kvm_mmu_page_role
190190
kvm_mmu_calc_root_page_role(struct kvm_vcpu *vcpu);
191+
static int mmu_page_zap_pte(struct kvm *kvm, struct kvm_mmu_page *sp,
192+
u64 *spte, struct list_head *invalid_list);
191193

192194
struct kvm_mmu_role_regs {
193195
const unsigned long cr0;
@@ -2076,20 +2078,19 @@ static void clear_sp_write_flooding_count(u64 *spte)
20762078
__clear_sp_write_flooding_count(sptep_to_sp(spte));
20772079
}
20782080

2079-
static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu,
2080-
gfn_t gfn,
2081-
gva_t gaddr,
2082-
unsigned level,
2083-
int direct,
2084-
unsigned int access)
2081+
/*
2082+
* Compute the role a shadow page would have if allocated for the given
2083+
* (gaddr, level, direct, access). This mirrors the role derivation in
2084+
* kvm_mmu_get_page() so the reuse guard (see kvm_mmu_child_sp_matches())
2085+
* and the allocation path cannot drift.
2086+
*/
2087+
static union kvm_mmu_page_role
2088+
kvm_mmu_child_role(struct kvm_vcpu *vcpu, gva_t gaddr, unsigned int level,
2089+
int direct, unsigned int access)
20852090
{
20862091
bool direct_mmu = vcpu->arch.mmu->direct_map;
20872092
union kvm_mmu_page_role role;
2088-
struct hlist_head *sp_list;
20892093
unsigned quadrant;
2090-
struct kvm_mmu_page *sp;
2091-
int collisions = 0;
2092-
LIST_HEAD(invalid_list);
20932094

20942095
role = vcpu->arch.mmu->mmu_role.base;
20952096
role.level = level;
@@ -2103,6 +2104,67 @@ static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu,
21032104
role.quadrant = quadrant;
21042105
}
21052106

2107+
return role;
2108+
}
2109+
2110+
/*
2111+
* Return true iff the present, non-large shadow-page table pointed to by
2112+
* @sptep can be safely reused as the child for a fault expecting a page at
2113+
* @gfn.
2114+
*
2115+
* Prior to the CVE-2026-46113 (unexpected GFN) fix, the fault-path walkers
2116+
* (__direct_map() and FNAME(fetch)) reused a present interior SPTE without
2117+
* re-validating that the child shadow page it references still maps the
2118+
* walk's expected gfn. A guest can arrange for the referenced child SP to
2119+
* have a different gfn, causing rmap add/remove to compute divergent gfns
2120+
* and corrupting the rmap list (pte_list_remove() BUG / use-after-free).
2121+
* This matches upstream's kvm_mmu_get_child_sp() -EEXIST guard, which does
2122+
* not exist on this (pre-6.2 MMU refactor) tree, so the equivalent check is
2123+
* applied at each reuse site instead.
2124+
*/
2125+
static bool kvm_mmu_child_sp_matches(struct kvm_vcpu *vcpu, u64 *sptep,
2126+
gfn_t gfn, gva_t gaddr, unsigned int level,
2127+
int direct, unsigned int access)
2128+
{
2129+
struct kvm_mmu_page *child;
2130+
2131+
child = to_shadow_page(*sptep & PT64_BASE_ADDR_MASK);
2132+
if (!child)
2133+
return false;
2134+
2135+
return child->gfn == gfn;
2136+
}
2137+
2138+
/*
2139+
* Zap a present interior SPTE whose referenced child shadow page does not
2140+
* match the walk's expected gfn/role, so the caller falls through to
2141+
* allocate (or look up) the correct child via kvm_mmu_get_page().
2142+
*/
2143+
static void kvm_mmu_drop_mismatched_child(struct kvm_vcpu *vcpu, u64 *sptep)
2144+
{
2145+
struct kvm_mmu_page *parent_sp = sptep_to_sp(sptep);
2146+
LIST_HEAD(invalid_list);
2147+
2148+
mmu_page_zap_pte(vcpu->kvm, parent_sp, sptep, &invalid_list);
2149+
kvm_mmu_remote_flush_or_zap(vcpu->kvm, &invalid_list, true);
2150+
}
2151+
2152+
static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu,
2153+
gfn_t gfn,
2154+
gva_t gaddr,
2155+
unsigned level,
2156+
int direct,
2157+
unsigned int access)
2158+
{
2159+
bool direct_mmu = vcpu->arch.mmu->direct_map;
2160+
union kvm_mmu_page_role role;
2161+
struct hlist_head *sp_list;
2162+
struct kvm_mmu_page *sp;
2163+
int collisions = 0;
2164+
LIST_HEAD(invalid_list);
2165+
2166+
role = kvm_mmu_child_role(vcpu, gaddr, level, direct, access);
2167+
21062168
sp_list = &vcpu->kvm->arch.mmu_page_hash[kvm_page_table_hashfn(gfn)];
21072169
for_each_valid_sp(vcpu->kvm, sp, sp_list) {
21082170
if (sp->gfn != gfn) {
@@ -2998,6 +3060,18 @@ static int __direct_map(struct kvm_vcpu *vcpu, gpa_t gpa, u32 error_code,
29983060
break;
29993061

30003062
drop_large_spte(vcpu, it.sptep);
3063+
3064+
/*
3065+
* If a present interior SPTE references a child shadow page
3066+
* whose gfn/role no longer matches this walk, zap it so the
3067+
* correct child is (re)allocated below. Guards against the
3068+
* CVE-2026-46113/CVE-2026-53359 rmap divergence UAF.
3069+
*/
3070+
if (is_shadow_present_pte(*it.sptep) &&
3071+
!kvm_mmu_child_sp_matches(vcpu, it.sptep, base_gfn, it.addr,
3072+
it.level - 1, true, ACC_ALL))
3073+
kvm_mmu_drop_mismatched_child(vcpu, it.sptep);
3074+
30013075
if (is_shadow_present_pte(*it.sptep))
30023076
continue;
30033077

arch/x86/kvm/mmu/paging_tmpl.h

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -661,10 +661,22 @@ static int FNAME(fetch)(struct kvm_vcpu *vcpu, gpa_t addr,
661661
clear_sp_write_flooding_count(it.sptep);
662662
drop_large_spte(vcpu, it.sptep);
663663

664+
table_gfn = gw->table_gfn[it.level - 2];
665+
access = gw->pt_access[it.level - 2];
666+
667+
/*
668+
* If a present interior SPTE references a child shadow page
669+
* whose gfn/role no longer matches this walk, zap it so the
670+
* correct child is (re)allocated below. Guards against the
671+
* CVE-2026-46113/CVE-2026-53359 rmap divergence UAF.
672+
*/
673+
if (is_shadow_present_pte(*it.sptep) &&
674+
!kvm_mmu_child_sp_matches(vcpu, it.sptep, table_gfn, addr,
675+
it.level - 1, false, access))
676+
kvm_mmu_drop_mismatched_child(vcpu, it.sptep);
677+
664678
sp = NULL;
665679
if (!is_shadow_present_pte(*it.sptep)) {
666-
table_gfn = gw->table_gfn[it.level - 2];
667-
access = gw->pt_access[it.level - 2];
668680
sp = kvm_mmu_get_page(vcpu, table_gfn, addr,
669681
it.level-1, false, access);
670682
/*
@@ -723,6 +735,17 @@ static int FNAME(fetch)(struct kvm_vcpu *vcpu, gpa_t addr,
723735

724736
drop_large_spte(vcpu, it.sptep);
725737

738+
/*
739+
* If a present interior SPTE references a child shadow page
740+
* whose gfn/role no longer matches this walk, zap it so the
741+
* correct child is (re)allocated below. Guards against the
742+
* CVE-2026-46113/CVE-2026-53359 rmap divergence UAF.
743+
*/
744+
if (is_shadow_present_pte(*it.sptep) &&
745+
!kvm_mmu_child_sp_matches(vcpu, it.sptep, base_gfn, addr,
746+
it.level - 1, true, direct_access))
747+
kvm_mmu_drop_mismatched_child(vcpu, it.sptep);
748+
726749
if (!is_shadow_present_pte(*it.sptep)) {
727750
sp = kvm_mmu_get_page(vcpu, base_gfn, addr,
728751
it.level - 1, true, direct_access);

0 commit comments

Comments
 (0)