Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <54BE7D7A.3010700@cs.uni-goettingen.de>
Date: Tue, 20 Jan 2015 17:08:26 +0100
From: Fabian Yamaguchi <fabian.yamaguchi@...uni-goettingen.de>
To: <oss-security@...ts.openwall.com>
CC: konrad Rieck <konrad.rieck@...-goettingen.de>
Subject: Vulnerabilities in VLC 2.1.5

Hi oss-security,

(please note, I'm not on the list.)

I recently discovered a couple of vulnerabilities in the latest stable
version of VLC (2.1.5), reported them to the developers and also
provided patches, most of which were applied. The most critical issues
are a buffer-overflow in the mp4-demuxer and another in the automatic
updater. For the last flaw, I also showed at 31C3 that it can indeed
be leveraged for arbitrary code execution.

Below you find links to the patches. Please note, that patches were
applied for the master-branch, so they may not all be immediately
applicable to 2.1.5. However, the attached original bug reports give
you all the details for 2.1.5.

* Buffer overflow in updater:

https://github.com/videolan/vlc/commit/fbe2837bc80f155c001781041a54c58b5524fc14

* Buffer overflow in mp4 demuxer:

https://github.com/videolan/vlc/commit/2e7c7091a61aa5d07e7997b393d821e91f593c39

* Potential buffer overflow in Schroedinger Encoder

https://github.com/videolan/vlc/commit/9bb0353a5c63a7f8c6fc853faa3df4b4df1f5eb5

* Invalid memory access in rtp code:

https://github.com/videolan/vlc/commit/204291467724867b79735c0ee3aeb0dbc2200f97

* Null-pointer dereference in dmo codec:

https://github.com/videolan/vlc/commit/229c385a79d48e41687fae8b4dfeaeef9c8c3eb7

I was wondering whether anybody could assign CVEs for these vulnerabilities.

Please note that the following problems were not fixed:

* The potential buffer overflow in the Dirac Encoder was not fixed as
  the Dirac encoder no longer exists in the master branch.
* The potential invalid writes in modules/services_discovery/sap.c and
  modules/access/ftp.c were not fixed as I did not provide a
  trigger. Note, that the code looks very similar to the confirmed bug
  in rtp_packetize_xiph_config, and so I leave it to you to decide
  whether you want to patch this.

I have not attached the triggers mentioned in the report. If anybody is
interested in these, please let me know.

Kind Regards,
Fabian Yamaguchi - University of Goettingen



Original Bug Reports
=====================

Buffer Overflow in MP4 Demuxer
===============================

The function MP4_ReadBox_name in modules/demux/mp4/libmp4.c contains a
buffer overflow. The following shows the vulnerable code:

---- snip ----

static int MP4_ReadBox_name( stream_t *p_stream, MP4_Box_t *p_box )
{
    MP4_READBOX_ENTER( MP4_Box_data_name_t );

    p_box->data.p_name->psz_text = malloc( p_box->i_size + 1 - 8 ); //(1)
    if( p_box->data.p_name->psz_text == NULL )
        MP4_READBOX_EXIT( 0 );

    memcpy( p_box->data.p_name->psz_text, p_peek, p_box->i_size - 8 ); //(2)
    p_box->data.p_name->psz_text[p_box->i_size - 8] = '\0';

#ifdef MP4_VERBOSE
        msg_Dbg( p_stream, "read box: \"name\" text=`%s'",
                 p_box->data.p_name->psz_text );
#endif
    MP4_READBOX_EXIT( 1 );
}

---- /snip ----

The attacker controls the size of the MP4 Box (p_box->i_size). If set
to 7, the argument passed to malloc at (1) is 0, and hence, the buffer
p_box->data.p_name->psz_text is close to 0 bytes long. The call to
memcpy at //(2) then copies "(size_t) -1" bytes into the buffer,
causing an overflow. We have attached the file crafted-mp4.mp4 that
triggers this issue. To reproduce it, you can place the file in your
working directory and run: vlc ./crafted-mp4.mp4. On our test systems,
this produces the following stack-trace:

---- snip ----

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fffee04a700 (LWP 1349)]
0x00007ffff68858e2 in __memcpy_avx_unaligned () from /usr/lib/libc.so.6
(gdb) bt
#0  0x00007ffff68858e2 in __memcpy_avx_unaligned () from /usr/lib/libc.so.6
#1  0x00007fffedb36fc2 in MP4_ReadBox_name (p_stream=0x7fffdcc04a08,
p_box=0x7fffdcd2b840) at mp4/libmp4.c:2603
#2  0x00007fffedb39e9f in MP4_ReadBox (p_stream=0x7fffdcc04a08,
p_father=0x7fffdcd2b5f0) at mp4/libmp4.c:3428
#3  0x00007fffedb28ba5 in MP4_ReadBoxContainerChildren
(p_stream=0x7fffdcc04a08, p_container=0x7fffdcd2b5f0,
    i_last_child=0) at mp4/libmp4.c:210
