Commit message: avformat/mxfdec: Fix DoS issues in mxf_read_index_entry_array()

Fixes: 20170829A.mxf

Co-Author: 张洪亮(望初)" <wangchu.zhl@alibaba-inc.com>
Found-by: Xiaohei and Wangchu from Alibaba Security Team
Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
CVE: CVE-2017-14170
CWE: CWE-834
Vulnerability description: In libavformat/mxfdec.c in FFmpeg 3.3.3 -> 2.4, a DoS in mxf_read_index_entry_array() due to lack of an EOF (End of File) check might cause huge CPU consumption. When a crafted MXF file, which claims a large "nb_index_entries" field in the header but does not contain sufficient backing data, is provided, the loop would consume huge CPU resources, since there is no EOF check inside the loop. Moreover, this big loop can be invoked multiple times if there is more than one applicable data segment in the crafted MXF file.

Project: FFmpeg
File: mxfdec.c

static int mxf_edit_unit_absolute_offset(MXFContext *mxf, MXFIndexTable *index_table, int64_t edit_unit, int64_t *edit_unit_out, int64_t *offset_out, int nag)
{
    int i;
    int64_t offset_temp = 0;

    for (i = 0; i < index_table->nb_segments; i++) {
        MXFIndexTableSegment *s = index_table->segments[i];

        edit_unit = FFMAX(edit_unit, s->index_start_position);  /* clamp if trying to seek before start */

        if (edit_unit < s->index_start_position + s->index_duration) {
            int64_t index = edit_unit - s->index_start_position;

            if (s->edit_unit_byte_count)
                offset_temp += s->edit_unit_byte_count * index;
            else if (s->nb_index_entries) {
                if (s->nb_index_entries == 2 * s->index_duration + 1)
                    index *= 2;     /* Avid index */

                if (index < 0 || index >= s->nb_index_entries) {
                    av_log(mxf->fc, AV_LOG_ERROR, "IndexSID %i segment at %"PRId64" IndexEntryArray too small\n",
                           index_table->index_sid, s->index_start_position);
                    return AVERROR_INVALIDDATA;
                }

                offset_temp = s->stream_offset_entries[index];
            } else {
                av_log(mxf->fc, AV_LOG_ERROR, "IndexSID %i segment at %"PRId64" missing EditUnitByteCount and IndexEntryArray\n",
                       index_table->index_sid, s->index_start_position);
                return AVERROR_INVALIDDATA;
            }

            if (edit_unit_out)
                *edit_unit_out = edit_unit;

            return mxf_absolute_bodysid_offset(mxf, index_table->body_sid, offset_temp, offset_out);
        } else {
            /* EditUnitByteCount == 0 for VBR indexes, which is fine since they use explicit StreamOffsets */
            offset_temp += s->edit_unit_byte_count * s->index_duration;
        }
    }

    if (nag)
        av_log(mxf->fc, AV_LOG_ERROR, "failed to map EditUnit %"PRId64" in IndexSID %i to an offset\n", edit_unit, index_table->index_sid);

    return AVERROR_INVALIDDATA;
}

Project: FFmpeg
File: mxfdec.c

static int mxf_read_content_storage(void *arg, AVIOContext *pb, int tag, int size, UID uid, int64_t klv_offset)
{
    MXFContext *mxf = arg;
    switch (tag) {
    case 0x1901:
        if (mxf->packages_refs)
            av_log(mxf->fc, AV_LOG_VERBOSE, "Multiple packages_refs\n");
        av_free(mxf->packages_refs);
        return mxf_read_strong_ref_array(pb, &mxf->packages_refs, &mxf->packages_count);
    }
    return 0;
}

Project: FFmpeg
File: mxfdec.c

