Commit message: None
CVE: CVE-2017-0818
CWE: CWE-772
Vulnerability description: A vulnerability in the Android media framework (n/a). Product: Android. Versions: 7.0, 7.1.1, 7.1.2, 8.0. Android ID: A-63581671.

Project: Android

MyOggExtractor::MyOggExtractor(
 const sp<DataSource> &source,
 const char *mimeType,
 size_t numHeaders,
 int64_t seekPreRollUs)
 : mSource(source),
      mOffset(0),
      mCurGranulePosition(0),
      mPrevGranulePosition(0),
      mCurrentPageSize(0),
      mFirstPacketInPage(true),
      mCurrentPageSamples(0),
      mNextLaceIndex(0),
      mMimeType(mimeType),
      mNumHeaders(numHeaders),
      mSeekPreRollUs(seekPreRollUs),
      mFirstDataOffset(-1) {
    mCurrentPage.mNumSegments = 0;

    vorbis_info_init(&mVi);
    vorbis_comment_init(&mVc);
}

Project: Android

 explicit MyVorbisExtractor(const sp<DataSource> &source)
 : MyOggExtractor(source,
                MEDIA_MIMETYPE_AUDIO_VORBIS,
 /* numHeaders */ 3,
 /* seekPreRollUs */ 0) {
 }

Project: Android

OggExtractor::OggExtractor(const sp<DataSource> &source)
 : mDataSource(source),
      mInitCheck(NO_INIT),
      mImpl(NULL) {
 for (int i = 0; i < 2; ++i) {
 if (mImpl != NULL) {
 delete mImpl;
 }
 if (i == 0) {
            mImpl = new MyVorbisExtractor(mDataSource);
 } else {
            mImpl = new MyOpusExtractor(mDataSource);
 }
        mInitCheck = mImpl->seekToOffset(0);

 if (mInitCheck == OK) {
            mInitCheck = mImpl->init();
 if (mInitCheck == OK) {
 break;
 }
 }
 }
}

Project: Android

bool SniffOgg(
 const sp<DataSource> &source, String8 *mimeType, float *confidence,
        sp<AMessage> *) {
 char tmp[4];
 if (source->readAt(0, tmp, 4) < 4 || memcmp(tmp, "OggS", 4)) {
 return false;
 }

    mimeType->setTo(MEDIA_MIMETYPE_CONTAINER_OGG);
 *confidence = 0.2f;

 return true;
}

Project: Android

 virtual uint64_t approxBitrate() const {
 return 0;
 }

Project: Android

uint64_t MyVorbisExtractor::approxBitrate() const {
 if (mVi.bitrate_nominal != 0) {
 return mVi.bitrate_nominal;
 }

 return (mVi.bitrate_lower + mVi.bitrate_upper) / 2;
}

Project: Android

void MyOggExtractor::buildTableOfContents() {
 off64_t offset = mFirstDataOffset;
 Page page;
 ssize_t pageSize;
 while ((pageSize = readPage(offset, &page)) > 0) {
        mTableOfContents.push();

 TOCEntry &entry =
            mTableOfContents.editItemAt(mTableOfContents.size() - 1);

        entry.mPageOffset = offset;
        entry.mTimeUs = getTimeUsOfGranule(page.mGranulePosition);

        offset += (size_t)pageSize;
 }


 static const size_t kMaxTOCSize = 8192;
 static const size_t kMaxNumTOCEntries = kMaxTOCSize / sizeof(TOCEntry);

 size_t numerator = mTableOfContents.size();

 if (numerator > kMaxNumTOCEntries) {
 size_t denom = numerator - kMaxNumTOCEntries;

 size_t accum = 0;
 for (ssize_t i = mTableOfContents.size() - 1; i >= 0; --i) {
            accum += denom;
 if (accum >= numerator) {
                mTableOfContents.removeAt(i);
                accum -= numerator;
 }
 }
 }
}

Project: Android

size_t OggExtractor::countTracks() {
 return mInitCheck != OK ? 0 : 1;
}

Project: Android

status_t MyOggExtractor::findNextPage(
 off64_t startOffset, off64_t *pageOffset) {
 *pageOffset = startOffset;

 for (;;) {
 char signature[4];
 ssize_t n = mSource->readAt(*pageOffset, &signature, 4);

 if (n < 4) {
 *pageOffset = 0;

 return (n < 0) ? n : (status_t)ERROR_END_OF_STREAM;
 }

 if (!memcmp(signature, "OggS", 4)) {
 if (*pageOffset > startOffset) {
                ALOGV("skipped %lld bytes of junk to reach next frame",
 (long long)(*pageOffset - startOffset));
 }

 return OK;
 }

 ++*pageOffset;
 }
}

