Back to Blog
medium SEVERITY8 min read

Fixing NULL Pointer Dereference in eMMC Memory Allocation

A high-severity NULL pointer dereference vulnerability was discovered and fixed in embedded eMMC storage handling code, where unchecked `malloc` and `calloc` return values could allow an attacker with a crafted eMMC image to crash the host process. The fix adds proper NULL checks after every memory allocation, preventing exploitation through maliciously oversized partition size fields. This type of vulnerability is surprisingly common in systems-level C code and serves as a reminder that defensi

O
By Orbis AppSec
Published May 10, 2026Reviewed June 3, 2026

Answer Summary

This is a NULL pointer dereference vulnerability (CWE-476) in C-based eMMC storage handling code, where `malloc` and `calloc` return values were used without checking whether they returned NULL. An attacker with the ability to supply a crafted eMMC image — for example, one containing oversized partition size fields — could trigger allocation failure and immediately crash the host process. The fix adds explicit NULL checks after every memory allocation call, returning an error to the caller instead of proceeding with a NULL pointer.

Vulnerability at a Glance

cweCWE-476
fixAdded NULL checks after every dynamic memory allocation, returning an error code on failure
riskHost process crash (denial of service) via crafted eMMC image with oversized partition size fields
languageC
root causeReturn values of `malloc` and `calloc` were used immediately without checking for NULL
vulnerabilityNULL Pointer Dereference

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 returns NULL)
  • 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_addr protections, 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:

  1. Input Validation: By checking partition_size against 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.

  2. Allocation Failure Handling: By checking the return value of malloc/calloc and 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.


Prevention & Best Practices

1. Always Check Allocator Return Values

This cannot be overstated. In C, there is no automatic exception for failed allocations. Make it a rule:

// ✅ The correct pattern — always
void *ptr = malloc(size);
if (ptr == NULL) {
    // Handle error
    return ERROR_CODE;
}

Some teams use wrapper functions to enforce this:

// Safe malloc wrapper that aborts or returns error on failure
void *safe_malloc(size_t size) {
    if (size == 0) {
        return NULL;  // Explicit handling of zero-size
    }
    void *ptr = malloc(size);
    if (ptr == NULL) {
        // Log, alert, or abort depending on context
        abort();  // For critical embedded systems where partial state is dangerous
    }
    return ptr;
}

⚠️ Note: Using abort() in a wrapper is appropriate for some embedded contexts but not for servers or user-facing applications where graceful degradation is preferred.

2. Validate Sizes from Untrusted Sources

Any size value derived from external input — a file, network packet, hardware register, or user input — must be validated before use in an allocation:

#define MAX_SAFE_PARTITION_SIZE (256 * 1024 * 1024)  // 256 MB upper bound

if (partition_size > MAX_SAFE_PARTITION_SIZE) {
    return -EINVAL;  // Reject before allocation attempt
}

3. Check for Integer Overflow in Size Calculations

A related vulnerability: when computing allocation sizes with multiplication (e.g., count * element_size), integer overflow can wrap the value to a small number, causing under-allocation followed by a heap buffer overflow:

// DANGEROUS: integer overflow possible
size_t total = count * sizeof(struct entry);  // Could overflow!
void *buf = malloc(total);

// SAFE: use checked multiplication
if (count > SIZE_MAX / sizeof(struct entry)) {
    return -EINVAL;  // Would overflow
}
size_t total = count * sizeof(struct entry);
void *buf = malloc(total);

On modern systems, consider using reallocarray() or similar checked-multiplication allocators.

4. Use Static Analysis Tools

Several tools can automatically detect unchecked allocator return values:

Tool Type Notes
Coverity Commercial SAST Excellent NULL dereference detection
Clang Static Analyzer Free SAST scan-build catches many cases
cppcheck Free SAST Good for embedded C
PVS-Studio Commercial Strong memory safety analysis
Valgrind Dynamic Catches runtime NULL dereferences
AddressSanitizer (ASan) Dynamic Fast runtime detection

Add these to your CI/CD pipeline:

# Example: GitHub Actions with cppcheck
- name: Run cppcheck
  run: cppcheck --enable=all --error-exitcode=1 src/

5. Adopt Safer Memory Patterns Where Possible

In new code, consider patterns that reduce the risk:

  • Use arena/pool allocators for fixed-size objects where allocation failure can be detected once at pool creation time.
  • Pre-allocate buffers of maximum expected size at startup when dealing with bounded inputs.
  • Consider Rust or C++ with RAII for new components where memory safety is critical — though this isn't always feasible in embedded contexts.

