Back to Blog
critical SEVERITY8 min read

Heap Corruption in Dynamic App Loaders: How Unvalidated Binary Size Fields Open the Door to Memory Attacks

A critical heap corruption vulnerability was discovered in a dynamic application loader where size values read directly from untrusted binary files were used to drive memory operations without any bounds validation. An attacker supplying a crafted app binary could overflow heap buffers, corrupt memory, and potentially achieve arbitrary code execution. The fix introduces strict bounds checks before memory operations and replaces unsafe allocation patterns with overflow-safe alternatives.

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

Answer Summary

This is a critical heap corruption vulnerability (CWE-122, CWE-190) in a C dynamic application loader, where size fields read from untrusted binary files were used directly in memory allocation and copy operations without any bounds validation. An attacker who controls the binary file can supply crafted size values that overflow heap buffers, corrupt adjacent memory, and potentially achieve arbitrary code execution. The fix adds strict upper-bound checks on every size field before it is used in a memory operation, and replaces raw `malloc(size)` patterns with overflow-safe allocation helpers that verify the computed allocation size does not wrap around.

Vulnerability at a Glance

cweCWE-122 (Heap-based Buffer Overflow), CWE-190 (Integer Overflow or Wraparound)
fixAdd strict bounds checks and overflow-safe allocation before every memory operation driven by file-supplied sizes
riskArbitrary code execution, heap corruption, denial of service
languageC
root causeSize values from untrusted binary files used in malloc/memcpy without bounds validation
vulnerabilityHeap Buffer Overflow / Integer Overflow in Dynamic App Loader

Heap Corruption in Dynamic App Loaders: How Unvalidated Binary Size Fields Open the Door to Memory Attacks

Introduction

When a system loads executable code at runtime — think plugin systems, firmware updaters, or embedded app frameworks — it must parse metadata from the binary itself to know how much memory to allocate and how much data to copy. This is a powerful capability, but it comes with a sharp edge: what happens when that metadata is wrong, or worse, deliberately crafted by an attacker?

This post walks through a critical heap corruption vulnerability found in a dynamic application loader written in C, explains exactly how it could be exploited, and breaks down the code changes that fixed it. Whether you write embedded firmware, OS-level loaders, or any C code that processes external data, the lessons here apply directly to your work.


The Vulnerability Explained

What Went Wrong

The vulnerability lives in component/dynamic_app/app_loader/app_loader.c, a component responsible for loading application binaries into memory at runtime. To do its job, the loader reads a binary image (the "tinf image") and extracts metadata fields from it — things like data_size, rel_data_size, got_entries, and bss_size.

The critical mistake: these size values were read directly from the untrusted binary and used immediately in memory operations without any validation.

Here's the problematic pattern that appeared in two separate places:

// BEFORE: No validation — apps_info_size is used blindly
memset(apps_info, 0x0, apps_info_size);
memcpy(apps_info, app_data_base + 1, apps_info_size);

And separately, when calculating how much heap memory to allocate:

// BEFORE: Integer overflow risk, then unsafe allocation
uint32_t app_data_size = tinf->rel_data_size + tinf->data_size +
                         tinf->got_entries + tinf->bss_size;

StackType_t *app_rel_data_base = malloc((app_data_size + DEFAULT_STACK_SIZE) * 4);
memset(app_rel_data_base, 0x0, (app_data_size + DEFAULT_STACK_SIZE) * 4);

There are actually two distinct vulnerabilities here working in combination:

Vulnerability 1: Missing Bounds Check Before memcpy

The apps_info_size parameter represents the size of a destination buffer. The tinf->data_size field comes from the binary being loaded. If apps_info_size is larger than what tinf->data_size actually covers, the memcpy will read beyond the valid source region — or the memset will zero out memory beyond the intended destination buffer.

An attacker can craft a binary where data_size is set to a small value while the loader's apps_info_size is large, causing the copy to walk off the end of the source data into adjacent heap memory.

Vulnerability 2: Integer Overflow in Size Calculation

The second issue is more subtle but equally dangerous:

uint32_t app_data_size = tinf->rel_data_size + tinf->data_size +
                         tinf->got_entries + tinf->bss_size;

