Attacker Value
Unknown
(1 user assessed)
Exploitability
Unknown
(1 user assessed)
User Interaction
Unknown
Privileges Required
Unknown
Attack Vector
Unknown
0

VLC zlib_decompress_extra Double Free Vulnerability

Disclosure Date: June 18, 2019
Add MITRE ATT&CK tactics and techniques that apply to this CVE.

Description

An issue was discovered in zlib_decompress_extra in modules/demux/mkv/util.cpp in VideoLAN VLC media player 3.x through 3.0.7. The Matroska demuxer, while parsing a malformed MKV file type, has a double free.

Add Assessment

2
Technical Analysis

CVE-2019-12874 VLC zlib_decompress_extra Double Free Vulnerability

VLC media player is a free and open-source portable cross-platform media player software developed by the VideoLAN project. VLC is available for desktop operating systems and mobile platforms, such as Android, iOS, iPadOS, Wizen, Windows 10 Mobile, and Windows Phone. It is also available on digital distribution platforms such as Apple’s App Store, Google Play, and Microsoft Store. It supports many audio and video compression methods and file formats, and can be used to stream media over computer networks.

A vulnerability was found in VLC’s lib_decompress_extra function, where the p_track parameter is freed and later deleted, which results a double free condition.

Technical Details

The lib_decompress_extra function can be found in modules/demux/mkv/util.cpp:

int32_t zlib_decompress_extra( demux_t * p_demux, mkv_track_t & tk )
{
    int result;
    z_stream d_stream;
    size_t n = 0;
    uint8_t * p_new_extra = NULL;

    msg_Dbg(p_demux,"Inflating private data");

    d_stream.zalloc = Z_NULL;
    d_stream.zfree = Z_NULL;
    d_stream.opaque = Z_NULL;
    if( inflateInit( &d_stream ) != Z_OK )
    {
        msg_Err( p_demux, "Couldn't initiate inflation ignore track %u",
                 tk.i_number );
        return 1;
    }

    d_stream.next_in = tk.p_extra_data;
    d_stream.avail_in = tk.i_extra_data;
    do
    {
        n++;
        void *alloc = realloc(p_new_extra, n*1024);
        if( alloc == NULL )
        {
            msg_Err( p_demux, "Couldn't allocate buffer to inflate data, ignore track %u",
                      tk.i_number );
            free(p_new_extra);
            inflateEnd( &d_stream );
            return 1;
        }

        p_new_extra = static_cast<uint8_t *>( alloc );
        d_stream.next_out = &p_new_extra[(n - 1) * 1024];
        d_stream.avail_out = 1024;
        result = inflate(&d_stream, Z_NO_FLUSH);
        if( result != Z_OK && result != Z_STREAM_END )
        {
            msg_Err( p_demux, "Zlib decompression failed. Result: %d", result );
            inflateEnd( &d_stream );
            free(p_new_extra);
            return 1;
        }
    }
    while ( d_stream.avail_out == 0 && d_stream.avail_in != 0  &&
            result != Z_STREAM_END );

    free( tk.p_extra_data );
    tk.i_extra_data = d_stream.total_out;
    p_new_extra = static_cast<uint8_t *>( realloc(p_new_extra, tk.i_extra_data) );
    if( !p_new_extra )
    {
        msg_Err( p_demux, "Couldn't allocate buffer to inflate data, ignore track %u",
                 tk.i_number );
        inflateEnd( &d_stream );
        return 1;
    }

    tk.p_extra_data = p_new_extra;

    inflateEnd( &d_stream );
    return 0;
}

The specific buggy block of code is narrowed down as follows:

    free( tk.p_extra_data );
    tk.i_extra_data = d_stream.total_out;
    p_new_extra = static_cast<uint8_t *>( realloc(p_new_extra, tk.i_extra_data) );
    if( !p_new_extra )
    {
        msg_Err( p_demux, "Couldn't allocate buffer to inflate data, ignore track %u",
                 tk.i_number );
        inflateEnd( &d_stream );
        return 1;
    }

As you can see, tk.p_extra_data is freed:

free( tk.p_extra_data );

And then if for some reason, the static casting for p_new_extra fails, the function returns with 1. It is hard to say exactly how a realloc failure would occur, typically this is due to out of memory, so that means tk.i_extra_data would have to be something at least bigger than 0x7fffffff on a 32-bit VLC application.

After the free, the zlib_decompress_extra function returns to a function called matroska_segment_c::ParseTrackEntry (found in modules/demux/mkv/matroska_segment_parse.cpp):

void matroska_segment_c::ParseTrackEntry( const KaxTrackEntry *m )
{
  ...
	if( p_track->i_compression_type == MATROSKA_COMPRESSION_ZLIB &&
            p_track->i_encoding_scope & MATROSKA_ENCODING_SCOPE_PRIVATE &&
            p_track->i_extra_data && p_track->p_extra_data &&
            zlib_decompress_extra( &sys.demuxer, *p_track ) )
        {
            msg_Err(&sys.demuxer, "Couldn't handle the track %u compression", p_track->i_number );
            delete p_track;
            return;
        }
  ...

Since the zlib_decompress_extra function returns 1 due to a failure from realloc, it is possible all those if conditions are true, which results the program printing an error message, and then the double free:

delete p_track;

Due to the failure requirement for realloc, an exploit is unlikely.

Patch Information

In the patch, tk_p_extra_data is set to a null pointer, which is safe for delete:

         msg_Err( p_demux, "Couldn't allocate buffer to inflate data, ignore track %u",
                  tk.i_number );
         inflateEnd( &d_stream );
+        tk.p_extra_data = NULL;
         return 1;
     }
 
         return 1;
CVSS V3 Severity and Metrics
Base Score:
None
Impact Score:
Unknown
Exploitability Score:
Unknown
Vector:
Unknown
Attack Vector (AV):
Unknown
Attack Complexity (AC):
Unknown
Privileges Required (PR):
Unknown
User Interaction (UI):
Unknown
Scope (S):
Unknown
Confidentiality (C):
Unknown
Integrity (I):
Unknown
Availability (A):
Unknown

General Information

Vendors

  • videolan

Products

  • vlc media player
Technical Analysis