Skip to content

Commit bd7ce17

Browse files
KVM: x86: Fix shadow paging use-after-free due to unexpected role
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> (cherry picked from commit 1b5de8ad73a7a312c68374860bab5bbce44075a9)
1 parent c286885 commit bd7ce17

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
@@ -2033,29 +2033,32 @@ kvm_mmu_child_role(struct kvm_vcpu *vcpu, gva_t gaddr, unsigned int level,
20332033
/*
20342034
* Return true iff the present, non-large shadow-page table pointed to by
20352035
* @sptep can be safely reused as the child for a fault expecting a page at
2036-
* @gfn.
2036+
* @gfn with role derived from (@gaddr, @level, @direct, @access).
20372037
*
2038-
* Prior to the CVE-2026-46113 (unexpected GFN) fix, the fault-path walkers
2039-
* (__direct_map() and FNAME(fetch)) reused a present interior SPTE without
2040-
* re-validating that the child shadow page it references still maps the
2041-
* walk's expected gfn. A guest can arrange for the referenced child SP to
2042-
* have a different gfn, causing rmap add/remove to compute divergent gfns
2043-
* and corrupting the rmap list (pte_list_remove() BUG / use-after-free).
2044-
* This matches upstream's kvm_mmu_get_child_sp() -EEXIST guard, which does
2045-
* not exist on this (pre-6.2 MMU refactor) tree, so the equivalent check is
2038+
* Prior to the CVE-2026-46113 (unexpected GFN) and CVE-2026-53359
2039+
* (unexpected role) fixes, the fault-path walkers (__direct_map() and
2040+
* FNAME(fetch)) reused a present interior SPTE without re-validating that
2041+
* the child shadow page it references still matches the walk's expected
2042+
* gfn and role. A guest can arrange for the referenced child SP to have a
2043+
* different gfn/role, causing rmap add/remove to compute divergent gfns and
2044+
* corrupting the rmap list (pte_list_remove() BUG / use-after-free). This
2045+
* matches upstream's kvm_mmu_get_child_sp() -EEXIST guard, which does not
2046+
* exist on this (pre-6.2 MMU refactor) tree, so the equivalent check is
20462047
* applied at each reuse site instead.
20472048
*/
20482049
static bool kvm_mmu_child_sp_matches(struct kvm_vcpu *vcpu, u64 *sptep,
20492050
gfn_t gfn, gva_t gaddr, unsigned int level,
20502051
int direct, unsigned int access)
20512052
{
2053+
union kvm_mmu_page_role role;
20522054
struct kvm_mmu_page *child;
20532055

20542056
child = to_shadow_page(*sptep & PT64_BASE_ADDR_MASK);
20552057
if (!child)
20562058
return false;
20572059

2058-
return child->gfn == gfn;
2060+
role = kvm_mmu_child_role(vcpu, gaddr, level, direct, access);
2061+
return child->gfn == gfn && child->role.word == role.word;
20592062
}
20602063

20612064
/*

0 commit comments

Comments
 (0)