pnggroup / libpng

LIBPNG: Portable Network Graphics support, official libpng repository
http://libpng.sf.net
Other
1.25k stars 612 forks source link

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

Open PromptFuzz opened 1 year ago

PromptFuzz commented 1 year ago

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)
jbowler commented 8 months ago

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

PromptFuzz commented 8 months ago

@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

jbowler commented 8 months ago

@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 commented 8 months ago

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)