Talos Vulnerability Report

TALOS-2017-0452

Blender multires_load_old_dm base vertex map Integer Overflow Code Execution Vulnerability

January 11, 2018
CVE Number

CVE-2017-12100

Summary

An exploitable integer overflow exists in the multires_load_old_dm 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 a .blend file in order to trigger this vulnerability.

Tested Versions

Blender v2.78c (32-bit)

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 occurs when loading an old Multires structure from a Mesh into a newer format. When handling an older version of a .blend file, the application will call a function to initialize the base vertices used for multi-resolution meshes. When allocating space for these vertices, the application will use the total number of vertices in some arithmetic which can overflow. This will then be used to perform an allocation which can be made to be smaller than the total number of vertices used to initialize an array. When initializing this array, the application can then write outside the bounds of the array which causes a heap-based buffer overflow.

After loading all the basic-blocks in a file, the application will call the blo_do_versions_250 function. This function will check the version of the file as specified in the FileGlobals structure and use it to perform various transformations on the data-structures in the file in order to provide backwards compatibility. At [1], the application will check if the version is less than or equal to 250 and that the subversionfile field is less than 1 (exclusive). After this is verified, the application will iterate through all the Object data-blocks in the file and at [2] check to see if its type field is set to OB_MESH(1). If so, then the object and its associated Mesh that's pointed to by the data field is then passed the multires_load_old function at [3].

