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 via strcpy() happens in C zlib and how to fix it

A critical buffer overflow vulnerability was discovered in `general/libzlib/gzlib.c` where multiple `strcpy()` and `strcat()` calls operated without bounds checking. An attacker controlling file paths or error messages could overflow destination buffers, potentially achieving arbitrary code execution. The fix replaces these unsafe string operations with bounded `memcpy()` calls that respect pre-calculated buffer lengths.

critical

How buffer overflow in rcdevice.c request parser happens in C and how to fix it

A critical buffer overflow vulnerability was discovered in `src/main/io/rcdevice.c` at line 489, where the RC device request parser wrote incoming data into a fixed-size buffer without validating against the hard-coded maximum capacity `RCDEVICE_PROTOCOL_MAX_DATA_SIZE`. An attacker controlling the device's I/O data stream could overflow the buffer by sending a payload longer than `expectedDataLength`, potentially achieving arbitrary code execution. The fix adds a second bounds check against the

critical

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

A critical buffer overflow vulnerability was discovered in the `readline()` function of `mdbx_load.c`, where an `fgets()` call passed a size parameter exceeding the actual allocated buffer by one byte. This off-by-one error could allow an attacker to trigger heap corruption by supplying oversized input via stdin, potentially leading to arbitrary code execution. The fix corrects the size parameter from `buf->iov_len + 1` to `buf->iov_len`, ensuring reads never exceed buffer boundaries.

critical

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

A critical heap buffer overflow vulnerability was discovered in the `dupstr()` function inside `tools/strliteral.c`, where `strcpy()` was called on a heap-allocated buffer without first verifying that `malloc()` had succeeded. If `malloc()` returned `NULL`, the subsequent `strcpy()` would write into address zero — corrupting memory and potentially enabling arbitrary code execution. The fix replaces the unsafe `strcpy()` call with a `NULL` check followed by a bounds-safe `memcpy()`, closing the v

medium

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

A stack buffer overflow in `PMenu_Do_Update()` within `src/menu/menu.c` allowed repeated unchecked `sprintf()` calls to overflow a fixed 1400-byte stack buffer when menu entries contained long text strings. The fix replaces every `sprintf(string + strlen(string), ...)` call with `snprintf(string + len, sizeof(string) - len, ...)`, tracking remaining buffer space and preventing writes beyond the buffer boundary.

critical

How out-of-bounds array access happens in C kernel modules and how to fix it

A high-severity out-of-bounds array access vulnerability was discovered in the natflow_conntrack.c kernel module where the `ct->proto.tcp.state` value was used directly as an array index without bounds validation. An attacker capable of manipulating TCP connection state could trigger reads beyond the `tcp_conntrack_names[]` array, potentially leaking kernel memory or causing system crashes. The fix adds a simple bounds check using `ARRAY_SIZE()` before array access.