* [PATCH v3 0/5] slab: ZERO_SIZE_PTR alignment and ERR_PTR hardening
@ 2026-09-03 20:37 Karl Mehltretter
2026-09-03 20:37 ` [PATCH v3 1/5] slab: align ZERO_SIZE_PTR to ARCH_KMALLOC_MINALIGN Karl Mehltretter
` (5 more replies)
0 siblings, 6 replies; 8+ messages in thread
From: Karl Mehltretter @ 2026-09-03 20:37 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, Arnd Bergmann, Greg Kroah-Hartman,
Shuah Khan, Nathan Chancellor, Nick Desaulniers, Bill Wendling,
Justin Stitt, linux-hardening, linux-mm, linux-kselftest,
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 limits ZERO_OR_NULL_PTR() to NULL and ZERO_SIZE_PTR. Patch 3
handles ERR_PTR explicitly in kfree() and hardened usercopy, without
giving ERR_PTR special meaning to other users such as krealloc(). The
old helper did not match ERR_PTR values either; patch 3 addresses the
adjacent hardening concern raised during review.
If backported, patches 1 and 2 should be taken together.
Patch 4 adds zero-size KUnit coverage; patch 5 adds ERR_PTR KUnit,
LKDTM, and kselftest coverage, which is why Arnd, Greg, Shuah, and
linux-kselftest@vger.kernel.org are newly copied on v3.
Tested on 940de590b839:
- GCC 15.2 UML slub_test: 10 passed, 2 skipped, including
test_zero_size_alloc and test_kfree_err_ptr; NULL and ZERO_SIZE_PTR
produced no warning, and the expected kfree(ERR_PTR) warning was
suppressed and counted
- GCC 15.2 matched x86_64 builds immediately before and after patch 3:
kfree grew by 15 bytes; __check_object_size gained a 13-byte
comparison on its normal path and a 23-byte cold abort path
- GCC 15.2 x86_64 defconfig with HARDENED_USERCOPY: build and normal
QEMU boot passed; the committed LKDTM selftest matched the distinct
ERR_PTR diagnostic after reaching usercopy_abort()
- Clang 21.1.8 armv5 UBSAN_ALIGNMENT: no alignment-assumption or
slab.h report; generated code retains the exact comparison and
single evaluation
- GCC 15.2 MIPS64 big-endian noncoherent build passed with
ARCH_KMALLOC_MINALIGN equal to 128
- Microchip SAM9X75 hardware (armv5, ARCH_KMALLOC_MINALIGN=32):
ZERO_SIZE_PTR was 0x20; zero-size allocation, ksize(), and free
passed with no boot splats
- Raspberry Pi 400 hardware (arm64, Cortex-A72,
ARCH_KMALLOC_MINALIGN=8): ZERO_SIZE_PTR remained 0x10; the ERR_PTR
KUnit and hardened-usercopy rejection paths passed with no boot
splats; the revised diagnostic was retested under x86_64 QEMU
---
v2 -> v3:
- document why patch 2's sentinel-derived low-address window in hardened
usercopy was incidental
- add new patch 3 to warn and return when kfree() receives an ERR_PTR
and reject ERR_PTR values in hardened usercopy
- replace patch 4's pointer-cast static assertion with a run-time KUnit
expectation
- add new patch 5 with KUnit and LKDTM coverage for patch 3
v2: https://lore.kernel.org/r/20260811141240.62519-1-kmehltretter@gmail.com
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 (5):
slab: align ZERO_SIZE_PTR to ARCH_KMALLOC_MINALIGN
slab: check for ZERO_SIZE_PTR by exact match
slab: handle ERR_PTR values in kfree and hardened usercopy
slab: test zero-size allocations in slub_kunit
slab: test ERR_PTR handling in kfree and hardened usercopy
drivers/misc/lkdtm/usercopy.c | 27 +++++++++++
include/linux/slab.h | 20 ++++++--
lib/tests/slub_kunit.c | 61 +++++++++++++++++++++++++
mm/slub.c | 3 ++
mm/usercopy.c | 3 ++
tools/testing/selftests/lkdtm/tests.txt | 1 +
6 files changed, 112 insertions(+), 3 deletions(-)
base-commit: 940de590b839f71d6dc846160534bf202401b8b7
--
2.53.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v3 1/5] slab: align ZERO_SIZE_PTR to ARCH_KMALLOC_MINALIGN
2026-09-03 20:37 [PATCH v3 0/5] slab: ZERO_SIZE_PTR alignment and ERR_PTR hardening Karl Mehltretter
@ 2026-09-03 20:37 ` Karl Mehltretter
2026-09-03 20:37 ` [PATCH v3 2/5] slab: check for ZERO_SIZE_PTR by exact match Karl Mehltretter
` (4 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Karl Mehltretter @ 2026-09-03 20:37 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, Arnd Bergmann, Greg Kroah-Hartman,
Shuah Khan, Nathan Chancellor, Nick Desaulniers, Bill Wendling,
Justin Stitt, linux-hardening, linux-mm, linux-kselftest,
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.
This can mislead compiler optimizations. 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: LLM
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 cda126def67a..563dadc16d82 100644
--- a/include/linux/slab.h
+++ b/include/linux/slab.h
@@ -262,13 +262,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)
@@ -625,6 +628,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] 8+ messages in thread
* [PATCH v3 2/5] slab: check for ZERO_SIZE_PTR by exact match
2026-09-03 20:37 [PATCH v3 0/5] slab: ZERO_SIZE_PTR alignment and ERR_PTR hardening Karl Mehltretter
2026-09-03 20:37 ` [PATCH v3 1/5] slab: align ZERO_SIZE_PTR to ARCH_KMALLOC_MINALIGN Karl Mehltretter
@ 2026-09-03 20:37 ` Karl Mehltretter
2026-09-03 20:37 ` [PATCH v3 3/5] slab: handle ERR_PTR values in kfree and hardened usercopy Karl Mehltretter
` (3 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Karl Mehltretter @ 2026-09-03 20:37 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, Arnd Bergmann, Greg Kroah-Hartman,
Shuah Khan, Nathan Chancellor, Nick Desaulniers, Bill Wendling,
Justin Stitt, linux-hardening, linux-mm, linux-kselftest,
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.
The rejected window was incidental to the sentinel value: address 17
already passed the old check, including in s390's mapped lowcore, and a
principled first-page bound would be a separate usercopy change.
Apart from hardened usercopy, direct in-tree users apply the helper
to allocation results or explicit sentinel values.
Assisted-by: LLM
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 563dadc16d82..458c4229b1d7 100644
--- a/include/linux/slab.h
+++ b/include/linux/slab.h
@@ -273,8 +273,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] 8+ messages in thread
* [PATCH v3 3/5] slab: handle ERR_PTR values in kfree and hardened usercopy
2026-09-03 20:37 [PATCH v3 0/5] slab: ZERO_SIZE_PTR alignment and ERR_PTR hardening Karl Mehltretter
2026-09-03 20:37 ` [PATCH v3 1/5] slab: align ZERO_SIZE_PTR to ARCH_KMALLOC_MINALIGN Karl Mehltretter
2026-09-03 20:37 ` [PATCH v3 2/5] slab: check for ZERO_SIZE_PTR by exact match Karl Mehltretter
@ 2026-09-03 20:37 ` Karl Mehltretter
2026-09-04 9:01 ` Vlastimil Babka (SUSE)
2026-09-03 20:37 ` [PATCH v3 4/5] slab: test zero-size allocations in slub_kunit Karl Mehltretter
` (2 subsequent siblings)
5 siblings, 1 reply; 8+ messages in thread
From: Karl Mehltretter @ 2026-09-03 20:37 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, Arnd Bergmann, Greg Kroah-Hartman,
Shuah Khan, Nathan Chancellor, Nick Desaulniers, Bill Wendling,
Justin Stitt, linux-hardening, linux-mm, linux-kselftest,
linux-kernel, llvm
Passing an ERR_PTR to kfree() currently reaches virt_to_page() and may
fault. Warn and return instead, leaving the bad caller visible without
using the pointer as allocator metadata.
Also reject ERR_PTR values in hardened usercopy. Keep both checks
separate from ZERO_OR_NULL_PTR(), whose exact matching is required by
krealloc().
Link: https://lore.kernel.org/r/CAG48ez05QVn6_gQ2TBrRa1a_DWQoaSSYubUsu5YMWxx-gqMijQ@mail.gmail.com
Link: https://lore.kernel.org/r/202608111716.0FA9DB17@keescook
Link: https://github.com/KSPP/linux/issues/93
Assisted-by: LLM
Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
---
mm/slub.c | 3 +++
mm/usercopy.c | 3 +++
2 files changed, 6 insertions(+)
diff --git a/mm/slub.c b/mm/slub.c
index f9b56cb439e7..027b44dd7f07 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -6780,6 +6780,9 @@ void kfree(const void *object)
if (unlikely(ZERO_OR_NULL_PTR(object)))
return;
+ if (WARN_ON(IS_ERR(object)))
+ return;
+
page = virt_to_page(object);
slab = page_slab(page);
if (!slab) {
diff --git a/mm/usercopy.c b/mm/usercopy.c
index 5de7a518b1b1..c8d8703544c6 100644
--- a/mm/usercopy.c
+++ b/mm/usercopy.c
@@ -157,6 +157,9 @@ static inline void check_bogus_address(const unsigned long ptr, unsigned long n,
/* Reject if NULL or ZERO-allocation. */
if (ZERO_OR_NULL_PTR(ptr))
usercopy_abort("null address", NULL, to_user, ptr, n);
+
+ if (IS_ERR_VALUE(ptr))
+ usercopy_abort("ERR_PTR", NULL, to_user, ptr, n);
}
static inline void check_heap_object(const void *ptr, unsigned long n,
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v3 4/5] slab: test zero-size allocations in slub_kunit
2026-09-03 20:37 [PATCH v3 0/5] slab: ZERO_SIZE_PTR alignment and ERR_PTR hardening Karl Mehltretter
` (2 preceding siblings ...)
2026-09-03 20:37 ` [PATCH v3 3/5] slab: handle ERR_PTR values in kfree and hardened usercopy Karl Mehltretter
@ 2026-09-03 20:37 ` Karl Mehltretter
2026-09-03 20:37 ` [PATCH v3 5/5] slab: test ERR_PTR handling in kfree and hardened usercopy Karl Mehltretter
2026-09-04 11:25 ` [PATCH v3 0/5] slab: ZERO_SIZE_PTR alignment and ERR_PTR hardening Harry Yoo
5 siblings, 0 replies; 8+ messages in thread
From: Karl Mehltretter @ 2026-09-03 20:37 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, Arnd Bergmann, Greg Kroah-Hartman,
Shuah Khan, Nathan Chancellor, Nick Desaulniers, Bill Wendling,
Justin Stitt, linux-hardening, linux-mm, linux-kselftest,
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: LLM
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 e3b63f0338d5..923d8646bca1 100644
--- a/lib/tests/slub_kunit.c
+++ b/lib/tests/slub_kunit.c
@@ -465,6 +465,48 @@ static void test_kmalloc_nolock_and_friends_kprobe(struct kunit *test)
}
#endif
+static void test_zero_size_alloc(struct kunit *test)
+{
+ unsigned long zsp = (unsigned long)ZERO_SIZE_PTR;
+ void *p, *r;
+
+ KUNIT_EXPECT_EQ(test, zsp % ARCH_KMALLOC_MINALIGN, 0UL);
+
+ 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;
@@ -489,6 +531,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_nolock_and_friends_perf),
#endif
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v3 5/5] slab: test ERR_PTR handling in kfree and hardened usercopy
2026-09-03 20:37 [PATCH v3 0/5] slab: ZERO_SIZE_PTR alignment and ERR_PTR hardening Karl Mehltretter
` (3 preceding siblings ...)
2026-09-03 20:37 ` [PATCH v3 4/5] slab: test zero-size allocations in slub_kunit Karl Mehltretter
@ 2026-09-03 20:37 ` Karl Mehltretter
2026-09-04 11:25 ` [PATCH v3 0/5] slab: ZERO_SIZE_PTR alignment and ERR_PTR hardening Harry Yoo
5 siblings, 0 replies; 8+ messages in thread
From: Karl Mehltretter @ 2026-09-03 20:37 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, Arnd Bergmann, Greg Kroah-Hartman,
Shuah Khan, Nathan Chancellor, Nick Desaulniers, Bill Wendling,
Justin Stitt, linux-hardening, linux-mm, linux-kselftest,
linux-kernel, llvm
Add KUnit coverage that kfree() warns and returns for ERR_PTR values
while remaining silent for NULL and ZERO_SIZE_PTR.
Add an LKDTM test that verifies hardened usercopy rejects an ERR_PTR
before attempting the copy.
Assisted-by: LLM
Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
---
drivers/misc/lkdtm/usercopy.c | 27 +++++++++++++++++++++++++
lib/tests/slub_kunit.c | 18 +++++++++++++++++
tools/testing/selftests/lkdtm/tests.txt | 1 +
3 files changed, 46 insertions(+)
diff --git a/drivers/misc/lkdtm/usercopy.c b/drivers/misc/lkdtm/usercopy.c
index 67db57249a34..10751e48211f 100644
--- a/drivers/misc/lkdtm/usercopy.c
+++ b/drivers/misc/lkdtm/usercopy.c
@@ -4,6 +4,7 @@
* hardening.
*/
#include "lkdtm.h"
+#include <linux/err.h>
#include <linux/slab.h>
#include <linux/highmem.h>
#include <linux/vmalloc.h>
@@ -273,6 +274,31 @@ static void do_usercopy_slab_whitelist(bool to_user)
}
/* Callable tests. */
+static void lkdtm_USERCOPY_ERR_PTR(void)
+{
+ unsigned long user_addr;
+ size_t size = unconst + 1;
+
+ user_addr = vm_mmap(NULL, 0, PAGE_SIZE,
+ PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, 0);
+ if (user_addr >= TASK_SIZE) {
+ pr_warn("Failed to allocate user memory\n");
+ return;
+ }
+
+ pr_info("attempting bad one-byte copy_to_user() from ERR_PTR\n");
+ if (copy_to_user((void __user *)user_addr, ERR_PTR(-EINVAL), size)) {
+ pr_warn("copy_to_user failed, but lacked Oops\n");
+ goto free_user;
+ }
+ pr_err("FAIL: ERR_PTR usercopy not detected!\n");
+ pr_expected_config_param(CONFIG_HARDENED_USERCOPY, "hardened_usercopy");
+
+free_user:
+ vm_munmap(user_addr, PAGE_SIZE);
+}
+
static void lkdtm_USERCOPY_SLAB_SIZE_TO(void)
{
do_usercopy_slab_size(true);
@@ -439,6 +465,7 @@ void __exit lkdtm_usercopy_exit(void)
}
static struct crashtype crashtypes[] = {
+ CRASHTYPE(USERCOPY_ERR_PTR),
CRASHTYPE(USERCOPY_SLAB_SIZE_TO),
CRASHTYPE(USERCOPY_SLAB_SIZE_FROM),
CRASHTYPE(USERCOPY_SLAB_WHITELIST_TO),
diff --git a/lib/tests/slub_kunit.c b/lib/tests/slub_kunit.c
index 923d8646bca1..079563387007 100644
--- a/lib/tests/slub_kunit.c
+++ b/lib/tests/slub_kunit.c
@@ -507,6 +507,23 @@ static void test_zero_size_alloc(struct kunit *test)
kvfree(ZERO_SIZE_PTR);
}
+static void test_kfree_err_ptr(struct kunit *test)
+{
+ if (!IS_ENABLED(CONFIG_BUG))
+ kunit_skip(test, "requires CONFIG_BUG");
+
+ kunit_warning_suppress(test) {
+ kfree(NULL);
+ KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 0);
+
+ kfree(ZERO_SIZE_PTR);
+ KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 0);
+
+ kfree(ERR_PTR(-EINVAL));
+ KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
+ }
+}
+
static int test_init(struct kunit *test)
{
slab_errors = 0;
@@ -532,6 +549,7 @@ static struct kunit_case test_cases[] = {
KUNIT_CASE(test_leak_destroy),
KUNIT_CASE(test_krealloc_redzone_zeroing),
KUNIT_CASE(test_zero_size_alloc),
+ KUNIT_CASE(test_kfree_err_ptr),
#ifdef CONFIG_PERF_EVENTS
KUNIT_CASE_SLOW(test_kmalloc_nolock_and_friends_perf),
#endif
diff --git a/tools/testing/selftests/lkdtm/tests.txt b/tools/testing/selftests/lkdtm/tests.txt
index bec57a02913a..d3e38c1c75cd 100644
--- a/tools/testing/selftests/lkdtm/tests.txt
+++ b/tools/testing/selftests/lkdtm/tests.txt
@@ -70,6 +70,7 @@ REFCOUNT_DEC_AND_TEST_SATURATED Saturation detected: still saturated
REFCOUNT_SUB_AND_TEST_SATURATED Saturation detected: still saturated
#REFCOUNT_TIMING timing only
#ATOMIC_TIMING timing only
+USERCOPY_ERR_PTR Kernel memory exposure attempt detected from ERR_PTR
USERCOPY_SLAB_SIZE_TO
USERCOPY_SLAB_SIZE_FROM
USERCOPY_SLAB_WHITELIST_TO
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v3 3/5] slab: handle ERR_PTR values in kfree and hardened usercopy
2026-09-03 20:37 ` [PATCH v3 3/5] slab: handle ERR_PTR values in kfree and hardened usercopy Karl Mehltretter
@ 2026-09-04 9:01 ` Vlastimil Babka (SUSE)
0 siblings, 0 replies; 8+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-09-04 9:01 UTC (permalink / raw)
To: Karl Mehltretter, Harry Yoo, Andrew Morton
Cc: Rasmus Villemoes, Hao Li, Christoph Lameter, David Rientjes,
Roman Gushchin, Catalin Marinas, Kees Cook, Gustavo A . R . Silva,
Arnd Bergmann, Greg Kroah-Hartman, Shuah Khan, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, linux-hardening,
linux-mm, linux-kselftest, linux-kernel, llvm
On 9/3/26 22:37, Karl Mehltretter wrote:
> Passing an ERR_PTR to kfree() currently reaches virt_to_page() and may
> fault. Warn and return instead, leaving the bad caller visible without
> using the pointer as allocator metadata.
>
> Also reject ERR_PTR values in hardened usercopy. Keep both checks
> separate from ZERO_OR_NULL_PTR(), whose exact matching is required by
> krealloc().
>
> Link: https://lore.kernel.org/r/CAG48ez05QVn6_gQ2TBrRa1a_DWQoaSSYubUsu5YMWxx-gqMijQ@mail.gmail.com
> Link: https://lore.kernel.org/r/202608111716.0FA9DB17@keescook
> Link: https://github.com/KSPP/linux/issues/93
> Assisted-by: LLM
> Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
> ---
> mm/slub.c | 3 +++
> mm/usercopy.c | 3 +++
> 2 files changed, 6 insertions(+)
>
> diff --git a/mm/slub.c b/mm/slub.c
> index f9b56cb439e7..027b44dd7f07 100644
> --- a/mm/slub.c
> +++ b/mm/slub.c
> @@ -6780,6 +6780,9 @@ void kfree(const void *object)
> if (unlikely(ZERO_OR_NULL_PTR(object)))
> return;
>
> + if (WARN_ON(IS_ERR(object)))
Wonder if WARN_ON_ONCE() would be better.
Also wonder about the benefits for hardening (as opposed to debugging).
Without this check it would fault. Now it warns, but hardened setups often
use panic_on_warn anyway, so the result is the same?
> + return;
> +
> page = virt_to_page(object);
> slab = page_slab(page);
> if (!slab) {
> diff --git a/mm/usercopy.c b/mm/usercopy.c
> index 5de7a518b1b1..c8d8703544c6 100644
> --- a/mm/usercopy.c
> +++ b/mm/usercopy.c
> @@ -157,6 +157,9 @@ static inline void check_bogus_address(const unsigned long ptr, unsigned long n,
> /* Reject if NULL or ZERO-allocation. */
> if (ZERO_OR_NULL_PTR(ptr))
> usercopy_abort("null address", NULL, to_user, ptr, n);
> +
> + if (IS_ERR_VALUE(ptr))
> + usercopy_abort("ERR_PTR", NULL, to_user, ptr, n);
> }
>
> static inline void check_heap_object(const void *ptr, unsigned long n,
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v3 0/5] slab: ZERO_SIZE_PTR alignment and ERR_PTR hardening
2026-09-03 20:37 [PATCH v3 0/5] slab: ZERO_SIZE_PTR alignment and ERR_PTR hardening Karl Mehltretter
` (4 preceding siblings ...)
2026-09-03 20:37 ` [PATCH v3 5/5] slab: test ERR_PTR handling in kfree and hardened usercopy Karl Mehltretter
@ 2026-09-04 11:25 ` Harry Yoo
5 siblings, 0 replies; 8+ messages in thread
From: Harry Yoo @ 2026-09-04 11:25 UTC (permalink / raw)
To: Karl Mehltretter
Cc: Vlastimil Babka, Andrew Morton, Rasmus Villemoes, Hao Li,
Christoph Lameter, David Rientjes, Roman Gushchin,
Catalin Marinas, Kees Cook, Gustavo A . R . Silva, Arnd Bergmann,
Greg Kroah-Hartman, Shuah Khan, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, linux-hardening,
linux-mm, linux-kselftest, linux-kernel, llvm
On Thu, Sep 03, 2026 at 10:37:15PM +0200, Karl Mehltretter wrote:
> 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 limits ZERO_OR_NULL_PTR() to NULL and ZERO_SIZE_PTR. Patch 3
> handles ERR_PTR explicitly in kfree() and hardened usercopy, without
> giving ERR_PTR special meaning to other users such as krealloc(). The
> old helper did not match ERR_PTR values either; patch 3 addresses the
> adjacent hardening concern raised during review.
I don't understand why v3 of this patchset suddenly implements
hardening for kfree() and usercopy to handle ERR_PTR().
The address range for 'zero or NULL PTR' range and ERR_PTR() do not
overlap. The bugfixes and hardening patches are irrelevant.
It should be a separate series?
Also usercopy.c changes go through the hardening tree, slub.c and
slub_kunit.c changes go through the slab tree, and LKDTM has its own
tree. Patch 3 and 5 touch files across multiple trees, which can be
avoided.
> If backported, patches 1 and 2 should be taken together.
>
> Patch 4 adds zero-size KUnit coverage; patch 5 adds ERR_PTR KUnit,
> LKDTM, and kselftest coverage, which is why Arnd, Greg, Shuah, and
> linux-kselftest@vger.kernel.org are newly copied on v3.
>
> Tested on 940de590b839:
>
> - GCC 15.2 UML slub_test: 10 passed, 2 skipped, including
> test_zero_size_alloc and test_kfree_err_ptr; NULL and ZERO_SIZE_PTR
> produced no warning, and the expected kfree(ERR_PTR) warning was
> suppressed and counted
> - GCC 15.2 matched x86_64 builds immediately before and after patch 3:
> kfree grew by 15 bytes; __check_object_size gained a 13-byte
> comparison on its normal path and a 23-byte cold abort path
> - GCC 15.2 x86_64 defconfig with HARDENED_USERCOPY: build and normal
> QEMU boot passed; the committed LKDTM selftest matched the distinct
> ERR_PTR diagnostic after reaching usercopy_abort()
> - Clang 21.1.8 armv5 UBSAN_ALIGNMENT: no alignment-assumption or
> slab.h report; generated code retains the exact comparison and
> single evaluation
> - GCC 15.2 MIPS64 big-endian noncoherent build passed with
> ARCH_KMALLOC_MINALIGN equal to 128
> - Microchip SAM9X75 hardware (armv5, ARCH_KMALLOC_MINALIGN=32):
> ZERO_SIZE_PTR was 0x20; zero-size allocation, ksize(), and free
> passed with no boot splats
> - Raspberry Pi 400 hardware (arm64, Cortex-A72,
> ARCH_KMALLOC_MINALIGN=8): ZERO_SIZE_PTR remained 0x10; the ERR_PTR
> KUnit and hardened-usercopy rejection paths passed with no boot
> splats; the revised diagnostic was retested under x86_64 QEMU
>
> ---
> v2 -> v3:
>
> - document why patch 2's sentinel-derived low-address window in hardened
> usercopy was incidental
> - add new patch 3 to warn and return when kfree() receives an ERR_PTR
> and reject ERR_PTR values in hardened usercopy
> - replace patch 4's pointer-cast static assertion with a run-time KUnit
> expectation
> - add new patch 5 with KUnit and LKDTM coverage for patch 3
>
> v2: https://lore.kernel.org/r/20260811141240.62519-1-kmehltretter@gmail.com
> 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 (5):
> slab: align ZERO_SIZE_PTR to ARCH_KMALLOC_MINALIGN
> slab: check for ZERO_SIZE_PTR by exact match
> slab: handle ERR_PTR values in kfree and hardened usercopy
> slab: test zero-size allocations in slub_kunit
> slab: test ERR_PTR handling in kfree and hardened usercopy
>
> drivers/misc/lkdtm/usercopy.c | 27 +++++++++++
> include/linux/slab.h | 20 ++++++--
> lib/tests/slub_kunit.c | 61 +++++++++++++++++++++++++
> mm/slub.c | 3 ++
> mm/usercopy.c | 3 ++
> tools/testing/selftests/lkdtm/tests.txt | 1 +
> 6 files changed, 112 insertions(+), 3 deletions(-)
>
>
> base-commit: 940de590b839f71d6dc846160534bf202401b8b7
> --
> 2.53.0
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-09-04 11:26 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-03 20:37 [PATCH v3 0/5] slab: ZERO_SIZE_PTR alignment and ERR_PTR hardening Karl Mehltretter
2026-09-03 20:37 ` [PATCH v3 1/5] slab: align ZERO_SIZE_PTR to ARCH_KMALLOC_MINALIGN Karl Mehltretter
2026-09-03 20:37 ` [PATCH v3 2/5] slab: check for ZERO_SIZE_PTR by exact match Karl Mehltretter
2026-09-03 20:37 ` [PATCH v3 3/5] slab: handle ERR_PTR values in kfree and hardened usercopy Karl Mehltretter
2026-09-04 9:01 ` Vlastimil Babka (SUSE)
2026-09-03 20:37 ` [PATCH v3 4/5] slab: test zero-size allocations in slub_kunit Karl Mehltretter
2026-09-03 20:37 ` [PATCH v3 5/5] slab: test ERR_PTR handling in kfree and hardened usercopy Karl Mehltretter
2026-09-04 11:25 ` [PATCH v3 0/5] slab: ZERO_SIZE_PTR alignment and ERR_PTR hardening Harry Yoo
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.