Commit message: nsvdec: don't ignore the return value of av_get_packet()

Fixes invalid reads with corrupted files.

CC: libav-stable@libav.org
Bug-Id: 1039
CVE: CVE-2017-9051
CWE: CWE-476
Vulnerability description: libav before 12.1 is vulnerable to an invalid read of size 1 due to NULL pointer dereferencing in the nsv_read_chunk function in libavformat/nsvdec.c.

Project: libav

static int nsv_parse_NSVf_header(AVFormatContext *s)
{
    NSVContext *nsv = s->priv_data;
    AVIOContext *pb = s->pb;
    unsigned int av_unused file_size;
    unsigned int size;
    int64_t duration;
    int strings_size;
    int table_entries;
    int table_entries_used;

    nsv->state = NSV_UNSYNC; /* in case we fail */

    size = avio_rl32(pb);
    if (size < 28)
        return -1;
    nsv->NSVf_end = size;

    file_size = (uint32_t)avio_rl32(pb);
    av_log(s, AV_LOG_TRACE, "NSV NSVf chunk_size %u\n", size);
    av_log(s, AV_LOG_TRACE, "NSV NSVf file_size %u\n", file_size);

    nsv->duration = duration = avio_rl32(pb); /* in ms */
    av_log(s, AV_LOG_TRACE, "NSV NSVf duration %"PRId64" ms\n", duration);

    strings_size = avio_rl32(pb);
    table_entries = avio_rl32(pb);
    table_entries_used = avio_rl32(pb);
    av_log(s, AV_LOG_TRACE, "NSV NSVf info-strings size: %d, table entries: %d, bis %d\n",
            strings_size, table_entries, table_entries_used);
    if (pb->eof_reached)
        return -1;

    av_log(s, AV_LOG_TRACE, "NSV got header; filepos %"PRId64"\n", avio_tell(pb));

    if (strings_size > 0) {
        char *strings; /* last byte will be '\0' to play safe with str*() */
        char *p, *endp;
        char *token, *value;
        char quote;

        p = strings = av_mallocz((size_t)strings_size + 1);
        if (!p)
            return AVERROR(ENOMEM);
        endp = strings + strings_size;
        avio_read(pb, strings, strings_size);
        while (p < endp) {
            while (*p == ' ')
                p++; /* strip out spaces */
            if (p >= endp-2)
                break;
            token = p;
            p = strchr(p, '=');
            if (!p || p >= endp-2)
                break;
            *p++ = '\0';
            quote = *p++;
            value = p;
            p = strchr(p, quote);
            if (!p || p >= endp)
                break;
            *p++ = '\0';
            av_log(s, AV_LOG_TRACE, "NSV NSVf INFO: %s='%s'\n", token, value);
            av_dict_set(&s->metadata, token, value, 0);
        }
        av_free(strings);
    }
    if (pb->eof_reached)
        return -1;

    av_log(s, AV_LOG_TRACE, "NSV got infos; filepos %"PRId64"\n", avio_tell(pb));

    if (table_entries_used > 0) {
        int i;
        nsv->index_entries = table_entries_used;
        if((unsigned)table_entries_used >= UINT_MAX / sizeof(uint32_t))
            return -1;
        nsv->nsvs_file_offset = av_malloc((unsigned)table_entries_used * sizeof(uint32_t));
        if (!nsv->nsvs_file_offset)
            return AVERROR(ENOMEM);

        for(i=0;i<table_entries_used;i++)
            nsv->nsvs_file_offset[i] = avio_rl32(pb) + size;

        if(table_entries > table_entries_used &&
           avio_rl32(pb) == MKTAG('T','O','C','2')) {
            nsv->nsvs_timestamps = av_malloc((unsigned)table_entries_used*sizeof(uint32_t));
            if (!nsv->nsvs_timestamps)
                return AVERROR(ENOMEM);
            for(i=0;i<table_entries_used;i++) {
                nsv->nsvs_timestamps[i] = avio_rl32(pb);
            }
        }
    }

    av_log(s, AV_LOG_TRACE, "NSV got index; filepos %"PRId64"\n", avio_tell(pb));

    avio_seek(pb, nsv->base_offset + size, SEEK_SET); /* required for dumbdriving-271.nsv (2 extra bytes) */

    if (pb->eof_reached)
        return -1;
    nsv->state = NSV_HAS_READ_NSVF;
    return 0;
}

Project: libav

