Skip to content

Commit 649ff5c

Browse files
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 - commit-source-sha 81ccda3 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 81ccda3 fixes kvm_mmu_get_child_sp() by adding spte_to_child_sp(*sptep)->role.word == role.word to its -EEXIST reuse guard, on top of the unexpected-GFN check. That function does not exist on this 4.18-based tree (see the prerequisite "unexpected GFN" commit for the full rationale); the equivalent guard lives in the shared helper kvm_mmu_child_sp_matches() applied at the three fault-path reuse sites. This commit adds the role.word comparison to that helper, using kvm_mmu_child_role() to derive the expected role exactly as kvm_mmu_get_page() does. Functionally equivalent to upstream. Commit 0cb2af2 ("KVM: x86: Fix shadow paging use-after-free due to unexpected GFN") fixed a shadow paging mismatch between stored and computed GFNs. A similar hole remains if the modified PDE points to a non-leaf page: the gfn can be made to match, but the role does not. The original large 2MB page creates a kvm_mmu_page with direct=1, while the new 4KB mapping needs one with direct=0; because the reuse path did not compare the role, the direct=1 page was reused. Installing a leaf SPTE then records an rmap entry under the walk's gfn, but when that child is zapped its parent has direct=1 and kvm_mmu_page_get_gfn() computes the gfn as sp->gfn + index instead of using sp->gfns[], so the recorded entry is not removed. When the memslot is dropped the shadow page is freed but the rmap entry survives, and later walks 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 51e2c75 commit 649ff5c

1 file changed

Lines changed: 13 additions & 10 deletions

File tree

arch/x86/kvm/mmu/mmu.c

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2110,29 +2110,32 @@ kvm_mmu_child_role(struct kvm_vcpu *vcpu, gva_t gaddr, unsigned int level,
21102110
/*
21112111
* Return true iff the present, non-large shadow-page table pointed to by
21122112
* @sptep can be safely reused as the child for a fault expecting a page at
2113-
* @gfn.
2113+
* @gfn with role derived from (@gaddr, @level, @direct, @access).
21142114
*
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
2115+
* Prior to the CVE-2026-46113 (unexpected GFN) and CVE-2026-53359
2116+
* (unexpected role) fixes, the fault-path walkers (__direct_map() and
2117+
* FNAME(fetch)) reused a present interior SPTE without re-validating that
2118+
* the child shadow page it references still matches the walk's expected
2119+
* gfn and role. A guest can arrange for the referenced child SP to have a
2120+
* different gfn/role, causing rmap add/remove to compute divergent gfns and
2121+
* corrupting the rmap list (pte_list_remove() BUG / use-after-free). This
2122+
* matches upstream's kvm_mmu_get_child_sp() -EEXIST guard, which does not
2123+
* exist on this (pre-6.2 MMU refactor) tree, so the equivalent check is
21232124
* applied at each reuse site instead.
21242125
*/
21252126
static bool kvm_mmu_child_sp_matches(struct kvm_vcpu *vcpu, u64 *sptep,
21262127
gfn_t gfn, gva_t gaddr, unsigned int level,
21272128
int direct, unsigned int access)
21282129
{
2130+
union kvm_mmu_page_role role;
21292131
struct kvm_mmu_page *child;
21302132

21312133
child = to_shadow_page(*sptep & PT64_BASE_ADDR_MASK);
21322134
if (!child)
21332135
return false;
21342136

2135-
return child->gfn == gfn;
2137+
role = kvm_mmu_child_role(vcpu, gaddr, level, direct, access);
2138+
return child->gfn == gfn && child->role.word == role.word;
21362139
}
21372140

21382141
/*

0 commit comments

Comments
 (0)