Talos Vulnerability Report

TALOS-2018-0645

Simple DirectMedia Layer SDL2_Image do_layer_surface code execution vulnerability

October 31, 2018
CVE Number

CVE-2018-3977

Summary

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.

Tested Versions

Simple DirectMedia Layer SDL2_image 2.0.3

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-122: Heap-based Buffer Overflow

Details

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.

Crash Information

───[ 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)                             

Timeline

2018-09-11 - Initial Vendor Contact
2018-09-26 - Vendor Patched
2018-mm-dd - Public Disclosure

Credit

Discovered by Lilith Q(‘.’Q) of Cisco Talos