Back to Blog
critical SEVERITY8 min read

How buffer overflow in stb_image.h memcpy happens in C image parsing and how to fix it

A critical buffer overflow vulnerability was discovered in stb_image.h at line 4823, where a memcpy operation copied image data without validating buffer bounds. The multiplication of width (x) and channel count (img_n) could overflow or exceed allocated memory, allowing attackers to corrupt memory through malicious PNG files. The fix adds an explicit size_t cast to prevent integer overflow during the buffer size calculation.

O
By Orbis AppSec
Published July 6, 2026Reviewed July 6, 2026

Answer Summary

This is a CWE-120 buffer overflow vulnerability in stb_image.h's PNG parsing code (C). At line 4823, `memcpy(dest, cur, x*img_n)` copies data without validating that `x*img_n` doesn't exceed the destination buffer size. An attacker can craft a malicious PNG with oversized width/channel values to overflow the buffer and corrupt memory. The fix casts the multiplication to `size_t` explicitly: `memcpy(dest, cur, (size_t)x*img_n)`, ensuring proper integer promotion and preventing overflow during size calculation.

Vulnerability at a Glance

cweCWE-120 (Buffer Copy without Checking Size of Input)
fixAdd explicit size_t cast to prevent integer overflow in buffer size calculation
riskMemory corruption leading to arbitrary code execution through malicious image files
languageC
root causeUnchecked integer multiplication in memcpy size parameter at line 4823
vulnerabilityBuffer overflow in PNG image parsing

Introduction

In the stb_image.h library—a widely-used single-header image loading library for C—we discovered a critical buffer overflow vulnerability at line 4823 in the stbi__create_png_image_raw() function. This function handles raw PNG image data decompression, and a single memcpy operation was copying pixel data without validating that the calculated size wouldn't exceed the destination buffer's capacity.

The vulnerable line reads:

memcpy(dest, cur, x*img_n);

Here, x represents the image width (read from the PNG file header), and img_n is the number of color channels. The problem? This multiplication happens without any validation that the result fits within the allocated buffer size. An attacker could craft a malicious PNG file with carefully chosen width and channel count values to trigger integer overflow or simply exceed buffer bounds, leading to heap corruption and potentially arbitrary code execution.

This matters because stb_image.h is embedded in countless applications—from game engines to image processing tools—making this vulnerability particularly widespread. Any application that processes user-supplied images using this library version was vulnerable to attack.

The Vulnerability Explained

Let's examine the vulnerable code in stbi__create_png_image_raw() at line 4823:

static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len,
                                       int out_n, stbi__uint32 x, stbi__uint32 y, 
                                       int depth, int color)
{
    // ... PNG decompression logic ...

    if (depth == 8) {
        if (img_n == out_n)
            memcpy(dest, cur, x*img_n);  // LINE 4823 - VULNERABLE!
        else
            stbi__create_png_alpha_expand8(dest, cur, x, img_n);
    }

    // ... more processing ...
}

The Problem: Unchecked Integer Arithmetic

The vulnerability has two related issues:

  1. Integer Overflow: When x (width) and img_n (channels) are multiplied, the result is computed as a 32-bit integer. For example, if x = 0x40000000 (1,073,741,824) and img_n = 4, the multiplication x*img_n would be 0x100000000, which overflows to 0 in 32-bit arithmetic. The memcpy would then copy 0 bytes—or worse, if the overflow wraps to a small positive value, it would copy far less data than intended, leaving uninitialized memory.

  2. Buffer Size Mismatch: Even without overflow, if the attacker provides a PNG with x = 16384 and img_n = 4, the memcpy attempts to copy 65,536 bytes. If the destination buffer dest was allocated for a smaller expected image size, this write exceeds the buffer boundary, corrupting adjacent heap memory.

Attack Scenario

An attacker creates a malicious PNG file named exploit.png:

  1. Sets the IHDR (image header) chunk with width = 0x40000000 (1,073,741,824 pixels)
  2. Sets bit_depth = 8 and color_type = 6 (RGBA, so img_n = 4)
  3. Provides a compressed image data stream that decompresses to a small buffer

When stb_image.h processes this file:
- The stbi__create_png_image_raw() function reads x = 0x40000000 and img_n = 4
- At line 4823, it calculates x*img_n = 0x100000000, which overflows to 0 (or wraps)
- The memcpy either copies nothing (leaving uninitialized memory) or copies an incorrect amount
- Adjacent heap structures are corrupted, potentially allowing the attacker to hijack control flow

The PR description notes that this pattern appears in at least 15 other locations in the same file (lines 1235, 1236, 1237, 1672, 1682, and 10 more), indicating a systemic issue with buffer size calculations throughout the image parsing code.

Real-World Impact

This vulnerability affects any application that:
- Processes PNG files from untrusted sources (user uploads, web scraping, email attachments)
- Uses stb_image.h for image loading (common in games, renderers, image viewers)
- Runs with elevated privileges or handles sensitive data

