CVE-2017-12105
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.
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 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 Object
s that compose the Scene
. These Object
s 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,
(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
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
In order to mitigate this vulnerability, it is recommended to not use untrusted .blend files within the application.
2017-09-27 - Vendor Disclosure
2018-01-11 - Public Release
Discovered by a member of Cisco Talos.