Talos Vulnerability Report

TALOS-2017-0425

Blender BKE_image_acquire_ibuf Integer Overflow Code Execution Vulnerability

January 11, 2018
CVE Number

CVE-2017-2918

Summary

An exploitable integer overflow exists in the Image loading functionality of the Blender open-source 3d creation suite v2.78c. A specially crafted .blend 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 open the file or use it as a library 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 allocates space for an Image type when rendering a .blend file. When allocating space for the Image, 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.

Due to it's flexibility, the .blend file format is capable of storing a vast number of data structures and is capable of completely serializing the state of the application to disk. This means that a data structure in C will have the exact same format as the version on disk. In the following file, the structure for an Image record is described. This data structure is populated using data from the file.

source/blender/makesdna/DNA_image_types.h:99
typedef struct Image {
    ID id;

    char name[1024];            /* file path, 1024 = FILE_MAX */
...
    int flag;
    short source, type;
    int lastframe;
...
    int gen_x, gen_y;
    char gen_type, gen_flag;
    short gen_depth;
    float gen_color[4];
...
} Image

When loading an Image record from a .blend file, the function BKE_image_acquire_ibuf in the source/blender/blenkernel/intern/image.c file will be called. This function will be passed two structures. One of which is the Image record that was read from the file. The other of which is the ImageUser record which is used to identify the image from either a Texture/Material, Background Image, or the Image Window. This function will call the image_acquire_ibuf function at [1] in order to allocate space for it.

source/blender/blenkernel/intern/image.c:4086
ImBuf *BKE_image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock)
{
    ImBuf *ibuf;

    BLI_spin_lock(&image_spin);

    ibuf = image_acquire_ibuf(ima, iuser, r_lock);      / [1]

    BLI_spin_unlock(&image_spin);

    return ibuf;
}

Once inside the image_acquire_ibuf functon, the application will first check a field named "ok" within the Image and ImageUser structures [2]. After this is done, the Image structure will have it's source and type fields checked. The provided proof-of-concept uses an image source of IMA_SRC_GENERATED(4). This will cause the case at [3] to be used to handle the acquiring of the ImBuf structure. Once the gen_x, gen_y, and gen_depth fields are validated, the add_ibuf_size function will be called [4]. It is prudent to note that at [5] another similar vulnerability can be found for the IMA_SRC_VIEWER(5) image source. This will be discussed later.

