CVE-2017-2918
An exploitable integer overflow exists in the Image loading functionality of the Blender open-source 3d creation suite v2.78c. 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 it 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 allocates space for an Image type when rendering a .blend file. When allocating space for the Image, the application will perform some arithmetic which can overflow. This result will then be used to perform an allocation which can allow for an undersized buffer. Later when the application attempts to render the image data into this buffer, a heap-based buffer overflow will occur.
Due to it’s flexibility, the .blend file format is capable of storing a vast number of data structures and is capable of completely serializing the state of the application to disk. This means that a data structure in C will have the exact same format as the version on disk. In the following file, the structure for an Image
record is described. This data structure is populated using data from the file.
source/blender/makesdna/DNA_image_types.h:99
typedef struct Image {
ID id;
char name[1024]; /* file path, 1024 = FILE_MAX */
...
int flag;
short source, type;
int lastframe;
...
int gen_x, gen_y;
char gen_type, gen_flag;
short gen_depth;
float gen_color[4];
...
} Image
When loading an Image record from a .blend file, the function BKE_image_acquire_ibuf
in the source/blender/blenkernel/intern/image.c
file will be called. This function will be passed two structures. One of which is the Image
record that was read from the file. The other of which is the ImageUser
record which is used to identify the image from either a Texture/Material, Background Image, or the Image Window. This function will call the image_acquire_ibuf
function at [1] in order to allocate space for it.
source/blender/blenkernel/intern/image.c:4086
ImBuf *BKE_image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock)
{
ImBuf *ibuf;
BLI_spin_lock(&image_spin);
ibuf = image_acquire_ibuf(ima, iuser, r_lock); / [1]
BLI_spin_unlock(&image_spin);
return ibuf;
}
Once inside the image_acquire_ibuf
functon, the application will first check a field named “ok” within the Image
and ImageUser
structures [2]. After this is done, the Image
structure will have it’s source
and type
fields checked. The provided proof-of-concept uses an image source
of IMA_SRC_GENERATED(4)
. This will cause the case at [3] to be used to handle the acquiring of the ImBuf
structure. Once the gen_x
, gen_y
, and gen_depth
fields are validated, the add_ibuf_size
function will be called [4]. It is prudent to note that at [5] another similar vulnerability can be found for the IMA_SRC_VIEWER(5)
image source. This will be discussed later.
source/blender/blenkernel/intern/image.c:3990
static ImBuf *image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock)
{
ImBuf *ibuf = NULL;
int frame = 0, index = 0;
if (r_lock)
*r_lock = NULL;
/* quick reject tests */
if (!image_quick_test(ima, iuser)) // [2]
return NULL;
...
if (ibuf == NULL) {
...
else if (ima->source == IMA_SRC_GENERATED) { // [3]
...
if (ima->gen_x == 0) ima->gen_x = 1024;
if (ima->gen_y == 0) ima->gen_y = 1024;
if (ima->gen_depth == 0) ima->gen_depth = 24;
ibuf = add_ibuf_size(ima->gen_x, ima->gen_y, ima->name, ima->gen_depth, (ima->gen_flag & IMA_GEN_FLOAT) != 0, ima->gen_type,
ima->gen_color, &ima->colorspace_settings); // [4]
image_assign_ibuf(ima, ibuf, index, 0);
ima->ok = IMA_OK_LOADED;
}
else if (ima->source == IMA_SRC_VIEWER) { // [5]
if (ima->type == IMA_TYPE_R_RESULT) {
...
ibuf = image_get_render_result(ima, iuser, r_lock);
}
...
}
The add_ibuf_size
function will then take the width
and height
fields that came from Image.gen_x
and Image.gen_y
and then pass them to the IMB_allocImBuf
function at [6]. This function will take the product of these fields and the size of an unsigned int
(4) and use them to calculate the size of a heap-buffer. If the result of this is larger than 32-bits, then an integer overflow will occur. This can result in an undersized buffer that when written to will cause a heap-based buffer overflow. At [7], the application will then write data into this buffer.
source/blender/blenkernel/intern/image.c:632
static ImBuf *add_ibuf_size(unsigned int width, unsigned int height, const char *name, int depth, int floatbuf, short gen_type,
const float color[4], ColorManagedColorspaceSettings *colorspace_settings)
{
ImBuf *ibuf;
unsigned char *rect = NULL;
float *rect_float = NULL;
if (floatbuf) {
ibuf = IMB_allocImBuf(width, height, depth, IB_rectfloat); // [6] Image.gen_flag & IMA_GEN_FLOAT(1)
...
}
else {
ibuf = IMB_allocImBuf(width, height, depth, IB_rect); // [6] Image.gen_flag & ~IMA_GEN_FLOAT(1)
...
}
...
switch (gen_type) {
case IMA_GENTYPE_GRID:
BKE_image_buf_fill_checker(rect, rect_float, width, height); // [7]
break;
case IMA_GENTYPE_GRID_COLOR:
BKE_image_buf_fill_checker_color(rect, rect_float, width, height); // [7]
break;
default:
BKE_image_buf_fill_color(rect, rect_float, width, height, color); // [7] Used by provided proof-of-concept
break;
}
return ibuf;
}
Each of the functions at [7] will then be used to populate the undersized buffer. At [8], the application will hand off initialization of the buffer to a thread. This will wrap a call to image_buf_fill_color_slice
at [9] and then later one of the loops at [10] will be used to write into the buffer. The proof-of-concept that is included chooses the non-rect_float
loop to overflow the buffer.
source/blender/blenkernel/intern/image_gen.c:97
void BKE_image_buf_fill_color(unsigned char *rect,
float *rect_float,
int width, int height,
const float color[4])
{
if (((size_t)width) * height < 64 * 64) {
...
}
else {
FillColorThreadData data;
data.rect = rect;
data.rect_float = rect_float;
data.width = width;
copy_v4_v4(data.color, color);
IMB_processor_apply_threaded_scanlines(
height, image_buf_fill_color_thread_do, &data); // [8] \
}
}
\
source/blender/blenkernel/intern/image_gen.c:82
static void image_buf_fill_color_thread_do(void *data_v,
int start_scanline,
int num_scanlines)
{
...
image_buf_fill_color_slice(rect,
rect_float,
data->width,
num_scanlines,
data->color); // [9] \
}
\
source/blender/blenkernel/intern/image_gen.c:48
static void image_buf_fill_color_slice(unsigned char *rect,
float *rect_float,
int width, int height,
const float color[4])
{
int x, y;
/* blank image */
if (rect_float) {
float linear_color[4];
srgb_to_linearrgb_v4(linear_color, color);
for (y = 0; y < height; y++) { // [10]
for (x = 0; x < width; x++) {
copy_v4_v4(rect_float, linear_color);
rect_float += 4;
}
}
}
if (rect) {
unsigned char ccol[4];
rgba_float_to_uchar(ccol, color);
for (y = 0; y < height; y++) { // [10] Loop chosen by proof-of-concept
for (x = 0; x < width; x++) {
rect[0] = ccol[0];
rect[1] = ccol[1];
rect[2] = ccol[2];
rect[3] = ccol[3];
rect += 4;
}
}
}
}
There is a path to similar bug if the Image.source
field is IMA_SRC_VIEWER(5)
and the Image.type
field is set to IMA_TYPE_R_RESULT(4)
. At [11], the image_get_render_result
function will get called. After selecting the Image.render_slot
index of the Image.renders
array at [12], the application will use the RenderResult.rectx
and RenderResult.recty
fields to allocate an ImBuf
at [13]. Immediately afterwards, the application will assign the Image to the undersized buffer. This will also cause a heap-based buffer overflow.
source/blender/blenkernel/intern/image.c:4041
else if (ima->source == IMA_SRC_VIEWER) { // [5]
if (ima->type == IMA_TYPE_R_RESULT) {
/* always verify entirely, and potentially
* returns pointer to release later */
ibuf = image_get_render_result(ima, iuser, r_lock); // [11] \
}
\
source/blender/blenkernel/intern/image.c:3666
static ImBuf *image_get_render_result(Image *ima, ImageUser *iuser, void **r_lock)
{
...
RenderResult rres;
...
if (from_render) {
RE_AcquireResultImage(re, &rres, actview);
}
else if (ima->renders[ima->render_slot]) { // [12]
rres = *(ima->renders[ima->render_slot]);
rres.have_combined = ((RenderView *)rres.views.first)->rectf != NULL;
}
...
if (ibuf == NULL) {
ibuf = IMB_allocImBuf(rres.rectx, rres.recty, 32, 0); // [13]
image_assign_ibuf(ima, ibuf, IMA_NO_INDEX, 0);
}
The structure for the RenderResult
is located within the source/blender/render/extern/include/RE_pipeline.h
function. The rectx
and recty
fields are multiplied with the size of an unsigned int
in order to calculate the size for the target buffer for the second bug.
source/blender/render/extern/include/RE_pipeline.h:137
typedef struct RenderResult {
struct RenderResult *next, *prev;
/* target image size */
int rectx, recty;
short crop, sample_nr;
/* the following rect32, rectf and rectz buffers are for temporary storage only, for RenderResult structs
* created in #RE_AcquireResultImage - which do not have RenderView */
/* optional, 32 bits version of picture, used for ogl render and image curves */
int *rect32;
/* if this exists, a copy of one of layers, or result of composited layers */
float *rectf;
/* if this exists, a copy of one of layers, or result of composited layers */
float *rectz;
...
} RenderResult;
(1ddc.b58): 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=00000003 ecx=00003c01 edx=03ed0000 esi=1e211000 edi=00004000
eip=00b23bc6 esp=305bfacc ebp=305bfaec iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202
blender!PyInit_mathutils_noise_types+0x2e5146:
00b23bc6 8806 mov byte ptr [esi],al ds:002b:1e211000=??
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 $FILENAME.blend
To trigger the vulnerability with the provided proof-of-concept, one can simply add it as a library and attempt to use the Image. Another way is to open the file and navigate to the DataBlocks view.
$ /path/to/blender.exe $FILENAME.blend
To automate triggering the vulnerability without user-interaction, apply the Image to a Texture and then include the texture in a Scene.
In order to mitigate this vulnerability, it is recommended to not open a .blend file or use a .blend file as a library from an untrusted user.
2017-09-06 - Vendor Disclosure
2018-01-11 - Public Release
Discovered by a member of Cisco Talos.