CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-22695
6.10.02%

Stride and Prejudice: The Libpng Heap Over-read Saga

Alon Barad
Alon Barad
Software Engineer

Feb 14, 2026·6 min read·22 visits

PoC Available

Executive Summary (TL;DR)

Libpng versions 1.6.51 through 1.6.53 contain a heap buffer over-read vulnerability triggered when processing 16-bit interlaced PNGs into 8-bit output. A flaw in how row strides are calculated allows `memcpy` to read out-of-bounds, causing Denial of Service (DoS) or information disclosure.

A regression in the world's most popular image library turns a routine memory copy into a massive heap over-read. By exploiting the 'simplified API' in libpng, attackers can force the library to read beyond allocated bounds using negative or padded row strides, leading to application crashes or potentially leaking heap memory during 16-bit to 8-bit downscaling.

The Hook: Simplicity is Complicated

If you've ever opened an image on a computer, you've likely used libpng. It is the reference library for the Portable Network Graphics (PNG) standard, embedded in everything from your web browser to your OS kernel. It is the plumbing of the visual internet. And like all ancient C plumbing, sometimes it leaks.

CVE-2026-22695 is a classic case of "no good deed goes unpunished." It stems from the library's Simplified API—a set of functions designed to save developers from the headache of manual PNG chunk parsing. Specifically, the function png_image_read_direct_scaled acts as a magic black box: you give it a complex PNG (maybe 16-bit, maybe interlaced), and ask for a simple 8-bit buffer back.

The problem? In the quest to handle image scaling and format conversion automatically, the library made a dangerous assumption about memory layout. It trusted the caller's definition of "row stride" (the width of a row in bytes) a little too much, forgetting that the source buffer might be smaller than the destination's stride. This creates a disconnect between where we are reading from and how much we are trying to read.

The Flaw: A Regression Tale

This vulnerability is a regression, which is developer-speak for "we fixed a bug and created a new one." It was introduced in commit 218612ddd, ironically while trying to fix a different vulnerability (CVE-2025-65018). The developers were trying to optimize how interlaced images are processed when being downscaled from 16-bit to 8-bit color depth.

Here is the logic flaw: When libpng processes an image, it often uses a temporary buffer, let's call it local_row, to hold the data for a single line of the image. The size of this buffer is determined by png_get_rowbytes(), which calculates the actual data width.

However, the output buffer provided by the application might have a different width. This is defined by row_stride. Why would row_stride be different? Two reasons:

  1. Padding: The application wants rows aligned to 4-byte boundaries for GPU efficiency.
  2. Orientation: The application passes a negative stride to indicate a bottom-up image orientation (standard behavior in Windows GDI bitmaps).

The vulnerability occurs because the code used the destination's row_stride to determine how many bytes to copy from the source local_row. If the stride is larger than the actual row width (padding), or if it's negative (which casts to a massive unsigned integer), the memcpy operation goes off the rails.

The Code: The Smoking Gun

Let's look at the crime scene in pngread.c. The code essentially performed a memory copy where the size argument was derived from user-controlled input (row_stride) rather than the actual size of the source buffer.

> [!NOTE] > In C, memcpy(dest, src, size) requires that both dest and src be at least size bytes long. If src is smaller, you are reading uninitialized heap memory.

Here is the vulnerable logic path:

// pngread.c (Vulnerable Logic)
 
// 1. local_row is allocated based on the ACTUAL image width
png_alloc_size_t row_bytes = png_get_rowbytes(png_ptr, info_ptr);
local_row = png_malloc(png_ptr, row_bytes);
 
// ... inside the processing loop ...
 
// 2. The code uses the caller's 'row_stride' to determine copy size
// If row_stride is negative (e.g. -100), casting to size_t makes it HUGE.
memcpy(output_row, local_row, (size_t)row_stride);
 
// 3. Advance the output pointer
output_row += row_stride;

The fix is simple but critical. We must decouple the amount of data we copy from the amount we advance the pointer. We only copy what we have (row_bytes), but we still jump ahead by row_stride to maintain the application's alignment requirements.

// pngread.c (Patched Logic in 1.6.54)
 