#4  0x00007fffedb28c55 in MP4_ReadBoxContainerRaw
(p_stream=0x7fffdcc04a08, p_container=0x7fffdcd2b5f0)
    at mp4/libmp4.c:230
#5  0x00007fffedb28cd5 in MP4_ReadBoxContainer (p_stream=0x7fffdcc04a08,
p_container=0x7fffdcd2b5f0)
    at mp4/libmp4.c:246
#6  0x00007fffedb39e9f in MP4_ReadBox (p_stream=0x7fffdcc04a08,
p_father=0x7fffdcd2b4e0) at mp4/libmp4.c:3428
#7  0x00007fffedb28ba5 in MP4_ReadBoxContainerChildren
(p_stream=0x7fffdcc04a08, p_container=0x7fffdcd2b4e0,
    i_last_child=0) at mp4/libmp4.c:210
#8  0x00007fffedb28c55 in MP4_ReadBoxContainerRaw
(p_stream=0x7fffdcc04a08, p_container=0x7fffdcd2b4e0)
    at mp4/libmp4.c:230
#9  0x00007fffedb37e61 in MP4_ReadBox_meta (p_stream=0x7fffdcc04a08,
p_box=0x7fffdcd2b4e0) at mp4/libmp4.c:2818
#10 0x00007fffedb39e9f in MP4_ReadBox (p_stream=0x7fffdcc04a08,
p_father=0x7fffdcd2b480) at mp4/libmp4.c:3428
#11 0x00007fffedb28ba5 in MP4_ReadBoxContainerChildren
(p_stream=0x7fffdcc04a08, p_container=0x7fffdcd2b480,
    i_last_child=0) at mp4/libmp4.c:210
#12 0x00007fffedb28c55 in MP4_ReadBoxContainerRaw
(p_stream=0x7fffdcc04a08, p_container=0x7fffdcd2b480)
    at mp4/libmp4.c:230
#13 0x00007fffedb28cd5 in MP4_ReadBoxContainer (p_stream=0x7fffdcc04a08,
p_container=0x7fffdcd2b480)
    at mp4/libmp4.c:246
#14 0x00007fffedb39e9f in MP4_ReadBox (p_stream=0x7fffdcc04a08,
p_father=0x7fffdcc05810) at mp4/libmp4.c:3428
#15 0x00007fffedb28ba5 in MP4_ReadBoxContainerChildren
(p_stream=0x7fffdcc04a08, p_container=0x7fffdcc05810,
    i_last_child=0) at mp4/libmp4.c:210
#16 0x00007fffedb28c55 in MP4_ReadBoxContainerRaw
(p_stream=0x7fffdcc04a08, p_container=0x7fffdcc05810)
    at mp4/libmp4.c:230
#17 0x00007fffedb28cd5 in MP4_ReadBoxContainer (p_stream=0x7fffdcc04a08,
p_container=0x7fffdcc05810)
    at mp4/libmp4.c:246
---Type <return> to continue, or q <return> to quit---
#18 0x00007fffedb39e9f in MP4_ReadBox (p_stream=0x7fffdcc04a08,
p_father=0x7fffdcc056c0) at mp4/libmp4.c:3428
#19 0x00007fffedb28ba5 in MP4_ReadBoxContainerChildren
(p_stream=0x7fffdcc04a08, p_container=0x7fffdcc056c0,
    i_last_child=1987014509) at mp4/libmp4.c:210
