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:
- Palette image (IHDR color type 3)
- Partial transparency via tRNS chunk with alpha values between 1 and 254 (not just 0 or 255)
- Gamma correction via gAMA chunk with non-1.0 gamma value
- Simplified API used (
png_image_begin_read_from_* and png_image_finish_read)
- Output format without alpha (e.g.,
PNG_FORMAT_RGB)
- 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:
- It checks
PNG_FLAG_OPTIMIZE_ALPHA and sees YES.
- It assumes pixel data is linear premultiplied (where
component <= alpha).
- But
png_do_compose never ran (PNG_COMPOSE was cleared).
- The palette was premultiplied but converted back to sRGB via
gamma_from_1.
- Expanded pixels are sRGB, not linear; component values can exceed alpha.
- 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.
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:
png_image_begin_read_from_*andpng_image_finish_read)PNG_FORMAT_RGB)background = NULLinpng_image_finish_read)These are all common, legitimate properties of real-world PNG files.
Root Cause
The function
png_image_read_compositeassumes pixel data has been premultiplied bypng_do_compose, producing linear values wherecomponent <= alphaalways holds. This invariant is violated for palette images with gamma correction due to a flag synchronization bug.The Invariant Violation
Location:
pngrtran.c:1845inpng_init_read_transformationsFor palette images with gamma correction, libpng performs composition directly on the palette entries (lines 1773-1836), then clears the
PNG_COMPOSEflag to prevent re-composition:The bug: This clears
PNG_COMPOSEbut does not clearPNG_FLAG_OPTIMIZE_ALPHA.The Resulting State
After
png_init_read_transformationsfor a palette+tRNS+gAMA image:PNG_COMPOSE = NO(cleared at line 1845)PNG_FLAG_OPTIMIZE_ALPHA = YES(still set frompng_set_alpha_mode_fixed)How This Causes the Overflow
When the simplified API calls
png_image_read_composite:PNG_FLAG_OPTIMIZE_ALPHAand seesYES.component <= alpha).png_do_composenever ran (PNG_COMPOSEwas cleared).gamma_from_1.Mathematical Analysis
The formula in
png_image_read_compositeassumes linear premultiplied data wherecomponent <= alpha. Under this assumption:alpha * 65535 + (255-alpha) * 65535 = 255 * 65535 = 1671142516711425 >> 15 = 509However, 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:249 * 65535 + (255-242) * 65535 = 262 * 65535 = 1717017017170170 >> 15 = 524Worst case (
component=255, alpha=1):255 * 65535 + 254 * 65535 = 509 * 65535 = 3335731533357315 >> 15 = 1017Impact
png_sRGB_delta[]and potentially other globals).Fix
The fix consists of two commits:
Commit 1 (788a624): Defensive Bounds Check
Adds a clamp before
PNG_sRGB_FROM_LINEARto prevent OOB read:Commit 2 (a05a48b): Flag Synchronization Fix
Addresses the root cause by clearing
PNG_FLAG_OPTIMIZE_ALPHAwhenPNG_COMPOSEis cleared for palette images: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:
png_image_finish_read(uses different code path).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_LINEARbut occurred inpng_init_read_transformationsduring palette premultiplication. Both vulnerabilities stem from the complexity of the alpha optimization code paths and thePNG_FLAG_OPTIMIZE_ALPHAflag.