Commit message: None
CVE: CVE-2018-9491
CWE: CWE-190
Vulnerability description: In AMediaCodecCryptoInfo_new of NdkMediaCodec.cpp, there is a possible out-of-bounds write due to an integer overflow. This could lead to remote code execution in external apps with no additional execution privileges needed. User interaction is needed for exploitation. Product: Android Versions: Android-7.0 Android-7.1.1 Android-7.1.2 Android-8.0 Android-8.1 Android-9.0 Android ID: A-111603051

Project: Android

bool AMediaCodecActionCode_isRecoverable(int32_t actionCode) {
 return (actionCode == ACTION_CODE_RECOVERABLE);
}

Project: Android

bool AMediaCodecActionCode_isTransient(int32_t actionCode) {
 return (actionCode == ACTION_CODE_TRANSIENT);
}

Project: Android

media_status_t AMediaCodecCryptoInfo_delete(AMediaCodecCryptoInfo* info) {
    free(info);
 return AMEDIA_OK;
}

Project: Android

media_status_t AMediaCodecCryptoInfo_getClearBytes(AMediaCodecCryptoInfo* ci, size_t *dst) {
 if (!ci) {
 return AMEDIA_ERROR_INVALID_OBJECT;
 }
 if (!dst) {
 return AMEDIA_ERROR_INVALID_PARAMETER;
 }
    memcpy(dst, ci->clearbytes, sizeof(size_t) * ci->numsubsamples);
 return AMEDIA_OK;
}

Project: Android

media_status_t AMediaCodecCryptoInfo_getEncryptedBytes(AMediaCodecCryptoInfo* ci, size_t *dst) {
 if (!ci) {
 return AMEDIA_ERROR_INVALID_OBJECT;
 }
 if (!dst) {
 return AMEDIA_ERROR_INVALID_PARAMETER;
 }
    memcpy(dst, ci->encryptedbytes, sizeof(size_t) * ci->numsubsamples);
 return AMEDIA_OK;
}

Project: Android

media_status_t AMediaCodecCryptoInfo_getKey(AMediaCodecCryptoInfo* ci, uint8_t *dst) {
 if (!ci) {
 return AMEDIA_ERROR_INVALID_OBJECT;
 }
 if (!dst) {
 return AMEDIA_ERROR_INVALID_PARAMETER;
 }
    memcpy(dst, ci->key, 16);
 return AMEDIA_OK;
}

Project: Android

cryptoinfo_mode_t AMediaCodecCryptoInfo_getMode(AMediaCodecCryptoInfo* ci) {
 if (!ci) {
 return (cryptoinfo_mode_t) AMEDIA_ERROR_INVALID_OBJECT;
 }
 return ci->mode;
}

Project: Android

size_t AMediaCodecCryptoInfo_getNumSubSamples(AMediaCodecCryptoInfo* ci) {
 return ci->numsubsamples;
}

Project: Android

void AMediaCodecCryptoInfo_setPattern(AMediaCodecCryptoInfo *info,
 cryptoinfo_pattern_t *pattern) {
    info->pattern.encryptBlocks = pattern->encryptBlocks;
    info->pattern.skipBlocks = pattern->skipBlocks;
}

Project: Android

 AMediaCodecPersistentSurface(
 const sp<IGraphicBufferProducer>& igbp,
 const sp<PersistentSurface>& ps)
 : Surface(igbp) {
        mPersistentSurface = ps;
 }

Project: Android

AMediaCodec* AMediaCodec_createCodecByName(const char *name) {
 return createAMediaCodec(name, false, false);
}

Project: Android

AMediaCodec* AMediaCodec_createDecoderByType(const char *mime_type) {
 return createAMediaCodec(mime_type, true, false);
}

Project: Android

AMediaCodec* AMediaCodec_createEncoderByType(const char *name) {
 return createAMediaCodec(name, true, true);
}

Project: Android

