Talos Vulnerability Report

TALOS-2019-0844

SDL_image XPM image colorhash parsing Code Execution Vulnerability

July 29, 2019
CVE Number

CVE-2019-5060

Summary

An exploitable code execution vulnerability exists in the XPM image rendering function of SDL2_image 2.0.4. A specially crafted XPM image can cause an integer overflow in the colorhash function, allocating too small of a buffer. This buffer can then be written out of bounds, resulting in a heap overflow, ultimately ending in code execution. An attacker can display a specially crafted image to trigger this vulnerability.

Tested Versions

SDL_image 2.0.4

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-190: Integer Overflow or Wraparound

Details

LibSDL is a multi-platform library for easy access to low-level hardware and graphics, providing support for a large number of video games, software and emulators. The LibSDL2_Image library is an optional component that deals specifically with parsing and displaying a variety of image file formats, creating a single, uniform API for image processing, regardless of the type.

XPM is a line-based image format. The first line contains the width, height, number of colors, and number of characters per pixel to be parsed [0].

SDL_image/IMG_xpm.c:1015

/*
 * The header string of an XPMv3 image has the format
 *
 * <width> <height> <ncolors> <cpp> [ <hotspot_x> <hotspot_y> ]
 *
 * where the hotspot coords are intended for mouse cursors.
 * Right now we don't use the hotspots but it should be handled
 * one day.
 */
if (SDL_sscanf(line, "%d %d %d %d", &w, &h, &ncolors, &cpp) != 4 // [0]
   || w <= 0 || h <= 0 || ncolors <= 0 || cpp <= 0) {
    error = "Invalid format description";
    goto done;
}

Using the number of colors from the parsed line above, a colorhash is created via the createcolorhash function [1].

SDL_image/IMG_xpm.c:97

static struct color_hash *create_colorhash(int maxnum) // [1]
{
    int bytes, s;
    struct color_hash *hash;

    /* we know how many entries we need, so we can allocate
    everything here */
    hash = (struct color_hash *)SDL_malloc(sizeof *hash);
    if (!hash)
        return NULL;

    /* use power-of-2 sized hash table for decoding speed */
    // #define STARTING_HASH_SIZE 256
    for (s = STARTING_HASH_SIZE; s < maxnum; s <<= 1) { } // [2]
    hash->size = s;
    hash->maxnum = maxnum;

    bytes = hash->size * sizeof(struct hash_entry **);
    hash->entries = NULL;   /* in case malloc fails */
    hash->table = (struct hash_entry **)SDL_malloc(bytes); // [3]

    if (!hash->table) {
        SDL_free(hash);
        return NULL;
    }

    SDL_memset(hash->table, 0, bytes);

    hash->entries = (struct hash_entry *)SDL_malloc(maxnum * sizeof(struct hash_entry));
    if (!hash->entries) {
        SDL_free(hash->table);
        SDL_free(hash);
        return NULL;
    }
    hash->next_free = hash->entries;
    printf("Exit create_colorhash\n");
    return hash;
}

SDL_image/IMG_xpm.c:77

struct color_hash {
    struct hash_entry **table;
    struct hash_entry *entries; /* array of all entries */
    struct hash_entry *next_free;
    int size;
    int maxnum;
};

The table entry in the colorhash is created in the createcolorhash function based on the number of colors passed into the function. The size of the colorhash table is the first value in the powers of 2 larger than the passed in number of colors [2]. The size of the allocation is this calculated value * 8 (sizeof(struct hashentry **)) [3]. This multiplication can cause an overflow, resulting in a very small allocation.

The next set of lines in the XPM format describes each color and the format of the pixels. The most basic format for each of these lines is to have a character with a given color. For instance, the following line shows that any following 'z' will be colored black and will produce a line of colors that has 3 black pixels, 3 white pixels, and then 3 black pixels.

z c #000000
. c #ffffff
zzz...zzz

After parsing a color line, the color is inserted into the hash table created above via the add_colorhash function based on a hash index. The hash function argument is the hash created above, the key is a substring of the color line, and the cpp is the characters per pixel read from image.

SDL_image/IMG_xpm.c:148

static int add_colorhash(struct color_hash *hash, char *key, int cpp, Uint32 color)
{
    int index = hash_key(key, cpp, hash->size);
    struct hash_entry *e = hash->next_free++;

    e->color = color;
    e->key = key;
    e->next = hash->table[index];
    hash->table[index] = e; // [3]
    return 1;
}

The index for the table is created from the hash_key function. This index is used when writing a value into the hash table [3].

SDL_image/IMG_xpm.c:86

static int hash_key(const char *key, int cpp, int size)
{
    int hash;

    hash = 0;
    while ( cpp-- > 0 ) {
        hash = hash * 33 + *key++;
    }
    return hash & (size - 1);
}

An attacker can specify a number of colors in the original to cause an overflow, resulting in a very small allocation. It is also possible to have a color hashed to an index, which would result in an out-of-bounds write of a stack address, potentially resulting in code execution.

Crash Information

ASAN:SIGSEGV
=================================================================
==46378==ERROR: AddressSanitizer: SEGV on unknown address 0x6020cb097a48 (pc 0x0000004baccf bp 0x000000ffffff sp 0x7ffc18971570 T0)
    #0 0x4bacce in add_colorhash ../IMG_xpm.c:160
    #1 0x4bacce in load_xpm ../IMG_xpm.c:1155
    #2 0x40653c in IMG_LoadTyped_RW ../IMG.c:195
    #3 0x404d70 in main ../showimage.c:56
    #4 0x7fccb19bc82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #5 0x405388 in _start (/root/vmshare/targets/sdl/SDL_image/build-afl-asan/showimage+0x405388)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV ../IMG_xpm.c:160 add_colorhash
==46378==ABORTING

Timeline

2019-05-30 - Vendor Disclosure
2019-07-03 - Vendor Patched
2019-07-29 - Public Release

Credit

Discovered by Cory Duplantis of Cisco Talos.