androidandroid-5.0-lollipopandroid-mediacodecstagefright

Secure Playback: Crash observed in MediaCodec


I am working on enabling secure playback on Lollipop. I am using ExoPlayer to validate the usecase. I am able to create a secure OMX video decoder component(H264.secure).

However, after the creation, I am facing a crash in MediaCodec as shown below

signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0xf0300000
eax f0300000  ebx ec1da038  ecx 00000005  edx 00000002
esi ec3ca200  edi f325b148
xcs 00000023  xds 0000002b  xes 0000002b  xfs 000000bf  xss 0000002b
eip ec0e1655  ebp e05ffb28  esp e05ffa90  flags 00210202  

#00 pc 000b5655  /system/lib/libstagefright.so (android::MediaCodec::onQueueInputBuffer(android::sp<android::AMessage> const&)+1061)
#01 pc 000b7b16  /system/lib/libstagefright.so (android::MediaCodec::onMessageReceived(android::sp<android::AMessage> const&)+1894)
#02 pc 0000e039  /system/lib/libstagefright_foundation.so (android::ALooperRoster::deliverMessage(android::sp<android::AMessage> const&)+345)
#03 pc 0000d3d0  /system/lib/libstagefright_foundation.so (android::ALooper::loop()+256)
#04 pc 0000d4ed  /system/lib/libstagefright_foundation.so (android::ALooper::LooperThread::threadLoop()+29)
#05 pc 000169de  /system/lib/libutils.so (android::Thread::_threadLoop(void*)+398)
#06 pc 0006fe92  /system/lib/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+98)
#07 pc 000160fa  /system/lib/libutils.so (thread_data_t::trampoline(thread_data_t const*)+122)

After some analysis i found that the crash occurs at function ACodec::allocateBuffersOnPort

I am a newbie on android. Any pointers to debug this would be helpful


Solution

  • To summarize, the issue is specific to a case when kFlagIsSecure is set and the creation of OMX buffer in a different process as compared to MediaCodec, which leads to a segmentation fault when accessed in MediaCodec. Please refer below for the detailed background about this issue.

    To overcome this issue, I would recommend the following changes in ACodec

    size_t totalSize = def.nBufferCountActual * def.nBufferSize;
    mDealer[portIndex] = new MemoryDealer(totalSize, "ACodec");
    
    /* Check if the component resides in same pid as ACodec */
    bool isLocalComponent = mOMX->livesLocally(mNode, getpid()); // New Code
    
    for (OMX_U32 i = 0; i < def.nBufferCountActual; ++i) {
       sp<IMemory> mem = mDealer[portIndex]->allocate(def.nBufferSize);
       ...
       ...
    

    and modify the check for allocation as below

    -- if ((portIndex == kPortIndexInput && (mFlags & kFlagIsSecure))
    --      || mUseMetadataOnEncoderOutput) {
    
    // Modified check
    ++if (isLocalComponent && ((portIndex == kPortIndexInput && (mFlags & kFlagIsSecure))
    ++      || mUseMetadataOnEncoderOutput)) {
    

    P.S: I would recommend you to check with Google about this solution.

    Background:

    ExoPlayer creates a video decoder as a MediaCodec component. When a new MediaCodec component is created, the corresponding object in JNI is created. Please note that there is no interaction with MediaPlayerService in this process.

    MediaCodec internally creates an ACodec which interacts with the OMX core and subsequently OMX component.

    ACodec is created in the same context as MediaCodec. When OMXClient::connect is invoked, the OMX handle is created in the MediaPlayer service's context. Hence, the process id of OMX component and ACodec would be different.

    For secure input buffers, there is a special handling in ACodec::allocateBuffersOnPorts. Here, the buffer pointer returned from allocateBuffer is wrapped as ABuffer and queued for consumption. In my view, there is a potential issue in the current implementation as below.

    ACodec::allocateBufferOnPort calls mOMX->allocateBuffer. mOMX is of type IOMX i.e. there is a binder interaction involved. Please do note this variable &buffer_data which will translate to ptr in ACodec::allocateBufferOnPorts layer as this is critical for the following part.

    In OMXNodeInstance which actually runs in MediaPlayerService's context, a traditional OMX_AllocateBuffer is called. In OMXNodeInstance::allocateBuffer, after the allocation *buffer_data is initialized with header->pBuffer which is basically a local pointer allocated by the OMX component potentially through a simple malloc call.

    When the control returns, the same pointer is written into the binder interface here and subsequently read back here. So, when the control comes out mOMX->allocateBuffer, the value of ptr is equivalent to header->pBuffer allocated by OMX component, but both of which are in 2 different processes.

    Hence, when ACodec creates the ABuffer based on this ptr which is then accessed in MediaCodec, there will be an access violation as the address was created in a different process' context as compared to MediaCodec's process id.