// Only copy the valid data available in local_row
memcpy(output_row, local_row, row_bytes);
 
// Advance the pointer by the stride (which handles the padding/gap)
output_row += row_stride;

The Exploit: Crashing the Party

To exploit this, we don't need a debugger; we need a weird image and a standard API call. The goal is to trick the application into asking libpng to perform an impossible copy.

The Attack Recipe:

  1. The Bait: Create a valid PNG image that uses Interlacing and 16-bit color depth (e.g., RGBA 16-bit). This forces the library into the do_local_scale logic path.
  2. The Trap: The target application must use the png_image_read_direct_scaled API (or similar simplified APIs) and request an 8-bit output format.
  3. The Trigger: The application must provide a row_stride that is either significantly larger than the image width (excessive padding) OR negative.

While an attacker can't always control the arguments passed to the library (the row_stride), many applications calculate this based on metadata or platform standards. For example, an application parsing a PNG to display it on a Windows surface might default to a negative stride for bottom-up rendering.

If the stride is negative, (size_t)row_stride becomes a number close to $2^{64}$. The memcpy tries to read until it hits an unmapped memory page, causing an immediate segmentation fault (DoS). If the stride is just largely padded, it copies adjacent heap chunks into the image output, potentially leaking sensitive data.

The Impact: Leaky Pipes

So, is this the end of the world? Probably not. Is it bad? Yes.

Denial of Service (DoS) is the guaranteed impact. By feeding a malicious image to a server processing user uploads (e.g., a profile picture resizer), an attacker could crash the worker process. If the process doesn't restart automatically, or if the attacker floods the service, the application goes down.

Information Disclosure is the more insidious risk. Since this is an over-read, libpng is copying data from the heap into the pixel buffer. That means whatever was sitting next to local_row in memory—previous HTTP requests, encryption keys, other users' images—gets baked into the resulting image.

If the attacker can download the processed image (which they usually can, seeing as they uploaded it), they can render the pixels and reconstruct the leaked memory. It's like Heartbleed, but for pixels.

The Fix: Get to 1.6.54

The remediation is straightforward: Update libpng to version 1.6.54.

If you are a developer using libpng directly, verify that you are not statically linking an older version. Check your dependencies. If you are a Linux sysadmin, apt update or yum update should be your priority today.

For those who cannot patch immediately, you might be able to mitigate this by ensuring your application:

  1. Does not use the Simplified API (png_image_read...).
  2. Does not use negative row strides.
  3. Sanitizes input images to reject interlaced 16-bit PNGs (though this is a cat-and-mouse game).

Ultimately, patch the library. It's the only way to be sure.

Official Patches

PNG GroupOfficial fix commit

Fix Analysis (1)

Technical Appendix

CVSS Score
6.1/ 10
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H
EPSS Probability
0.02%
Top 100% most exploited

Affected Systems

libpng 1.6.51libpng 1.6.52libpng 1.6.53Applications statically linking vulnerable libpngLinux distributions (Ubuntu, Debian, Fedora, Amazon Linux)

Affected Versions Detail

Product
Affected Versions
Fixed Version
libpng
PNG Group
>= 1.6.51, <= 1.6.531.6.54
AttributeDetail
CWE IDCWE-125 (Heap Buffer Over-read)
CVSS v3.16.1 (Medium)
Attack VectorLocal (File Processing)
Availability ImpactHigh (Crash)
Confidentiality ImpactLow (Heap Leak)
Exploit StatusPoC Available

MITRE ATT&CK Mapping

T1005Data from Local System
Collection
T1203Exploitation for Client Execution
Execution
CWE-125
Out-of-bounds Read

The software reads data past the end, or before the beginning, of the intended buffer.

Known Exploits & Detection

GitHub Issue #778Detailed reproduction steps involving interlaced 16-bit PNGs and negative strides.

Vulnerability Timeline

Regression introduced in commit 218612d
2025-11-19
Vulnerability reported by researcher via Issue #778
2026-01-06
CVE Published and Patch Released
2026-01-12

References & Sources

  • [1]GitHub Security Advisory
  • [2]NVD Entry

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.