Memory safety: buffer overflow / data corruption in slab allocator krealloc/kvrealloc path
Description
The commit fixes a memory-safety vulnerability in the slab allocator's reallocation paths (k(v)ealloc/krealloc). Previously, when reallocating a slab object, especially when shrinking the size or relocating to a different NUMA node, the code could copy orig_size bytes into a newly allocated buffer sized new_size. If new_size < orig_size, this could overflow the new buffer or corrupt adjacent memory, leading to data loss or a kernel crash. The patch applies min(new_size, orig_size) (and related changes) to ensure only the valid amount of data is copied, and preserves allocation behavior when moving to another node. This mitigates a potential buffer overflow and data-corruption pathway in kvrealloc/krealloc.
Impact scope: memory safety in the slab allocator’s reallocation logic across NUMA boundaries or shrinking allocations. The fix is foundational and addresses a real vulnerability in the pre-fix code path.
Proof of Concept
Note: The following PoC is a theoretical demonstration intended to illustrate the vulnerability and the effect of the fix. It is not executable user-space code and assumes a kernel environment capable of exercising the krealloc/kvrealloc path on a multi-NUMA system.
Preconditions:
- A Linux kernel built with slab/slub allocator (mm/slub.c) that contains the vulnerable krealloc/kvrealloc path (pre-fix).
- A multi-NUMA system or a scenario where a reallocation could relocate to a different node or shrink the object size.
- A kernel module or in-kernel test harness that can call krealloc with a deliberate new_size < orig_size and, if possible, enable relocation via GFP_THISNODE-style behavior.
Theoretical exploit scenario (before the fix):
1) Allocate a slab object of orig_size bytes using krealloc (or kmalloc followed by krealloc) on a given NUMA node.
2) Initialize the object with known data.
3) Call krealloc with new_size < orig_size (and request relocation to a different NUMA node if the allocator path allows it via GFP_THISNODE).
4) In the vulnerable path, the code would copy orig_size bytes into a buffer sized new_size, causing a buffer overflow into adjacent memory or data corruption of nearby allocations.
5) Observe memory corruption, kernel crash, or potential data leakage depending on what lies adjacent to the allocated object.
Theoretical fix effect (after the patch):
- The copy operation uses min(new_size, orig_size) so only the valid portion is copied, preventing overflow when shrinking or relocating.
Minimal illustrative code (pseudo-kernel, not recommended to run as-is):
// Pseudo-kernel code to illustrate the vulnerability and fix (not portable or executable)
// In actual kernel code, krealloc(p, new_size, align, flags) is used
void vulnerability_poc(void) {
size_t orig_size = 256; // size of the original allocation
size_t new_size = 32; // shrinking, potentially triggering overflow in the pre-fix path
void *p = krealloc(NULL, orig_size, 0, GFP_KERNEL); // equivalent to kmalloc(orig_size, GFP_KERNEL)
if (!p) return;
// Initialize data
memset(p, 0xAA, orig_size);
// Attempt relocation to another NUMA node if possible (requires appropriate GFP flags)
// On systems where relocation is permitted via GFP_THISNODE, this can exercise the problematic path
void *q = krealloc(p, new_size, 0, GFP_KERNEL | GFP_THISNODE);
if (!q) {
// handle failure
kfree(p);
return;
}
// Without the fix, a memcpy-like copy could overflow the new buffer.
// With the fix, only min(orig_size, new_size) bytes are copied.
// Validate memory state, then clean up
kfree(q);
}
Notes:
- This PoC is intended to conceptually illustrate the vulnerable operation and the preventive min(...) introduced by the patch. Real-world exploitation would require precise kernel instrumentation and is not advised on production systems.
Commit Details
Author: Linus Torvalds
Date: 2026-04-24 16:39 UTC
Message:
Merge tag 'slab-for-7.1-fix' of git://git.kernel.org/pub/scm/linux/kernel/git/vbabka/slab
Pull slab fix from Vlastimil Babka:
- A stable fix for k(v)ealloc() where reallocating on a different node
or shrinking the object can result in either losing the original data
or a buffer overflow (Marco Elver)
* tag 'slab-for-7.1-fix' of git://git.kernel.org/pub/scm/linux/kernel/git/vbabka/slab:
slub: fix data loss and overflow in krealloc()
Triage Assessment
Vulnerability Type: Memory safety (buffer overflow / data corruption)
Confidence: HIGH
Reasoning:
The commit fixes a memory safety issue in k(v)ealloc()/krealloc() related to reallocating on a different NUMA node or shrinking the object, which could lead to data loss or a buffer overflow. It adjusts allocation behavior and uses min(...) when copying data to the new buffer to prevent overreads/writes, addressing a potential vulnerability in memory handling.
Verification Assessment
Vulnerability Type: Memory safety: buffer overflow / data corruption in slab allocator krealloc/kvrealloc path
Confidence: HIGH
Affected Versions: v7.0-rc6 and earlier (mm/slub.c krealloc path); patched in the 7.1 slab fix backport
Code Diff
diff --git a/mm/slub.c b/mm/slub.c
index 92362eeb13e5b7..161079ac5ba128 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -6645,16 +6645,6 @@ __do_krealloc(const void *p, size_t new_size, unsigned long align, gfp_t flags,
if (!kasan_check_byte(p))
return NULL;
- /*
- * If reallocation is not necessary (e. g. the new size is less
- * than the current allocated size), the current allocation will be
- * preserved unless __GFP_THISNODE is set. In the latter case a new
- * allocation on the requested node will be attempted.
- */
- if (unlikely(flags & __GFP_THISNODE) && nid != NUMA_NO_NODE &&
- nid != page_to_nid(virt_to_page(p)))
- goto alloc_new;
-
if (is_kfence_address(p)) {
ks = orig_size = kfence_ksize(p);
} else {
@@ -6673,6 +6663,16 @@ __do_krealloc(const void *p, size_t new_size, unsigned long align, gfp_t flags,
}
}
+ /*
+ * If reallocation is not necessary (e. g. the new size is less
+ * than the current allocated size), the current allocation will be
+ * preserved unless __GFP_THISNODE is set. In the latter case a new
+ * allocation on the requested node will be attempted.
+ */
+ if (unlikely(flags & __GFP_THISNODE) && nid != NUMA_NO_NODE &&
+ nid != page_to_nid(virt_to_page(p)))
+ goto alloc_new;
+
/* If the old object doesn't fit, allocate a bigger one */
if (new_size > ks)
goto alloc_new;
@@ -6707,7 +6707,7 @@ __do_krealloc(const void *p, size_t new_size, unsigned long align, gfp_t flags,
if (ret && p) {
/* Disable KASAN checks as the object's redzone is accessed. */
kasan_disable_current();
- memcpy(ret, kasan_reset_tag(p), orig_size ?: ks);
+ memcpy(ret, kasan_reset_tag(p), min(new_size, (size_t)(orig_size ?: ks)));
kasan_enable_current();
}
@@ -6941,7 +6941,7 @@ void *kvrealloc_node_align_noprof(const void *p, size_t size, unsigned long alig
if (p) {
/* We already know that `p` is not a vmalloc address. */
kasan_disable_current();
- memcpy(n, kasan_reset_tag(p), ksize(p));
+ memcpy(n, kasan_reset_tag(p), min(size, ksize(p)));
kasan_enable_current();
kfree(p);