Project: Android

status_t MyOggExtractor::findPrevGranulePosition(
 off64_t pageOffset, uint64_t *granulePos) {
 *granulePos = 0;

 off64_t prevPageOffset = 0;
 off64_t prevGuess = pageOffset;
 for (;;) {
 if (prevGuess >= 5000) {
            prevGuess -= 5000;
 } else {
            prevGuess = 0;
 }

        ALOGV("backing up %lld bytes", (long long)(pageOffset - prevGuess));

 status_t err = findNextPage(prevGuess, &prevPageOffset);
 if (err == ERROR_END_OF_STREAM) {
 continue;
 } else if (err != OK) {
 return err;
 }

 if (prevPageOffset < pageOffset || prevGuess == 0) {
 break;
 }
 }

 if (prevPageOffset == pageOffset) {
 return UNKNOWN_ERROR;
 }

    ALOGV("prevPageOffset at %lld, pageOffset at %lld",
 (long long)prevPageOffset, (long long)pageOffset);

 for (;;) {
 Page prevPage;
 ssize_t n = readPage(prevPageOffset, &prevPage);

 if (n <= 0) {
 return (status_t)n;
 }

        prevPageOffset += n;

 if (prevPageOffset == pageOffset) {
 *granulePos = prevPage.mGranulePosition;
 return OK;
 }
 }
}

Project: Android

    sp<MetaData> getFileMetaData() { return mFileMeta; }

Project: Android

sp<MetaData> OggSource::getFormat() {
 return mExtractor->mImpl->getFormat();
}

Project: Android

sp<MetaData> MyOggExtractor::getFormat() const {
 return mMeta;
}

Project: Android

sp<MetaData> OggExtractor::getMetaData() {
 return mImpl->getFileMetaData();
}

Project: Android

int32_t MyOggExtractor::getPacketBlockSize(MediaBuffer *buffer) {
 const uint8_t *data =
 (const uint8_t *)buffer->data() + buffer->range_offset();

 size_t size = buffer->range_length();

    ogg_buffer buf;
    buf.data = (uint8_t *)data;
    buf.size = size;
    buf.refcount = 1;
    buf.ptr.owner = NULL;

    ogg_reference ref;
    ref.buffer = &buf;
    ref.begin = 0;
    ref.length = size;
    ref.next = NULL;

    ogg_packet pack;
    pack.packet = &ref;
    pack.bytes = ref.length;
    pack.b_o_s = 0;
    pack.e_o_s = 0;
    pack.granulepos = 0;
    pack.packetno = 0;

 return vorbis_packet_blocksize(&mVi, &pack);
}

Project: Android

 virtual int64_t getTimeUsOfGranule(uint64_t granulePos) const {
 if (granulePos > INT64_MAX / 1000000ll) {
 return INT64_MAX;
 }
 return granulePos * 1000000ll / mVi.rate;
 }

Project: Android

int64_t MyOpusExtractor::getTimeUsOfGranule(uint64_t granulePos) const {
 uint64_t pcmSamplePosition = 0;
 if (granulePos > mCodecDelay) {
        pcmSamplePosition = granulePos - mCodecDelay;
 }
 if (pcmSamplePosition > INT64_MAX / 1000000ll) {
 return INT64_MAX;
 }
 return pcmSamplePosition * 1000000ll / kOpusSampleRate;
}

Project: Android

sp<IMediaSource> OggExtractor::getTrack(size_t index) {
 if (index >= 1) {
 return NULL;
 }

 return new OggSource(this);
}

Project: Android

sp<MetaData> OggExtractor::getTrackMetaData(
 size_t index, uint32_t /* flags */) {
 if (index >= 1) {
 return NULL;
 }

 return mImpl->getFormat();
}

Project: Android

