Talos Vulnerability Report

TALOS-2019-0820

Simple DirectMedia Layer SDL2_image IMG_LoadPCX_RW code execution vulnerability

July 2, 2019
CVE Number

CVE-2019-5051

Summary

An exploitable heap-based buffer overflow vulnerability exists when loading a PCX file in SDL2_image, version 2.0.4. A missing error handler can lead to a buffer overflow and potential code execution. An attacker can provide a specially crafted image file to trigger this vulnerability.

Tested Versions

Simple DirectMedia Layer SDL2_image 2.0.4

Product URLs

https://www.libsdl.org/projects/SDL_image/

CVSSv3 Score

8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-390: Detection of Error Condition Without Action

Details

This vulnerability is present in the SDL2_image library, which is used for loading images in different formats.

There is a vulnerability in the function responsible for loading PCX files. A specially crafted PCX file can lead to a heap buffer overflow and remote code execution.

Let's investigate this vulnerability. After we attempt to load a malformed PCX file, the following state appears:

./showimage PoC.pcx
=================================================================
==25249==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xb2c32848 at pc 0x08057d04 bp 0xbf987968 sp 0xbf987958
WRITE of size 1 at 0xb2c32848 thread T0
    #0 0x8057d03 in IMG_LoadPCX_RW ../IMG_pcx.c:178
    #1 0x804c301 in IMG_LoadTyped_RW ../IMG.c:195
    #2 0x804beff in IMG_Load ../IMG.c:136
    #3 0x804c3e7 in IMG_LoadTexture ../IMG.c:212
    #4 0x804b65c in main ../showimage.c:101
    #5 0xb7014636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636)
    #6 0x804ad50  (/home/icewall/bugs/SDL_src/SDL2_image-2.0.4/build/showimage+0x804ad50)

0xb2c32848 is located 0 bytes to the right of 264-byte region [0xb2c32740,0xb2c32848)
allocated by thread T0 here:
    #0 0xb72c0dee in malloc (/usr/lib/i386-linux-gnu/libasan.so.2+0x96dee)
    #1 0x81258d2 in SDL_malloc_REAL /home/icewall/bugs/SDL_src/SDL2-2.0.9/src/stdlib/SDL_malloc.c:5328
    #2 0x813cbca in SDL_CreateRGBSurfaceWithFormat_REAL /home/icewall/bugs/SDL_src/SDL2-2.0.9/src/video/SDL_surface.c:122
    #3 0x813ce89 in SDL_CreateRGBSurface_REAL /home/icewall/bugs/SDL_src/SDL2-2.0.9/src/video/SDL_surface.c:166
    #4 0x80abf8f in SDL_CreateRGBSurface /home/icewall/bugs/SDL_src/SDL2-2.0.9/src/dynapi/SDL_dynapi_procs.h:473
    #5 0x805797c in IMG_LoadPCX_RW ../IMG_pcx.c:141
    #6 0x804c301 in IMG_LoadTyped_RW ../IMG.c:195
    #7 0x804beff in IMG_Load ../IMG.c:136
    #8 0x804c3e7 in IMG_LoadTexture ../IMG.c:212
    #9 0x804b65c in main ../showimage.c:101
    #10 0xb7014636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636)

SUMMARY: AddressSanitizer: heap-buffer-overflow ../IMG_pcx.c:178 IMG_LoadPCX_RW
Shadow bytes around the buggy address:
  0x365864b0: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
  0x365864c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x365864d0: 00 00 00 00 00 00 00 00 01 fa fa fa fa fa fa fa
  0x365864e0: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
  0x365864f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x36586500: 00 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa fa
  0x36586510: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x36586520: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x36586530: fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa fa
  0x36586540: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x36586550: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
==25249==ABORTING

A heap-based buffer overflow appeared inside the IMG_LoadPCX_RW function. Let's take a closer look at the code where the vulnerability takes place:

IMG_pcx.c
146    bpl = pcxh.NPlanes * pcxh.BytesPerLine;
147    if (bpl > surface->pitch) {
148      error = "bytes per line is too large (corrupt?)";
149    }    
150    buf = (Uint8 *)SDL_calloc(SDL_max(bpl, surface->pitch), 1);
151    row = (Uint8 *)surface->pixels;  
152    for ( y=0; y<surface->h; ++y ) {
153        /* decode a scan line to a temporary buffer first */
154        int i, count = 0;
155        Uint8 ch;
156        Uint8 *dst = (src_bits == 8) ? row : buf;
157        if ( pcxh.Encoding == 0 ) {
158            if(!SDL_RWread(src, dst, bpl, 1)) {
159                error = "file truncated";
160                goto done;
161            }
162        } else {
163            for(i = 0; i < bpl; i++) {
164                if(!count) {
165                    if(!SDL_RWread(src, &ch, 1, 1)) {
166                        error = "file truncated";
167                        goto done;
168                    }
169                    if( (ch & 0xc0) == 0xc0) {
170                        count = ch & 0x3f;
171                        if(!SDL_RWread(src, &ch, 1, 1)) {
172                            error = "file truncated";
173                            goto done;
174                        }
175                    } else
176                        count = 1;
177                }
178                dst[i] = ch;
179                count--;
180            }
181        }

        (...)

214        row += surface->pitch;
215    }

