Back to Blog
critical SEVERITY8 min read

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

O
By orbisai0security
May 28, 2026

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

Introduction

There's a classic category of bugs in C that security engineers have been fighting for decades: the integer overflow that silently corrupts memory. These bugs are deceptively simple — a single missing bounds check, a type mismatch, or an arithmetic operation that wraps around — and yet they can be catastrophic. Today we're examining exactly this kind of vulnerability, discovered and patched in an embedded web server written in C for an ESP-based IoT device.

This post walks through a critical-severity heap buffer overflow (CWE-120/CWE-122) triggered by an integer overflow (CWE-190) in the HTTP request handling code. If you write C or C++ — especially for embedded or resource-constrained systems — this is a pattern you need to recognize and avoid.


The Vulnerability Explained

What Happened?

The web server reads HTTP POST request bodies by trusting the Content-Length header. The problematic code looked like this:

// VULNERABLE CODE (before fix)
int len = r->content_len;
if (len <= 0 || len > 4096)
    return httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, "Bad length"), ESP_FAIL;

char *buf = malloc(len + 1);  // ← Danger zone

At first glance, this looks reasonable. There's even a bounds check! But the devil is in the details.

The Integer Overflow Path

Here's the problem: r->content_len is a signed integer (int), but malloc() takes a size_t, which is an unsigned type. On a 32-bit system, SIZE_MAX is 0xFFFFFFFF (4,294,967,295).

Consider what happens when content_len is set to the maximum value of a signed 32-bit integer (INT_MAX = 0x7FFFFFFF = 2,147,483,647):

  1. The bounds check len > 4096 catches this — ✅ good so far.

But what if the underlying HTTP parser stores content_len as a value that, when cast to size_t, becomes enormous? Or what if a future refactor changes the type? The deeper architectural issue is that arithmetic on int before passing to malloc() is inherently unsafe in this context.

The classic exploit path is:

Content-Length: 4294967295   (0xFFFFFFFF as size_t)

If len is treated as size_t and equals SIZE_MAX (0xFFFFFFFF), then:

malloc(len + 1)
// = malloc(0xFFFFFFFF + 1)
// = malloc(0x100000000)
// On a 32-bit system: malloc(0)  ← wraps to zero!

malloc(0) is implementation-defined — it may return a non-NULL pointer to a zero-byte allocation, or it may return NULL. Either way, the subsequent read loop:

int rx = 0;
while (rx < len) {
    int n = httpd_req_recv(r, buf + rx, len - rx);
    rx += n;
}

...attempts to read up to len bytes (potentially gigabytes worth of intent) into a buffer that holds zero usable bytes. This is a textbook heap buffer overflow.

Real-World Impact

On an embedded ESP device, heap corruption can lead to:

  • Remote Code Execution (RCE): By carefully crafting the overflow, an attacker may control adjacent heap metadata or function pointers.
  • Denial of Service (DoS): Corrupting the heap allocator's internal structures causes a crash on the next allocation or free.
  • Sensitive Data Exposure: Overwriting adjacent heap objects may expose credentials, session tokens, or configuration data.
  • Device Takeover: On IoT devices with no memory protection (no MMU, no ASLR), heap overflows are often directly exploitable.

Attack Scenario

An attacker on the same network (or with access to the device's HTTP port) sends a crafted POST request:

POST /api/settings HTTP/1.1
Host: 192.168.1.100
Content-Length: 4294967295
Authorization: Bearer <valid-token>

<attacker-controlled payload>

The server allocates a near-zero buffer, then copies the attacker's payload into it, corrupting the heap. From here, a skilled attacker can pivot to arbitrary code execution on the microcontroller.


The Fix

What Changed?

The fix is elegant and follows secure C coding best practices precisely. Here's the corrected code:

// FIXED CODE (after patch)

// Step 1: Validate using the SIGNED type first (catches negative/absent header)
if (r->content_len <= 0 || r->content_len > 4096)
    return httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, "Bad length"), ESP_FAIL;

// Step 2: Widen to size_t AFTER validation — now safe
size_t len = (size_t)r->content_len;
char *buf = malloc((size_t)len + 1);
if (!buf)
    return httpd_resp_send_err(r, HTTPD_500_INTERNAL_SERVER_ERROR, "OOM"), ESP_FAIL;

size_t rx = 0;
while (rx < len) {
    int n = httpd_req_recv(r, buf + rx, len - rx);
    if (n <= 0) { free(buf); return ESP_FAIL; }
    rx += (size_t)n;
}
buf[len] = '\0';

The same pattern was applied to the read_json_body() helper function:

// FIXED read_json_body()
if (r->content_len <= 0 || (size_t)r->content_len > max_len) {
    httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, "Invalid body length");
    return NULL;
}
size_t len = (size_t)r->content_len;
char *buf = malloc((size_t)len + 1);

Why This Fix Works

The fix follows the "validate first, widen later" principle — a cornerstone of safe C programming:

