Talos Vulnerability Report

TALOS-2017-0451

Blender customData_add_layer__internal Integer Overflow Code Execution Vulnerability

January 11, 2018
CVE Number

CVE-2017-12099

Summary

An exploitable integer overflow exists in the upgrade of the legacy Mesh attribute tface 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.

During the initial load of a .blend file, a version check of the file is triggered in order to adjust legacy features. Blender has a series of fixes isolated by blocks based off of the version, shown below.

source/blender/blenloader/intern/readfile.c:8334
static void do_versions(FileData *fd, Library *lib, Main *main)
{
    ...
    blo_do_versions_pre250(fd, lib, main); // [0]
    blo_do_versions_250(fd, lib, main);
    blo_do_versions_260(fd, lib, main);
    blo_do_versions_270(fd, lib, main);
}

The numbers 250 - 270 correspond to checks pertaining to that particular version block. During the checks for version before 2.5 [0], there are a few fixes for versions before 2.42 [1]. One of which fixes specific CustomData for Mesh objects [2]

source/blender/blenloader/intern/versioning_legacy.c:2470
void blo_do_versions_pre250(FileData *fd, Library *lib, Main *main) {
    ...
    if (main->versionfile <= 242) { [1]
    ...
        for (me = main->mesh.first; me; me = me->id.next)
            customdata_version_242(me); [2]
    } 
}

If the current Mesh object claims to have a number of total layers in its fdata and has the deprecated field tface, then a particular fix is applied.

