Summary
Heap buffer overflow in the libpng simplified API function png_image_finish_read when processing 16-bit interlaced PNGs with 8-bit output format. Attacker-crafted interlaced PNG files cause heap writes beyond allocated buffer bounds.
Technical Details
Vulnerability Mechanism
- PNG IHDR declares 16-bit color depth with interlacing (Adam7).
- Application requests 8-bit output in RGBA format (
PNG_FORMAT_RGBA).
- Application allocates
PNG_IMAGE_SIZE(image) bytes (sized for 8-bit output).
- During interlaced image processing,
png_combine_row writes using 16-bit IHDR depth before transformation.
- Buffer overflow occurs because the function writes 16-bit data to an 8-bit sized buffer.
Root Cause
For interlaced PNGs, the simplified API's promise that PNG_IMAGE_SIZE(image) suffices is violated during 16-to-8 bit transformations. The internal png_combine_row function processes interlaced images by writing using the IHDR bit-depth (16-bit) before the bit-depth transformation occurs, but the buffer is sized for the requested output format (8-bit). Non-interlaced images are not affected as they use a different processing path.
Proof of Concept
png_image image;
memset(&image, 0, sizeof(image));
image.version = PNG_IMAGE_VERSION;
png_image_begin_read_from_file(&image, "malicious.png"); // 16-bit RGB, interlaced
image.format = PNG_FORMAT_RGBA; // 8-bit output
size_t size = PNG_IMAGE_SIZE(image); // Returns 4096 bytes
void *buffer = malloc(size);
png_image_finish_read(&image, NULL, buffer, 0, NULL); // Writes 6144 bytes (VULNERABLE!)
Overflow Calculation
32×32 pixels, 16-bit RGB (IHDR) to 8-bit RGBA (requested output):
IHDR declares: 16 bits/channel × 3 channels = 6 bytes/pixel
Output requested: 8 bits/channel × 4 channels = 4 bytes/pixel
Buffer allocated: 32×32×4 = 4096 bytes (sized for 8-bit output)
Data written: 32×32×6 = 6144 bytes (using IHDR depth)
Overflow: 6144 - 4096 = 2048 bytes
Larger images produce proportionally larger overflows. For example, for a 256×256 interlaced image, the overflow is 131072 bytes.
Impact
Attack Vector
Prerequisites:
- Application uses simplified API (
png_image_*).
- Application processes untrusted PNG files.
- Application requests 8-bit output format.
Attack: Craft 16-bit interlaced PNG ==> heap overflow during png_image_finish_read ==> heap corruption
Consequences
- Memory Corruption (High): A buffer overflow of a size proportional to the image width enables heap metadata corruption.
- Arbitrary Code Execution (Moderate): Heap corruption exploitable for control-flow hijacking.
- Denial of Service (High): Deterministic crash.
Exploitability
- Attack Complexity: Low (trivial IHDR modification).
- User Interaction: Required (victim opens malicious PNG).
- Privileges Required: None.
Affected Software
Applications using simplified API:
- Image viewers supporting 16-bit PNG images.
- Game engines loading 16-bit PNG textures.
- Document processors (PDF viewers, office suites).
The low-level libpng API is not affected. Functions like png_read_png manage their buffers internally.
Fix
The fix evolved through two consecutive commits:
- commit 16b5e3823918840aae65c0a6da57c78a5a496a4d: the initial fix.
- commit 218612ddd6b17944e21eda56caf8b4bf7779d1ea: the final fix.
Evolution
The fix required two commits:
- Initial (16b5e38): Rejected all 16-to-8 bit transformations via validation. Too restrictive—broke legitimate non-interlaced cases.
- Final (218612d): Removed validation, added intermediate buffer for interlaced images only. Maintains full API compatibility.
For downstream maintainers: Apply both commits together. The first alone will reject legitimate transformations.
Implementation
For interlaced 16-to-8 bit conversions, the fix uses an intermediate buffer:
/* Detect interlaced images requiring 16-to-8 scaling */
if (png_ptr->interlaced != 0)
do_local_scale = 1;
/* Allocate intermediate buffer sized for transformed output */
png_voidp row = png_malloc(png_ptr, png_get_rowbytes(png_ptr, info_ptr));
display->local_row = row;
/* Process via png_image_read_direct_scaled using intermediate buffer */
result = png_safe_execute(image, png_image_read_direct_scaled, display);
png_free(png_ptr, row);
The new png_image_read_direct_scaled function reads interlaced passes into local_row (sized for transformed 8-bit output), then copies to the user buffer. This prevents png_combine_row from writing 16-bit data into an 8-bit sized buffer.
Key Changes
- Non-interlaced images: Continue using the fast path unchanged (safe).
- Interlaced images: Use intermediate
local_row buffer sized for 16-bit data.
- API compatibility: 16-to-8 bit transformations remain supported.
Rationale: This targeted fix addresses the vulnerability specifically for interlaced images where png_combine_row writes using IHDR bit-depth. The intermediate buffer prevents overflow while maintaining full API compatibility and performance for non-interlaced images.
Mitigation
Application Developers
Immediate (before upgrading to libpng 1.6.51):
If you cannot upgrade immediately and need to process untrusted PNG files:
/* WARNING: This is an UNSUPPORTED workaround that accesses internal libpng
* structures (image.opaque) which violate the simplified API contract and
* are NOT part of the public API. This code may break in ANY future libpng
* version, and may fail compilation if libpng changes internal definitions.
*
* USE ONLY as an emergency temporary measure.
* Upgrade to libpng 1.6.51+ immediately.
*/
png_image image;
memset(&image, 0, sizeof(image));
image.version = PNG_IMAGE_VERSION;
if (png_image_begin_read_from_file(&image, filename))
{
png_structp png_ptr = image.opaque->png_ptr;
png_infop info_ptr = image.opaque->info_ptr;
/* Workaround: For interlaced 16-bit PNGs, force 16-bit output */
if (png_get_bit_depth(png_ptr, info_ptr) == 16 &&
png_get_interlace_type(png_ptr, info_ptr) != PNG_INTERLACE_NONE)
image.format = PNG_FORMAT_LINEAR_RGBA; // Force 16-bit output
else
image.format = PNG_FORMAT_RGBA; // Safe to use 8-bit output
size_t size = PNG_IMAGE_SIZE(image);
void *buffer = malloc(size);
png_image_finish_read(&image, NULL, buffer, 0, NULL);
}
Long-term: Upgrade to libpng 1.6.51+ where 16-to-8 bit transformations are fully supported and safe for all image types.
Distribution Maintainers
Priority: High
Apply to all supported versions. Notify downstream packages.
Detection
Vulnerable Code Pattern
image.format = PNG_FORMAT_RGBA; // Hardcoded 8-bit
png_image_finish_read(&image, ...); // No depth validation
Testing
Create test case with 16-bit interlaced PNG:
convert -depth 16 -size 32x32 -interlace Adam7 xc:red test.png
./your_app test.png
Expected results:
- Vulnerable versions: Crash with heap-buffer-overflow (detected by ASan)
- Patched versions: Successfully process the image
- Note: Non-interlaced 16-bit PNGs do NOT trigger the vulnerability
References
Summary
Heap buffer overflow in the libpng simplified API function
png_image_finish_readwhen processing 16-bit interlaced PNGs with 8-bit output format. Attacker-crafted interlaced PNG files cause heap writes beyond allocated buffer bounds.Technical Details
Vulnerability Mechanism
PNG_FORMAT_RGBA).PNG_IMAGE_SIZE(image)bytes (sized for 8-bit output).png_combine_rowwrites using 16-bit IHDR depth before transformation.Root Cause
For interlaced PNGs, the simplified API's promise that
PNG_IMAGE_SIZE(image)suffices is violated during 16-to-8 bit transformations. The internalpng_combine_rowfunction processes interlaced images by writing using the IHDR bit-depth (16-bit) before the bit-depth transformation occurs, but the buffer is sized for the requested output format (8-bit). Non-interlaced images are not affected as they use a different processing path.Proof of Concept
Overflow Calculation
32×32 pixels, 16-bit RGB (IHDR) to 8-bit RGBA (requested output):
Larger images produce proportionally larger overflows. For example, for a 256×256 interlaced image, the overflow is 131072 bytes.
Impact
Attack Vector
Prerequisites:
png_image_*).Attack: Craft 16-bit interlaced PNG ==> heap overflow during
png_image_finish_read==> heap corruptionConsequences
Exploitability
Affected Software
Applications using simplified API:
The low-level libpng API is not affected. Functions like
png_read_pngmanage their buffers internally.Fix
The fix evolved through two consecutive commits:
Evolution
The fix required two commits:
For downstream maintainers: Apply both commits together. The first alone will reject legitimate transformations.
Implementation
For interlaced 16-to-8 bit conversions, the fix uses an intermediate buffer:
The new
png_image_read_direct_scaledfunction reads interlaced passes intolocal_row(sized for transformed 8-bit output), then copies to the user buffer. This preventspng_combine_rowfrom writing 16-bit data into an 8-bit sized buffer.Key Changes
local_rowbuffer sized for 16-bit data.Rationale: This targeted fix addresses the vulnerability specifically for interlaced images where
png_combine_rowwrites using IHDR bit-depth. The intermediate buffer prevents overflow while maintaining full API compatibility and performance for non-interlaced images.Mitigation
Application Developers
Immediate (before upgrading to libpng 1.6.51):
If you cannot upgrade immediately and need to process untrusted PNG files:
Long-term: Upgrade to libpng 1.6.51+ where 16-to-8 bit transformations are fully supported and safe for all image types.
Distribution Maintainers
Priority: High
Apply to all supported versions. Notify downstream packages.
Detection
Vulnerable Code Pattern
Testing
Create test case with 16-bit interlaced PNG:
Expected results:
References