Relevant Security Standards

  • CWE-476: NULL Pointer Dereference — directly applicable here
  • CWE-252: Unchecked Return Value — the root cause
  • CWE-789: Memory Allocation with Excessive Size Value — relevant to the attack vector
  • CERT C Rule MEM32-C: Detect and handle memory allocation errors
  • OWASP: Denial of Service: General DoS attack patterns

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.

Frequently Asked Questions

What is a NULL pointer dereference vulnerability?

A NULL pointer dereference occurs when a program attempts to read from or write to memory using a pointer that holds the value NULL (0). In C, functions like `malloc` and `calloc` return NULL when they cannot satisfy an allocation request. If the program uses that pointer without checking, it triggers undefined behavior — typically a segmentation fault and process crash.

How do you prevent NULL pointer dereference in C memory allocation?

Always check the return value of `malloc`, `calloc`, `realloc`, and similar functions before using the returned pointer. If the return value is NULL, handle the error gracefully — log it, free any already-allocated resources, and return an error code to the caller rather than proceeding.

What CWE is NULL pointer dereference?

NULL pointer dereference is classified as CWE-476: NULL Pointer Dereference. It is consistently listed among the most dangerous software weaknesses in the CWE Top 25.

Is checking errno enough to prevent NULL pointer dereference after malloc?

No. You must check the return value of `malloc`/`calloc` directly for NULL. While `errno` may be set to `ENOMEM` on some platforms, the standard and portable approach is to test the returned pointer itself before any use.

Can static analysis detect NULL pointer dereference from unchecked malloc?

Yes. Static analysis tools such as Semgrep, Coverity, Clang Static Analyzer, and cppcheck can reliably detect patterns where the return value of `malloc` or `calloc` is used without a NULL check. Orbis AppSec detected this exact pattern automatically in the eMMC handling code.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #4

Related Articles

critical

How buffer overflow via strcpy() happens in C zlib and how to fix it

A critical buffer overflow vulnerability was discovered in `general/libzlib/gzlib.c` where multiple `strcpy()` and `strcat()` calls operated without bounds checking. An attacker controlling file paths or error messages could overflow destination buffers, potentially achieving arbitrary code execution. The fix replaces these unsafe string operations with bounded `memcpy()` calls that respect pre-calculated buffer lengths.

critical

How buffer overflow in rcdevice.c request parser happens in C and how to fix it

A critical buffer overflow vulnerability was discovered in `src/main/io/rcdevice.c` at line 489, where the RC device request parser wrote incoming data into a fixed-size buffer without validating against the hard-coded maximum capacity `RCDEVICE_PROTOCOL_MAX_DATA_SIZE`. An attacker controlling the device's I/O data stream could overflow the buffer by sending a payload longer than `expectedDataLength`, potentially achieving arbitrary code execution. The fix adds a second bounds check against the

critical

How buffer overflow in fgets() happens in C and how to fix it

A critical buffer overflow vulnerability was discovered in the `readline()` function of `mdbx_load.c`, where an `fgets()` call passed a size parameter exceeding the actual allocated buffer by one byte. This off-by-one error could allow an attacker to trigger heap corruption by supplying oversized input via stdin, potentially leading to arbitrary code execution. The fix corrects the size parameter from `buf->iov_len + 1` to `buf->iov_len`, ensuring reads never exceed buffer boundaries.

critical

How heap buffer overflow happens in C dupstr() and how to fix it

A critical heap buffer overflow vulnerability was discovered in the `dupstr()` function inside `tools/strliteral.c`, where `strcpy()` was called on a heap-allocated buffer without first verifying that `malloc()` had succeeded. If `malloc()` returned `NULL`, the subsequent `strcpy()` would write into address zero — corrupting memory and potentially enabling arbitrary code execution. The fix replaces the unsafe `strcpy()` call with a `NULL` check followed by a bounds-safe `memcpy()`, closing the v

medium

How stack buffer overflow happens in C PMenu_Do_Update() and how to fix it

A stack buffer overflow in `PMenu_Do_Update()` within `src/menu/menu.c` allowed repeated unchecked `sprintf()` calls to overflow a fixed 1400-byte stack buffer when menu entries contained long text strings. The fix replaces every `sprintf(string + strlen(string), ...)` call with `snprintf(string + len, sizeof(string) - len, ...)`, tracking remaining buffer space and preventing writes beyond the buffer boundary.

critical

How out-of-bounds array access happens in C kernel modules and how to fix it

A high-severity out-of-bounds array access vulnerability was discovered in the natflow_conntrack.c kernel module where the `ct->proto.tcp.state` value was used directly as an array index without bounds validation. An attacker capable of manipulating TCP connection state could trigger reads beyond the `tcp_conntrack_names[]` array, potentially leaking kernel memory or causing system crashes. The fix adds a simple bounds check using `ARRAY_SIZE()` before array access.