static int mxf_read_packet_old(AVFormatContext *s, AVPacket *pkt)
{
    KLVPacket klv;
    MXFContext *mxf = s->priv_data;
    int ret;

    while ((ret = klv_read_packet(&klv, s->pb)) == 0) {
        PRINT_KEY(s, "read packet", klv.key);
        av_log(s, AV_LOG_TRACE, "size %"PRIu64" offset %#"PRIx64"\n", klv.length, klv.offset);
        if (IS_KLV_KEY(klv.key, mxf_encrypted_triplet_key)) {
            ret = mxf_decrypt_triplet(s, pkt, &klv);
            if (ret < 0) {
                av_log(s, AV_LOG_ERROR, "invalid encoded triplet\n");
                return ret;
            }
            return 0;
        }
        if (IS_KLV_KEY(klv.key, mxf_essence_element_key) ||
            IS_KLV_KEY(klv.key, mxf_canopus_essence_element_key) ||
            IS_KLV_KEY(klv.key, mxf_avid_essence_element_key)) {
            int index = mxf_get_stream_index(s, &klv);
            int64_t next_ofs, next_klv;
            AVStream *st;
            MXFTrack *track;
            AVCodecParameters *par;

            if (index < 0) {
                av_log(s, AV_LOG_ERROR,
                       "error getting stream index %"PRIu32"\n",
                       AV_RB32(klv.key + 12));
                goto skip;
            }

            st = s->streams[index];
            track = st->priv_data;

            if (s->streams[index]->discard == AVDISCARD_ALL)
                goto skip;

            next_klv = avio_tell(s->pb) + klv.length;
            next_ofs = mxf_set_current_edit_unit(mxf, klv.offset);

            if (next_ofs >= 0 && next_klv > next_ofs) {
                /* if this check is hit then it's possible OPAtom was treated as OP1a
                 * truncate the packet since it's probably very large (>2 GiB is common) */
                avpriv_request_sample(s,
                                      "OPAtom misinterpreted as OP1a? "
                                      "KLV for edit unit %i extending into "
                                      "next edit unit",
                                      mxf->current_edit_unit);
                klv.length = next_ofs - avio_tell(s->pb);
            }

            /* check for 8 channels AES3 element */
            if (klv.key[12] == 0x06 && klv.key[13] == 0x01 && klv.key[14] == 0x10) {
                ret = mxf_get_d10_aes3_packet(s->pb, s->streams[index],
                                              pkt, klv.length);
                if (ret < 0) {
                    av_log(s, AV_LOG_ERROR, "error reading D-10 aes3 frame\n");
                    return ret;
                }
            } else {
                ret = av_get_packet(s->pb, pkt, klv.length);
                if (ret < 0)
                    return ret;
            }
            pkt->stream_index = index;
            pkt->pos = klv.offset;

            par = st->codecpar;

            if (par->codec_type == AVMEDIA_TYPE_VIDEO && next_ofs >= 0) {
                /* mxf->current_edit_unit good - see if we have an
                 * index table to derive timestamps from */
                MXFIndexTable *t = &mxf->index_tables[0];

                if (mxf->nb_index_tables >= 1 && mxf->current_edit_unit < t->nb_ptses) {
                    pkt->dts = mxf->current_edit_unit + t->first_dts;
                    pkt->pts = t->ptses[mxf->current_edit_unit];
                } else if (track && track->intra_only) {
                    /* intra-only -> PTS = EditUnit.
                     * let utils.c figure out DTS since it can be < PTS if low_delay = 0 (Sony IMX30) */
                    pkt->pts = mxf->current_edit_unit;
                }
            } else if (par->codec_type == AVMEDIA_TYPE_AUDIO) {
                ret = mxf_set_audio_pts(mxf, par, pkt);
                if (ret < 0)
                    return ret;
            }

            /* seek for truncated packets */
            avio_seek(s->pb, next_klv, SEEK_SET);

            return 0;
        } else
        skip:
            avio_skip(s->pb, klv.length);
    }
    return avio_feof(s->pb) ? AVERROR_EOF : ret;
}

Project: FFmpeg
File: mxfdec.c

static int mxf_read_tagged_value(void *arg, AVIOContext *pb, int tag, int size, UID uid, int64_t klv_offset)
{
    MXFTaggedValue *tagged_value = arg;
    switch (tag){
    case 0x5001:
        return mxf_read_utf16be_string(pb, size, &tagged_value->name);
    case 0x5003:
        return mxf_read_indirect_value(tagged_value, pb, size);
    }
    return 0;
}

Project: FFmpeg
File: mxfdec.c

static MXFPackage* mxf_resolve_source_package(MXFContext *mxf, UID package_uid)
{
    MXFPackage *package = NULL;
    int i;

    for (i = 0; i < mxf->packages_count; i++) {
        package = mxf_resolve_strong_ref(mxf, &mxf->packages_refs[i], SourcePackage);
        if (!package)
            continue;

        if (!memcmp(package->package_uid, package_uid, 16))
            return package;
    }
    return NULL;
}

Project: FFmpeg
File: mxfdec.c

static int mxf_umid_to_str(UID ul, UID uid, char **str)
{
    int i;
    char *p;
    p = *str = av_mallocz(sizeof(UID) * 4 + 2 + 1);
    if (!p)
        return AVERROR(ENOMEM);
    snprintf(p, 2 + 1, "0x");
    p += 2;
    for (i = 0; i < sizeof(UID); i++) {
        snprintf(p, 2 + 1, "%.2X", ul[i]);
        p += 2;

    }
    for (i = 0; i < sizeof(UID); i++) {
        snprintf(p, 2 + 1, "%.2X", uid[i]);
        p += 2;
    }
    return 0;
}