status_t MyOggExtractor::init() {
    mMeta = new MetaData;
    mMeta->setCString(kKeyMIMEType, mMimeType);

 status_t err;
 MediaBuffer *packet;
 for (size_t i = 0; i < mNumHeaders; ++i) {
 if ((err = _readNextPacket(&packet, /* calcVorbisTimestamp = */ false)) != OK) {
 return err;
 }
        ALOGV("read packet of size %zu\n", packet->range_length());
        err = verifyHeader(packet, /* type = */ i * 2 + 1);
        packet->release();
        packet = NULL;
 if (err != OK) {
 return err;
 }
 }

    mFirstDataOffset = mOffset + mCurrentPageSize;

 off64_t size;
 uint64_t lastGranulePosition;
 if (!(mSource->flags() & DataSource::kIsCachingDataSource)
 && mSource->getSize(&size) == OK
 && findPrevGranulePosition(size, &lastGranulePosition) == OK) {

 int64_t durationUs = getTimeUsOfGranule(lastGranulePosition);

        mMeta->setInt64(kKeyDuration, durationUs);

        buildTableOfContents();
 }

 return OK;
}

Project: Android

void MyOggExtractor::parseFileMetaData() {
    mFileMeta = new MetaData;
    mFileMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_OGG);

 for (int i = 0; i < mVc.comments; ++i) {
 const char *comment = mVc.user_comments[i];
 size_t commentLength = mVc.comment_lengths[i];
        parseVorbisComment(mFileMeta, comment, commentLength);
 }
}

Project: Android

void parseVorbisComment(
 const sp<MetaData> &fileMeta, const char *comment, size_t commentLength)
{
 struct {
 const char *const mTag;
 uint32_t mKey;
 } kMap[] = {
 { "TITLE", kKeyTitle },
 { "ARTIST", kKeyArtist },
 { "ALBUMARTIST", kKeyAlbumArtist },
 { "ALBUM ARTIST", kKeyAlbumArtist },
 { "COMPILATION", kKeyCompilation },
 { "ALBUM", kKeyAlbum },
 { "COMPOSER", kKeyComposer },
 { "GENRE", kKeyGenre },
 { "AUTHOR", kKeyAuthor },
 { "TRACKNUMBER", kKeyCDTrackNumber },
 { "DISCNUMBER", kKeyDiscNumber },
 { "DATE", kKeyDate },
 { "YEAR", kKeyYear },
 { "LYRICIST", kKeyWriter },
 { "METADATA_BLOCK_PICTURE", kKeyAlbumArt },
 { "ANDROID_LOOP", kKeyAutoLoop },
 };

 for (size_t j = 0; j < sizeof(kMap) / sizeof(kMap[0]); ++j) {
 size_t tagLen = strlen(kMap[j].mTag);
 if (!strncasecmp(kMap[j].mTag, comment, tagLen)
 && comment[tagLen] == '=') {
 if (kMap[j].mKey == kKeyAlbumArt) {
                    extractAlbumArt(
                            fileMeta,
 &comment[tagLen + 1],
                            commentLength - tagLen - 1);
 } else if (kMap[j].mKey == kKeyAutoLoop) {
 if (!strcasecmp(&comment[tagLen + 1], "true")) {
                        fileMeta->setInt32(kKeyAutoLoop, true);
 }
 } else {
                    fileMeta->setCString(kMap[j].mKey, &comment[tagLen + 1]);
 }
 }
 }

}

Project: Android

status_t OggSource::read(
 MediaBuffer **out, const ReadOptions *options) {
 *out = NULL;

 int64_t seekTimeUs;
 ReadOptions::SeekMode mode;
 if (options && options->getSeekTo(&seekTimeUs, &mode)) {
 status_t err = mExtractor->mImpl->seekToTime(seekTimeUs);
 if (err != OK) {
 return err;
 }
 }

 MediaBuffer *packet;
 status_t err = mExtractor->mImpl->readNextPacket(&packet);

 if (err != OK) {
 return err;
 }

#if 0
 int64_t timeUs;
 if (packet->meta_data()->findInt64(kKeyTime, &timeUs)) {
        ALOGI("found time = %lld us", timeUs);
 } else {
        ALOGI("NO time");
 }
#endif

    packet->meta_data()->setInt32(kKeyIsSyncFrame, 1);

 *out = packet;

 return OK;
}

Project: Android

 virtual status_t readNextPacket(MediaBuffer **buffer) {
 return _readNextPacket(buffer, /* calcVorbisTimestamp = */ true);
 }

Project: Android

