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-22801
6.80.02%

The 16-Bit Straitjacket: How Silencing Warnings Broke libpng (CVE-2026-22801)

Alon Barad
Alon Barad
Software Engineer

Feb 14, 2026·6 min read·42 visits

PoC Available

Executive Summary (TL;DR)

A heap buffer over-read caused by incorrect integer casting in libpng's simplified write API. Introduced in 2016 to fix compiler warnings, it truncates row strides to 16 bits. This allows attackers to trigger crashes or potentially leak memory by providing negative or large row strides. Fixed in version 1.6.54.

For nearly a decade, a dormant bug lurked inside the simplified write API of libpng, the world's reference library for PNG manipulation. Born from an attempt to silence harmless compiler warnings in 2016, CVE-2026-22801 is a textbook integer truncation vulnerability. By forcibly casting row strides to 16-bit integers, the library inadvertently blinded itself to image dimensions and memory layouts, leading to a heap buffer over-read. This vulnerability allows attackers to trick applications into reading past allocated memory boundaries, potentially bleeding sensitive heap data into generated image files or crashing the process entirely.

The Hook: Silence is Deadly

In the world of C/C++ development, compiler warnings are like the check engine light in your car. Prudent engineers investigate the cause; lazy ones just put a piece of black tape over the dashboard. In October 2016, the maintainers of libpng reached for the black tape. To silence nagging warnings on legacy 16-bit systems, they introduced explicit casts to png_uint_16 in the simplified write API. It seemed harmless—a quick housekeeping task to clean up the build logs.

Fast forward to 2026, and that "cleanup" has been revealed as a critical structural failure. By forcing modern 32-bit and 64-bit values into a 16-bit straitjacket, the library lost its ability to do basic math regarding memory offsets. This isn't just a crash bug; it's a reminder that (type_cast) is often a developer saying, "Shut up, I know what I'm doing," right before proving they absolutely do not.

The Flaw: Integer Truncation & The Stride

To understand the vulnerability, you have to understand the concept of a "stride." When handling raw image data in memory, the stride is the number of bytes from the beginning of one row of pixels to the beginning of the next. Usually, this is just width * bytes_per_pixel. However, in many graphics systems (looking at you, BMP), strides can be negative to indicate a bottom-up image layout, or they can include padding bytes.

The vulnerability exists in png_write_image_16bit and png_write_image_8bit. The code takes a user-supplied stride (often a standard int or long) and casts it to png_uint_16. Here is the math of the disaster:

  1. Large Strides: If you have a massive image with a stride of 70,000 bytes, casting it to a 16-bit unsigned integer performs a modulo operation. 70000 % 65536 = 4464. The library now thinks the next row is only 4,464 bytes away, not 70,000. It reads data from the wrong location, creating a garbled image and potentially crashing.

  2. Negative Strides: This is the juicy part. A negative stride (e.g., -100) is represented in 2's complement as a very large unsigned number (e.g., 0xFFFFFF9C on 32-bit). When you cast that to uint16, you get 0xFF9C (65,436). Instead of moving backwards in memory to flip the image, the library jumps forward by 65k bytes, rocketing past the end of the allocated buffer and into the wild heap yonder.

The Code: The Smoking Gun

Let's look at the anatomy of the failure. The code below shows the logic introduced in version 1.6.26 and how it was remediated in 1.6.54. The intent was to support the simplified API, designed to make reading/writing PNGs easy for developers who don't want to manage complex png_struct pointers.

Vulnerable Code (Pre-1.6.54):

/* Inside png_write_image_8bit */
png_uint_16 row_stride; // <--- THE ROOT CAUSE
 
/* ... input_stride passed by caller ... */
 
if (input_stride < 0)
   row_stride = (png_uint_16)(-input_stride); // Truncation happens here
else
   row_stride = (png_uint_16)input_stride;    // And here
 
/* Later used to calculate memory offsets */
const void *row = (const void*)(buffer + (y * row_stride));

Because row_stride is defined as a 16-bit integer, any input exceeding UINT16_MAX is mutilated. The library calculates the memory address of the pixel rows based on this corrupted value.

