CVE-2017-12103
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.
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 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 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_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;
}
(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
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 in the application.
2017-09-27 - Vendor Disclosure
2018-01-11 - Public Release
Discovered by a member of Cisco Talos.