From: sashiko-bot@kernel.org
To: "Kuniyuki Iwashima" <kuniyu@google.com>,
"Andrey Ryabinin" <ryabinin.a.a@gmail.com>,
"Andrew Morton" <akpm@linux-foundation.org>,
"Vlastimil Babka" <vbabka@kernel.org>,
"Harry Yoo" <harry@kernel.org>
Cc: "Alexander Potapenko" <glider@google.com>,
"Andrey Konovalov" <andreyknvl@gmail.com>,
"Dmitry Vyukov" <dvyukov@google.com>,
"Vincenzo Frascino" <vincenzo.frascino@arm.com>,
"Hao Li" <hao.li@linux.dev>, "Christoph Lameter" <cl@gentwo.org>,
"David Rientjes" <rientjes@google.com>,
"Roman Gushchin" <roman.gushchin@linux.dev>,
kasan-dev@googlegroups.com, linux-mm@kvack.org
Subject: Re: [PATCH v2 2/2] mm/kasan/kunit: Add double-free tests involving kfree_rcu_sheaf().
Date: Wed, 28 Oct 2026 22:26:18 +0000 [thread overview]
Message-ID: <sashiko-outbox-188501@kernel.org> (raw)
In-Reply-To: <20261027222557.1072105-3-kuniyu@google.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] mm/kasan: missing Kconfig guards in __kmem_cache_double_free_rcu()
- [Medium] mm/kasan/kunit: CPU and NUMA migration race in __kmem_cache_double_free_rcu()
--
commit 2267f8620b312bfdd7e76e11214e3ba726322685
Author: Kuniyuki Iwashima <kuniyu@google.com>
mm/kasan/kunit: Add double-free tests involving kfree_rcu_sheaf().
This commit adds KUnit test cases to verify that KASAN immediately detects
double-free combinations involving kfree_rcu_sheaf() and prevents a
reallocated victim object from being poisoned when the RCU sheaf is flushed.
> diff --git a/mm/kasan/kasan_test_c.c b/mm/kasan/kasan_test_c.c
> index b9e167ed5be32..83c418c0ef878 100644
> --- a/mm/kasan/kasan_test_c.c
> +++ b/mm/kasan/kasan_test_c.c
[ ... ]
> @@ -1070,6 +1071,134 @@ static void kmem_cache_double_free(struct kunit *test)
> kmem_cache_destroy(cache);
> }
>
> +#ifndef MODULE
> +static void __kmem_cache_double_free_rcu(struct kunit *test, int mode)
> +{
> + struct kasan_rcu_info *p, *victim = NULL, **objs;
> + struct kmem_cache *cache;
> + int i, n = 0;
> +
> + KASAN_TEST_NEEDS_CONFIG_ON(test, CONFIG_KASAN_GENERIC);
> +
> + cache = kmem_cache_create("test_cache", sizeof(*p), 0, SLAB_NO_MERGE,
> + NULL);
> + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cache);
> +
> + if (!cache->sheaf_capacity) {
> + kmem_cache_destroy(cache);
> + kunit_skip(test, "Test requires sheaves");
> + }
[Severity: Medium]
Should __kmem_cache_double_free_rcu() also check that
CONFIG_KVFREE_RCU_BATCHED is enabled and CONFIG_PREEMPT_RT is disabled?
In calculate_sheaf_capacity(), cache->sheaf_capacity is set to a non-zero
value whenever CONFIG_SLUB_TINY is disabled and debug flags are not set on
the cache, regardless of CONFIG_PREEMPT_RT or CONFIG_KVFREE_RCU_BATCHED.
However, kvfree_call_rcu() in mm/slab_common.c only calls kfree_rcu_sheaf()
when CONFIG_KVFREE_RCU_BATCHED is enabled and CONFIG_PREEMPT_RT is disabled:
mm/slab_common.c:
#ifndef CONFIG_KVFREE_RCU_BATCHED
void kvfree_call_rcu(struct kvfree_rcu_head *head, void *ptr)
{
if (head) {
kasan_record_aux_stack(ptr);
call_rcu(&head->head, kvfree_rcu_cb);
return;
}
...
#endif
and in the batched implementation:
mm/slab_common.c:kvfree_call_rcu() {
...
if (!IS_ENABLED(CONFIG_PREEMPT_RT) && kfree_rcu_sheaf(ptr))
return;
...
}
When CONFIG_PREEMPT_RT=y or CONFIG_KVFREE_RCU_BATCHED=n (for example with
CONFIG_RCU_STRICT_GRACE_PERIOD=y or CONFIG_TINY_RCU=y), cache->sheaf_capacity
is non-zero so the test does not skip, but kfree_rcu(p, rcu) bypasses
__kfree_rcu_sheaf() where kasan_slab_pre_free() and
kasan_mark_rcu_free_pending() are called.
Wouldn't this cause KUNIT_EXPECT_KASAN_FAIL() to fail across all three modes,
free and poison the reallocated live victim object during
kvfree_rcu_barrier_on_cache(cache) in modes 1 and 2, and queue the same
p->rcu head twice in mode 3?
Adding KASAN_TEST_NEEDS_CONFIG_ON(test, CONFIG_KVFREE_RCU_BATCHED) and
KASAN_TEST_NEEDS_CONFIG_OFF(test, CONFIG_PREEMPT_RT) would skip the test on
configurations where kfree_rcu_sheaf() is not used.
> +
> + objs = kunit_kcalloc(test, cache->sheaf_capacity * 2, sizeof(*objs),
> + GFP_KERNEL);
> + if (!objs) {
> + kunit_err(test, "Allocation failed: %s\n", __func__);
> + goto out;
> + }
> +
> + p = kmem_cache_alloc(cache, GFP_KERNEL);
> + if (!p) {
> + kunit_err(test, "Allocation failed: %s\n", __func__);
> + goto out;
> + }
> +
> + switch (mode) {
> + case 1:
> + kmem_cache_free(cache, p);
> + /* p is already in quarantine. */
> + KUNIT_EXPECT_KASAN_FAIL(test, kfree_rcu(p, rcu));
> + break;
> + case 2:
> + /* The actual free order is inverse. */
> + kfree_rcu(p, rcu);
> + KUNIT_EXPECT_KASAN_FAIL(test, kmem_cache_free(cache, p));
> + break;
> + case 3:
> + migrate_disable();
> + kfree_rcu(p, rcu);
> +
> + for (i = 0; i < cache->sheaf_capacity - 1; i++) {
> + objs[i] = kmem_cache_alloc(cache, GFP_KERNEL);
> + kfree_rcu(objs[i], rcu);
> + }
> +
> + KUNIT_EXPECT_KASAN_FAIL(test, kfree_rcu(p, rcu));
> + migrate_enable();
> +
> + /*
> + * Move the 1st full sheaf to quarantine, keeping the
> + * 2nd in pcs->rcu_free.
> + */
> + rcu_barrier();
> + break;
> + }
> +
> + /*
> + * Move p to quarantine if not poisoned yet (case 2,
> + * or fallback in case 3).
> + */
> + if (kasan_byte_accessible(p))
> + kvfree_rcu_barrier_on_cache(cache);
> +
> + /*
> + * Evict p from quarantine and reallocate it as a live victim object
> + * before the RCU sheaf is flushed.
> + */
> + kasan_quarantine_remove_cache(cache);
> +
> + for (i = 0; i < cache->sheaf_capacity * 2; i++) {
> + objs[n] = kmem_cache_alloc(cache, GFP_KERNEL);
> + if (!objs[n])
> + break;
> + if (objs[n] == p) {
> + victim = objs[n];
> + break;
> + }
> + n++;
> + }
> +
> + for (i = 0; i < n; i++)
> + kmem_cache_free(cache, objs[i]);
> +
> + KUNIT_EXPECT_PTR_EQ(test, victim, p);
[Severity: Medium]
Can task migration across CPUs or NUMA nodes cause spurious failures in
__kmem_cache_double_free_rcu()?
Because kunit_try_catch_run() executes the test in an unbound kthread, the
task can migrate whenever migration is not disabled:
1. p = kmem_cache_alloc(cache, GFP_KERNEL) runs before migrate_disable() in
mode 3, and modes 1 and 2 do not call migrate_disable() at all. In
kfree_rcu_sheaf() in mm/slab_common.c:
s = slab->slab_cache;
if (likely(!IS_ENABLED(CONFIG_NUMA) || slab_nid(slab) == numa_mem_id()))
return __kfree_rcu_sheaf(s, obj, SLAB_FREE_DEFAULT);
return false;
If the task migrates to a CPU on a different NUMA node between allocating p
and calling kfree_rcu(p, rcu), slab_nid(slab) == numa_mem_id() is false and
kfree_rcu_sheaf() returns false, falling back to the non-sheaf
kvfree_call_rcu() path. This bypasses __kfree_rcu_sheaf(), causing
KUNIT_EXPECT_KASAN_FAIL() to fail and queueing the double-freed object into
krcp.
2. Following the switch block, rcu_barrier(),
kvfree_rcu_barrier_on_cache(cache), and kasan_quarantine_remove_cache(cache)
can sleep with migration enabled, and the victim reallocation loop also runs
without migrate_disable(). If the task migrates to another NUMA node before
or during the loop, refill_objects() only refills from local_node while
__refill_objects_any() skips p's node because n->nr_partial <= s->min_partial,
allocating a new slab instead of p. Even on a single NUMA node, if the task
migrates to another CPU mid-loop after refill_sheaf() moves p into the first
CPU's pcs->main->objects[0], p remains in the first CPU's per-CPU sheaf and
KUNIT_EXPECT_PTR_EQ(test, victim, p) fails.
Would it make sense to wrap the initial allocation and free sequence in
migrate_disable() / migrate_enable() for all modes, and either keep the task
on the same CPU/node or use kmem_cache_alloc_node(..., folio_nid(virt_to_folio(p)))
under migrate_disable() when reallocating victim?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261027222557.1072105-1-kuniyu@google.com?part=2
prev parent reply other threads:[~2026-10-28 22:26 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-27 22:25 [PATCH v2 0/2] mm/kasan: Detect double-free involving kfree_rcu_sheaf() Kuniyuki Iwashima
2026-10-27 22:25 ` [PATCH v2 1/2] " Kuniyuki Iwashima
2026-10-28 16:22 ` Alexander Potapenko
2026-10-28 18:13 ` Andrey Ryabinin
2026-10-27 22:25 ` [PATCH v2 2/2] mm/kasan/kunit: Add double-free tests " Kuniyuki Iwashima
2026-10-28 16:23 ` Alexander Potapenko
2026-10-28 19:10 ` Andrey Ryabinin
2026-10-28 19:24 ` Kuniyuki Iwashima
2026-10-28 22:26 ` sashiko-bot [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-188501@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=akpm@linux-foundation.org \
--cc=andreyknvl@gmail.com \
--cc=cl@gentwo.org \
--cc=dvyukov@google.com \
--cc=glider@google.com \
--cc=hao.li@linux.dev \
--cc=harry@kernel.org \
--cc=kasan-dev@googlegroups.com \
--cc=kuniyu@google.com \
--cc=linux-mm@kvack.org \
--cc=rientjes@google.com \
--cc=roman.gushchin@linux.dev \
--cc=ryabinin.a.a@gmail.com \
--cc=sashiko-reviews@lists.linux.dev \
--cc=vbabka@kernel.org \
--cc=vincenzo.frascino@arm.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.