CVE-2026-98818: mm/slab: do not wake up kswapd in __kfree_rcu_sheaf()
In the Linux kernel, the following vulnerability has been resolved:
mm/slab: do not wake up kswapd in kfreercusheaf()
Since kfreercu() can be called under pilock (a raw spinlock in the scheduler), kfreercu() itself should never allocate memory with GFPKSWAPDRECLAIM as waking up kswapd ends up acquiring pilock, which leads to a deadlock.
Reproducing the issue even intentionally was not straightforward. The setcpusallowedforce() path that is called under pilock is exercised very rarely, and competing tasks that allocate memory will most likely wake kswapd up.
Therefore the existence of the deadlock was verified with a modified kernel that has a lockdep map for waking up kswapd:
====================================================== WARNING: possible circular locking dependency detected 7.2.0-rc1-slab-for-next+ #10 Not tainted ------------------------------------------------------ git/6660 is trying to acquire lock: ffff8e524533c550 (&p->pilock){-.-.}-{2:2}, at: rawspinlockirqsave+0x12/0x20
but task is already holding lock: ffff8e57bffff1a0 (&pgdat->kswapdwait){....}-{3:3}, at: rawspinlockirqsave+0x12/0x20
which lock already depends on the new lock.
the existing dependency chain (in reverse order) is:
-> #2 (&pgdat->kswapdwait){....}-{3:3}: lockacquire+0x5a4/0xc50 lockacquire.part.0+0xb7/0x240 lockacquire+0x70/0x170 rawspinlockirqsave+0x44/0x80 rawspinlockirqsave+0x12/0x20 wakeupcommonlock+0x31/0xa0 wakeup+0x20/0x40 kswapdwakeupwake+0x7e/0x110 wakeupkswapd+0x295/0x320 wakeallkswapds+0xac/0x1a0 allocpagesslowpath.constprop.0+0x282/0xf80 allocfrozenpagesnoprof+0x32a/0x360 allocslabpage+0x2e/0x160 allocateslab+0x82/0x420 newslab+0x52/0xb0 refillobjects+0x13d/0x190 refillsheaf+0x5d/0xd0 pcsreplaceemptymain+0x230/0xb10 [...]
-> #1 (kswapdwakeup){-.-.}-{0:0}: lockacquire+0x5a4/0xc50 locksync.part.0+0x75/0x100 locksync+0x36/0x70 mightwakeupkswapd+0x61/0xa0 kfreercusheaf+0x33/0xd90 kvfreecallrcu+0x1d4/0x3b0 setcpusallowedforce+0x163/0x220 cpusetcpusallowedfallback+0x18b/0x240 selectfallbackrq+0x1e6/0x250 [...]
-> #0 (&p->pilock){-.-.}-{2:2}: checkprevadd+0xe6/0xe00 validatechain+0x51e/0x6e0 lockacquire+0x5a4/0xc50 lockacquire.part.0+0xb7/0x240 lockacquire+0x70/0x170 rawspinlockirqsave+0x44/0x80 rawspinlockirqsave+0x12/0x20 trytowakeup+0x77/0xa90 defaultwakefunction+0x27/0x60 autoremovewakefunction+0x23/0xb0 wakeupcommon+0xb8/0x170 wakeupcommonlock+0x51/0xa0 wakeup+0x20/0x40 kswapdwakeupwake+0x7e/0x110 wakeupkswapd+0x295/0x320 wakeallkswapds+0xac/0x1a0 allocpagesslowpath.constprop.0+0x282/0xf80 allocfrozenpagesnoprof+0x32a/0x360 allocslabpage+0x2e/0x160 allocateslab+0x82/0x420 newslab+0x52/0xb0 refillobjects+0x13d/0x190 refillsheaf+0x5d/0xd0 pcsreplaceemptymain+0x230/0xb10 [...]
other info that might help us debug this:
Chain exists of: &p->pilock --> kswapdwakeup --> &pgdat->kswapdwait
Possible unsafe locking scenario:
CPU0 CPU1 ---- ---- lock(&pgdat->kswapdwait); lock(kswapdwakeup); lock(&pgdat->kswapdwait); lock(&p->pilock);
DEADLOCK
3 locks held by git/6660: #0: ffff8e519f0e9e20 (&type->imutexdirkey#6){++++}-{4:4}, at: lookupslow+0x2d/0x60 #1: ffffffff8a31c460 (kswapdwakeup){-.-.}- ---truncated---