Menu

#336 A heap_overflow on PluginTIFF.cpp when Load() TIFF

open
nobody
None
5
2021-08-26
2021-08-26
0x79h
No

tl;dr:
When Load TIFF files, "ImageWidth" and "TileWidth" is read from file. FreeImage alloc memory size is controlled by "ImageWidth", but FreeImage write memory size if controlled by "TileWidth". if result of "TileWidth" diff with "ImageWidth", it will writing data more than the allocated memory.

When the program reads a TIFF file, it will be handed to the Load function of the 'PluginTIFF.cpp' file,
in Load function, we alloc memory by CreateImageType(), and write memory in Load function with "memcpy". And the "height", "width", "bitspersample", "samplesperpixel", "tileRowSize" and "imageRowSize" is read from file.

static FIBITMAP * DLL_CALLCONV
Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) {
......
    uint32_t height = 0; 
    uint32_t width = 0; 
    uint16_t bitspersample = 1;
    uint16_t samplesperpixel = 1;
......
        TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width);
        TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height);
        TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &samplesperpixel);
        TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bitspersample);
        TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);             
        TIFFGetField(tif, TIFFTAG_ICCPROFILE, &iccSize, &iccBuf);
        TIFFGetFieldDefaulted(tif, TIFFTAG_PLANARCONFIG, &planar_config);
.......
        // ---------------------------------------------------------------------------------

        if(loadMethod == LoadAsRBGA) {
......
        } else if(loadMethod == LoadAsTiled) {
            // ---------------------------------------------------------------------------------
            // Tiled image loading
            // ---------------------------------------------------------------------------------

            uint32_t tileWidth, tileHeight;
            uint32_t src_line = 0;

            // create a new DIB                                                     v-------- alloc memory there
            dib = CreateImageType( header_only, image_type, width, height, bitspersample, samplesperpixel);
......
                // calculate src line and dst pitch
                unsigned dst_pitch = FreeImage_GetPitch(dib);
                uint32_t tileRowSize = (uint32_t)TIFFTileRowSize(tif);
                uint32_t imageRowSize = (uint32_t)TIFFScanlineSize(tif);


                // In the tiff file the lines are saved from up to down 
                // In a DIB the lines must be saved from down to up

                BYTE *bits = FreeImage_GetScanLine(dib, height - 1);

                for (uint32_t y = 0; y < height; y += tileHeight) {                 // <---- write memory there     
                    int32_t nrows = (y + tileHeight > height ? height - y : tileHeight);                    

                    for (uint32_t x = 0, rowSize = 0; x < width; x += tileWidth, rowSize += tileRowSize) {
                        memset(tileBuffer, 0, tileSize);

                        // read one tile
                        if (TIFFReadTile(tif, tileBuffer, x, y, 0, 0) < 0) {
                            free(tileBuffer);
                            throw "Corrupted tiled TIFF file";
                        }
                        // convert to strip
                        if(x + tileWidth > width) {
                            src_line = imageRowSize - rowSize;
                        } else {
                            src_line = tileRowSize;
                        }
                        BYTE *src_bits = tileBuffer;
                        BYTE *dst_bits = bits + rowSize;
                        for(int k = 0; k < nrows; k++) {
                            memcpy(dst_bits, src_bits, MIN(dst_pitch, src_line));
                            src_bits += tileRowSize;
                            dst_bits -= dst_pitch;
                        }
                    }

                    bits -= nrows * dst_pitch;
                }
......
}

CreateImageType() alloc memory by "width","height","bitspersample" and "samplesperpixel".

static FIBITMAP* 
CreateImageType(BOOL header_only, FREE_IMAGE_TYPE fit, int width, int height, uint16_t bitspersample, uint16_t samplesperpixel) {
    FIBITMAP *dib = NULL;
......
    int bpp = bitspersample * samplesperpixel;      //<---- bpp == bitspersample * samplesperpixel
......
    dib = FreeImage_AllocateHeader(header_only, width, height, bpp, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK);
......
    return dib;
}

FIBITMAP * DLL_CALLCONV
FreeImage_AllocateHeader(BOOL header_only, int width, int height, int bpp, unsigned red_mask, unsigned green_mask, unsigned blue_mask) {
    return FreeImage_AllocateBitmap(header_only, NULL, 0, FIT_BITMAP, width, height, bpp, red_mask, green_mask, blue_mask);
}

static FIBITMAP * 
FreeImage_AllocateBitmap(BOOL header_only, BYTE *ext_bits, unsigned ext_pitch, FREE_IMAGE_TYPE type, int width, int height, int bpp, unsigned red_mask, unsigned green_mask, unsigned blue_mask) {
......

        size_t dib_size = FreeImage_GetInternalImageSize(header_only || ext_bits, width, height, bpp, need_masks);      //<---- calc alloc size by bpp, width and height

        if(dib_size == 0) {
            // memory allocation will fail (probably a malloc overflow)
            free(bitmap);
            return NULL;
        }

        bitmap->data = (BYTE *)FreeImage_Aligned_Malloc(dib_size * sizeof(BYTE), FIBITMAP_ALIGNMENT);   //<---- real alloc function
.......
}

