Fixing NULL Pointer Dereference in eMMC Memory Allocation: A Deep Dive
Introduction
Memory allocation failures are one of the oldest and most persistent classes of bugs in C and C++ programming. Yet, despite decades of awareness, unchecked malloc and calloc return values continue to appear in production codebases — especially in embedded systems and low-level storage drivers where developers are often focused on performance and functionality over defensive programming.
This post covers a high-severity NULL pointer dereference vulnerability (V-003) that was recently discovered and patched in src/emmc.c and src/devtools.c. The vulnerability allowed an attacker who could supply a crafted eMMC image to deliberately crash the host process by triggering a NULL pointer dereference through manipulated partition size fields.
If you write C code that handles untrusted input — especially in storage drivers, file parsers, or embedded firmware — this one's for you.
The Vulnerability Explained
What Is a NULL Pointer Dereference?
In C, malloc() and calloc() return a pointer to newly allocated heap memory. But they can also return NULL — a special sentinel value indicating that the allocation failed. This happens when:
- The system is under memory pressure (not enough free heap)
- The requested size is
0(implementation-defined, but often returnsNULL) - The requested size is extremely large (overflow or exceeds available memory)
When a program receives NULL back from an allocator and then tries to read from or write to that pointer, it triggers a NULL pointer dereference. In user-space applications, this typically causes a segmentation fault and process crash. In kernel or embedded contexts, the consequences can be even more severe.
The Vulnerable Code Pattern
The vulnerable code in emmc.c followed a pattern like this:
// VULNERABLE: No NULL check after allocation
uint8_t *partition_buffer = malloc(partition_size);
memcpy(partition_buffer, source_data, partition_size); // CRASH if malloc returned NULL
Or with calloc:
// VULNERABLE: No NULL check after calloc
struct partition_entry *entries = calloc(num_entries, sizeof(struct partition_entry));
entries[0].start_lba = read_lba(); // CRASH if calloc returned NULL
At first glance, these look harmless — especially in development environments where memory is plentiful. But the danger emerges when the size parameter is attacker-controlled.
The Attack Vector
Here's where it gets interesting. The partition_size variable in emmc.c was derived from fields in the eMMC partition table — a data structure read directly from the eMMC image. Consider this attack scenario:
Step 1: An attacker crafts a malicious eMMC image with an intentionally oversized partition size field. For example, setting partition_size to 0xFFFFFFFF (4 GB) or even 0x0.
Step 2: The host system parses this image. The code calls malloc(0xFFFFFFFF), which fails on most systems and returns NULL.
Step 3: The code proceeds without checking the return value, immediately dereferencing the NULL pointer.
Step 4: The host process crashes — a classic Denial of Service (DoS) via a crafted input file.
Attacker crafts eMMC image
│
▼
partition_size = 0xFFFFFFFF (from malicious image header)
│
▼
malloc(0xFFFFFFFF) → returns NULL
│
▼
memcpy(NULL, src, size) → SIGSEGV / process crash
│
▼
Host process terminated (DoS)
Real-World Impact
- Denial of Service: Any system that processes untrusted eMMC images (imaging tools, forensic software, emulators, provisioning systems) could be crashed on demand.
- Potential for Further Exploitation: In some architectures and contexts, NULL pointer dereferences can be escalated beyond a simple crash. Historically, on Linux kernels before
mmap_min_addrprotections, an attacker could map memory at address 0 and turn a NULL dereference into arbitrary code execution. - Supply Chain Risk: Embedded firmware provisioning tools that process eMMC images from third-party vendors are particularly exposed.
The Fix
What Changed
The fix is conceptually straightforward but critically important: every call to malloc and calloc now has its return value checked before use. If allocation fails, the code handles the error gracefully — typically by logging the failure and returning an error code — rather than proceeding with a NULL pointer.
Before (Vulnerable Pattern)
// src/emmc.c - BEFORE FIX
static int parse_partition_table(emmc_image_t *img, uint32_t partition_size) {
uint8_t *buffer = malloc(partition_size);
// No NULL check — if malloc fails, buffer is NULL
memset(buffer, 0, partition_size); // Undefined behavior / crash
read_partition_data(img, buffer, partition_size);
// ... processing ...
free(buffer);
return 0;
}
// src/devtools.c - BEFORE FIX
partition_entry_t *entries = calloc(entry_count, sizeof(partition_entry_t));
// No NULL check
for (int i = 0; i < entry_count; i++) {
entries[i].flags = DEFAULT_FLAGS; // Crash if calloc returned NULL
}
After (Fixed Pattern)
// src/emmc.c - AFTER FIX
static int parse_partition_table(emmc_image_t *img, uint32_t partition_size) {
// Validate size before allocation
if (partition_size == 0 || partition_size > MAX_PARTITION_SIZE) {
log_error("Invalid partition size: %u", partition_size);
return -EINVAL;
}
uint8_t *buffer = malloc(partition_size);
if (buffer == NULL) { // ✅ NULL check added
log_error("Failed to allocate %u bytes for partition buffer", partition_size);
return -ENOMEM;
}
memset(buffer, 0, partition_size);
read_partition_data(img, buffer, partition_size);
// ... processing ...
free(buffer);
return 0;
}
// src/devtools.c - AFTER FIX
partition_entry_t *entries = calloc(entry_count, sizeof(partition_entry_t));
if (entries == NULL) { // ✅ NULL check added
log_error("Failed to allocate partition entries table");
return -ENOMEM;
}
for (int i = 0; i < entry_count; i++) {
entries[i].flags = DEFAULT_FLAGS; // Safe — entries is guaranteed non-NULL
}
Why This Fix Works
The fix addresses the vulnerability at two levels:
-
Input Validation: By checking
partition_sizeagainst a reasonable maximum (MAX_PARTITION_SIZE) before even attempting allocation, the code rejects obviously malicious inputs early. This is a classic example of the "validate before use" principle. -
Allocation Failure Handling: By checking the return value of
malloc/callocand returning an error code instead of proceeding, the code converts a potential crash into a controlled, recoverable error. The caller can log it, report it, and continue operating.
Conclusion
The NULL pointer dereference vulnerability in emmc.c is a textbook example of how a simple, one-line oversight — failing to check a malloc return value — can open the door to denial-of-service attacks when the size parameter comes from untrusted input. The fix is equally straightforward: validate inputs, check return values, and handle errors gracefully.
Key Takeaways
- ✅ Always check the return value of
malloc,calloc,realloc, and similar allocators - ✅ Validate sizes derived from external data before using them in allocations
- ✅ Guard against integer overflow in size calculations
- ✅ Use static analysis tools in CI/CD to catch these patterns automatically
- ✅ Treat allocation failure as a real, exploitable scenario — not just a theoretical edge case
Memory safety in C requires constant vigilance. The good news is that tools, patterns, and a security-first mindset can catch the vast majority of these issues before they reach production. Make NULL checks a reflex, not an afterthought.
This vulnerability was identified and fixed as part of an automated security scanning process. Kudos to the team for the quick turnaround on the fix.
Have questions about memory safety in embedded C? Drop them in the comments below.