source/blender/blenloader/intern/versioning_legacy.c:371
static void customdata_version_242(Mesh *me)
{
    ...
    if (!me->fdata.totlayer) {
        ...

        if (me->tface) {
            ...
            me->mcol = CustomData_add_layer(&me->fdata, CD_MCOL, CD_CALLOC, NULL, me->totface); [3]
            me->mtface = CustomData_add_layer(&me->fdata, CD_MTFACE, CD_CALLOC, NULL, me->totface);

Because tface is deprecated, the new mcol and mtface must be created. This is done via the CustomData_add_layer API. Note that me->totface is from the Mesh object created from file data [3]. CustomData_add_layer calls an internal API customData_add_layer__internal to allocate the memory necessary for the new mcol and mtface objects [4].

source/blender/blenkernel/intern/customdata.c:1930
void *CustomData_add_layer(CustomData *data, int type, int alloctype,
                        void *layerdata, int totelem)
{
    CustomDataLayer *layer;
    const LayerTypeInfo *typeInfo = layerType_getInfo(type);

    layer = customData_add_layer__internal(data, type, alloctype, layerdata,
                                        totelem, typeInfo->defaultname); [4]
    CustomData_update_typemap(data);

    if (layer)
        return layer->data;

    return NULL;
}

During the creation of this customData layer, the size of the layer is calculated by multiplying the total elements (me->totface from above) by the size of the structure. By supplying a large enough value, this size variable can be overflown to be a much smaller number than required [5].

source/blender/blenkernel/intern/customdata.c:1841
static CustomDataLayer *customData_add_layer__internal(CustomData *data, int type, int alloctype, void *layerdata,
                                                    int totelem, const char *name)
{
    const LayerTypeInfo *typeInfo = layerType_getInfo(type);
    const size_t size = (size_t)totelem * typeInfo->size; [5]

    ...
    else if (size > 0) {
        if (alloctype == CD_DUPLICATE && layerdata) {
            newlayerdata = MEM_mallocN(size, layerType_getName(type));
        }
        else {
            newlayerdata = MEM_callocN(size, layerType_getName(type)); [6]
        }

        if (!newlayerdata)
            return NULL;
    }
    ...
    data->layers[index].type = type;
    data->layers[index].flag = flag;
    data->layers[index].data = newlayerdata; [6]
    ...
    return &data->layers[index]; [7]

This overflown size value is used in the MEM_callocN call [6]. This newly created allocation is then set in the layers.data element [7] and is then returned back to the call of customdata_version_242. This layer is then filled by leveraging old tface data [8].

    source/blender/blenkernel/intern/customdata.c:1841
    me->mcol = CustomData_add_layer(&me->fdata, CD_MCOL, CD_CALLOC, NULL, me->totface);
    me->mtface = CustomData_add_layer(&me->fdata, CD_MTFACE, CD_CALLOC, NULL, me->totface);

    mtf = me->mtface;
    mcol = me->mcol;
    tf = me->tface;

    for (a = 0; a < me->totface; a++, mtf++, tf++, mcol += 4) {
        memcpy(mcol, tf->col, sizeof(tf->col)); [8]
        memcpy(mtf->uv, tf->uv, sizeof(tf->uv)); [8]

        mtf->flag = tf->flag;
        mtf->unwrap = tf->unwrap;
        mtf->mode = tf->mode;
        mtf->tile = tf->tile;
        mtf->tpage = tf->tpage;
        mtf->transp = tf->transp;
    }

Because the allocated segments are too small to hold the requested data, the allocations are overflown causing a heap corruptions, potentially leading to code execution.

Crash Information

Initial allocation before overflow

eax=07a58acc ebx=07a58bb4 ecx=07a58acc edx=07a57448 esi=07aea40c edi=07a57444
eip=00a96cc0 esp=0022f918 ebp=0022f928 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
blender!PyInit_mathutils_noise_types+0x148240:
00a96cc0 f30f6f4220      movdqu  xmm0,xmmword ptr [edx+20h] ds:0023:07a57468=deadbeefdeadbeefdeadbeefdeadbeef

Return from customdata_add_layer with CD_MCOL
07948bb4  00000000 00000000 00000000 00000000  ................
07948bc4  abababab abababab feeefeee 00000000  ................
07948bd4  00000000 63385a14 0032ab6b 07948ac8  .....Z8ck.2.....
07948be4  07948cd0 feeefeee feeefeee feeefeee  ................
07948bf4  feeefeee feeefeee feeefeee feeefeee  ................
07948c04  feeefeee 773b5a03 1c32ab6b 00000068  .....Z;wk.2.h...
07948c14  07948cfc 07948b2c 07d52104 07d5b814  ....,....!......
07948c24  00000000 00000000 6873654d 00000000  ........Mesh....

address 07948bb4 found in
    _HEAP @ 3ee0000
        HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
            07948ba8 0006 0000  [00]   07948bb0    00014 - (busy)

State of the allocation after partial overflow

Breakpoint 2 hit
eax=00000003 ebx=07a58be4 ecx=07a58b50 edx=07a574fc esi=07aea40c edi=07a574f8
eip=00a96cc0 esp=0022f918 ebp=0022f928 iopl=0         nv up ei ng nz na po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000283
blender!PyInit_mathutils_noise_types+0x148240:
00a96cc0 f30f6f4220      movdqu  xmm0,xmmword ptr [edx+20h] ds:0023:07a5751c=deadbeefdeadbeefdeadbeefdeadbeef
0:000> dc 07a58bb4
07a58bb4  deadbeef deadbeef deadbeef deadbeef  ................
07a58bc4  deadbeef deadbeef deadbeef deadbeef  ................
07a58bd4  deadbeef deadbeef deadbeef deadbeef  ................
07a58be4  07a58cd0 feeefeee feeefeee feeefeee  ................
07a58bf4  feeefeee feeefeee feeefeee feeefeee  ................
07a58c04  feeefeee 153a09d0 1c325688 00000068  ......:..V2.h...
07a58c14  07a58cfc 07a58b2c 07e61fcc 07e6b6e4  ....,...........
07a58c24  00000000 00000000 6873654d 00000000  ........Mesh....

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, run blender with the newly created proof-of-concept.

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

Timeline

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

Credit

Discovered by Cory Duplantis and a member of Cisco Talos.