[PATCH] kasan: fix cache shrink race with CPU hotplug

0 views
Skip to first unread message

Hui Su

unread,
Aug 6, 2026, 8:10:55 AM (3 days ago) Aug 6
to Andrey Ryabinin, Andrew Morton, Alexander Potapenko, Andrey Konovalov, Dmitry Vyukov, Vincenzo Frascino, Zqiang, kasa...@googlegroups.com, linu...@kvack.org, linux-...@vger.kernel.org, Hui Su, sta...@vger.kernel.org
kasan_quarantine_remove_cache() first invokes per_cpu_remove_cache() on
all online CPUs. Each callback moves objects belonging to the cache from
cpu_quarantine to the CPU's shrink_qlist, where they can later be freed
from task context.

kmem_cache_destroy() invokes the quarantine removal path while holding
cpus_read_lock(), but kmem_cache_shrink() does not. The latter can
therefore race with CPU offlining as follows:

kmem_cache_shrink() CPU hotplug
------------------- -----------
on_each_cpu()
CPU1 moves objects to
CPU1's shrink_qlist
on_each_cpu() returns
CPU1 goes offline
kasan_cpu_offline()
drains cpu_quarantine
leaves shrink_qlist untouched
for_each_online_cpu()
skips CPU1

The objects left on CPU1's shrink_qlist are not returned to the slab
allocator. This may prevent kmem_cache_shrink() from releasing slabs
that would otherwise become empty. If CPU1 remains offline, a later
kmem_cache_destroy() also skips the list and can report that the cache
still contains objects.

Per-CPU shrink_qlist storage exists for every possible CPU, and each
list is protected by its own raw spinlock. Iterate over possible CPUs
so that a list populated before its CPU went offline is drained as well.

Fixes: 07d067e4f2ce ("kasan: fix sleeping function called from invalid context on RT kernel")
Cc: sta...@vger.kernel.org
Signed-off-by: Hui Su <sh_...@163.com>
---
mm/kasan/quarantine.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/mm/kasan/quarantine.c b/mm/kasan/quarantine.c
index 6958aa713c67..16f4e67beee8 100644
--- a/mm/kasan/quarantine.c
+++ b/mm/kasan/quarantine.c
@@ -355,7 +355,12 @@ void kasan_quarantine_remove_cache(struct kmem_cache *cache)
*/
on_each_cpu(per_cpu_remove_cache, cache, 1);

