Add KVM x86 shadow paging use-after-free fixes ahead of RHEL; bump to 211.30.2
This commit is contained in:
parent
fa067cacb0
commit
9d9b973c72
@ -0,0 +1,131 @@
|
||||
From 0cb2af2ea66ad8ff195c156ea690f11216285bdf Mon Sep 17 00:00:00 2001
|
||||
From: Sean Christopherson <seanjc@google.com>
|
||||
Date: Wed, 15 Apr 2026 00:52:34 +0000
|
||||
Subject: [PATCH] KVM: x86: Fix shadow paging use-after-free due to unexpected
|
||||
GFN
|
||||
|
||||
The shadow MMU computes GFNs for direct shadow pages using sp->gfn plus
|
||||
the SPTE index. This assumption breaks for shadow paging if the guest
|
||||
page tables are modified between VM entries (similar to commit
|
||||
aad885e77496, "KVM: x86/mmu: Drop/zap existing present SPTE even
|
||||
when creating an MMIO SPTE", 2026-03-27). The flow is as follows:
|
||||
|
||||
- a PDE is installed for a 2MB mapping, and a page in that area is
|
||||
accessed. KVM creates a kvm_mmu_page consisting of 512 4KB pages;
|
||||
the kvm_mmu_page is marked by FNAME(fetch) as direct-mapped because
|
||||
the guest's mapping is a huge page (and thus contiguous).
|
||||
|
||||
- the PDE mapping is changed from outside the guest.
|
||||
|
||||
- the guest accesses another page in the same 2MB area. KVM installs
|
||||
a new leaf SPTE and rmap entry; the SPTE uses the "correct" GFN
|
||||
(i.e. based on the new mapping, as changed in the previous step) but
|
||||
that GFN is outside of the [sp->gfn, sp->gfn + 511] range; therefore
|
||||
the rmap entry cannot be found and removed when the kvm_mmu_page
|
||||
is zapped.
|
||||
|
||||
- the memslot that covers the first 2MB mapping is deleted, and the
|
||||
kvm_mmu_page for the now-invalid GPA is zapped. However, rmap_remove()
|
||||
only looks at the [sp->gfn, sp->gfn + 511] range established in step 1,
|
||||
and fails to find the rmap entry that was recorded by step 3.
|
||||
|
||||
- any operation that causes an rmap walk for the same page accessed
|
||||
by step 3 then walks a stale rmap and dereferences a freed kvm_mmu_page.
|
||||
This includes dirty logging or MMU notifier invalidations (e.g., from
|
||||
MADV_DONTNEED).
|
||||
|
||||
The underlying issue is that KVM's walking of shadow PTEs assumes that
|
||||
if a SPTE is present when KVM wants to install a non-leaf SPTE, then the
|
||||
existing kvm_mmu_page must be for the correct gfn. Because the only way
|
||||
for the gfn to be wrong is if KVM messed up and failed to zap a SPTE...
|
||||
which shouldn't happen, but *actually* only happens in response to a
|
||||
guest write.
|
||||
|
||||
That bug dates back literally forever, as even the first version of KVM
|
||||
assumes that the GFN matches and walks into the "wrong" shadow page.
|
||||
However, that was only an imprecision until 2032a93d66fa ("KVM: MMU:
|
||||
Don't allocate gfns page for direct mmu pages") came along.
|
||||
|
||||
Fix it by checking for a target gfn mismatch and zapping the existing
|
||||
SPTE. That way the old SP and rmap entries are gone, KVM installs
|
||||
the rmap in the right location, and everyone is happy.
|
||||
|
||||
Fixes: 2032a93d66fa ("KVM: MMU: Don't allocate gfns page for direct mmu pages")
|
||||
Fixes: 6aa8b732ca01 ("kvm: userspace interface")
|
||||
Reported-by: Alexander Bulekov <bkov@amazon.com>
|
||||
Reported-by: Fred Griffoul <fgriffo@amazon.co.uk>
|
||||
Cc: stable@vger.kernel.org
|
||||
Signed-off-by: Sean Christopherson <seanjc@google.com>
|
||||
Link: https://patch.msgid.link/20260503201029.106481-1-pbonzini@redhat.com/
|
||||
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
|
||||
|
||||
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
|
||||
index 24fbc9ea502a..892246204435 100644
|
||||
--- a/arch/x86/kvm/mmu/mmu.c
|
||||
+++ b/arch/x86/kvm/mmu/mmu.c
|
||||
@@ -182,6 +182,8 @@ static struct kmem_cache *pte_list_desc_cache;
|
||||
struct kmem_cache *mmu_page_header_cache;
|
||||
|
||||
static void mmu_spte_set(u64 *sptep, u64 spte);
|
||||
+static int mmu_page_zap_pte(struct kvm *kvm, struct kvm_mmu_page *sp,
|
||||
+ u64 *spte, struct list_head *invalid_list);
|
||||
|
||||
struct kvm_mmu_role_regs {
|
||||
const unsigned long cr0;
|
||||
@@ -1287,19 +1289,6 @@ static void drop_spte(struct kvm *kvm, u64 *sptep)
|
||||
rmap_remove(kvm, sptep);
|
||||
}
|
||||
|
||||
-static void drop_large_spte(struct kvm *kvm, u64 *sptep, bool flush)
|
||||
-{
|
||||
- struct kvm_mmu_page *sp;
|
||||
-
|
||||
- sp = sptep_to_sp(sptep);
|
||||
- WARN_ON_ONCE(sp->role.level == PG_LEVEL_4K);
|
||||
-
|
||||
- drop_spte(kvm, sptep);
|
||||
-
|
||||
- if (flush)
|
||||
- kvm_flush_remote_tlbs_sptep(kvm, sptep);
|
||||
-}
|
||||
-
|
||||
/*
|
||||
* Write-protect on the specified @sptep, @pt_protect indicates whether
|
||||
* spte write-protection is caused by protecting shadow page table.
|
||||
@@ -2466,7 +2455,8 @@ static struct kvm_mmu_page *kvm_mmu_get_child_sp(struct kvm_vcpu *vcpu,
|
||||
{
|
||||
union kvm_mmu_page_role role;
|
||||
|
||||
- if (is_shadow_present_pte(*sptep) && !is_large_pte(*sptep))
|
||||
+ if (is_shadow_present_pte(*sptep) && !is_large_pte(*sptep) &&
|
||||
+ spte_to_child_sp(*sptep) && spte_to_child_sp(*sptep)->gfn == gfn)
|
||||
return ERR_PTR(-EEXIST);
|
||||
|
||||
role = kvm_mmu_child_role(sptep, direct, access);
|
||||
@@ -2544,13 +2534,16 @@ static void __link_shadow_page(struct kvm *kvm,
|
||||
|
||||
BUILD_BUG_ON(VMX_EPT_WRITABLE_MASK != PT_WRITABLE_MASK);
|
||||
|
||||
- /*
|
||||
- * If an SPTE is present already, it must be a leaf and therefore
|
||||
- * a large one. Drop it, and flush the TLB if needed, before
|
||||
- * installing sp.
|
||||
- */
|
||||
- if (is_shadow_present_pte(*sptep))
|
||||
- drop_large_spte(kvm, sptep, flush);
|
||||
+ if (is_shadow_present_pte(*sptep)) {
|
||||
+ struct kvm_mmu_page *parent_sp;
|
||||
+ LIST_HEAD(invalid_list);
|
||||
+
|
||||
+ parent_sp = sptep_to_sp(sptep);
|
||||
+ WARN_ON_ONCE(parent_sp->role.level == PG_LEVEL_4K);
|
||||
+
|
||||
+ mmu_page_zap_pte(kvm, parent_sp, sptep, &invalid_list);
|
||||
+ kvm_mmu_remote_flush_or_zap(kvm, &invalid_list, true);
|
||||
+ }
|
||||
|
||||
spte = make_nonleaf_spte(sp->spt, sp_ad_disabled(sp));
|
||||
|
||||
--
|
||||
2.50.1 (Apple Git-155)
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
From 81ccda30b4e83d8f5cc4fd50503c44e3a33abfeb Mon Sep 17 00:00:00 2001
|
||||
From: Paolo Bonzini <pbonzini@redhat.com>
|
||||
Date: Fri, 12 Jun 2026 22:18:12 +0200
|
||||
Subject: [PATCH] KVM: x86: Fix shadow paging use-after-free due to unexpected
|
||||
role
|
||||
|
||||
Commit 0cb2af2ea66ad ("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: 2032a93d66fa ("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>
|
||||
|
||||
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
|
||||
index 9368a71336fe..c13b80fe3125 100644
|
||||
--- a/arch/x86/kvm/mmu/mmu.c
|
||||
+++ b/arch/x86/kvm/mmu/mmu.c
|
||||
@@ -2459,13 +2459,15 @@ static struct kvm_mmu_page *kvm_mmu_get_child_sp(struct kvm_vcpu *vcpu,
|
||||
u64 *sptep, gfn_t gfn,
|
||||
bool direct, unsigned int access)
|
||||
{
|
||||
- union kvm_mmu_page_role role;
|
||||
+ union kvm_mmu_page_role role = kvm_mmu_child_role(sptep, direct, access);
|
||||
|
||||
- if (is_shadow_present_pte(*sptep) && !is_large_pte(*sptep) &&
|
||||
- spte_to_child_sp(*sptep) && spte_to_child_sp(*sptep)->gfn == gfn)
|
||||
+ if (is_shadow_present_pte(*sptep) &&
|
||||
+ !is_large_pte(*sptep) &&
|
||||
+ spte_to_child_sp(*sptep) &&
|
||||
+ spte_to_child_sp(*sptep)->gfn == gfn &&
|
||||
+ spte_to_child_sp(*sptep)->role.word == role.word)
|
||||
return ERR_PTR(-EEXIST);
|
||||
|
||||
- role = kvm_mmu_child_role(sptep, direct, access);
|
||||
return kvm_mmu_get_shadow_page(vcpu, gfn, role);
|
||||
}
|
||||
|
||||
--
|
||||
2.50.1 (Apple Git-155)
|
||||
|
||||
10
kernel.spec
10
kernel.spec
@ -182,7 +182,7 @@ Summary: The Linux kernel
|
||||
# This is needed to do merge window version magic
|
||||
%define patchlevel 12
|
||||
# This allows pkg_release to have configurable %%{?dist} tag
|
||||
%define specrelease 211.30.1%{?buildid}%{?dist}
|
||||
%define specrelease 211.30.2%{?buildid}%{?dist}
|
||||
# This defines the kabi tarball version
|
||||
%define kabiversion 6.12.0-211.7.1.el10_2
|
||||
|
||||
@ -1449,6 +1449,8 @@ Patch1411: 1411-arm64-errata-mitigate-tlbi-errata-on-various-arm-cpus.patch
|
||||
Patch1412: 1412-arm64-errata-mitigate-tlbi-errata-on-nvidia-olympus-cpu.patch
|
||||
Patch1413: 1413-arm64-errata-mitigate-tlbi-errata-on-microsoft-azure-cobalt.patch
|
||||
Patch1414: 1414-fs-smb-client-fix-out-of-bounds-read-in-cifs-sanitize-prepa.patch
|
||||
Patch1415: 1415-kvm-x86-fix-shadow-paging-use-after-free-due-to-unexpected-gfn.patch
|
||||
Patch1416: 1416-kvm-x86-fix-shadow-paging-use-after-free-due-to-unexpected-role.patch
|
||||
# END OF PATCH DEFINITIONS
|
||||
|
||||
%description
|
||||
@ -2620,6 +2622,8 @@ ApplyPatch 1411-arm64-errata-mitigate-tlbi-errata-on-various-arm-cpus.patch
|
||||
ApplyPatch 1412-arm64-errata-mitigate-tlbi-errata-on-nvidia-olympus-cpu.patch
|
||||
ApplyPatch 1413-arm64-errata-mitigate-tlbi-errata-on-microsoft-azure-cobalt.patch
|
||||
ApplyPatch 1414-fs-smb-client-fix-out-of-bounds-read-in-cifs-sanitize-prepa.patch
|
||||
ApplyPatch 1415-kvm-x86-fix-shadow-paging-use-after-free-due-to-unexpected-gfn.patch
|
||||
ApplyPatch 1416-kvm-x86-fix-shadow-paging-use-after-free-due-to-unexpected-role.patch
|
||||
# END OF PATCH APPLICATIONS
|
||||
|
||||
# Any further pre-build tree manipulations happen here.
|
||||
@ -5124,6 +5128,10 @@ fi\
|
||||
#
|
||||
#
|
||||
%changelog
|
||||
* Fri Jul 03 2026 Andrew Lukoshko <alukoshko@almalinux.org> - 6.12.0-211.30.2
|
||||
- Fix KVM x86 shadow paging use-after-free due to unexpected GFN ahead of RHEL (1415)
|
||||
- Fix KVM x86 shadow paging use-after-free due to unexpected role ahead of RHEL (1416)
|
||||
|
||||
* Fri Jul 03 2026 Andrew Lukoshko <alukoshko@almalinux.org> - 6.12.0-211.30.1
|
||||
- Recreate RHEL 6.12.0-211.30.1 from CentOS Stream 10 and upstream stable backports (1399-1414)
|
||||
- RHEL 6.12.0-211.30.1 now also carries the vgic-its (CVE-2026-46316) and ipv4/ipv6
|
||||
|
||||
Loading…
Reference in New Issue
Block a user