linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 0/3] slab: ZERO_SIZE_PTR alignment and exact matching
@ 2026-08-11 14:12 Karl Mehltretter
  2026-08-11 14:12 ` [PATCH v2 1/3] slab: align ZERO_SIZE_PTR to ARCH_KMALLOC_MINALIGN Karl Mehltretter
                   ` (2 more replies)
  0 siblings, 3 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 promise ARCH_KMALLOC_MINALIGN alignment but
return (void *)16 for zero-size requests. Patch 1 aligns the sentinel
to that promise. Patch 2 makes ZERO_OR_NULL_PTR() match only NULL and
the sentinel, so increasing the sentinel does not widen the set it
matches. Patch 3 adds KUnit coverage.

In hardened usercopy, patch 2 changes check_bogus_address() so nonzero
addresses below ZERO_SIZE_PTR no longer cause its null-address abort.

v1 -> v2:

  - express the poison-pointer bound as 0x100 instead of 128 (Catalin)
  - make ZERO_OR_NULL_PTR() use exact matches
  - add KUnit coverage

Tested on bcc44b6785f21 with the series applied:

  - GCC 15.2 QEMU boots on ten configurations spanning sentinel values
    16, 32, 64 and 128, all passing the zero-size allocation selftest
  - the x86_64 test also passes with GCC 8.1, the minimum supported
    compiler
  - the new KUnit test passes on UML
  - Clang UBSAN_ALIGNMENT is clean on armv5 and the generated code
    retains the exact sentinel comparison
  - Clang builds the macro for 21 targets at -O2 and -Os, with single
    evaluation confirmed in the generated IR
  - patch 2 changes vmlinux text by -2 bytes on x86_64 and -72 bytes
    on armv5, and arm64 gains 44 symbol bytes

v1: https://lore.kernel.org/r/20260808105622.62026-1-kmehltretter@gmail.com
RFC: https://lore.kernel.org/r/20260712120728.96628-1-kmehltretter@gmail.com

Karl Mehltretter (3):
  slab: align ZERO_SIZE_PTR to ARCH_KMALLOC_MINALIGN
  slab: check for ZERO_SIZE_PTR by exact match
  slab: test zero-size allocations in slub_kunit

 include/linux/slab.h   | 20 +++++++++++++++++---
 lib/tests/slub_kunit.c | 43 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 60 insertions(+), 3 deletions(-)


base-commit: bcc44b6785f216eb939226ade6e3910baa30516b
--
2.53.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [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

* Re: [PATCH v2 2/3] slab: check for ZERO_SIZE_PTR by exact match
  2026-08-11 14:12 ` [PATCH v2 2/3] slab: check for ZERO_SIZE_PTR by exact match Karl Mehltretter
@ 2026-08-12  0:17   ` Kees Cook
  0 siblings, 0 replies; 5+ messages in thread
From: Kees Cook @ 2026-08-12  0:17 UTC (permalink / raw)
  To: Karl Mehltretter
  Cc: Vlastimil Babka, Harry Yoo, Andrew Morton, Rasmus Villemoes,
	Hao Li, Christoph Lameter, David Rientjes, Roman Gushchin,
	Catalin Marinas, Gustavo A. R. Silva, linux-hardening, linux-mm,
	linux-kernel, llvm

On Tue, Aug 11, 2026 at 04:12:39PM +0200, Karl Mehltretter wrote:
> 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.

Can we move ZERO_SIZE_PTR to cover the ERR_PTR range too? See this issue:
https://github.com/KSPP/linux/issues/93

> This also changes check_bogus_address() in hardened usercopy: nonzero
> addresses below ZERO_SIZE_PTR no longer cause its null-address abort.

And then check_bogus_address would also catch ERR_PTR.

-Kees

-- 
Kees Cook

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-08-12  0:17 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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-12  0:17   ` Kees Cook
2026-08-11 14:12 ` [PATCH v2 3/3] slab: test zero-size allocations in slub_kunit Karl Mehltretter

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).