// Then later:
malloc((app_data_size + DEFAULT_STACK_SIZE) * 4);

If an attacker sets the individual size fields in the binary to large values, their sum can wrap around to zero (or a small number) due to 32-bit unsigned integer overflow. The malloc then allocates a tiny buffer, but the subsequent memset uses the original (large) computed size — writing far beyond the end of the allocated region.

This is a classic integer overflow leading to heap buffer overflow.

Real-World Impact

On an embedded system (which this loader targets, given the use of FreeRTOS primitives like StackType_t and xTaskCreate), heap corruption can lead to:

  • Arbitrary code execution — overwriting function pointers or vtables stored on the heap
  • Privilege escalation — corrupting security-sensitive data structures
  • Denial of service — crashing the device or causing undefined behavior
  • Persistent compromise — on devices with persistent storage, a corrupted heap can persist across reboots

The attack surface is anyone who can supply an app binary to the loader. Depending on the system, this might be a firmware update mechanism, a removable storage device, or a network-delivered payload.

Attack Scenario

Imagine an IoT device that supports third-party "apps" loaded from an SD card. An attacker places a crafted binary on the SD card with the following fields set maliciously:

tinf->data_size     = 0x00000001   // Tiny — only 1 word of data
tinf->rel_data_size = 0x3FFFFFFF   // Huge
tinf->got_entries   = 0x3FFFFFFF   // Huge
tinf->bss_size      = 0x00000002   // Small

The sum rel_data_size + data_size + got_entries + bss_size overflows a 32-bit unsigned integer, wrapping to a small value. malloc allocates, say, 16 bytes. The subsequent memset then zeros out gigabytes of address space (or until it segfaults/corrupts critical data). Game over.


The Fix

The patch addresses both vulnerabilities with targeted, minimal changes that are easy to audit.

Fix 1: Bounds Validation Before memcpy

// AFTER: Validate that apps_info_size fits within the binary's data region
if (apps_info_size > (int)(tinf->data_size * sizeof(uint32_t) - sizeof(uint32_t))) {
    RTK_LOGE(TAG, "App data_size too small for apps_info!\n");
    return APP_INVALID;
}
memset(apps_info, 0x0, apps_info_size);
memcpy(apps_info, app_data_base + 1, apps_info_size);

This check ensures that before any memory operation occurs, the requested apps_info_size is consistent with what the binary claims to contain. If the binary's data_size is too small to hold the data we're about to copy, we reject the binary immediately with APP_INVALID. No memory operation happens on untrusted sizes.

Fix 2: Integer Overflow Detection

// AFTER: Detect overflow before it can cause harm
uint32_t app_data_size = tinf->rel_data_size + tinf->data_size +
                         tinf->got_entries + tinf->bss_size;
uint32_t alloc_words = app_data_size + DEFAULT_STACK_SIZE;

if (alloc_words < app_data_size) {  // Overflow check
    RTK_LOGE(TAG, "App data size overflow!\n");
    return APP_INVALID;
}

This is a classic and elegant technique for detecting unsigned integer overflow in C: after the addition, check if the result is smaller than one of the operands. If alloc_words < app_data_size, the addition wrapped around, and we know an overflow occurred. The binary is rejected before any allocation happens.

Fix 3: calloc Instead of malloc + memset

// BEFORE:
StackType_t *app_rel_data_base = malloc((app_data_size + DEFAULT_STACK_SIZE) * 4);
memset(app_rel_data_base, 0x0, (app_data_size + DEFAULT_STACK_SIZE) * 4);

// AFTER:
StackType_t *app_rel_data_base = calloc(alloc_words, sizeof(uint32_t));

This change is elegant for two reasons:

  1. calloc is overflow-safe: Unlike malloc(n * size), calloc(n, size) is required by the C standard to detect multiplication overflow and return NULL if it occurs. This provides an additional safety net.

  2. Eliminates the redundant memset: calloc zero-initializes the allocation automatically. The separate memset after malloc was not only redundant — it was using the same potentially-overflowed size value, making it dangerous. Removing it eliminates that risk entirely.

The Full Diff at a Glance

