Multiple vulnerabilities in xine <= 1.1.4

»Recently, the xine-team requested that someone from oCERT review a few patches to xine. I performed that review and a light security code review of other sections. The details of oCERT-2008-008 follow . . .

1. Exploitable heap buffer overflows

A. In demux_qt.c, function parse_moov_atom() reads in metadata atom
sizes as supplied from the user.  If the size of a metadata atom,
like artist, is 0xf, the resulting atom size is 0. Prior to reading
in the atom string, xine_malloc() is called with this undersized
value resulting in an allocation of 1 byte.  The allocated space is
then the target of a strncpy. When called, strncpy() is supplied the
atom string size minus 1.  This wraps the value to UINT_MAX making
strncpy to copy until a (NUL) 0x00 byte is seen. This results in a
highly controllable heap buffer overflow.

B. In demux_matroska.c, the function parse_block_group() is vulnerable
to a heap overflow through the abuse of ebml element length values.
In particular, when processing MATROSKA_ID_CL_BLOCK the element
length is passed into read_block_data().  read_block_data() treats
the length as a signed 32-bit in and passes it to
alloc_block_data().  This function performs a simple check: if the
current block_data_size is less than the supplied length, then it
re/malloc()s the given length. This buffer and the length is then
passed in to the input plugin's read() function.  This opens up a few
avenues of attack.  The first is since the allocation is unchecked, a
NULL value may be read() over - resulting in a segmentation fault in
most cases.  However, if the CL_BLOCK is processed twice, the first
can specify a valid size and provide valid data.  However, a second
CL_BLOCK can give a negative size.  This will cause
alloc_block_data() to not reallocate the buffer, but the input plugin
will still be given the user-supplied length for reading.  This will
result in a heap buffer overflow.  In particular, if the source data
file ends within a reasonable size, the heap buffer can be
overwritten without writing past the available pages to the process.
The read() function will then return an error indicating failure.  If
the length given was -1, then read_block_data() will still evaluate
the read() as success and continue on as if nothing happened.
However, if another negative value is used, the process will return
failure up the stack.

C. In demux_real.c, real_parse_audio_specific_data() uses a
user-supplied height (codec_data_length) as the divisor when
calculating frame_size.  It may be zero, resulting in a divide by
zero. In addition, there is not check for successful frame_buffer
allocation which may result in NULL dereferences later.  In addition
to a divide by zero, if sps is 0 and w and h are both 65535 (they are
read as 16-bit ints), then the frame_size will overflow resulting in
an underallocation. If sps is non-zero, it is also possible to
incorrectly calculate the frame_size.  It appears that this is
exploitable.  In demux_real_send_chunk(), if the audio type is
BUF_AUDIO_COOK, ..._ATRK, ..._28_8, or ..._SIPRO, the frame buffer
will be populated as the width and height are traversed.  This allows
for a direct overflow which is somewhat controllable.  In particular,
the easiest way to limit the total length is to truncate the source
data after overflowing the buffer sufficiently.  However, there are
other tweaks which allow fine grain control.  COOK and ATRK both use
'sps' to control the increment read while SIPRO uses the width (w),
and 28_8 uses the 'cfs' -- all of these values user-supplied.

D. In demux_realaudio.c, open_ra_file() calculates the frame size using
two (or three, depending) values with a maximum of 65535 each.  When
three values are used, the frame size calculation may overflow.  This
allows for a similar attack described in above in 1-C. In addition,
the xine_xmalloc() call is unchecked so even a failed allocation
would slip through and result in a crash.

