CVE-2017-12102
An exploitable integer overflow exists in the way that the Blender open-source 3d creation suite v2.78c converts curves to polygons. 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
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 converts various curve types into polygons . When allocating space for a BevList
structure, the application will perform some arithmetic which can overflow. This result will be used to perform an allocation which can allow for the buffer to be undersized. When the application tries to write values into this 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_CURVE(2)
, OB_SURF(3)
, or OB_FONT(4)
then it will call the BKE_displist_make_curveTypes
function at [7]. The provided-proof-of-concept uses the OB_CURVE(2)
value as the object’s type
. The BKE_displist_make_curveTypes
function is simply a wrapper that ensures the object’s curve_cache
field is allocated and allocate one if it isn’t. Once this is done, at [8] the application will call the do_makeDispListCurveTypes
function.
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_CURVE:
case OB_SURF:
case OB_FONT:
BKE_displist_make_curveTypes(scene, ob, 0); // [7] \
break;
...
\
source/blender/blenkernel/intern/displist.c:1754
void BKE_displist_make_curveTypes(Scene *scene, Object *ob, const bool for_orco)
{
...
dispbase = &(ob->curve_cache->disp);
do_makeDispListCurveTypes(scene, ob, dispbase, &ob->derivedFinal, 0, for_orco, 0); // [8]
The do_makeDispListCurveTypes
will first cast the ob->data
field into a pointer to a Curve
structure. Once this is done, the application will against use the object type to determine how to convert the object. Due to the ob->type
field being an OB_CURVE(2)
, the application will choose the next case which will call the BKE_curve_BevelList_make
function [9]. This function contains the vulnerability described by this advisory.
source/blender/blenkernel/intern/displist.c:1507
static void do_makeDispListCurveTypes(Scene *scene, Object *ob, ListBase *dispbase,
DerivedMesh **r_dm_final,
const bool for_render, const bool for_orco, const bool use_render_resolution)
{
Curve *cu = ob->data;
...
if (ob->type == OB_SURF) {
...
else if (ELEM(ob->type, OB_CURVE, OB_FONT)) {
...
if (ob->type == OB_FONT) {
...
}
else {
BKE_nurbList_duplicate(&nubase, BKE_curve_nurbs_get(cu));
}
...
BKE_curve_bevelList_make(ob, &nubase, for_render != false); // [9]
Within the BKE_curve_bevelList_make
function, the application will again grab the Curve
structure out of the ob->data
field [10]. Following this, the application will set a flag, need_seglen
, depending on whether or not the bevfac1_mapping
or bevfac2_mapping
fields belonging to the Curve
structure are set as ‘SEGMENT’ or ‘SPLINE’ [11]. Once this is determined, the application will then enter a loop which will iterate through the linked list of Nurb
structures that were passed as an argument. At [12], the application will then validate some of the fields within the Nurb
structure. Once this is done, the application will then branch to one of the cases at [13] based on the type of the Nurb
. Each one of these cases contains a similar instance of the vulnerability.
source/blender/blenkernel/intern/curve.c:2588
void BKE_curve_bevelList_make(Object *ob, ListBase *nurbs, bool for_render)
{
...
Curve *cu = ob->data; // [10]
...
const bool need_seglen = // [11]
ELEM(cu->bevfac1_mapping, CU_BEVFAC_MAP_SEGMENT, CU_BEVFAC_MAP_SPLINE) ||
ELEM(cu->bevfac2_mapping, CU_BEVFAC_MAP_SEGMENT, CU_BEVFAC_MAP_SPLINE);
...
nu = nurbs->first;
...
for (; nu; nu = nu->next) {
if (nu->hide && is_editmode)
continue;
...
if (!BKE_nurb_check_valid_u(nu)) { // [12] \
...
else {
BevPoint *bevp;
if (for_render && cu->resolu_ren != 0)
resolu = cu->resolu_ren;
else
resolu = nu->resolu;
segcount = SEGMENTSU(nu);
if (nu->type == CU_POLY) { // [13]
...
else if (nu->type == CU_BEZIER) { // [13]
...
else if (nu->type == CU_NURBS) { // [13]
...
\
source/blender/blenkernel/intern/curve.c:3994
bool BKE_nurb_check_valid_u(struct Nurb *nu)
{
if (nu->pntsu <= 1)
return false;
if (nu->type != CU_NURBS)
return true;
if (nu->pntsu < nu->orderu) return false;
if (((nu->flag & CU_NURB_CYCLIC) == 0) && (nu->flagu & CU_NURB_BEZIER)) { // type-o? nu->flag should be nu->flagu
if (nu->orderu == 4) {
if (nu->pntsu < 5)
return false;
}
else {
if (nu->orderu != 3)
return false;
}
}
return true;
}
The provided proof-of-concept exercises the CU_POLY(0)
case. This case executes the following code. At [14], the application will read the nu->pntsu
field from the Nurb
and then use this in the allocation that follows. This allocation will allocate space for the size of a BevList
of 36-bytes which includes an array of BevPoint
structures which are each 80-bytes. If the number of BevPoint
structures as specified by the nu->pntsu
field causes the expression at [14] to have a result that is larger than 32-bits, then this arithmetic will overflow which will result in an undersized allocation being made. At [15] when the application tries to write to this structure, an out-of-bounds write will occur which can lead to code execution under the context of the application.
source/blender/blenkernel/intern/curve.c:2662
len = nu->pntsu; // [14]
bl = MEM_callocN(sizeof(BevList) + len * sizeof(BevPoint), "makeBevelList2");
if (need_seglen && (nu->flagu & CU_NURB_CYCLIC) == 0) {
bl->seglen = MEM_mallocN(segcount * sizeof(float), "makeBevelList2_seglen"); // [15]
bl->segbevcount = MEM_mallocN(segcount * sizeof(int), "makeBevelList2_segbevcount");
}
...
bl->poly = (nu->flagu & CU_NURB_CYCLIC) ? 0 : -1; // [15]
bl->nr = len;
bl->dupe_nr = 0;
bl->charidx = nu->charidx;
...
The case for CU_BEZIER(1)
contains a similar vulnerability which calculates the number of BevPoint
structures using the nu->resolu
and nu->pntsu
fields [16].
source/blender/blenkernel/intern/curve.c:2705
else if (nu->type == CU_BEZIER) {
/* in case last point is not cyclic */
len = segcount * resolu + 1; // [16]
bl = MEM_callocN(sizeof(BevList) + len * sizeof(BevPoint), "makeBevelBPoints");
if (need_seglen && (nu->flagu & CU_NURB_CYCLIC) == 0) {
bl->seglen = MEM_mallocN(segcount * sizeof(float), "makeBevelBPoints_seglen");
bl->segbevcount = MEM_mallocN(segcount * sizeof(int), "makeBevelBPoints_segbevcount");
}
...
The case for CU_NURBS(4)
also contains a similar vulnerability without the addition of the number 1 as in the CU_BEZIER(1)
case [17].
source/blender/blenkernel/intern/curve.c:2841
else if (nu->type == CU_NURBS) {
if (nu->pntsv == 1) {
len = (resolu * segcount); // [17]
bl = MEM_callocN(sizeof(BevList) + len * sizeof(BevPoint), "makeBevelList3");
if (need_seglen && (nu->flagu & CU_NURB_CYCLIC) == 0) {
bl->seglen = MEM_mallocN(segcount * sizeof(float), "makeBevelList3_seglen");
bl->segbevcount = MEM_mallocN(segcount * sizeof(int), "makeBevelList3_segbevcount");
}
...
(23c4.24f4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=2fef2ba4 ecx=13dd4fbc edx=03333333 esi=1d6a1010 edi=1d6a0fec
eip=0141cde8 esp=2f41f548 ebp=2f41f5b0 iopl=0 nv up ei ng nz ac pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010296
blender!PyInit_mathutils_noise_types+0x1fe368:
0141cde8 894718 mov dword ptr [edi+18h],eax ds:002b:1d6a1004=????????
0:016> !heap -p -a @edi
address 1d6a0fec found in
_DPH_HEAP_ROOT @ 6e41000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
1bd4171c: 1d6a0fe8 18 - 1d6a0000 2000
6b818d9c 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
0141cd6f blender!PyInit_mathutils_noise_types+0x001fe2ef
0142e925 blender!PyInit_mathutils_noise_types+0x0020fea5
0142c6c2 blender!PyInit_mathutils_noise_types+0x0020dc42
0150af74 blender!PyInit_mathutils_noise_types+0x002ec4f4
014414c3 blender!PyInit_mathutils_noise_types+0x00222a43
013e7b44 blender!PyInit_mathutils_noise_types+0x001c90c4
0165c157 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:016> lm a blender.exe
Browse full module list
start end module name
00cd0000 04638000 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.