Talos Vulnerability Report

TALOS-2017-0488

Simple DirectMedia Layer SDL2_Image IMG_LoadLBM_RW Code Execution Vulnerability

March 1, 2018
CVE Number

CVE-2017-12122

Summary

An exploitable code execution vulnerability exists in the ILBM image rendering functionality of SDL2_image-2.0.2. A specially crafted ILBM 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.2

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 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 LibSDL2Image reads in an image, if the image contains the “FORM” string at offset 0x4, and the “PBM” or “ILBM” strings at offset 0x8, then the file is considered a ILBM (Interleaved Bitmap) file, and parsed accordingly. LIBSDL2Image first looks for the Bitmap Headers of the file, and then reads the values into a BMHD structure. This looks like such:

00000000 46 4F 52 4D 00 02 6E 98 49 4C 42 4D 42 4D 48 44 00 00 00 14 FORM..n.ILBMBMHD....
00000014 02 00 02 00 00 00 00 00 15 00 01 00 00 00 0A 0A 02 00 02 00 

The above 0x28 bytes of a ILBM file will result in a bmhd structure (size 0x14) as such: type = struct {

Uint16 w;
Uint16 h;
Sint16 x;
Sint16 y;
Uint8 planes;
Uint8 mask;
Uint8 tcomp;
Uint8 pad1;
Uint16 tcolor;
Uint8 xAspect;
Uint8 yAspect;
Sint16 Lpage;
Sint16 Hpage;


<(^_^)> print bmhd
$1 = {
    Uint16 w = 0x200, 
    Uint16 h = 0x200, 
    Sint16 x = 0x0, 
    Sint16 y = 0x0, 
    Uint8 planes = 0x15, 
    Uint8 mask = 0x0, 
    Uint8 tcomp = 0x1, 
    Uint8 pad1 = 0x0, 
    Uint16 tcolor = 0x0, 
    Uint8 xAspect = 0xa, 
    Uint8 yAspect = 0xa, 
    Sint16 Lpage = 0x200, 
    Sint16 Hpage = 0x200
}

With the input essentially being a direct read from the file into the structure. After this read has been performed, LibSDL2Image will create an RGBSurface to store the data. This RGBSurface is the universal object for storing image data from any given image format. It is during the IMGLoad_RW functions that the data is converted correctly from the given to the RGBSurface type, which is given as follows:

type = struct SDL_Surface {
Uint32 flags;
SDL_PixelFormat *format;
int w;
int h;
int pitch;
void *pixels;
void *userdata;
int locked;
void *lock_data;
SDL_Rect clip_rect;
struct SDL_BlitMap *map;
int refcount;
} *

Which is created by the following line of code, converting from bmhd to SDL_Surface:

if ( ( Image = SDL_CreateRGBSurface( SDL_SWSURFACE, width, bmhd.h, (bmhd.planes==24 || flagHAM==1)?24:8, 0, 0, 0, 0 ) ) == NULL )

During runtime of the crash, this ends up looking like:

SDL_CreateRGBSurface_REAL(flags=0x0, width=0x200, height=0x200, depth=0x8, Rmask=0x0, Gmask=0x0, Bmask=0x0, Amask=0x0)

The SDLPixelformat member of the SDLSurface is what contains the actual RGB data, and this is stored inside of the SDLPalette *palette member of the SDLSurface, which is defined as such:

type = struct SDL_Palette {
    int ncolors;
    SDL_Color *colors;
    Uint32 version;
    int refcount;
} *

The SDL_Color *color array contains the raw bit data, and is allocated by the following line:

palette->colors =
    (SDL_Color *) SDL_malloc(ncolors * sizeof(*palette->colors));

The size of a given SDLColor struct is 0x4, and ncolors is passed as a parameter to the SDLAllocPalette function, and is given by (1<<0x8 == 0x100):

//SDL_AllocPalette(int ncolors) 
SDL_AllocPalette((1 << surface->format->BitsPerPixel));

The BitsPerPixel field is taken from the depth parameter passed into SDLCreateRGBSurface, so the resulting SDLmalloc is (0x4 * (1<<0x8)), or 0x400.

Interestingly, when the data is actually taken from the file and thrown into the heap data of size 0x400, the following loop occurs:

for ( i=nbcolors; i < (Uint32)nbrcolorsfinal; i++ ){
    Image->format->palette->colors[i].r = Image->format->palette->colors[i%nbcolors].r;
    Image->format->palette->colors[i].g = Image->format->palette->colors[i%nbcolors].g;
Image->format->palette->colors[i].b = Image->format->palette->colors[i%nbcolors].b;
}

The nbrcolorsfinal variable is populated with the following code:

int nbrcolorsfinal = 1 << (nbplanes + stencil);
[...]
if ( nbrcolorsfinal > (1<<bmhd.planes) ) {
    nbrcolorsfinal = (1<<bmhd.planes);
}
[...]

The assumption is that the bmhd.planes (which is 0x15 in our structure) corresponds exactly with the depth parameter passed to SDLCreateRGBSurfaceREAL, such that the structure has enough space allocated to store the raw rgb data, however this assumption is not true for all bhmd.plane values:

if ( ( Image = SDL_CreateRGBSurface( SDL_SWSURFACE, width, bmhd.h, (bmhd.planes==24 || flagHAM==1)?24:8, 0, 0, 0, 0 ) ) == NULL )

It is only true if the Uint8 planes field is equal to 24 or is less than 8 (assuming the flagHAM flag is not set), providing another value results in a constrained heap overflow of user controlled data during the color population loop that was previously listed.

Crash Information

Program received signal SIGSEGV, Segmentation fault.
0x00007f666bcc642a in IMG_LoadLBM_RW (src=0x16cfa30) at IMG_lbm.c:294
294                 Image->format->palette->colors[i].r = Image->format->palette->colors[i%nbcolors].r;
--------------------------------------------------------------------------[ registers ]----
$rax   : 0x000000000000009a -> 0x000000000000009a
$rbx   : 0x0000000000000000 -> 0x0000000000000000
$rcx   : 0x000000000177f000 -> 0x000000000177f000
$rdx   : 0x0000000000000010 -> 0x0000000000000010
$rsp   : 0x00007ffd79443ab0 -> 0x0000000200000000 -> 0x0000000200000000
$rbp   : 0x00007ffd79443e90 -> 0x00007ffd79443ed0 -> 0x00007ffd79443f00 -> 0x00007ffd79443f40 -> 0x0000000000000000 ->           
0x0000000000000000
$rsi   : 0x00000000016f6fc0 -> 0xff5d69dbff96b6eb
$rdi   : 0x00007f666c26d540 -> 0x0000000000000030 -> 0x0000000000000030
$rip   : 0x00007f666bcc642a -> <IMG_LoadLBM_RW+2336> mov BYTE PTR [rcx], al
$r8    : 0x00000000016e98c0 -> 0x0000000000000000 -> 0x0000000000000000
$r9    : 0x00007ffd79443978 -> 0x00007f666bf95c7d -> 0x4855c3c9f8458b48 -> 0x4855c3c9f8458b48
$eflags: [carry PARITY adjust zero sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
------------------------------------------------------------------------------[ stack ]----
0x00007ffd79443ab0|+0x00: 0x0000000200000000 -> 0x0000000200000000      <-$rsp
0x00007ffd79443ab8|+0x08: 0x00000000016cfa30 -> 0x00007f666bf45e48 -> 0x20ec8348e5894855 -> 0x20ec8348e5894855
0x00007ffd79443ac0|+0x10: 0x0000000000000000 -> 0x0000000000000000
0x00007ffd79443ac8|+0x18: 0x000000000171e658 -> 0x007171be665549ae -> 0x007171be665549ae
0x00007ffd79443ad0|+0x20: 0x0000000002000200 -> 0x0000000002000200
0x00007ffd79443ad8|+0x28: 0x0a0a000000010015 -> 0x0a0a000000010015
0x00007ffd79443ae0|+0x30: 0x3300000002000200 -> 0x3300000002000200
0x00007ffd79443ae8|+0x38: 0x00007ffd00026e08 -> 0x00007ffd00026e08
-------------------------------------------------------------------[ code:i386:x86-64 ]----
0x7f666bcc641c <IMG_LoadLBM_RW+2322> mov    eax, edx
0x7f666bcc641e <IMG_LoadLBM_RW+2324> mov    eax, eax
0x7f666bcc6420 <IMG_LoadLBM_RW+2326> shl    rax, 0x2
0x7f666bcc6424 <IMG_LoadLBM_RW+2330> add    rax, rsi
0x7f666bcc6427 <IMG_LoadLBM_RW+2333> movzx  eax, BYTE PTR [rax]
 ->0x7f666bcc642a <IMG_LoadLBM_RW+2336> mov    BYTE PTR [rcx], al
0x7f666bcc642c <IMG_LoadLBM_RW+2338> mov    rax, QWORD PTR [rbp-0x8]
0x7f666bcc6430 <IMG_LoadLBM_RW+2342> mov    rax, QWORD PTR [rax+0x8]
0x7f666bcc6434 <IMG_LoadLBM_RW+2346> mov    rax, QWORD PTR [rax+0x8]
0x7f666bcc6438 <IMG_LoadLBM_RW+2350> mov    rax, QWORD PTR [rax+0x8]
0x7f666bcc643c <IMG_LoadLBM_RW+2354> mov    edx, DWORD PTR [rbp-0x30]
---------------------------------------------------------------[ source:IMG_lbm.c+294 ]----
 290              nbrcolorsfinal = (1<<bmhd.planes);
 291          }
 292          for ( i=nbcolors; i < (Uint32)nbrcolorsfinal; i++ )
 293          {
            // Image=0x00007ffd79443e88 -> [...] -> 0x0000000000000000
-> 294               Image->format->palette->colors[i].r = Image->format->palette->colors[i%nbcolors].r;
 295              Image->format->palette->colors[i].g = Image->format->palette->colors[i%nbcolors].g;
 296              Image->format->palette->colors[i].b = Image->format->palette->colors[i%nbcolors].b;
 297          }
 298          if ( !pbm )
----------------------------------------------------------------------------[ threads ]----
[#0] Id 1, Name: "", stopped, reason: SIGSEGV
------------------------------------------------------------------------------[ trace ]----
[#0] 0x7f666bcc642a->Name: IMG_LoadLBM_RW(src=0x16cfa30)
[#1] 0x7f666bcc17ef->Name: IMG_LoadTyped_RW(src=0x16cfa30, freesrc=0x1, type=0x7ffd79444441 "<(-_-)>/asdf")
[#2] 0x7f666bcc15e0->Name: IMG_Load(file=0x7ffd79444440 "<(-_-)>/asdf")
[#3] 0x400b85->Name: main(argc=0x2, argv=0x7ffd79444028)

Timeline

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

Credit

Discovered by Lilith <(x_x)> of Cisco Talos.