Talos Vulnerability Report

TALOS-2017-0498

Simple DirectMedia Layer SDL2_image do_layer_surface Double-Free Vulnerability

March 1, 2018
CVE Number

CVE-2017-14449

Summary

A double-Free vulnerability exists in the XCF image rendering functionality of SDL2_image-2.0.2. A specially crafted XCF image can cause a Double-Free situation to occur. An attacker can display a specially crafted image to trigger this vulnerability.

Tested Versions

Simple DirectMedia Layer SDL2_image 2.0.2

Product URLs

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

CVSSv3 Score

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

CWE

CWE-415: Double Free

Details

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 reading in an XCF file, the file type used natively by GIMP, LibSDL2 must read in all the layers of the image before rendering them on top of each other, and then blitting them directly to the SDL surface. A portion of the code is given below:

// IMG_xcf.c
 // Blit layers backwards, because Gimp saves them highest first
for (i = offsets; i > 0; i--) {
        SDL_Rect rs, rd;
        SDL_RWseek (src, head->layer_file_offsets [i-1], RW_SEEK_SET); // [1]
        layer = read_xcf_layer (src);
        do_layer_surface (lays, src, head, layer, load_tile); //[2]

Given offsets from the file, the program seeks to that spot and reads an XCF Layer, which is then parsed, resulting in the memory therefore being allocated and written, inside of [2]. Further more, a hierarchy object is created from the 'hierarchyfileoffset' inside of the layer object. The definitions for both of these structures are given below:

<(^_^)> print *layer
$3 = {
  width = 0x200, 
  height = 0x200, 
  layer_type = 0x0, 
  name = 0xbccc60 "Background", 
  properties = 0x20, 
  hierarchy_file_offset = 0x10e, 
  layer_mask_offset = 0x0, 
  offset_x = 0x0, 
  offset_y = 0x0, 
  visible = 0x1
}
<(^_^)> print *hierarchy 
$4 = {
  width = 0x200, 
  height = 0x200, 
  bpp = 0x2,   //[1]
  level_file_offsets = 0xc6ca60
}

Of most interest is the 'bpp' field inside of the hierarchy struct at [1]. When this is set to 0x2, assuming that the rest of the layer parses correctly, the code ends up at a peculiar switch statement.

switch (hierarchy->bpp) {
[...]
            case 2: 
                /* Indexed / Greyscale + Alpha */
                switch (head->image_type) { //[1]
    [...]

                    default:
                        fprintf(stderr, "Unknown Gimp image type (%d)\n", head->image_type);
                        if (hierarchy) {
                                if (hierarchy->level_file_offsets)
                                        SDL_free(hierarchy->level_file_offsets); //[2]
                            free_xcf_hierarchy(hierarchy); //[3]
                        }
[...]

The image controls the head->imagetype field at [1], so the path taken is up to the image. If the head->imagetype is not IMAGEINDEXED or IMAGEGREEYSCALE, then the code frees hierarchy->levelfileoffsets at [2], and then calls freexcfhierarchy at [3]. The source for freexcfhierarchy is given below:

static void free_xcf_hierarchy (xcf_hierarchy * h) {
    SDL_free (h->level_file_offsets); //[1]
    SDL_free (h);
    }

And as given at [1], the code ends up freeing the same levelfileoffsets hierarchy as before, resulting in a double free vulnerability. It is questionable whether or not this code path has ever been hit or reviewed before, given the relative lack of distance between the two frees.

It should be noted that most modern implementations of malloc in system libraries will catch a double free error and throw an assertion error immediately, however, this is dependent on the memory allocator that developers may use when linking with this library.

Crash Information

Unknown Gimp image type (0)
*** Error in `/root/work_work/triages/libsdl/crashes/img_read_plain': double free or corruption (!prev): 0x0000000000c6ca60 ***

Program received signal SIGABRT, Aborted.
0x00007ffff74af067 in __GI_raise (sig=sig@entry=0x6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
56      ../nptl/sysdeps/unix/sysv/linux/raise.c: No such file or directory.
---------------[ registers ]----
$rax   : 0x0000000000000000
$rbx   : 0x0000000000000080
$rcx   : 0xffffffffffffffff
$rdx   : 0x0000000000000006
$rsp   : 0x00007fffffffda38 -> 0x00007ffff74b0448 -> <abort+328> mov rdx, QWORD PTR fs:0x10
$rbp   : 0x00007fffffffddd0 -> 0x00007fffffffdde0 -> "0000000000c6ca60"
$rsi   : 0x000000000000294c
$rdi   : 0x000000000000294c
$rip   : 0x00007ffff74af067 -> <raise+55> cmp rax, 0xfffffffffffff000
$r8    : 0x3036616336633030 ("00c6ca60"?)
$r9    : 0x6f6974707572726f ("orruptio"?)
$r10   : 0x0000000000000008
$r11   : 0x0000000000000202
$r12   : 0x00007fffffffdbe0 -> 0x0000000000000001
$r13   : 0x0000000000000007
$r14   : 0x0000000000000080
$r15   : 0x0000000000000007
$eflags: [carry parity adjust zero sign trap INTERRUPT direction overflow resume virtualx86 identification]
-------------------[ stack ]----
0x00007fffffffda38|+0x00: 0x00007ffff74b0448 -> <abort+328> mov rdx, QWORD PTR fs:0x10  <-$rsp
0x00007fffffffda40|+0x08: 0x0000000000000020
0x00007fffffffda48|+0x10: 0x0000000000000000
0x00007fffffffda50|+0x18: 0x0000000000000000
0x00007fffffffda58|+0x20: 0x0000000000000000
0x00007fffffffda60|+0x28: 0x0000000000000000
0x00007fffffffda68|+0x30: 0x0000000000000000
0x00007fffffffda70|+0x38: 0x0000000000000000
--------[ code:i386:x86-64 ]----
0x7ffff74af054 <raise+36>       add    BYTE PTR [rcx-0x289cb73a], cl
0x7ffff74af05a <raise+42>       movsxd rsi, esi
0x7ffff74af05d <raise+45>       movsxd rdi, ecx
0x7ffff74af060 <raise+48>       mov    eax, 0xea
0x7ffff74af065 <raise+53>       syscall 
->0x7ffff74af067 <raise+55>       cmp    rax, 0xfffffffffffff000
0x7ffff74af06d <raise+61>       ja     0x7ffff74af08d <__GI_raise+93>
0x7ffff74af06f <raise+63>       repz   ret
0x7ffff74af071 <raise+65>       nop    DWORD PTR [rax+0x0]
0x7ffff74af078 <raise+72>       test   ecx, ecx
0x7ffff74af07a <raise+74>       jg     0x7ffff74af057 <__GI_raise+39>
-----------------[ threads ]----
[#0] Id 5, Name: "img_read_plain", stopped, reason: SIGABRT
[#1] Id 4, Name: "img_read_plain", stopped, reason: SIGABRT
[#2] Id 3, Name: "img_read_plain", stopped, reason: SIGABRT
[#3] Id 2, Name: "img_read_plain", stopped, reason: SIGABRT
[#4] Id 1, Name: "img_read_plain", stopped, reason: SIGABRT
-------------------[ trace ]----
[#0] 0x7ffff74af067->Name: __GI_raise(sig=0x6)
[#1] 0x7ffff74b0448->Name: __GI_abort()
[#2] 0x7ffff74ed1b4->Name: __libc_message(do_abort=0x1, fmt=0x7ffff75e2210 "*** Error in `%s': %s: 0x%s ***\n")
[#3] 0x7ffff74f298e->Name: malloc_printerr(action=0x1, str=0x7ffff75e2318 "double free or corruption (!prev)", ptr=<optimized out>)
[#4] 0x7ffff74f3696->Name: _int_free(av=<optimized out>, p=<optimized out>, have_lock=0x0)  
[#5] 0x7ffff7afcd0e->Name: SDL_free_REAL(ptr=0xc6ca60)
[#6] 0x7ffff7aa0aec->Name: SDL_free(a=0xc6ca60)
[#7] 0x7ffff7849e28->Name: free_xcf_hierarchy(h=0x887ae0)
[#8] 0x7ffff784a902->Name: do_layer_surface(surface=0xc503b0, src=0xc6b6c0, head=0xc6aa00, layer=0xc39b70, load_tile=0x7ffff784a07f      
<load_xcf_tile_rle>)
[#9] 0x7ffff784ae24->Name: IMG_LoadXCF_RW(src=0xc6b6c0)

Timeline

2017-11-28 - Vendor Disclosure
2018-03-01 - Public Release

Credit

Discovered by Lilith (<_<) of Cisco Talos.