Talos Vulnerability Report

TALOS-2017-0434

Blender Object CustomData_external_read Integer Overflow Code Execution Vulnerability

January 11, 2018
CVE Number

CVE-2017-12082

Summary

An exploitable integer overflow exists in the CustomData Mesh loading functionality of the Blender open-source 3d creation suite. A .blend file with a specially crafted external data 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 edit an object within a .blend library in their Scene 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 a CustomData layer from a Mesh object within a Blender library file (.blend). When allocating space for the layer data within a Mesh, the application will perform some arithmetic which can overflow. This result will be used to perform an allocation which can allow for an undersized buffer. Later, when the application attempts to read data from the external data file into this buffer, a heap-based buffer overflow will occur.

When attempting to edit a Mesh object that has been imported into the user's scene, the application will attempt to read each layer that composes the Mesh object. One of the options of a layer is to use external data. When opening up a file, the application will iterate through all the data-blocks that use the ID_ME (0x454d0000) code type. After identifying a data-block of the Mesh, the application will execute the following code to map the pointers within the structure that was de-serialized from the data-block to the correct positions in memory. At [1], the application will fix the pointers that are stored within a CustomData structure. Inside this function at [2], the application will fix-up both the pointers for the layers field and the external fields. The layer field points to a structure CustomDataLayer. This structure contains the type of the layer as well as any flags associated with it in the flag field. If the flag field has the CD_FLAG_EXTERNAL (8) bit set, then the application will assume that the layer's contents will be stored in the external pointer. This pointer points to a CustomDataExternal structure which contains the filename to load the data from.

source/blender/blenloader/intern/readfile.c:4563
static void direct_link_mesh(FileData *fd, Mesh *mesh)
{
...
    direct_link_customdata(fd, &mesh->vdata, mesh->totvert);        // [1] \
    direct_link_customdata(fd, &mesh->edata, mesh->totedge);        // [1] \
    direct_link_customdata(fd, &mesh->fdata, mesh->totface);        // [1] \
    direct_link_customdata(fd, &mesh->ldata, mesh->totloop);        // [1] \
    direct_link_customdata(fd, &mesh->pdata, mesh->totpoly);        // [1] \
...
}
\
source/blender/blenloader/intern/readfile.c:4527
static void direct_link_customdata(FileData *fd, CustomData *data, int count)
{
...
    data->layers = newdataadr(fd, data->layers);                    // [2]
...
    data->external = newdataadr(fd, data->external);                // [3]

    while (i < data->totlayer) {
        CustomDataLayer *layer = &data->layers[i];
        if (layer->flag & CD_FLAG_EXTERNAL)
            layer->flag &= ~CD_FLAG_IN_MEMORY;
...
    }
}

Later when the application attempts to read data from thus layer such as when editing the Mesh, the application will execute the following function. This function will ensure that the external field is set [4], and that one of the layers defined in the data argument has the CD_FLAG_EXTERNAL (8) flag set [5]. If this flag is set, the application will read the filename from the structure and then convert it to an absolute path at [6]. At [7], the application will finally attempt to open up the external file.

source/blender/blenkernel/intern/customdata.c:3539
void CustomData_external_read(CustomData *data, ID *id, CustomDataMask mask, int totelem)
{
    CustomDataExternal *external = data->external;
    CustomDataLayer *layer;
    CDataFile *cdf;
    CDataFileLayer *blay;
    char filename[FILE_MAX];
    const LayerTypeInfo *typeInfo;
    int i, update = 0;

    if (!external)                                                      // [4]
        return;

    for (i = 0; i < data->totlayer; i++) {
        layer = &data->layers[i];
        typeInfo = layerType_getInfo(layer->type);
...
        else if ((layer->flag & CD_FLAG_EXTERNAL) && typeInfo->read) {  // [5]
            update = 1;
        }
    }

    if (!update)
        return;

    customdata_external_filename(filename, id, external);               // [6]

    cdf = cdf_create(CDF_TYPE_MESH);
    if (!cdf_read_open(cdf, filename)) {                                // [7]
        cdf_free(cdf);
        fprintf(stderr, "Failed to read %s layer from %s.\n", layerType_getName(layer->type), filename);
        return;
    }

    for (i = 0; i < data->totlayer; i++) {
...
    }

    cdf_read_close(cdf);
}

To open up the file, the application will execute the cdf_read_open function. This function will open up a stream to the file, and then hand it off to the cdf_read_header function [8]. At the beginning of this function, the application will read the header [9], followed by checking it's header for it's fingerprint and version number at [10]. Once the byte order is determined, the application will then validate at [11] that the header.type field is of either CDFTYPEIMAGE(0) or CDFTYPEMESH(1). At [12], depending on this type the application will read a CDataFileImageHeader, or a CDataFileMeshHeader from the file. After this is done, the application will attempt to read the number of layers embedded within this file. To allocate space for this, the application will read the totlayer field from the header and multiply it by the size of a CDataFileLayer structure [13]. This structure has a size of 84-bytes. If the product of the totlayer field and the number 84 is larger than 32-bits, then this allocation size will overflow resulting in a size that is smaller than expected. Later at [14] when the application attempts to read data from the file into this undersized buffer, a buffer overflow will occur. This can lead to code execution under the context of the application.