in FreeImage_GetInternalImageSize(), it calc alloc size

static size_t
FreeImage_GetInternalImageSize(BOOL header_only, unsigned width, unsigned height, unsigned bpp, BOOL need_masks) {
size_t dib_size = sizeof(FREEIMAGEHEADER);
......
if(!header_only) {
const size_t header_size = dib_size;

// pixels are aligned on a 16 bytes boundary
dib_size += (size_t)CalculatePitch(CalculateLine(width, bpp)) * (size_t)height;    //<--- alloc size by line lenght * height, line lenght calc by width and bpp( = bitspersample * samplesperpixel)
......
}

return dib_size;
}

but when we write memory, the read size calc by "tileWidth" and "width", loop (tileHeight//height) * (tileWidth//width) times.

                for (uint32_t y = 0; y < height; y += tileHeight) {                                            //<-- loop tileHeight//height times
                    int32_t nrows = (y + tileHeight > height ? height - y : tileHeight);                    

                    for (uint32_t x = 0, rowSize = 0; x < width; x += tileWidth, rowSize += tileRowSize) { //<-- loop tileWidth//width times
                        memset(tileBuffer, 0, tileSize);

                        // read one tile
                        if (TIFFReadTile(tif, tileBuffer, x, y, 0, 0) < 0) {
                            free(tileBuffer);
                            throw "Corrupted tiled TIFF file";
                        }
                        // convert to strip
                        if(x + tileWidth > width) {
                            src_line = imageRowSize - rowSize;
                        } else {
                            src_line = tileRowSize;
                        }
                        BYTE *src_bits = tileBuffer;
                        BYTE *dst_bits = bits + rowSize;
                        for(int k = 0; k < nrows; k++) {
                            memcpy(dst_bits, src_bits, MIN(dst_pitch, src_line));
                            src_bits += tileRowSize;
                            dst_bits -= dst_pitch;
                        }
                    }

                    bits -= nrows * dst_pitch;
                }

Finally, it will cause the heap overflow if we make the result of "TileWidth" diff with "ImageWidth". Moreover, it may has the risk of arbitrary code execution.


windbg:

ModLoad: 10000000 105ee000   D:\temp\_zzz\FreeImage.dll
ModLoad: 752b0000 752c4000   C:\Windows\SysWOW64\VCRUNTIME140.dll
ModLoad: 755e0000 75643000   C:\Windows\SysWOW64\WS2_32.dll
ModLoad: 77050000 7710f000   C:\Windows\SysWOW64\RPCRT4.dll
(8b8c.710): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=dd680000 edx=00000000 esi=77442044 edi=7744260c
eip=774e1b72 esp=006ff63c ebp=006ff668 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!LdrpDoDebuggerBreak+0x2b:
774e1b72 cc              int     3
0:000> g
(8b8c.710): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for D:\temp\_zzz\FreeImage.dll
eax=00000049 ebx=00000008 ecx=00000001 edx=00000001 esi=06667fd0 edi=06662000
eip=102a6e8f esp=006ff86c ebp=006ffa58 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
FreeImage!memcpy+0x51f:
102a6e8f 8807            mov     byte ptr [edi],al          ds:002b:06662000=??
0:000> db edi-0x20
06661fe0  00 00 00 00 00 00 00 00-2a 2a 2a 2a 2a 2a 2a 2a  ........********
06661ff0  49 49 49 49 49 49 49 49-49 49 49 49 49 49 49 49  IIIIIIIIIIIIIIII
06662000  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
06662010  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
06662020  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
06662030  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
06662040  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
06662050  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
0:000> kv
 # ChildEBP RetAddr      Args to Child              
00 006ff870 100378e8     06662000 06667fd0 00000001 FreeImage!memcpy+0x51f (FPO: [3,0,2]) (CONV: cdecl) [d:\agent\_work\4\s\src\vctools\crt\vcruntime\src\string\i386\memcpy.asm @ 669] 
01 006ffabc 1000d9de     006ffb10 06619fc8 06667fd0 FreeImage!_TIFFmemcmp+0x2d28 (FPO: [5,139,3])
02 006ffaf0 1000da60     00000012 006ffb10 06619fc8 FreeImage!FreeImage_LoadFromHandle+0x8e (FPO: [Uses EBP] [4,3,2])
03 006ffb1c 00b0107b     00000012 06615fee 00000000 FreeImage!FreeImage_Load+0x50 (FPO: [3,4,2])

TestFile:

data:image/tiff;base64,SUkqAAgAAAAGAAABAwABAAAAMAAwMAEBAwABAAAAMAAwMBcBBAAYAAAACAAAABEBAwABAAAAMAABABYBAwABAAAAMAABAEIBAwABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
2 Attachments

Discussion


Log in to post a comment.

MongoDB Logo MongoDB