- for_each_online_cpu(cpu) {
+ /*
+ * A CPU can go offline after on_each_cpu() returns, leaving cache
+ * objects on that CPU's shrink list. Scan all possible CPUs to
+ * drain those lists.
+ */
+ for_each_possible_cpu(cpu) {
sq = per_cpu_ptr(&shrink_qlist, cpu);
raw_spin_lock_irqsave(&sq->lock, flags);
qlist_move_cache(&sq->qlist, &to_free, cache);
--
2.43.0

Andrew Morton

unread,
Aug 6, 2026, 4:51:29 PM (3 days ago) Aug 6
to Hui Su, Andrey Ryabinin, Alexander Potapenko, Andrey Konovalov, Dmitry Vyukov, Vincenzo Frascino, Zqiang, kasa...@googlegroups.com, linu...@kvack.org, linux-...@vger.kernel.org, sta...@vger.kernel.org
Thanks.

Please always describe the userspace-visible runtime effects of a bug
when proposing a fix.

> Fixes: 07d067e4f2ce ("kasan: fix sleeping function called from invalid context on RT kernel")
> Cc: sta...@vger.kernel.org

Especially when proposing a backport. At present we've provided our
-stable recipients no reason to accept the patch.

Documentation/process/stable-kernel-rules.rst says "it must fix a real
bug that bothers people".


> --- a/mm/kasan/quarantine.c
> +++ b/mm/kasan/quarantine.c
> @@ -355,7 +355,12 @@ void kasan_quarantine_remove_cache(struct kmem_cache *cache)
> */
> on_each_cpu(per_cpu_remove_cache, cache, 1);
>
> - for_each_online_cpu(cpu) {
> + /*
> + * A CPU can go offline after on_each_cpu() returns, leaving cache
> + * objects on that CPU's shrink list. Scan all possible CPUs to
> + * drain those lists.
> + */
> + for_each_possible_cpu(cpu) {

In some configurations, for_each_possible_cpu() can do a lot more work
than for_each_online_cpu(). What are the worst-case userspace-visible
runtime effects of this additional work?


Hui Su

unread,
Aug 7, 2026, 2:53:11 AM (3 days ago) Aug 7
to Andrew Morton, Hui Su, ryabin...@gmail.com, gli...@google.com, andre...@gmail.com, dvy...@google.com, vincenzo...@arm.com, qiang1...@intel.com, kasa...@googlegroups.com, linu...@kvack.org, linux-...@vger.kernel.org, sta...@vger.kernel.org
Hi Andrew,
Thanks.

On my machine, the userspace-visible effect was that unmounting a 9p
filesystem returned successfully, but the kernel reported that the 9p
fcall slab cache still had objects during cache destruction.

The sequence was:
1. mount a virtio-9p filesystem;
2. read files from it on CPU1;
3. write to the 9p fcall slab cache's sysfs shrink file while racing
with CPU1 offlining;
4. unmount the filesystem.

The shrink step is what can leave objects on the offline CPU's
shrink_qlist; the later umount exposes the problem through
kmem_cache_destroy().

The mount and umount commands both returned 0. Unmounting the filesystem
then reports this dmesg excerpt:

[ 2994.380134][ T111] BUG 9p-fcall-cache-1 (Tainted: G B ): Objects remaining on __kmem_cache_shutdown()
[ 2994.381140][ T111] Object 0xff11000004361118 @offset=4376
[ 2994.381607][ T111] Allocated in p9_fcall_init+0x201/0x400 age=19564 cpu=1 pid=104
[ 2994.382591][ T111] p9_fcall_init+0x201/0x400
[ 2994.382810][ T111] p9_tag_alloc+0x12f/0x700
[ 2994.382982][ T111] p9_client_prepare_req+0x102/0x3e0
[ 2994.383165][ T111] p9_client_rpc+0x1ab/0xa50
[ 2994.383334][ T111] p9_client_getattr_dotl+0xb0/0x1a0
[ 2994.383515][ T111] v9fs_vfs_getattr_dotl+0x115/0x360
[ 2994.383719][ T111] vfs_getattr_nosec+0x22c/0x3a0
[ 2994.383910][ T111] vfs_statx+0xd7/0x170
[ 2994.384062][ T111] vfs_fstatat+0x45/0x80
[ 2994.384215][ T111] __do_sys_newfstatat+0x84/0xe0
[ 2994.384386][ T111] do_syscall_64+0x115/0x6a0
[ 2994.384566][ T111] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 2994.399720][ T111] WARNING: mm/slub.c:1244 at __kmem_cache_shutdown+0x363/0x500, CPU#0: busybox/111
[ 2994.405655][ T111] Call Trace:
[ 2994.406325][ T111] kmem_cache_destroy+0x73/0x1b0
[ 2994.406630][ T111] p9_client_destroy+0x271/0x3c0
[ 2994.407210][ T111] v9fs_session_close+0x3c/0x260
[ 2994.407409][ T111] v9fs_kill_super+0x48/0x90
[ 2994.407584][ T111] deactivate_locked_super+0xa3/0x160
[ 2994.407778][ T111] cleanup_mnt+0x1dd/0x3e0

So the userspace-visible effect is not that umount fails. It is that a
successful userspace-triggered teardown leaves the 9p fcall slab cache
with objects still present, produces the slab shutdown warning above, and
prevents the cache from being destroyed cleanly.
> > Fixes: 07d067e4f2ce ("kasan: fix sleeping function called from invalid context on RT kernel")
> > Cc: sta...@vger.kernel.org
>
> Especially when proposing a backport. At present we've provided our
> -stable recipients no reason to accept the patch.
>
> Documentation/process/stable-kernel-rules.rst says "it must fix a real
> bug that bothers people".
>

Yes, I agree. I did not include the real bug dmesg in the original
changelog, which made the stable rationale unclear.

The 9p case above shows that this is a real bug: a userspace-triggered
teardown path can leave objects in the 9p fcall cache and report the slab
shutdown warning after umount.
Should I keep Cc: stable in PATCH v2 with this dmesg and
userspace-visible effect added to the changelog?

>
> > --- a/mm/kasan/quarantine.c
> > +++ b/mm/kasan/quarantine.c
> > @@ -355,7 +355,12 @@ void kasan_quarantine_remove_cache(struct kmem_cache *cache)
> > */
> > on_each_cpu(per_cpu_remove_cache, cache, 1);
> >
> > - for_each_online_cpu(cpu) {
> > + /*
> > + * A CPU can go offline after on_each_cpu() returns, leaving cache
> > + * objects on that CPU's shrink list. Scan all possible CPUs to
> > + * drain those lists.
> > + */
> > + for_each_possible_cpu(cpu) {
>
> In some configurations, for_each_possible_cpu() can do a lot more work
> than for_each_online_cpu(). What are the worst-case userspace-visible
> runtime effects of this additional work?

The impact is limited to CONFIG_KASAN_GENERIC kernels. quarantine.o is
built only for CONFIG_KASAN_GENERIC, and the quarantine helpers are
compiled as no-ops otherwise, so non-KASAN kernels and tag-based KASAN
builds are not affected by this change.

For CONFIG_KASAN_GENERIC kernels, the extra cost is paid only when
removing or shrinking a cache, not on the normal allocation/free fast
path. The added work is one raw-spinlock-protected empty-list check per
possible CPU.

Andrew Morton

unread,
Aug 7, 2026, 5:38:08 PM (2 days ago) Aug 7
to Hui Su, ryabin...@gmail.com, gli...@google.com, andre...@gmail.com, dvy...@google.com, vincenzo...@arm.com, qiang1...@intel.com, kasa...@googlegroups.com, linu...@kvack.org, linux-...@vger.kernel.org, sta...@vger.kernel.org
On Fri, 7 Aug 2026 14:50:31 +0800 Hui Su <sh_...@163.com> wrote:

> > Especially when proposing a backport. At present we've provided our
> > -stable recipients no reason to accept the patch.
> >
> > Documentation/process/stable-kernel-rules.rst says "it must fix a real
> > bug that bothers people".
> >
>
> Yes, I agree. I did not include the real bug dmesg in the original
> changelog, which made the stable rationale unclear.
>
> The 9p case above shows that this is a real bug: a userspace-triggered
> teardown path can leave objects in the 9p fcall cache and report the slab
> shutdown warning after umount.

OK, thanks.

> Should I keep Cc: stable in PATCH v2 with this dmesg and
> userspace-visible effect added to the changelog?

Yes please!

Hui Su

unread,
Aug 7, 2026, 11:16:08 PM (2 days ago) Aug 7
to Andrey Ryabinin, Andrew Morton, Alexander Potapenko, Andrey Konovalov, Dmitry Vyukov, Vincenzo Frascino, Zqiang, kasa...@googlegroups.com, linu...@kvack.org, linux-...@vger.kernel.org, sta...@vger.kernel.org, Hui Su
kasan_quarantine_remove_cache() first invokes per_cpu_remove_cache() on
all online CPUs. Each callback moves objects belonging to the cache from
cpu_quarantine to the CPU's shrink_qlist, where they can later be freed
from task context.

kmem_cache_destroy() invokes the quarantine removal path while holding
cpus_read_lock(), but kmem_cache_shrink() does not. The latter can
therefore race with CPU offlining as follows:

kmem_cache_shrink() CPU hotplug
------------------- -----------
on_each_cpu()
CPU1 moves objects to
CPU1's shrink_qlist
on_each_cpu() returns
CPU1 goes offline
kasan_cpu_offline()
drains cpu_quarantine
leaves shrink_qlist untouched
for_each_online_cpu()
skips CPU1

The objects left on CPU1's shrink_qlist are not returned to the slab
allocator. This may prevent kmem_cache_shrink() from releasing slabs
that would otherwise become empty. If CPU1 remains offline, a later
kmem_cache_destroy() also skips the list and can report that the cache
still contains objects.

An intermittent occurrence was observed with a virtio-9p filesystem.
The mount and umount commands both returned 0, but the kernel logged
the following during the userspace-triggered teardown:

[ 2994.380134][ T111] BUG 9p-fcall-cache-1 (Tainted: G B ): Objects remaining on __kmem_cache_shutdown()
[ 2994.381140][ T111] Object 0xff11000004361118 @offset=4376
[ 2994.381607][ T111] Allocated in p9_fcall_init+0x201/0x400 age=19564 cpu=1 pid=104
[ 2994.382591][ T111] p9_fcall_init+0x201/0x400
[ 2994.382810][ T111] p9_tag_alloc+0x12f/0x700
[ 2994.382982][ T111] p9_client_prepare_req+0x102/0x3e0
[ 2994.383165][ T111] p9_client_rpc+0x1ab/0xa50
[ 2994.383334][ T111] p9_client_getattr_dotl+0xb0/0x1a0
[ 2994.383515][ T111] v9fs_vfs_getattr_dotl+0x115/0x360
[ 2994.383719][ T111] vfs_getattr_nosec+0x22c/0x3a0
[ 2994.383910][ T111] vfs_statx+0xd7/0x170
[ 2994.384062][ T111] vfs_fstatat+0x45/0x80
[ 2994.384215][ T111] __do_sys_newfstatat+0x84/0xe0
[ 2994.384386][ T111] do_syscall_64+0x115/0x6a0
[ 2994.384566][ T111] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 2994.399720][ T111] WARNING: mm/slub.c:1244 at __kmem_cache_shutdown+0x363/0x500, CPU#0: busybox/111
[ 2994.405655][ T111] Call Trace:
[ 2994.406325][ T111] kmem_cache_destroy+0x73/0x1b0
[ 2994.406630][ T111] p9_client_destroy+0x271/0x3c0
[ 2994.407210][ T111] v9fs_session_close+0x3c/0x260
[ 2994.407409][ T111] v9fs_kill_super+0x48/0x90
[ 2994.407584][ T111] deactivate_locked_super+0xa3/0x160
[ 2994.407778][ T111] cleanup_mnt+0x1dd/0x3e0

Thus, a successful umount left objects in the 9p fcall cache and
prevented the cache from being destroyed cleanly.

Per-CPU shrink_qlist storage exists for every possible CPU, and each
list is protected by its own raw spinlock. Iterate over possible CPUs
so that a list populated before its CPU went offline is drained as well.

for_each_possible_cpu() can do more work than for_each_online_cpu(), but
this change only affects CONFIG_KASAN_GENERIC kernels. The extra work is
limited to cache shrink and cache destruction paths and does not affect
the normal allocation/free fast path. It adds one raw-spinlock-protected
scan of each possible CPU's shrink list. These lists are normally empty;
a non-empty list is traversed to remove objects belonging to the cache
being shrunk or destroyed.

Fixes: 07d067e4f2ce ("kasan: fix sleeping function called from invalid context on RT kernel")
Cc: sta...@vger.kernel.org
Signed-off-by: Hui Su <sh_...@163.com>
---
Changes in v2:
- Document the intermittent virtio-9p slab shutdown log and user-visible effect.
- Explain the runtime cost of scanning all possible CPUs.
- No code changes.

Link to v1: https://lore.kernel.org/r/20260806121006....@163.com

mm/kasan/quarantine.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/mm/kasan/quarantine.c b/mm/kasan/quarantine.c
index 6958aa713c67..16f4e67beee8 100644
--- a/mm/kasan/quarantine.c
+++ b/mm/kasan/quarantine.c
@@ -355,7 +355,12 @@ void kasan_quarantine_remove_cache(struct kmem_cache *cache)
*/
on_each_cpu(per_cpu_remove_cache, cache, 1);

- for_each_online_cpu(cpu) {
+ /*
+ * A CPU can go offline after on_each_cpu() returns, leaving cache
+ * objects on that CPU's shrink list. Scan all possible CPUs to
+ * drain those lists.
+ */
+ for_each_possible_cpu(cpu) {

Andrew Morton

unread,
Aug 8, 2026, 3:39:24 AM (yesterday) Aug 8
to Hui Su, Andrey Ryabinin, Alexander Potapenko, Andrey Konovalov, Dmitry Vyukov, Vincenzo Frascino, Zqiang, kasa...@googlegroups.com, linu...@kvack.org, linux-...@vger.kernel.org, sta...@vger.kernel.org
> ...
>

Thanks, I'll queue this for testing while we await maintainer review.

AI review suggests that there's a pre-existing quarantine_size
accounting flaw later in this function:

https://sashiko.dev/#/patchset/20260808031459....@163.com

If true, I'm surprised this hasn't yet been reported.


Also, I'd like to see a need_resched() wrapping that expensive

/* Scanning whole quarantine can take a while. */
raw_spin_unlock_irqrestore(&quarantine_lock, flags);
cond_resched();
raw_spin_lock_irqsave(&quarantine_lock, flags);


Hui Su

unread,
Aug 8, 2026, 5:40:04 AM (yesterday) Aug 8
to ak...@linux-foundation.org, andre...@gmail.com, dvy...@google.com, gli...@google.com, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@kvack.org, qiang1...@intel.com, ryabin...@gmail.com, sh_...@163.com, sta...@vger.kernel.org, vincenzo...@arm.com
Thanks Andrew,

On Sat, Aug 08, 2026 at 12:39:21AM -0700, Andrew Morton wrote:
> AI review suggests that there's a pre-existing quarantine_size
> accounting flaw later in this function:
>
> https://sashiko.dev/#/patchset/20260808031459....@163.com
>
> If true, I'm surprised this hasn't yet been reported.

I'll take a closer look at this. If it turns out to be a real issue,
I'll send a separate patch since it's pre-existing and unrelated to the
hotplug race fix.

> Also, I'd like to see a need_resched() wrapping that expensive
>
> /* Scanning whole quarantine can take a while. */
> raw_spin_unlock_irqrestore(&quarantine_lock, flags);
> cond_resched();
> raw_spin_lock_irqsave(&quarantine_lock, flags);

Good point, that makes sense. Would you prefer that I fold this into a
v3 of this patch, or send it as a separate follow-up patch?

Thanks,
Hui

Andrew Morton

unread,
Aug 8, 2026, 12:53:50 PM (yesterday) Aug 8
to Hui Su, andre...@gmail.com, dvy...@google.com, gli...@google.com, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@kvack.org, qiang1...@intel.com, ryabin...@gmail.com, sta...@vger.kernel.org, vincenzo...@arm.com
Separate patches, please. One patch per concept.
Reply all
Reply to author
Forward
0 new messages