Talos Vulnerability Report

TALOS-2017-0408

Blender Sequencer imb_loadiris Integer Overflow Code Execution Vulnerability

January 11, 2018
CVE Number

CVE-2017-2901

Summary

An exploitable integer overflow exists in the IRIS loading functionality of the Blender open-source 3d creation suite version 2.78c. A specially crafted .iris file can cause an integer overflow resulting in a buffer overflow which can allow for code execution under the context of the application. An attacker can convince a user to use the file as an asset via the sequencer in order to trigger this vulnerability.

Tested Versions

Blender v2.78c

Product URLs

http://www.blender.org git://git.blender.org/blender.git

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

Blender is a professional, open-source 3d computer graphics application. It is used for creating animated films, visual effects, art, 3d printed applications, and video games. It is also capable of doing minimalistic video editing and sequencing as needed by the user. There are various features that it provides which allow for a user to perform a multitude of actions as required by a particular project.

This vulnerability exists with how the Blender application loads an IRIS file as a resource for the video sequencer. When allocating space for the image data within a .iris file, the application will perform some arithmetic which can overflow. This result will then be used to perform an allocation which can allow for an undersized buffer. Later when the application attempts to render the image data into this buffer, a heap-based buffer overflow will occur.

When loading an image file, the function IMB_loadiffname in the source/blender/imbuf/intern/readimage.c file will be called. Inside this function, the application will first open the file and then call the IMB_loadifffile function [2].

source/blender/imbuf/intern/readimage.c:212
ImBuf *IMB_loadiffname(const char *filepath, int flags, char colorspace[IM_MAX_SPACE])
{
...
    file = BLI_open(filepath_tx, O_BINARY | O_RDONLY, 0);                   // [1]
    if (file == -1)
        return NULL;

    ibuf = IMB_loadifffile(file, filepath, flags, colorspace, filepath_tx); // [2]

Inside the IMB_loadifffile function, the application will first map the whole file into memory using the mmap system-call [3]. After the file is successfully mapped into memory, the resulting pages will be passed to the IMB_ibImageFromMemory function [4]. This function is responsible for figuring out which file-format handlers to use, and then to call its respective loader.

source/blender/imbuf/intern/readimage.c:165
ImBuf *IMB_loadifffile(int file, const char *filepath, int flags, char colorspace[IM_MAX_SPACE], const char *descr)
{
...
    imb_mmap_lock();
    mem = mmap(NULL, size, PROT_READ, MAP_SHARED, file, 0);             // [3]
    imb_mmap_unlock();

    if (mem == (unsigned char *) -1) {
        fprintf(stderr, "%s: couldn't get mapping %s\n", __func__, descr);
        return NULL;
    }

    ibuf = IMB_ibImageFromMemory(mem, size, flags, colorspace, descr);  // [4]

Inside the following function, the application will iterate through a global list that contains different handlers for all of the image files that the application supports. At [5], the application will call the function responsible for loading the image out of memory.

source/blender/imbuf/intern/readimage.c:104
ImBuf *IMB_ibImageFromMemory(unsigned char *mem, size_t size, int flags, char colorspace[IM_MAX_SPACE], const char *descr)
{
...
    for (type = IMB_FILE_TYPES; type < IMB_FILE_TYPES_LAST; type++) {
        if (type->load) {
            ibuf = type->load(mem, size, flags, effective_colorspace);              // [5]
            if (ibuf) {
                imb_handle_alpha(ibuf, flags, colorspace, effective_colorspace);
                return ibuf;
            }
        }
    }

When loading a .iris file, the following function, imb_loadiris, will be called. This function will first verify that the file-magic of the file matches the required one for the IRIS file format [6]. Afterwards, the header will be read at [7]. This header contains the values that will be used to trigger the integer overflow.

source/blender/imbuf/intern/iris.c:262
struct ImBuf *imb_loadiris(const unsigned char *mem, size_t size, int flags, char colorspace[IM_MAX_SPACE])
{
...
    if (!imb_is_a_iris(mem)) return NULL;   // [6]
...
    readheader(inf, &image);                // [7]
    if (image.imagic != IMAGIC) {
        fprintf(stderr, "longimagedata: bad magic number in image file\n");
        return(NULL);
    }

After reading and validating the magic within the header, the following code will be executed. This will be used to determine how the image data that is decoded is stored. First, the image is checked if it’s encoded with run-length-encoding followed by checking the bits-per-pixel [8] of the header. Within the provided proof-of-concept, the value read from the file that is stored to the rle variable is 0.

source/blender/imbuf/intern/iris.c:291
    rle = ISRLE(image.type);        // [8]
    bpp = BPP(image.type);
    if (bpp != 1 && bpp != 2) {
        fprintf(stderr, "longimagedata: image must have 1 or 2 byte per pix chan\n");
        return(NULL);
    }
    
    xsize = image.xsize;
    ysize = image.ysize;
    zsize = image.zsize;

Eventually, the application will use the values read from the header to allocate an image buffer at [9]. This will take use the product of the xsize and ysize variables and use them to allocate a buffer that will contain the decoded image data. If the product of these variables with the value 4 is larger than 32-bits, then the size for the allocation will overflow. Later at [10], a buffer overflow may occur when writing to the image-buffer. This can lead to code execution under the context of the application.

source/blender/imbuf/intern/iris.c:415
        if (bpp == 1) {
            
            ibuf = IMB_allocImBuf(xsize, ysize, 8 * zsize, IB_rect);    // [9]
            if (ibuf->planes > 32) ibuf->planes = 32;

            base = ibuf->rect;
            zbase = (unsigned int *)ibuf->zbuf;
            
            MFILE_SEEK(inf, HEADER_SIZE);                               // [10]
            rledat = MFILE_DATA(inf);
            
            for (z = 0; z < zsize; z++) {
                
                if (z < 4) lptr = base;
                else if (z < 8) lptr = zbase;
                
                for (y = 0; y < ysize; y++) {

                    interleaverow((uchar *)lptr, rledat, 3 - z, xsize); // [11]
                    rledat += xsize;
                    
                    lptr += xsize;
                }
            }
            
        }

Crash Information

(1da0.21e4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=09800200 ebx=00008000 ecx=09e21003 edx=00007c01 esi=09800600 edi=09e20004
eip=0167ff15 esp=0490ecec ebp=0490edbc iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
blender!osl_texture_set_interp_code+0x109715:
0167ff15 8801            mov     byte ptr [ecx],al          ds:002b:09e21003=??

Exploit Proof-of-Concept

Included with this advisory is a generator for the vulnerability. This proof-of-concept requires python and takes a single-argument which is the filename to write the .iris file to.

$ python poc.py $FILENAME.iris

To trigger the vulnerability, one can simply add it as an asset or they can pass it as an argument to the blender executable.

$ /path/to/blender.exe -a $FILENAME.iris

Mitigation

In order to mitigate this vulnerability, it is recommended to not use untrusted image files as an asset when using the sequencer.

Timeline

2017-09-01 - Vendor Disclosure
2017-09-14 - Vendor patched
2018-01-11 - Public Release

Credit

Discovered by a member of Cisco Talos.