source/blender/blenloader/intern/versioning_250.c:732
void blo_do_versions_250(FileData *fd, Library *lib, Main *main)
{
...
    if (main->versionfile < 250 || (main->versionfile == 250 && main->subversionfile < 1)) {    // [1]
...
        for (ob = main->object.first; ob; ob = ob->id.next) {
...
            if (ob->type == OB_MESH) {                                                          // [2]
                Mesh *me = blo_do_versions_newlibadr(fd, lib, ob->data);
                void *olddata = ob->data;
                ob->data = me;
...
                if (me && me->id.lib == NULL && me->mr && me->mr->level_count > 1) {
                    multires_load_old(ob, me);                                                  // [3]
                }

                ob->data = olddata;
            }
...

Once inside the multires_load_old function, at [4] the application will assign a pointer to a Multires structure from the Mesh structure's mr field. Dereferencing the levels field results in a MultiresData structure that contains a number of fields that are re-assigned to the Mesh structure. In these fields, the value of totvert is significant to note as it is used to trigger this vulnerability. Once re-assigning the fields at [5], the application will then convert the faces defined by the Mesh into polygons which is then used to create a DerivedMesh object. This and the Mesh itself are passed as arguments to multires_load_old_dm at [6].

source/blender/blenkernel/intern/multires.c:2064
void multires_load_old(Object *ob, Mesh *me)
{
...
    lvl = me->mr->levels.first;                     // [4]
...
    me->totvert = lvl->totvert;
    me->totedge = lvl->totedge;
    me->totface = lvl->totface;
...
    multires_load_old_vcols(me);
    multires_load_old_face_flags(me);

    /* multiresModifier_subdivide (actually, multires_subdivide) expects polys, not tessfaces! */
    BKE_mesh_convert_mfaces_to_mpolys(me);          // [5]
...
    orig = CDDM_from_mesh(me);
...
    dm = multires_make_derived_from_derived(orig, mmd, ob, 0);

    multires_load_old_dm(dm, me, mmd->totlvl + 1);  // [6]
...

The multires_load_old_dm function is responsible for converting the old Multires-formatted structure into its newer format. At [7], the application will assign the mr field belonging to the Mesh to a pointer. At [8], the application will extract the total number of vertices defined within the DerivedMesh that was made earlier using the fields from the Mesh structure and assign the result to the totvert variable. Once this is done, at [9] the application will use this variable to calculate a product using the size of an int (4) with the number of vertices and pass this as a size for an allocation. If the result of this arithmetic has a result larger than a 32-bit number, then this allocation will overflow resulting in an undersized buffer. At [10], when the application attempts to initialize this array, the application will use the difference between the total number of vertices in the first MultiresData and the total number of vertices in the second MultiresData from the file to write into the array. During this, an out-of-bounds write can occur resulting in a heap-based buffer overflow. Within the provided proof-of-concept, the value used for the level 1 totvert field is 1. This type of overwrite can allow for code execution within the context of the application.

source/blender/blenkernel/intern/multires.c:1851
static void multires_load_old_dm(DerivedMesh *dm, Mesh *me, int totlvl)
{
    MultiresLevel *lvl, *lvl1;
    Multires *mr = me->mr;                                          // [7]
...
    vsrc = mr->verts;
    vdst = dm->getVertArray(dm);
    totvert = (unsigned int)dm->getNumVerts(dm);                    // [8]
    vvmap = MEM_callocN(sizeof(int) * totvert, "multires vvmap");   // [9]

    lvl1 = mr->levels.first;
    /* Load base verts */
    for (i = 0; i < lvl1->totvert; ++i) {
        vvmap[totvert - lvl1->totvert + i] = src;                   // [10]
        src++;
    }

Crash Information

(2a50.2a9c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=00000001 ecx=00000002 edx=328d6ffc esi=329c6fd4 edi=02da1710
eip=014aa00b esp=002fe8f8 ebp=002fe968 iopl=0         nv up ei pl nz na po cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010203
blender!PyInit_mathutils_noise_types+0x28b58b:
014aa00b 891c82          mov     dword ptr [edx+eax*4],ebx ds:002b:328d7000=????????

0:000> !heap -p -a @edx
    address 328d6ffc found in
    _DPH_HEAP_ROOT @ 4a1000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                328612a4:         328d6ff8                4 -         328d6000             2000
    6b7e8d9c verifier!AVrfDebugPageHeapAllocate+0x0000023c
    77d6fb79 ntdll!RtlDebugAllocateHeap+0x00000032
    77d09a73 ntdll!RtlpAllocateHeap+0x0003914a
    77cd0b43 ntdll!RtlAllocateHeap+0x0000014c
    0292f5b3 blender!xmlGetThreadId+0x0000c0b3
    02915fc1 blender!xmlListWalk+0x00223241
    017cd129 blender!osl_texture_set_swrap_code+0x00171e99
    014a9fe2 blender!PyInit_mathutils_noise_types+0x0028b562
    014a9dbb blender!PyInit_mathutils_noise_types+0x0028b33b
    013681b4 blender!PyInit_mathutils_noise_types+0x00149734
    013582f0 blender!PyInit_mathutils_noise_types+0x00139870
    0134ee74 blender!PyInit_mathutils_noise_types+0x001303f4
    01361989 blender!PyInit_mathutils_noise_types+0x00142f09
    01401fab blender!PyInit_mathutils_noise_types+0x001e352b
    00e6ddca blender!xmlFileMatch+0x000078fa
    00e6e88f blender!xmlFileMatch+0x000083bf
    00e6f7b1 blender!xmlFileMatch+0x000092e1
    00e7bd3f blender!xmlFileMatch+0x0001586f
    00e7c8c5 blender!xmlFileMatch+0x000163f5
    00e7c4a6 blender!xmlFileMatch+0x00015fd6
    00e7abfa blender!xmlFileMatch+0x0001472a
    00e6c583 blender!xmlFileMatch+0x000060b3
    00e69b1b blender!xmlFileMatch+0x0000364b
    0290a125 blender!xmlListWalk+0x002173a5
    75f7919f KERNEL32!BaseThreadInitThunk+0x0000000e
    77cda8cb ntdll!__RtlUserThreadStart+0x00000020
    77cda8a1 ntdll!_RtlUserThreadStart+0x0000001b

0:000> lm a blender.exe
Browse full module list
start    end        module name
00cd0000 04638000   blender  C (export symbols)       blender.exe

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.zip $FILENAME.blend

To trigger the vulnerability, one can simply open the file or use it as a library. It can also be passed as an argument to the blender executable.

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

Mitigation

In order to mitigate this vulnerability, it is recommended to not use untrusted blender files.

Timeline

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

Credit

Discovered by a member of Cisco Talos.