media_status_t AMediaCodec_createInputSurface(AMediaCodec *mData, ANativeWindow **surface) {
 if (surface == NULL || mData == NULL) {
 return AMEDIA_ERROR_INVALID_PARAMETER;
 }
 *surface = NULL;

    sp<IGraphicBufferProducer> igbp = NULL;
 status_t err = mData->mCodec->createInputSurface(&igbp);
 if (err != NO_ERROR) {
 return translate_error(err);
 }

 *surface = new Surface(igbp);
 ANativeWindow_acquire(*surface);
 return AMEDIA_OK;
}

Project: Android

media_status_t AMediaCodec_createPersistentInputSurface(ANativeWindow **surface) {
 if (surface == NULL) {
 return AMEDIA_ERROR_INVALID_PARAMETER;
 }
 *surface = NULL;

    sp<PersistentSurface> ps = MediaCodec::CreatePersistentInputSurface();
 if (ps == NULL) {
 return AMEDIA_ERROR_UNKNOWN;
 }

    sp<IGraphicBufferProducer> igbp = ps->getBufferProducer();
 if (igbp == NULL) {
 return AMEDIA_ERROR_UNKNOWN;
 }

 *surface = new AMediaCodecPersistentSurface(igbp, ps);
 ANativeWindow_acquire(*surface);

 return AMEDIA_OK;
}

Project: Android

media_status_t AMediaCodec_delete(AMediaCodec *mData) {
 if (mData != NULL) {
 if (mData->mCodec != NULL) {
            mData->mCodec->release();
            mData->mCodec.clear();
 }

 if (mData->mLooper != NULL) {
 if (mData->mHandler != NULL) {
                mData->mLooper->unregisterHandler(mData->mHandler->id());
 }
            mData->mLooper->stop();
            mData->mLooper.clear();
 }
 delete mData;
 }
 return AMEDIA_OK;
}

Project: Android

ssize_t AMediaCodec_dequeueInputBuffer(AMediaCodec *mData, int64_t timeoutUs) {
 size_t idx;
 status_t ret = mData->mCodec->dequeueInputBuffer(&idx, timeoutUs);
    requestActivityNotification(mData);
 if (ret == OK) {
 return idx;
 }
 return translate_error(ret);
}

Project: Android

ssize_t AMediaCodec_dequeueOutputBuffer(AMediaCodec *mData,
 AMediaCodecBufferInfo *info, int64_t timeoutUs) {
 size_t idx;
 size_t offset;
 size_t size;
 uint32_t flags;
 int64_t presentationTimeUs;
 status_t ret = mData->mCodec->dequeueOutputBuffer(&idx, &offset, &size, &presentationTimeUs,
 &flags, timeoutUs);
    requestActivityNotification(mData);
 switch (ret) {
 case OK:
            info->offset = offset;
            info->size = size;
            info->flags = flags;
            info->presentationTimeUs = presentationTimeUs;
 return idx;
 case -EAGAIN:
 return AMEDIACODEC_INFO_TRY_AGAIN_LATER;
 case android::INFO_FORMAT_CHANGED:
 return AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED;
 case INFO_OUTPUT_BUFFERS_CHANGED:
 return AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED;
 default:
 break;
 }
 return translate_error(ret);
}

Project: Android

media_status_t AMediaCodec_flush(AMediaCodec *mData) {
 return translate_error(mData->mCodec->flush());
}

Project: Android

AMediaFormat* AMediaCodec_getBufferFormat(AMediaCodec *mData, size_t index) {
    sp<AMessage> format;
    mData->mCodec->getOutputFormat(index, &format);
 return AMediaFormat_fromMsg(&format);
}

Project: Android

