[PATCH 2/8] slub_kunit: Add a test for slab_freelist_hardened=
From: Chris Down
Date: Tue Oct 27 2026 - 03:38:05 EST
Signed-off-by: Chris Down <chris@xxxxxxxxxxxxxx>
---
lib/tests/slub_kunit.c | 52 ++++++++++++++++++++++++++++++++++++++++++
mm/slub.c | 2 ++
2 files changed, 54 insertions(+)
diff --git a/lib/tests/slub_kunit.c b/lib/tests/slub_kunit.c
index e3b63f0338d5..5ace73368523 100644
--- a/lib/tests/slub_kunit.c
+++ b/lib/tests/slub_kunit.c
@@ -9,6 +9,8 @@
#include <linux/delay.h>
#include <linux/perf_event.h>
#include <linux/kprobes.h>
+#include <linux/string_choices.h>
+#include <linux/swab.h>
#include "../mm/slab.h"
static struct kunit_resource resource;
@@ -107,6 +109,54 @@ static void test_first_word(struct kunit *test)
kmem_cache_destroy(s);
}
+static void test_freelist_hardened(struct kunit *test)
+{
+ struct kmem_cache *s = test_kmem_cache_create("TestSlub_freelist_hardened",
+ 64, SLAB_CONSISTENCY_CHECKS);
+ bool hardened = slab_freelist_hardened_enabled();
+ unsigned long ptr_addr, raw;
+ void *a, *b, *c;
+
+ a = kmem_cache_alloc(s, GFP_KERNEL);
+ b = kmem_cache_alloc(s, GFP_KERNEL);
+ c = kmem_cache_alloc(s, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, a);
+ KUNIT_ASSERT_NOT_NULL(test, b);
+ KUNIT_ASSERT_NOT_NULL(test, c);
+
+ if (virt_to_slab(a) != virt_to_slab(b) ||
+ virt_to_slab(b) != virt_to_slab(c)) {
+ kmem_cache_free(s, a);
+ kmem_cache_free(s, b);
+ kmem_cache_free(s, c);
+ kmem_cache_destroy(s);
+ kunit_skip(test, "objects were allocated from different slabs");
+ }
+
+ /*
+ * Caches with debug flags have no sheaves, so a and b are pushed onto
+ * the slab freelist in that order.
+ */
+ kmem_cache_free(s, a);
+ kmem_cache_free(s, b);
+
+ ptr_addr = (unsigned long)b + s->offset;
+ raw = *(unsigned long *)ptr_addr;
+
+ kunit_info(test, "freelist hardening is %s\n", str_on_off(hardened));
+ KUNIT_EXPECT_EQ(test, raw == (unsigned long)a, !hardened);
+#ifdef CONFIG_SLAB_FREELIST_HARDENED
+ if (hardened)
+ KUNIT_EXPECT_EQ(test, raw,
+ (unsigned long)a ^ s->random ^ swab(ptr_addr));
+#endif
+
+ kmem_cache_free(s, c);
+ validate_slab_cache(s);
+ KUNIT_EXPECT_EQ(test, 0, slab_errors);
+ 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",
@@ -480,6 +530,7 @@ static struct kunit_case test_cases[] = {
#ifndef CONFIG_KASAN
KUNIT_CASE(test_next_pointer),
KUNIT_CASE(test_first_word),
+ KUNIT_CASE(test_freelist_hardened),
KUNIT_CASE(test_clobber_50th_byte),
#endif
@@ -505,5 +556,6 @@ static struct kunit_suite test_suite = {
};
kunit_test_suite(test_suite);
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
MODULE_DESCRIPTION("Kunit tests for slub allocator");
MODULE_LICENSE("GPL");
diff --git a/mm/slub.c b/mm/slub.c
index c82bc626b657..de4937fcb293 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>
@@ -220,6 +221,7 @@ static DEFINE_STATIC_KEY_FALSE(strict_numa);
* so it must not change once the first slab has been populated.
*/
DEFINE_STATIC_KEY_TRUE_RO(slab_freelist_hardened);
+EXPORT_SYMBOL_IF_KUNIT(slab_freelist_hardened);
static bool slab_freelist_hardened_early __initdata = true;
static int __init setup_slab_freelist_hardened(char *str)
--
2.54.0