Commit message: asfdemux: Check that we have enough data available before parsing bool/uint extended content descriptors

https://bugzilla.gnome.org/show_bug.cgi?id=777955
CVE: CVE-2017-5847
CWE: CWE-125
Vulnerability description: The gst_asf_demux_process_ext_content_desc function in gst/asfdemux/gstasfdemux.c in gst-plugins-ugly in GStreamer allows remote attackers to cause a denial of service (out-of-bounds heap read) via vectors involving extended content descriptors.

Project: gst-plugins-ugly
File: gstasfdemux.c

gst_asf_demux_activate_mode (GstPad * sinkpad, GstObject * parent,
    GstPadMode mode, gboolean active)
{
  gboolean res;
  GstASFDemux *demux;

  demux = GST_ASF_DEMUX (parent);

  switch (mode) {
    case GST_PAD_MODE_PUSH:
      demux->state = GST_ASF_DEMUX_STATE_HEADER;
      demux->streaming = TRUE;
      res = TRUE;
      break;
    case GST_PAD_MODE_PULL:
      if (active) {
        demux->state = GST_ASF_DEMUX_STATE_HEADER;
        demux->streaming = FALSE;

        res = gst_pad_start_task (sinkpad, (GstTaskFunction) gst_asf_demux_loop,
            demux, NULL);
      } else {
        res = gst_pad_stop_task (sinkpad);
      }
      break;
    default:
      res = FALSE;
      break;
  }
  return res;
}

Project: gst-plugins-ugly
File: gstasfdemux.c

gst_asf_demux_change_state (GstElement * element, GstStateChange transition)
{
  GstASFDemux *demux = GST_ASF_DEMUX (element);
  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;

  switch (transition) {
    case GST_STATE_CHANGE_NULL_TO_READY:{
      gst_segment_init (&demux->segment, GST_FORMAT_TIME);
      demux->need_newsegment = TRUE;
      demux->segment_running = FALSE;
      demux->keyunit_sync = FALSE;
      demux->accurate = FALSE;
      demux->adapter = gst_adapter_new ();
      demux->metadata = gst_caps_new_empty ();
      demux->global_metadata = gst_structure_new_empty ("metadata");
      demux->data_size = 0;
      demux->data_offset = 0;
      demux->index_offset = 0;
      demux->base_offset = 0;
      demux->flowcombiner = gst_flow_combiner_new ();

      break;
    }
    default:
      break;
  }

  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
  if (ret == GST_STATE_CHANGE_FAILURE)
    return ret;

  switch (transition) {
    case GST_STATE_CHANGE_PAUSED_TO_READY:
      gst_asf_demux_reset (demux, FALSE);
      break;

    case GST_STATE_CHANGE_READY_TO_NULL:
      gst_asf_demux_reset (demux, FALSE);
      gst_flow_combiner_free (demux->flowcombiner);
      demux->flowcombiner = NULL;
      break;
    default:
      break;
  }

  return ret;
}

Project: gst-plugins-ugly
File: gstasfdemux.c

gst_asf_demux_check_buffer_is_header (GstASFDemux * demux, GstBuffer * buf)
{
  AsfObject obj;
  GstMapInfo map;
  gboolean valid;
  g_assert (buf != NULL);

  GST_LOG_OBJECT (demux, "Checking if buffer is a header");

  gst_buffer_map (buf, &map, GST_MAP_READ);

  /* we return false on buffer too small */
  if (map.size < ASF_OBJECT_HEADER_SIZE) {
    gst_buffer_unmap (buf, &map);
    return FALSE;
  }

  /* check if it is a header */
  valid =
      asf_demux_peek_object (demux, map.data, ASF_OBJECT_HEADER_SIZE, &obj,
      TRUE);
  gst_buffer_unmap (buf, &map);
  if (valid && obj.id == ASF_OBJ_HEADER) {
    return TRUE;
  }
  return FALSE;
}

Project: gst-plugins-ugly
File: gstasfdemux.c

gst_asf_demux_descramble_buffer (GstASFDemux * demux, AsfStream * stream,
    GstBuffer ** p_buffer)
{
  GstBuffer *descrambled_buffer;
  GstBuffer *scrambled_buffer;
  GstBuffer *sub_buffer;
  guint offset;
  guint off;
  guint row;
  guint col;
  guint idx;

  /* descrambled_buffer is initialised in the first iteration */
  descrambled_buffer = NULL;
  scrambled_buffer = *p_buffer;

  if (gst_buffer_get_size (scrambled_buffer) <
      stream->ds_packet_size * stream->span)
    return;

  for (offset = 0; offset < gst_buffer_get_size (scrambled_buffer);
      offset += stream->ds_chunk_size) {
    off = offset / stream->ds_chunk_size;
    row = off / stream->span;
    col = off % stream->span;
    idx = row + col * stream->ds_packet_size / stream->ds_chunk_size;
    GST_DEBUG ("idx=%u, row=%u, col=%u, off=%u, ds_chunk_size=%u", idx, row,
        col, off, stream->ds_chunk_size);
    GST_DEBUG ("scrambled buffer size=%" G_GSIZE_FORMAT
        ", span=%u, packet_size=%u", gst_buffer_get_size (scrambled_buffer),
        stream->span, stream->ds_packet_size);
    GST_DEBUG ("gst_buffer_get_size (scrambled_buffer) = %" G_GSIZE_FORMAT,
        gst_buffer_get_size (scrambled_buffer));
    sub_buffer =
        gst_buffer_copy_region (scrambled_buffer, GST_BUFFER_COPY_MEMORY,
        idx * stream->ds_chunk_size, stream->ds_chunk_size);
    if (!offset) {
      descrambled_buffer = sub_buffer;
    } else {
      descrambled_buffer = gst_buffer_append (descrambled_buffer, sub_buffer);
    }
  }

  GST_BUFFER_TIMESTAMP (descrambled_buffer) =
      GST_BUFFER_TIMESTAMP (scrambled_buffer);
  GST_BUFFER_DURATION (descrambled_buffer) =
      GST_BUFFER_DURATION (scrambled_buffer);
  GST_BUFFER_OFFSET (descrambled_buffer) = GST_BUFFER_OFFSET (scrambled_buffer);
  GST_BUFFER_OFFSET_END (descrambled_buffer) =
      GST_BUFFER_OFFSET_END (scrambled_buffer);

  /* FIXME/CHECK: do we need to transfer buffer flags here too? */

  gst_buffer_unref (scrambled_buffer);
  *p_buffer = descrambled_buffer;
}

Project: gst-plugins-ugly
File: gstasfdemux.c

gst_asf_demux_get_stream_video_format (asf_stream_video_format * fmt,
    guint8 ** p_data, guint64 * p_size)
{
  if (*p_size < (4 + 4 + 4 + 2 + 2 + 4 + 4 + 4 + 4 + 4 + 4))
    return FALSE;

  fmt->size = gst_asf_demux_get_uint32 (p_data, p_size);
  /* Sanity checks */
  if (fmt->size < 40) {
    GST_WARNING ("Corrupted asf_stream_video_format (size < 40)");
    return FALSE;
  }
  if ((guint64) fmt->size - 4 > *p_size) {
    GST_WARNING ("Corrupted asf_stream_video_format (codec_data is too small)");
    return FALSE;
  }
  fmt->width = gst_asf_demux_get_uint32 (p_data, p_size);
  fmt->height = gst_asf_demux_get_uint32 (p_data, p_size);
  fmt->planes = gst_asf_demux_get_uint16 (p_data, p_size);
  fmt->depth = gst_asf_demux_get_uint16 (p_data, p_size);
  fmt->tag = gst_asf_demux_get_uint32 (p_data, p_size);
  fmt->image_size = gst_asf_demux_get_uint32 (p_data, p_size);
  fmt->xpels_meter = gst_asf_demux_get_uint32 (p_data, p_size);
  fmt->ypels_meter = gst_asf_demux_get_uint32 (p_data, p_size);
  fmt->num_colors = gst_asf_demux_get_uint32 (p_data, p_size);
  fmt->imp_colors = gst_asf_demux_get_uint32 (p_data, p_size);
  return TRUE;
}

Project: gst-plugins-ugly
File: gstasfdemux.c

gst_asf_demux_get_string (gchar ** p_str, guint16 * p_strlen,
    guint8 ** p_data, guint64 * p_size)
{
  guint16 s_length;
  guint8 *s;

  *p_str = NULL;

  if (*p_size < 2)
    return FALSE;

  s_length = gst_asf_demux_get_uint16 (p_data, p_size);

  if (p_strlen)
    *p_strlen = s_length;

  if (s_length == 0) {
    GST_WARNING ("zero-length string");
    *p_str = g_strdup ("");
    return TRUE;
  }

  if (!gst_asf_demux_get_bytes (&s, s_length, p_data, p_size))
    return FALSE;

  g_assert (s != NULL);

  /* just because They don't exist doesn't
   * mean They are not out to get you ... */
  if (s[s_length - 1] != '\0') {
    s = g_realloc (s, s_length + 1);
    s[s_length] = '\0';
  }

  *p_str = (gchar *) s;
  return TRUE;
}

Project: gst-plugins-ugly
File: gstasfdemux.c

gst_asf_demux_get_uint16 (guint8 ** p_data, guint64 * p_size)
{
  guint16 ret;

  g_assert (*p_size >= 2);
  ret = GST_READ_UINT16_LE (*p_data);
  *p_data += sizeof (guint16);
  *p_size -= sizeof (guint16);
  return ret;
}

Project: gst-plugins-ugly
File: gstasfdemux.c

gst_asf_demux_parse_data_object_start (GstASFDemux * demux, guint8 * data)
{
  AsfObject obj;

  if (!asf_demux_peek_object (demux, data, 50, &obj, TRUE)) {
    GST_WARNING_OBJECT (demux, "Corrupted data");
    return FALSE;
  }
  if (obj.id != ASF_OBJ_DATA) {
    GST_WARNING_OBJECT (demux, "headers not followed by a DATA object");
    return FALSE;
  }

  demux->state = GST_ASF_DEMUX_STATE_DATA;

  if (!demux->broadcast && obj.size > 50) {
    demux->data_size = obj.size - 50;
    /* CHECKME: for at least one file this is off by +158 bytes?! */
    demux->index_offset = demux->data_offset + demux->data_size;
  } else {
    demux->data_size = 0;
    demux->index_offset = 0;
  }

  demux->packet = 0;

  if (!demux->broadcast) {
    /* skip object header (24 bytes) and file GUID (16 bytes) */
    demux->num_packets = GST_READ_UINT64_LE (data + (16 + 8) + 16);
  } else {
    demux->num_packets = 0;
  }

  if (demux->num_packets == 0)
    demux->seekable = FALSE;

  /* fallback in the unlikely case that headers are inconsistent, can't hurt */
  if (demux->data_size == 0 && demux->num_packets > 0) {
    demux->data_size = demux->num_packets * demux->packet_size;
    demux->index_offset = demux->data_offset + demux->data_size;
  }

  /* process pending stream objects and create pads for those */
  gst_asf_demux_process_queued_extended_stream_objects (demux);

  GST_INFO_OBJECT (demux, "Stream has %" G_GUINT64_FORMAT " packets, "
      "data_offset=%" G_GINT64_FORMAT ", data_size=%" G_GINT64_FORMAT
      ", index_offset=%" G_GUINT64_FORMAT, demux->num_packets,
      demux->data_offset, demux->data_size, demux->index_offset);

  return TRUE;
}