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

critical

How buffer overflow happens in C tar header parsing and how to fix it

A critical buffer overflow vulnerability was discovered in `microtar/microtar.c` where the `raw_to_header()` and `header_to_raw()` functions used unbounded `strcpy()` and `sprintf()` calls to copy tar header fields. Malicious tar files with non-null-terminated name fields could overflow destination buffers, potentially leading to code execution. The fix replaces all unsafe string operations with bounded alternatives: `memcpy()` with explicit null-termination and `snprintf()` instead of `sprintf(

critical

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

A critical buffer overflow vulnerability was discovered in `src/firmware/src/net/ieee80211.c` at line 1584, where the `ieee80211_input()` function processed raw 802.11 data frames without verifying that the incoming frame was large enough to contain a valid `ieee80211_frame` header. An attacker within wireless range could craft undersized or malformed frames to trigger memory corruption, potentially leading to remote code execution on the firmware. The fix adds a single, targeted bounds check th

high

How buffer overflow from unsafe string copy functions happens in C network interface code and how to fix it

A high-severity buffer overflow vulnerability was discovered in `generic/eth-impl.c`, where unsafe `strncpy()` and `sprintf()` calls could write beyond buffer boundaries when handling network interface names and device filenames. The fix replaced these dangerous functions with bounded `snprintf()` calls that guarantee null-termination and prevent memory corruption.

critical

How buffer overflow in FuzzIxml.c sprintf() happens in C and how to fix it

A critical buffer overflow vulnerability was discovered in `fuzzer/FuzzIxml.c` where `sprintf()` wrote a PID-formatted filename into a fixed 256-byte stack buffer without any bounds checking. The fix replaces `sprintf()` with `snprintf()`, explicitly passing the buffer size to prevent any overflow. While exploitation in this specific fuzzer context requires local access, the pattern is a textbook example of CWE-120 that developers should recognize and eliminate everywhere it appears.

critical

How buffer overflow happens in C HTML parsing and how to fix it

A critical buffer overflow vulnerability in `include/html_parse.h` allowed attackers to overflow buffers by providing malicious HTML input exceeding buffer capacity. The fix adds proper bounds checking before memcpy() operations to prevent memory corruption and potential code execution.

critical

How buffer overflow in memcpy() happens in Node.js N-API bindings and how to fix it

A critical buffer overflow vulnerability was discovered in the GetBufferAsVector() function in examples_nodejs/src/zupt_napi.cpp, where memcpy() copied data from JavaScript Uint8Array buffers without proper bounds validation. This vulnerability could allow attackers to trigger memory corruption by providing maliciously crafted input arrays to the native Node.js module, potentially leading to crashes or arbitrary code execution.