Location Before After
Pre-memcpy check None Bounds validation against tinf->data_size
Allocation size (app_data_size + DEFAULT_STACK_SIZE) * 4 calloc(alloc_words, sizeof(uint32_t)) with overflow check
Zero-initialization Separate memset after malloc Built into calloc
Error handling Silent overflow Explicit APP_INVALID return with log message

Prevention & Best Practices

This vulnerability is representative of a broad class of bugs that appear whenever code processes externally-supplied binary data. Here's how to avoid them:

1. Never Trust Size Fields From External Data

Any value read from a file, network packet, or external binary must be treated as attacker-controlled. Validate every size field against known constraints before using it in any memory operation.

// Pattern: Always validate before use
if (external_size > MAX_ALLOWED_SIZE || external_size > buffer_capacity) {
    return ERROR_INVALID;
}
memcpy(dest, src, external_size);  // Now safe

2. Check for Integer Overflow Before Arithmetic on Sizes

When combining size values, overflow is a real risk. Use the "result smaller than operand" trick for unsigned integers:

uint32_t total = a + b;
if (total < a) { /* overflow */ return ERROR; }

For more complex calculations, consider using compiler builtins:

// GCC/Clang built-in overflow detection
uint32_t result;
if (__builtin_add_overflow(a, b, &result)) {
    return ERROR_OVERFLOW;
}

3. Prefer calloc Over malloc + memset

calloc(n, size) is safer than malloc(n * size) because:
- It handles multiplication overflow internally
- It zero-initializes, removing the need for a separate memset
- It removes one opportunity to use an overflowed size value

4. Validate Binary Formats With a Parser, Not Ad-Hoc Checks

For complex binary formats, consider writing a dedicated validation function that checks all fields before any processing begins. This "parse, then use" pattern keeps validation logic centralized and auditable.

// Better pattern: validate everything upfront
if (!validate_tinf_header(tinf, tinf_img_size)) {
    return APP_INVALID;
}
// Now proceed with confidence

5. Use Static Analysis Tools

Tools that can catch these issues automatically:

  • Coverity — detects integer overflow and buffer overflows in C/C++
  • CodeQL — GitHub's semantic code analysis, excellent for taint tracking
  • AddressSanitizer (ASan) — runtime detection of heap overflows during testing
  • UBSan — detects undefined behavior including integer overflow at runtime
  • Frama-C — formal verification for C, can prove absence of buffer overflows

6. Relevant Security Standards

This vulnerability maps to well-known weakness categories:

  • CWE-122: Heap-based Buffer Overflow
  • CWE-190: Integer Overflow or Wraparound
  • CWE-20: Improper Input Validation
  • OWASP: A03:2021 – Injection (binary injection is a form of this)
  • CERT C: Rule INT30-C (Ensure unsigned integer operations do not wrap), Rule ARR38-C (Guarantee functions do not form invalid pointers)

Conclusion

This vulnerability is a textbook example of why input validation is not optional when processing external binary data. The original code was likely written with trusted inputs in mind — but in security, "trusted" is a dangerous assumption. Any code path that can be reached with attacker-supplied data must validate every field it uses.

The fix here is minimal, targeted, and effective:
- Bounds check before copy — ensures source data is large enough
- Overflow check before allocation — ensures the computed size is sane
- calloc instead of malloc + memset — eliminates an entire class of size-confusion bugs

The broader lesson: when your code loads, parses, or processes binary data from any external source — files, network, removable media — treat every field as hostile until proven otherwise. A few lines of validation code can be the difference between a secure system and a compromised one.

Found a similar pattern in your codebase? Audit every place where externally-supplied size values flow into malloc, memcpy, memset, or similar functions. The effort is small; the payoff is enormous.


This vulnerability was identified and fixed using automated security scanning. The fix was verified by re-scan and LLM-assisted code review.

Frequently Asked Questions

What is heap corruption in a dynamic app loader?

Heap corruption occurs when a program writes data outside the bounds of a heap-allocated buffer. In a dynamic app loader, if the loader reads a size field from a binary file and uses it directly in malloc() or memcpy() without checking it first, an attacker who crafts the binary can supply an enormous or carefully chosen size value that causes the write to overflow the allocated region and corrupt adjacent heap metadata or data.

