CVE-2017-12100
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.
Blender v2.78c (32-bit)
http://www.blender.org git://git.blender.org/blender.git
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE-190 - Integer Overflow or Wraparound
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++;
}
(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
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
In order to mitigate this vulnerability, it is recommended to not use untrusted blender files.
2017-09-27 - Vendor Disclosure
2018-01-11 - Public Release
Discovered by a member of Cisco Talos.