From 55597d84e37be32f0aa3b4ed0572ddf069e358fa Mon Sep 17 00:00:00 2001 From: Andrew Lukoshko Date: Wed, 8 Jul 2026 13:58:53 +0000 Subject: [PATCH] Re-add AlmaLinux ahead-of-RHEL security fixes (CVE-2026-46113 KVM + rtmutex) on 553.141.1; bump to 553.141.2 --- ...x86-fix-shadow-paging-use-after-free.patch | 253 ++++++++++++++++++ ...86-mmu-ensure-hugepage-is-in-by-slot.patch | 134 ++++++++++ ...tex-use-waiter-task-in-remove-waiter.patch | 86 ++++++ ...skip-remove-waiter-when-not-enqueued.patch | 71 +++++ SPECS/kernel.spec | 26 +- 5 files changed, 564 insertions(+), 6 deletions(-) create mode 100644 SOURCES/1100-kvm-x86-fix-shadow-paging-use-after-free.patch create mode 100644 SOURCES/1101-kvm-x86-mmu-ensure-hugepage-is-in-by-slot.patch create mode 100644 SOURCES/1102-rtmutex-use-waiter-task-in-remove-waiter.patch create mode 100644 SOURCES/1104-rtmutex-skip-remove-waiter-when-not-enqueued.patch 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/SOURCES/1101-kvm-x86-mmu-ensure-hugepage-is-in-by-slot.patch b/SOURCES/1101-kvm-x86-mmu-ensure-hugepage-is-in-by-slot.patch new file mode 100644 index 000000000..b012af26b --- /dev/null +++ b/SOURCES/1101-kvm-x86-mmu-ensure-hugepage-is-in-by-slot.patch @@ -0,0 +1,134 @@ +From: Paolo Bonzini +Date: Fri, 26 Jun 2026 19:46:19 +0200 +Subject: KVM: x86/mmu: Ensure hugepage is in by slot before checking max mapping level + +From: Sean Christopherson + +commit ef057cbf825e03b63f6edf5980f96abf3c53089d upstream. + +When recovering hugepages in the shadow MMU, verify that the base gfn of +the shadow page is actually contained within the target memslot, *before* +querying the max mapping level given the shadow page's gfn. Failure to +pre-check the validity of the gfn can lead to an out-of-bounds access to +the slot's lpage_info (which typically manifests as a host #PF because the +lpage_info is vmalloc'd) if the guest creates a hugepage mapping (in its +PTEs) that extends "below" the bounds of a memslot. + +When faulting in memory for a guest, and the size of the guest mapping is +greater than KVM's (current) max mapping, then KVM will create a "direct" +shadow page (direct in that there are no gPTEs to shadow, and so the target +gfn is a direct calculation given the base gfn of the shadow page). The +hugepage recovery flow looks for such direct shadow pages, as forcing 4KiB +mappings when dirty logging generates the guest > host mapping size case. +When the 4KiB restriction is lifted, then KVM can replace the shadow page +with a hugepage. + +But if KVM originally used a smaller mapping than the guest because the +range of memory covered by the guest hugepage exceeds the bounds of a +memslot, then KVM will link a direct shadow page with a gfn that is outside +the bounds of the memslot being used to fault in memory. The rmap entry +added for the leaf mapping is correct and within bounds, but the gfn of the +leaf SPTE's parent shadow page will be out of bounds. + + BUG: unable to handle page fault for address: ffffc90000806ffc + #PF: supervisor read access in kernel mode + #PF: error_code(0x0000) - not-present page + PGD 100000067 P4D 100000067 PUD 1002a7067 PMD 10612f067 PTE 0 + Oops: Oops: 0000 [#1] SMP + CPU: 13 UID: 1000 PID: 757 Comm: mmu_stress_test Not tainted 7.1.0-rc1-48ce1e26eace-x86_pir_to_irr_comments-vm #341 PREEMPT + Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 0.0.0 02/06/2015 + RIP: 0010:kvm_mmu_max_mapping_level+0x79/0x2b0 [kvm] + Call Trace: + + kvm_mmu_recover_huge_pages+0x21b/0x320 [kvm] + kvm_set_memslot+0x1ee/0x590 [kvm] + kvm_set_memory_region.part.0+0x3a1/0x4d0 [kvm] + kvm_vm_ioctl+0x9bf/0x15d0 [kvm] + __x64_sys_ioctl+0x8a/0xd0 + do_syscall_64+0xb7/0xbb0 + entry_SYSCALL_64_after_hwframe+0x4b/0x53 + RIP: 0033:0x7f21c0f1a9bf + + +Don't bother pre-checking the bounds of the potential hugepage, i.e. don't +check that e.g. sp->gfn + KVM_PAGES_PER_HPAGE(sp->role.level + 1) is also +within the memslot, as the checks performed by kvm_mmu_max_mapping_level() +are a superset of the basic bounds checks. I.e. pre-checking the full +range would be a dubious micro-optimization. + +Fixes: 9eba50f8d7fc ("KVM: x86/mmu: Consult max mapping level when zapping collapsible SPTEs") +Cc: stable@vger.kernel.org +Cc: David Matlack +Cc: James Houghton +Cc: Alexander Bulekov +Cc: Fred Griffoul +Cc: Alexander Graf +Cc: David Woodhouse +Cc: Filippo Sironi +Cc: Ivan Orlov +Signed-off-by: Sean Christopherson +Signed-off-by: Paolo Bonzini +--- + arch/x86/kvm/mmu/mmu.c | 19 +++++++++++++------ + include/linux/kvm_host.h | 7 ++++++- + 2 files changed, 19 insertions(+), 7 deletions(-) + +diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c +index 9597112..12d3e1f 100644 +--- a/arch/x86/kvm/mmu/mmu.c ++++ b/arch/x86/kvm/mmu/mmu.c +@@ -6045,13 +6045,20 @@ restart: + pfn = spte_to_pfn(*sptep); + + /* +- * We cannot do huge page mapping for indirect shadow pages, +- * which are found on the last rmap (level = 1) when not using +- * tdp; such shadow pages are synced with the page table in +- * the guest, and the guest page table is using 4K page size +- * mapping if the indirect sp has level = 1. ++ * Direct shadow page can be replaced by a hugepage if the host ++ * mapping level allows it and the memslot maps all of the host ++ * hugepage. Note! If the memslot maps only part of the ++ * hugepage, sp->gfn may be below slot->base_gfn, and querying ++ * the max mapping level would cause an out-of-bounds lpage_info ++ * access. So the gfn bounds check *must* be done first. ++ * ++ * Indirect shadow pages are created when the guest page tables ++ * are using 4K pages. Since the host mapping is always ++ * constrained by the page size in the guest, indirect shadow ++ * pages are never collapsible. + */ +- if (sp->role.direct && !kvm_is_reserved_pfn(pfn) && ++ if (sp->role.direct && is_gfn_in_memslot(slot, sp->gfn) && ++ !kvm_is_reserved_pfn(pfn) && + sp->role.level < kvm_mmu_max_mapping_level(kvm, slot, sp->gfn, + pfn, PG_LEVEL_NUM)) { + pte_list_remove(kvm, rmap_head, sptep); +diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h +index eaebea6..c37570a 100644 +--- a/include/linux/kvm_host.h ++++ b/include/linux/kvm_host.h +@@ -1378,6 +1378,11 @@ static inline struct rcuwait *kvm_arch_vcpu_get_wait(struct kvm_vcpu *vcpu) + #endif + } + ++static inline bool is_gfn_in_memslot(const struct kvm_memory_slot *slot, gfn_t gfn) ++{ ++ return gfn >= slot->base_gfn && gfn < slot->base_gfn + slot->npages; ++} ++ + /* + * Wake a vCPU if necessary, but don't do any stats/metadata updates. Returns + * true if the vCPU was blocking and was awakened, false otherwise. +@@ -1464,7 +1469,7 @@ try_get_memslot(struct kvm_memory_slot *slot, gfn_t gfn) + if (!slot) + return NULL; + +- if (gfn >= slot->base_gfn && gfn < slot->base_gfn + slot->npages) ++ if (is_gfn_in_memslot(slot, gfn)) + return slot; + else + return NULL; +-- +2.43.0 + diff --git a/SOURCES/1102-rtmutex-use-waiter-task-in-remove-waiter.patch b/SOURCES/1102-rtmutex-use-waiter-task-in-remove-waiter.patch new file mode 100644 index 000000000..61ab36220 --- /dev/null +++ b/SOURCES/1102-rtmutex-use-waiter-task-in-remove-waiter.patch @@ -0,0 +1,86 @@ +From 3bfdc63936dd4773109b7b8c280c0f3b5ae7d349 Mon Sep 17 00:00:00 2001 +From: Keenan Dong +Date: Wed, 8 Apr 2026 16:46:00 +0800 +Subject: [PATCH] rtmutex: Use waiter::task instead of current in + remove_waiter() + +remove_waiter() is used by the slowlock paths, but it is also used for +proxy-lock rollback in rt_mutex_start_proxy_lock() when invoked from +futex_requeue(). + +In the latter case waiter::task is not current, but remove_waiter() +operates on current for the dequeue operation. That results in several +problems: + + 1) the rbtree dequeue happens without waiter::task::pi_lock being held + + 2) the waiter task's pi_blocked_on state is not cleared, which leaves a + dangling pointer primed for UAF around. + + 3) rt_mutex_adjust_prio_chain() operates on the wrong top priority waiter + task + +Use waiter::task instead of current in all related operations in +remove_waiter() to cure those problems. + +[ tglx: Fixup rt_mutex_adjust_prio_chain(), add a comment and amend the + changelog ] + +Fixes: 8161239a8bcc ("rtmutex: Simplify PI algorithm and make highest prio task get lock") +Reported-by: Yuan Tan +Reported-by: Yifan Wu +Reported-by: Juefei Pu +Reported-by: Xin Liu +Signed-off-by: Keenan Dong +Signed-off-by: Thomas Gleixner +Cc: stable@vger.kernel.org + +[ AlmaLinux 8 (4.18.0-553): backport of upstream 3bfdc63936dd. This el8 + tree already carries the modern rtmutex rework (struct rt_mutex_base). + The kernel is built with -std=gnu89, so the upstream scoped_guard( + raw_spinlock, ...) form does not compile (C99 for-init decl); the plain + raw_spin_lock()/raw_spin_unlock() pair on waiter_task->pi_lock is used + instead, achieving the same end state. ] + +diff --git a/kernel/locking/rtmutex.c b/kernel/locking/rtmutex.c +index 95db3bb..cb855e3 100644 +--- a/kernel/locking/rtmutex.c ++++ b/kernel/locking/rtmutex.c +@@ -1501,20 +1501,23 @@ static bool rtmutex_spin_on_owner(struct rt_mutex_base *lock, + * + * Must be called with lock->wait_lock held and interrupts disabled. It must + * have just failed to try_to_take_rt_mutex(). ++ * ++ * When invoked from rt_mutex_start_proxy_lock() waiter::task != current ! + */ + static void __sched remove_waiter(struct rt_mutex_base *lock, + struct rt_mutex_waiter *waiter) + { + bool is_top_waiter = (waiter == rt_mutex_top_waiter(lock)); + struct task_struct *owner = rt_mutex_owner(lock); ++ struct task_struct *waiter_task = waiter->task; + struct rt_mutex_base *next_lock; + + lockdep_assert_held(&lock->wait_lock); + +- raw_spin_lock(¤t->pi_lock); ++ raw_spin_lock(&waiter_task->pi_lock); + rt_mutex_dequeue(lock, waiter); +- current->pi_blocked_on = NULL; +- raw_spin_unlock(¤t->pi_lock); ++ waiter_task->pi_blocked_on = NULL; ++ raw_spin_unlock(&waiter_task->pi_lock); + + /* + * Only update priority if the waiter was the highest priority +@@ -1550,7 +1553,7 @@ static void __sched remove_waiter(struct rt_mutex_base *lock, + raw_spin_unlock_irq(&lock->wait_lock); + + rt_mutex_adjust_prio_chain(owner, RT_MUTEX_MIN_CHAINWALK, lock, +- next_lock, NULL, current); ++ next_lock, NULL, waiter_task); + + raw_spin_lock_irq(&lock->wait_lock); + } +-- +2.50.1 diff --git a/SOURCES/1104-rtmutex-skip-remove-waiter-when-not-enqueued.patch b/SOURCES/1104-rtmutex-skip-remove-waiter-when-not-enqueued.patch new file mode 100644 index 000000000..a64dd105d --- /dev/null +++ b/SOURCES/1104-rtmutex-skip-remove-waiter-when-not-enqueued.patch @@ -0,0 +1,71 @@ +From 40a25d59e85b3c8709ac2424d44f65610467871e Mon Sep 17 00:00:00 2001 +From: Davidlohr Bueso +Date: Thu, 7 May 2026 04:29:13 -0700 +Subject: [PATCH] locking/rtmutex: Skip remove_waiter() when waiter is not + enqueued + +syzbot triggered the following splat in remove_waiter() via +FUTEX_CMP_REQUEUE_PI: + + KASAN: null-ptr-deref in range [0x0000000000000a88-0x0000000000000a8f] + class_raw_spinlock_constructor + remove_waiter+0x159/0x1200 kernel/locking/rtmutex.c:1561 + rt_mutex_start_proxy_lock+0x103/0x120 + futex_requeue+0x10e4/0x20d0 + __x64_sys_futex+0x34f/0x4d0 + +task_blocks_on_rt_mutex() does not arm the waiter upon deadlock detection, +leaving waiter->task nil, where 3bfdc63936dd ("rtmutex: Use waiter::task instead +of current in remove_waiter()") made this fatal. + +Furthermore, rt_mutex_start_proxy_lock() should not be calling into remove_waiter() +upon a successfully grabbing the rtmutex. 1a1fb985f2e2 ("futex: Handle early deadlock +return correctly"), moved the remove_waiter() out of __rt_mutex_start_proxy_lock() +(where 'ret' was only ever 0 or < 0) into the wrapper. Tighten this check to +account for try_to_take_rt_mutex(). + +Fixes: 3bfdc63936dd ("rtmutex: Use waiter::task instead of current in remove_waiter()") +Reported-by: syzbot+78147abe6c524f183ee9@syzkaller.appspotmail.com +Signed-off-by: Davidlohr Bueso +Signed-off-by: Thomas Gleixner +Cc: stable@vger.kernel.org +Closes: https://lore.kernel.org/all/69f114ac.050a0220.ac8b.0003.GAE@google.com/ +Link: https://patch.msgid.link/20260507112913.1019537-1-dave@stgolabs.net + +[ AlmaLinux 8 (4.18.0-553): backport of upstream 40a25d59e85b. rt_mutex_ + start_proxy_lock() lives in kernel/locking/rtmutex_api.c on el8 (same as + upstream); the ret<0 fix applies there. This tree already carries + 1a1fb985f2e2 - __rt_mutex_start_proxy_lock() returns 1 on + try_to_take_rt_mutex() success, so the old 'if (unlikely(ret))' wrongly + called remove_waiter() on success; the ret<0 tightening is required. The + local __rt_mutex_start_proxy_lock() call takes 3 args (no &wake_q). ] + +diff --git a/kernel/locking/rtmutex.c b/kernel/locking/rtmutex.c +index cb855e3..469d43d 100644 +--- a/kernel/locking/rtmutex.c ++++ b/kernel/locking/rtmutex.c +@@ -1514,6 +1514,9 @@ static void __sched remove_waiter(struct rt_mutex_base *lock, + + lockdep_assert_held(&lock->wait_lock); + ++ if (!waiter_task) /* never enqueued */ ++ return; ++ + raw_spin_lock(&waiter_task->pi_lock); + rt_mutex_dequeue(lock, waiter); + waiter_task->pi_blocked_on = NULL; +diff --git a/kernel/locking/rtmutex_api.c b/kernel/locking/rtmutex_api.c +index 56d1938..c4e1913 100644 +--- a/kernel/locking/rtmutex_api.c ++++ b/kernel/locking/rtmutex_api.c +@@ -322,7 +322,7 @@ int __sched rt_mutex_start_proxy_lock(struct rt_mutex_base *lock, + + raw_spin_lock_irq(&lock->wait_lock); + ret = __rt_mutex_start_proxy_lock(lock, waiter, task); +- if (unlikely(ret)) ++ if (unlikely(ret < 0)) + remove_waiter(lock, waiter); + raw_spin_unlock_irq(&lock->wait_lock); + +-- +2.50.1 diff --git a/SPECS/kernel.spec b/SPECS/kernel.spec index 0320e8f9d..0e30baab7 100644 --- a/SPECS/kernel.spec +++ b/SPECS/kernel.spec @@ -49,10 +49,11 @@ # define buildid .local %define specversion 4.18.0 -%define pkgrelease 553.141.1.el8_10 +%define pkgrelease 553.141.2.el8_10 +%define tarfile_release 553.141.1.el8_10 # allow pkg_release to have configurable %%{?dist} tag -%define specrelease 553.141.1%{?dist} +%define specrelease 553.141.2%{?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 @@ -550,6 +551,10 @@ Patch2005: 0005-Bring-back-deprecated-pci-ids-to-qla2xxx-driver.patch 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 +Patch1100: 1100-kvm-x86-fix-shadow-paging-use-after-free.patch +Patch1101: 1101-kvm-x86-mmu-ensure-hugepage-is-in-by-slot.patch +Patch1102: 1102-rtmutex-use-waiter-task-in-remove-waiter.patch +Patch1104: 1104-rtmutex-skip-remove-waiter-when-not-enqueued.patch # END OF PATCH DEFINITIONS @@ -1108,9 +1113,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} @@ -1127,6 +1132,10 @@ ApplyPatch 0005-Bring-back-deprecated-pci-ids-to-qla2xxx-driver.patch 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 +ApplyPatch 1100-kvm-x86-fix-shadow-paging-use-after-free.patch +ApplyPatch 1101-kvm-x86-mmu-ensure-hugepage-is-in-by-slot.patch +ApplyPatch 1102-rtmutex-use-waiter-task-in-remove-waiter.patch +ApplyPatch 1104-rtmutex-skip-remove-waiter-when-not-enqueued.patch # END OF PATCH APPLICATIONS @@ -2729,6 +2738,11 @@ fi # # %changelog +* Wed Jul 08 2026 Andrei Lukoshko - 4.18.0-553.141.2 +- Re-add AlmaLinux ahead-of-RHEL security fixes dropped by the 553.141.1 re-import: + KVM x86 shadow-paging use-after-free CVE-2026-46113 (1100-1101), and rtmutex + self-deadlock NULL deref in remove_waiter() upstream 3bfdc63936dd + 40a25d59e85b (1102, 1104) + * Tue Jul 07 2026 Andrei Lukoshko - 4.18.0-553.141.1 - hpsa: bring back deprecated PCI ids #CFHack #CFHack2024 - mptsas: bring back deprecated PCI ids #CFHack #CFHack2024