The overflow appears at line 178. It happens because, as you can see at line 147, the calculated bpl value is checked against the surface->pitch field. If bpl is bigger, the error variable is set at line 148, but no action is taken (like e.g in lines 159-160 where the goto done instruction is executed).
That lack of action leads to a heap-based buffer overflow in a scenario where src_bits is equal to 8, causing the dst pointer to have the value of row, where row == surface->pixels.

The overflow will take place during the first iteration, when bpl > sizeof(surface->pixels), or later because in line 214 the row pointer is moved by the value of surface->pitch.

A dump of malformed PCX file :

struct PCX file         0h  80h Fg: Bg: 
ubyte Manufacturer      Ah  0h  1h  Fg: Bg: 
enum PCX_TYPE version   v3up (5h)   1h  1h  Fg: Bg: 
ubyte encoding          1h  2h  1h  Fg: Bg: 
ubyte BitsPerPixel      8h  3h  1h  Fg: Bg: 
ushort Xmin             FF7Fh   4h  2h  Fg: Bg: 
ushort Ymin             FFFFh   6h  2h  Fg: Bg: 
ushort Xmax             0h  8h  2h  Fg: Bg: 
ushort Ymax             0h  Ah  2h  Fg: Bg: 
ushort HDpi             100h    Ch  2h  Fg: Bg: 
ushort VDpi             101h    Eh  2h  Fg: Bg: 
ubyte Colormap[48]      10h 30h Fg: Bg: 
ubyte Reserved          1h  40h 1h  Fg: Bg: 
ubyte NPlanes           1h  41h 1h  Fg: Bg: 
ushort BytesPerLine     1000h   42h 2h  Fg: Bg: 
ushort PaletteInfo      1001h   44h 2h  Fg: Bg: 
ushort HscreenSize      0h  46h 2h  Fg: Bg: 
ushort VscreenSize      100h    48h 2h  Fg: Bg: 
ubyte Filler[54]        4Ah 36h Fg: Bg: 

An attacker fulling controlling the size of overwrite and its content can turn this heap-based buffer overflow in a remote code execution.

Crash Information

./showimage PoC.pcx
=================================================================
==25249==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xb2c32848 at pc 0x08057d04 bp 0xbf987968 sp 0xbf987958
WRITE of size 1 at 0xb2c32848 thread T0
    #0 0x8057d03 in IMG_LoadPCX_RW ../IMG_pcx.c:178
    #1 0x804c301 in IMG_LoadTyped_RW ../IMG.c:195
    #2 0x804beff in IMG_Load ../IMG.c:136
    #3 0x804c3e7 in IMG_LoadTexture ../IMG.c:212
    #4 0x804b65c in main ../showimage.c:101
    #5 0xb7014636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636)
    #6 0x804ad50  (/home/icewall/bugs/SDL_src/SDL2_image-2.0.4/build/showimage+0x804ad50)

0xb2c32848 is located 0 bytes to the right of 264-byte region [0xb2c32740,0xb2c32848)
allocated by thread T0 here:
    #0 0xb72c0dee in malloc (/usr/lib/i386-linux-gnu/libasan.so.2+0x96dee)
    #1 0x81258d2 in SDL_malloc_REAL /home/icewall/bugs/SDL_src/SDL2-2.0.9/src/stdlib/SDL_malloc.c:5328
    #2 0x813cbca in SDL_CreateRGBSurfaceWithFormat_REAL /home/icewall/bugs/SDL_src/SDL2-2.0.9/src/video/SDL_surface.c:122
    #3 0x813ce89 in SDL_CreateRGBSurface_REAL /home/icewall/bugs/SDL_src/SDL2-2.0.9/src/video/SDL_surface.c:166
    #4 0x80abf8f in SDL_CreateRGBSurface /home/icewall/bugs/SDL_src/SDL2-2.0.9/src/dynapi/SDL_dynapi_procs.h:473
    #5 0x805797c in IMG_LoadPCX_RW ../IMG_pcx.c:141
    #6 0x804c301 in IMG_LoadTyped_RW ../IMG.c:195
    #7 0x804beff in IMG_Load ../IMG.c:136
    #8 0x804c3e7 in IMG_LoadTexture ../IMG.c:212
    #9 0x804b65c in main ../showimage.c:101
    #10 0xb7014636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636)

SUMMARY: AddressSanitizer: heap-buffer-overflow ../IMG_pcx.c:178 IMG_LoadPCX_RW
Shadow bytes around the buggy address:
  0x365864b0: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
  0x365864c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x365864d0: 00 00 00 00 00 00 00 00 01 fa fa fa fa fa fa fa
  0x365864e0: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
  0x365864f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x36586500: 00 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa fa
  0x36586510: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x36586520: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x36586530: fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa fa
  0x36586540: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x36586550: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
==25249==ABORTING

Timeline

2019-05-08 - Vendor Disclosure
2019-07-02 - Vendor Patched; Public Release

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.