uint8_t* AMediaCodec_getInputBuffer(AMediaCodec *mData, size_t idx, size_t *out_size) {
 if (mData->mAsyncNotify != NULL) {
        sp<MediaCodecBuffer> abuf;
 if (mData->mCodec->getInputBuffer(idx, &abuf) != 0) {
 return NULL;
 }

 if (out_size != NULL) {
 *out_size = abuf->capacity();
 }
 return abuf->data();
 }

    android::Vector<android::sp<android::MediaCodecBuffer> > abufs;
 if (mData->mCodec->getInputBuffers(&abufs) == 0) {
 size_t n = abufs.size();
 if (idx >= n) {
            ALOGE("buffer index %zu out of range", idx);
 return NULL;
 }
 if (abufs[idx] == NULL) {
            ALOGE("buffer index %zu is NULL", idx);
 return NULL;
 }
 if (out_size != NULL) {
 *out_size = abufs[idx]->capacity();
 }
 return abufs[idx]->data();
 }
    ALOGE("couldn't get input buffers");
 return NULL;
}

Project: Android

media_status_t AMediaCodec_getName(
 AMediaCodec *mData,
 char** out_name) {
 if (out_name == NULL) {
 return AMEDIA_ERROR_INVALID_PARAMETER;
 }

 AString compName;
 status_t err = mData->mCodec->getName(&compName);
 if (err != OK) {
 return translate_error(err);
 }
 *out_name = strdup(compName.c_str());
 return AMEDIA_OK;
}

Project: Android

uint8_t* AMediaCodec_getOutputBuffer(AMediaCodec *mData, size_t idx, size_t *out_size) {
 if (mData->mAsyncNotify != NULL) {
        sp<MediaCodecBuffer> abuf;
 if (mData->mCodec->getOutputBuffer(idx, &abuf) != 0) {
 return NULL;
 }

 if (out_size != NULL) {
 *out_size = abuf->capacity();
 }
 return abuf->data();
 }

    android::Vector<android::sp<android::MediaCodecBuffer> > abufs;
 if (mData->mCodec->getOutputBuffers(&abufs) == 0) {
 size_t n = abufs.size();
 if (idx >= n) {
            ALOGE("buffer index %zu out of range", idx);
 return NULL;
 }
 if (out_size != NULL) {
 *out_size = abufs[idx]->capacity();
 }
 return abufs[idx]->data();
 }
    ALOGE("couldn't get output buffers");
 return NULL;
}

Project: Android

AMediaFormat* AMediaCodec_getOutputFormat(AMediaCodec *mData) {
    sp<AMessage> format;
    mData->mCodec->getOutputFormat(&format);
 return AMediaFormat_fromMsg(&format);
}

Project: Android

media_status_t AMediaCodec_queueInputBuffer(AMediaCodec *mData,
 size_t idx, off_t offset, size_t size, uint64_t time, uint32_t flags) {

 AString errorMsg;
 status_t ret = mData->mCodec->queueInputBuffer(idx, offset, size, time, flags, &errorMsg);
 return translate_error(ret);
}

Project: Android

media_status_t AMediaCodec_queueSecureInputBuffer(
 AMediaCodec* codec,
 size_t idx,
 off_t offset,
 AMediaCodecCryptoInfo* crypto,
 uint64_t time,
 uint32_t flags) {

 CryptoPlugin::SubSample *subSamples = new CryptoPlugin::SubSample[crypto->numsubsamples];
 for (int i = 0; i < crypto->numsubsamples; i++) {
        subSamples[i].mNumBytesOfClearData = crypto->clearbytes[i];
        subSamples[i].mNumBytesOfEncryptedData = crypto->encryptedbytes[i];
 }

 CryptoPlugin::Pattern pattern;
    pattern.mEncryptBlocks = crypto->pattern.encryptBlocks;
    pattern.mSkipBlocks = crypto->pattern.skipBlocks;

 AString errormsg;
 status_t err  = codec->mCodec->queueSecureInputBuffer(idx,
            offset,
            subSamples,
            crypto->numsubsamples,
            crypto->key,
            crypto->iv,
 (CryptoPlugin::Mode)crypto->mode,
            pattern,
            time,
            flags,
 &errormsg);
 if (err != 0) {
        ALOGE("queSecureInputBuffer: %s", errormsg.c_str());
 }
 delete [] subSamples;
 return translate_error(err);
}

