From 5541d897584127b795dbbdc51a78a2348b4eaaa4 Mon Sep 17 00:00:00 2001 From: "Vlastimil Babka (SUSE)" Date: Mon, 31 Aug 2026 18:02:38 +0200 Subject: [PATCH 1/2] mm/slab: disallow kfree_rcu_sheaf() on PREEMPT_RT again This partially reverts commit 2a8bb29ec9b2 ("mm/slab: allow kfree_rcu_sheaf() on PREEMPT_RT"). It was based on the assumption that local_trylock() is safe on PREEMPT_RT from any context. However kvfree_rcu() is also called by set_cpus_allowed_force() with task_struct::pi_lock acquired and there it's not safe, as syzbot has reported. For the immediate fix, skip kfree_rcu_sheaf() on PREEMPT_RT again from kvfree_call_rcu(). In theory, kfree_rcu_nolock() would have the same problem when called from under pi_lock on PREEMPT_RT but that can be addressed if such a caller is proposed. Add an explanation comment, courtesy of Sebastian. Reported-by: syzbot+acf142088e0182172e58@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=acf142088e0182172e58 Reported-by: ThangNN99 Fixes: 2a8bb29ec9b2 ("mm/slab: allow kfree_rcu_sheaf() on PREEMPT_RT") Reviewed-by: Sebastian Andrzej Siewior Link: https://patch.msgid.link/20260831-b4-kfree_rcu_hotfix-v1-1-4f0fb882638b@kernel.org Reviewed-by: Harry Yoo (Meta) Signed-off-by: Vlastimil Babka (SUSE) --- mm/slab_common.c | 18 ++++++++---------- mm/slub.c | 5 +++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/mm/slab_common.c b/mm/slab_common.c index b19ba1b31484..7223a7596dab 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -1667,14 +1667,6 @@ static bool kfree_rcu_sheaf(void *obj) { struct kmem_cache *s; struct slab *slab; - unsigned int free_flags = SLAB_FREE_DEFAULT; - - /* - * It is not safe to spin on PREEMPT_RT because the kernel might be - * holding a raw spinlock and slab acquires sleeping locks. - */ - if (IS_ENABLED(CONFIG_PREEMPT_RT)) - free_flags = SLAB_FREE_NOLOCK; if (is_vmalloc_addr(obj)) return false; @@ -1685,7 +1677,7 @@ static bool kfree_rcu_sheaf(void *obj) s = slab->slab_cache; if (likely(!IS_ENABLED(CONFIG_NUMA) || slab_nid(slab) == numa_mem_id())) - return __kfree_rcu_sheaf(s, obj, free_flags); + return __kfree_rcu_sheaf(s, obj, SLAB_FREE_DEFAULT); return false; } @@ -2034,7 +2026,13 @@ void kvfree_call_rcu(struct kvfree_rcu_head *head, void *ptr) if (!head) might_sleep(); - if (kfree_rcu_sheaf(ptr)) + /* + * kvfree_rcu() is called by set_cpus_allowed_force() with + * task_struct::pi_lock acquired. On PREEMPT_RT the local_trylock() + * usage below will acquire the waitlock which must be avoided. + * Therefore avoid it on PREEMPT_RT. + */ + if (!IS_ENABLED(CONFIG_PREEMPT_RT) && kfree_rcu_sheaf(ptr)) return; // Queue the object but don't yet schedule the batch. diff --git a/mm/slub.c b/mm/slub.c index f9b56cb439e7..7a7e906a0e44 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -6088,8 +6088,9 @@ static void rcu_free_sheaf(struct rcu_head *head) /* * kvfree_call_rcu() can be called while holding a raw_spinlock_t. Since * __kfree_rcu_sheaf() may acquire a spinlock_t (sleeping lock on PREEMPT_RT), - * this would violate lock nesting rules. Therefore, kvfree_call_rcu() avoids - * this problem by passing SLAB_FREE_NOLOCK on PREEMPT_RT. + * this would violate lock nesting rules. Therefore, kfree_call_rcu_nolock() + * avoids this problem by passing SLAB_FREE_NOLOCK. kvfree_call_rcu() is + * bypassing the sheaves layer completely on PREEMPT_RT. * * However, lockdep still complains that it is invalid to acquire spinlock_t * while holding raw_spinlock_t, even on !PREEMPT_RT where spinlock_t is a From 4a724bcf5d703e18957397914d79156fa2cf1174 Mon Sep 17 00:00:00 2001 From: "Harry Yoo (Meta)" Date: Thu, 3 Sep 2026 15:32:26 +0100 Subject: [PATCH 2/2] mm/slab: take n->list_lock in __slab_try_return_freelist() to avoid race Commit ba7425312607 ("mm, slab: add an optimistic __slab_try_return_freelist()") incorrectly assumed that nobody has freed an object to the slab as long as slab->freelist is NULL and cmpxchg succeeds. However, as reported by Hyunwoo Kim [1], other CPUs might have freed an object to the slab, insert the slab to the partial list, then allocated an object from the slab, and be in the middle of removing the slab from the list under n->list_lock. Since __refill_objects_node() puts the slab back on pc.slabs outside n->list_lock, it might insert the slab into that list while the slab is concurrently being removed from n->partial. This led to a list corruption [1]: list_add corruption. next->prev should be prev (ffff888100000248), but was dead000000000122. (next=ffffea000416e410). kernel BUG at lib/list_debug.c:29! Oops: invalid opcode: 0000 [#1] SMP NOPTI CPU: 1 UID: 65534 PID: 144 Comm: poc Not tainted 7.2.0-16172-gcf72cbb39da8-dirty #1 PREEMPT(lazy) RIP: 0010:__list_add_valid_or_report+0x80/0xd0 ... Call Trace: alloc_from_new_slab+0x183/0x300 ___slab_alloc+0x31c/0x890 __kmalloc_noprof+0x3d4/0x800 lsm_blob_alloc+0x2d/0x50 security_msg_msg_alloc+0x26/0x90 load_msg+0x1aa/0x210 do_msgsnd+0x91/0x800 do_syscall_64+0x109/0x5d0 entry_SYSCALL_64_after_hwframe+0x77/0x7f ... Kernel panic - not syncing: Fatal exception This is a classic ABA problem where cmpxchg succeeds but the state has changed since __refill_objects_node() took the freelist from the slab. As Vlastimil Babka mentioned [2], it should be rare to return more than one slab (due to the racy read of slab->counters in get_partial_node_bulk()). Therefore, instead of introducing additional complexity, acquire and release n->list_lock twice in the worst case. Return the slab directly to the partial list and hold n->list_lock across the cmpxchg and add_partial(). This is similar to the initial version of commit ba7425312607 [3]. This is enough to avoid the race as the list manipulation is serialized by n->list_lock. While at it, bring back unlikely() hint now that the condition is unlikely. Reported-by: Hyunwoo Kim Closes: https://lore.kernel.org/linux-mm/apPa-cGLcyt90l-E@v4bel [1] Link: https://lore.kernel.org/linux-mm/ae25c193-b95f-40c1-83b6-1c2546467e41@kernel.org [2] Link: https://lore.kernel.org/all/20260421-b4-refill-optimistic-return-v1-1-24f0bfc1acff@kernel.org [3] Fixes: ba7425312607 ("mm, slab: add an optimistic __slab_try_return_freelist()") Cc: stable@vger.kernel.org Signed-off-by: Harry Yoo (Meta) Link: https://patch.msgid.link/20260903-slab-fix-aba-v3-1-b44cb6badd54@kernel.org Reviewed-by: Hao Li Signed-off-by: Vlastimil Babka (SUSE) --- mm/slub.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/mm/slub.c b/mm/slub.c index 7a7e906a0e44..54ec12503357 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -5680,10 +5680,12 @@ static noinline void free_to_partial_list( * * Fail if the slab isn't full anymore due to a concurrent free. */ -static bool __slab_try_return_freelist(struct kmem_cache *s, struct slab *slab, - void *head, int cnt) +static bool __slab_try_return_freelist(struct kmem_cache *s, + struct kmem_cache_node *n, + struct slab *slab, void *head, int cnt) { struct freelist_counters old, new; + unsigned long flags; old.freelist = slab->freelist; old.counters = slab->counters; @@ -5695,9 +5697,15 @@ static bool __slab_try_return_freelist(struct kmem_cache *s, struct slab *slab, new.counters = old.counters; new.inuse -= cnt; - if (!slab_update_freelist(s, slab, &old, &new, "__slab_try_return_freelist")) - return false; + spin_lock_irqsave(&n->list_lock, flags); + if (!slab_update_freelist(s, slab, &old, &new, "__slab_try_return_freelist")) { + spin_unlock_irqrestore(&n->list_lock, flags); + return false; + } + + add_partial(n, slab, ADD_TO_TAIL); + spin_unlock_irqrestore(&n->list_lock, flags); return true; } @@ -7297,10 +7305,8 @@ __refill_objects_node(struct kmem_cache *s, void **p, gfp_t gfp, unsigned int mi void *head = object; void *tail; - if (__slab_try_return_freelist(s, slab, head, count)) { - list_add(&slab->slab_list, &pc.slabs); + if (__slab_try_return_freelist(s, n, slab, head, count)) break; - } do { tail = object; @@ -7313,7 +7319,7 @@ __refill_objects_node(struct kmem_cache *s, void **p, gfp_t gfp, unsigned int mi break; } - if (!list_empty(&pc.slabs)) { + if (unlikely(!list_empty(&pc.slabs))) { spin_lock_irqsave(&n->list_lock, flags); list_for_each_entry(slab, &pc.slabs, slab_list)