Back to Blog
high SEVERITY7 min read

Heap Buffer Overflow in stb_image.h: How a Missing Bounds Check Could Lead to Code Execution

A critical heap buffer overflow vulnerability was discovered and patched in a vendored copy of `stb_image.h`, a popular single-header image loading library. The root cause was a missing bounds check that allowed attacker-controlled image data to trigger memory writes beyond allocated heap buffers, potentially enabling arbitrary code execution. A single defensive guard — rejecting negative buffer lengths before any memory operation — closes this dangerous attack vector.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published May 28, 2026•Reviewed June 3, 2026

Answer Summary

A heap buffer overflow vulnerability in stb_image.h (CWE-122) allowed attackers to write beyond allocated heap memory through crafted image files. The root cause was missing validation of buffer length parameters before memory allocation and writes. The fix adds a bounds check that rejects negative or invalid buffer lengths before any memory operation occurs, preventing the overflow entirely.

Vulnerability at a Glance

cweCWE-122 (Heap-based Buffer Overflow)
fixAdd validation guard rejecting negative buffer lengths before memory operations
riskArbitrary code execution through memory corruption via malicious image files
languageC
root causeMissing bounds check on buffer length parameter before heap allocation
vulnerabilityHeap Buffer Overflow in Image Buffer Allocation

Introduction

Image parsing is one of the most dangerous attack surfaces in any application. Images are everywhere — uploaded by users, fetched from remote URLs, embedded in documents — and the code that decodes them is often decades old, written in C, and riddled with assumptions about input validity that simply don't hold in adversarial environments.

stb_image.h is a beloved single-header C library used by thousands of projects to load PNG, JPEG, BMP, GIF, and other image formats. Its simplicity is its appeal: drop one file into your project and you can parse images. But that same simplicity — a single vendored file that rarely gets updated — makes it a magnet for lingering vulnerabilities.

This post breaks down a critical heap buffer overflow found in a vendored copy of stb_image.h inside the hipster tool, explains how it could be exploited, and walks through the surgical one-line fix that closes the vulnerability.


The Vulnerability Explained

What Is a Heap Buffer Overflow?

A heap buffer overflow (CWE-120) occurs when a program writes data beyond the boundaries of a buffer allocated on the heap. Unlike stack overflows, heap overflows are subtler — they don't immediately crash the program in obvious ways — but they are equally dangerous. An attacker who controls what gets written, and where, can corrupt adjacent heap metadata, overwrite function pointers, or achieve arbitrary code execution.

Where Did This Vulnerability Live?

The vulnerable function is stbi__getn(), located at line 1405 of hipster/ext_src/stb/stb_image.h:

// BEFORE the fix
static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n)
{
   if (s->io.read) {
      int blen = (int) (s->img_buffer_end - s->img_buffer);
      if (blen < n) {
         // ... reads from I/O and copies into buffer
      }
   }
   // ...
}

The parameter n represents the number of bytes to read into buffer. This value is derived directly from fields inside the image file being parsed — things like declared chunk sizes, compressed data lengths, or image dimensions.

Here's the problem: n is a signed integer, and there is no check that it is non-negative before use.

If an attacker crafts a malicious image file where a header field produces a negative value for n, several bad things can happen:

  1. The size comparison blen < n behaves incorrectly — a negative n will always be less than blen (a non-negative size), so the branch logic may be bypassed.
  2. Downstream memcpy or read operations that receive a negative n cast to size_t will interpret it as a massive unsigned integer (e.g., -1 becomes 0xFFFFFFFF on 32-bit or 0xFFFFFFFFFFFFFFFF on 64-bit), triggering a catastrophic out-of-bounds write.

The Attack Chain

Attacker crafts malicious image
        │
        ▼
Image header field encodes a negative or oversized length
        │
        ▼
stbi__getn() called with attacker-controlled n
        │
        ▼
No negative-value guard → size comparison bypassed
        │
        ▼
memcpy writes (size_t)(-1) bytes → heap overflow
        │
        ▼
Heap metadata corruption → potential arbitrary code execution

Real-World Impact

This class of vulnerability is well-documented in older stb_image releases and has been assigned multiple CVEs over the years. The impact depends on context:

  • Remote Code Execution (RCE): If the application processes images from untrusted sources (uploads, URLs), an attacker can deliver a crafted image and potentially execute arbitrary code in the process.
  • Denial of Service (DoS): At minimum, the heap corruption will crash the application.
  • Privilege Escalation: If the vulnerable process runs with elevated privileges, code execution becomes especially dangerous.

The hipster tool bundles this library as a vendored copy — meaning it doesn't benefit from upstream patches unless someone manually updates the file. This is a common and dangerous pattern with single-header libraries.


The Fix

What Changed

The fix is elegantly minimal — a single guard clause at the top of stbi__getn():

