Talos Vulnerability Report

TALOS-2017-0454

Blender BKE_curve_bevelList_make Integer Overflow Code Execution Vulnerability

January 11, 2018
CVE Number

CVE-2017-12102

Summary

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.

Tested Versions

Blender v2.78c

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 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 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_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");
                    }
...

Crash Information

(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

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

Credit

Discovered by a member of Cisco Talos.