Project: Android

media_status_t AMediaCodec_releaseCrypto(AMediaCodec *mData) {
 return translate_error(mData->mCodec->releaseCrypto());
}

Project: Android

void AMediaCodec_releaseName(
 AMediaCodec * /* mData */,
 char* name) {
 if (name != NULL) {
        free(name);
 }
}

Project: Android

media_status_t AMediaCodec_releaseOutputBufferAtTime(
 AMediaCodec *mData, size_t idx, int64_t timestampNs) {
    ALOGV("render @ %" PRId64, timestampNs);
 return translate_error(mData->mCodec->renderOutputBufferAndRelease(idx, timestampNs));
}

Project: Android

media_status_t AMediaCodec_setAsyncNotifyCallback(
 AMediaCodec *mData,
 AMediaCodecOnAsyncNotifyCallback callback,
 void *userdata) {
 if (mData->mAsyncNotify == NULL && userdata != NULL) {
        mData->mAsyncNotify = new AMessage(kWhatAsyncNotify, mData->mHandler);
 status_t err = mData->mCodec->setCallback(mData->mAsyncNotify);
 if (err != OK) {
            ALOGE("setAsyncNotifyCallback: err(%d), failed to set async callback", err);
 return translate_error(err);
 }
 }

 Mutex::Autolock _l(mData->mAsyncCallbackLock);
    mData->mAsyncCallback = callback;
    mData->mAsyncCallbackUserData = userdata;

 return AMEDIA_OK;
}

Project: Android

media_status_t AMediaCodec_setInputSurface(
 AMediaCodec *mData, ANativeWindow *surface) {

 if (surface == NULL || mData == NULL) {
 return AMEDIA_ERROR_INVALID_PARAMETER;
 }

 AMediaCodecPersistentSurface *aMediaPersistentSurface =
 static_cast<AMediaCodecPersistentSurface *>(surface);
 if (aMediaPersistentSurface->mPersistentSurface == NULL) {
 return AMEDIA_ERROR_INVALID_PARAMETER;
 }

 return translate_error(mData->mCodec->setInputSurface(
            aMediaPersistentSurface->mPersistentSurface));
}

Project: Android

media_status_t AMediaCodec_setNotificationCallback(AMediaCodec *mData, OnCodecEvent callback,
 void *userdata) {
    mData->mCallback = callback;
    mData->mCallbackUserData = userdata;
 return AMEDIA_OK;
}

Project: Android

media_status_t AMediaCodec_setParameters(
 AMediaCodec *mData, const AMediaFormat* params) {
 if (params == NULL || mData == NULL) {
 return AMEDIA_ERROR_INVALID_PARAMETER;
 }
    sp<AMessage> nativeParams;
 AMediaFormat_getFormat(params, &nativeParams);
    ALOGV("setParameters: %s", nativeParams->debugString(0).c_str());

 return translate_error(mData->mCodec->setParameters(nativeParams));
}

Project: Android

media_status_t AMediaCodec_signalEndOfInputStream(AMediaCodec *mData) {

 if (mData == NULL) {
 return AMEDIA_ERROR_INVALID_PARAMETER;
 }

 status_t err = mData->mCodec->signalEndOfInputStream();
 if (err == INVALID_OPERATION) {
 return AMEDIA_ERROR_INVALID_OPERATION;
 }

 return translate_error(err);

}

Project: Android

media_status_t AMediaCodec_start(AMediaCodec *mData) {
 status_t ret =  mData->mCodec->start();
 if (ret != OK) {
 return translate_error(ret);
 }
    mData->mActivityNotification = new AMessage(kWhatActivityNotify, mData->mHandler);
    mData->mActivityNotification->setInt32("generation", mData->mGeneration);
    requestActivityNotification(mData);
 return AMEDIA_OK;
}

Project: Android

