Headline
CVE-2017-12105: TALOS-2017-0457 || Cisco Talos Intelligence Group
An exploitable integer overflow exists in the way that the Blender open-source 3d creation suite v2.78c applies a particular object modifier to a Mesh. 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 the file as a library in order to trigger this vulnerability.
Summary
An exploitable integer overflow exists in the way that the Blender open-source 3d creation suite v2.78c applies a particular object modifier to a Mesh. 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 the file as a library 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 exists with how the Blender application applies a specific modifier type to a Mesh. When allocating space for the number of vertices in a Mesh in order to apply the modifier, the application will perform some arithmetic which can overflow. This result will then be used to perform an allocation which can allow for the heap-buffer to be undersized. When the application tries to write values into this undersized buffer, a heap-based buffer overflow will occur.
After loading a .blend file, the application will immediately try to update the scene and all of it’s objects in order to render it in the user-interface. When the application opens the file, the Scene that was specified in the FileGlobals structure will be used to determine the entry-point of the project. Within this Scene, the default World will be specified along with the list of Objects that compose the Scene. These Objects are wrapped in a Base structure. After opening up a .blend file, the application will proceed to update the scene in the rendering viewport using the BKE_scene_update_tagged function. This function will update all the objects within a Scene that have the DOIT flag specified in it’s tag field. To do this, the application will recurse through all the objects and call a number of functions at [1], [2], [3], and [4]. Once [4] is reached, the application will have pushed a function pointer to the scene_update_object_func function into it’s task-scheduler’s stack. Finally at [5], the BKE_object_handle_update_ex is called.
source/blender/blenkernel/intern/scene.c:1765
void BKE_scene_update_tagged(EvaluationContext *eval_ctx, Main *bmain, Scene *scene)
{
...
if (!use_new_eval) {
scene_update_tagged_recursive(eval_ctx, bmain, scene, scene); // [1] \
}
...
\
source/blender/blenkernel/intern/scene.c:1697
static void scene_update_tagged_recursive(EvaluationContext *eval_ctx, Main *bmain, Scene *scene, Scene *scene_parent)
{
...
scene_update_objects(eval_ctx, bmain, scene, scene_parent); // [2] \
...
\
source/blender/blenkernel/intern/scene.c:1624
static void scene_update_objects(EvaluationContext *eval_ctx, Main *bmain, Scene *scene, Scene *scene_parent)
{
...
DAG_threaded_update_begin(scene, scene_update_object_add_task, task_pool); // [3] \
...
\
source/blender/blenkernel/intern/scene.c:1526
static void scene_update_object_add_task(void *node, void *user_data)
{
TaskPool *task_pool = user_data;
BLI_task_pool_push(task_pool, scene_update_object_func, node, false, TASK_PRIORITY_LOW); // [4] \
}
\
source/blender/blenkernel/intern/scene.c:1460
static void scene_update_object_func(TaskPool * __restrict pool, void *taskdata, int threadid)
{
...
/* We only update object itself here, dupli-group will be updated
* separately from main thread because of we've got no idea about
* dependencies inside the group.
*/
BKE_object_handle_update_ex(eval_ctx, scene_parent, object, scene->rigidbody_world, false); // [5]
...
Inside the BKE_object_handle_update_ex function, the application will check if the object has data that needs to be recalculated [6]. To do this, it will call the BKE_object_handle_data_update function which will then determine the type of the object. If the type is an OB_MESH(1) then it will call the makeDerivedMesh function at [7]. This function is simply a wrapper around a function responsible for building the data for the function. At [8], the makeDerivedMesh function will call mesh_build_data.
source/blender/blenkernel/intern/object.c:2637
void BKE_object_handle_update_ex(EvaluationContext *eval_ctx,
Scene *scene, Object *ob,
RigidBodyWorld *rbw,
const bool do_proxy_update)
{
...
if (ob->recalc & OB_RECALC_DATA) {
BKE_object_handle_data_update(eval_ctx, scene, ob); // [6] \
}
...
\
source/blender/blenkernel/intern/object_update.c:159
void BKE_object_handle_data_update(EvaluationContext *eval_ctx,
Scene *scene,
Object *ob)
{
...
switch (ob->type) {
...
case OB_MESH:
{
BMEditMesh *em = (ob == scene->obedit) ? BKE_editmesh_from_object(ob) : NULL;
...
if (em) {
makeDerivedMesh(scene, ob, em, data_mask, false); // [7] \
}
else {
makeDerivedMesh(scene, ob, NULL, data_mask, false); // [7] \
}
break;
}
...
\
source/blender/blenkernel/intern/DerivedMesh.c:2697
void makeDerivedMesh(
Scene *scene, Object *ob, BMEditMesh *em,
CustomDataMask dataMask, const bool build_shapekey_layers)
{
...
if (em) {
...
}
else {
mesh_build_data(scene, ob, dataMask, build_shapekey_layers, need_mapping); // [8]
}
}
The mesh_build_data function is primarily a wrapper around the mesh_calc_modifiers function at [9]. Once inside the mesh_calc_modifiers function, the application will first grab the Mesh structure from the Object at [10]. After assigning some default variables, the application will then enter a loop which iterate through all of the ModifierData objects [11] associated with the Object that’s being processed. The application will then check to see that the modifier is enabled for the particular scene [12], and then check to see if the Object modifier’s type-info is set to eModifierTypeType_OnlyDeform at [13]. With the provided proof-of-concept, the Object modifier that was used to support this value was a ModifierData type of eModifierType_MeshDeform which results in the modifier having the structure of a MeshDeformModifierData. At the end of this advisory, a list of the ModifierData types with this particular attribute will be enumerated. Finally at [14], the BKE_mesh_vertexCos_get function (which contains the vulnerability) is called.
source/blender/blenkernel/intern/DerivedMesh.c:2596
static void mesh_build_data(
Scene *scene, Object *ob, CustomDataMask dataMask,
const bool build_shapekey_layers, const bool need_mapping)
{
...
mesh_calc_modifiers( // [9] \
scene, ob, NULL, false, 1, need_mapping, dataMask, -1, true, build_shapekey_layers,
true,
&ob->derivedDeform, &ob->derivedFinal);
...
}
\
source/blender/blenkernel/intern/DerivedMesh.c:1730
static void mesh_calc_modifiers(
Scene *scene, Object *ob, float (*inputVertexCos)[3],
const bool useRenderParams, int useDeform,
const bool need_mapping, CustomDataMask dataMask,
const int index, const bool useCache, const bool build_shapekey_layers,
const bool allow_gpu,
/* return args */
DerivedMesh **r_deform, DerivedMesh **r_final)
{
Mesh *me = ob->data; // [10]
...
int numVerts = me->totvert;
const int required_mode = useRenderParams ? eModifierMode_Render : eModifierMode_Realtime;
...
const bool sculpt_mode = ob->mode & OB_MODE_SCULPT && ob->sculpt && !useRenderParams;
const bool sculpt_dyntopo = (sculpt_mode && ob->sculpt->bm) && !useRenderParams;
...
if (useDeform) {
if (inputVertexCos)
...
for (; md; md = md->next, curr = curr->next) { // [11]
const ModifierTypeInfo *mti = modifierType_getInfo(md->type);
md->scene = scene;
if (!modifier_isEnabled(scene, md, required_mode)) { // [12]
continue;
}
if (useDeform < 0 && mti->dependsOnTime && mti->dependsOnTime(md)) {
continue;
}
if (mti->type == eModifierTypeType_OnlyDeform && !sculpt_dyntopo) { // [13]
if (!deformedVerts)
deformedVerts = BKE_mesh_vertexCos_get(me, &numVerts); // [14]
modwrap_deformVerts(md, ob, NULL, deformedVerts, numVerts, deform_app_flags);
}
...
}
Inside the BKE_mesh_vertexCos_get function, the application will first grab the number of vertices out of the Mesh and assign it to the numVerts variable. This variable will be used in the allocation at [15] to allocate an array of float[3] elements. If the product of the size of a float[3] (12 bytes) and the number of vertices stored in numVerts is larger than 32-bits, then this arithmetic can overflow. When this result is used in the allocation, an undersized buffer will be allocated on the heap. Later at [16], when the application attempts to copy the vertex coordinates from the Mesh into this array, a heap-based buffer overflow will occur. This will write outside the bounds of the buffer which can allow for code execution under the context of the application.
source/blender/blenkernel/intern/mesh.c:1717
float (*BKE_mesh_vertexCos_get(const Mesh *me, int *r_numVerts))[3]
{
int i, numVerts = me->totvert;
float (*cos)[3] = MEM_mallocN(sizeof(*cos) * numVerts, "vertexcos1"); // [15]
if (r_numVerts) *r_numVerts = numVerts;
for (i = 0; i < numVerts; i++)
copy_v3_v3(cos[i], me->mvert[i].co); // [16]
return cos;
}
As described previously, there are a handful of ModifierData types that result in the mti->type field being set to eModifierTypeType_OnlyDeform which is required to trigger this vulnerability. To enter the vulnerable case, the ModifierData->type field must be set to one of the following types.
source/blender/makesdna/DNA_modifier_types.h:35
typedef enum ModifierType {
...
eModifierType_Lattice = 2,
eModifierType_Curve = 3,
...
eModifierType_Wave = 7,
eModifierType_Armature = 8,
eModifierType_Hook = 9,
eModifierType_Softbody = 10,
...
eModifierType_Displace = 14,
...
eModifierType_Smooth = 16,
eModifierType_Cast = 17,
eModifierType_MeshDeform = 18,
eModifierType_ParticleSystem = 19,
...
eModifierType_Cloth = 22,
eModifierType_Collision = 23,
...
eModifierType_Shrinkwrap = 25,
...
eModifierType_SimpleDeform = 28,
...
eModifierType_Surface = 30,
...
eModifierType_ShapeKey = 32,
...
eModifierType_Warp = 35,
...
eModifierType_LaplacianSmooth = 43,
...
eModifierType_MeshCache = 46,
eModifierType_LaplacianDeform = 47,
...
eModifierType_CorrectiveSmooth = 51,
Crash Information
(3a54.3084): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=42424242 ebx=15a0db1c ecx=1d2b0f4c edx=00000000 esi=10000000 edi=1b327040
eip=018e0143 esp=214df798 ebp=214df7b0 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
blender!PyInit_mathutils_noise_types+0x2616c3:
018e0143 8947c0 mov dword ptr [edi-40h],eax ds:002b:1b327000=????????
0:015> !heap -p -a @edi-40
address 1b327000 found in
_DPH_HEAP_ROOT @ 8c1000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
1b4a109c: 1b326ff8 4 - 1b326000 2000
6ace8d9c verifier!AVrfDebugPageHeapAllocate+0x0000023c
77d6fb79 ntdll!RtlDebugAllocateHeap+0x00000032
77d09a73 ntdll!RtlpAllocateHeap+0x0003914a
77cd0b43 ntdll!RtlAllocateHeap+0x0000014c
02d67fbf blender!xmlListWalk+0x0021523f
01c2d347 blender!osl_texture_set_swrap_code+0x001720b7
018e00f7 blender!PyInit_mathutils_noise_types+0x00261677
018d8ae6 blender!PyInit_mathutils_noise_types+0x0025a066
018d86a9 blender!PyInit_mathutils_noise_types+0x00259c29
018d858d blender!PyInit_mathutils_noise_types+0x00259b0d
0196af04 blender!PyInit_mathutils_noise_types+0x002ec484
018a14c3 blender!PyInit_mathutils_noise_types+0x00222a43
01847b44 blender!PyInit_mathutils_noise_types+0x001c90c4
01abc157 blender!osl_texture_set_swrap_code+0x00000ec7
10001e05 pthreadVC2!pthread_setcanceltype+0x00000b30
756f0bc4 msvcrt!_beginthreadex+0x000000c9
756f0cec msvcrt!_endthreadex+0x0000008a
75f7919f KERNEL32!BaseThreadInitThunk+0x0000000e
77cda8cb ntdll!__RtlUserThreadStart+0x00000020
77cda8a1 ntdll!_RtlUserThreadStart+0x0000001b
0:015> dc @edi-40 - 8
1b326ff8 00000000 41414141 ???????? ???????? ....AAAA????????
1b327008 ???????? ???????? ???????? ???????? ????????????????
1b327018 ???????? ???????? ???????? ???????? ????????????????
1b327028 ???????? ???????? ???????? ???????? ????????????????
1b327038 ???????? ???????? ???????? ???????? ????????????????
1b327048 ???????? ???????? ???????? ???????? ????????????????
1b327058 ???????? ???????? ???????? ???????? ????????????????
1b327068 ???????? ???????? ???????? ???????? ????????????????
0:015> kv
# ChildEBP RetAddr Args to Child
00 214df7b0 018d8ae6 1b326ffc 00000000 211fcfa4 blender!PyInit_mathutils_noise_types+0x2616c3
01 214dfad4 018d86a9 1ae6eba4 211fcb64 00000000 blender!PyInit_mathutils_noise_types+0x25a066
02 214dfb24 018d858d 1ae6eba4 211fcb64 26000009 blender!PyInit_mathutils_noise_types+0x259c29
03 214dfb50 0196af04 1ae6eba4 211fcb64 00000000 blender!PyInit_mathutils_noise_types+0x259b0d
04 214dfb80 018a14c3 1ae1fff4 1ae6eba4 00000000 blender!PyInit_mathutils_noise_types+0x2ec484
05 214dfbec 01847b44 1ae1fff4 1ae6eba4 211fcb64 blender!PyInit_mathutils_noise_types+0x222a43
06 214dfc30 01abc157 19f0ebcc 13718fa4 00000001 blender!PyInit_mathutils_noise_types+0x1c90c4
07 214dfc54 10001e05 1d0fcfe4 00000000 214dfcb0 blender!osl_texture_set_swrap_code+0xec7
08 214dfc78 756f0bc4 00b80970 61b8f0ae 00000000 pthreadVC2!pthread_setcanceltype+0xb30
09 214dfcb0 756f0cec 214dfcc4 75f7919f 00b809b8 msvcrt!_beginthreadex+0xc9 (FPO: [Non-Fpo])
0a 214dfcb8 75f7919f 00b809b8 214dfd08 77cda8cb msvcrt!_endthreadex+0x8a (FPO: [Non-Fpo])
0b 214dfcc4 77cda8cb 00b809b8 6300c7ee 00000000 KERNEL32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
0c 214dfd08 77cda8a1 ffffffff 77ccf684 00000000 ntdll!__RtlUserThreadStart+0x20 (FPO: [SEH])
0d 214dfd18 00000000 756f0ca7 00b809b8 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
0:015> lm a blender.exe
Browse full module list
start end module name
01130000 04a98000 blender C (export symbols) \path\to\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, treat it as a library and import the Curve object, or pass it 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 .blend files within the application.
Timeline
2017-09-27 - Vendor Disclosure
2018-01-11 - Public Release
Discovered by a member of Cisco Talos.