The Fix (1.6.54): The fix is elegantly simple: stop assuming the world fits in 16 bits. The variable type was promoted, and the casts were removed or adjusted to handle full integer widths correctly.

> [!NOTE] > This highlights a common anti-pattern: prioritizing "clean builds" (zero warnings) over correct logic. A warning about "implicit conversion loses integer precision" exists for a reason.

The Exploit: Bleeding the Heap

Exploiting this requires a scenario where an attacker controls the arguments passed to png_image_write_to_file or its siblings. While this is a "local" vulnerability, consider a web service that converts raw image data or other formats into PNGs. If the service allows the user to specify metadata like input stride (or if the stride is calculated from user-supplied dimensions that are excessively large), we can trigger the bug.

The Attack Chain:

  1. Setup: Target a service using libpng < 1.6.54 to convert raw buffers to PNG.
  2. Injection: Provide a raw buffer and metadata claiming a negative stride (e.g., for a "vertical flip" operation) or a massive width.
  3. Trigger: The application calls png_image_write_to_file. The internal cast corrupts the stride.
  4. The Over-read: libpng attempts to read row data. Due to the corrupted stride, the read pointer buffer + (y * stride) points far outside the actual input buffer.
  5. Exfiltration: libpng obediently reads this out-of-bounds heap memory—which could contain SSL keys, session tokens, or other users' data—and treats it as pixel data. It compresses this "noise" into the resulting PNG.
  6. Decode: The attacker downloads the generated PNG. Visually, it looks like static noise. Mathematically, it is a dump of the server's heap memory.

The Impact: From Crash to Leak

Why should we care about a library bug from 2016? Because libpng is everywhere. It is in your browser, your OS, your phone, and your server-side image processing pipelines (ImageMagick, GD, etc.).

Availability (DoS): This is the most immediate threat. If the calculated offset lands on an unmapped memory page, the application segfaults immediately. For a service processing user images, this is a trivial Denial of Service.

Confidentiality (Information Disclosure): This is the silent killer. As described in the exploit section, this is effectively a "Heartbleed" for image processing. If the memory layout is favorable (i.e., the offset lands in readable heap memory), the library will serialize sensitive process memory into a valid image file. Detection is difficult because the application doesn't crash; it just produces a "weird" image.

Mitigation: Patching the Hole

The remediation is straightforward: Update libpng to version 1.6.54 immediately.

If you are a developer using libpng directly and cannot update:

  1. Sanitize Input: Ensure that row_stride values passed to the API never exceed 65535 and are never negative if you are passing them to the simplified API functions.
  2. Audit Usage: grep your codebase for png_image_write_to_file. If you are calculating strides based on user input, add bounds checking before the call.

For Linux administrators, this package is likely managed by your distro's package manager. Check the provided ALAS/RHSA advisories. The patch has been backported to most LTS distributions (Ubuntu, Debian, RHEL), so a standard apt update && apt upgrade should suffice.

Official Patches

libpngOfficial GitHub Advisory

Technical Appendix

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

Affected Systems

libpng 1.6.26 - 1.6.53ImageMagick (linked)GD Library (linked)Browsers (older versions linked against system libpng)Linux Distributions (Ubuntu, RedHat, Debian, SUSE)

Affected Versions Detail

Product
Affected Versions
Fixed Version
libpng
libpng
>= 1.6.26, < 1.6.541.6.54
AttributeDetail
CWE IDCWE-125 (Out-of-bounds Read)
CWE IDCWE-190 (Integer Overflow)
CVSS v3.16.8 (Medium)
Attack VectorLocal / Context-Dependent
ImpactInformation Disclosure / DoS
Introducedv1.6.26 (Oct 2016)
Fixedv1.6.54 (Jan 2026)

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.

Vulnerability Timeline

Vulnerability introduced in libpng 1.6.26 via warning suppression casts
2016-10-01
CVE-2026-22801 Assigned and Disclosed
2026-01-12
PoC availability noted by ALAS
2026-01-12
Fix released in libpng 1.6.54
2026-01-21

References & Sources

  • [1]GHSA 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.