Talos Vulnerability Report

TALOS-2017-0455

Blender BKE_vfont_to_curve_ex Integer Overflow Code Execution Vulnerability

January 11, 2018
CVE Number

CVE-2017-12103

Summary

An exploitable integer overflow exists in the way that the Blender open-source 3d creation suite v2.78c converts text rendered as a font into a curve. 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 rendered text into a Curve when editing a Scene. When allocating space for wide-character form of text to be rendered, the application will perform some arithmetic which can overflow. This result will be used to perform an allocation which can allow for an undersized buffer due to the overflow. Immediately after, when the application tries to convert the UTF-8 string to wide-character within 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. Within the provided proof-of-concept is a Curve object which actually describes text which is to be rendered. 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_FONT(4) 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_FONT(4), the application will choose the next case which will call the BKE_vfont_to_curve_nubase function [9]. This function is simply a wrapper around the BKE_vfont_to_curve_ex function [10]. The BKE_vfont_to_curve_ex function is the function directly responsible for the vulnerability described in 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) {
            BKE_vfont_to_curve_nubase(G.main, ob, FO_EDIT, &nubase);        // [9]
        }
...
\
source/blender/blenkernel/intern/font.c:1312
bool BKE_vfont_to_curve_nubase(Main *bmain, Object *ob, int mode, ListBase *r_nubase)
{
    BLI_assert(ob->type == OB_FONT);

    return BKE_vfont_to_curve_ex(bmain, ob, mode, r_nubase,                 // [10]
                                 NULL, NULL, NULL, NULL);
}

Now within the BKE_vfont_to_curve_ex function, the application will again grab the Curve structure out of the ob->data field as well as dereference the cu->editfont field from the Curve structure. At [11], the application will check to see if the cu->editfont field is defined or not. Due to this being the first time that the application is interacting with this structure, the field will result as NULL. This causes the application to proceed to initialize an EditFont structure. At [12], the application will grab the len_wchar field from the Curve structure and assign it to the slen variable. This variable is used by the allocation at [13] and is directly responsible for triggering the vulnerability. The application will take the slen variable, add 1 to it, and then multiply it by the size of a wchar_t or 2 in order to allocate enough space for converting a UTF-8 string into a wide-char string. It the result of this arithmetic is larger than 32-bits, then an overflow will occur which can result in an undersized buffer. The provided proof-of-concept specifies cu->len_wchar as 2147483647 (0x7fffffff) which results in a zero-sized allocation. To convert this string, the application will then use the cu->str field from the Curve structure and the slen variable as arguments to the BLI_strncpy_wchar_from_utf8 function. This happens at [14].