Step Action Why It's Safe
1. Validate signed Check content_len <= 0 and content_len > 4096 using signed comparison Catches negative values and out-of-range values before any casting
2. Widen to size_t Cast to size_t only after validation passes At this point, value is guaranteed to be in [1, 4096], so size_t cast is safe
3. Use size_t throughout rx, loop comparisons, and arithmetic all use size_t Eliminates mixed signed/unsigned arithmetic that causes subtle overflow bugs

The key insight: size_t arithmetic cannot wrap to zero when the value has already been validated to be ≤ 4096. malloc(4096 + 1) will always allocate 4097 bytes — exactly what we need.


Prevention & Best Practices

1. Validate Before You Widen

Always validate untrusted integer values using their native type before casting to a wider or unsigned type:

// ❌ BAD: Cast first, validate later (or never)
size_t len = (size_t)untrusted_input;
if (len > MAX_LEN) { ... }  // Too late — overflow already possible

// ✅ GOOD: Validate first, widen after
if (untrusted_input <= 0 || untrusted_input > MAX_LEN) { ... }
size_t len = (size_t)untrusted_input;  // Now safe

2. Never Trust HTTP Headers Directly

HTTP headers are attacker-controlled. Treat Content-Length, Content-Type, and any other header as untrusted input requiring validation before use in memory operations.

3. Use size_t for Sizes and Lengths

Whenever you're working with memory sizes, buffer lengths, or loop counters tied to memory operations, use size_t. Mixing int and size_t in arithmetic is a common source of overflow bugs.

// ❌ Risky: int arithmetic fed to malloc
int len = get_length();
char *buf = malloc(len + 1);

// ✅ Safe: size_t arithmetic after validation
if (len <= 0 || len > MAX) return error;
size_t safe_len = (size_t)len;
char *buf = malloc(safe_len + 1);

4. Enable Compiler Warnings

Modern compilers can catch many of these issues. Enable and heed these flags:

# GCC / Clang
-Wall -Wextra -Wconversion -Wsign-conversion -Wshadow

# For embedded (ESP-IDF)
# Add to CMakeLists.txt:
target_compile_options(${COMPONENT_LIB} PRIVATE -Wconversion -Wsign-conversion)

5. Use Static Analysis Tools

Several tools can automatically detect integer overflow → buffer overflow chains:

  • Coverity — Industry-standard static analyzer, free for open source
  • CodeQL — GitHub's semantic code analysis engine (detects CWE-190, CWE-122)
  • Semgrep — Fast, customizable pattern-based scanner
  • clang-tidy — Includes bugprone-integer-division, cert-int30-c
  • OrbisAI Security — AI-powered scanner that detected this exact vulnerability

6. Consider AddressSanitizer During Development

For embedded targets that support it, or during host-based unit testing:

# Compile with AddressSanitizer
gcc -fsanitize=address,undefined -g -o server_test server_test.c

# Run your tests — heap overflows will be caught immediately
./server_test

7. Reference Security Standards

This vulnerability maps to well-documented weaknesses:


Conclusion

This vulnerability is a perfect illustration of why C programming for embedded systems demands extreme care with integer types. The original code wasn't obviously wrong — it had a bounds check! — but the subtle interaction between signed integers, unsigned memory sizes, and 32-bit arithmetic created a critical exploitable path.

The fix is just a few lines of code, but it embodies a principle that every C developer should internalize: validate in the type you received, widen only after you're sure it's safe.

Key takeaways:

  • 🔴 HTTP headers are attacker-controlled — never use them directly in memory operations
  • 🔴 Integer overflow is silentmalloc(len + 1) won't warn you when len + 1 wraps to 0
  • 🟢 Validate signed, then widen to size_t — this is the safe pattern for C memory allocation
  • 🟢 Use compiler warnings and static analysis — tools like CodeQL and OrbisAI can catch these automatically
  • 🟢 Test with AddressSanitizer — it will catch heap overflows that slip past code review

Heap buffer overflows remain one of the most dangerous and exploitable vulnerability classes in C code. In embedded systems — where there's often no OS-level memory protection, no ASLR, and no sandboxing — a single heap overflow can mean complete device compromise.

Write the bounds check. Validate before you widen. Your future self (and your users) will thank you.


This vulnerability was automatically detected and patched by OrbisAI Security. Automated security scanning catches issues like this before they reach production.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #18

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

Critical Memory Safety Bug: Free of Uninitialized Memory in Rust Telemetry (CVE-2021-29937)

CVE-2021-29937 is a critical memory safety vulnerability in the Rust `telemetry` crate (versions prior to 0.1.3) that allows freeing uninitialized memory, leading to undefined behavior, potential crashes, and possible code execution. The fix involves upgrading the crate from version 0.1.0 to 0.1.3, which patches the unsafe memory handling at the root cause. Despite Rust's reputation for memory safety, this vulnerability demonstrates that `unsafe` code blocks can still introduce serious bugs that