CVE-2017-17670: vlc: type conversion vulnerability
Dec 15, 2017About
A type conversion vulnerability exist in the MP4 demux module in VLC <=2.2.8. This issue has been assigned CVE-2017-17670 and it could be used to cause an arbitrary free.
Details
MP4 is a container format for video, audio, subtitles and images. The various parts of an .mp4 are organized as hierarchical boxes/atoms in big-endian byte ordering [1].
VLC processes these boxes by using a lookup table:
vlc-2.2.8/modules/demux/mp4/libmp4.c
3297 static const struct
3298 {
3299 uint32_t i_type;
3300 int (*MP4_ReadBox_function )( stream_t *p_stream, MP4_Box_t *p_box );
3301 void (*MP4_FreeBox_function )( MP4_Box_t *p_box );
3302 uint32_t i_parent; /* set parent to restrict, duplicating if needed; 0 for any */
3303 } MP4_Box_Function [] =
3304 {
3305 /* Containers */
3306 { ATOM_moov, MP4_ReadBoxContainer, MP4_FreeBox_Common, 0 },
3307 { ATOM_trak, MP4_ReadBoxContainer, MP4_FreeBox_Common, ATOM_moov },
....
3565 /* Last entry */
3566 { 0, MP4_ReadBox_default, NULL, 0 }
3567 };
vlc-2.2.8/modules/demux/mp4/libmp4.c
3574 static MP4_Box_t *MP4_ReadBox( stream_t *p_stream, MP4_Box_t *p_father )
3575 {
3576 MP4_Box_t *p_box = calloc( 1, sizeof( MP4_Box_t ) ); /* Needed to ensure simple on error handler */
3577 unsigned int i_index;
....
3582 if( !MP4_ReadBoxCommon( p_stream, p_box ) )
3583 {
....
3587 }
....
3605 /* Now search function to call */
3606 for( i_index = 0; ; i_index++ )
3607 {
....
3613 if( ( MP4_Box_Function[i_index].i_type == p_box->i_type )||
3614 ( MP4_Box_Function[i_index].i_type == 0 ) )
3615 {
3616 break;
3617 }
3618 }
3619
3620 if( !(MP4_Box_Function[i_index].MP4_ReadBox_function)( p_stream, p_box ) )
3621 {
3622 MP4_BoxFree( p_stream, p_box );
3623 return NULL;
3624 }
3625
3626 return p_box;
3627 }
MP4_ReadBox() allocates a MP4_Box_t structure and invokes
MP4_ReadBoxCommon() to read the properties common to all mp4 boxes;
i_size
and i_type
(and optionally an extended size). Afterwards,
MP4_Box_Function is used to dispatch further parsing to a suitable
function based on its i_type
.
When VLC is done with the boxes, they are freed with MP4_BoxFree():
vlc-2.2.8/modules/demux/mp4/libmp4.c
3633 void MP4_BoxFree( stream_t *s, MP4_Box_t *p_box )
3634 {
3635 unsigned int i_index;
....
3650 /* Now search function to call */
3651 if( p_box->data.p_payload )
3652 {
3653 for( i_index = 0; ; i_index++ )
3654 {
....
3660 if( ( MP4_Box_Function[i_index].i_type == p_box->i_type )||
3661 ( MP4_Box_Function[i_index].i_type == 0 ) )
3662 {
3663 break;
3664 }
3665 }
3666 if( MP4_Box_Function[i_index].MP4_FreeBox_function == NULL )
3667 {
....
3677 }
3678 else
3679 {
3680 MP4_Box_Function[i_index].MP4_FreeBox_function( p_box );
3681 }
....
3685 }
Again, i_type
is used to find a suitable free-function.
The reason this may be problematic is that i_type
could be changed
when VLC handles sinf
and frma
boxes in TrackCreateES() – meaning
that a box may be read as one type, and freed as another.
sinf
is the “Protection Scheme Information Box” and it’s used for
protected/encrypted media. frma
is the “Original Format Box” and it’s
used to declare the format of the unprotected media.
If a sinf/frma is found underneath a sample box, the i_type
of that
sample is replaced with the original format declared in the frma
:
vlc-2.2.8/modules/demux/mp4/mp4.c
2180 static int TrackCreateES( demux_t *p_demux, mp4_track_t *p_track,
2181 unsigned int i_chunk, es_out_id_t **pp_es )
2182 {
....
2208 p_sample = MP4_BoxGet( p_track->p_stsd, "[%d]",
2209 i_sample_description_index - 1 );
....
2219 p_track->p_sample = p_sample;
2220
2221 if( ( p_frma = MP4_BoxGet( p_track->p_sample, "sinf/frma" ) ) && p_frma->data.p_frma )
2222 {
2223 msg_Warn( p_demux, "Original Format Box: %4.4s", (char *)&p_frma->data.p_frma->i_type );
2224
2225 p_sample->i_type = p_frma->data.p_frma->i_type;
2226 }
....
No sanity check is done to make sure that the MP4_FreeBox_function
associated with the new i_type
is compatible with the old
MP4_ReadBox_function.
Example
One way to abuse the type change is to have a soun
changed to a
vide
. This results in a 72-byte allocation (x86-64) for the
p_sample_soun
member of the p_box->data union when the box is read:
vlc-2.2.8/modules/demux/mp4/libmp4.c
1614 static int MP4_ReadBox_sample_soun( stream_t *p_stream, MP4_Box_t *p_box )
1615 {
1616 p_box->i_handler = ATOM_soun;
1617 MP4_READBOX_ENTER( MP4_Box_data_sample_soun_t );
....
vlc-2.2.8/modules/demux/mp4/libmp4.h
1351 #define MP4_READBOX_ENTER( MP4_Box_data_TYPE_t ) \
....
1369 if( !( p_box->data.p_payload = calloc( 1, sizeof( MP4_Box_data_TYPE_t ) ) ) ) \
1370 { \
....
1373 }
where p_box
is MP4_Box_t:
vlc-2.2.8/modules/demux/mp4/libmp4.h
1284 typedef struct MP4_Box_s
1285 {
....
1296 MP4_Box_data_t data; /* union of pointers on extended data depending
1297 on i_type (or i_usertype) */
....
1306 } MP4_Box_t;
and MP4_Box_data_t:
vlc-2.2.8/modules/demux/mp4/libmp4.h
1200 typedef union MP4_Box_data_s
1201 {
....
1220 MP4_Box_data_sample_vide_t *p_sample_vide;
1221 MP4_Box_data_sample_soun_t *p_sample_soun;
....
1278 void *p_payload; /* for unknow type */
1279 } MP4_Box_data_t;
(gdb) p sizeof(MP4_Box_data_sample_soun_t)
$1 = 72
After the box has had its type changed to vide
and it’s later freed,
the p_sample_vide
member of the p_box->data union is used:
vlc-2.2.8/modules/demux/mp4/libmp4.c
1861 void MP4_FreeBox_sample_vide( MP4_Box_t *p_box )
1862 {
1863 FREENULL( p_box->data.p_sample_vide->p_qt_image_description );
1864 }
(gdb) p sizeof(MP4_Box_data_sample_vide_t)
$2 = 96
(gdb)
vlc-2.2.8/modules/demux/mp4/libmp4.h
529 typedef struct MP4_Box_data_sample_vide_s
530 {
...
557 uint8_t *p_qt_image_description;
558
559 } MP4_Box_data_sample_vide_t;
p_sample_vide
is 24 bytes larger than p_sample_soun
, and
p_qt_image_description
is at the end of the vide struct; i.e. the
pointer to be free()d is read out-of-bounds from potentially
user-controlled memory.
mkmp4.py
at [2]
$ uname -imrs
FreeBSD 11.1-RELEASE-p4 amd64 GENERIC
$ ./mkmp4.py file.mp4
$ vlc --version
VLC media player 2.2.8 Weatherwax (revision 2.2.7-14-g3cc1d8cba9)
$ gdb -q --args vlc file.mp4
(gdb) set breakpoint pending on
(gdb) b libmp4.c:1618
No source file named libmp4.c.
Breakpoint 1 (libmp4.c:1618) pending.
(gdb) b libmp4.c:1863
No source file named libmp4.c.
Breakpoint 2 (libmp4.c:1863) pending.
(gdb) r
[...]
Breakpoint 3, MP4_ReadBox_sample_soun (p_stream=0x802ab2710, p_box=0x802a85000) at demux/mp4/libmp4.c:1618
1618 p_box->data.p_sample_soun->p_qt_description = NULL;
(gdb) p p_box->data.p_sample_soun
$1 = (MP4_Box_data_sample_soun_t *) 0x802a79810
(gdb) c
Continuing.
Breakpoint 4, MP4_FreeBox_sample_vide (p_box=0x802a85000) at demux/mp4/libmp4.c:1863
1863 FREENULL( p_box->data.p_sample_vide->p_qt_image_description );
(gdb) p p_box->data.p_sample_vide
$2 = (MP4_Box_data_sample_vide_t *) 0x802a79810
(gdb) p p_box->data.p_sample_vide->p_qt_image_description
$3 = (uint8_t *) 0x1122334455667788 <Error reading address 0x1122334455667788: Bad address>
(gdb) b free
Breakpoint 5 at 0x8019d3ce4
(gdb) c
Continuing.
Breakpoint 5, 0x00000008019d3ce4 in free () from /lib/libc.so.7
(gdb) p/x $rdi
$4 = 0x1122334455667788
(gdb) c
Continuing.
Program received signal SIGBUS, Bus error.
0x00000008019d36f3 in realloc () from /lib/libc.so.7
(gdb) x/i $rip
0x8019d36f3 <realloc+3939>: mov rbx,QWORD PTR [rax+rcx*8+0x68]
(gdb) i r
rax 0x1122334455600000 1234605616436084736
rbx 0x1122334455667788 1234605616436508552
rcx 0x5a 90
[...]
(gdb) bt 4
#0 0x00000008019d36f3 in realloc () from /lib/libc.so.7
#1 0x00000008019d3d51 in free () from /lib/libc.so.7
#2 0x0000000806d7fafd in MP4_FreeBox_sample_vide (p_box=0x802a85000) at demux/mp4/libmp4.c:1863
#3 0x0000000806d7fcfd in MP4_BoxFree (s=0x802ab2710, p_box=0x802a85000) at demux/mp4/libmp4.c:3680
Solution
This issue does not affect the HEAD of the VLC master branch.