CVE-2017-12104
An exploitable integer overflow exists in the way that the Blender open-source 3d creation suite v2.78c draws a Particle object. 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 the file as a library in order to trigger this vulnerability.
Blender v2.78c (32-bit)
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 draws a ParticleSystem
object as a PART_DRAW_AXIS(4)
type. When allocating space for the number of particles, the application will perform some arithmetic which can overflow. This result will then be used to perform an allocation which can allow for the resulting heap-buffer to be undersized. When the application tries to write values into this undersized buffer, a heap-based buffer overflow will occur.
When drawing an object, the application will call the following function. The draw_object
function will figure out whether or not to draw particles or the object that the particles are emitted from. At [1], the application will first check to see if the particlesystem
field contains any elements in its linked list. This is used to set the has_particles
flag. The provided proof-of-concept attempts to skip unnecessary code when triggering the vulnerability and so various flags were set in order to achieve the desired result. The flags specified for the ‘mode’ field are set to zero which results in ob->mode
being equivalent to OB_MODE_OBJECT(0)
. This causes the skip_object
field to be set to true which skips rendering some objects. Eventually at [3], the application will iterate through all the ParticleSystem
objects within the Object
and hand it off to the draw_new_particle_system
function.
source/blender/editors/space_view3d/drawobject.c:7455
void draw_object(Scene *scene, ARegion *ar, View3D *v3d, Base *base, const short dflag)
{
...
Object *ob = base->object;
...
const bool is_obact = (ob == OBACT);
const bool render_override = (v3d->flag2 & V3D_RENDER_OVERRIDE) != 0;
const bool is_picking = (G.f & G_PICKSEL) != 0;
const bool has_particles = (ob->particlesystem.first != NULL); // [1]
bool skip_object = false; /* Draw particles but not their emitter object. */
...
if (has_particles) {
...
if (ob->mode == OB_MODE_OBJECT) {
ParticleSystem *psys;
skip_object = render_override; // [2]
for (psys = ob->particlesystem.first; psys; psys = psys->next) {
/* Once we have found a psys which renders its emitter object, we are done. */
if (psys->part->draw & PART_DRAW_EMITTER) {
skip_object = false;
break;
}
}
}
}
...
if ((dflag & DRAW_PICKING) == 0 && (base->flag & OB_FROMDUPLI) == 0 && (v3d->flag2 & V3D_RENDER_SHADOW) == 0) {
/* don't do xray in particle mode, need the z-buffer */
if (!(ob->mode & OB_MODE_PARTICLE_EDIT)) {
...
}
...
if ((ob->particlesystem.first) &&
(ob != scene->obedit))
{
ParticleSystem *psys;
...
for (psys = ob->particlesystem.first; psys; psys = psys->next) {
...
draw_new_particle_system(scene, v3d, rv3d, base, psys, dt, dflag); // [3]
}
At the beginning of the draw_new_particle_system
function, the application will assign a few variables and then check some flags of the respective objects. The provided proof-of-concept sets the part->draw_as
field to the value PART_DRAW_REND(10)
. This results in the part->ren_as
field being assigned to the draw_as
variable [4]. The shortest path discovered by the author to the vulnerability was to use PART_DRAW_AXIS(4)
as the value for part->ren_as
. A little bit later at [5], the application will use some fields from both the ParticleSystem
and ParticleSettings
objects to perform a calculation. This uses the psys->totchild
field and the part->disp
field to calculate the total number of children. After that, the application will assign the psys->totpart
field to the local totpart
variable [6]. It is these three fields that are used by the proof-of-concept to trigger the vulnerability. After assigning those variables, at [7] the application will assign a true value to the create_cdata
field. This variable is used to control the case for the allocation that contains the vulnerability. Finally at [8], the application will perform some arithmetic that is used to make the vulnerable allocation. This code may already result in an overflow, but at both [9] and [10] are more calculations that influence the buffer that will be allocated. If the result of the arithmetic at [8], [9], and [10] is larger than 32-bits then an integer overflow will occur. Due to this integer overflow, the allocations at [11] can all be made to be undersized. This can result in a heap-based buffer overflow when either of them are used.
source/blender/editors/space_view3d/drawobject.c:4995
static void draw_new_particle_system(Scene *scene, View3D *v3d, RegionView3D *rv3d,
Base *base, ParticleSystem *psys,
const char ob_dt, const short dflag)
{
Object *ob = base->object;
...
ParticleSettings *part = psys->part;
ParticleData *pars = psys->particles;
...
if (part->draw_as == PART_DRAW_REND) // [4]
draw_as = part->ren_as;
else
draw_as = part->draw_as;
if (draw_as == PART_DRAW_NOT)
return;
...
if (part->type == PART_HAIR && !psys->childcache)
totchild = 0;
else
totchild = psys->totchild * part->disp / 100; // [5]
...
totpart = psys->totpart; // [6]
...
switch (draw_as) {
...
case PART_DRAW_CROSS:
case PART_DRAW_AXIS:
...
if (draw_as == PART_DRAW_AXIS)
create_cdata = 1; // [7]
break;
...
}
...
if (ELEM(draw_as, PART_DRAW_DOT, PART_DRAW_CROSS, PART_DRAW_LINE) &&
(part->draw_col > PART_DRAW_COL_MAT))
{
create_cdata = 1; // [7]
}
...
if (draw_as && ELEM(draw_as, PART_DRAW_PATH, PART_DRAW_CIRC) == 0) {
int tot_vec_size = (totpart + totchild) * 3 * sizeof(float); // [8]
...
if (part->draw_as == PART_DRAW_REND && part->trail_count > 1) {
tot_vec_size *= part->trail_count; // [9]
psys_make_temp_pointcache(ob, psys);
}
switch (draw_as) {
case PART_DRAW_AXIS:
case PART_DRAW_CROSS:
tot_vec_size *= 6; // [10]
if (draw_as != PART_DRAW_CROSS)
create_cdata = 1;
break;
case PART_DRAW_LINE:
tot_vec_size *= 2; // [10]
break;
case PART_DRAW_BB:
tot_vec_size *= 4; // [10]
create_ndata = 1;
break;
}
...
if (!pdd->vdata)
pdd->vdata = MEM_callocN(tot_vec_size, "particle_vdata"); // [11]
if (create_cdata && !pdd->cdata)
pdd->cdata = MEM_callocN(tot_vec_size, "particle_cdata"); // [11]
if (create_ndata && !pdd->ndata)
pdd->ndata = MEM_callocN(tot_vec_size, "particle_ndata"); // [11]
if (part->draw & PART_DRAW_VEL && draw_as != PART_DRAW_LINE) {
if (!pdd->vedata) // [11]
pdd->vedata = MEM_callocN(2 * (totpart + totchild) * 3 * sizeof(float), "particle_vedata");
need_v = 1;
}
Continuing execution after the vulnerable allocation is made, the application will check to see if the draw_as
variable is not equal to PART_DRAW_PATH(6)
[12]. After verifying a few more fields and that the part->trail_count
of the ParticleSettings
field is larger than 1, then the function draw_particle_data
at [13] will be called. The draw_particle_data
function is a very simple function that will specially handle the PART_DRAW_BB(9)
constant, and then wrap the call to the draw_particle
function at [14].
source/blender/editors/space_view3d/drawobject.c:5255
if ((pdd || draw_as == PART_DRAW_CIRC) && draw_as != PART_DRAW_PATH) { // [12]
...
if (pdd && (pdd->flag & PARTICLE_DRAW_DATA_UPDATED) &&
(pdd->vedata || part->draw & (PART_DRAW_SIZE | PART_DRAW_NUM | PART_DRAW_HEALTH)) == 0)
{
totpoint = pdd->totpoint; /* draw data is up to date */
}
else {
...
drawn = 0;
if (part->draw_as == PART_DRAW_REND && part->trail_count > 1) {
...
else {
state.time = cfra;
if (psys_get_particle_state(&sim, a, &state, 0)) {
draw_particle_data(psys, rv3d, // [13] \
&state, draw_as, imat, &bb, psys->pdd,
pa_time, pa_size, r_tilt, pixsize_scale);
totpoint++;
drawn = 1;
}
}
\
source/blender/editors/space_view3d/drawobject.c:4948
static void draw_particle_data(ParticleSystem *psys, RegionView3D *rv3d,
ParticleKey *state, int draw_as,
float imat[4][4], ParticleBillboardData *bb, ParticleDrawData *pdd,
const float ct, const float pa_size, const float r_tilt, const float pixsize_scale)
{
...
if (draw_as == PART_DRAW_BB) {
...
]
pixsize = ED_view3d_pixel_size(rv3d, state->co) * pixsize_scale;
draw_particle(state, draw_as, part->draw, pixsize, imat, part->draw_line, bb, pdd); // [14]
}
Finally inside the draw_particle
function, the application will assign a pointer from the pdd->vd
field. This is one of the undersized heap-buffers that was allocated earlier. Immediately afterwards, the application will check the draw_as
argument [15]. If draw_as
argument is set to PART_DRAW_AXIS(4)
then the application will write to the pointer that was assigned at [15]. Due to the heap-buffers being undersized as a result of the 32-bit integer overflow, this will result in a heap-based buffer overflow which can lead to code execution under the context of the application.
source/blender/editors/space_view3d/drawobject.c:4799
static void draw_particle(ParticleKey *state, int draw_as, short draw, float pixsize,
float imat[4][4], const float draw_line[2], ParticleBillboardData *bb, ParticleDrawData *pdd)
{
...
if (pdd) {
vd = pdd->vd; // [15]
cd = pdd->cd;
if (pdd->ma_col) {
copy_v3_v3(ma_col, pdd->ma_col);
}
}
...
switch (draw_as) {
...
case PART_DRAW_CROSS:
case PART_DRAW_AXIS: // [16]
{
vec[0] = 2.0f * pixsize;
vec[1] = vec[2] = 0.0;
mul_qt_v3(state->rot, vec);
if (draw_as == PART_DRAW_AXIS) {
if (cd) {
cd[1] = cd[2] = cd[4] = cd[5] = 0.0; // [17]
cd[0] = cd[3] = 1.0;
cd[6] = cd[8] = cd[9] = cd[11] = 0.0;
cd[7] = cd[10] = 1.0;
cd[13] = cd[12] = cd[15] = cd[16] = 0.0;
cd[14] = cd[17] = 1.0;
pdd->cd += 18;
}
copy_v3_v3(vec2, state->co);
}
...
(1dfc.3750): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00a7f6e8 ebx=00a7f6d0 ecx=00a7f510 edx=304f6ffc esi=1dbeaffc edi=2ffdcfcc
eip=01395c56 esp=00a7f4e8 ebp=00a7f550 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
blender!osl_floor_ff+0xabb96:
01395c56 c7461400000000 mov dword ptr [esi+14h],0 ds:002b:1dbeb010=????????
0:000> !heap -p -a @esi
address 1dbeaffc found in
_DPH_HEAP_ROOT @ b21000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
1db116b4: 1dbeaff8 4 - 1dbea000 2000
6ace8d9c verifier!AVrfDebugPageHeapAllocate+0x0000023c
77d6fb79 ntdll!RtlDebugAllocateHeap+0x00000032
77d09a73 ntdll!RtlpAllocateHeap+0x0003914a
77cd0b43 ntdll!RtlAllocateHeap+0x0000014c
02d8f5b3 blender!xmlGetThreadId+0x0000c0b3
02d75fc1 blender!xmlListWalk+0x00223241
01c2d129 blender!osl_texture_set_swrap_code+0x00171e99
013920f6 blender!osl_floor_ff+0x000a8036
013947e5 blender!osl_floor_ff+0x000aa725
01381a6f blender!osl_floor_ff+0x000979af
01383101 blender!osl_floor_ff+0x00099041
01382863 blender!osl_floor_ff+0x000987a3
0154ac30 blender!osl_texture_set_twidth+0x000e25f0
012e678c blender!xmlFileMatch+0x000202bc
012e5d83 blender!xmlFileMatch+0x0001f8b3
012cc58f blender!xmlFileMatch+0x000060bf
012c9b1b blender!xmlFileMatch+0x0000364b
02d6a125 blender!xmlListWalk+0x002173a5
75f7919f KERNEL32!BaseThreadInitThunk+0x0000000e
77cda8cb ntdll!__RtlUserThreadStart+0x00000020
77cda8a1 ntdll!_RtlUserThreadStart+0x0000001b
0:000> u .
blender!osl_floor_ff+0xabb96:
01395c56 c7461400000000 mov dword ptr [esi+14h],0
01395c5d c7461000000000 mov dword ptr [esi+10h],0
01395c64 c7460800000000 mov dword ptr [esi+8],0
01395c6b c7460400000000 mov dword ptr [esi+4],0
01395c72 c7460c0000803f mov dword ptr [esi+0Ch],3F800000h
01395c79 c7060000803f mov dword ptr [esi],3F800000h
01395c7f c7462c00000000 mov dword ptr [esi+2Ch],0
01395c86 c7462400000000 mov dword ptr [esi+24h],0
0:000> lm a blender.exe
Browse full module list
start end module name
01130000 04a98000 blender C (export symbols) \path\to\blender.exe
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.zip $FILENAME.blend
To trigger the vulnerability, one can simply open the file, treat it as a library and import the Curve object, or pass it as an argument to the blender executable.
$ /path/to/blender.exe $FILENAME.blend
In order to mitigate this vulnerability, it is recommended to not use untrusted .blend files within the application.
2017-09-27 - Vendor Disclosure
2018-01-11 - Public Release
Discovered by a member of Cisco Talos.