static int nsv_parse_NSVs_header(AVFormatContext *s)
{
    NSVContext *nsv = s->priv_data;
    AVIOContext *pb = s->pb;
    uint32_t vtag, atag;
    uint16_t vwidth, vheight;
    AVRational framerate;
    int i;
    AVStream *st;
    NSVStream *nst;

    vtag = avio_rl32(pb);
    atag = avio_rl32(pb);
    vwidth = avio_rl16(pb);
    vheight = avio_rl16(pb);
    i = avio_r8(pb);

    av_log(s, AV_LOG_TRACE, "NSV NSVs framerate code %2x\n", i);
    if(i&0x80) { /* odd way of giving native framerates from docs */
        int t=(i & 0x7F)>>2;
        if(t<16) framerate = (AVRational){1, t+1};
        else     framerate = (AVRational){t-15, 1};

        if(i&1){
            framerate.num *= 1000;
            framerate.den *= 1001;
        }

        if((i&3)==3)      framerate.num *= 24;
        else if((i&3)==2) framerate.num *= 25;
        else              framerate.num *= 30;
    }
    else
        framerate= (AVRational){i, 1};

    nsv->avsync = avio_rl16(pb);
    nsv->framerate = framerate;

    av_log(s, AV_LOG_TRACE, "NSV NSVs vsize %"PRIu16"x%"PRIu16"\n",
           vwidth, vheight);

    /* XXX change to ap != NULL ? */
    if (s->nb_streams == 0) { /* streams not yet published, let's do that */
        nsv->vtag = vtag;
        nsv->atag = atag;
        nsv->vwidth = vwidth;
        nsv->vheight = vwidth;
        if (vtag != T_NONE) {
            int i;
            st = avformat_new_stream(s, NULL);
            if (!st)
                goto fail;

            st->id = NSV_ST_VIDEO;
            nst = av_mallocz(sizeof(NSVStream));
            if (!nst)
                goto fail;
            st->priv_data = nst;
            st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
            st->codecpar->codec_tag = vtag;
            st->codecpar->codec_id = ff_codec_get_id(nsv_codec_video_tags, vtag);
            st->codecpar->width = vwidth;
            st->codecpar->height = vheight;
            st->codecpar->bits_per_coded_sample = 24; /* depth XXX */

            avpriv_set_pts_info(st, 64, framerate.den, framerate.num);
            st->start_time = 0;
            st->duration = av_rescale(nsv->duration, framerate.num, 1000*framerate.den);

            for(i=0;i<nsv->index_entries;i++) {
                if(nsv->nsvs_timestamps) {
                    av_add_index_entry(st, nsv->nsvs_file_offset[i], nsv->nsvs_timestamps[i],
                                       0, 0, AVINDEX_KEYFRAME);
                } else {
                    int64_t ts = av_rescale(i*nsv->duration/nsv->index_entries, framerate.num, 1000*framerate.den);
                    av_add_index_entry(st, nsv->nsvs_file_offset[i], ts, 0, 0, AVINDEX_KEYFRAME);
                }
            }
        }
        if (atag != T_NONE) {
            st = avformat_new_stream(s, NULL);
            if (!st)
                goto fail;

            st->id = NSV_ST_AUDIO;
            nst = av_mallocz(sizeof(NSVStream));
            if (!nst)
                goto fail;
            st->priv_data = nst;
            st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
            st->codecpar->codec_tag = atag;
            st->codecpar->codec_id = ff_codec_get_id(nsv_codec_audio_tags, atag);

            st->need_parsing = AVSTREAM_PARSE_FULL; /* for PCM we will read a chunk later and put correct info */

            /* set timebase to common denominator of ms and framerate */
            avpriv_set_pts_info(st, 64, 1, framerate.num*1000);
            st->start_time = 0;
            st->duration = (int64_t)nsv->duration * framerate.num;
        }
    } else {
        if (nsv->vtag != vtag || nsv->atag != atag || nsv->vwidth != vwidth || nsv->vheight != vwidth) {
            av_log(s, AV_LOG_TRACE, "NSV NSVs header values differ from the first one!!!\n");
        }
    }

    nsv->state = NSV_HAS_READ_NSVS;
    return 0;
fail:
    /* XXX */
    nsv->state = NSV_UNSYNC;
    return -1;
}

Project: libav
File: nsvdec.c

static int nsv_probe(AVProbeData *p)
{
    int i;
    int score;
    int vsize, asize, auxcount;
    score = 0;
    av_log(NULL, AV_LOG_TRACE, "nsv_probe(), buf_size %d\n", p->buf_size);
    /* check file header */
    /* streamed files might not have any header */
    if (p->buf[0] == 'N' && p->buf[1] == 'S' &&
        p->buf[2] == 'V' && (p->buf[3] == 'f' || p->buf[3] == 's'))
        return AVPROBE_SCORE_MAX;
    /* XXX: do streamed files always start at chunk boundary ?? */
    /* or do we need to search NSVs in the byte stream ? */
    /* seems the servers don't bother starting clean chunks... */
    /* sometimes even the first header is at 9KB or something :^) */
    for (i = 1; i < p->buf_size - 3; i++) {
        if (p->buf[i+0] == 'N' && p->buf[i+1] == 'S' &&
            p->buf[i+2] == 'V' && p->buf[i+3] == 's') {
            score = AVPROBE_SCORE_MAX/5;
            /* Get the chunk size and check if at the end we are getting 0xBEEF */
            auxcount = p->buf[i+19];
            vsize = p->buf[i+20]  | p->buf[i+21] << 8;
            asize = p->buf[i+22]  | p->buf[i+23] << 8;
            vsize = (vsize << 4) | (auxcount >> 4);
            if ((asize + vsize + i + 23) <  p->buf_size - 2) {
                if (p->buf[i+23+asize+vsize+1] == 0xEF &&
                    p->buf[i+23+asize+vsize+2] == 0xBE)
                    return AVPROBE_SCORE_MAX-20;
            }
        }
    }
    /* so we'll have more luck on extension... */
    if (av_match_ext(p->filename, "nsv"))
        return AVPROBE_SCORE_EXTENSION;
    /* FIXME: add mime-type check */
    return score;
}