#20 0x00007fffedb3a345 in MP4_BoxGetRoot (s=0x7fffdcc04a08) at
mp4/libmp4.c:3592
#21 0x00007fffedb1e8ef in LoadInitFrag (p_demux=0x7fffdcc04c38,
b_smooth=false) at mp4/mp4.c:235
#22 0x00007fffedb1edaa in Open (p_this=0x7fffdcc04c38) at mp4/mp4.c:361

We believe that this flaw is exploitable for arbitrary code execution,
albeit it may not be easy. Since VLC is a multi-threaded application,
techniques similar to those used in the exploit for Apache
mod-setenvif described in
http://www.halfdog.net/Security/2011/ApacheModSetEnvIfIntegerOverflow/DemoExploit.html
can possibly be leveraged to achieve this.

---- /snip ----

Buffer Overlow in the Updater
=============================

I have one more buffer overflow to report in the updater affecting both
the stable release and the master branch. A possible patch against the
master branch is attached.

The function `GetUpdateFile` in src/misc/update.c contains a buffer
overflow due to an integer truncation. At (1) the length of the
update-file downloaded from the update server is stored in a 64 bit
integer. While adding '1' to this integer inside the argument to
malloc at (2) at first succeeds, to pass the value to malloc, it is
cast to size_t. On 32 bit platforms (or 64 bit platforms running the
32 bit version of VLC), size_t is 4 byte wide, and thus, a truncation
occurs. If the file size is chosen to be 0xffffffff, then close to 0
bytes are allocated. The call to stream_Read at (3) then copies
0xffffffff bytes into this buffer, causing an overflow.

static bool GetUpdateFile( update_t *p_update )
{
    stream_t *p_stream = NULL;
    char *psz_version_line = NULL;
    char *psz_update_data = NULL;

    p_stream = stream_UrlNew( p_update->p_libvlc, UPDATE_VLC_STATUS_URL );
    if( !p_stream )
    {
        msg_Err( p_update->p_libvlc, "Failed to open %s for reading",
                 UPDATE_VLC_STATUS_URL );
        goto error;
    }

    const int64_t i_read = stream_Size( p_stream ); // (1)
    psz_update_data = malloc( i_read + 1 ); // (2)
    if( !psz_update_data )
        goto error;

    if( stream_Read( p_stream, psz_update_data, i_read ) != i_read ) //(3)
    {
        msg_Err( p_update->p_libvlc, "Couldn't download update file %s",
                UPDATE_VLC_STATUS_URL );
        goto error;
    }
    psz_update_data[i_read] = '\0';
    ...
}

We have tested whether this vulnerability can be triggered by
redirecting all requests to update.videolan.org to a local web-server
that hosts a file named vlc/status-win-x86 containing 0xffffffff 'A's,
and then clicking on 'Help->Check for updates' in VLC. This bug should
also be triggered when automatically checking for updates, so this
flaw can probably be triggered without user interaction.

Since VLC is a multithreaded application, different results were
observed in the debugger. Most notably, in several runs, the process
crashed with an instruction pointer of 0x41414141 ("AAAA"), which
strongly suggests that this flaw is remotely exploitable for arbitrary
code execution.


Memory corruption in RTP Code
==============================

The function rtp_packetize_xiph_config in modules/stream_out/rtpfmt.c
allows an out-bof-bounds write-access to be caused when streaming a
crafted Ogg Vorbis file RTP. The following excerpt of rtpfmt.c shows
the vulnerable code:

---- snip ----

int rtp_packetize_xiph_config( sout_stream_id_t *id, const char *fmtp,
                               int64_t i_pts )
{
    if (fmtp == NULL)
        return VLC_EGENERIC;

    /* extract base64 configuration from fmtp */
    char *start = strstr(fmtp, "configuration=");
    assert(start != NULL);
    start += sizeof("configuration=") - 1;
    char *end = strchr(start, ';');
    assert(end != NULL);
    size_t len = end - start;   // (0)
    char b64[len + 1];		// (1)
    memcpy(b64, start, len);	// (2)
    b64[len] = '\0';
    ...
}

---- /snip ----

rtp_packetize_xiph_config allocates a buffer on the stack at (1) where
the size depends on the local variable 'len'. The variable 'len' is
calculated at (0) to be the length of a string contained in the Ogg
Vorbis file, and therefore, it is attacker-controlled. If the amount
of stack memory is not sufficient to hold the buffer (a 'len' value of
1052896 byte was enough in our tests on a 64bit Linux machine with 8
GB RAM), the start of the buffer will point to a location outside of
the stack. Subsequently copying data into the buffer at (2) will then
corrupt non-stack memory.

We have attached a test-case (input786432.ogg) that highlights this
problem by causing an invalid memory access at (2). The crash can be
triggered by placing the attached file in the working directory and
running the following command:

vlc input786432.ogg --sout
'#rtp{dst=localhost,sdp=rtsp://localhost:8080/foo.sdp}'

This leads to the following stack-trace:

---- snip ----

Program received signal SIGSEGV, Segmentation fault.
0x00007fffc6e87ab1 in memcpy (__len=1052896, __src=0x7fffc0595e3e,
__dest=0x7fffc74a6cc0) at /usr/include/x86_64-linux-gnu/bits/string3.h:51
51	  return __builtin___memcpy_chk (__dest, __src, __len, __bos0 (__dest));
(gdb) where
#0  0x00007fffc6e87ab1 in memcpy (__len=1052896, __src=0x7fffc0595e3e,
__dest=0x7fffc74a6cc0) at /usr/include/x86_64-linux-gnu/bits/string3.h:51
#1  rtp_packetize_xiph_config (id=id@...ry=0x7fffc0004140,
fmtp=<optimized out>, i_pts=4619686105) at rtpfmt.c:563
#2  0x00007fffc6e81412 in Send (p_stream=<optimized out>,
id=0x7fffc0004140, p_buffer=0x7fffdcde0650) at rtp.c:1283
#3  0x00007ffff796f8c6 in sout_InputSendBuffer (p_input=0x7fffc0000ad0,
p_buffer=p_buffer@...ry=0x7fffdcde0650) at stream_output/stream_output.c:235
#4  0x00007ffff791b1ed in DecoderPlaySout (p_sout_block=0x7fffdcde0650,
p_dec=0x7fffdd0a2828) at input/decoder.c:1467
#5  DecoderProcessSout (p_block=0x0, p_dec=0x7fffdd0a2828) at
input/decoder.c:1523
#6  DecoderProcess (p_block=<optimized out>, p_dec=0x7fffdd0a2828) at
input/decoder.c:1754
#7  DecoderThread (p_data=0x7fffdd0a2828) at input/decoder.c:896
#8  0x00007ffff6d4b0a4 in start_thread (arg=0x7fffc75a8700) at
pthread_create.c:309
#9  0x00007ffff687bccd in clone () at
../sysdeps/unix/sysv/linux/x86_64/clone.S:111

---- /snip ----

We also found the following two other functions that employ the same
problematic programming pattern and should probably be patched as
well:

modules/services_discovery/sap.c:

---- snip ----

static sdp_t *ParseSDP (vlc_object_t *p_obj, const char *psz_sdp)
{
    // ...

    /* TODO: use iconv and charset attribute instead of EnsureUTF8 */
    while (*psz_sdp)
    {
        /* Extract one line */
        char *eol = strchr (psz_sdp, '\n');
        size_t linelen = eol ? (size_t)(eol - psz_sdp) : strlen (psz_sdp);
        char line[linelen + 1];
        memcpy (line, psz_sdp, linelen);
        line[linelen] = '\0';
	// ...
    }
// ...
}

---- /snip ----

modules/access/ftp.c:

