xorl %eax, %eax

CVE-2009-3378: Mozilla Firefox liboggplay Uninitialized Pointer Dereference

leave a comment »

The last vulnerability being released in MFSA-2009-63 was this one. This bug was reported by Juan Becerra of Mozilla and it can be found in media/liboggplay/src/liboggplay/oggplay_data.c file which is part of the liboggplay library.

int
oggplay_data_handle_theora_frame (OggPlayTheoraDecode *decode,
                                  const yuv_buffer *buffer) {

  size_t                size = sizeof (OggPlayVideoRecord);
  int                   i, ret;
  long                  y_size, uv_size, y_offset, uv_offset;
  unsigned char       * p;
  unsigned char       * q;
  unsigned char       * p2;
  unsigned char       * q2;
  OggPlayVideoRecord  * record;
  OggPlayVideoData    * data;
       ...
  /*
   * *grumble* theora plays silly buggers with pointers so we need to do
   * a row-by-row copy (stride may be negative)
   */
  y_offset = (decode->video_info.offset_x&~1) 
              + buffer->y_stride*(decode->video_info.offset_y&~1);
  p = data->y;
  q = buffer->y + y_offset;
  for (i = 0; i < decode->y_height; i++) {
    memcpy(p, q, decode->y_width);
    p += decode->y_width;
    q += buffer->y_stride;
  }
       ...
  return E_OGGPLAY_CONTINUE;
}

Pointer ‘buffer’ is used to contain uncompressed frames to and from the codec. The given snippet is a simple copy operation of the decoded width. However, there is a case where ‘buffer->y’ would be uninitialized and the memcpy() will attempt to copy data from uninitialized memory and thus, result in a uninitialized pointer dereference.
The exact code path to achieve this is the following…

int theora_decode_packetin(theora_state *_td,ogg_packet *_op){
  th_api_wrapper *api;
  ogg_int64_t     gp;
  int             ret;
  if(!_td||!_td->i||!_td->i->codec_setup)return OC_FAULT;
  api=(th_api_wrapper *)_td->i->codec_setup;
  ret=th_decode_packetin(api->decode,_op,&gp);
  if(ret<0)return OC_BADPACKET;
  _td->granulepos=gp;
  return 0;
}

This will be called first but th_decode_packetin() can return TH_DUPFRAME (meaning that it doesn’t exist in the Theora stream), as we can read at media/libtheora/include/theora/codec.h this has a positive value:

#define TH_DUPFRAME   (1)
/*@}*/

Consequently, theora_decode_packetin() will return zero and the next function that will be called is the theora_decode_YUVout() located in media/libtheora/lib/decapiwrapper.c too.

int theora_decode_YUVout(theora_state *_td,yuv_buffer *_yuv){
  th_api_wrapper  *api;
  th_dec_ctx      *decode;
  th_ycbcr_buffer  buf;
  int              ret;
  if(!_td||!_td->i||!_td->i->codec_setup)return OC_FAULT;
  api=(th_api_wrapper *)_td->i->codec_setup;
  decode=(th_dec_ctx *)api->decode;
  if(!decode)return OC_FAULT;
  ret=th_decode_ycbcr_out(decode,buf);
  if(ret>=0){
    _yuv->y_width=buf[0].width;
    _yuv->y_height=buf[0].height;
    _yuv->y_stride=buf[0].stride;
    _yuv->uv_width=buf[1].width;
    _yuv->uv_height=buf[1].height;
    _yuv->uv_stride=buf[1].stride;
    _yuv->y=buf[0].data;
    _yuv->u=buf[1].data;
    _yuv->v=buf[2].data;
  }
  return ret;
}

which doesn’t perform any operations on the “_yuv” buffer and thus, it will return with no error too. This means that even though the ‘yuv_buffer’ is still uninitialized, oggplay_data_handle_theora_frame() will be invoked and attempt to copy the width and dereference an uninitialized pointer.
To fix this, Mozilla developers changed the oggplay_initialise() initialization routine found in media/liboggplay/src/liboggplay/oggplay.c to set the decoder to inactive which was missing. Here is the patch:

   /*
    * set all the tracks to inactive
    */
   for (i = 0; i < me->num_tracks; i++) {
     me->decode_data[i]->active = 0;
   }
+  me->active_tracks = 0;

As you can read, even though the ‘decode_data[]’ array was cleared, the active tracks for decoding was not. This patch updates it to be initialized to zero too. And of course, the equivalent Theora routine which resides in media/liboggplay/src/liboggplay/oggplay_callback.c under oggplay_init_theora() was changed to increment the OGG player handle’s active tracks like this:

   decoder->frame_delta = 0;
   decoder->y_width = 0;
   decoder->convert_to_rgb = 0;
   decoder->decoder.decoded_type = OGGPLAY_YUV_VIDEO;
+  decoder->decoder.player->active_tracks++;
 }

Finally, oggplay_initialise_decoder() was changed in order to set the decoder to active.

   decoder->content_type_name =
           oggz_stream_get_content_type (me->oggz, serialno);
-  decoder->active = 0;
+  decoder->active = 1;
   decoder->final_granulepos = -1;
   decoder->player = me;

As Matthew Gregan pointed out, by enabling the decoder, it will decode the packets before they reach the code that causes the crash. This is why this approach was used to fix the vulnerability.
Finally, M. Gregan also gave a PoC OGG file that triggers the bug and can be downloaded here.

Written by xorl

November 13, 2009 at 22:17

Posted in bugs

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s