ssize_t MyOggExtractor::readPage(off64_t offset, Page *page) {
 uint8_t header[27];
 ssize_t n;
 if ((n = mSource->readAt(offset, header, sizeof(header)))
 < (ssize_t)sizeof(header)) {
        ALOGV("failed to read %zu bytes at offset %#016llx, got %zd bytes",
 sizeof(header), (long long)offset, n);

 if (n < 0) {
 return n;
 } else if (n == 0) {
 return ERROR_END_OF_STREAM;
 } else {
 return ERROR_IO;
 }
 }

 if (memcmp(header, "OggS", 4)) {
 return ERROR_MALFORMED;
 }

 if (header[4] != 0) {

 return ERROR_UNSUPPORTED;
 }

    page->mFlags = header[5];

 if (page->mFlags & ~7) {
 return ERROR_MALFORMED;
 }

    page->mGranulePosition = U64LE_AT(&header[6]);

#if 0
    printf("granulePosition = %llu (0x%llx)\n",
           page->mGranulePosition, page->mGranulePosition);
#endif

    page->mSerialNo = U32LE_AT(&header[14]);
    page->mPageNo = U32LE_AT(&header[18]);

    page->mNumSegments = header[26];
 if (mSource->readAt(
                offset + sizeof(header), page->mLace, page->mNumSegments)
 < (ssize_t)page->mNumSegments) {
 return ERROR_IO;
 }

 size_t totalSize = 0;;
 for (size_t i = 0; i < page->mNumSegments; ++i) {
        totalSize += page->mLace[i];
 }

#if 0
 String8 tmp;
 for (size_t i = 0; i < page->mNumSegments; ++i) {
 char x[32];
        sprintf(x, "%s%u", i > 0 ? ", " : "", (unsigned)page->mLace[i]);

        tmp.append(x);
 }

    ALOGV("%c %s", page->mFlags & 1 ? '+' : ' ', tmp.string());
#endif

 return sizeof(header) + page->mNumSegments + totalSize;
}

Project: Android

status_t MyOggExtractor::seekToOffset(off64_t offset) {
 if (mFirstDataOffset >= 0 && offset < mFirstDataOffset) {
        offset = mFirstDataOffset;
 }

 off64_t pageOffset;
 status_t err = findNextPage(offset, &pageOffset);

 if (err != OK) {
 return err;
 }

    findPrevGranulePosition(pageOffset, &mPrevGranulePosition);

    mOffset = pageOffset;

    mCurrentPageSize = 0;
    mFirstPacketInPage = true;
    mCurrentPageSamples = 0;
    mCurrentPage.mNumSegments = 0;
    mCurrentPage.mPrevPacketSize = -1;
    mNextLaceIndex = 0;


 return OK;
}

Project: Android

status_t MyOggExtractor::seekToTime(int64_t timeUs) {
    timeUs -= mSeekPreRollUs;
 if (timeUs < 0) {
        timeUs = 0;
 }

 if (mTableOfContents.isEmpty()) {
 uint64_t bps = approxBitrate();
 if (bps <= 0) {
 return INVALID_OPERATION;
 }

 off64_t pos = timeUs * bps / 8000000ll;

        ALOGV("seeking to offset %lld", (long long)pos);
 return seekToOffset(pos);
 }

 size_t left = 0;
 size_t right_plus_one = mTableOfContents.size();
 while (left < right_plus_one) {
 size_t center = left + (right_plus_one - left) / 2;

 const TOCEntry &entry = mTableOfContents.itemAt(center);

 if (timeUs < entry.mTimeUs) {
            right_plus_one = center;
 } else if (timeUs > entry.mTimeUs) {
            left = center + 1;
 } else {
            left = center;
 break;
 }
 }

 if (left == mTableOfContents.size()) {
 --left;
 }

 const TOCEntry &entry = mTableOfContents.itemAt(left);

    ALOGV("seeking to entry %zu / %zu at offset %lld",
         left, mTableOfContents.size(), (long long)entry.mPageOffset);

 return seekToOffset(entry.mPageOffset);
}

Project: Android

status_t OggSource::start(MetaData * /* params */) {
 if (mStarted) {
 return INVALID_OPERATION;
 }

    mStarted = true;

 return OK;
}

Project: Android

status_t OggSource::stop() {
    mStarted = false;

 return OK;
}

Project: Android

status_t MyOpusExtractor::verifyHeader(MediaBuffer *buffer, uint8_t type) {
 switch (type) {
 case 1:
 return verifyOpusHeader(buffer);
 case 3:
 return verifyOpusComments(buffer);
 default:
 return INVALID_OPERATION;
 }
}

Project: Android

MyOggExtractor::~MyOggExtractor() {
    vorbis_comment_clear(&mVc);
    vorbis_info_clear(&mVi);
}

Project: Android

OggExtractor::~OggExtractor() {
 delete mImpl;
    mImpl = NULL;
}

Project: Android

OggSource::~OggSource() {
 if (mStarted) {
        stop();
 }
}