Skip to content

Commit f7a421d

Browse files
committed
KVM: x86: Fix shadow paging use-after-free due to unexpected role
jira VULN-191426 cve CVE-2026-53359 commit-author Paolo Bonzini <pbonzini@redhat.com> commit 81ccda3 upstream-diff | Upstream adds the check to kvm_mmu_get_child_sp(), which doesn't exist here, so it goes into kvm_mmu_child_sp_exists() at the reuse sites in __direct_map() and FNAME(fetch). The expected role comes from kvm_mmu_child_role(), split out of kvm_mmu_get_page() (as upstream did in 2e65e84) so the check and the allocator can't derive different roles. Commit 0cb2af2 ("KVM: x86: Fix shadow paging use-after-free due to unexpected GFN") fixed a shadow paging mismatch between stored and computed GFNs; the bug could be triggered by changing a PDE mapping from outside the guest, and then deleting a memslot. The rmap_remove() call would miss entries created after the PDE change because the GFN of the leaf SPTE does not match the GFN of the struct kvm_mmu_page. A similar hole however remains if the modified PDE points to a non-leaf page. In this case the gfn can be made to match, but the role does not match: the original large 2MB page creates a kvm_mmu_page with direct=1, while the new 4KB needs a kvm_mmu_page with direct=0. However, kvm_mmu_get_child_sp() does not compare the role, and therefore reuses the page. The next step is installing a leaf (4KB) SPTE on the new path which records an rmap entry under the gfn resolved by the walk. But when that child is zapped its parent kvm_mmu_page has direct=1 and kvm_mmu_page_get_gfn() computes the gfn for the 4KB page as sp->gfn + index instead of using sp->shadowed_translation[] (or sp->gfns[] in older kernels). It therefore fails to remove the recorded entry. When the memslot is dropped the shadow page is freed but the rmap entry survives, as in the scenario that was already fixed. Code that later walks that gfn (dirty logging, MMU notifier invalidation, and so on) dereferences an sptep that lies 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: Paolo Bonzini <pbonzini@redhat.com> (cherry picked from commit 81ccda3) Signed-off-by: Sultan Alsawaf <sultan@ciq.com>
1 parent 6e68f66 commit f7a421d

2 files changed

Lines changed: 39 additions & 16 deletions

File tree

arch/x86/kvm/mmu/mmu.c

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2053,20 +2053,13 @@ static void clear_sp_write_flooding_count(u64 *spte)
20532053
__clear_sp_write_flooding_count(sptep_to_sp(spte));
20542054
}
20552055

2056-
static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu,
2057-
gfn_t gfn,
2058-
gva_t gaddr,
2059-
unsigned level,
2060-
int direct,
2061-
unsigned int access)
2056+
static union kvm_mmu_page_role
2057+
kvm_mmu_child_role(struct kvm_vcpu *vcpu, gva_t gaddr, unsigned level,
2058+
int direct, unsigned int access)
20622059
{
20632060
bool direct_mmu = vcpu->arch.mmu->direct_map;
20642061
union kvm_mmu_page_role role;
2065-
struct hlist_head *sp_list;
20662062
unsigned quadrant;
2067-
struct kvm_mmu_page *sp;
2068-
int collisions = 0;
2069-
LIST_HEAD(invalid_list);
20702063

20712064
role = vcpu->arch.mmu->mmu_role.base;
20722065
role.level = level;
@@ -2080,6 +2073,25 @@ static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu,
20802073
role.quadrant = quadrant;
20812074
}
20822075

2076+
return role;
2077+
}
2078+
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)
2085+
{
2086+
bool direct_mmu = vcpu->arch.mmu->direct_map;
2087+
union kvm_mmu_page_role role;
2088+
struct hlist_head *sp_list;
2089+
struct kvm_mmu_page *sp;
2090+
int collisions = 0;
2091+
LIST_HEAD(invalid_list);
2092+
2093+
role = kvm_mmu_child_role(vcpu, gaddr, level, direct, access);
2094+
20832095
sp_list = &vcpu->kvm->arch.mmu_page_hash[kvm_page_table_hashfn(gfn)];
20842096
for_each_valid_sp(vcpu->kvm, sp, sp_list) {
20852097
if (sp->gfn != gfn) {
@@ -2289,15 +2301,22 @@ static int mmu_page_zap_pte(struct kvm *kvm, struct kvm_mmu_page *sp,
22892301
return 0;
22902302
}
22912303

2292-
static bool kvm_mmu_child_sp_exists(u64 *sptep, gfn_t gfn)
2304+
static bool kvm_mmu_child_sp_exists(struct kvm_vcpu *vcpu, u64 *sptep,
2305+
gfn_t gfn, gva_t gaddr, unsigned level,
2306+
int direct, unsigned int access)
22932307
{
2308+
union kvm_mmu_page_role role;
22942309
struct kvm_mmu_page *child;
22952310

22962311
if (!is_shadow_present_pte(*sptep) || is_large_pte(*sptep))
22972312
return false;
22982313

22992314
child = to_shadow_page(*sptep & PT64_BASE_ADDR_MASK);
2300-
return child && child->gfn == gfn;
2315+
if (!child)
2316+
return false;
2317+
2318+
role = kvm_mmu_child_role(vcpu, gaddr, level, direct, access);
2319+
return child->gfn == gfn && child->role.word == role.word;
23012320
}
23022321

23032322
static void drop_large_spte(struct kvm_vcpu *vcpu, u64 *sptep)
@@ -3001,7 +3020,8 @@ static int __direct_map(struct kvm_vcpu *vcpu, gpa_t gpa, u32 error_code,
30013020
if (it.level == level)
30023021
break;
30033022

3004-
if (kvm_mmu_child_sp_exists(it.sptep, base_gfn))
3023+
if (kvm_mmu_child_sp_exists(vcpu, it.sptep, base_gfn, it.addr,
3024+
it.level - 1, true, ACC_ALL))
30053025
continue;
30063026

30073027
drop_large_spte(vcpu, it.sptep);

arch/x86/kvm/mmu/paging_tmpl.h

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

663663
table_gfn = gw->table_gfn[it.level - 2];
664+
access = gw->pt_access[it.level - 2];
664665
sp = NULL;
665-
if (!kvm_mmu_child_sp_exists(it.sptep, table_gfn)) {
666+
if (!kvm_mmu_child_sp_exists(vcpu, it.sptep, table_gfn, addr,
667+
it.level - 1, false, access)) {
666668
drop_large_spte(vcpu, it.sptep);
667-
access = gw->pt_access[it.level - 2];
668669
sp = kvm_mmu_get_page(vcpu, table_gfn, addr,
669670
it.level-1, false, access);
670671
/*
@@ -721,7 +722,9 @@ static int FNAME(fetch)(struct kvm_vcpu *vcpu, gpa_t addr,
721722

722723
validate_direct_spte(vcpu, it.sptep, direct_access);
723724

724-
if (!kvm_mmu_child_sp_exists(it.sptep, base_gfn)) {
725+
if (!kvm_mmu_child_sp_exists(vcpu, it.sptep, base_gfn, addr,
726+
it.level - 1, true,
727+
direct_access)) {
725728
drop_large_spte(vcpu, it.sptep);
726729
sp = kvm_mmu_get_page(vcpu, base_gfn, addr,
727730
it.level - 1, true, direct_access);

0 commit comments

Comments
 (0)