CVE-2018-3839
An exploitable code execution vulnerability exists in the XCF image rendering functionality of SDL2_image-2.0.2. A specially crafted XCF image can cause an out-of-bounds write on the heap, resulting in code execution. An attacker can display a specially crafted image to trigger this vulnerability.
Simple DirectMedia Layer SDL2_image 2.0.2
https://www.libsdl.org/projects/SDL_image/
6.5 - CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H CVSSv3 Calculator: https://www.first.org/cvss/calculator/3.0
CWE-122: Heap-based Buffer Overflow
LibSDL is a multi-platform library for easy access to low level hardware and graphics, providing support for a large amount of games, software, and emulators. The last known count of software using LibSDL (from 2012) listed the number at upwards of 120. The LibSDL2_Image library is an optional component that deals specifically with parsing and displaying a variety of image file formats, creating a single and uniform API for image processing, regardless of the type.
When parsing and storing an XCF file (the native file type for Gimp), the LibSDL2 library implements a custom RLE inflater for the compressed XCF data. The code that handles this is listed below:
//IMG_xcf.c
static unsigned char * load_xcf_tile_rle (SDL_RWops * src, Uint32 len, int bpp, int x, int y)
unsigned char * load, * t, * data, * d;
Uint32 reallen;
int i, size, count, j, length;
unsigned char val;
t = load = (unsigned char *) SDL_malloc (len);
reallen = SDL_RWread (src, t, 1, len);
data = (unsigned char *) SDL_malloc (x*y*bpp);
[...]
From the above, it should be noted that the bpp
,x
, and y
parameters are all under the file’s control, along with the len
field. The bpp
parameter in particular is taken from the xcf_hierarchy
struct.
typedef struct {
Uint32 cwidth;
Uint32 height;
Uint32 bpp;
Uint32 * level_file_offsets;
} xcf_hierarchy;
This structure has it’s fields populated directly from the XCF image file being rendered as such:
static xcf_hierarchy * read_xcf_hierarchy (SDL_RWops * src) { xcf_hierarchy * h; int i; h = (xcf_hierarchy *) SDL_malloc (sizeof (xcf_hierarchy)); h->width = SDL_ReadBE32 (src); h->height = SDL_ReadBE32 (src); h->bpp = SDL_ReadBE32 (src);Thus, we know that the bpp
variable can be anything from 0x0 to 0xffffffff, so going back to the load_xcf_tile_rle function, an issue quickly arises:
static unsigned char * load_xcf_tile_rle (SDL_RWops * src, Uint32 len, int bpp, int x, int y)
unsigned char * load, * t, * data, * d;
Uint32 reallen;
int i, size, count, j, length;
unsigned char val;
t = load = (unsigned char *) SDL_malloc (len);
reallen = SDL_RWread (src, t, 1, len);
data = (unsigned char *) SDL_malloc (x*y*bpp); //[1]
for (i = 0; i < bpp; i++) { //[2]
[...]
Since x, y, and bpp are all of integers, when all multiplied at [1], the resulting value passed into SDL_malloc is also of type integer. As noted before, since bpp can be 0x0-0xffffffff, this results in an integer overflow for a high enough value of bpp
, causing the value passed into SDL_malloc to be less than bpp itself. This causes the subsequent usages of bpp
to be outside the bounds of the allocated buffer and possible memory corruption. Due to line [2], bpp does have some bounds, such that (bpp < 0) => causes the corruption loop to skip. The tricky part with this bug is how much one could actually get away with corrupting as the writes in memory occur with the following code:
*d = *t++; //[1]
d += bpp; //[2]
}
So while it is technically a controlled write, since we control the value of t
at [1], remember that bpp must be rather large, and also cannot be negative. Thus, when the destination pointer d
is incremented by bpp at [2], the odds of hitting unmapped memory increase exponentially for each loop iteration. On a 32-bit machine, the probability of successful exploitation with this bug would increase tremendously though as the pointer d
would wrap around a lot faster, leaving less chance for segfaulting due to unmapped memory access.
(Note: Compiled with ASAN)
Program received signal SIGSEGV, Segmentation fault.
0x00007fe914baf213 in load_xcf_tile_rle (src=0x60700000dfb0, len=0x309b, bpp=0x40000003, x=0x40, y=0x40) at IMG_xcf.c:527
warning: Source file is more recent than executable.
527 val = *t++; //pull next byte for value to write.
-----------------------------------------------------[ registers ]----
$rax : 0x0000627040015103 -> 0x0000000000000000 -> 0x0000000000000000
$rbx : 0x00007ffcb1150ba0 -> 0x00007ffcb1152462 -> 0x643530342f464358 -> 0x643530342f464358 ("XCF/405d"?)
$rdx : 0x00000000000000e2 -> 0x00000000000000e2
$rsp : 0x00007ffcb1150840 -> 0x00007ffcb11508b0 -> 0x00007ffcb1150960 -> 0x00007ffcb1150a20 -> 0x00007ffcb1150a60 ->
0x00007ffcb1150a90 -> 0x0000000000000002 -> 0x0000000000000002
$rbp : 0x00007ffcb11508b0 -> 0x00007ffcb1150960 -> 0x00007ffcb1150a20 -> 0x00007ffcb1150a60 -> 0x00007ffcb1150a90 ->
0x0000000000000002 -> 0x0000000000000002
$rsi : 0x0000000000000013 -> 0x0000000000000013
$rdi : 0x00007fe915139540 -> 0x0000000000000014 -> 0x0000000000000014
$rip : 0x00007fe914baf213 -> 0x489848a0458b1088 -> 0x489848a0458b1088
$r8 : 0x0000000000000008 -> 0x0000000000000008
$r9 : 0x0000000000000000 -> 0x0000000000000000
$r10 : 0x0000000000000000 -> 0x0000000000000000
$r11 : 0x0000000000000008 -> 0x0000000000000008
$r12 : 0x00000000004bd3e4 -> <_start+0> xor ebp, ebp
$r13 : 0x00007ffcb1150b90 -> 0x0000000000000002 -> 0x0000000000000002
$r14 : 0xfffffffffffffff8 -> 0xfffffffffffffff8
$r15 : 0x0000000000000000 -> 0x0000000000000000
$eflags: [CARRY PARITY ADJUST zero SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification]
---------------------------------------------------------[ stack ]----
0x00007ffcb1150840|+0x00: 0x00007ffcb11508b0 -> 0x00007ffcb1150960 -> 0x00007ffcb1150a20 -> 0x00007ffcb1150a60 ->
0x00007ffcb1150a90 -> 0x0000000000000002 -> 0x0000000000000002 <-
$rsp
0x00007ffcb1150848|+0x08: 0x0000004000000040 -> 0x0000000000000000 -> 0x0000000000000000
0x00007ffcb1150850|+0x10: 0x0000309b40000003 -> 0x0000309b40000003
0x00007ffcb1150858|+0x18: 0x000060700000dfb0 -> 0x00007fe914e11e48 -> 0x20ec8348e5894855 -> 0x20ec8348e5894855
0x00007ffcb1150860|+0x20: 0x0000000000000000 -> 0x0000000000000000
0x00007ffcb1150868|+0x28: 0xe2007fe913d1197d
0x00007ffcb1150870|+0x30: 0x0000627000015100 -> 0xbebebebebebebee2
0x00007ffcb1150878|+0x38: 0x0000309b14e11f03 -> 0x0000309b14e11f03
----------------------------------------------[ code:i386:x86-64 ]----
0x7fe914baf1ff <load_xcf_tile_rle+384> mov BYTE PTR [rbp-0x41], al
0x7fe914baf202 <load_xcf_tile_rle+387> mov DWORD PTR [rbp-0x20], 0x0
0x7fe914baf209 <load_xcf_tile_rle+394> jmp 0x7fe914baf222 <load_xcf_tile_rle+419>
0x7fe914baf20b <load_xcf_tile_rle+396> mov rax, QWORD PTR [rbp-0x10]
0x7fe914baf20f <load_xcf_tile_rle+400> movzx edx, BYTE PTR [rbp-0x41]
->0x7fe914baf213 <load_xcf_tile_rle+404> mov BYTE PTR [rax], dl
0x7fe914baf215 <load_xcf_tile_rle+406> mov eax, DWORD PTR [rbp-0x60]
0x7fe914baf218 <load_xcf_tile_rle+409> cdqe
0x7fe914baf21a <load_xcf_tile_rle+411> add QWORD PTR [rbp-0x10], rax
0x7fe914baf21e <load_xcf_tile_rle+415> add DWORD PTR [rbp-0x20], 0x1
0x7fe914baf222 <load_xcf_tile_rle+419> mov eax, DWORD PTR [rbp-0x20]
------------------------------------------[ source:IMG_xcf.c+527 ]----
523
524 count += length; //count var unused, lol
525 size -= length;
526
// t=0x00007ffcb11508a8 -> [...] -> 0xe3e3e4f6e201df01, val=0xe2L
-> 527 val = *t++; //pull next byte for value to write.
528
529 for (j = 0; j < length; j++) { //Write pixel-consecutive bytes.
530 *d = val; // oob write here... 405d
531 d += bpp;
-------------------------------------------------------[ threads ]----
[#0] Id 1, Name: "", stopped, reason: SIGSEGV
---------------------------------------------------------[ trace ]----
[#0] 0x7fe914baf213->Name: load_xcf_tile_rle(src=0x60700000dfb0, len=0x309b, bpp=0x40000003, x=0x40, y=0x40)
[#1] 0x7fe914baf55d->Name: do_layer_surface(surface=0x60800000be20, src=0x60700000dfb0, head=0x60700000df40, layer=0x60600000eea0,
load_tile=0x7fe914baf07f <load_xcf_tile_rle>)
[#2] 0x7fe914bafe24->Name: IMG_LoadXCF_RW(src=0x60700000dfb0)
[#3] 0x7fe914b8d7ef->Name: IMG_LoadTyped_RW(src=0x60700000dfb0, freesrc=0x1, type=0x0)
[#4] 0x7fe914b8d5e0->Name: IMG_Load(file=0x7ffcb1152462 "boop")
[#5] 0x4bd553->Name: main(argc=<optimized out>, argv=<optimized out>)
----------------------------------------------------------------------
2018-02-06 - Vendor Disclosure
2018-02-07 - Vendor Patched
2018-04-10 - Public Release
Discovered by Lilith of Cisco Talos.