Add fix for CVE-2026-46113 (KVM x86 shadow paging UAF) ahead of RHEL; bump to 553.139.3
This commit is contained in:
parent
932faf1f47
commit
9787fb255b
253
SOURCES/1100-kvm-x86-fix-shadow-paging-use-after-free.patch
Normal file
253
SOURCES/1100-kvm-x86-fix-shadow-paging-use-after-free.patch
Normal file
@ -0,0 +1,253 @@
|
||||
kpatch-cve: CVE-2026-46113
|
||||
kpatch-cve-url: https://access.redhat.com/security/cve/CVE-2026-46113
|
||||
kpatch-cvss: 8.8
|
||||
kpatch-description: KVM: x86: Fix shadow paging use-after-free due to unexpected GFN/role
|
||||
kpatch-kernel: 4.18.0-553.139.1.el8_10
|
||||
kpatch-patch-url: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0cb2af2ea66ad8ff195c156ea690f11216285bdf
|
||||
|
||||
From: KernelCare Security Team
|
||||
Subject: [PATCH] KVM: x86: Fix shadow paging use-after-free due to unexpected GFN/role
|
||||
|
||||
CVE: CVE-2026-46113
|
||||
|
||||
Upstream Status: manually adapted, not a literal cherry-pick. This
|
||||
combines the effect of two upstream fixes:
|
||||
- commit 0cb2af2ea66a ("KVM: x86: Fix shadow paging use-after-free
|
||||
due to unexpected GFN"), which is the fix CVE-2026-46113 refers to
|
||||
- commit 81ccda30b4e8 ("KVM: x86: Fix shadow paging use-after-free
|
||||
due to unexpected role"), a follow-up closing a second, related
|
||||
hole in the same code path (found while hardening it after the
|
||||
first fix)
|
||||
|
||||
Neither commit applies to this kernel: both are written against
|
||||
upstream's post-refactor MMU (kvm_mmu_get_child_sp()/
|
||||
kvm_mmu_get_shadow_page()/__link_shadow_page(), introduced by a large
|
||||
prerequisite series -- e.g. "KVM: x86/mmu: Derive shadow MMU page role
|
||||
from parent" and "KVM: x86/mmu: pull call to drop_large_spte() into
|
||||
__link_shadow_page()" -- that this 4.18-based kernel does not carry).
|
||||
This kernel still uses the older, pre-refactor shape: each of the
|
||||
three "walk into an existing non-leaf SPTE or install a new one" call
|
||||
sites (kvm_mmu_get_page()+link_shadow_page() in __direct_map() and in
|
||||
both loops of FNAME(fetch)) independently does:
|
||||
|
||||
drop_large_spte(vcpu, it.sptep);
|
||||
if (!is_shadow_present_pte(*it.sptep)) {
|
||||
sp = kvm_mmu_get_page(...);
|
||||
link_shadow_page(vcpu, it.sptep, sp);
|
||||
}
|
||||
|
||||
i.e. if *it.sptep is already present (and not a large leaf,
|
||||
already handled by drop_large_spte()), the walk just continues into
|
||||
whatever child it already points to, without checking that the child
|
||||
actually matches the gfn/role being walked to. This is exactly the
|
||||
root cause both upstream commits describe: if the guest's page tables
|
||||
are modified between VM entries (shadow paging) such that a PDE now
|
||||
resolves to a different gfn, or to a translation requiring a
|
||||
differently-shaped shadow page (e.g. a 2MB direct-mapped leaf split
|
||||
into a 4KB indirect page table), the stale child is reused as-is.
|
||||
When that stale child is later zapped, kvm_mmu_page_get_gfn() derives
|
||||
the wrong gfn for its rmap entries (sp->gfn + index, or sp->gfn
|
||||
itself, instead of the actual mapped gfn), so rmap_remove() cannot
|
||||
find and remove them. If the memslot backing the old translation is
|
||||
then dropped, the shadow page is freed while the stale rmap entry
|
||||
survives; any later rmap walk over that gfn (dirty logging, MMU
|
||||
notifier invalidation such as MADV_DONTNEED, ...) dereferences freed
|
||||
memory.
|
||||
|
||||
Fix this at each of the three call sites by validating, before
|
||||
falling through to "reuse the existing child", that the present
|
||||
non-large SPTE actually points to a child with the expected gfn *and*
|
||||
role (mirroring 81ccda30b4e8's role check on top of 0cb2af2ea66a's gfn
|
||||
check). If it doesn't, the stale SPTE (and its subtree) is torn down
|
||||
via mmu_page_zap_pte() + kvm_mmu_remote_flush_or_zap() -- the same
|
||||
primitives upstream's __link_shadow_page() now uses -- before
|
||||
installing the correct child. This preserves the existing fast path
|
||||
(no hash-table lookup) for the common case where the already-linked
|
||||
child is correct.
|
||||
|
||||
The role/gfn comparison itself is factored into two small helpers,
|
||||
kvm_mmu_child_role() (the role computation already done at the top of
|
||||
kvm_mmu_get_page(), extracted unchanged so both functions share it)
|
||||
and shadow_page_role_matches().
|
||||
|
||||
Note on kernel commit a955cad84cda ("KVM: x86/mmu: Retry page fault if
|
||||
root is invalidated by memslot update"): during upstream's stable
|
||||
backport of 0cb2af2ea66a to 5.10.y/5.15.y, this turned out to be a
|
||||
required prerequisite -- without it, testers hit WARN_ON regressions
|
||||
in kvm_mmu_zap_all_fast()/kvm_mmu_zap_all() (see the "stable backports
|
||||
for ..." thread on kvm@vger.kernel.org, message
|
||||
20260630223723.83727-1-zcgao@amazon.com). This kernel already has the
|
||||
equivalent logic (is_page_fault_stale()/is_obsolete_sp(), used in both
|
||||
direct_page_fault() and FNAME(page_fault)), confirmed present before
|
||||
writing this patch, so no separate prerequisite patch is needed here.
|
||||
|
||||
Reported-by: Hyunwoo Kim <imv4bel@gmail.com>
|
||||
Reported-by: Alexander Bulekov <bkov@amazon.com>
|
||||
Reported-by: Fred Griffoul <fgriffo@amazon.co.uk>
|
||||
---
|
||||
arch/x86/kvm/mmu/mmu.c | 72 ++++++++++++++++++++++++++++++++++-------
|
||||
arch/x86/kvm/mmu/paging_tmpl.h | 40 ++++++++++++++++++++--
|
||||
2 files changed, 96 insertions(+), 16 deletions(-)
|
||||
|
||||
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
|
||||
index c4854dc02..696c3b4c6 100644
|
||||
--- a/arch/x86/kvm/mmu/mmu.c
|
||||
+++ b/arch/x86/kvm/mmu/mmu.c
|
||||
@@ -2002,21 +2002,13 @@ static void clear_sp_write_flooding_count(u64 *spte)
|
||||
__clear_sp_write_flooding_count(sptep_to_sp(spte));
|
||||
}
|
||||
|
||||
-static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu,
|
||||
- gfn_t gfn,
|
||||
- gva_t gaddr,
|
||||
- unsigned level,
|
||||
- int direct,
|
||||
- unsigned int access)
|
||||
+static union kvm_mmu_page_role kvm_mmu_child_role(struct kvm_vcpu *vcpu,
|
||||
+ gva_t gaddr, unsigned level,
|
||||
+ int direct,
|
||||
+ unsigned int access)
|
||||
{
|
||||
- bool direct_mmu = vcpu->arch.mmu->direct_map;
|
||||
union kvm_mmu_page_role role;
|
||||
- struct hlist_head *sp_list;
|
||||
unsigned quadrant;
|
||||
- struct kvm_mmu_page *sp;
|
||||
- int ret;
|
||||
- int collisions = 0;
|
||||
- LIST_HEAD(invalid_list);
|
||||
|
||||
role = vcpu->arch.mmu->mmu_role.base;
|
||||
role.level = level;
|
||||
@@ -2028,6 +2020,46 @@ static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu,
|
||||
role.quadrant = quadrant;
|
||||
}
|
||||
|
||||
+ return role;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Returns true if @sptep already links to a present, non-large shadow page
|
||||
+ * that matches @gfn and @role, i.e. it is safe to keep walking through the
|
||||
+ * existing child instead of replacing it. The gfn of a direct shadow page
|
||||
+ * only tracks its *first* mapping, so an intervening guest PTE change can
|
||||
+ * leave a present SPTE pointing at a child that was allocated for a
|
||||
+ * different gfn and/or role; blindly reusing it leads to a stale rmap entry
|
||||
+ * (and eventually a use-after-free) once the mismatched child is zapped.
|
||||
+ */
|
||||
+static bool shadow_page_role_matches(u64 *sptep, gfn_t gfn,
|
||||
+ union kvm_mmu_page_role role)
|
||||
+{
|
||||
+ struct kvm_mmu_page *child;
|
||||
+
|
||||
+ if (!is_shadow_present_pte(*sptep) || is_large_pte(*sptep))
|
||||
+ return false;
|
||||
+
|
||||
+ child = to_shadow_page(*sptep & PT64_BASE_ADDR_MASK);
|
||||
+ return child->gfn == gfn && child->role.word == role.word;
|
||||
+}
|
||||
+
|
||||
+static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu,
|
||||
+ gfn_t gfn,
|
||||
+ gva_t gaddr,
|
||||
+ unsigned level,
|
||||
+ int direct,
|
||||
+ unsigned int access)
|
||||
+{
|
||||
+ bool direct_mmu = vcpu->arch.mmu->direct_map;
|
||||
+ union kvm_mmu_page_role role = kvm_mmu_child_role(vcpu, gaddr, level,
|
||||
+ direct, access);
|
||||
+ struct hlist_head *sp_list;
|
||||
+ struct kvm_mmu_page *sp;
|
||||
+ int ret;
|
||||
+ int collisions = 0;
|
||||
+ LIST_HEAD(invalid_list);
|
||||
+
|
||||
sp_list = &vcpu->kvm->arch.mmu_page_hash[kvm_page_table_hashfn(gfn)];
|
||||
for_each_valid_sp(vcpu->kvm, sp, sp_list) {
|
||||
if (sp->gfn != gfn) {
|
||||
@@ -2946,8 +2978,20 @@ static int __direct_map(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault)
|
||||
break;
|
||||
|
||||
drop_large_spte(vcpu, it.sptep);
|
||||
- if (is_shadow_present_pte(*it.sptep))
|
||||
- continue;
|
||||
+ if (is_shadow_present_pte(*it.sptep)) {
|
||||
+ union kvm_mmu_page_role role;
|
||||
+ LIST_HEAD(invalid_list);
|
||||
+
|
||||
+ role = kvm_mmu_child_role(vcpu, it.addr, it.level - 1,
|
||||
+ true, ACC_ALL);
|
||||
+ if (shadow_page_role_matches(it.sptep, base_gfn, role))
|
||||
+ continue;
|
||||
+
|
||||
+ mmu_page_zap_pte(vcpu->kvm, sptep_to_sp(it.sptep),
|
||||
+ it.sptep, &invalid_list);
|
||||
+ kvm_mmu_remote_flush_or_zap(vcpu->kvm, &invalid_list,
|
||||
+ true);
|
||||
+ }
|
||||
|
||||
sp = kvm_mmu_get_page(vcpu, base_gfn, it.addr,
|
||||
it.level - 1, true, ACC_ALL);
|
||||
diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
|
||||
index 3b825e60a..911dbe698 100644
|
||||
--- a/arch/x86/kvm/mmu/paging_tmpl.h
|
||||
+++ b/arch/x86/kvm/mmu/paging_tmpl.h
|
||||
@@ -676,10 +676,28 @@ static int FNAME(fetch)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault,
|
||||
clear_sp_write_flooding_count(it.sptep);
|
||||
drop_large_spte(vcpu, it.sptep);
|
||||
|
||||
+ table_gfn = gw->table_gfn[it.level - 2];
|
||||
+ access = gw->pt_access[it.level - 2];
|
||||
+
|
||||
+ if (is_shadow_present_pte(*it.sptep)) {
|
||||
+ union kvm_mmu_page_role role;
|
||||
+ LIST_HEAD(invalid_list);
|
||||
+
|
||||
+ role = kvm_mmu_child_role(vcpu, fault->addr,
|
||||
+ it.level - 1, false, access);
|
||||
+ if (!shadow_page_role_matches(it.sptep, table_gfn,
|
||||
+ role)) {
|
||||
+ mmu_page_zap_pte(vcpu->kvm,
|
||||
+ sptep_to_sp(it.sptep),
|
||||
+ it.sptep, &invalid_list);
|
||||
+ kvm_mmu_remote_flush_or_zap(vcpu->kvm,
|
||||
+ &invalid_list,
|
||||
+ true);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
sp = NULL;
|
||||
if (!is_shadow_present_pte(*it.sptep)) {
|
||||
- table_gfn = gw->table_gfn[it.level - 2];
|
||||
- access = gw->pt_access[it.level - 2];
|
||||
sp = kvm_mmu_get_page(vcpu, table_gfn, fault->addr,
|
||||
it.level-1, false, access);
|
||||
/*
|
||||
@@ -736,6 +754,24 @@ static int FNAME(fetch)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault,
|
||||
|
||||
drop_large_spte(vcpu, it.sptep);
|
||||
|
||||
+ if (is_shadow_present_pte(*it.sptep)) {
|
||||
+ union kvm_mmu_page_role role;
|
||||
+ LIST_HEAD(invalid_list);
|
||||
+
|
||||
+ role = kvm_mmu_child_role(vcpu, fault->addr,
|
||||
+ it.level - 1, true,
|
||||
+ direct_access);
|
||||
+ if (!shadow_page_role_matches(it.sptep, base_gfn,
|
||||
+ role)) {
|
||||
+ mmu_page_zap_pte(vcpu->kvm,
|
||||
+ sptep_to_sp(it.sptep),
|
||||
+ it.sptep, &invalid_list);
|
||||
+ kvm_mmu_remote_flush_or_zap(vcpu->kvm,
|
||||
+ &invalid_list,
|
||||
+ true);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
if (!is_shadow_present_pte(*it.sptep)) {
|
||||
sp = kvm_mmu_get_page(vcpu, base_gfn, fault->addr,
|
||||
it.level - 1, true, direct_access);
|
||||
--
|
||||
2.39.2
|
||||
@ -49,10 +49,11 @@
|
||||
# define buildid .local
|
||||
|
||||
%define specversion 4.18.0
|
||||
%define pkgrelease 553.139.1.el8_10
|
||||
%define pkgrelease 553.139.3.el8_10
|
||||
%define tarfile_release 553.139.1.el8_10
|
||||
|
||||
# allow pkg_release to have configurable %%{?dist} tag
|
||||
%define specrelease 553.139.1%{?dist}
|
||||
%define specrelease 553.139.3%{?dist}
|
||||
|
||||
%define pkg_release %{specrelease}%{?buildid}
|
||||
|
||||
@ -446,7 +447,7 @@ BuildRequires: xmlto
|
||||
BuildRequires: asciidoc
|
||||
%endif
|
||||
|
||||
Source0: linux-%{specversion}-%{pkgrelease}.tar.xz
|
||||
Source0: linux-%{specversion}-%{tarfile_release}.tar.xz
|
||||
|
||||
Source9: x509.genkey
|
||||
|
||||
@ -551,6 +552,9 @@ Patch2006: 0006-Bring-back-deprecated-pci-ids-to-lpfc-driver.patch
|
||||
Patch2007: 0007-Bring-back-deprecated-pci-ids-to-qla4xxx-driver.patch
|
||||
Patch2008: 0008-Bring-back-deprecated-pci-ids-to-be2iscsi-driver.patch
|
||||
|
||||
# AlmaLinux ahead-of-RHEL security fixes
|
||||
Patch1100: 1100-kvm-x86-fix-shadow-paging-use-after-free.patch
|
||||
|
||||
# END OF PATCH DEFINITIONS
|
||||
|
||||
BuildRoot: %{_tmppath}/%{name}-%{KVERREL}-root
|
||||
@ -1108,9 +1112,9 @@ ApplyOptionalPatch()
|
||||
fi
|
||||
}
|
||||
|
||||
%setup -q -n %{name}-%{specversion}-%{pkgrelease} -c
|
||||
cp -v %{SOURCE9000} linux-%{specversion}-%{pkgrelease}/certs/rhel.pem
|
||||
mv linux-%{specversion}-%{pkgrelease} linux-%{KVERREL}
|
||||
%setup -q -n %{name}-%{specversion}-%{tarfile_release} -c
|
||||
cp -v %{SOURCE9000} linux-%{specversion}-%{tarfile_release}/certs/rhel.pem
|
||||
mv linux-%{specversion}-%{tarfile_release} linux-%{KVERREL}
|
||||
|
||||
cd linux-%{KVERREL}
|
||||
|
||||
@ -1128,6 +1132,9 @@ ApplyPatch 0006-Bring-back-deprecated-pci-ids-to-lpfc-driver.patch
|
||||
ApplyPatch 0007-Bring-back-deprecated-pci-ids-to-qla4xxx-driver.patch
|
||||
ApplyPatch 0008-Bring-back-deprecated-pci-ids-to-be2iscsi-driver.patch
|
||||
|
||||
# AlmaLinux ahead-of-RHEL security fixes
|
||||
ApplyPatch 1100-kvm-x86-fix-shadow-paging-use-after-free.patch
|
||||
|
||||
# END OF PATCH APPLICATIONS
|
||||
|
||||
# Any further pre-build tree manipulations happen here.
|
||||
@ -2729,6 +2736,16 @@ fi
|
||||
#
|
||||
#
|
||||
%changelog
|
||||
* Thu Jul 02 2026 Andrei Lukoshko <alukoshko@almalinux.org> - 4.18.0-553.139.3
|
||||
- Replace CVE-2026-46113 backport patch series with a single combined
|
||||
adaptation of upstream 0cb2af2ea66a and 81ccda30b4e8 (1100)
|
||||
|
||||
* Thu Jul 02 2026 Andrei Lukoshko <alukoshko@almalinux.org> - 4.18.0-553.139.2
|
||||
- Fix CVE-2026-46113: KVM x86 shadow paging use-after-free due to unexpected
|
||||
GFN, backported ahead of RHEL from the 5.15.y stable series with its
|
||||
prerequisites and the follow-up unexpected-role and hugepage-recovery
|
||||
fixes (1100-1107)
|
||||
|
||||
* Tue Jun 30 2026 Andrei Lukoshko <alukoshko@almalinux.org> - 4.18.0-553.139.1
|
||||
- hpsa: bring back deprecated PCI ids #CFHack #CFHack2024
|
||||
- mptsas: bring back deprecated PCI ids #CFHack #CFHack2024
|
||||
|
||||
Loading…
Reference in New Issue
Block a user