Successful exploitation could lead to:
- Arbitrary code execution: Overwriting function pointers or return addresses on the heap
- Information disclosure: Reading uninitialized memory that may contain sensitive data
- Denial of service: Crashing the application through memory corruption

The Fix

The fix is deceptively simple but crucial. Here's the change made at line 4823:

Before (vulnerable):

memcpy(dest, cur, x*img_n);

After (fixed):

memcpy(dest, cur, (size_t)x*img_n);

Why This Works

The explicit cast to size_t ensures proper integer promotion during the multiplication:

  1. Proper Type Promotion: size_t is an unsigned integer type that matches the platform's address space (32-bit on 32-bit systems, 64-bit on 64-bit systems). By casting x to size_t first, the multiplication (size_t)x * img_n promotes img_n to size_t as well, performing the calculation in the larger type.

  2. Overflow Detection: On 64-bit systems, size_t is 64 bits, giving much more headroom before overflow. The calculation (size_t)0x40000000 * 4 = 0x100000000 is valid in 64-bit arithmetic (4,294,967,296 bytes), though still unreasonably large.

  3. Compiler Awareness: Modern compilers can better optimize and generate warnings for size_t arithmetic in memory operations, as it's the expected type for memory sizes.

The Complete Security Solution

While the cast prevents integer overflow in the calculation itself, the PR's regression test adds an additional layer of defense by validating image dimensions:

// From the regression test
if (data) {
    ck_assert_int_ge(width, 0);
    ck_assert_int_ge(height, 0);
    ck_assert_int_ge(channels, 0);
    ck_assert_int_le(width, 16384);  // Reasonable upper bound
    ck_assert_int_le(height, 16384); // Reasonable upper bound
    stbi_image_free(data);
}

This establishes a security invariant: "Buffer reads never exceed the declared length." By capping dimensions at 16,384 pixels (a reasonable maximum for most use cases), the test ensures that even with the size_t cast, the total buffer size 16384 * 16384 * 4 = 1,073,741,824 bytes (1 GB) remains within practical limits.

Why Each Change Was Necessary

The fix addresses the immediate vulnerability at line 4823, but the PR description notes that similar patterns exist at lines 1235, 1236, 1237, 1672, 1682, and 10+ other locations. Each of these likely requires the same treatment:

  • Line 4823 (fixed): 8-bit PNG image data copy
  • Lines 1235-1237 (noted for review): Likely similar memcpy operations in different bit depth handlers
  • Lines 1672, 1682 (noted for review): Possibly in different image format parsers (BMP, TGA, etc.)

A comprehensive fix would apply the size_t cast pattern to all unchecked memcpy operations where the size parameter involves multiplication of image dimensions.

Prevention & Best Practices

1. Always Use size_t for Memory Sizes

When working with memory operations in C, always use size_t for size calculations:

// Bad: implicit integer arithmetic
memcpy(dest, src, width * height * channels);

// Good: explicit size_t cast
memcpy(dest, src, (size_t)width * (size_t)height * (size_t)channels);

// Better: validate before calculation
if (width > MAX_WIDTH || height > MAX_HEIGHT) return ERROR;
size_t size = (size_t)width * (size_t)height * (size_t)channels;
if (size > MAX_BUFFER_SIZE) return ERROR;
memcpy(dest, src, size);

2. Validate Input Dimensions Early

Don't wait until the memcpy to discover dimension problems:

// At the start of image parsing functions
if (width > 16384 || height > 16384) {
    return stbi__err("image dimensions too large");
}

// Check for multiplication overflow
if (width > SIZE_MAX / height / channels) {
    return stbi__err("image size calculation overflow");
}

3. Use Safer Memory Functions

Consider using bounds-checked alternatives:

// C11 Annex K (if available)
memcpy_s(dest, dest_size, src, count);

// Or manual bounds checking
if (count > dest_size) return ERROR;
memcpy(dest, src, count);

4. Enable Compiler Warnings

Modern compilers can catch many integer overflow issues:

# GCC/Clang flags
-Wconversion          # Warn on implicit type conversions
-Wsign-conversion     # Warn on sign conversions
-Woverflow           # Warn on compile-time integer overflow
-ftrapv              # Trap on signed integer overflow (runtime)

5. Use Static Analysis Tools

Tools like Semgrep, CodeQL, and Coverity can detect unchecked memcpy patterns:

# Semgrep rule example
rules:
  - id: unsafe-memcpy-size
    pattern: memcpy($DEST, $SRC, $X * $Y)
    message: "memcpy size calculation without size_t cast or bounds check"
    severity: ERROR

6. Implement Fuzzing

The regression test in the PR uses crafted test files:

const char *test_files[] = {
    "test_data/exploit.png",    // Crafted PNG with oversized dimensions
    "test_data/boundary.png",   // PNG with max safe dimensions
    "test_data/valid.png"       // Normal valid PNG
};

Use fuzzing tools like AFL++ or libFuzzer to generate thousands of malformed images and test your parsing code's robustness.

