CVE-2017-2905
An exploitable integer overflow exists in the bmp loading functionality of the Blender open-source 3d creation suite version 2.78c. A specially crafted .bmp
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 a .bmp
file as an asset for the video sequencer. When allocating space for the image data within a .bmp
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;
}
}
}
When a .bmp
file is determined, the function imb_bmp_decode
is used to load the image from memory. First, the application will read the BMPFILEHEADER
to determine where the pixel data is [6]. Aftewards the BMPINFOHEADER
structure function will be read to determine the dimensions of the bitmap image [7]. This structure contains the sizes that will be combined to trigger the integer overflow.
source/blender/imbuf/intern/bmp.c:123
struct ImBuf *imb_bmp_decode(const unsigned char *mem, size_t size, int flags, char colorspace[IM_MAX_SPACE])
{
...
bmp = mem + LITTLE_LONG(*(int *)(mem + 10));
...
/* for systems where an int needs to be 4 bytes aligned */
memcpy(&bmi, mem, sizeof(bmi)); // [6]
skip = LITTLE_LONG(bmi.biSize); // [7]
x = LITTLE_LONG(bmi.biWidth);
y = LITTLE_LONG(bmi.biHeight);
depth = LITTLE_SHORT(bmi.biBitCount);
xppm = LITTLE_LONG(bmi.biXPelsPerMeter);
yppm = LITTLE_LONG(bmi.biYPelsPerMeter);
if (depth <= 8) {
ibuf_depth = 24;
}
else {
ibuf_depth = depth;
}
Once the dimensions in the BMPINFOHEADER
structure have been read, the application will allocate space for the image data at [8]. This will take the x
and y
variables and multiply them by the ibuf_depth
. If the product of all of these variables is larger than 32-bits, then an integer overflow will occur. This will result in an undersized heap-buffer. At [9], the application will enter a loop in order to read each row of pixel data from the file. At [10], the application will write to the buffer that was under-allocated resulting in a heap-based buffer overflow.
source/blender/imbuf/intern/bmp.c:177
if (flags & IB_test) {
ibuf = IMB_allocImBuf(x, y, ibuf_depth, 0);
}
else {
ibuf = IMB_allocImBuf(x, y, ibuf_depth, IB_rect); // [8]
rect = (unsigned char *) ibuf->rect;
if (depth <= 8) {
const int rowsize = (depth * x + 31) / 32 * 4;
const char (*palette)[4] = (void *)(mem + skip);
const int startmask = ((1 << depth) - 1) << 8;
for (i = y; i > 0; i--) { // [9]
int index;
int bitoffs = 8;
int bitmask = startmask;
int nbytes = 0;
const char *pcol;
if (top_to_bottom) {
rect = (unsigned char *) &ibuf->rect[(i - 1) * x];
}
for (j = x; j > 0; j--) {
bitoffs -= depth;
bitmask >>= depth;
index = (bmp[0] & bitmask) >> bitoffs;
pcol = palette[index];
/* intentionally BGR -> RGB */
rect[0] = pcol[2]; // [10]
rect[1] = pcol[1];
rect[2] = pcol[0];
rect[3] = 255;
rect += 4;
if (bitoffs == 0) {
/* Advance to the next byte */
bitoffs = 8;
bitmask = startmask;
nbytes += 1;
bmp += 1;
}
}
/* Advance to the next row */
bmp += (rowsize - nbytes);
}
}
(25a4.253c): 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=0f930036 edx=00000000 esi=0ffc1000 edi=0f930435
eip=0182521f esp=0081f09c ebp=0081f110 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_interp_code+0x10ea1f:
0182521f 8806 mov byte ptr [esi],al ds:002b:0ffc1000=??
0:000> ? poi(poi(@ebp-8)+20)
Evaluate expression: 268173316 = 0ffc0004
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 .bmp
file to.
$ python poc.py $FILENAME.bmp
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.bmp
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.