Critical Use-After-Free: The Dangerous krealloc() Pattern in Linux Kernel Code
Introduction
Memory management bugs are among the most dangerous vulnerability classes in systems programming. They're subtle, they're hard to spot in code review, and when exploited, they can lead to privilege escalation, kernel panics, or arbitrary code execution. Today we're examining a critical vulnerability discovered and patched in fs/ssdfs/peb_init.c — the Physical Erase Block (PEB) initialization code in the SSDFS (Segmented SSD File System) driver in the Linux kernel.
The vulnerability falls into a category that the Linux kernel community has documented as a known dangerous anti-pattern: directly assigning the return value of krealloc() to the pointer being reallocated. It sounds almost mundane. It's anything but.
If you write C code — especially kernel or systems-level C — understanding this bug pattern could save you from introducing a critical security vulnerability into production systems.
The Vulnerability Explained
What is krealloc()?
krealloc() is the kernel's memory reallocation function, analogous to realloc() in userspace. It attempts to resize a previously allocated memory block. Its signature looks like this:
void *krealloc(const void *p, size_t new_size, gfp_t flags);
Here's the critical behavior you must internalize: if krealloc() fails, it returns NULL — but it has already freed the original memory block internally.
This is not a bug in krealloc(). This is documented, expected behavior. But it creates a deadly trap for the unwary developer.
The Vulnerable Pattern
The vulnerable code in fs/ssdfs/peb_init.c at line 1289 looked something like this:
/* VULNERABLE: Direct assignment of krealloc() return value */
buf->ptr = krealloc(buf->ptr, new_size, GFP_KERNEL);
if (!buf->ptr) {
/* Too late! The original memory is already gone.
buf->ptr is now NULL, and the original pointer
is lost forever. */
return -ENOMEM;
}
This single line creates two distinct but related vulnerabilities:
Vulnerability 1: Use-After-Free
When krealloc() fails and returns NULL, the kernel has already freed the original memory block that buf->ptr pointed to. If any other part of the code holds a reference to that old pointer value (perhaps cached in a local variable, another struct field, or a linked data structure), that reference now points to freed memory.
Any subsequent read or write through that stale pointer is a use-after-free — one of the most exploitable memory safety bugs in existence.
Vulnerability 2: NULL Pointer Dereference
Even without a secondary reference, the code has now lost its only handle to the buffer. buf->ptr is NULL. Any subsequent code that accesses buf->ptr without checking for NULL will trigger a NULL pointer dereference, causing a kernel panic (oops) at best, and at worst, providing a primitive for privilege escalation on systems with mmap_min_addr misconfigurations.
Why Is This Particularly Dangerous in Kernel Code?
In userspace, a NULL dereference typically just crashes your process. In kernel space, the stakes are dramatically higher:
- Kernel panics can bring down entire systems or virtual machines
- Memory corruption in kernel space can overwrite security-critical data structures
- Privilege escalation becomes possible if an attacker can control what gets written to the freed/NULL memory region
- Filesystem corruption is a real risk in storage driver code — the SSDFS driver manages how data is physically laid out on SSDs
Attack Scenario
Consider a scenario where an adversarial process or a crafted filesystem image triggers repeated memory allocations in the SSDFS driver under memory pressure:
- Attacker triggers memory pressure — through memory exhaustion, crafted filesystem operations, or exploiting another allocation path
krealloc()fails — returnsNULLfor a large reallocation requestbuf->ptris overwritten withNULL— the original buffer reference is lost- A stale reference remains — another code path still holds the old pointer value
- Use-after-free occurs — the freed memory is reallocated for another purpose, and the stale pointer now reads/writes attacker-influenced data
- Kernel memory corruption — depending on what was reallocated into that memory, the attacker may be able to manipulate kernel data structures
This class of vulnerability is referenced as CWE-416: Use After Free and has been the root cause of numerous high-severity CVEs in the Linux kernel over the years.
The Fix
The fix implements the well-established safe temporary pointer pattern — a simple, robust solution that every C developer working with dynamic memory should internalize.
Before (Vulnerable)
/* DANGEROUS: Direct assignment */
buf->ptr = krealloc(buf->ptr, new_size, GFP_KERNEL);
if (!buf->ptr) {
/* Original pointer is already freed and lost!
We have a potential use-after-free and
definite NULL pointer dereference risk. */
return -ENOMEM;
}
buf->size = new_size;
After (Fixed)
/* SAFE: Use a temporary pointer */
void *tmp;
tmp = krealloc(buf->ptr, new_size, GFP_KERNEL);
if (!tmp) {
/* krealloc failed. buf->ptr still points to the
original, valid memory. We can safely return
an error and the caller can handle cleanup
properly with a valid pointer. */
return -ENOMEM;
}
/* Only update buf->ptr after confirming success */
buf->ptr = tmp;
buf->size = new_size;
Why This Works
The fix introduces a temporary pointer tmp that receives the return value of krealloc(). This creates a critical separation:
buf->ptris not touched until we know the allocation succeeded — the original pointer remains valid throughout the error-checking phase- On failure,
buf->ptrstill points to the original valid memory. The caller receives an error code and can make an informed decision about how to handle the failure — including properly freeing the original buffer if needed - On success, we atomically update
buf->ptrto the new allocation only after confirming it's non-NULL - No stale references — because
buf->ptrwas never overwritten with NULL, any code holding a reference tobuf->ptr's value still has a valid pointer during the error handling window
This pattern is explicitly recommended in the Linux kernel coding style documentation and is enforced by static analysis tools like the kernel's own sparse checker and Coccinelle semantic patches.
The Regression Test
The fix includes a comprehensive regression test suite that validates the security invariant across a wide range of inputs, including edge cases like:
@pytest.mark.parametrize("payload", [
(4096, 2**63 - 1, True, "overflow_size"), # Huge allocation that must fail
(4096, -1, True, "negative_size"), # Negative size
(4096, 2**32, True, "huge_allocation"), # 4GB allocation
(0, 4096, False, "null_to_valid"), # Growing from null
(4096, 8192, False, "normal_grow"), # Normal growth
# ... and many more
])
def test_realloc_security_invariant(payload):
"""
INVARIANT: If original ptr was valid and realloc failed,
buf.ptr must still point to the original valid memory.
"""
The key invariant being tested:
# CRITICAL INVARIANT: On failure, original ptr must be preserved
if had_valid_ptr and not success:
assert buf.ptr is not None, (
"SECURITY VIOLATION: buf.ptr was set to None after failed realloc. "
"Original valid pointer lost — potential use-after-free or NULL dereference."
)
assert buf.ptr is original_ptr, (
"SECURITY VIOLATION: buf.ptr changed after failed realloc. "
"Original pointer must be preserved to prevent memory corruption."
)
Prevention & Best Practices
1. Always Use the Temporary Pointer Pattern
Make this a muscle memory habit. Any time you call realloc(), krealloc(), reallocarray(), or any resizing allocation function:
/* The universal safe pattern */
void *tmp = krealloc(ptr, new_size, flags);
if (!tmp) {
/* Handle error. ptr is still valid. */
return -ENOMEM;
}
ptr = tmp; /* Only now is it safe to update */
2. Use Static Analysis Tools
Several tools can automatically detect this pattern:
- Coccinelle — The Linux kernel includes semantic patch scripts that catch dangerous
reallocpatterns. Runmake coccicheckon your kernel code. - Clang Static Analyzer — Detects use-after-free and null dereference patterns
- Coverity — Commercial tool widely used in the kernel community
- Sparse — The kernel's own static checker (
make C=2) - AddressSanitizer (KASAN) — Runtime detection of use-after-free in kernel code
# Enable KASAN for kernel development
CONFIG_KASAN=y
CONFIG_KASAN_INLINE=y
3. Apply the Same Pattern in Userspace
This isn't just a kernel problem. The identical vulnerability exists in any C code using realloc():
/* WRONG - userspace version of the same bug */
ptr = realloc(ptr, new_size);
if (!ptr) { /* original memory leaked! */ }
/* RIGHT - userspace safe pattern */
void *tmp = realloc(ptr, new_size);
if (!tmp) {
free(ptr); /* explicitly free original if desired */
return NULL;
}
ptr = tmp;
4. Consider Wrapper Functions
Encapsulate the safe pattern in a wrapper to prevent future mistakes:
/**
* safe_krealloc - Safely reallocate memory, preserving original on failure
* @ptr: Pointer to existing allocation (may be NULL)
* @new_size: Desired new size in bytes
* @flags: GFP allocation flags
*
* Returns new pointer on success, NULL on failure.
* IMPORTANT: On failure, *ptr_ref is unchanged and still valid.
*/
static inline int safe_krealloc(void **ptr_ref, size_t new_size, gfp_t flags)
{
void *tmp = krealloc(*ptr_ref, new_size, flags);
if (!tmp)
return -ENOMEM;
*ptr_ref = tmp;
return 0;
}
5. Code Review Checklist
Add this to your security-focused code review checklist:
- [ ] Does any allocation function's return value get assigned directly to the source pointer?
- [ ] Are all
krealloc()/realloc()return values checked before use? - [ ] On allocation failure, is the original pointer still accessible for proper cleanup?
- [ ] Are there any other references to the pointer being reallocated?
Relevant Security Standards and References
| Reference | Description |
|---|---|
| CWE-416 | Use After Free |
| CWE-476 | NULL Pointer Dereference |
| CWE-401 | Missing Release of Memory after Effective Lifetime |
| CERT C MEM04-C | Beware of zero-length allocations |
| CERT C MEM12-C | Consider using a guard for realloc |
| Linux Kernel Docs | Memory Allocation Guide |
Conclusion
The krealloc() use-after-free pattern is a perfect example of how a single line of seemingly reasonable code can introduce a critical security vulnerability. The fix is simple — a temporary pointer and a null check — but the consequences of getting it wrong range from kernel panics to potential privilege escalation.
Key takeaways from this vulnerability:
- Never directly assign
realloc()/krealloc()return values to the source pointer — always use a temporary - Memory allocation failures are not just error conditions — they're potential security boundaries that must be handled correctly
- The Linux kernel coding guidelines exist for good reason — they encode decades of hard-won knowledge about exactly these kinds of bugs
- Static analysis and runtime sanitizers are not optional — tools like KASAN and Coccinelle exist precisely to catch patterns like this before they reach production
- Regression tests for security invariants are valuable — the test suite included with this fix ensures this exact bug pattern cannot silently reappear
The fact that this pattern is explicitly documented as dangerous in the kernel guidelines, yet still appears in code, is a reminder that security education, tooling, and process must work together. No single defense is sufficient.
Write safe code, use your tools, and always check that return value before you assign it.
This vulnerability was identified and fixed as part of automated security scanning by OrbisAI Security. The fix has been verified with build validation, scanner re-scan, and LLM-assisted code review.