libpng icon indicating copy to clipboard operation
libpng copied to clipboard

An infinite loop in png_read_png->..->png_write_row

Open PromptFuzz opened this issue 1 year ago • 4 comments

Summary

A infinite loop bug found in png_read_png. Remote attackers could leverage this vulnerability to cause a denial-of-service via a crafted PNG file.

POC

#include <png.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <vector>
#include <fstream>
#include <iostream>

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {

    // Initialize libpng variables
    png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    png_infop info_ptr = png_create_info_struct(png_ptr);

    // Create a FILE pointer to read the input data
    FILE *in_file = fmemopen((void *)data, size, "rb");
    
    // Set up the read callback function
    png_set_read_fn(png_ptr, (png_voidp)in_file, [](png_structp png_ptr, png_bytep data, png_size_t length) {
        fread(data, 1, length, (FILE *)png_get_io_ptr(png_ptr));
    });
    
    // Read the PNG image
    png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
  
  return 0;
}

POC input

timeout-f74021412fba530904cddd63e3033f1527d52d76

Version

Found on version of 2023/06/07. Reproducible on the master branch.

Compile commands

# export the flags.
    SANITIZER_FLAGS="-O2 -fsanitize=address,undefined -fsanitize-address-use-after-scope -g "
    FUZZER_FLAGS="-fsanitize=fuzzer-no-link -fno-omit-frame-pointer -g -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION $SANITIZER_FLAGS"
    export CFLAGS="${CFLAGS:-} $FUZZER_FLAGS"
    export CXXFLAGS="${CXXFLAGS:-} $FUZZER_FLAGS"
# build the libpng library.
    cd $SRC/libpng
    autoreconf -f -i
    ./configure
    make -j$(nproc) clean
    make -j$(nproc) libpng16.la

Compile the poc program

clang++ -fsanitize=fuzzer -O0 -g -fsanitize=address,undefined -ftrivial-auto-var-init=zero -enable-trivial-auto-var-init-zero-knowing-it-will-be-removed-from-clang -I/src/libpng/include  poc.cc -o test.out /src/libpng/lib/libpng16.so

Reproduce Step

./test.out timeout-f74021412fba530904cddd63e3033f1527d52d76

Additional Information

When the variable i = 0xff (image_height = 0x100) in the loop from lines 751-755, the png_read_row(png_ptr, *rp, NULL); will hang.

//pngread.c
void PNGAPI
png_read_image(png_structrp png_ptr, png_bytepp image) {
...
   for (j = 0; j < pass; j++)
   {
      rp = image;
751      for (i = 0; i < image_height; i++)
752      {
753        png_read_row(png_ptr, *rp, NULL);
754        rp++;
755      }
   }

The program finally hang at the below loop.

png_read_IDAT_data(png_structrp png_ptr, png_bytep output,
    png_alloc_size_t avail_out)
{
...
   do
   {
      ... // hang
  }while (avail_out > 0);

Stack trace

...
[#3] 0x7ffff76278d4 → png_read_IDAT_data(png_ptr=0x61a000000080, output=0x0, avail_out=0x91bd0800)
[#4] 0x7ffff7628f13 → png_read_finish_IDAT(png_ptr=0x61a000000080)
[#5] 0x7ffff762c960 → png_read_finish_row(png_ptr=0x61a000000080)
[#6] 0x7ffff74cc279 → png_read_row(png_ptr=0x61a000000080, row=0x617000037d00 "\003\002\001\006\004\002\t\006\003\f\b\004\017\n\005\022\f\006\025\016\a\030\020\b\033\022\t\036\024\n!\026\v$\030\f'\032\r*\034\016-\036\0170 \0203\"\0216$\0229&\023<(\024?*\025B,\026E.\027H0\030K2\031N4\032Q6\033T8\034W:\035Z<\036]>\037`@ cB!fD\"iF#lH$oJ%rL&uN'xP({R)~T*\201V+\204X,\207Z-\212\\.\215^/\220`0\223b1\226d2\231f3\234h4\237j5\242l6\245n7\250p8\253r9\256t:\261v;\264x<\267z=\272|>\275~?\300\200@ÂAƄBɆC̈DϊEҌFՎGؐHےIޔJ\341\226K\344\230L\347\232M\352\234N\355\236O\360\240P\363\242Q\366\244R\371\246S\374\250T\377\252U\002\254V\005\256W\b\260X\v\262Y\016\264Z\021\266[\024\270\\\027\272]\032\274^\035\276_ \300`#\302a&\304b)\306c,\310d/\312e2\314f5\316g8\320h;\322i>\324jA\326kD\330lG\332mJ\334nM\336oP\340pS\342qV\344rY\346s\\\350t_\352ub\354ve\356wh\360xk\362yn\364zq\366{t\370|w\372}z\374~}\376\177\200", dsp_row=0x0)
[#7] 0x7ffff74d050f → png_read_image(png_ptr=0x61a000000080, image=0x61d000000080)
[#8] 0x7ffff74d805e → png_read_png(png_ptr=0x61a000000080, info_ptr=0x6130000003c0, transforms=0x0, params=0x0)
[#9] 0x5555556d8ef4 → LLVMFuzzerTestOneInput(data=0x613000000200 "\211PNG\r\n\032\n", size=0x160)

PromptFuzz avatar Aug 30 '23 07:08 PromptFuzz

I suggest you submit a small program and the input file that will repro this. As reported the bug is incredible.

jbowler avatar Dec 23 '23 00:12 jbowler

@jbowler Hi, I have provided the PoC program and PoC input at: timeout_poc.tar.gz

You can reproduce this issue by running: poc.out timeout-f74021412fba530904cddd63e3033f1527d52d76

PromptFuzz avatar Dec 28 '23 07:12 PromptFuzz

@jbowler Hi, I have provided the PoC program and PoC input at: [timeout_poc.tar.gz]

You need to remove that file, it is reported by Chrome as containing a virus. I've separately reported this to github.com

Please do not post compiled programs here. They are not useful in bug reports. What I'm asking for is a simple example which compiles, links, runs and demonstrates the problem. A program of this size is likely to be inappropriate even if you provide the source code.

jbowler avatar Dec 28 '23 21:12 jbowler

But apart from that your code is wrong; your read function does no error handling so when it reaches the end of the file (which it does because the enormous IDAT at the end is truncated) it just keeps on reading.

@ctruta: application bug (bad read function)

jbowler avatar Dec 28 '23 22:12 jbowler