Add KUnit coverage for the SLAB_STORE_USER reuse transition. The test creates a non-mergeable cache, pins the task against migration, verifies that a fresh object has no previous lifetime, then frees and immediately reallocates the object and verifies that the completed pair was retained. Exclude KASAN configurations because quarantine can intentionally prevent immediate reuse. Assisted-by: Codex:gpt-5 Signed-off-by: Pengpeng Hou --- lib/tests/slub_kunit.c | 35 +++++++++++++++++++++++++++++++++++ mm/slab.h | 4 ++++ mm/slub.c | 10 ++++++++++ 3 files changed, 49 insertions(+) diff --git a/lib/tests/slub_kunit.c b/lib/tests/slub_kunit.c index fa6d31dbca16..62d0f5e64268 100644 --- a/lib/tests/slub_kunit.c +++ b/lib/tests/slub_kunit.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include "../mm/slab.h" @@ -106,6 +107,38 @@ static void test_first_word(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); + migrate_disable(); + p = kmem_cache_alloc(s, GFP_KERNEL); + if (!p) { + KUNIT_FAIL(test, "failed to allocate the first object"); + goto out_enable; + } + KUNIT_EXPECT_FALSE(test, slab_test_has_previous_lifetime(s, p)); + + kmem_cache_free(s, p); + q = kmem_cache_alloc(s, GFP_KERNEL); + if (q != p) { + KUNIT_FAIL(test, "freed object was not immediately reused"); + if (q) + kmem_cache_free(s, q); + goto out_enable; + } + KUNIT_EXPECT_TRUE(test, slab_test_has_previous_lifetime(s, q)); + + kmem_cache_free(s, q); +out_enable: + migrate_enable(); + kmem_cache_destroy(s); +} + static void test_clobber_50th_byte(struct kunit *test) { struct kmem_cache *s = test_kmem_cache_create("TestSlub_50th_word_free", @@ -395,6 +428,7 @@ static struct kunit_case test_cases[] = { #ifndef CONFIG_KASAN KUNIT_CASE(test_next_pointer), KUNIT_CASE(test_first_word), + KUNIT_CASE(test_store_user_previous_lifetime), KUNIT_CASE(test_clobber_50th_byte), #endif @@ -419,3 +453,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 f5e336b6b6b0..15634d9603f8 100644 --- a/mm/slab.h +++ b/mm/slab.h @@ -518,6 +518,10 @@ 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); +#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 355fbffb981f..d6b7487fb300 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -1148,6 +1149,15 @@ void print_tracking(struct kmem_cache *s, void *object) print_track("Freed", get_track(s, object, TRACK_PREV_FREE), pr_time); } +#if IS_ENABLED(CONFIG_SLUB_KUNIT_TEST) +bool slab_test_has_previous_lifetime(struct kmem_cache *s, void *object) +{ + return get_track(s, object, TRACK_PREV_ALLOC)->addr && + get_track(s, object, TRACK_PREV_FREE)->addr; +} +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.50.1 (Apple Git-155)