Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
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


      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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox