Talos Vulnerability Report

TALOS-2017-0413

Blender Sequencer imb_get_anim_type Streams Integer Overflow Code Execution Vulnerability

January 11, 2018
CVE Number

CVE-2017-2906

Summary

An exploitable integer overflow exists in the animation playing functionality of the Blender open-source 3d creation suite version 2.78c. A specially created .avi 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 in order to trigger this vulnerability.

Tested Versions

Blender v2.78c

Product URLs

http://www.blender.org git://git.blender.org/blender.git

CVSSv3 Score

8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-190 - Integer Overflow or Wraparound

Details

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 verifies the type of animation when adding or playing a video via the sequencer. When allocating space for the number of audio/video streams within a .avi 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 load data from the file into this buffer, a heap-based buffer overflow will occur.

When determining the animation type, the function imb_get_anim_type in the source/blender/imbuf/intern/util.c file will be called. Inside this function, the application will call various functions in order to determine the animation type. Within these various tests is a call to the function is_avi [1]. This function is simply a wrapper which will then call AVI_is_avi.

source/blender/imbuf/intern/util.c:376
int imb_get_anim_type(const char *name)
{
    int type;
    BLI_stat_t st;

    BLI_assert(!BLI_path_is_rel(name));

    if (UTIL_DEBUG) printf("%s: %s\n", __func__, name);
...
    if (isavi(name)) return (ANIM_AVI);         // [1] \
...
    return ANIM_NONE;
}

source/blender/imbuf/intern/util.c:376
static int isavi(const char *name)
{
#ifdef WITH_AVI
    return AVI_is_avi(name);        // [2]
#else
    (void)name;
    return false;
#endif
}

The AVI_is_avi function contains the integer overflow described in this advisory. At the beginning of the function, the application will first open up the filename [3] followed by validating different parts of the header in order to determine it is a .avi file type. After confirming it has a proper header, various fields are read from the file. One of these fields is the number of streams at [4]. This field is used to determine the number of audio or video streams that may follow.

source/blender/avi/intern/avi.c:233
bool AVI_is_avi(const char *name)
{
    int temp, fcca, j;
    AviMovie movie = {NULL};
    AviMainHeader header;
    AviBitmapInfoHeader bheader;
    int movie_tracks = 0;
    
    DEBUG_PRINT("opening movie\n");

    movie.type = AVI_MOVIE_READ;
    movie.fp = BLI_fopen(name, "rb");               // [3]
    movie.offset_table = NULL;
...
    movie.header->MicroSecPerFrame = GET_FCC(movie.fp);
    movie.header->MaxBytesPerSec = GET_FCC(movie.fp);
    movie.header->PaddingGranularity = GET_FCC(movie.fp);
    movie.header->Flags = GET_FCC(movie.fp);
    movie.header->TotalFrames = GET_FCC(movie.fp);
    movie.header->InitialFrames = GET_FCC(movie.fp);
    movie.header->Streams = GET_FCC(movie.fp);      // [4]
    movie.header->SuggestedBufferSize = GET_FCC(movie.fp);
    movie.header->Width = GET_FCC(movie.fp);
    movie.header->Height = GET_FCC(movie.fp);
    movie.header->Reserved[0] = GET_FCC(movie.fp);
    movie.header->Reserved[1] = GET_FCC(movie.fp);
    movie.header->Reserved[2] = GET_FCC(movie.fp);
    movie.header->Reserved[3] = GET_FCC(movie.fp);

Within the same function, the application will then check to see if the number of streams are they are greater or equal to 1 [5]. This checks that a file is not malformed due to having a signed value in the field. However, immediately afterwards the application will multiply this value by the size of an AviStreamRec header. When compiled, this size is 0x4c bytes in length. Due to a failure to accommodate for the overflow, this multiply can cause the size to wrap which may result in a size that is smaller then an AviStreamRec being used for an allocation.

source/blender/avi/intern/avi.c:288
    if (movie.header->Streams < 1) {                // [5]
        DEBUG_PRINT("streams less than 1\n");
        fclose(movie.fp);
        return 0;
    }
    
    movie.streams = (AviStreamRec *) MEM_callocN(sizeof(AviStreamRec) * movie.header->Streams, "moviestreams");         // [6]

Later, the application will then use the number of streams as a terminator for a loop [7] in order to read contents from the file into the movie.streams buffer. If the number of streams multiplied by the size of an AviStreamRec (0x4c) is larger than 32-bits, then this loop will write outside the bounds of the buffer leading to a heap-based buffer overflow.

src/source/blender/avi/intern/avi.c:296
    for (temp = 0; temp < movie.header->Streams; temp++) {      // [7]
...
        movie.streams[temp].sh.Type = GET_FCC(movie.fp);
        movie.streams[temp].sh.Handler = GET_FCC(movie.fp);

        fcca = movie.streams[temp].sh.Handler;
        
...
        movie.streams[temp].sh.Flags = GET_FCC(movie.fp);
        movie.streams[temp].sh.Priority = GET_TCC(movie.fp);
        movie.streams[temp].sh.Language = GET_TCC(movie.fp);
        movie.streams[temp].sh.InitialFrames = GET_FCC(movie.fp);
        movie.streams[temp].sh.Scale = GET_FCC(movie.fp);
        movie.streams[temp].sh.Rate = GET_FCC(movie.fp);
        movie.streams[temp].sh.Start = GET_FCC(movie.fp);
        movie.streams[temp].sh.Length = GET_FCC(movie.fp);
        movie.streams[temp].sh.SuggestedBufferSize = GET_FCC(movie.fp);
        movie.streams[temp].sh.Quality = GET_FCC(movie.fp);
        movie.streams[temp].sh.SampleSize = GET_FCC(movie.fp);
        movie.streams[temp].sh.left = GET_TCC(movie.fp);
        movie.streams[temp].sh.top = GET_TCC(movie.fp);
        movie.streams[temp].sh.right = GET_TCC(movie.fp);
        movie.streams[temp].sh.bottom = GET_TCC(movie.fp);
...
    }
    
    MEM_freeN(movie.streams);
    fclose(movie.fp);

    /* at least one video track is needed */
    return (movie_tracks != 0); 

}

Crash Information

(76c.2520): 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=146c0fdc ecx=00000000 edx=0b42d8c0 esi=00000000 edi=04335298 eip=0172b39a esp=0490f1d4 ebp=0490f288 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+0x4010a: 0172b39a 894324 mov dword ptr [ebx+24h],eax ds:002b:146c1000=????????

0:000> !heap -p -a @ebx address 146c0fdc found in _DPH_HEAP_ROOT @ 9251000 in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize) 14622784: 146c0fd8 24 - 146c0000 2000

Exploit Proof-of-Concept

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 .avi file to.

$ python poc.py $FILENAME.avi

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.avi

Mitigation

In order to mitigate this vulnerability, it is recommended to not use untrusted animation files as an asset when composing a scene.

Timeline

2017-09-06 - Vendor Disclosure
2017-01-11 - Public Release

Credit

Discovered by a member of Cisco Talos.