Hi,

Similar memory leak is in tiff loading function. I am sending test cases as
files for both tiff and png.

This is because buffer will not be deleted in case of exception between new
and delete:

    *char* *buffer = *new* *char*[bufferSize];

    *if*( !buffer )

    {

        TIFFClose(hInTiffHandle);

        PODOFO_RAISE_ERROR( ePdfError_OutOfMemory );

    }



    *for*(row = 0; row < height; row++)

    {

        *if*(TIFFReadScanline(hInTiffHandle,

                            &buffer[row * scanlineSize],

                            row) == (-1))

        {

            TIFFClose(hInTiffHandle);

            PODOFO_RAISE_ERROR( ePdfError_UnsupportedImageFormat );

        }

    }



    PdfMemoryInputStream stream(buffer, bufferSize);



    SetImageData(*static_cast*<*unsigned* *int*>(width),

                 *static_cast*<*unsigned* *int*>(height),

                 *static_cast*<*unsigned* *int*>(bitsPerSample),

                 &stream);



    *delete*[] buffer;

Here is output from valgrind and clang memory sanitizer:

==17391== 8,192 bytes in 1 blocks are definitely lost in loss record 1,202
of 1,257
==17391==    at 0x4C3089F: operator new[](unsigned long) (in
/usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==17391==    by 0x970753: PoDoFo::PdfImage::LoadFromTiffHandle(void*)
(PdfImage.cpp:638)
==17391==    by 0x9711ED: PoDoFo::PdfImage::LoadFromTiffData(unsigned char
const*, long) (PdfImage.cpp:838)

==7771==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 8192 byte(s) in 1 object(s) allocated from:
    #0 0x55f796949e05 in operator new[](unsigned long)
/checkout/src/llvm-project/compiler-rt/lib/lsan/lsan_interceptors.cc:231:37
    #1 0x55f796eb7ec5 in PoDoFo::PdfImage::LoadFromTiffHandle(void*)
podofo/doc/PdfImage.cpp:638
    #2 0x55f796eb895f in PoDoFo::PdfImage::LoadFromTiffData(unsigned char
const*, long) podofo/doc/PdfImage.cpp:838

On Tue, Nov 5, 2019 at 8:17 PM Michal Sudolsky <sudols...@gmail.com> wrote:

> If function png_read_image encounters error it will call longjmp and will
> jump to place where was called setjmp. In such case pBuffer and pRows will
> never be deallocated.
>
> Cut of code from mentioned functions:
> ```
>     // Read the file
>     if( setjmp(png_jmpbuf(pPng)) )
>     {
>         png_destroy_read_struct(&pPng, &pInfo, (png_infopp)NULL);
>         PODOFO_RAISE_ERROR( ePdfError_InvalidHandle );
>     }
>
>     long lLen = static_cast<long>(png_get_rowbytes(pPng, pInfo) * height);
>     char* pBuffer = static_cast<char*>(podofo_calloc(lLen, sizeof(char)));
> ...
>     png_bytepp pRows = static_cast<png_bytepp>(podofo_calloc(height,
> sizeof(png_bytep)));
> ...
>     png_read_image(pPng, pRows);
> ...
>     podofo_free(pBuffer);
>     podofo_free(pRows);
> ```
>
> Test example:
> ```
> // include podofo, vector, etc
> int main()
> {
>     PdfMemDocument doc;
>     PdfImage image(&doc);
>     std::vector<unsigned char> corrupt_png = {
>         0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A,
>             0x00, 0x00, 0x00, 0x0D,
>                 'I', 'H', 'D', 'R',
>                 0x00, 0x00, 0x04, 0x00,
>                 0x00, 0x00, 0x05, 0xA9,
>                 0x08, 0x02, 0x00, 0x00, 0x00,
>                 0x68, 0x1B, 0xF7, 0x46,
>             0x00, 0x00, 0x00, 0x00,
>                 'I', 'D', 'A', 'T',
>                 0x35, 0xAF, 0x06, 0x1E,
>             0x00, 0x00, 0x00, 0x00,
>                 'I', 'E', 'N', 'D',
>                 0xAE, 0x42, 0x60, 0x82
>     };
>     try
>     {
>         image.LoadFromPngData(corrupt_png.data(), corrupt_png.size());
>     }
>     catch(PdfError &e)
>     {
>         e.PrintErrorMsg();
>     }
>     return 0;
> }
> ```
>
> Test for leaks using valgrind (libpng outputs errors to stderr as it is
> not muted in podofo like libjpeg and libtiff):
> ```
> libpng error: Not enough image data
>
> PoDoFo encountered an error. Error: 2 ePdfError_InvalidHandle
> Error Description: A NULL handle was passed, but initialized data was
> expected.
> Callstack:
> #0 Error Source: podofo/doc/PdfImage.cpp:1142
>
> ==97240==
> ==97240== HEAP SUMMARY:
> ==97240==     in use at exit: 4,462,920 bytes in 2 blocks
> ==97240==   total heap usage: 128 allocs, 126 frees, 4,567,527 bytes
> allocated
> ==97240==
> ==97240== 4,462,920 (11,592 direct, 4,451,328 indirect) bytes in 1 blocks
> are definitely lost in loss record 2 of 2
> ==97240==    at 0x4C31B25: calloc (in
> /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
> ==97240==    by 0x1759CE: PoDoFo::podofo_calloc(unsigned long, unsigned
> long)
> ==97240==    by 0x1E0EE4: PoDoFo::PdfImage::LoadFromPngData(unsigned char
> const*, long)
> ==97240==    by 0x125610: main
> ==97240==
> ==97240== LEAK SUMMARY:
> ==97240==    definitely lost: 11,592 bytes in 1 blocks
> ==97240==    indirectly lost: 4,451,328 bytes in 1 blocks
> ==97240==      possibly lost: 0 bytes in 0 blocks
> ==97240==    still reachable: 0 bytes in 0 blocks
> ==97240==         suppressed: 0 bytes in 0 blocks
> ```
>
> There is png image which advertises that it has dimensions 1024x1449 and
> RGB colorspace so after decompression it will take 1024 * 1449 * 3 =
> 4,451,328 bytes of memory as pBuffer. It has height 1449 so there are
> allocated 1449 row pointers which takes 1449 * 8 = 11,592 bytes as pRows.
> Buffer pBuffer is reported as indirect loss because there are pointers
> in pRows which points to it.
>
> This leak is especially dangerous in server applications that uses podofo
> as library. Let say such application takes as inputs from network pdf and
> image then inserts image into pdf and sends resulting pdf back. Attacker
> can repeatedly send crafted png images like in test example above to
> exhaust memory and cause crash.
>
>
> Also line "if( setjmp(png_jmpbuf(pPng)) )" is probably undefined behaviour
> (there should be comparison with integer constant):
>
> https://en.cppreference.com/w/cpp/utility/program/setjmp
>
>
> Also function pngReadData should call png_error in case it cannot read
> requested length of data but instead it will read only available data or do
> nothing (if _size == _pos):
>
> ```
>         if (length > _size - _pos)
>         {
>             memcpy(data, &_data[_pos], _size - _pos);
>             _pos = _size;
>         }
>         else
>         {
>             memcpy(data, &_data[_pos], length);
>             _pos += length;
>         }
> ```
>
>
> Also are you sure that throwing C++ exceptions from function which is
> called from C code in another library (libjpeg) is not undefined behaviour
> or something? Maybe in PdfImage::LoadFromJpegData should be used
> setjmp/longjmp mechanism too:
>
> ```
> extern "C" {
> void JPegErrorExit(j_common_ptr cinfo)
> {
> #if 1
>     char buffer[JMSG_LENGTH_MAX];
>
>     /* Create the message */
>     (*cinfo->err->format_message) (cinfo, buffer);
> #endif
>     jpeg_destroy(cinfo);
>     PODOFO_RAISE_ERROR_INFO( ePdfError_UnsupportedImageFormat, buffer);
> }
> ```
>
>
_______________________________________________
Podofo-users mailing list
Podofo-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/podofo-users

Reply via email to