Skip to content

Out-of-bounds read in `png_image_read_composite`

High
ctruta published GHSA-9mpm-9pxh-mg4f Dec 3, 2025

Package

libpng

Affected versions

>= 1.6.0, < 1.6.52

Patched versions

1.6.52

Description

Summary

An out-of-bounds read vulnerability in libpng's simplified API allows reading up to 1012 bytes beyond the png_sRGB_base[512] array when processing palette PNG images with partial transparency and gamma correction.

Important: The PNG files that trigger this vulnerability are valid per the PNG specification. No malicious crafting is required. The bug is in libpng's internal state management, not in the image data. Any legitimate PNG meeting the trigger conditions will cause the out-of-bounds read.

Trigger Conditions

All of the following conditions must be met:

  1. Palette image (IHDR color type 3)
  2. Partial transparency via tRNS chunk with alpha values between 1 and 254 (not just 0 or 255)
  3. Gamma correction via gAMA chunk with non-1.0 gamma value
  4. Simplified API used (png_image_begin_read_from_* and png_image_finish_read)
  5. Output format without alpha (e.g., PNG_FORMAT_RGB)
  6. No explicit background color (background = NULL in png_image_finish_read)

These are all common, legitimate properties of real-world PNG files.

Root Cause

The function png_image_read_composite assumes pixel data has been premultiplied by png_do_compose, producing linear values where component <= alpha always holds. This invariant is violated for palette images with gamma correction due to a flag synchronization bug.

The Invariant Violation

Location: pngrtran.c:1845 in png_init_read_transformations

For palette images with gamma correction, libpng performs composition directly on the palette entries (lines 1773-1836), then clears the PNG_COMPOSE flag to prevent re-composition:

/* Prevent the transformations being done again. */
png_ptr->transformations &= ~(PNG_COMPOSE | PNG_GAMMA);

The bug: This clears PNG_COMPOSE but does not clear PNG_FLAG_OPTIMIZE_ALPHA.

The Resulting State

After png_init_read_transformations for a palette+tRNS+gAMA image:

  • PNG_COMPOSE = NO (cleared at line 1845)
  • PNG_FLAG_OPTIMIZE_ALPHA = YES (still set from png_set_alpha_mode_fixed)

How This Causes the Overflow

When the simplified API calls png_image_read_composite:

  1. It checks PNG_FLAG_OPTIMIZE_ALPHA and sees YES.
  2. It assumes pixel data is linear premultiplied (where component <= alpha).
  3. But png_do_compose never ran (PNG_COMPOSE was cleared).
  4. The palette was premultiplied but converted back to sRGB via gamma_from_1.
  5. Expanded pixels are sRGB, not linear; component values can exceed alpha.
  6. The formula computes an out-of-range index:
component *= 257*255;  /* =65535 */
component += (255-alpha)*png_sRGB_table[outrow[c]];
component = PNG_sRGB_FROM_LINEAR(component);  /* OOB read here */

Mathematical Analysis

The formula in png_image_read_composite assumes linear premultiplied data where component <= alpha. Under this assumption:

  • Maximum: alpha * 65535 + (255-alpha) * 65535 = 255 * 65535 = 16711425
  • Index: 16711425 >> 15 = 509
  • Verdict: Valid index (array size is 512)

However, PNG alpha is explicitly non-premultiplied per the PNG specification. When the data arrives as sRGB (due to the flag synchronization bug), component values can legitimately exceed alpha. For example, with component=249, alpha=242:

  • Value: 249 * 65535 + (255-242) * 65535 = 262 * 65535 = 17170170
  • Index: 17170170 >> 15 = 524
  • Verdict: Invalid index, 12 entries (24 bytes) past buffer

Worst case (component=255, alpha=1):

  • Value: 255 * 65535 + 254 * 65535 = 509 * 65535 = 33357315
  • Index: 33357315 >> 15 = 1017
  • Verdict: Invalid index, 506 entries (1012 bytes) past buffer

Impact

  • Information disclosure: Reading adjacent global data (png_sRGB_delta[] and potentially other globals).
  • Denial of service: Read extending up to 1012 bytes past array may access unmapped memory and crash.
  • No attacker control required: Legitimate, non-malicious PNG files trigger this bug.

Fix

The fix consists of two commits:

Commit 1 (788a624): Defensive Bounds Check

Adds a clamp before PNG_sRGB_FROM_LINEAR to prevent OOB read:

if (component > 255*65535)
   component = 255*65535;
component = PNG_sRGB_FROM_LINEAR(component);

Commit 2 (a05a48b): Flag Synchronization Fix

Addresses the root cause by clearing PNG_FLAG_OPTIMIZE_ALPHA when PNG_COMPOSE is cleared for palette images:

/* In png_init_read_transformations (pngrtran.c) */
png_ptr->transformations &= ~(PNG_COMPOSE | PNG_GAMMA);
png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA;  /* Added */

And using the appropriate composition formula based on the flag state.

Mitigation

Upgrade to libpng 1.6.52 or later.

If immediate upgrade is not possible:

  • Provide an explicit background color to png_image_finish_read (uses different code path).
  • Use the low-level API instead of simplified API.
  • Request alpha-preserving output (e.g., PNG_FORMAT_RGBA) to avoid composition path.

Related Vulnerabilities

This is distinct from CVE-2025-64720 (fixed in libpng 1.6.51), which also involved PNG_sRGB_FROM_LINEAR but occurred in png_init_read_transformations during palette premultiplication. Both vulnerabilities stem from the complexity of the alpha optimization code paths and the PNG_FLAG_OPTIMIZE_ALPHA flag.

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
Required
Scope
Unchanged
Confidentiality
Low
Integrity
None
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H

CVE ID

CVE-2025-66293

Weaknesses

Out-of-bounds Read

The product reads data past the end, or before the beginning, of the intended buffer. Learn more on MITRE.

Credits