CVE-2017-12082
An exploitable integer overflow exists in the CustomData
Mesh loading functionality of the Blender open-source 3d creation suite. A .blend file with a specially crafted external data 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 edit an object within a .blend library in their Scene 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 CustomData
layer from a Mesh
object within a Blender library file (.blend). When allocating space for the layer data within a Mesh
, 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. Later, when the application attempts to read data from the external data file into this buffer, a heap-based buffer overflow will occur.
When attempting to edit a Mesh
object that has been imported into the user’s scene, the application will attempt to read each layer that composes the Mesh
object. One of the options of a layer is to use external
data. When opening up a file, the application will iterate through all the data-blocks that use the ID_ME
(0x454d0000) code type. After identifying a data-block of the Mesh
, the application will execute the following code to map the pointers within the structure that was de-serialized from the data-block to the correct positions in memory. At [1], the application will fix the pointers that are stored within a CustomData
structure. Inside this function at [2], the application will fix-up both the pointers for the layers
field and the external
fields. The layer field points to a structure CustomDataLayer
. This structure contains the type
of the layer as well as any flags associated with it in the flag
field. If the flag
field has the CD_FLAG_EXTERNAL
(8) bit set, then the application will assume that the layer’s contents will be stored in the external
pointer. This pointer points to a CustomDataExternal
structure which contains the filename to load the data from.
source/blender/blenloader/intern/readfile.c:4563
static void direct_link_mesh(FileData *fd, Mesh *mesh)
{
...
direct_link_customdata(fd, &mesh->vdata, mesh->totvert); // [1] \
direct_link_customdata(fd, &mesh->edata, mesh->totedge); // [1] \
direct_link_customdata(fd, &mesh->fdata, mesh->totface); // [1] \
direct_link_customdata(fd, &mesh->ldata, mesh->totloop); // [1] \
direct_link_customdata(fd, &mesh->pdata, mesh->totpoly); // [1] \
...
}
\
source/blender/blenloader/intern/readfile.c:4527
static void direct_link_customdata(FileData *fd, CustomData *data, int count)
{
...
data->layers = newdataadr(fd, data->layers); // [2]
...
data->external = newdataadr(fd, data->external); // [3]
while (i < data->totlayer) {
CustomDataLayer *layer = &data->layers[i];
if (layer->flag & CD_FLAG_EXTERNAL)
layer->flag &= ~CD_FLAG_IN_MEMORY;
...
}
}
Later when the application attempts to read data from thus layer such as when editing the Mesh
, the application will execute the following function. This function will ensure that the external
field is set [4], and that one of the layers defined in the data
argument has the CD_FLAG_EXTERNAL
(8) flag set [5]. If this flag is set, the application will read the filename from the structure and then convert it to an absolute path at [6]. At [7], the application will finally attempt to open up the external file.
source/blender/blenkernel/intern/customdata.c:3539
void CustomData_external_read(CustomData *data, ID *id, CustomDataMask mask, int totelem)
{
CustomDataExternal *external = data->external;
CustomDataLayer *layer;
CDataFile *cdf;
CDataFileLayer *blay;
char filename[FILE_MAX];
const LayerTypeInfo *typeInfo;
int i, update = 0;
if (!external) // [4]
return;
for (i = 0; i < data->totlayer; i++) {
layer = &data->layers[i];
typeInfo = layerType_getInfo(layer->type);
...
else if ((layer->flag & CD_FLAG_EXTERNAL) && typeInfo->read) { // [5]
update = 1;
}
}
if (!update)
return;
customdata_external_filename(filename, id, external); // [6]
cdf = cdf_create(CDF_TYPE_MESH);
if (!cdf_read_open(cdf, filename)) { // [7]
cdf_free(cdf);
fprintf(stderr, "Failed to read %s layer from %s.\n", layerType_getName(layer->type), filename);
return;
}
for (i = 0; i < data->totlayer; i++) {
...
}
cdf_read_close(cdf);
}
To open up the file, the application will execute the cdf_read_open
function. This function will open up a stream to the file, and then hand it off to the cdf_read_header
function [8]. At the beginning of this function, the application will read the header [9], followed by checking it’s header for it’s fingerprint and version number at [10]. Once the byte order is determined, the application will then validate at [11] that the header.type
field is of either CDF_TYPE_IMAGE(0) or CDF_TYPE_MESH(1). At [12], depending on this type the application will read a CDataFileImageHeader
, or a CDataFileMeshHeader
from the file. After this is done, the application will attempt to read the number of layers embedded within this file. To allocate space for this, the application will read the totlayer
field from the header
and multiply it by the size of a CDataFileLayer
structure [13]. This structure has a size of 84-bytes. If the product of the totlayer
field and the number 84 is larger than 32-bits, then this allocation size will overflow resulting in a size that is smaller than expected. Later at [14] when the application attempts to read data from the file into this undersized buffer, a buffer overflow will occur. This can lead to code execution under the context of the application.
source/blender/blenkernel/intern/customdata_file.c:279
bool cdf_read_open(CDataFile *cdf, const char *filename)
{
FILE *f;
f = BLI_fopen(filename, "rb");
if (!f)
return 0;
...
if (!cdf_read_header(cdf)) { // [8]
cdf_read_close(cdf);
return 0;
}
...
return 1;
}
\
source/blender/blenkernel/intern/customdata_file.c:145
static int cdf_read_header(CDataFile *cdf)
{
CDataFileHeader *header;
CDataFileImageHeader *image;
CDataFileMeshHeader *mesh;
CDataFileLayer *layer;
FILE *f = cdf->readf;
size_t offset = 0;
int a;
header = &cdf->header;
if (!fread(header, sizeof(CDataFileHeader), 1, cdf->readf)) // [9]
return 0;
if (memcmp(header->ID, "BCDF", sizeof(header->ID)) != 0) // [10]
return 0;
if (header->version > CDF_VERSION)
return 0;
cdf->switchendian = header->endian != cdf_endian();
header->endian = cdf_endian();
...
if (!ELEM(header->type, CDF_TYPE_IMAGE, CDF_TYPE_MESH)) // [11]
return 0;
offset += header->structbytes;
header->structbytes = sizeof(CDataFileHeader);
if (fseek(f, offset, SEEK_SET) != 0)
return 0;
if (header->type == CDF_TYPE_IMAGE) { // [12]
image = &cdf->btype.image;
if (!fread(image, sizeof(CDataFileImageHeader), 1, f))
return 0;
...
offset += image->structbytes;
image->structbytes = sizeof(CDataFileImageHeader);
}
else if (header->type == CDF_TYPE_MESH) {
mesh = &cdf->btype.mesh;
if (!fread(mesh, sizeof(CDataFileMeshHeader), 1, f))
return 0;
...
offset += mesh->structbytes;
mesh->structbytes = sizeof(CDataFileMeshHeader);
}
if (fseek(f, offset, SEEK_SET) != 0)
return 0;
cdf->layer = MEM_callocN(sizeof(CDataFileLayer) * header->totlayer, "CDataFileLayer"); // [13]
...
for (a = 0; a < header->totlayer; a++) {
layer = &cdf->layer[a];
if (!fread(layer, sizeof(CDataFileLayer), 1, f)) // [14]
return 0;
...
if (layer->datatype != CDF_DATA_FLOAT)
return 0;
offset += layer->structbytes;
layer->structbytes = sizeof(CDataFileLayer);
if (fseek(f, offset, SEEK_SET) != 0)
return 0;
}
...
return 1;
}
(1e5c.18f4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=16a03054 ebx=00000057 ecx=00000050 edx=00000053 esi=16a03004 edi=33ff1000
eip=0249af2a esp=0444ef64 ebp=0444ef84 iopl=0 nv up ei pl nz na pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00210207
blender!xmlListWalk+0x2181aa:
0249af2a f3a4 rep movs byte ptr es:[edi],byte ptr [esi]
0:000> !heap -p -a @edi
address 33ff1000 found in
_DPH_HEAP_ROOT @ 8ec1000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
306903dc: 33ff0ff8 4 - 33ff0000 2000
0:000> dc @esi
16a03004 00000000 00000000 00000000 00000000 ................
16a03014 00000000 00000000 00000000 00000000 ................
16a03024 00000000 00000000 00000000 00000000 ................
16a03034 00000000 00000000 00000000 00000000 ................
16a03044 00000000 00000000 00000000 00000000 ................
16a03054 00000000 00000000 00000000 00000000 ................
16a03064 00000000 00000000 c0c0c0c0 c0c0c0c0 ................
16a03074 c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0 ................
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. This will also generate a file that will be imported as the CustomData
layer with the suffix .data
.
$ python poc.py $FILENAME.blend
To trigger the vulnerability, one can import the Mesh object from the .blend file into their scene. Once the user tries to edit or interact with the Object
that utilizes the Mesh
object, the application will crash.
$ /path/to/blender.exe
# Hit Shift+F1 to import a .blend file as a library
# Navigate to the `Object` that one wishes to import and select it.
# Create an instance of the specified `Object` in the scene.
# Hit Enter to choose Edit-Mode
In order to mitigate this vulnerability, it is recommended to not use untrusted library files in their Scene.
2017-09-06 - Vendor Disclosure
2018-01-11 - Public Release
Discovered by a member of Cisco Talos.