media_status_t AMediaCodec_stop(AMediaCodec *mData) {
 media_status_t ret = translate_error(mData->mCodec->stop());

    sp<AMessage> msg = new AMessage(kWhatStopActivityNotifications, mData->mHandler);
    sp<AMessage> response;
    msg->postAndAwaitResponse(&response);
    mData->mActivityNotification.clear();

 return ret;
}

Project: Android

CodecHandler::CodecHandler(AMediaCodec *codec) {
    mCodec = codec;
}

Project: Android

static AMediaCodec * createAMediaCodec(const char *name, bool name_is_type, bool encoder) {
 AMediaCodec *mData = new AMediaCodec();
    mData->mLooper = new ALooper;
    mData->mLooper->setName("NDK MediaCodec_looper");
 size_t res = mData->mLooper->start(
 false, // runOnCallingThread
 true, // canCallJava XXX
            PRIORITY_AUDIO);
 if (res != OK) {
        ALOGE("Failed to start the looper");
 AMediaCodec_delete(mData);
 return NULL;
 }
 if (name_is_type) {
        mData->mCodec = android::MediaCodec::CreateByType(mData->mLooper, name, encoder);
 } else {
        mData->mCodec = android::MediaCodec::CreateByComponentName(mData->mLooper, name);
 }
 if (mData->mCodec == NULL) { // failed to create codec
 AMediaCodec_delete(mData);
 return NULL;
 }
    mData->mHandler = new CodecHandler(mData);
    mData->mLooper->registerHandler(mData->mHandler);
    mData->mGeneration = 1;
    mData->mRequestedActivityNotification = false;
    mData->mCallback = NULL;

    mData->mAsyncCallback = {};
    mData->mAsyncCallbackUserData = NULL;

 return mData;
}

Project: Android