---- snip ----
static int ftp_SendCommand( vlc_object_t *obj, access_sys_t *sys,
                            const char *fmt, ... )
{
    size_t fmtlen = strlen( fmt );
    char fmtbuf[fmtlen + 3];

    memcpy( fmtbuf, fmt, fmtlen );
    memcpy( fmtbuf + fmtlen, "\r\n", 3 );

    // ...
}

---- /snip ----

Potential buffer overflow in Dirac/Schroedinger Encoder
=======================================================

The function Encode in modules/codec/dirac.c may allow an attacker to
cause a buffer overflow on 32 bit systems. At (1), a raw 32 bit value
is read using the macro GetDWBE and stored in the local variable
'len', which is then passed to malloc as a first argument at
(2). However, before allocation, 'sizeof(eos)' is added to the
length. If the length is chosen to be 0xffffffff, the summation will
overflow, resulting in the allocation of a small buffer. Subsequently
copying 'len' bytes into the small buffer can then cause an overflow
at (3).

---- snip ----

static block_t *Encode( encoder_t *p_enc, picture_t *p_pic )
{
// ...

            /* Presence of a Sequence header indicates a seek point */
            if( 0 == p_block->p_buffer[4] )
            {
                p_block->i_flags |= BLOCK_FLAG_TYPE_I;

                if( !p_enc->fmt_out.p_extra ) {
                    const uint8_t eos[] = {
'B','B','C','D',0x10,0,0,0,13,0,0,0,0 };
                    uint32_t len = GetDWBE( p_block->p_buffer + 5 ); (1)
                    // ...
                    p_enc->fmt_out.p_extra = malloc( len + sizeof(eos));
//(2)
                    if( !p_enc->fmt_out.p_extra )
                        return NULL;
                    memcpy( p_enc->fmt_out.p_extra,
p_block->p_buffer,len); //(3)
                    memcpy( (uint8_t*)p_enc->fmt_out.p_extra + len, eos,
sizeof(eos) );
                    SetDWBE( (uint8_t*)p_enc->fmt_out.p_extra + len +
10, len );
                    p_enc->fmt_out.i_extra = len + sizeof(eos);
                }
            }
// ...
}

---- /snip ----

The same code can be found in function Encode in
modules/codec/schroedinger.c. We have not built a trigger for this
issue, however, if it can be triggered, we suspect that it can be
leveraged for arbitrary code execution just like the vulnerability in
the MP4 demuxer.

Minor issues (Null-pointer dereferences)
=======================================

We also found the following minor issuesthat we believe can at most
result in a null-pointer dereference and thus, a crash. For the sake
of completeness, we report them as well.

The allocations at (1)-(6) in the function TrackCreateES in
/modules/demux/mp4/mp4.c are not checked, possibly resulting in
subsequent null-pointer dereferences when calling memcpy in the
respective next line.

---- snip ----
/*
 * TrackCreateES:
 * Create ES and PES to init decoder if needed, for a track starting at
i_chunk
 */