// AFTER the fix
static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n)
{
+  if (n < 0) return 0;   // ← THE FIX
   if (s->io.read) {
      int blen = (int) (s->img_buffer_end - s->img_buffer);
      if (blen < n) {
         // ...
      }
   }
   // ...
}

Before: Any caller could pass a negative n, which would silently bypass size checks and potentially trigger a massive out-of-bounds memory operation.

After: If n is negative, the function immediately returns 0 (indicating zero bytes read) without touching the buffer. The caller receives a safe failure signal and can handle it gracefully.

Why This Works

The fix enforces a precondition: byte counts must be non-negative. This is a classic example of input validation at the trust boundary — the point where external, attacker-controlled data (the image file) influences internal program behavior.

By rejecting the invalid input before any memory operation occurs, the vulnerability is neutralized regardless of what the image file contains. The fix is:

  • ✅ Minimal — one line, no logic changes elsewhere
  • ✅ Correct — a request to read a negative number of bytes is always invalid
  • ✅ Safe — returns a failure code rather than crashing or corrupting memory
  • ✅ Non-breaking — no legitimate caller should ever pass n < 0

The Diff at a Glance

 static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n)
 {
+   if (n < 0) return 0;
    if (s->io.read) {
       int blen = (int) (s->img_buffer_end - s->img_buffer);
       if (blen < n) {

One line. One character of indentation. One critical security boundary enforced.


Conclusion

A single missing bounds check — if (n < 0) return 0; — was all that stood between a functioning image loader and a critical heap buffer overflow. This is a humbling reminder that:

  1. The most dangerous vulnerabilities are often the simplest. One missing guard, one implicit cast, one forgotten validation.
  2. Vendored code is your code. When you copy a library into your repository, you own its bugs. Keep it updated.
  3. Image parsers are high-value targets. They process untrusted binary data, they're written in C, and they're often ancient. Treat them with extra scrutiny.
  4. Defense in depth matters. Input validation, memory-safe tooling, fuzzing, and sandboxing each add a layer of protection. No single measure is sufficient.

The fix here was automated, verified, and deployed — but the underlying lesson is one every C/C++ developer should internalize: never trust a length that came from outside your process. Validate it. Clamp it. Reject it if it's wrong. Your heap will thank you.


This vulnerability was identified and fixed by OrbisAI Security. Automated security scanning, triage, and patching — so your team can focus on building.

Prevention and further reading

Related Articles

critical

Aardvark.Cef.Process.Core Shared Memory Handler: Unvalidated memcpy

The shared memory handler in Aardvark.Cef.Process.Core failed to validate that the `length` parameter passed from JavaScript did not exceed the destination buffer size before calling `memcpy`. This allowed a compromised Chromium renderer process to corrupt heap memory by supplying an oversized `handle->length` value to the `openMapping` function. The fix adds explicit bounds checking using `GetArrayBufferByteLength()` before the copy operation.

high

XShmGetImage Heap Corruption: Unvalidated Image Height in streamproxy

The `XShmGetImage` function in the X11 shared memory image path copies pixel data row-by-row using `memcpy` without validating that the source image height matches the destination buffer height. An attacker or compromised server could provide an oversized source image, causing writes beyond the allocated heap buffer and triggering heap corruption or code execution.

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 Buffer Overflow via strcpy() Happens in C++ XML Parsers and How to Fix It

A critical buffer overflow vulnerability was discovered in `buildroot-external/package/libxmlparser/xmlParser.cpp`, where the `toXMLString` function used `_tcscpy()` to write XML escape sequences into a destination buffer without any bounds checking. An attacker supplying a crafted XML document could overflow the buffer and potentially execute arbitrary code. The fix replaces all five unsafe `_tcscpy()` calls with `memcpy()` calls that copy only the exact number of bytes required for each escape

critical

How Heap Buffer Overflows Happen in C++ ZIP Extraction and How to Fix Them

A critical heap buffer overflow vulnerability was discovered in `TKLiveSync/unzip.cpp`, where ZIP archive entry names were copied into a `PATH_MAX`-sized heap buffer using `strcpy()` without any length validation. Since the ZIP specification allows entry names up to 65,535 bytes — far exceeding typical `PATH_MAX` values of 1,024 to 4,096 bytes — a crafted archive could overflow the buffer and corrupt heap memory. The fix replaces the unsafe `strcpy`/`dirname` pattern with `std::string` operation

medium

How Integer Overflow happens in C++ image processing and how to fix it

A signed integer overflow in OpenCV's `bilateralFilter.cpp` allowed the buffer size calculation `cal_width * cal_height * cn` to wrap around to a small or negative value, causing `padding.resize()` to allocate far less memory than needed. Subsequent `memcpy` operations would then write beyond the allocated buffer, creating a heap corruption primitive. The fix is a single targeted cast to `size_t` that promotes the multiplication to unsigned 64-bit arithmetic before any overflow can occur.