E. In id3.c, id3v23_interp_frame() allocates a buffer for reading the
frame using the user-supplied size and adds 1.  This allows the size
to wrap to 0 creating an empty, but successful, malloc() allocation.
Next, read will be called using the original size.  Depending on the
plugin, this will either read until the data ends/a segfault occurs
or it will trigger the negative-length mentioned bugs.  If it is the
input_file, then the buffer can be overflowed and the amount
controlled by the remaining file size. (Note, the over-sized value is
read in at 566 and bypasses the checks n 569 since the value will

2. Potentially exploitable

A. In demux_mng.c, mymng_process_header() accepts a width and height as
arguments.  These are then multiplied by 3 for image allocation.  Not
only is the integer operation unchecked, but the allocation is also
unchecked for failure.  The source of the width and height was not
determined, thus the 'potential' rating.

B. In demux_mod.c, open_mod_file() relies on the input plugin to supply a
filesize.  This size is then used in malloc() without checking to see
if the allocation failed.  With input types like input_net.c, this
will result in a 0-sized allocation, but with input_file.c a large,
or invalid, file entry would be required to cause trouble.  However,
filesize may also come from plugins like input_http.c.  In this case,
the filesize is supplied by the server which provides an easier
attack surface.  For example, if the filesize is -1, the read() check
will pass (if an 'error' occurs), and the negative value will be
passed into ModPlug_Load. However, the libmod code was not reviewed
and a large, or negative, content size may trigger the input plugin
systemic read() issues discussed below.

C. In demux_real.c, real_parse_mdpr() uses a string_name_size supplied
by the user.  This size is represented as a char type.  This size is
then used in an allocation where 1 is added, presumably for the NUL
character.  If the char size is 255 (or -1), then when 1 is added,
the value will wrap to 0.  This will result in an allocation of size
0.  After allocation, a memcpy() occurs of the user-supplied data.
If char is signed (default), then this will result in a segmentation
fault as 0xff will be extended to 0xffffffff and memcpy() will read
and/or write out of bounds.  This affects mime_type and
mime_type_size as well. In addition, the type_specific_data
allocation is not checked for failure not for a zero-valued size, an
unexpected process termination issue.

3. Unexpected process termination and other issues

A. Many of the input plugins improperly handle negative-valued lengths
during read function calls.  In most cases, the length is handled as
an off_t.  (Even though this may be 64-bit, it is still signed and
type promotion will usually keep -1 a -1.)  Depending on the read
state, a check for a preview may be done.  If so, this will result in
the length being used to memcpy() data from a preview buffer.  In
general, this will result in a crash.  Many of the input plugins are
affected: - input_file.c, input_net.c, input_smb.c, input_http.c, ...
In some cases, even a 0 size may result in misbehaving.  In
input_http.c, after the preview checks, if the supplied length (nlen)
is 0, the length used (n) will become negative after: 446:   n = nlen
- num_bytes; In all of these cases, buffer overflows are completely
possible as well as out of bounds reads. If the source data is
attacker controlled, like a file, http response, and so one, then it
may be possible to use these functions to aid a targeted heap buffer
overflow. 1-B is a good example where the negative value allows the
attacker to exploit allocation as opposed to just causing a crash.

B. In demux_qt.c, parse_reference_atom() takes a user-supplied
size for a string of an RDRF_ATOM.  If the current_atom_size and
string_size are both supplied as 0xffffffff, then code will allocate
a buffer of 1 (or some value offset by the url text).  This will
result in an unbounded memcpy(), most likely resulting in a process
crash. (If SIGSEGV signals are trapped with subsequent heap access,
this may be exploitable.)

C. In demux_matroska.c, the handling of the MATROSKA_ID_TR_CODECPRIVATE
track entry element relies on a user-supplied length for allocation
and no failure check is present prior to use.  Given that the buffer
is 0, this will most likely result in a SIGSEGV signal. However, if
there is no data to read from the file, this will result in the
read() function returning -1 (i.e., with input_file). The read return
value is compared against a 64-bit cast of the element size (-1 ->
-1) in ebml.c:190, but type promotion should make the -1 values
equal.  ebml_read_elm_data will return successfully, and xine-lib
will continue executing. In most cases, it appears the size and/or
NULL allocation will be ignored. But in other cases, it will be
directly accessed (fourcc) or the size used in allocation (MPEG4).
The case of the MPEG4 codec, a malloc() is based on the supplied
length plus a sizeof() which will overflow.  Unfortunately, this may
only corrupt the heap bookkeeping a small amount
(demux_matroska.c:1308) prior to a memcpy() which will result in a
segmentation fault.

D. In demux_qt.c, a compressed MOV (CMOV_ATOM) may result in an out of
bounds read by zlib during inflation.  In particular, a small
moov_atom_size will underflow when avail_in is set (demux_qt.c:2191).
This will allow inflate() to read well past the available data.
Given that the size of the output buffer is also user-supplied
(2192), enough space can be allocated to allow zlib to either return
with an error or cause the process to crash.

E. In demux_qt.c, when allocating STSD_ATOM atoms, calloc() is used with
a user-supplied count.  In general, this is safe, but if the count is
0, calloc() will still return successfully (on many systems). This
may result in unexpected behavior. In this case, it may result in the
pointer being used as the media_id (with MEDIA_VIDEO) which in turn
makes it into the trak->properties.  This may result in out of bounds
memory accesses, heap corruption, or worse.

F. In demux_real.c, real_parse_headers() reads a chunk size and type from the
user.  If the type is PROP_TAG, MDPR_TAG, or CONT_TAG, then the chunk
size is used in an allocation.  Allocation failure is unchecked and
an immediate read may result in a segmentation fault.  In addition,
the chunk_size is used as the data_chunk_size.  This is used to
calculate the normpos which is used to output video.  Given that this
wasn't analyzed further, it's unclear if additional exploitable
conditions exist.

G. In demux_real.c, real_parse_headers() uses a user-supplied length to
reindex into an allocated buffer (523, 524).  The length is unchecked
and may point outside of the allocated region. This will result in an
out of bounds read which may result in a crash. In addition, the
other accesses of of type_specific_data do not ensure that it is of
sufficient size.  Line 512 may read past the allocated space. Line
520 may pre-index the buffer if the specific_len is less than 5. Line
527 may also read past the end of the buffer, and so on.

Updated details and analysis can be found over at oCERT.

This page does not necessarily reflect the views of my employer or anyone I'm associated with.