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.


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.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #18

Related Articles

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.

critical

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

A critical buffer overflow vulnerability was discovered in `backend/sgx/enclave/enclave.c` where the `ecall_store_data` function performed `memcpy` operations without proper bounds checking against the actual destination buffer size. An attacker could supply a malicious `data_len` parameter to overflow the enclave's secure storage buffer, potentially corrupting trusted execution environment memory. The fix replaces a hardcoded magic number check with a precise size comparison against the actual