source/blender/blenkernel/intern/customdata_file.c:279
bool cdf_read_open(CDataFile *cdf, const char *filename)
{
    FILE *f;

    f = BLI_fopen(filename, "rb");
    if (!f)
        return 0;
...
    if (!cdf_read_header(cdf)) {                                                            // [8]
        cdf_read_close(cdf);
        return 0;
    }
...
    return 1;
}
\
source/blender/blenkernel/intern/customdata_file.c:145
static int cdf_read_header(CDataFile *cdf)
{
    CDataFileHeader *header;
    CDataFileImageHeader *image;
    CDataFileMeshHeader *mesh;
    CDataFileLayer *layer;
    FILE *f = cdf->readf;
    size_t offset = 0;
    int a;

    header = &cdf->header;

    if (!fread(header, sizeof(CDataFileHeader), 1, cdf->readf))                             // [9]
        return 0;

    if (memcmp(header->ID, "BCDF", sizeof(header->ID)) != 0)                                // [10]
        return 0;
    if (header->version > CDF_VERSION)
        return 0;

    cdf->switchendian = header->endian != cdf_endian();
    header->endian = cdf_endian();

...

    if (!ELEM(header->type, CDF_TYPE_IMAGE, CDF_TYPE_MESH))                                 // [11]
        return 0;

    offset += header->structbytes;
    header->structbytes = sizeof(CDataFileHeader);

    if (fseek(f, offset, SEEK_SET) != 0)
        return 0;

    if (header->type == CDF_TYPE_IMAGE) {                                                   // [12]
        image = &cdf->btype.image;
        if (!fread(image, sizeof(CDataFileImageHeader), 1, f))
            return 0;
...
        offset += image->structbytes;
        image->structbytes = sizeof(CDataFileImageHeader);
    }
    else if (header->type == CDF_TYPE_MESH) {
        mesh = &cdf->btype.mesh;
        if (!fread(mesh, sizeof(CDataFileMeshHeader), 1, f))
            return 0;
...
        offset += mesh->structbytes;
        mesh->structbytes = sizeof(CDataFileMeshHeader);
    }

    if (fseek(f, offset, SEEK_SET) != 0)
        return 0;

    cdf->layer = MEM_callocN(sizeof(CDataFileLayer) * header->totlayer, "CDataFileLayer");  // [13]
...
    for (a = 0; a < header->totlayer; a++) {
        layer = &cdf->layer[a];

        if (!fread(layer, sizeof(CDataFileLayer), 1, f))                                    // [14]
            return 0;
...
        if (layer->datatype != CDF_DATA_FLOAT)
            return 0;

        offset += layer->structbytes;
        layer->structbytes = sizeof(CDataFileLayer);

        if (fseek(f, offset, SEEK_SET) != 0)
            return 0;
    }
...
    return 1;
}

Crash Information

(1e5c.18f4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=16a03054 ebx=00000057 ecx=00000050 edx=00000053 esi=16a03004 edi=33ff1000
eip=0249af2a esp=0444ef64 ebp=0444ef84 iopl=0         nv up ei pl nz na pe cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00210207
blender!xmlListWalk+0x2181aa:

0249af2a f3a4            rep movs byte ptr es:[edi],byte ptr [esi]
0:000> !heap -p -a @edi
    address 33ff1000 found in
    _DPH_HEAP_ROOT @ 8ec1000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                306903dc:         33ff0ff8                4 -         33ff0000             2000
0:000> dc @esi
16a03004  00000000 00000000 00000000 00000000  ................
16a03014  00000000 00000000 00000000 00000000  ................
16a03024  00000000 00000000 00000000 00000000  ................
16a03034  00000000 00000000 00000000 00000000  ................
16a03044  00000000 00000000 00000000 00000000  ................
16a03054  00000000 00000000 00000000 00000000  ................
16a03064  00000000 00000000 c0c0c0c0 c0c0c0c0  ................
16a03074  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0  ................

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. This will also generate a file that will be imported as the CustomData layer with the suffix .data.

$ python poc.py $FILENAME.blend

To trigger the vulnerability, one can import the Mesh object from the .blend file into their scene. Once the user tries to edit or interact with the Object that utilizes the Mesh object, the application will crash.

$ /path/to/blender.exe

# Hit Shift+F1 to import a .blend file as a library
# Navigate to the `Object` that one wishes to import and select it.
# Create an instance of the specified `Object` in the scene.
# Hit Enter to choose Edit-Mode

Mitigation

In order to mitigate this vulnerability, it is recommended to not use untrusted library files in their Scene.

Timeline

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

Credit

Discovered by a member of Cisco Talos.