diff --git a/SOURCES/1100-kvm-x86-fix-shadow-paging-use-after-free.patch b/SOURCES/1100-kvm-x86-fix-shadow-paging-use-after-free.patch new file mode 100644 index 000000000..358bd0d97 --- /dev/null +++ b/SOURCES/1100-kvm-x86-fix-shadow-paging-use-after-free.patch @@ -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 +Reported-by: Alexander Bulekov +Reported-by: Fred Griffoul +--- + 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 diff --git a/SPECS/kernel.spec b/SPECS/kernel.spec index f2278ca00..9be1b8dae 100644 --- a/SPECS/kernel.spec +++ b/SPECS/kernel.spec @@ -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 - 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 - 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 - 4.18.0-553.139.1 - hpsa: bring back deprecated PCI ids #CFHack #CFHack2024 - mptsas: bring back deprecated PCI ids #CFHack #CFHack2024