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 orbisai0security
May 28, 2026

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

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.


Prevention & Best Practices

1. Always Validate Lengths Before Memory Operations

Any time a length or size value originates from external input (files, network, user input), validate it before use:

// Bad: trust the file header
int chunk_size = read_int_from_file(f);
memcpy(dest, src, chunk_size);  // ← dangerous!

// Good: validate first
int chunk_size = read_int_from_file(f);
if (chunk_size < 0 || chunk_size > MAX_ALLOWED_SIZE) {
    return ERROR_INVALID_INPUT;
}
memcpy(dest, src, chunk_size);  // ← safe

2. Prefer size_t for Sizes — But Validate Signed Inputs First

memcpy, malloc, and similar functions take size_t (unsigned). When you cast a signed integer to size_t, negative values silently become enormous numbers. Always validate before casting:

// Dangerous implicit cast
void read_data(int n) {
    char *buf = malloc(n);          // if n < 0, malloc((size_t)-1) → allocation failure or huge alloc
    memcpy(buf, src, n);            // if n < 0, writes gigabytes
}

// Safe pattern
void read_data(int n) {
    if (n <= 0 || n > MAX_SAFE_SIZE) return;
    char *buf = malloc((size_t)n);  // now safe
    if (!buf) return;
    memcpy(buf, src, (size_t)n);    // now safe
}

3. Keep Vendored Libraries Updated

Single-header libraries like stb_image.h are easy to vendor but easy to forget. Establish a process:

  • Track the upstream version you've vendored (add a comment at the top of the file)
  • Subscribe to security advisories for the library (GitHub security advisories, OSV.dev)
  • Automate dependency scanning — tools like osv-scanner, trivy, or grype can detect known-vulnerable vendored files
  • Schedule periodic reviews of vendored code, especially image/audio/video parsers

4. Use Memory-Safe Wrappers Where Possible

If your language or framework provides safer alternatives, prefer them:

  • Use AddressSanitizer (ASan) during development and testing to catch buffer overflows at runtime
  • Use fuzzing (libFuzzer, AFL++) on image parsing code — this exact class of bug is highly fuzz-detectable
  • Consider sandboxing image parsing in a separate process with limited privileges

5. Reference Security Standards

This vulnerability maps to well-known security weaknesses:

Standard Reference
CWE CWE-120: Buffer Copy without Checking Size of Input
CWE CWE-787: Out-of-bounds Write
OWASP A03:2021 – Injection
OWASP Input Validation Cheat Sheet

6. Tools to Detect This Class of Issue

  • Static Analysis: cppcheck, clang-tidy, Coverity, CodeQL
  • Dynamic Analysis: AddressSanitizer (-fsanitize=address), Valgrind
  • Fuzzing: libFuzzer, AFL++, Honggfuzz
  • Dependency Scanning: osv-scanner, trivy, grype

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.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #13

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

Critical Heap Buffer Overflow in SSDP Control Point: How Unbounded String Operations Put Networks at Risk

A critical heap buffer overflow vulnerability was discovered and patched in the SSDP control point implementation (`ssdp_ctrlpt.c`), where multiple unbounded `strcpy` and `strcat` operations constructed HTTP request buffers without any length validation. Network-received SSDP response fields — including service type strings and location URLs — could be crafted by an attacker to exceed buffer boundaries, potentially enabling arbitrary code execution or denial of service. The fix replaces the unsa

critical

Heap Buffer Overflow in OPDS Parser: How a Misplaced Variable Nearly Opened the Door to Remote Code Execution

A critical heap buffer overflow vulnerability was discovered in `lib/OpdsParser/OpdsParser.cpp`, where the buffer allocation size was calculated *after* a fixed chunk size was used to allocate memory, meaning the actual bytes read could exceed the allocated buffer. On embedded devices parsing untrusted OPDS catalog data from the network, this flaw could allow a remote attacker to corrupt heap memory and potentially achieve arbitrary code execution. The fix was elegantly simple: move the `toRead`

critical

Heap Buffer Overflow in BLE MIDI: How a Missing Bounds Check Opens the Door to Remote Exploitation

A critical heap buffer overflow vulnerability was discovered in the BLE MIDI packet assembly code of `blemidi.c`, where attacker-controlled packet length values could trigger writes beyond allocated heap memory. The fix adds an integer overflow guard before the `malloc` call, ensuring that maliciously crafted BLE MIDI packets can no longer corrupt heap memory. This vulnerability is particularly dangerous because it is remotely exploitable by any nearby Bluetooth device — no physical access requi

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

Heap Corruption via Unchecked memcpy: How Integer Overflow Bugs Corrupt Memory in Windows File Operations

A critical buffer overflow vulnerability was discovered in `phlib/nativefile.c`, where multiple `memcpy` calls copied filename and extended-attribute data into fixed-size structures without verifying that source lengths didn't exceed destination buffer boundaries. An attacker supplying an oversized filename or EA name could corrupt adjacent heap memory, potentially enabling arbitrary code execution. The fix replaces unchecked arithmetic with Windows' safe integer helpers (`RtlULongAdd`, `RtlULon