Menu

#337 A heap_overflow on PluginJPEG.cpp when Load() SOF(Start Of Frame) JPEG

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

tl;dr:
When Load SOF(Start Of Frame) JPEG files, "components" of SOF is read from file. FreeImage alloc memory size is controlled by "components" , but sometime FreeImage replace default size to alloc. if "components" is greater than default in alloc function, it will writing data more than the allocated memory.

When the program reads a JPEG file, it will be handed to the Load function of the 'PluginJPEG.cpp' file,
in Load function, we alloc memory by FreeImage_AllocateHeader(), and write memory in jpeg_read_scanlines(). And the "components" is read from file.

static FIBITMAP * DLL_CALLCONV
Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) {
    if (handle) {
......
        struct jpeg_decompress_struct cinfo;
......
            // step 3: read handle parameters with jpeg_read_header()

            jpeg_read_header(&cinfo, TRUE);                //<---- read components from file
......
            if((cinfo.output_components == 4) && (cinfo.out_color_space == JCS_CMYK)) {
......
            } else {
                // RGB or greyscale image              v------------ alloc memory function
                dib = FreeImage_AllocateHeader(header_only, cinfo.output_width, cinfo.output_height, 8 * cinfo.output_components, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK);
......
            }
......
            if((cinfo.out_color_space == JCS_CMYK) && ((flags & JPEG_CMYK) != JPEG_CMYK)) {
......
            } else if((cinfo.out_color_space == JCS_CMYK) && ((flags & JPEG_CMYK) == JPEG_CMYK)) {
......
            } else {
                // normal case (RGB or greyscale image)

                while (cinfo.output_scanline < cinfo.output_height) {
                    JSAMPROW dst = FreeImage_GetScanLine(dib, cinfo.output_height - cinfo.output_scanline - 1);

                    jpeg_read_scanlines(&cinfo, &dst, 1);       // <---------- write memory
                }

                // step 7b: swap red and blue components (see LibJPEG/jmorecfg.h: #define RGB_RED, ...)
                // The default behavior of the JPEG library is kept "as is" because LibTIFF uses 
                // LibJPEG "as is".
......
    }

    return NULL;
}

FreeImage_AllocateHeader() alloc memory by "output_components", but FreeImage_AllocateHeader function maybe replace default size with it.

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) {
.......
    // check pixel bit depth
    switch(type) {
        case FIT_BITMAP:
            switch(bpp) {
                case 1:
                case 4:
                case 8:
                    break;
                case 16:
                    need_masks = TRUE;
                    break;
                case 24:
                case 32:
                    break;
                default:
                    bpp = 8;                                                                    //<---- change bpp there
                    break;
            }
            break;
.......
        size_t dib_size = FreeImage_GetInternalImageSize(header_only || ext_bits, width, height, bpp, need_masks);          //<---- calc alloc size by bpp(num_components)

        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(num_components)
......
    }

    return dib_size;
}

but when we use jpeg_read_scanlines, the read size calc by "num_components", usually equal "output_components".

METHODDEF(void)
null_convert (j_decompress_ptr cinfo,
          JSAMPIMAGE input_buf, JDIMENSION input_row,
          JSAMPARRAY output_buf, int num_rows)
{
  register JSAMPROW outptr;
  register JSAMPROW inptr;
  register JDIMENSION count;
  register int num_comps = cinfo->num_components;
  JDIMENSION num_cols = cinfo->output_width;
  int ci;

  while (--num_rows >= 0) {
    /* It seems fastest to make a separate pass for each component. */
    for (ci = 0; ci < num_comps; ci++) {
      inptr = input_buf[ci][input_row];
      outptr = output_buf[0] + ci;
      for (count = num_cols; count > 0; count--) {
    *outptr = *inptr++; /* don't need GETJSAMPLE() here */        //<---- copy size calc by width(output_width) and bpp(num_components)
    outptr += num_comps;
      }
    }
    input_row++;
    output_buf++;
  }
}

callstack

FreeImage.dll!null_convert(jpeg_decompress_struct * cinfo, unsigned char * * * input_buf, unsigned int input_row, unsigned char * * output_buf, int num_rows)
FreeImage.dll!sep_upsample(jpeg_decompress_struct * cinfo, unsigned char * * * input_buf, unsigned int * in_row_group_ctr, unsigned int in_row_groups_avail, unsigned char * * output_buf, unsigned int * out_row_ctr, unsigned int out_rows_avail)
FreeImage.dll!process_data_simple_main(jpeg_decompress_struct * cinfo, unsigned char * * output_buf, unsigned int * out_row_ctr, unsigned int out_rows_avail)
FreeImage.dll!jpeg_read_scanlines(jpeg_decompress_struct * cinfo, unsigned char * * scanlines, unsigned int max_lines)
FreeImage.dll!Load(FreeImageIO * io, void * handle, int page, int flags, void * data)
FreeImage.dll!FreeImage_LoadFromHandle(FREE_IMAGE_FORMAT fif, FreeImageIO * io, void * handle, int flags)
FreeImage.dll!FreeImage_Load(FREE_IMAGE_FORMAT fif, const char * filename, int flags)

Finally, it will cause the heap overflow if we make the "components" not in [1, 2, 3]. 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
(5cb0.349c): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=608e0000 edx=00000000 esi=77442044 edi=7744260c
eip=774e1b72 esp=0133f5c8 ebp=0133f5f4 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
(5cb0.349c): 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=08539007 ebx=00000009 ecx=00000880 edx=073590c8 esi=00000721 edi=00000000
eip=1009aec5 esp=0133f6cc ebp=00000000 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206
FreeImage!jinit_color_deconverter+0x935:
1009aec5 8808            mov     byte ptr [eax],cl          ds:002b:08539007=??
0:000> db eax-0x20
08538fe7  00 00 00 00 00 80 00 00-00 c0 c0 c0 c0 c0 80 c0  ................
08538ff7  c0 c0 c0 c0 d0 d0 d0 80-d0 ?? ?? ?? ?? ?? ?? ??  .........???????
08539007  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
08539017  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
08539027  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
08539037  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
08539047  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
08539057  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
0:000> kv
 # ChildEBP RetAddr      Args to Child              
00 0133f6d8 1009a383     00000808 073536cc 00000000 FreeImage!jinit_color_deconverter+0x935 (FPO: [Uses EBP] [5,0,1])
01 0133f704 100991d3     073536c0 07354fe0 07355008 FreeImage!jinit_upsampler+0x263 (FPO: [Uses EBP] [7,1,4])
02 0133f734 10085bfa     0133f830 0133fa18 0133f754 FreeImage!jinit_d_main_controller+0x1f3 (FPO: [Uses EBP] [4,0,4])
03 0133f74c 1002283f     00000000 0133fa18 00000001 FreeImage!jpeg_read_scanlines+0x8a (FPO: [3,0,1])
04 0133fa48 1000d9de     0133fa9c 07339fc8 ffffffff FreeImage!jpeg_freeimage_dst+0x1b1f (FPO: [5,183,3])
05 0133fa7c 1000da60     00000002 0133fa9c 07339fc8 FreeImage!FreeImage_LoadFromHandle+0x8e (FPO: [Uses EBP] [4,3,2])
06 0133faa8 00b0107b     00000002 07335fee 00000000 FreeImage!FreeImage_Load+0x50 (FPO: [3,4,2])

TestFile:

data:image/jpeg;base64,/9j/2wADAP/KACMICAgICAkBETLvESEDESH/QSF5IgAxETExFDExIjExETEx/9oACAF5AAAAAA==
2 Attachments

Discussion


Log in to post a comment.

MongoDB Logo MongoDB