mm/kasan/quarantine.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-)
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: stable@vger.kernel.org
Signed-off-by: Hui Su <sh_def@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.1642946-1-sh_def@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
Hui Su <sh_def@163.com> writes:
> 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: stable@vger.kernel.org
> Signed-off-by: Hui Su <sh_def@163.com>
Reviewed-by: Andrey Ryabinin <ryabinin.a.a@gmail.com>
On Sat, 8 Aug 2026 11:14:59 +0800 Hui Su <sh_def@163.com> wrote: > 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: > > ... > 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.3032812-1-sh_def@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);
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.3032812-1-sh_def@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
On Sat, 8 Aug 2026 17:38:05 +0800 Hui Su <sh_def@163.com> wrote: > 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.3032812-1-sh_def@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? Separate patches, please. One patch per concept.
© 2016 - 2026 Red Hat, Inc.