CVE-2017-2903
An exploitable integer overflow exists in the DPX loading functionality of the Blender open-source 3d creation suite version 2.78c. A specially crafted .cin
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 use the file as an asset via the sequencer 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 loads an DPX file as a resource for the video sequencer. When allocating space for the image data within a .cin
file, 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.
When loading an image file, the function IMB_loadiffname
in the source/blender/imbuf/intern/readimage.c
file will be called. Inside this function, the application will first open the file and then call the IMB_loadifffile
function [2].
source/blender/imbuf/intern/readimage.c:212
ImBuf *IMB_loadiffname(const char *filepath, int flags, char colorspace[IM_MAX_SPACE])
{
...
file = BLI_open(filepath_tx, O_BINARY | O_RDONLY, 0); // [1]
if (file == -1)
return NULL;
ibuf = IMB_loadifffile(file, filepath, flags, colorspace, filepath_tx); // [2]
Inside the IMB_loadifffile
function, the application will first map the whole file into memory using the mmap
system-call [3]. After the file is successfully mapped into memory, the resulting pages will be passed to the IMB_ibImageFromMemory
function [4]. This function is responsible for figuring out which file-format handlers to use, and then to call its respective loader.
source/blender/imbuf/intern/readimage.c:165
ImBuf *IMB_loadifffile(int file, const char *filepath, int flags, char colorspace[IM_MAX_SPACE], const char *descr)
{
...
imb_mmap_lock();
mem = mmap(NULL, size, PROT_READ, MAP_SHARED, file, 0); // [3]
imb_mmap_unlock();
if (mem == (unsigned char *) -1) {
fprintf(stderr, "%s: couldn't get mapping %s\n", __func__, descr);
return NULL;
}
ibuf = IMB_ibImageFromMemory(mem, size, flags, colorspace, descr); // [4]
Inside the following function, the application will iterate through a global list that contains different handlers for all of the image files that the application supports. At [5], the application will call the function responsible for loading the image out of memory.
source/blender/imbuf/intern/readimage.c:104
ImBuf *IMB_ibImageFromMemory(unsigned char *mem, size_t size, int flags, char colorspace[IM_MAX_SPACE], const char *descr)
{
...
for (type = IMB_FILE_TYPES; type < IMB_FILE_TYPES_LAST; type++) {
if (type->load) {
ibuf = type->load(mem, size, flags, effective_colorspace); // [5]
if (ibuf) {
imb_handle_alpha(ibuf, flags, colorspace, effective_colorspace);
return ibuf;
}
}
}
After determining that the file is of a DPX or CINEON file, the function at [6] will be called. This will execute the logImageOpenFromMemory
function which will check the header of the file in order to determine whether to call logImageIsDpx
or logImageIsCineon
functions. If a DPX image file was detected, then the dpxOpen
function will be called at [7]
source/blender/imbuf/intern/cineon/cineon_dpx.c:52
static struct ImBuf *imb_load_dpx_cineon(
const unsigned char *mem, size_t size, int use_cineon, int flags,
char colorspace[IM_MAX_SPACE])
{
...
image = logImageOpenFromMemory(mem, size); // [6] \
\
source/blender/imbuf/intern/cineon/logImageCore.c:118
LogImageFile *logImageOpenFromMemory(const unsigned char *buffer, unsigned int size)
{
if (logImageIsDpx(buffer))
return dpxOpen(buffer, 1, size); // [7]
else if (logImageIsCineon(buffer))
return cineonOpen(buffer, 1, size);
return NULL;
}
Once returning from logImageOpenFromMemory
, the application will execute the following code. This code will first extract the dimensions and color-depth at [8]. Afterwards, it will allocate a buffer using these values at [9]. Due to a failure to check to see if the product of the width
, height
and 4 (for 32-bits) will be larger than a 32-bit value, this will result in an undersized buffer being used. At [10], when the application attempts to decode image data into this undersized buffer a buffer overflow may occur.
source/blender/imbuf/intern/cineon/cineon_dpx.c:71
logImageGetSize(image, &width, &height, &depth); // [8]
ibuf = IMB_allocImBuf(width, height, 32, IB_rectfloat | flags); // [9]
if (ibuf == NULL) {
logImageClose(image);
return NULL;
}
if (!(flags & IB_test)) {
if (logImageGetDataRGBA(image, ibuf->rect_float, 1) != 0) { // [10]
logImageClose(image);
IMB_freeImBuf(ibuf);
return NULL;
}
IMB_flipy(ibuf);
}
When trying to decode the image data, the logImageGetDataRGBA
function will be called. This function iterates through the number of elements defined in the header and uses it to determine how to decode the image data that’s stored after it. At [11], the application will call the logImageElementGetData
function. Inside the logImageElementGetData
function, the application will determine the current element’s bitsPerSample
and then call the respective function to decode the image data. The provided proof-of-concept uses a bitsPerSample
of 1.
source/blender/imbuf/intern/cineon/logImageCore.c:367
int logImageGetDataRGBA(LogImageFile *logImage, float *data, int dataIsLinearRGB)
{
...
for (i = 0; i < logImage->numElements; i++) {
/* descriptor_Depth and descriptor_Composite are not supported */
if (logImage->element[i].descriptor != descriptor_Depth && logImage->element[i].descriptor != descriptor_Composite) {
...
/* Load data */
if (logImageElementGetData(logImage, logImage->element[i], elementData[i]) != 0) { // [11] \
if (verbose) printf("DPX/Cineon: Cannot read elementData[%d]\n.", i);
for (j = 0; j < i; j++)
if (elementData[j] != NULL)
MEM_freeN(elementData[j]);
return 1;
}
}
...
}
\
source/blender/imbuf/intern/cineon/logImageCore.c:561
static int logImageElementGetData(LogImageFile *logImage, LogImageElement logElement, float *data)
{
switch (logElement.bitsPerSample) {
case 1:
return logImageElementGetData1(logImage, logElement, data); // [12]
...
}
}
Finally the application will enter a loop which will decode the data from the file into the undersized buffer that was allocated. At [13], this buffer will be written to which will trigger the heap-based buffer overflow. This can allow for code execution under the context of the application.
source/blender/imbuf/intern/cineon/logImageCore.c:591
static int logImageElementGetData1(LogImageFile *logImage, LogImageElement logElement, float *data)
{
...
/* read 1 bit data padded to 32 bits */
for (y = 0; y < logImage->height; y++) {
for (x = 0; x < logImage->width * logElement.depth; x += 32) {
...
pixel = swap_uint(pixel, logImage->isMSB);
for (offset = 0; offset < 32 && x + offset < logImage->width; offset++)
data[y * logImage->width * logElement.depth + x + offset] = (float)((pixel >> offset) & 0x01); // [13]
}
}
return 0;
}
(242c.2b40): 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=00000000 ecx=1419affc edx=00000000 esi=00000000 edi=148d6e6c
eip=016f0aa7 esp=00abec0c ebp=00abec24 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246
blender!osl_texture_set_swrap_code+0x45817:
016f0aa7 f30f11448104 movss dword ptr [ecx+eax*4+4],xmm0 ds:002b:1419b000=????????
0:000> !heap -p -a @ecx
address 1419affc found in
_DPH_HEAP_ROOT @ c11000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
14222750: 1419aff8 4 -
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 .cin
file to.
$ python poc.py $FILENAME.cin
To trigger the vulnerability, one can simply add it as an asset or they can pass it as an argument to the blender executable.
$ /path/to/blender.exe -a $FILENAME.cin
In order to mitigate this vulnerability, it is recommended to not use untrusted image files as an asset when using the sequencer.
2017-09-06 - Vendor Disclosure
2018-01-11 - Public Release
Discovered by a member of Cisco Talos.