»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 wrap.) 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.