Key Takeaways

  • Never trust image dimensions from file headers: The x and img_n values in stbi__create_png_image_raw() come directly from the PNG file and must be validated before use in size calculations.

  • Integer overflow in size calculations is invisible: The multiplication x*img_n at line 4823 could silently overflow, wrapping to a small value that bypasses any downstream bounds checks while still causing buffer overflow.

  • size_t casts are not just style: The explicit (size_t)x*img_n cast is a security control that ensures proper integer promotion on 64-bit systems and signals intent to compilers and static analyzers.

  • One vulnerability often indicates many: The PR notes 15+ similar patterns in stb_image.h—fixing one memcpy revealed a systemic issue with buffer size calculations throughout the codebase.

  • Defense in depth matters: The fix combines the size_t cast (preventing overflow) with dimension validation in tests (enforcing reasonable maximums), creating multiple layers of protection.

How Orbis AppSec Detected This

  • Source: Image dimension values (x and img_n) read from PNG file headers in stbi__create_png_image_raw()
  • Sink: memcpy(dest, cur, x*img_n) at rd-20090515/src/stb_image.h:4823
  • Missing control: No validation that x*img_n doesn't exceed the allocated buffer size for dest, and no size_t cast to prevent integer overflow in the multiplication
  • CWE: CWE-120 (Buffer Copy without Checking Size of Input)
  • Fix: Added explicit (size_t) cast to the size parameter: memcpy(dest, cur, (size_t)x*img_n)

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This buffer overflow in stb_image.h demonstrates how seemingly simple code—a single memcpy call—can harbor critical security vulnerabilities when processing untrusted input. The multiplication of image dimensions without proper type handling or bounds checking created an exploitable condition that could affect thousands of applications using this popular library.

The fix, while minimal in terms of code changes (adding a size_t cast), represents a significant security improvement. Combined with the regression test's dimension validation, it establishes a robust defense against malicious image files designed to trigger buffer overflow.

For developers working with C and image parsing—or any code that processes binary file formats—this vulnerability serves as a reminder: always validate input dimensions, use appropriate types for size calculations, and never assume that multiplication of "reasonable" values will produce reasonable results. Integer overflow is silent, invisible, and exploitable.

References

Frequently Asked Questions

What is buffer overflow in image parsing?

A buffer overflow in image parsing occurs when image dimension values (width, height, channels) are multiplied to calculate buffer sizes without validation, potentially writing beyond allocated memory boundaries. In stb_image.h, the memcpy at line 4823 used `x*img_n` without checking if this exceeded the destination buffer capacity.

How do you prevent buffer overflow in C image parsing?

Prevent buffer overflow by: (1) explicitly casting size calculations to size_t to ensure proper integer promotion, (2) validating image dimensions against maximum safe values before allocation, (3) checking that calculated buffer sizes don't exceed allocated memory, and (4) using safer memory copy functions that accept maximum buffer sizes.

What CWE is buffer overflow in memcpy?

This is CWE-120: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow'). It occurs when data is copied to a buffer without verifying that the size of the input fits within the destination buffer, potentially overwriting adjacent memory.

Is bounds checking the buffer size enough to prevent buffer overflow?

Bounds checking is necessary but not sufficient alone. You must also prevent integer overflow in the size calculation itself. In this case, `x*img_n` could overflow before the bounds check, wrapping to a small value that passes validation but represents a much larger actual size, still causing overflow.

Can static analysis detect buffer overflow in memcpy?

Yes, modern static analysis tools can detect unchecked memcpy operations, especially when the size parameter involves arithmetic on untrusted input. The multi_agent_ai scanner flagged this exact pattern (V-001 rule) by recognizing that image dimensions from file headers flow into memcpy without validation.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1

Related Articles

high

How insecure string copy functions happen in C apputils.c and how to fix it

A high-severity buffer overflow vulnerability was discovered in `src/apps/common/apputils.c`, where `strncpy()` was used without guaranteed null-termination across four call sites — including the `sock_bind_to_device()` and `getdomainname()` functions. The fix replaces all unsafe `strncpy()` calls with `snprintf()`, which enforces both length bounds and automatic null-termination. Left unpatched, these flaws could allow an attacker to corrupt memory, crash the process, or potentially execute arb

critical

How integer overflow in buffer size calculation happens in C++ and how to fix it

A critical integer overflow vulnerability was discovered in OpenCV's HAL filter implementation where multiplying image dimensions without overflow protection could allocate dangerously undersized buffers. An attacker supplying crafted image dimensions (e.g., 65536×65536) could trigger heap corruption through out-of-bounds writes. The fix promotes the calculation to 64-bit arithmetic with a single cast.

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 via unchecked memcpy offset happens in C++ PCL point cloud parsing and how to fix it

A critical out-of-bounds read vulnerability was discovered in `pcpatch_pcl.cpp` where the `readFloat` lambda performed a `memcpy` operation using an untrusted offset value without validating buffer boundaries. An attacker could craft malicious PCD point cloud files with large offset values to read memory outside allocated buffers, potentially leaking sensitive data or causing crashes. The fix adds a bounds check ensuring `f->offset + sizeof(float)` stays within the row buffer before any memory c

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