source/blender/blenkernel/intern/font.c:621
bool BKE_vfont_to_curve_ex(Main *bmain, Object *ob, int mode, ListBase *r_nubase,
                           const wchar_t **r_text, int *r_text_len, bool *r_text_free,
                           struct CharTrans **r_chartransdata)
{
    Curve *cu = ob->data;
    EditFont *ef = cu->editfont;
...
    if (ef) {                                                                   // [11]
        slen = ef->len;
        mem = ef->textbuf;
        custrinfo = ef->textbufinfo;
    }
    else {
        wchar_t *mem_tmp;
        slen = cu->len_wchar;                                                   // [12]

        /* Create unicode string */
        mem_tmp = MEM_mallocN(((slen + 1) * sizeof(wchar_t)), "convertedmem");  // [13] XXX

        BLI_strncpy_wchar_from_utf8(mem_tmp, cu->str, slen + 1);                // [14]

        if (cu->strinfo == NULL) {  /* old file */
            cu->strinfo = MEM_callocN((slen + 4) * sizeof(CharInfo), "strinfo compat");
        }
        custrinfo = cu->strinfo;

        mem = mem_tmp;
    }

The BLI_strncpy_wchar_from_utf8 function will explicitly trust the maxncpy argument and use it to terminate the loop at [15]. This loop will iterate through each UTF-8 character pointed to by the Curve's str field and hand it off to the BLI_str_utf8_as_unicode_and_size function. This function will determine the size of the UTF-8 character, and return the character as a unicode glyph. Each glyph will then be written to the undersized buffer at [16] causing a buffer overflow. This can lead to code execution under the context of the application.

source/blender/blenlib/intern/string_utf8.c:348
size_t BLI_strncpy_wchar_from_utf8(wchar_t *__restrict dst_w, const char *__restrict src_c, const size_t maxncpy)
{
    const size_t maxlen = maxncpy - 1;
    size_t len = 0;

...
    while (*src_c && len != maxlen) {                           // [15]
        size_t step = 0;
        unsigned int unicode = BLI_str_utf8_as_unicode_and_size(src_c, &step);
        if (unicode != BLI_UTF8_ERR) {
            *dst_w = (wchar_t)unicode;                          // [16] XXX
            src_c += step;
        }
        else {
            *dst_w = '?';                                       // [16] XXX
            src_c = BLI_str_find_next_char_utf8(src_c, NULL);
        }
        dst_w++;
        len++;
    }

    *dst_w = 0;                                                 // [16] XXX

    return len;
}

Crash Information

(1f88.2a6c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000032 ebx=2fcceb32 ecx=00aef9a4 edx=00000032 esi=304aafff edi=337c3000
eip=0163b3a0 esp=00aef98c ebp=00aef99c 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!osl_texture_set_interp_code+0x114ba0:
0163b3a0 668907          mov     word ptr [edi],ax        ds:002b:337c3000=????

0:000> !heap -p -a @edi
    address 337c3000 found in
    _DPH_HEAP_ROOT @ b91000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                2e723000:         337c2ff8                4 -         337c2000             2000
    6b818d9c verifier!AVrfDebugPageHeapAllocate+0x0000023c
    77d6fb79 ntdll!RtlDebugAllocateHeap+0x00000032
    77d09a73 ntdll!RtlpAllocateHeap+0x0003914a
    77cd0b43 ntdll!RtlAllocateHeap+0x0000014c
    02907fbf blender!xmlListWalk+0x0021523f
    017cd347 blender!osl_texture_set_swrap_code+0x001720b7
    013bcf90 blender!PyInit_mathutils_noise_types+0x0019e510
    013bea6c blender!PyInit_mathutils_noise_types+0x0019ffec
    0142e8d8 blender!PyInit_mathutils_noise_types+0x0020fe58
    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
    0165b716 blender!osl_texture_set_swrap_code+0x00000486
    013e7c54 blender!PyInit_mathutils_noise_types+0x001c91d4
    013e6f55 blender!PyInit_mathutils_noise_types+0x001c84d5
    00e7b414 blender!xmlFileMatch+0x00014f44
    00e6c589 blender!xmlFileMatch+0x000060b9
    00e69b1b blender!xmlFileMatch+0x0000364b
    0290a125 blender!xmlListWalk+0x002173a5
    75f7919f KERNEL32!BaseThreadInitThunk+0x0000000e
    77cda8cb ntdll!__RtlUserThreadStart+0x00000020
    77cda8a1 ntdll!_RtlUserThreadStart+0x0000001b

0:000> ub .
blender!osl_texture_set_interp_code+0x114b87:
0163b387 c7450800000000  mov     dword ptr [ebp+8],0
0163b38e 50              push    eax
0163b38f 56              push    esi
0163b390 e80bf7ffff      call    blender!osl_texture_set_interp_code+0x1142a0 (0163aaa0)
0163b395 83c408          add     esp,8
0163b398 83f8ff          cmp     eax,0FFFFFFFFh
0163b39b 7408            je      blender!osl_texture_set_interp_code+0x114ba5 (0163b3a5)
0163b39d 037508          add     esi,dword ptr [ebp+8]

0:000> lm a blender.exe
Browse full module list
start    end        module name
00cd0000 04638000   blender  C (export symbols)       \path\to\blender\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 in the application.

Timeline

2017-09-27 - Vendor Disclosure
2018-01-11 - Public Release

Credit

Discovered by a member of Cisco Talos.