[PATCH 3/4] mm/slub: test previous lifetime tracking
From: Pengpeng Hou
Date: Tue Jun 16 2026 - 10:29:04 EST
Add KUnit coverage for the previous-lifetime tracking state transition.
The test allocates an object from a SLAB_STORE_USER cache, frees it and
allocates again from the same cache. The immediate reuse case should move
the completed alloc/free lifetime into the previous-lifetime slots before
the new allocation track is recorded.
Expose a small KUnit-only helper so the test can check the internal
tracking state without parsing printk output or intentionally triggering a
real use-after-free.
Signed-off-by: Pengpeng Hou <pengpeng@xxxxxxxxxxx>
---
lib/tests/slub_kunit.c | 33 +++++++++++++++++++++++++++++++++
mm/slab.h | 10 ++++++++++
mm/slub.c | 13 +++++++++++++
3 files changed, 56 insertions(+)
diff --git a/lib/tests/slub_kunit.c b/lib/tests/slub_kunit.c
index fa6d31dbca16..debd28c7f7a8 100644
--- a/lib/tests/slub_kunit.c
+++ b/lib/tests/slub_kunit.c
@@ -160,6 +160,37 @@ static void test_kmalloc_redzone_access(struct kunit *test)
kmem_cache_destroy(s);
}
+static void test_store_user_previous_lifetime(struct kunit *test)
+{
+ struct kmem_cache *s;
+ void *p;
+ void *q;
+
+ s = test_kmem_cache_create("TestSlub_prev_lifetime", 64,
+ SLAB_STORE_USER | SLAB_NO_MERGE);
+ KUNIT_ASSERT_NOT_NULL(test, s);
+
+ p = kmem_cache_alloc(s, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, p);
+ KUNIT_EXPECT_FALSE(test, slab_test_has_previous_lifetime(s, p));
+
+ kmem_cache_free(s, p);
+
+ q = kmem_cache_alloc(s, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, q);
+ if (q != p) {
+ KUNIT_FAIL(test, "expected immediate reuse of the freed object");
+ kmem_cache_free(s, q);
+ kmem_cache_destroy(s);
+ return;
+ }
+
+ KUNIT_EXPECT_TRUE(test, slab_test_has_previous_lifetime(s, q));
+
+ kmem_cache_free(s, q);
+ kmem_cache_destroy(s);
+}
+
struct test_kfree_rcu_struct {
struct rcu_head rcu;
};
@@ -400,6 +431,7 @@ static struct kunit_case test_cases[] = {
KUNIT_CASE(test_clobber_redzone_free),
KUNIT_CASE(test_kmalloc_redzone_access),
+ KUNIT_CASE(test_store_user_previous_lifetime),
KUNIT_CASE(test_kfree_rcu),
KUNIT_CASE(test_kfree_rcu_wq_destroy),
KUNIT_CASE(test_leak_destroy),
@@ -419,3 +451,4 @@ kunit_test_suite(test_suite);
MODULE_DESCRIPTION("Kunit tests for slub allocator");
MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
diff --git a/mm/slab.h b/mm/slab.h
index 1bf9c3021ae3..0b3813696ca9 100644
--- a/mm/slab.h
+++ b/mm/slab.h
@@ -487,6 +487,16 @@ bool slab_in_kunit_test(void);
static inline bool slab_in_kunit_test(void) { return false; }
#endif
+#if IS_ENABLED(CONFIG_SLUB_KUNIT_TEST)
+bool slab_test_has_previous_lifetime(struct kmem_cache *s, void *object);
+#else
+static inline bool
+slab_test_has_previous_lifetime(struct kmem_cache *s, void *object)
+{
+ return false;
+}
+#endif
+
/*
* slub is about to manipulate internal object metadata. This memory lies
* outside the range of the allocated object, so accessing it would normally
diff --git a/mm/slub.c b/mm/slub.c
index 358f42e92207..5e94bd1bed81 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -46,6 +46,7 @@
#include <linux/prandom.h>
#include <kunit/test.h>
#include <kunit/test-bug.h>
+#include <kunit/visibility.h>
#include <linux/sort.h>
#include <linux/irq_work.h>
#include <linux/kprobes.h>
@@ -1169,6 +1170,18 @@ void print_tracking(struct kmem_cache *s, void *object)
}
}
+#if IS_ENABLED(CONFIG_SLUB_KUNIT_TEST)
+bool slab_test_has_previous_lifetime(struct kmem_cache *s, void *object)
+{
+ if (!(s->flags & SLAB_STORE_USER))
+ return false;
+
+ return track_has_record(get_track(s, object, TRACK_PREV_ALLOC)) &&
+ track_has_record(get_track(s, object, TRACK_PREV_FREE));
+}
+EXPORT_SYMBOL_IF_KUNIT(slab_test_has_previous_lifetime);
+#endif
+
static void print_slab_info(const struct slab *slab)
{
pr_err("Slab 0x%p objects=%u used=%u fp=0x%p flags=%pGp\n",
--
2.43.0