Summary
Heap buffer over-read in the libpng simplified API function png_image_finish_read when processing interlaced 16-bit PNGs with 8-bit output format and non-minimal row stride. This is a regression introduced by the fix for CVE-2025-65018.
Technical Details
Vulnerability Mechanism
- Application uses simplified API with interlaced 16-bit PNG and 8-bit output.
- Application provides
row_stride larger than actual row width (for alignment) or negative (for bottom-up layout).
- The
png_image_read_direct_scaled() function (introduced in CVE-2025-65018 fix) uses stride instead of row width for memcpy size.
- Buffer over-read occurs because
local_row is sized for actual row width, not stride.
Root Cause
The new png_image_read_direct_scaled() function contains:
memcpy(output_row, local_row, (size_t)row_bytes);
This violates the established pattern in pngread.c where:
- Stride (
row_bytes) is used for pointer advancement
- Width (
png_get_rowbytes()) is used for data bounds/copy sizes
Attack Scenarios
Padded Stride (over-read):
row_stride = 144 bytes (aligned)
row_width = 128 bytes (actual data)
Over-read = 16 bytes per row
Negative Stride (crash):
row_stride = -128
(size_t)(-128) = 18,446,744,073,709,551,488
Result: memcpy attempts to read ~18 exabytes → crash
Impact
Attack Vector
Prerequisites:
- Application uses simplified API (
png_image_*).
- Application processes untrusted PNG files.
- Application uses non-minimal row stride (common for alignment or bottom-up layouts).
Attack: Craft interlaced 16-bit PNG + application uses padded/negative stride ==> heap over-read or crash
Consequences
- Information Disclosure (Low): Over-read may expose adjacent heap data.
- Denial of Service (High): Negative stride causes deterministic crash.
Exploitability
- Attack Complexity: Low (common API usage patterns).
- User Interaction: Required (victim opens PNG file).
- Privileges Required: None.
Affected Software
Applications using simplified API with non-default row stride:
- Image viewers with memory-aligned buffers.
- Graphics frameworks using bottom-up pixel layouts.
- Game engines with specific texture alignment requirements.
Note: Applications using default stride (row_stride = 0 or row_stride = PNG_IMAGE_ROW_STRIDE(image)) are NOT affected.
Fix
Implementation
@@ -3138,9 +3138,11 @@ png_image_read_direct_scaled(png_voidp argument)
png_imagep image = display->image;
png_structrp png_ptr = image->opaque->png_ptr;
+ png_inforp info_ptr = image->opaque->info_ptr;
png_bytep local_row = png_voidcast(png_bytep, display->local_row);
png_bytep first_row = png_voidcast(png_bytep, display->first_row);
ptrdiff_t row_bytes = display->row_bytes;
+ size_t copy_bytes = png_get_rowbytes(png_ptr, info_ptr);
int passes;
@@ -3170,7 +3172,7 @@ png_image_read_direct_scaled(png_voidp argument)
png_read_row(png_ptr, local_row, NULL);
/* Copy from local_row to user buffer. */
- memcpy(output_row, local_row, (size_t)row_bytes);
+ memcpy(output_row, local_row, copy_bytes);
output_row += row_bytes;
}
}
Key Changes
- Add
info_ptr declaration to access row width via png_get_rowbytes().
- Add
copy_bytes variable for actual data size.
- Use
copy_bytes for memcpy size (data bounds).
- Keep
row_bytes for pointer advancement (navigation).
This follows the established pattern throughout pngread.c.
Detection
Vulnerable Code Pattern
Applications are vulnerable if they:
- Process interlaced 16-bit PNGs via simplified API
- Use non-default row stride
/* Vulnerable pattern */
ptrdiff_t stride = (row_width + 15) & ~15; /* Aligned stride */
png_image_finish_read(&image, NULL, buffer, stride, NULL); /* VULNERABLE */
/* Also vulnerable */
ptrdiff_t stride = -row_width; /* Bottom-up layout */
png_image_finish_read(&image, NULL, last_row, stride, NULL); /* VULNERABLE */
Testing
Create interlaced 16-bit test PNG:
convert -depth 16 -size 32x32 -interlace PNG xc:red test.png
Run with AddressSanitizer:
clang -fsanitize=address -g test_program.c -lpng -o test
./test test.png
Expected:
- Vulnerable versions (1.6.51-1.6.53): ASan reports heap-buffer-overflow READ
- Fixed versions (1.6.54+): Clean execution
Timeline
| Date |
Event |
| 2025-11-19 |
CVE-2025-65018 fix committed (introduces regression) |
| 2025-11-21 |
libpng 1.6.51 released |
| 2025-12-03 |
libpng 1.6.52 released |
| 2025-12-05 |
libpng 1.6.53 released |
| 2026-01-06 |
@simecek reports regression with proposed fix (GitHub Issue #778) |
| 2026-01-07 |
Vulnerability and proposed fix confirmed |
| 2026-01-09 |
CVE requested |
| 2026-01-12 |
libpng 1.6.54 released |
Relationship to CVE-2025-65018
This vulnerability is a regression introduced by the fix for CVE-2025-65018:
|
CVE-2025-65018 (GH-755) |
This Issue (GH-778) |
| Type |
Heap buffer overflow (WRITE) |
Heap buffer over-read (READ) |
| CWE |
CWE-787 |
CWE-125 |
| Location |
png_combine_row() |
png_image_read_direct_scaled() |
| Trigger |
Interlaced 16-bit PNG, any stride |
Interlaced 16-bit PNG, non-default stride |
| Introduced |
libpng 1.6.0 |
libpng 1.6.51 (by CVE-2025-65018 fix) |
| Impact |
Write overflow, potential RCE |
Read overflow, info leak + DoS |
References
Credits
- Vulnerability discovery: Petr Simecek, Stanislav Fort, Pavel Kohout
- Proposed fix: Petr Simecek
- Verification and release: Cosmin Truta (libpng maintainer)
Summary
Heap buffer over-read in the libpng simplified API function
png_image_finish_readwhen processing interlaced 16-bit PNGs with 8-bit output format and non-minimal row stride. This is a regression introduced by the fix for CVE-2025-65018.Technical Details
Vulnerability Mechanism
row_stridelarger than actual row width (for alignment) or negative (for bottom-up layout).png_image_read_direct_scaled()function (introduced in CVE-2025-65018 fix) uses stride instead of row width formemcpysize.local_rowis sized for actual row width, not stride.Root Cause
The new
png_image_read_direct_scaled()function contains:This violates the established pattern in
pngread.cwhere:row_bytes) is used for pointer advancementpng_get_rowbytes()) is used for data bounds/copy sizesAttack Scenarios
Padded Stride (over-read):
Negative Stride (crash):
Impact
Attack Vector
Prerequisites:
png_image_*).Attack: Craft interlaced 16-bit PNG + application uses padded/negative stride ==> heap over-read or crash
Consequences
Exploitability
Affected Software
Applications using simplified API with non-default row stride:
Note: Applications using default stride (
row_stride = 0orrow_stride = PNG_IMAGE_ROW_STRIDE(image)) are NOT affected.Fix
Implementation
Key Changes
info_ptrdeclaration to access row width viapng_get_rowbytes().copy_bytesvariable for actual data size.copy_bytesfor memcpy size (data bounds).row_bytesfor pointer advancement (navigation).This follows the established pattern throughout
pngread.c.Detection
Vulnerable Code Pattern
Applications are vulnerable if they:
Testing
Create interlaced 16-bit test PNG:
Run with AddressSanitizer:
clang -fsanitize=address -g test_program.c -lpng -o test ./test test.pngExpected:
Timeline
Relationship to CVE-2025-65018
This vulnerability is a regression introduced by the fix for CVE-2025-65018:
png_combine_row()png_image_read_direct_scaled()References
Credits