CVE-2018-3977
An exploitable code execution vulnerability exists in the XCF image rendering functionality of SDL2_image-2.0.3. A specially crafted XCF image can cause a heap overflow, resulting in code execution. An attacker can display a specially crafted image to trigger this vulnerability.
Simple DirectMedia Layer SDL2_image 2.0.3
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-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 video games, software and emulators. The last known count of software using LibSDL (from 2012) was at more than 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 will first create an SDL_Surface object based on the dimensions of the destination object:
chs = SDL_CreateRGBSurface(SDL_SWSURFACE, head->width, head->height, 32,
0x00FF0000,0x0000FF00,0x000000FF,0xFF000000);
The head->width
and head->height
fields are taken directly from the file, as are the first eight bytes, big endian, after the XCF signature.
67 69 6D 70 20 78 63 66 20 66 69 6C 65 00 00 00 00 01 00 00 02 00 | gimp xcf file…...
Based of these values, the size of the buffer that stores the resulting picture data after parsing SDL_Surface->pixels
is allocated to a size corresponding to the data of the final image. The resulting allocation is then calculated with the following code:
/* Get the pixels */
if (surface->w && surface->h) {
/* Assumptions checked in surface_size_assumptions assert above */
Sint64 size = ((Sint64)surface->h * surface->pitch); //[1]
if (size < 0 || size > SDL_MAX_SINT32) {
/* Overflow... */
SDL_FreeSurface(surface);
SDL_OutOfMemory();
return NULL;
}
surface->pixels = SDL_malloc((size_t)size);
Each XCF image consists of a set of layers that get displayed on top of each other. Each of these layers consists of an XCF_hierarchy, which defines a set of XCF_levels inside of the file, which each in turn define a set of tiles that actually define the raw pixel data that is written to the surface->pixels buffer from above.
For each tile, in each XCF_level, inside each layer (three nested loops), we end up writing to the destination buffer at a given offset corresponding to the location of the tile. To clarify:
level = read_xcf_level(src);
ty = tx = 0;
for (j = 0; level->tile_file_offsets[j]; j++) { //[1]
SDL_RWseek(src, level->tile_file_offsets[j], RW_SEEK_SET);
ox = tx + 64 > level->width ? level->width % 64 : 64;
oy = ty + 64 > level->height ? level->height % 64 : 64;
[…]
p8 = tile;
p16 = (Uint16 *) p8;
p = (Uint32 *) p8;
for (y = ty; y < ty + oy; y++) {
row = (Uint32 *) ((Uint8 *) surface->pixels + y * surface->pitch + tx * 4); //[2]
switch (hierarchy->bpp) {
case 4:
for (x = tx; x < tx + ox; x++)
*row++ = Swap32(*p++);
break;
case 3:
for (x = tx; x < tx + ox; x++) {
*row = 0xFF000000;
*row |= ((Uint32)*p8++ << 16);
*row |= ((Uint32)*p8++ << 8);
*row |= ((Uint32)*p8++ << 0);
row++;
}
[…]
}
free_xcf_tile(tile);
tx += 64; //[3]
if (tx >= level->width) {
tx = 0;
ty += 64;
}
if (ty >= level->height) {
break;
}
At [1], we see that we’re going to keep writing to the destination surface->pixels
as long as we have more level->tile_file_offsets, which is an array completely under an attacker’s control. When deciding the destination of the formatted image data, the pointer is calculated at [2], as an offset from the surface->pixel
buffer, which is affected by both y
and tx
. At [3], we can see that tx
will keep increasing as long as it is less than the level->width
and if there are more level->tile_file_offsets
, which are also under attacker control.
Thus, the image can describe an arbitrary amount of tiles within the XCF_layer, which, since there’s no checking or correspondence of the layer’s size to the destination surface’s size, allow an attacker to overwrite an arbitrary amount of data on the heap, potentially leading to code execution.
───[ registers ]────
$rax : 0x0000000000000003
$rbx : 0x00007ffe86c67bc0 -> 0xdfe2e6e5e9e7e5e5
$rcx : 0x00000c3a00002003 -> 0x0000000000000000
$rdx : 0x000061d0000102fa -> 0x0000000000000000
$rsp : 0x00007ffe86c67520 -> 0x00006060000103c7 -> 0x0000021800081000 -> 0x0000000000000000
$rbp : 0x00007ffe86c67b50 -> 0x00007ffe86c67fb0 -> 0x00007ffe86c68070 -> 0x00007ffe86c680b0 -> 0x00007ffe86c68140 ->
0x0000000000515e30 -> <__libc_csu_init+0> push r15
$rsi : 0x00000c3a0000204f -> 0x0000000000000000
$rdi : 0x000061d000010280 -> 0x0000000000000000
$rip : 0x00007f47a44f04b8 -> <do_layer_surface+4040> call 0x7f47a4470cd0 <__asan_report_store4@plt>
$r8 : 0x00007f47a5219a00 -> 0x00007f47a359e2b1 -> <__libc_start_main+241> mov edi, eax
$r9 : 0x0000000000000000
$r10 : 0x0000000000000000
$r11 : 0x000000000000000a
$r12 : 0x000000000041ce80 -> <_start+0> xor ebp, ebp
$r13 : 0x00007ffe86c68220 -> 0x0000000000000002
$r14 : 0x0000000000000000
$r15 : 0x0000000000000000
$eflags: [CARRY PARITY ADJUST zero sign trap INTERRUPT direction overflow resume virtualx86 identification]
-----─[ stack ]────
0x00007ffe86c67520│+0x00: 0x00006060000103c7 -> 0x0000021800081000 -> 0x0000000000000000 ← $rsp
0x00007ffe86c67528│+0x08: 0x00007f47a35f0a9f -> <__GI__IO_file_xsgetn+303> add QWORD PTR [rbx+0x8], r12
0x00007ffe86c67530│+0x10: 0x000060200006a374 -> 0x000022c0ff000200
0x00007ffe86c67538│+0x18: 0x0004000000000000
0x00007ffe86c67540│+0x20: 0x000060200006a370 -> 0xff000200ff000200
0x00007ffe86c67548│+0x28: 0x00000000000001c0
0x00007ffe86c67550│+0x30: 0x00006060000103cb -> 0x0000000000000218
0x00007ffe86c67558│+0x38: 0x00007f47a51f12b0 -> 0x000000af850fc085 -> 0x0000000000000000
────[ code:i386:x86-64 ]────
0x7f47a44f049f <do_layer_surface+4015> rol BYTE PTR [rbx], 0x88
0x7f47a44f04a2 <do_layer_surface+4018> ror DWORD PTR [rdx-0x3106b], 0xff
0x7f47a44f04a9 <do_layer_surface+4025> cmp cl, dl
0x7f47a44f04ab <do_layer_surface+4027> jl 0x7f47a44f04bd <do_layer_surface+4045>
0x7f47a44f04b1 <do_layer_surface+4033> mov rdi, QWORD PTR [rbp-0x310]
-> 0x7f47a44f04b8 <do_layer_surface+4040> call 0x7f47a4470cd0 <__asan_report_store4@plt>
↳ 0x7f47a4470cd0 <__asan_report_store4@plt+0> jmp QWORD PTR [rip+0x294472] # 0x7f47a4705148
0x7f47a4470cd6 <__asan_report_store4@plt+6> push 0x26
0x7f47a4470cdb <__asan_report_store4@plt+11> jmp 0x7f47a4470a60
0x7f47a4470ce0 <SDL_Log@plt+0> jmp QWORD PTR [rip+0x29446a] # 0x7f47a4705150
0x7f47a4470ce6 <SDL_Log@plt+6> push 0x27
0x7f47a4470ceb <SDL_Log@plt+11> jmp 0x7f47a4470a60
─────[ source:IMG_xcf.c+660 ]────
656 break;
658 case 3:
659 for (x = tx; x < tx + ox; x++) {
// row=0x00007ffe86c67ac8 -> [...] -> 0x0000000000000000
-> 660 *row = 0xFF000000;
661 *row |= ((Uint32)*p8++ << 16);
662 *row |= ((Uint32)*p8++ << 8);
663 *row |= ((Uint32)*p8++ << 0);
664 row++;
─[ trace ]────
[#0] 0x7f47a44f04b8 -> Name: do_layer_surface(surface=0x608000011ea0, src=0x60700001e630, head=0x60700001e6a0, lay...
[#1] 0x7f47a44ecc3c -> Name: IMG_LoadXCF_RW(src=0x60700001e630)...
[#2] 0x7f47a4471752 -> Name: IMG_LoadTyped_RW(src=0x60700001e630, freesrc=0x1, type=0x7ffe86c6a242 "xcf")...
[#3] 0x7f47a44714ca -> Name: IMG_Load(file=0x7ffe86c6a220 "boop.xcf")...
[#4] 0x515d18 -> Name: main(argc=0x2, argv=0x7ffe86c68228)...
──────────────
==31600==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61d000010280 at pc 0x7f47a44f04bd bp 0x7ffe86c67510 sp
0x7ffe86c67508
WRITE of size 4 at 0x61d000010280 thread T0
#0 0x7f47a44f04bc in do_layer_surface /root/boop/work_work/triages/libsdl/src/SDL2_image-2.0.3/IMG_xcf.c:660:30
#1 0x7f47a44ecc3b in IMG_LoadXCF_RW /root/boop/work_work/triages/libsdl/src/SDL2_image-2.0.3/IMG_xcf.c:823:5
#2 0x7f47a4471751 in IMG_LoadTyped_RW /root/boop/work_work/triages/libsdl/src/SDL2_image-2.0.3/IMG.c:195:17
#3 0x7f47a44714c9 in IMG_Load /root/boop/work_work/triages/libsdl/src/SDL2_image-2.0.3/IMG.c:136:12
#4 0x515d17 in main /root/boop/work_work/triages/libsdl/./fuzzy_image.c:22:15
#5 0x7f47a359e2b0 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x202b0)
#6 0x41cea9 in _start (/root/boop/work_work/triages/libsdl/fuzzy_image+0x41cea9)
0x61d000010280 is located 0 bytes to the right of 2048-byte region [0x61d00000fa80,0x61d000010280)
allocated by thread T0 here:
#0 0x4dec18 in malloc (/root/boop/work_work/triages/libsdl/fuzzy_image+0x4dec18)
#1 0x7f47a4926a6b in SDL_malloc_REAL (/usr/local/lib/libSDL2-2.0.so.0+0x204a6b)
#2 0x7f47a4b14e21 in SDL_CreateRGBSurfaceWithFormat_REAL (/usr/local/lib/libSDL2-2.0.so.0+0x3f2e21)
#3 0x7f47a4b15f85 in SDL_CreateRGBSurface_REAL (/usr/local/lib/libSDL2-2.0.so.0+0x3f3f85)
#4 0x7f47a47e7b89 in SDL_CreateRGBSurface (/usr/local/lib/libSDL2-2.0.so.0+0xc5b89)
#5 0x7f47a44eca92 in IMG_LoadXCF_RW /root/boop/work_work/triages/libsdl/src/SDL2_image-2.0.3/IMG_xcf.c:809:10
#6 0x7f47a4471751 in IMG_LoadTyped_RW /root/boop/work_work/triages/libsdl/src/SDL2_image-2.0.3/IMG.c:195:17
#7 0x7f47a44714c9 in IMG_Load /root/boop/work_work/triages/libsdl/src/SDL2_image-2.0.3/IMG.c:136:12
#8 0x515d17 in main /root/boop/work_work/triages/libsdl/./fuzzy_image.c:22:15
#9 0x7f47a359e2b0 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x202b0)
2018-09-11 - Initial Vendor Contact
2018-09-26 - Vendor Patched
2018-mm-dd - Public Disclosure
Discovered by Lilith Q(‘.’Q) of Cisco Talos