Re-add AlmaLinux ahead-of-RHEL security fixes (CVE-2026-46113 KVM + rtmutex) on 553.141.1; bump to 553.141.2
This commit is contained in:
parent
2fb771f048
commit
55597d84e3
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
|
||||
134
SOURCES/1101-kvm-x86-mmu-ensure-hugepage-is-in-by-slot.patch
Normal file
134
SOURCES/1101-kvm-x86-mmu-ensure-hugepage-is-in-by-slot.patch
Normal file
@ -0,0 +1,134 @@
|
||||
From: Paolo Bonzini <pbonzini@redhat.com>
|
||||
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 <seanjc@google.com>
|
||||
|
||||
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:
|
||||
<TASK>
|
||||
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
|
||||
</TASK>
|
||||
|
||||
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 <dmatlack@google.com>
|
||||
Cc: James Houghton <jthoughton@google.com>
|
||||
Cc: Alexander Bulekov <bkov@amazon.com>
|
||||
Cc: Fred Griffoul <fgriffo@amazon.co.uk>
|
||||
Cc: Alexander Graf <graf@amazon.de>
|
||||
Cc: David Woodhouse <dwmw@amazon.co.uk>
|
||||
Cc: Filippo Sironi <sironi@amazon.de>
|
||||
Cc: Ivan Orlov <iorlov@amazon.co.uk>
|
||||
Signed-off-by: Sean Christopherson <seanjc@google.com>
|
||||
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
|
||||
---
|
||||
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
|
||||
|
||||
86
SOURCES/1102-rtmutex-use-waiter-task-in-remove-waiter.patch
Normal file
86
SOURCES/1102-rtmutex-use-waiter-task-in-remove-waiter.patch
Normal file
@ -0,0 +1,86 @@
|
||||
From 3bfdc63936dd4773109b7b8c280c0f3b5ae7d349 Mon Sep 17 00:00:00 2001
|
||||
From: Keenan Dong <keenanat2000@gmail.com>
|
||||
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 <yuantan098@gmail.com>
|
||||
Reported-by: Yifan Wu <yifanwucs@gmail.com>
|
||||
Reported-by: Juefei Pu <tomapufckgml@gmail.com>
|
||||
Reported-by: Xin Liu <bird@lzu.edu.cn>
|
||||
Signed-off-by: Keenan Dong <keenanat2000@gmail.com>
|
||||
Signed-off-by: Thomas Gleixner <tglx@kernel.org>
|
||||
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
|
||||
@ -0,0 +1,71 @@
|
||||
From 40a25d59e85b3c8709ac2424d44f65610467871e Mon Sep 17 00:00:00 2001
|
||||
From: Davidlohr Bueso <dave@stgolabs.net>
|
||||
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 <dave@stgolabs.net>
|
||||
Signed-off-by: Thomas Gleixner <tglx@kernel.org>
|
||||
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
|
||||
@ -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 <alukoshko@almalinux.org> - 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 <alukoshko@almalinux.org> - 4.18.0-553.141.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