source/blender/blenkernel/intern/image.c:3990
static ImBuf *image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock)
{
    ImBuf *ibuf = NULL;
    int frame = 0, index = 0;

    if (r_lock)
        *r_lock = NULL;

    /* quick reject tests */
    if (!image_quick_test(ima, iuser))                                          // [2]
        return NULL;
...
    if (ibuf == NULL) {
...
        else if (ima->source == IMA_SRC_GENERATED) {                            // [3]
...
            if (ima->gen_x == 0) ima->gen_x = 1024;
            if (ima->gen_y == 0) ima->gen_y = 1024;
            if (ima->gen_depth == 0) ima->gen_depth = 24;
            ibuf = add_ibuf_size(ima->gen_x, ima->gen_y, ima->name, ima->gen_depth, (ima->gen_flag & IMA_GEN_FLOAT) != 0, ima->gen_type,
                                 ima->gen_color, &ima->colorspace_settings);    // [4]
            image_assign_ibuf(ima, ibuf, index, 0);
            ima->ok = IMA_OK_LOADED;
        }
        else if (ima->source == IMA_SRC_VIEWER) {                               // [5]
            if (ima->type == IMA_TYPE_R_RESULT) {
...
                ibuf = image_get_render_result(ima, iuser, r_lock);
            }
...
}

The add_ibuf_size function will then take the width and height fields that came from Image.gen_x and Image.gen_y and then pass them to the IMB_allocImBuf function at [6]. This function will take the product of these fields and the size of an unsigned int(4) and use them to calculate the size of a heap-buffer. If the result of this is larger than 32-bits, then an integer overflow will occur. This can result in an undersized buffer that when written to will cause a heap-based buffer overflow. At [7], the application will then write data into this buffer.

source/blender/blenkernel/intern/image.c:632
static ImBuf *add_ibuf_size(unsigned int width, unsigned int height, const char *name, int depth, int floatbuf, short gen_type,
                            const float color[4], ColorManagedColorspaceSettings *colorspace_settings)
{
    ImBuf *ibuf;
    unsigned char *rect = NULL;
    float *rect_float = NULL;

    if (floatbuf) {
        ibuf = IMB_allocImBuf(width, height, depth, IB_rectfloat);                  // [6] Image.gen_flag & IMA_GEN_FLOAT(1)
...
    }
    else {
        ibuf = IMB_allocImBuf(width, height, depth, IB_rect);                       // [6] Image.gen_flag & ~IMA_GEN_FLOAT(1)
...
    }
...
    switch (gen_type) {
        case IMA_GENTYPE_GRID:
            BKE_image_buf_fill_checker(rect, rect_float, width, height);            // [7]
            break;
        case IMA_GENTYPE_GRID_COLOR:
            BKE_image_buf_fill_checker_color(rect, rect_float, width, height);      // [7]
            break;
        default:
            BKE_image_buf_fill_color(rect, rect_float, width, height, color);       // [7] Used by provided proof-of-concept
            break;
    }

    return ibuf;
}

Each of the functions at [7] will then be used to populate the undersized buffer. At [8], the application will hand off initialization of the buffer to a thread. This will wrap a call to image_buf_fill_color_slice at [9] and then later one of the loops at [10] will be used to write into the buffer. The proof-of-concept that is included chooses the non-rect_float loop to overflow the buffer.

source/blender/blenkernel/intern/image_gen.c:97
void BKE_image_buf_fill_color(unsigned char *rect,
                              float *rect_float,
                              int width, int height,
                              const float color[4])
{
    if (((size_t)width) * height < 64 * 64) {
...
    }
    else {
        FillColorThreadData data;
        data.rect = rect;
        data.rect_float = rect_float;
        data.width = width;
        copy_v4_v4(data.color, color);
        IMB_processor_apply_threaded_scanlines(
            height, image_buf_fill_color_thread_do, &data);     // [8] \
    }
}
\
source/blender/blenkernel/intern/image_gen.c:82
static void image_buf_fill_color_thread_do(void *data_v,
                                           int start_scanline,
                                           int num_scanlines)
{
...
    image_buf_fill_color_slice(rect,
                               rect_float,
                               data->width,
                               num_scanlines,
                               data->color);                    // [9] \
}
\
source/blender/blenkernel/intern/image_gen.c:48
static void image_buf_fill_color_slice(unsigned char *rect,
                                       float *rect_float,
                                       int width, int height,
                                       const float color[4])
{
    int x, y;

    /* blank image */
    if (rect_float) {
        float linear_color[4];
        srgb_to_linearrgb_v4(linear_color, color);
        for (y = 0; y < height; y++) {                          // [10]
            for (x = 0; x < width; x++) {
                copy_v4_v4(rect_float, linear_color);
                rect_float += 4;
            }
        }
    }

    if (rect) {
        unsigned char ccol[4];
        rgba_float_to_uchar(ccol, color);
        for (y = 0; y < height; y++) {                          // [10] Loop chosen by proof-of-concept
            for (x = 0; x < width; x++) {
                rect[0] = ccol[0];
                rect[1] = ccol[1];
                rect[2] = ccol[2];
                rect[3] = ccol[3];
                rect += 4;
            }
        }
    }
}

There is a path to similar bug if the Image.source field is IMA_SRC_VIEWER(5) and the Image.type field is set to IMA_TYPE_R_RESULT(4). At [11], the image_get_render_result function will get called. After selecting the Image.render_slot index of the Image.renders array at [12], the application will use the RenderResult.rectx and RenderResult.recty fields to allocate an ImBuf at [13]. Immediately afterwards, the application will assign the Image to the undersized buffer. This will also cause a heap-based buffer overflow.

source/blender/blenkernel/intern/image.c:4041
        else if (ima->source == IMA_SRC_VIEWER) {                   // [5]
            if (ima->type == IMA_TYPE_R_RESULT) {
                /* always verify entirely, and potentially
                 * returns pointer to release later */
                ibuf = image_get_render_result(ima, iuser, r_lock); // [11] \
            }
\
source/blender/blenkernel/intern/image.c:3666
static ImBuf *image_get_render_result(Image *ima, ImageUser *iuser, void **r_lock)
{
...
    RenderResult rres;
...
    if (from_render) {
        RE_AcquireResultImage(re, &rres, actview);
    }
    else if (ima->renders[ima->render_slot]) {                      // [12]
        rres = *(ima->renders[ima->render_slot]);
        rres.have_combined = ((RenderView *)rres.views.first)->rectf != NULL;
    }
...
    if (ibuf == NULL) {
        ibuf = IMB_allocImBuf(rres.rectx, rres.recty, 32, 0);       // [13]
        image_assign_ibuf(ima, ibuf, IMA_NO_INDEX, 0);
    }

The structure for the RenderResult is located within the source/blender/render/extern/include/RE_pipeline.h function. The rectx and recty fields are multiplied with the size of an unsigned int in order to calculate the size for the target buffer for the second bug.

source/blender/render/extern/include/RE_pipeline.h:137
typedef struct RenderResult {
    struct RenderResult *next, *prev;

    /* target image size */
    int rectx, recty;
    short crop, sample_nr;

    /* the following rect32, rectf and rectz buffers are for temporary storage only, for RenderResult structs
     * created in #RE_AcquireResultImage - which do not have RenderView */

    /* optional, 32 bits version of picture, used for ogl render and image curves */
    int *rect32;
    /* if this exists, a copy of one of layers, or result of composited layers */
    float *rectf;
    /* if this exists, a copy of one of layers, or result of composited layers */
    float *rectz;
...
} RenderResult;

Crash Information

(1ddc.b58): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000003 ecx=00003c01 edx=03ed0000 esi=1e211000 edi=00004000
eip=00b23bc6 esp=305bfacc ebp=305bfaec 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!PyInit_mathutils_noise_types+0x2e5146:
00b23bc6 8806            mov     byte ptr [esi],al          ds:002b:1e211000=??

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 .blend file to.

$ python poc.py $FILENAME.blend

To trigger the vulnerability with the provided proof-of-concept, one can simply add it as a library and attempt to use the Image. Another way is to open the file and navigate to the DataBlocks view.

$ /path/to/blender.exe $FILENAME.blend

To automate triggering the vulnerability without user-interaction, apply the Image to a Texture and then include the texture in a Scene.

Mitigation

In order to mitigate this vulnerability, it is recommended to not open a .blend file or use a .blend file as a library from an untrusted user.

Timeline

2017-09-06 - Vendor Disclosure
2018-01-11 - Public Release

Credit

Discovered by a member of Cisco Talos.