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 Orbis AppSec
Published May 28, 2026Reviewed June 3, 2026

Answer Summary

This vulnerability is a classic integer overflow to heap buffer overflow (CWE-190 → CWE-122) in a C-based embedded ESP web server. The HTTP `Content-Length` header was cast to a signed 32-bit integer and passed directly to `malloc()` with no size validation, meaning a crafted maximum-value header could wrap the allocation size to zero on 32-bit systems. The fix validates that the signed `Content-Length` value is greater than zero and within an acceptable range before calling `malloc()`, preventing the overflow entirely. Developers working with embedded HTTP servers in C should always validate externally-supplied size values before using them in memory allocation calls.

Vulnerability at a Glance

cweCWE-190 (Integer Overflow) → CWE-122 (Heap-Based Buffer Overflow)
fixValidate that the signed Content-Length value is > 0 and within safe bounds before allocation
riskRemote code execution or device crash via crafted HTTP request
languageC (Embedded / ESP framework)
root causeHTTP Content-Length header cast to signed int and used in malloc() without bounds validation
vulnerabilityInteger Overflow to Heap Buffer Overflow

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.

Frequently Asked Questions

What is an integer overflow to heap buffer overflow?

An integer overflow to heap buffer overflow occurs when a numeric value (such as a size from user input) overflows its data type boundary, producing an unexpectedly small or zero value that is then used to allocate a buffer. The attacker subsequently writes more data into that undersized buffer than it can hold, corrupting adjacent heap memory.

How do you prevent integer overflow in C memory allocation?

Always validate externally supplied size values before using them in malloc() or similar calls. Check that the value is positive, non-zero, and below a defined maximum safe limit. Use unsigned types carefully and consider wrapper functions that enforce these checks consistently.

What CWE is integer overflow to heap buffer overflow?

The chain is CWE-190 (Integer Overflow or Wraparound) leading to CWE-122 (Heap-Based Buffer Overflow). CWE-190 describes the root cause (the numeric wraparound), while CWE-122 describes the resulting memory corruption impact.

Is input length limiting enough to prevent this vulnerability?

Not by itself. You must also validate the *sign* and *magnitude* of the parsed integer value before using it as an allocation size. A length limit on the raw header string does not prevent a semantically crafted value like INT_MAX from passing through the parser and wrapping in a size calculation.

Can static analysis detect integer overflow in malloc() calls?

Yes. Static analysis tools such as Semgrep, Coverity, and CodeQL can identify patterns where user-controlled values are cast to signed integers and used directly in allocation calls without intervening range checks. Orbis AppSec detected this exact pattern automatically.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #18

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).