static int TrackCreateES( demux_t *p_demux, mp4_track_t *p_track,
                          unsigned int i_chunk, es_out_id_t **pp_es )
{
    // ...
	
    if( ( ( p_esds = MP4_BoxGet( p_sample, "esds" ) ) ||
          ( p_esds = MP4_BoxGet( p_sample, "wave/esds" ) ) )&&
        ( p_esds->data.p_esds )&&
        ( p_decconfig ) )
    {

	// ...

        if( p_track->fmt.i_extra > 0 )
        {
            p_track->fmt.p_extra = malloc( p_track->fmt.i_extra ); // (1)
            memcpy( p_track->fmt.p_extra,
p_decconfig->p_decoder_specific_info,
                    p_track->fmt.i_extra );
        }
    }
    else
    {
	// ...

                if( p_track->fmt.i_extra > 0 )
                {
                    p_track->fmt.p_extra = malloc( p_track->fmt.i_extra
);//(2)
                    memcpy( p_track->fmt.p_extra,

p_sample->data.p_sample_vide->p_qt_image_description,
                            p_track->fmt.i_extra); // (7)
                }
                break;

          	// ...
                if( p_track->fmt.i_extra > 0 )
                {
                    p_track->fmt.p_extra = malloc( p_track->fmt.i_extra
);//(3)
                    memcpy( p_track->fmt.p_extra,
                            p_sample->data.p_sample_soun->p_qt_description,
                            p_track->fmt.i_extra); // (8)
                }
                if( p_track->fmt.i_extra == 56 && p_sample->i_type ==
VLC_CODEC_ALAC )
                {
                    p_track->fmt.audio.i_channels =
*((uint8_t*)p_track->fmt.p_extra + 41);
                    p_track->fmt.audio.i_rate =
GetDWBE((uint8_t*)p_track->fmt.p_extra + 52);
                }
                break;

            case VLC_FOURCC( 'v', 'c', '-', '1' ):
            {
                MP4_Box_t *p_dvc1 = MP4_BoxGet( p_sample, "dvc1" );
                if( p_dvc1 )
                {
                    p_track->fmt.i_extra = p_dvc1->data.p_dvc1->i_vc1;
                    if( p_track->fmt.i_extra > 0 )
                    {
                        p_track->fmt.p_extra = malloc(
p_dvc1->data.p_dvc1->i_vc1 );//(4)
                        memcpy( p_track->fmt.p_extra,
p_dvc1->data.p_dvc1->p_vc1,
                                p_track->fmt.i_extra );
                    }
                }
                else
                {
                    msg_Err( p_demux, "missing dvc1" );
                }
                break;
            }

            /* avc1: send avcC (h264 without annexe B, ie without start
code)*/
            case VLC_FOURCC( 'a', 'v', 'c', '1' ):
            {
                MP4_Box_t *p_avcC = MP4_BoxGet( p_sample, "avcC" );

                if( p_avcC )
                {
                    p_track->fmt.i_extra = p_avcC->data.p_avcC->i_avcC;
                    if( p_track->fmt.i_extra > 0 )
                    {
                        p_track->fmt.p_extra = malloc(
p_avcC->data.p_avcC->i_avcC );//(5)
                        memcpy( p_track->fmt.p_extra,
p_avcC->data.p_avcC->p_avcC,
                                p_track->fmt.i_extra );
                    }
                }
                else
                {
                    msg_Err( p_demux, "missing avcC" );
                }
                break;
            }
            case VLC_FOURCC( 'h', 'v', 'c', '1' ):
            {
                MP4_Box_t *p_hvcC = MP4_BoxGet( p_sample, "hvcC" );

                if( p_hvcC )
                {
                    p_track->fmt.i_extra = p_hvcC->data.p_hvcC->i_hvcC;
                    if( p_track->fmt.i_extra > 0 )
                    {
                        p_track->fmt.p_extra = malloc(
p_hvcC->data.p_hvcC->i_hvcC );//(6)
                        memcpy( p_track->fmt.p_extra,
p_hvcC->data.p_hvcC->p_hvcC,
                                p_track->fmt.i_extra );
                    }
                    p_track->fmt.i_codec = VLC_CODEC_HEVC;
                }
                else
                {
                    msg_Err( p_demux, "missing hvcC" );
                }
                break;
            }

         // ...
        }
    }

#undef p_decconfig

    if( pp_es )
        *pp_es = es_out_Add( p_demux->out, &p_track->fmt );

    return VLC_SUCCESS;
}

---- /snip ----

In function EncoderSetAudioType in modules/codec/dmo/dmo.c, the
allocation at (1) is not checked, possibly resulting a null-pointer
dereference in the subsequent call to memcpy.

static int EncoderSetAudioType( encoder_t *p_enc, IMediaObject *p_dmo )
{
	// ...
	if( p_wf->cbSize )
	{
        	msg_Dbg( p_enc, "found cbSize: %i", p_wf->cbSize );
        	p_enc->fmt_out.i_extra = p_wf->cbSize;
        	p_enc->fmt_out.p_extra = malloc(p_enc->fmt_out.i_extra );//(1)
        	memcpy( p_enc->fmt_out.p_extra, &p_wf[1], p_enc->fmt_out.i_extra );
        }
	// ...
}

Powered by blists - more mailing lists

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.