CVE-2017-2900
An exploitable integer overflow exists in the PNG loading functionality of the Blender open-source 3d creation suite version 2.78c. A specially crafted .png
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 PNG image file as an asset for the video sequencer. When allocating space for the image data within a .png
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 PNG formatted file is determined, the function imb_loadpng
is used to load the image from memory. First, the application will allocate a structure using libpng
to initialize the state used to interact with the PNG file [6]. Afterwards the png_create_info_struct
function will be used to allocate the structure for reading the image [7].
source/blender/imbuf/intern/png.c:514
ImBuf *imb_loadpng(const unsigned char *mem, size_t size, int flags, char colorspace[IM_MAX_SPACE])
{
...
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL);
if (png_ptr == NULL) {
printf("Cannot png_create_read_struct\n"); // [6]
return NULL;
}
png_set_error_fn(png_ptr, NULL, imb_png_error, imb_png_warning);
info_ptr = png_create_info_struct(png_ptr); // [7]
if (info_ptr == NULL) {
png_destroy_read_struct(&png_ptr, (png_infopp)NULL,
(png_infopp)NULL);
printf("Cannot png_create_info_struct\n");
return NULL;
}
Once the initial structures are initialized, the application will read the IHDR
chunk from the image file [8]. This chunk contains various information about the file such as the image dimensions, the compression method, and the image’s pixel depth. The width
, height
, and bytesperpixel
variables which are read here are later combined via a multiplication that can cause an integer overflow.
source/blender/imbuf/intern/png.c:568
// png_set_sig_bytes(png_ptr, 8);
png_read_info(png_ptr, info_ptr);
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth,
&color_type, NULL, NULL, NULL); // [8]
bytesperpixel = png_get_channels(png_ptr, info_ptr);
After reading the IHDR
chunk from the file, the application will begin to prepare reading the image’s contents into a buffer. It does this by allocating an ImBuf
structure using the width
and height
variables that were read earlier [9]. This function will assign the dimensions to the x
and y
fields of the structure. Later at [10], the function uses these dimensions to allocate space for the image data that composes the image. Due to the values for each dimension in the header being 32-bits, this calculation can be made to overflow which will result in an undersized value being used to allocate the buffer.
source/blender/imbuf/intern/png.c:605
ibuf = IMB_allocImBuf(width, height, 8 * bytesperpixel, 0); // [9]
...
if (ibuf && ((flags & IB_test) == 0)) {
if (bit_depth == 16) {
...
else {
imb_addrectImBuf(ibuf);
pixels = MEM_mallocN(((size_t)ibuf->x) * ibuf->y * bytesperpixel * sizeof(unsigned char), "pixels"); // [10]
if (pixels == NULL) {
printf("Cannot allocate pixels array\n");
longjmp(png_jmpbuf(png_ptr), 1);
}
At [11], another allocation that may be overflown is made. This allocation contains the pointer to each row within the image buffer. Finally, the application will attempt to decompress data from the file into the undersized buffer at [12]. If the product of the width
and height
variables from the format’s IHDR
chunk is larger than 32-bits then the call to png_read_image
will write outside the bounds of the image buffer which can cause a buffer overflow [13]. These can allow for an aggressor to execute code from within the context of the application.
/* allocate memory for an array of row-pointers */
row_pointers = (png_bytepp) MEM_mallocN(ibuf->y * sizeof(png_bytep), "row_pointers"); // [11]
if (row_pointers == NULL) {
printf("Cannot allocate row-pointers array\n");
longjmp(png_jmpbuf(png_ptr), 1);
}
/* set the individual row-pointers to point at the correct offsets */
for (i = 0; i < ibuf->y; i++) {
row_pointers[ibuf->y - 1 - i] = (png_bytep)
((unsigned char *)pixels + (((size_t)i) * ibuf->x) * bytesperpixel * sizeof(unsigned char)); // [12]
}
png_read_image(png_ptr, row_pointers); // [13]
(1754.21d4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=1491cfe0 ebx=15156ffc ecx=00030000 edx=00030000 esi=148ecfe0 edi=15156ffc
eip=02afaf2a esp=04a2ed0c ebp=04a2ed4c iopl=0 nv up ei pl nz na po cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010203
blender!xmlListWalk+0x2181aa:
02afaf2a f3a4 rep movs byte ptr es:[edi],byte ptr [esi]
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 .png
file to.
$ python poc.py $FILENAME.png
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.png
In order to mitigate this vulnerability, it is recommended to not use untrusted image files as an asset when using the sequencer.
2017-09-17 - Vendor Disclosure
2018-01-11 - Public Release
Discovered by a member of Cisco Talos.