Talos Vulnerability Report

TALOS-2017-0491

Simple DirectMedia Layer SDL2_image Image Palette Population Code Execution Vulnerability

March 1, 2018
CVE Number

CVE-2017-14442

Summary

An exploitable code execution vulnerability exists in the BMP image rendering functionality of SDL2_image-2.0.2. A specially crafted BMP image can cause a stack 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-121: Stack-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 reading in a BMP file, libSDL2_Image must populate a color palette object for the image. As per the BMP file format, one of the headers read in is the “biClrUsed” field, which designates how many colors are within the color palette. This is read by the following code:

//IMG_bmp.c:683
    biSize = SDL_ReadLE32(src);        //offset 0x16 in file
    if (biSize == 40) {
    	biWidth = SDL_ReadLE32(src);          //0x1A
 	   	biHeight = SDL_ReadLE32(src);         //0x1E 
 	    	biPlanes = SDL_ReadLE16(src);         //0x23
    		biBitCount = SDL_ReadLE16(src);       //0x25
    		biCompression = SDL_ReadLE32(src);
    		biSizeImage = SDL_ReadLE32(src);
    		biXPelsPerMeter = SDL_ReadLE32(src);  //0x2e
   		biYPelsPerMeter = SDL_ReadLE32(src);  //0x32
    		biClrUsed = SDL_ReadLE32(src);        //0x36  [1]
    		biClrImportant = SDL_ReadLE32(src);   //0x3a

At [1], the biClrUsed field is read in as an unsigned 32-bit integer. After this, the variable is never mentioned again, at least until it is used to populate the palette array as such:

if (biBitCount <= 8) {
	if (biClrUsed == 0) {
		biClrUsed = 1 << biBitCount;
    		}
    		for (i = 0; i < (int) biClrUsed; ++i) {  //[1] 
        		SDL_RWread(src, &palette[i], 4, 1);
   		 }

Since there isn’t any validation on the “biClrUsed” field at all, the result is a stack based buffer overflow completely under control of an attacker.

Crash Information

$rax   : 0x019c3c51016b173b -> 0x019c3c51016b173b
$rbx   : 0x0000000000000000 -> 0x0000000000000000
$rcx   : 0x0000000001e28b77 -> 0x0000000001e28b77
$rdx   : 0x0000000000000004 -> 0x0000000000000004
$rsp   : 0x00007ffe43fe6810 -> 0x0000000100000000 -> 0x0000000100000000
$rbp   : 0x00007ffe43fe6cd0 -> 0x00007ffe43fe6cf0 -> 0x00007ffe43fe6d30 -> 0x00007ffe43fe6d60 -> 0x00007ffe43fe6da0 ->     
    0x0000000000000000    
-> 0x0000000000000000
$rsi   : 0x0000000002735c60 -> 0x0000000000000000 -> 0x0000000000000000
$rdi   : 0x00007ffe43fe6cc0 -> 0x0000000000000000 -> 0x0000000000000000
rip   : 0x00007f8c982f6105 -> <LoadICOCUR_RW+1197> mov rdx, QWORD PTR [rax+0x20]
$r8    : 0x0000000002735c60 -> 0x0000000000000000 -> 0x0000000000000000
$r9    : 0x00007ffe43fe6cbc -> 0x0000000001e28b78 -> 0x0000000001e28b78
$r10   : 0x0000000000ff0000 -> 0x0000000000ff0000
$r11   : 0x00007f8c9856d5ce -> 0x20ec8348e5894855 -> 0x20ec8348e5894855
$r12   : 0x0000000000400a10 -> <_start+0> xor ebp, ebp
$r13   : 0x00007ffe43fe6e80 -> 0x0000000000000002 -> 0x0000000000000002
$r14   : 0x0000000000000000 -> 0x0000000000000000
$r15   : 0x0000000000000000 -> 0x0000000000000000
$eflags: [CARRY parity adjust zero SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification]
----------------------------------------------------------------------------------------------------------------------------------------------    --------------------------------------------------------[ stack ]----
0x00007ffe43fe6810|+0x00: 0x0000000100000000 -> 0x0000000100000000      <-$rsp
0x00007ffe43fe6818|+0x08: 0x0000000002756200 -> 0x00007f8c98578e48 -> 0x20ec8348e5894855 -> 0x20ec8348e5894855
0x00007ffe43fe6820|+0x10: 0x000000000000000b -> 0x000000000000000b
0x00007ffe43fe6828|+0x18: 0x0000068800000000 -> 0x0000068800000000
0x00007ffe43fe6830|+0x20: 0x01c2989401703453 -> 0x01c2989401703453
0x00007ffe43fe6838|+0x28: 0x01dd8e7701df886f -> 0x01dd8e7701df886f
0x00007ffe43fe6840|+0x30: 0x0163234e015e1b45 -> 0x0163234e015e1b45
0x00007ffe43fe6848|+0x38: 0x01753b6b01692b59 -> 0x01753b6b01692b59
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------[ code:i386:x86-64 ]----
0x7f8c982f60f4 <LoadICOCUR_RW+1180> rol    BYTE PTR [rbx-0x74fe13bb], 1
0x7f8c982f60fa <LoadICOCUR_RW+1186> rex.RB (bad)
0x7f8c982f60fc <LoadICOCUR_RW+1188> cmp    eax, DWORD PTR [rbp-0x14]
0x7f8c982f60ff <LoadICOCUR_RW+1191> jg     0x7f8c982f60c2 <LoadICOCUR_RW+1130>
0x7f8c982f6101 <LoadICOCUR_RW+1193> mov    rax, QWORD PTR [rbp-0x20]
->0x7f8c982f6105 <LoadICOCUR_RW+1197> mov    rdx, QWORD PTR [rax+0x20]
0x7f8c982f6109 <LoadICOCUR_RW+1201> mov    rax, QWORD PTR [rbp-0x20]
0x7f8c982f610d <LoadICOCUR_RW+1205> mov    ecx, DWORD PTR [rax+0x14]
0x7f8c982f6110 <LoadICOCUR_RW+1208> mov    rax, QWORD PTR [rbp-0x20]
0x7f8c982f6114 <LoadICOCUR_RW+1212> mov    eax, DWORD PTR [rax+0x18]
0x7f8c982f6117 <LoadICOCUR_RW+1215> imul   eax, ecx
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------[ source:IMG_bmp.c+761 ]----
757          }
758      }
759  
760      /* Read the surface pixels.  Note that the bmp image is upside down */
            // surface=0x00007ffe43fe6cb0 -> [...] -> 0x019c3c51016b173b, bits=0x00007ffe43fe6ca8 -> [...] -> 0x01e4b28b01f7d1a1
-> 761       bits = (Uint8 *) surface->pixels + (surface->h * surface->pitch); //points immediately after 0x1000 buffer
762      switch (ExpandBMP) {
763      case 1:
764          bmpPitch = (biWidth + 7) >> 3;
765          pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0);
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------[ threads ]----
[#0] Id 1, Name: "", stopped, reason: SIGSEGV
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------[ trace ]----
[#0] 0x7f8c982f6105->Name: LoadICOCUR_RW(src=0x2756200, type=0x1, freesrc=0x0)
[#1] 0x7f8c982f6651->Name: IMG_LoadICO_RW(src=0x2756200)
[#2] 0x7f8c982f47ef->Name: IMG_LoadTyped_RW(src=0x2756200, freesrc=0x1, type=0x7ffe43fe8459 "/<(^_^)>")
[#3] 0x7f8c982f45e0->Name: IMG_Load(file=0x7ffe43fe8458 "./<(^_^)>")
[#4] 0x400b85->Name: main(argc=0x2, argv=0x7ffe43fe6e88)
-----------------------------------------------------------------------------------------------------------------------------------------

Timeline

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

Credit

Discovered by Yves Younan and Lilith of Cisco Talos.