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 orbisai0security
May 28, 2026

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.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1139

Related Articles

critical

Heap Buffer Overflow in Audio Ring Buffer: How a Missing Bounds Check Could Crash Your App

A critical heap buffer overflow vulnerability was discovered in `audio_backend.c`, where the audio ring buffer's `memcpy` operations lacked bounds validation before writing PCM data. Without checking that incoming data sizes fell within the allocated buffer's capacity, a maliciously crafted audio file could corrupt adjacent heap memory, potentially enabling arbitrary code execution. The fix adds a concise pre-flight validation guard that rejects out-of-range write requests before any memory oper

critical

Heap Overflow in TOML Parser: How Integer Overflow Leads to Memory Corruption

A critical heap buffer overflow vulnerability was discovered and patched in the centitoml TOML parser, where missing integer overflow validation on a `MALLOC(len+1)` call could allow an attacker to trigger memory corruption via a crafted TOML configuration file. The vulnerability (CWE-190) is reachable through community-distributed mod or map files that the game loads from its `config/` directory, making it a realistic attack vector for remote code execution. A targeted one-line guard now preven

critical

Critical Integer Sign Bug in runtime_malloc(): How a Missing Check Enables Heap Corruption

A critical vulnerability in `runtime/zenith_runtime.c` allowed the `runtime_malloc()` function to accept negative size values, which when cast to an unsigned type could either trigger a massive failed allocation or produce a dangerously undersized buffer ripe for overflow. The fix adds a simple but essential guard clause that rejects non-positive sizes before they ever reach `malloc()`. Left unpatched, this class of bug can lead to heap metadata corruption, process crashes, or even arbitrary cod

critical

Heap Buffer Overflow in Path Normalization: How Two Unsafe memcpy Calls Almost Became a Critical Exploit

A critical heap buffer overflow vulnerability was discovered and patched in `src/aux.c`, where two `memcpy` calls in a path normalization function copied data into buffers without verifying sufficient capacity. An attacker capable of influencing the current working directory path — through deeply nested directories or crafted symlinks — could trigger heap corruption with potentially severe consequences. The fix introduces an integer overflow guard that ensures buffer allocation math cannot wrap

critical

Critical Buffer Overflow in iiod Parser: How a Missing Bounds Check Opened the Door to Remote Code Execution

A critical buffer overflow vulnerability was discovered in the `iiod` parser's `yy_input()` function, where an off-by-one bounds check allowed an oversized network input stream to overflow a fixed-size buffer, potentially overwriting adjacent stack or heap memory. Because this code path is reachable from the network without authentication, a remote attacker could exploit this flaw to achieve arbitrary code execution. The fix tightens the bounds enforcement and ensures the function returns the co

critical

Integer Overflow to Heap Buffer Overflow: How a Missing Size Check Almost Took Down an Embedded Web Server

A critical integer overflow vulnerability (CWE-190 → CWE-122) was discovered and fixed in an embedded ESP web server, where the HTTP Content-Length header value was cast to a signed integer and used directly in a `malloc()` call without proper size validation. On 32-bit systems, a crafted request with a maximum-sized Content-Length value could cause the allocation size to wrap to zero, allowing an attacker to overflow the heap with arbitrary data. The fix correctly validates the signed header va