void CodecHandler::onMessageReceived(const sp<AMessage> &msg) {

 switch (msg->what()) {
 case kWhatRequestActivityNotifications:
 {
 if (mCodec->mRequestedActivityNotification) {
 break;
 }

            mCodec->mCodec->requestActivityNotification(mCodec->mActivityNotification);
            mCodec->mRequestedActivityNotification = true;
 break;
 }

 case kWhatActivityNotify:
 {
 {
 int32_t generation;
                msg->findInt32("generation", &generation);

 if (generation != mCodec->mGeneration) {
 break;
 }

                mCodec->mRequestedActivityNotification = false;
 }

 if (mCodec->mCallback) {
                mCodec->mCallback(mCodec, mCodec->mCallbackUserData);
 }
 break;
 }

 case kWhatAsyncNotify:
 {
 int32_t cbID;
 if (!msg->findInt32("callbackID", &cbID)) {
                 ALOGE("kWhatAsyncNotify: callbackID is expected.");
 break;
 }

             ALOGV("kWhatAsyncNotify: cbID = %d", cbID);

 switch (cbID) {
 case MediaCodec::CB_INPUT_AVAILABLE:
 {
 int32_t index;
 if (!msg->findInt32("index", &index)) {
                         ALOGE("CB_INPUT_AVAILABLE: index is expected.");
 break;
 }

 Mutex::Autolock _l(mCodec->mAsyncCallbackLock);
 if (mCodec->mAsyncCallbackUserData != NULL
 || mCodec->mAsyncCallback.onAsyncInputAvailable != NULL) {
                         mCodec->mAsyncCallback.onAsyncInputAvailable(
                                 mCodec,
                                 mCodec->mAsyncCallbackUserData,
                                 index);
 }

 break;
 }

 case MediaCodec::CB_OUTPUT_AVAILABLE:
 {
 int32_t index;
 size_t offset;
 size_t size;
 int64_t timeUs;
 int32_t flags;

 if (!msg->findInt32("index", &index)) {
                         ALOGE("CB_OUTPUT_AVAILABLE: index is expected.");
 break;
 }
 if (!msg->findSize("offset", &offset)) {
                         ALOGE("CB_OUTPUT_AVAILABLE: offset is expected.");
 break;
 }
 if (!msg->findSize("size", &size)) {
                         ALOGE("CB_OUTPUT_AVAILABLE: size is expected.");
 break;
 }
 if (!msg->findInt64("timeUs", &timeUs)) {
                         ALOGE("CB_OUTPUT_AVAILABLE: timeUs is expected.");
 break;
 }
 if (!msg->findInt32("flags", &flags)) {
                         ALOGE("CB_OUTPUT_AVAILABLE: flags is expected.");
 break;
 }

 AMediaCodecBufferInfo bufferInfo = {
 (int32_t)offset,
 (int32_t)size,
                         timeUs,
 (uint32_t)flags};

 Mutex::Autolock _l(mCodec->mAsyncCallbackLock);
 if (mCodec->mAsyncCallbackUserData != NULL
 || mCodec->mAsyncCallback.onAsyncOutputAvailable != NULL) {
                         mCodec->mAsyncCallback.onAsyncOutputAvailable(
                                 mCodec,
                                 mCodec->mAsyncCallbackUserData,
                                 index,
 &bufferInfo);
 }

 break;
 }

 case MediaCodec::CB_OUTPUT_FORMAT_CHANGED:
 {
                     sp<AMessage> format;
 if (!msg->findMessage("format", &format)) {
                         ALOGE("CB_OUTPUT_FORMAT_CHANGED: format is expected.");
 break;
 }

 AMediaFormat *aMediaFormat = AMediaFormat_fromMsg(&format);

 Mutex::Autolock _l(mCodec->mAsyncCallbackLock);
 if (mCodec->mAsyncCallbackUserData != NULL
 || mCodec->mAsyncCallback.onAsyncFormatChanged != NULL) {
                         mCodec->mAsyncCallback.onAsyncFormatChanged(
                                 mCodec,
                                 mCodec->mAsyncCallbackUserData,
                                 aMediaFormat);
 }

 break;
 }

 case MediaCodec::CB_ERROR:
 {
 status_t err;
 int32_t actionCode;
 AString detail;
 if (!msg->findInt32("err", &err)) {
                         ALOGE("CB_ERROR: err is expected.");
 break;
 }
 if (!msg->findInt32("action", &actionCode)) {
                         ALOGE("CB_ERROR: action is expected.");
 break;
 }
                     msg->findString("detail", &detail);
                     ALOGE("Decoder reported error(0x%x), actionCode(%d), detail(%s)",
                           err, actionCode, detail.c_str());

 Mutex::Autolock _l(mCodec->mAsyncCallbackLock);
 if (mCodec->mAsyncCallbackUserData != NULL
 || mCodec->mAsyncCallback.onAsyncError != NULL) {
                         mCodec->mAsyncCallback.onAsyncError(
                                 mCodec,
                                 mCodec->mAsyncCallbackUserData,
                                 translate_error(err),
                                 actionCode,
                                 detail.c_str());
 }

 break;
 }

 default:
 {
                     ALOGE("kWhatAsyncNotify: callbackID(%d) is unexpected.", cbID);
 break;
 }
 }
 break;
 }

 case kWhatStopActivityNotifications:
 {
            sp<AReplyToken> replyID;
            msg->senderAwaitsResponse(&replyID);

            mCodec->mGeneration++;
            mCodec->mRequestedActivityNotification = false;

            sp<AMessage> response = new AMessage;
            response->postReply(replyID);
 break;
 }

 default:
            ALOGE("shouldn't be here");
 break;
 }

}

Project: Android

static void requestActivityNotification(AMediaCodec *codec) {
 (new AMessage(kWhatRequestActivityNotifications, codec->mHandler))->post();
}

Project: Android

static media_status_t translate_error(status_t err) {
 if (err == OK) {
 return AMEDIA_OK;
 } else if (err == -EAGAIN) {
 return (media_status_t) AMEDIACODEC_INFO_TRY_AGAIN_LATER;
 }
    ALOGE("sf error code: %d", err);
 return AMEDIA_ERROR_UNKNOWN;
}

Project: Android

 virtual ~AMediaCodecPersistentSurface() {
 }