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
•Technically reviewed by Anupam Mediratta•Published May 28, 2026•Reviewed 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

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

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.

Prevention and further reading

Related Articles

critical

SGX Enclave ecall_store_data memcpy Buffer Overflow in 256-Byte

The Intel SGX enclave's trusted bridge functions `ecall_store_data` and `ecall_retrieve_data` used `memcpy()` to move data into and out of a fixed 256-byte `secure_storage` buffer without validating that `data_len` fit within destination boundaries. An attacker providing oversized `data_len` values could corrupt enclave memory, breaking SGX's confidentiality guarantees.

critical

How NULL pointer dereference from unchecked malloc() happens in C and how to fix it

A critical memory safety vulnerability was discovered in `bench/tokenizer/tokenizer.c` where `malloc()` was called without checking its return value before passing the pointer to `memcpy()`. If allocation fails and `malloc()` returns NULL, the subsequent `memcpy()` writes to address zero, causing heap corruption or potential arbitrary code execution. The fix adds a single NULL check immediately after allocation, exiting cleanly on failure rather than proceeding with a dangerously invalid pointer

critical

How Stack Buffer Overflows Happen in C with sprintf() and How to Fix Them

A critical stack buffer overflow was discovered in `libuv/Learn-libuv/docs/code/tty-gravity/main.c` where `sprintf()` wrote ANSI escape sequences and user-controlled variables into a fixed 500-byte buffer without any bounds checking. An attacker controlling the `pos`, `width`, or `message` variables could overflow the stack, overwrite return addresses, and potentially achieve arbitrary code execution. The fix replaces `sprintf()` with `snprintf()` and adds explicit length validation to ensure wr

high

How insecure string copy functions happen in C and how to fix them

A high-severity buffer overflow risk was discovered in `login/main.c` where `strcpy()` was used to copy the `HOME` environment variable into a fixed-size 512-byte buffer without any bounds checking. An attacker controlling the `HOME` environment variable could overflow `pwd_file_name`, potentially corrupting memory or hijacking execution. The fix replaces the two-step `strcpy`/`strcat` pattern with a single, bounds-safe `snprintf` call.

high

How c.lang.security.use-after-free.use-after-free happens in C and how to fix it

A use-after-free vulnerability was discovered in `ggml-alloc.c` where `galloc->leaf_allocs` could be referenced after being freed during graph memory reallocation. The fix nullifies the pointer immediately after `free()` and uses explicit `sizeof(struct leaf_alloc)` to prevent undefined behavior. This defensive hardening eliminates an exploit primitive in a speech-to-text processing pipeline.

critical

How buffer overflow in Intel SGX enclave ECALLs happens in C and how to fix it

A critical buffer overflow vulnerability was discovered in Intel SGX enclave functions `ecall_encrypt_data` and `ecall_decrypt_data` in `backend/sgx/enclave/enclave.c`. The functions performed memory operations without validating that the provided buffer lengths matched the actual allocated buffer sizes, allowing an attacker controlling the untrusted application to trigger heap corruption within the secure enclave by passing oversized length parameters.