How do you prevent heap buffer overflows in C when parsing binary files?

Always validate every size field read from an external file against a known-good maximum before using it in any memory operation. Use overflow-safe allocation helpers (e.g., reallocarray() or a checked_malloc() wrapper) that detect integer wraparound, and prefer bounded copy functions (memcpy with an explicit limit) over unbounded ones.

What CWE is heap buffer overflow?

The primary CWE is CWE-122 (Heap-based Buffer Overflow). When the overflow is triggered by an integer wraparound in a size calculation, CWE-190 (Integer Overflow or Wraparound) also applies.

Is ASLR/DEP enough to prevent heap corruption exploitation?

No. While ASLR and DEP raise the bar for exploitation, they do not eliminate the risk. Modern heap-exploitation techniques (e.g., House of Force, tcache poisoning) can bypass these mitigations. The correct fix is to eliminate the vulnerability at the source with proper bounds validation.

Can static analysis detect unvalidated binary size fields in C?

Yes. Static analysis tools such as Semgrep, Coverity, and CodeQL can trace tainted data from file-read operations to malloc/memcpy sinks and flag cases where no bounds check intervenes. Orbis AppSec detected this exact pattern automatically and opened a pull request with the fix.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1139

Related Articles

critical

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

A critical buffer overflow vulnerability was discovered in `src/firmware/src/net/ieee80211.c` at line 1584, where the `ieee80211_input()` function processed raw 802.11 data frames without verifying that the incoming frame was large enough to contain a valid `ieee80211_frame` header. An attacker within wireless range could craft undersized or malformed frames to trigger memory corruption, potentially leading to remote code execution on the firmware. The fix adds a single, targeted bounds check th

critical

How buffer overflow in FuzzIxml.c sprintf() happens in C and how to fix it

A critical buffer overflow vulnerability was discovered in `fuzzer/FuzzIxml.c` where `sprintf()` wrote a PID-formatted filename into a fixed 256-byte stack buffer without any bounds checking. The fix replaces `sprintf()` with `snprintf()`, explicitly passing the buffer size to prevent any overflow. While exploitation in this specific fuzzer context requires local access, the pattern is a textbook example of CWE-120 that developers should recognize and eliminate everywhere it appears.

critical

How buffer overflow happens in C libficus.c sprintf() and how to fix it

A buffer overflow vulnerability was discovered in `runtime/ficus/impl/libficus.c` where `sprintf()` was used to write a formatted compiler version string into a fixed-size stack buffer without any bounds checking. The fix replaces both vulnerable `sprintf()` calls with `snprintf()`, passing `sizeof(cver)` as the maximum write length to ensure the buffer can never be overrun. This change eliminates the risk of stack memory corruption that could be triggered by an attacker with control over the bu

critical

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

A critical buffer overflow vulnerability was discovered in the Linux kernel's Kconfig build system where `strcpy()` copied user-controlled symbol values into a fixed-size buffer without bounds checking. This flaw in `scripts/kconfig/symbol.c` could allow attackers to overwrite adjacent memory when processing malicious Kconfig files. The fix replaces the unsafe `strcpy()` with `memcpy()` using explicit length calculations.

high

How integer overflow in malloc happens in C bipartite matching and how to fix it

A high-severity integer overflow vulnerability was discovered in the bipartite matching algorithm implementation where unchecked multiplication operations for memory allocation could wrap around, causing undersized buffer allocations and subsequent heap overflow. The fix replaces vulnerable `malloc(sizeof(int) * V)` patterns with safe `calloc(V, sizeof(int))` calls and adds proper bounds validation to prevent exploitation.

high

How buffer overflow via sprintf() happens in C networking code and how to fix it

A high-severity buffer overflow vulnerability was discovered in `profile.c` where `sprintf()` was used to format server addresses without any bounds checking. An attacker who could influence the `SERVER_BASE_PORT` value or trigger integer overflow in the port calculation could write beyond the `server_address` buffer. The fix replaces `sprintf()` with `snprintf()` using explicit buffer size limits at both call sites (lines 99 and 220).