* [PATCH v2 1/3] slab: align ZERO_SIZE_PTR to ARCH_KMALLOC_MINALIGN
2026-08-11 14:12 [PATCH v2 0/3] slab: ZERO_SIZE_PTR alignment and exact matching Karl Mehltretter
@ 2026-08-11 14:12 ` Karl Mehltretter
2026-08-11 14:12 ` [PATCH v2 2/3] slab: check for ZERO_SIZE_PTR by exact match Karl Mehltretter
2026-08-11 14:12 ` [PATCH v2 3/3] slab: test zero-size allocations in slub_kunit Karl Mehltretter
2 siblings, 0 replies; 5+ messages in thread
From: Karl Mehltretter @ 2026-08-11 14:12 UTC (permalink / raw)
To: Vlastimil Babka, Harry Yoo, Andrew Morton
Cc: Karl Mehltretter, Rasmus Villemoes, Hao Li, Christoph Lameter,
David Rientjes, Roman Gushchin, Catalin Marinas, Kees Cook,
Gustavo A. R. Silva, linux-hardening, linux-mm, linux-kernel,
llvm
The kmalloc entry points are annotated with __assume_kmalloc_alignment
but return ZERO_SIZE_PTR, currently (void *)16, for zero-size requests.
This violates the annotation when ARCH_KMALLOC_MINALIGN exceeds 16.
Clang's UBSAN_ALIGNMENT detects the violation on armv5. GCC and Clang
retain the ZERO_OR_NULL_PTR() range check but eliminate an exact
ZERO_SIZE_PTR comparison after an annotated allocation.
Define ZERO_SIZE_PTR as the greater of 16 and ARCH_KMALLOC_MINALIGN,
retaining the existing value where it is already aligned. Assert that
ARCH_KMALLOC_MINALIGN remains below 0x100, the value of LIST_POISON1
when POISON_POINTER_DELTA is zero, so the sentinel remains distinct
from that poison pointer.
Fixes: 94a58c360a45 ("slab.h: sprinkle __assume_aligned attributes")
Assisted-by: Claude:claude-fable-5
Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
---
include/linux/slab.h | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/include/linux/slab.h b/include/linux/slab.h
index 32c9f8ed7ae20..3e012cc4f001e 100644
--- a/include/linux/slab.h
+++ b/include/linux/slab.h
@@ -259,13 +259,16 @@ enum _slab_flag_bits {
/*
* ZERO_SIZE_PTR will be returned for zero sized kmalloc requests.
+ * It satisfies the alignment promised by __assume_kmalloc_alignment
+ * and keeps the historic value 16 where that is already aligned.
*
* Dereferencing ZERO_SIZE_PTR will lead to a distinct access fault.
*
* ZERO_SIZE_PTR can be passed to kfree though in the same way that NULL can.
* Both make kfree a no-op.
*/
-#define ZERO_SIZE_PTR ((void *)16)
+#define ZERO_SIZE_PTR ((void *)(ARCH_KMALLOC_MINALIGN > 16 ? \
+ ARCH_KMALLOC_MINALIGN : 16))
#define ZERO_OR_NULL_PTR(x) ((unsigned long)(x) <= \
(unsigned long)ZERO_SIZE_PTR)
@@ -622,6 +625,13 @@ static inline bool kmem_dump_obj(void *object) { return false; }
#define KMALLOC_SHIFT_LOW ilog2(KMALLOC_MIN_SIZE)
#endif
+/*
+ * Keep ZERO_SIZE_PTR at most 128, i.e. below 0x100: LIST_POISON1 is
+ * 0x100 when POISON_POINTER_DELTA is 0, and no architecture currently
+ * has an ARCH_KMALLOC_MINALIGN above 128.
+ */
+static_assert(ARCH_KMALLOC_MINALIGN < 0x100);
+
/*
* Setting ARCH_SLAB_MINALIGN in arch headers allows a different alignment.
* Intended for arches that get misalignment faults even for 64 bit integer
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH v2 2/3] slab: check for ZERO_SIZE_PTR by exact match
2026-08-11 14:12 [PATCH v2 0/3] slab: ZERO_SIZE_PTR alignment and exact matching Karl Mehltretter
2026-08-11 14:12 ` [PATCH v2 1/3] slab: align ZERO_SIZE_PTR to ARCH_KMALLOC_MINALIGN Karl Mehltretter
@ 2026-08-11 14:12 ` Karl Mehltretter
2026-08-12 0:17 ` Kees Cook
2026-08-11 14:12 ` [PATCH v2 3/3] slab: test zero-size allocations in slub_kunit Karl Mehltretter
2 siblings, 1 reply; 5+ messages in thread
From: Karl Mehltretter @ 2026-08-11 14:12 UTC (permalink / raw)
To: Vlastimil Babka, Harry Yoo, Andrew Morton
Cc: Karl Mehltretter, Rasmus Villemoes, Hao Li, Christoph Lameter,
David Rientjes, Roman Gushchin, Catalin Marinas, Kees Cook,
Gustavo A. R. Silva, linux-hardening, linux-mm, linux-kernel,
llvm
ZERO_OR_NULL_PTR() returns true for every value less than or equal to
ZERO_SIZE_PTR. With the sentinel raised to ARCH_KMALLOC_MINALIGN, up
to 128 on some architectures, the helper matches additional values
that are neither NULL nor the sentinel.
Compare explicitly against NULL and ZERO_SIZE_PTR. Store the argument
in an unsigned long temporary to support both pointer and integer
address arguments while evaluating it only once.
This also changes check_bogus_address() in hardened usercopy: nonzero
addresses below ZERO_SIZE_PTR no longer cause its null-address abort.
Apart from hardened usercopy, direct in-tree users apply the helper
to allocation results or explicit sentinel values.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
---
include/linux/slab.h | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/include/linux/slab.h b/include/linux/slab.h
index 3e012cc4f001e..023e10ab77f8d 100644
--- a/include/linux/slab.h
+++ b/include/linux/slab.h
@@ -270,8 +270,12 @@ enum _slab_flag_bits {
#define ZERO_SIZE_PTR ((void *)(ARCH_KMALLOC_MINALIGN > 16 ? \
ARCH_KMALLOC_MINALIGN : 16))
-#define ZERO_OR_NULL_PTR(x) ((unsigned long)(x) <= \
- (unsigned long)ZERO_SIZE_PTR)
+#define ZERO_OR_NULL_PTR(x) \
+({ \
+ unsigned long __zon_ptr = (unsigned long)(x); \
+ __zon_ptr == 0 || \
+ __zon_ptr == (unsigned long)ZERO_SIZE_PTR; \
+})
#include <linux/kasan.h>
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH v2 3/3] slab: test zero-size allocations in slub_kunit
2026-08-11 14:12 [PATCH v2 0/3] slab: ZERO_SIZE_PTR alignment and exact matching Karl Mehltretter
2026-08-11 14:12 ` [PATCH v2 1/3] slab: align ZERO_SIZE_PTR to ARCH_KMALLOC_MINALIGN Karl Mehltretter
2026-08-11 14:12 ` [PATCH v2 2/3] slab: check for ZERO_SIZE_PTR by exact match Karl Mehltretter
@ 2026-08-11 14:12 ` Karl Mehltretter
2 siblings, 0 replies; 5+ messages in thread
From: Karl Mehltretter @ 2026-08-11 14:12 UTC (permalink / raw)
To: Vlastimil Babka, Harry Yoo, Andrew Morton
Cc: Karl Mehltretter, Rasmus Villemoes, Hao Li, Christoph Lameter,
David Rientjes, Roman Gushchin, Catalin Marinas, Kees Cook,
Gustavo A. R. Silva, linux-hardening, linux-mm, linux-kernel,
llvm
Add KUnit coverage for the zero-size allocation contract, including
ZERO_SIZE_PTR alignment and exact ZERO_OR_NULL_PTR() matching.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
---
lib/tests/slub_kunit.c | 43 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 43 insertions(+)
diff --git a/lib/tests/slub_kunit.c b/lib/tests/slub_kunit.c
index fa6d31dbca166..a02fc6b401e3e 100644
--- a/lib/tests/slub_kunit.c
+++ b/lib/tests/slub_kunit.c
@@ -380,6 +380,48 @@ static void test_kmalloc_kfree_nolock(struct kunit *test)
}
#endif
+static void test_zero_size_alloc(struct kunit *test)
+{
+ unsigned long zsp = (unsigned long)ZERO_SIZE_PTR;
+ void *p, *r;
+
+ static_assert((unsigned long)ZERO_SIZE_PTR % ARCH_KMALLOC_MINALIGN == 0);
+
+ p = kmalloc(0, GFP_KERNEL);
+ KUNIT_EXPECT_PTR_EQ(test, p, ZERO_SIZE_PTR);
+ KUNIT_EXPECT_EQ(test, ksize(p), 0);
+ kfree(p);
+
+ KUNIT_EXPECT_PTR_EQ(test, kzalloc(0, GFP_KERNEL), ZERO_SIZE_PTR);
+ KUNIT_EXPECT_PTR_EQ(test, kmalloc_array(0, 8, GFP_KERNEL), ZERO_SIZE_PTR);
+ KUNIT_EXPECT_PTR_EQ(test, kcalloc(4, 0, GFP_KERNEL), ZERO_SIZE_PTR);
+
+ p = kvmalloc(0, GFP_KERNEL);
+ KUNIT_EXPECT_PTR_EQ(test, p, ZERO_SIZE_PTR);
+ kvfree(p);
+
+ p = krealloc(NULL, 0, GFP_KERNEL);
+ KUNIT_EXPECT_PTR_EQ(test, p, ZERO_SIZE_PTR);
+ r = krealloc(p, 64, GFP_KERNEL);
+ KUNIT_EXPECT_FALSE(test, ZERO_OR_NULL_PTR(r));
+ p = krealloc(r, 0, GFP_KERNEL);
+ KUNIT_EXPECT_PTR_EQ(test, p, ZERO_SIZE_PTR);
+ kfree(p);
+
+ /* Only NULL and the zero-size sentinel match. */
+ KUNIT_EXPECT_TRUE(test, ZERO_OR_NULL_PTR(NULL));
+ KUNIT_EXPECT_TRUE(test, ZERO_OR_NULL_PTR(ZERO_SIZE_PTR));
+ KUNIT_EXPECT_FALSE(test, ZERO_OR_NULL_PTR((void *)1));
+ KUNIT_EXPECT_FALSE(test, ZERO_OR_NULL_PTR((void *)(zsp - 1)));
+ KUNIT_EXPECT_FALSE(test, ZERO_OR_NULL_PTR((void *)(zsp + 1)));
+ KUNIT_EXPECT_FALSE(test, ZERO_OR_NULL_PTR((void *)(zsp * 2)));
+
+ /* freeing the sentinel must stay a no-op */
+ kfree(ZERO_SIZE_PTR);
+ kfree_sensitive(ZERO_SIZE_PTR);
+ kvfree(ZERO_SIZE_PTR);
+}
+
static int test_init(struct kunit *test)
{
slab_errors = 0;
@@ -404,6 +446,7 @@ static struct kunit_case test_cases[] = {
KUNIT_CASE(test_kfree_rcu_wq_destroy),
KUNIT_CASE(test_leak_destroy),
KUNIT_CASE(test_krealloc_redzone_zeroing),
+ KUNIT_CASE(test_zero_size_alloc),
#ifdef CONFIG_PERF_EVENTS
KUNIT_CASE_SLOW(test_kmalloc_kfree_nolock),
#endif
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread