Commit message: avformat/asfdec: Fix DoS in asf_build_simple_index()

Fixes: Missing EOF check in loop
No testcase

Found-by: Xiaohei and Wangchu from Alibaba Security Team
Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
CVE: CVE-2017-14223
CWE: CWE-399
Vulnerability description: In libavformat/asfdec_f.c in FFmpeg 3.3.3, a DoS in asf_build_simple_index() due to lack of an EOF (End of File) check might cause huge CPU consumption. When a crafted ASF file, which claims a large "ict" field in the header but does not contain sufficient backing data, is provided, the for loop would consume huge CPU and memory resources, since there is no EOF check inside the loop.

Project: FFmpeg

static int64_t asf_read_pts(AVFormatContext *s, int stream_index,
                            int64_t *ppos, int64_t pos_limit)
{
    ASFContext *asf     = s->priv_data;
    AVPacket pkt1, *pkt = &pkt1;
    ASFStream *asf_st;
    int64_t pts;
    int64_t pos = *ppos;
    int i;
    int64_t start_pos[ASF_MAX_STREAMS];

    for (i = 0; i < s->nb_streams; i++)
        start_pos[i] = pos;

    if (s->packet_size > 0)
        pos = (pos + s->packet_size - 1 - s->internal->data_offset) /
              s->packet_size * s->packet_size +
              s->internal->data_offset;
    *ppos = pos;
    if (avio_seek(s->pb, pos, SEEK_SET) < 0)
        return AV_NOPTS_VALUE;

    ff_read_frame_flush(s);
    asf_reset_header(s);
    for (;;) {
        if (av_read_frame(s, pkt) < 0) {
            av_log(s, AV_LOG_INFO, "asf_read_pts failed\n");
            return AV_NOPTS_VALUE;
        }

        pts = pkt->dts;

        if (pkt->flags & AV_PKT_FLAG_KEY) {
            i = pkt->stream_index;

            asf_st = &asf->streams[s->streams[i]->id];

            pos = asf_st->packet_pos;
            av_assert1(pkt->pos == asf_st->packet_pos);

            av_add_index_entry(s->streams[i], pos, pts, pkt->size,
                               pos - start_pos[i] + 1, AVINDEX_KEYFRAME);
            start_pos[i] = asf_st->packet_pos + 1;

            if (pkt->stream_index == stream_index) {
                av_packet_unref(pkt);
                break;
            }
        }
        av_packet_unref(pkt);
    }

    *ppos = pos;
    return pts;
}

Project: FFmpeg
File: asfdec_f.c

static int asf_read_seek(AVFormatContext *s, int stream_index,
                         int64_t pts, int flags)
{
    ASFContext *asf = s->priv_data;
    AVStream *st    = s->streams[stream_index];
    int ret = 0;

    if (s->packet_size <= 0)
        return -1;

    /* Try using the protocol's read_seek if available */
    if (s->pb) {
        int64_t ret = avio_seek_time(s->pb, stream_index, pts, flags);
        if (ret >= 0)
            asf_reset_header(s);
        if (ret != AVERROR(ENOSYS))
            return ret;
    }

    /* explicitly handle the case of seeking to 0 */
    if (!pts) {
        asf_reset_header(s);
        avio_seek(s->pb, s->internal->data_offset, SEEK_SET);
        return 0;
    }

    if (!asf->index_read) {
        ret = asf_build_simple_index(s, stream_index);
        if (ret < 0)
            asf->index_read = -1;
    }

    if (asf->index_read > 0 && st->index_entries) {
        int index = av_index_search_timestamp(st, pts, flags);
        if (index >= 0) {
            /* find the position */
            uint64_t pos = st->index_entries[index].pos;

            /* do the seek */
            av_log(s, AV_LOG_DEBUG, "SEEKTO: %"PRId64"\n", pos);
            if(avio_seek(s->pb, pos, SEEK_SET) < 0)
                return -1;
            asf_reset_header(s);
            skip_to_key(s);
            return 0;
        }
    }
    /* no index or seeking by index failed */
    if (ff_seek_frame_binary(s, stream_index, pts, flags) < 0)
        return -1;
    asf_reset_header(s);
    skip_to_key(s);
    return 0;
}

Project: FFmpeg

static int asf_read_stream_properties(AVFormatContext *s, int64_t size)
{
    ASFContext *asf = s->priv_data;
    AVIOContext *pb = s->pb;
    AVStream *st;
    ASFStream *asf_st;
    ff_asf_guid g;
    enum AVMediaType type;
    int type_specific_size, sizeX;
    unsigned int tag1;
    int64_t pos1, pos2, start_time;
    int test_for_ext_stream_audio, is_dvr_ms_audio = 0;

    if (s->nb_streams == ASF_MAX_STREAMS) {
        av_log(s, AV_LOG_ERROR, "too many streams\n");
        return AVERROR(EINVAL);
    }

    pos1 = avio_tell(pb);

    st = avformat_new_stream(s, NULL);
    if (!st)
        return AVERROR(ENOMEM);
    avpriv_set_pts_info(st, 32, 1, 1000); /* 32 bit pts in ms */
    start_time     = asf->hdr.preroll;

    if (!(asf->hdr.flags & 0x01)) { // if we aren't streaming...
        int64_t fsize = avio_size(pb);
        if (fsize <= 0 || (int64_t)asf->hdr.file_size <= 0 ||
            20*FFABS(fsize - (int64_t)asf->hdr.file_size) < FFMIN(fsize, asf->hdr.file_size))
            st->duration = asf->hdr.play_time /
                       (10000000 / 1000) - start_time;
    }
    ff_get_guid(pb, &g);

    test_for_ext_stream_audio = 0;
    if (!ff_guidcmp(&g, &ff_asf_audio_stream)) {
        type = AVMEDIA_TYPE_AUDIO;
    } else if (!ff_guidcmp(&g, &ff_asf_video_stream)) {
        type = AVMEDIA_TYPE_VIDEO;
    } else if (!ff_guidcmp(&g, &ff_asf_jfif_media)) {
        type                = AVMEDIA_TYPE_VIDEO;
        st->codecpar->codec_id = AV_CODEC_ID_MJPEG;
    } else if (!ff_guidcmp(&g, &ff_asf_command_stream)) {
        type = AVMEDIA_TYPE_DATA;
    } else if (!ff_guidcmp(&g, &ff_asf_ext_stream_embed_stream_header)) {
        test_for_ext_stream_audio = 1;
        type                      = AVMEDIA_TYPE_UNKNOWN;
    } else {
        return -1;
    }
    ff_get_guid(pb, &g);
    avio_skip(pb, 8); /* total_size */
    type_specific_size = avio_rl32(pb);
    avio_rl32(pb);
    st->id = avio_rl16(pb) & 0x7f; /* stream id */
    asf->asfid2avid[st->id] = s->nb_streams - 1;
    asf_st = &asf->streams[st->id];

    avio_rl32(pb);

    if (test_for_ext_stream_audio) {
        ff_get_guid(pb, &g);
        if (!ff_guidcmp(&g, &ff_asf_ext_stream_audio_stream)) {
            type            = AVMEDIA_TYPE_AUDIO;
            is_dvr_ms_audio = 1;
            ff_get_guid(pb, &g);
            avio_rl32(pb);
            avio_rl32(pb);
            avio_rl32(pb);
            ff_get_guid(pb, &g);
            avio_rl32(pb);
        }
    }

    st->codecpar->codec_type = type;
    if (type == AVMEDIA_TYPE_AUDIO) {
        int ret = ff_get_wav_header(s, pb, st->codecpar, type_specific_size, 0);
        if (ret < 0)
            return ret;
        if (is_dvr_ms_audio) {
            st->request_probe    = 1;
            st->codecpar->codec_tag = 0;
        }
        if (st->codecpar->codec_id == AV_CODEC_ID_AAC)
            st->need_parsing = AVSTREAM_PARSE_NONE;
        else
            st->need_parsing = AVSTREAM_PARSE_FULL;
        /* We have to init the frame size at some point .... */
        pos2 = avio_tell(pb);
        if (size >= (pos2 + 8 - pos1 + 24)) {
            asf_st->ds_span        = avio_r8(pb);
            asf_st->ds_packet_size = avio_rl16(pb);
            asf_st->ds_chunk_size  = avio_rl16(pb);
            avio_rl16(pb);  // ds_data_size
            avio_r8(pb);    // ds_silence_data
        }
        if (asf_st->ds_span > 1) {
            if (!asf_st->ds_chunk_size                                ||
                (asf_st->ds_packet_size / asf_st->ds_chunk_size <= 1) ||
                asf_st->ds_packet_size % asf_st->ds_chunk_size)
                asf_st->ds_span = 0;  // disable descrambling
        }
    } else if (type == AVMEDIA_TYPE_VIDEO &&
               size - (avio_tell(pb) - pos1 + 24) >= 51) {
        avio_rl32(pb);
        avio_rl32(pb);
        avio_r8(pb);
        avio_rl16(pb);        /* size */
        sizeX             = avio_rl32(pb); /* size */
        st->codecpar->width  = avio_rl32(pb);
        st->codecpar->height = avio_rl32(pb);
        /* not available for asf */
        avio_rl16(pb); /* panes */
        st->codecpar->bits_per_coded_sample = avio_rl16(pb); /* depth */
        tag1                             = avio_rl32(pb);
        avio_skip(pb, 20);
        if (sizeX > 40) {
            st->codecpar->extradata_size = ffio_limit(pb, sizeX - 40);
            st->codecpar->extradata      = av_mallocz(st->codecpar->extradata_size +
                                                   AV_INPUT_BUFFER_PADDING_SIZE);
            if (!st->codecpar->extradata)
                return AVERROR(ENOMEM);
            avio_read(pb, st->codecpar->extradata, st->codecpar->extradata_size);
        }

        /* Extract palette from extradata if bpp <= 8 */
        /* This code assumes that extradata contains only palette */
        /* This is true for all paletted codecs implemented in libavcodec */
        if (st->codecpar->extradata_size && (st->codecpar->bits_per_coded_sample <= 8)) {
#if HAVE_BIGENDIAN
            int i;
            for (i = 0; i < FFMIN(st->codecpar->extradata_size, AVPALETTE_SIZE) / 4; i++)
                asf_st->palette[i] = av_bswap32(((uint32_t *)st->codecpar->extradata)[i]);
#else
            memcpy(asf_st->palette, st->codecpar->extradata,
                   FFMIN(st->codecpar->extradata_size, AVPALETTE_SIZE));
#endif
            asf_st->palette_changed = 1;
        }

        st->codecpar->codec_tag = tag1;
        st->codecpar->codec_id  = ff_codec_get_id(ff_codec_bmp_tags, tag1);
        if (tag1 == MKTAG('D', 'V', 'R', ' ')) {
            st->need_parsing = AVSTREAM_PARSE_FULL;
            /* issue658 contains wrong w/h and MS even puts a fake seq header
             * with wrong w/h in extradata while a correct one is in the stream.
             * maximum lameness */
            st->codecpar->width      =
                st->codecpar->height = 0;
            av_freep(&st->codecpar->extradata);
            st->codecpar->extradata_size = 0;
        }
        if (st->codecpar->codec_id == AV_CODEC_ID_H264)
            st->need_parsing = AVSTREAM_PARSE_FULL_ONCE;
        if (st->codecpar->codec_id == AV_CODEC_ID_MPEG4)
            st->need_parsing = AVSTREAM_PARSE_FULL_ONCE;
    }
    pos2 = avio_tell(pb);
    avio_skip(pb, size - (pos2 - pos1 + 24));

    return 0;
}

Project: FFmpeg
File: asfdec_f.c

static void get_id3_tag(AVFormatContext *s, int len)
{
    ID3v2ExtraMeta *id3v2_extra_meta = NULL;

    ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, len);
    if (id3v2_extra_meta)
        ff_id3v2_parse_apic(s, &id3v2_extra_meta);
    ff_id3v2_free_extra_meta(&id3v2_extra_meta);
}

Project: FFmpeg
File: asfdec_f.c

static int get_value(AVIOContext *pb, int type, int type2_size)
{
    switch (type) {
    case 2:
        return (type2_size == 32) ? avio_rl32(pb) : avio_rl16(pb);
    case 3:
        return avio_rl32(pb);
    case 4:
        return avio_rl64(pb);
    case 5:
        return avio_rl16(pb);
    default:
        return INT_MIN;
    }
}