POSTED BY: Anestis Bechtsoudis / 04.05.2016

Android stagefright ih264d_read_mmco_commands libavc heap overflow

CENSUS ID:CENSUS-2016-0004
CVE ID:CVE-2016-0842
Android ID:25818142
Affected Products:Android OS 6.0 — 6.0.1
Class:Out-of-bounds Write (CWE-787)
Discovered by:Anestis Bechtsoudis

Android provides a media playback engine at the native level called Stagefright that comes built-in with software-based codecs for several popular media formats. Stagefright features for audio and video playback include integration with OpenMAX codecs, session management, time-synchronized rendering, transport control, and DRM.

CENSUS engineers have discovered that the libavc H.264 software decoder invoked by libstagefright has an out-of-bounds write vulnerability at the ih264d_read_mmco_commands() function.

The vulnerability can be triggered remotely when a victim Android device starts decoding an H.264 compressed video resource (MMS, chat apps, browser, etc.) using the vulnerable built-in software decoder. Successful exploitation of the vulnerability can result into executing unauthorized code with the increased permissions of the mediaserver daemon.

Details

In the H.264 MMCO (Memory Management Control Operations) commands parsing, a while loop is executed at lines 873-927 until the END_OF_MMCO delimiter is detected at the input stream.

+android-6.0.0_r2/external/libavc/decoder/ih264d_dpb_mgr.c:
828 WORD32 ih264d_read_mmco_commands(struct _DecStruct * ps_dec)
829 {
830    dec_bit_stream_t *ps_bitstrm = ps_dec->ps_bitstrm;
831    dpb_commands_t *ps_dpb_cmds = ps_dec->ps_dpb_cmds;
832    dec_slice_params_t * ps_slice = ps_dec->ps_cur_slice;
833    WORD32 j;
...
871                u4_mmco = ih264d_uev(pu4_bitstrm_ofst,
872                                     pu4_bitstrm_buf);
873                while(u4_mmco != END_OF_MMCO)  /* Execute loop until
 magic value detected from input stream */
874                {
875                    ps_mmc_params = &ps_dpb_cmds->as_mmc_params[j];  /* Get heap ptr
 using j as index — sizeof(as_mmc_params) = 32 (MAX_REF_BUFS ) */
876                    ps_mmc_params->u4_mmco = u4_mmco;  /* OOB write of size 4
 due to lack of ps_mmc_params boundary checks */
877                    switch(u4_mmco)
878                    {
...
922                    }
923                    u4_mmco = ih264d_uev(pu4_bitstrm_ofst,
924                                         pu4_bitstrm_buf);
925
926                    j++;  /* Increase index counter without boundary checks */
927                }
928                ps_dpb_cmds->u1_num_of_commands = j;
929
930            }
931        }
932        ps_dpb_cmds->u1_dpb_commands_read = 1;
933        ps_dpb_cmds->u1_dpb_commands_read_slc = 1;
934
935    }
936    u4_bit_ofst = ps_dec->ps_bitstrm->u4_ofst — u4_bit_ofst;
937    return u4_bit_ofst;
938 }

On each loop an internal counter (j) is increased, at line 926. The counter is used to index heap memory at line 875 and then as a write address at the line that follows. The j index counter is not checked against sizeof(as_mmc_params), resulting into a heap overflow when the offset points past the end of the ps_dpb_cmds allocated memory.

We have prepared a proof-of-concept file (PoC in base64 format) that can trigger a 4-byte out-of-bounds-write in the heap due to the absence of the END_OF_MMCO terminator.

The following GDB session demonstrates the PoC setting a big value for the j counter which then results into indexing heap memory outside the ps_dec map (allocated by ih264d_allocate_static_bufs()).

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 18532]
--------------------------------------------------------------------------[regs]
  R0:  0xFFFFFFFF  R1: 0x00000000  R2:  0xFFFFFFFE  R3:  0x001F5C2E
  R4:  0xB62BF2A4  R5: 0x000080F6  R6:  0xB5A80000  R7:  0xB5C62000
  R8:  0xB62BF098  R9: 0xB62BF2B4  R10: 0xB633FFF8  R11: 0x00000026
  R12: 0xFFFFFFFF  SP: 0xB5BE1388  LR:  0x00000000  PC:  0xB5C10242  N z C v q j e a i f T
[0xB5BE1388]------------------------------------------------------[stack]
0xB5BE13D8 : 00 00 00 00 00 00 00 00 — 00 00 00 00 61 C0 C0 B5 ............a...
0xB5BE13C8 : 14 E4 93 B5 00 00 00 00 — 02 00 00 00 00 00 00 00 ................
0xB5BE13B8 : 00 00 00 00 01 00 00 00 — FF FF FF FF 00 20 C6 B5 ............. ..
0xB5BE13A8 : 00 60 91 B5 00 00 00 00 — A8 EE C3 B5 85 D4 BF B5 .`..............
0xB5BE1398 : B4 F2 2B B6 A4 F2 2B B6 — 00 00 00 00 00 00 A8 B5 ..+...+.........
0xB5BE1388 : 80 C2 C8 B5 01 00 00 00 — 38 89 99 AA 00 20 C6 B5 ........8.... ..
--------------------------------------------------------------------------[code]
   0xb5c10243 :	str.w	r0, [r10, #8]
   0xb5c10247 :	cmp	r2, #5
   0xb5c10249 :	bhi.n	0xb5c10260 
   0xb5c1024b :	tbb	[pc, r2]
   0xb5c1024f :	lsls	r6, r2, #12
   0xb5c10251 :	movs	r2, #41	; 0x29
   0xb5c10253 :	lsls	r5, r3, #12
   0xb5c10255 :	mov	r0, r4
--------------------------------------------------------------------------------
ih264d_read_mmco_commands (ps_dec=ps_dec@entry=0xb5c62000) at external/libavc/decoder/ih264d_dpb_mgr.c:876
876	                    ps_mmc_params->u4_mmco = u4_mmco;
gdb$ p j
$1 = 0x80f6
gdb$ info proc mappings
process 18523
Mapped address spaces:

	Start Addr   End Addr       Size     Offset objfile
        ...
	0xb6280000 0xb6340000    0xc0000        0x0 [anon:libc_malloc]   <-- Region allocated by ih264d_allocate_static_bufs() -- r10 + off points past the end
	0xb6340000 0xb6341000     0x1000        0x0 [anon:linker_alloc]
        ...

The accompanying ASan report against the master branch is also demonstrating the heap OOB write.

==31645==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xb1607618 at pc 0xb37af034 bp 0xb35fe8f8 sp 0xb35fe8f4
WRITE of size 4 at 0xb1607618 thread T3 (le.h264.decoder)
    #0 0xb37af033 in ih264d_read_mmco_commands external/libavc/decoder/ih264d_dpb_mgr.c:876
    #1 0xb375502b in ih264d_parse_pslice external/libavc/decoder/ih264d_parse_pslice.c:2078
    #2 0xb379da87 in ih264d_parse_decode_slice external/libavc/decoder/ih264d_parse_slice.c:1886
    #3 0xb376fab3 in ih264d_parse_nal_unit external/libavc/decoder/ih264d_parse_headers.c:1068
    #4 0xb371ffeb in ih264d_video_decode external/libavc/decoder/ih264d_api.c:2011
    #5 0xb3728c5f in ih264d_api_function external/libavc/decoder/ih264d_api.c:3518
    #6 0xb371716b in android::SoftAVC::onQueueFilled(unsigned int) frameworks/av/media/libstagefright/codecs/avcdec/SoftAVCDec.cpp:586
    #7 0xb4f2ea37 in android::SimpleSoftOMXComponent::onMessageReceived(android::sp const&) frameworks/av/media/libstagefright/omx/SimpleSoftOMXComponent.cpp:375
    #8 0xb4f334e3 in android::AHandlerReflector::onMessageReceived(android::sp const&) frameworks/av/include/media/stagefright/foundation/AHandlerReflector.h:35
    #9 0xb62d5a53 in android::AHandler::deliverMessage(android::sp const&) frameworks/av/media/libstagefright/foundation/AHandler.cpp:27
    #10 0xb62e20ef in android::AMessage::deliver() frameworks/av/media/libstagefright/foundation/AMessage.cpp:354
    #11 0xb62d95d3 in android::ALooper::loop() frameworks/av/media/libstagefright/foundation/ALooper.cpp:216
    #12 0xb5714ed5 in android::Thread::_threadLoop(void*) system/core/libutils/Threads.cpp:752
    #13 0xb5674adf in __pthread_start(void*) bionic/libc/bionic/pthread_create.cpp:200
    #14 0xb56474bb in __start_thread bionic/libc/bionic/clone.cpp:41

0xb1607618 is located 12 bytes to the right of 524-byte region [0xb1607400,0xb160760c)
allocated by thread T3 (le.h264.decoder) here:
    #15 0xb63923d3 in malloc_stats ??:?
    #16 0xb371dd87 in ih264d_allocate_static_bufs external/libavc/decoder/ih264d_api.c:1258
    #17 0xb3728ba7 in ih264d_create external/libavc/decoder/ih264d_api.c:1432
    #18 0xb3728ba7 in ih264d_api_function external/libavc/decoder/ih264d_api.c:3509
    #19 0xb3714a1b in android::SoftAVC::initDecoder() frameworks/av/media/libstagefright/codecs/avcdec/SoftAVCDec.cpp:318
    #20 0xb3716583 in android::SoftAVC::onQueueFilled(unsigned int) frameworks/av/media/libstagefright/codecs/avcdec/SoftAVCDec.cpp:479
    #21 0xb4f2ea37 in android::SimpleSoftOMXComponent::onMessageReceived(android::sp const&) frameworks/av/media/libstagefright/omx/SimpleSoftOMXComponent.cpp:375
    #22 0xb4f334e3 in android::AHandlerReflector::onMessageReceived(android::sp const&) frameworks/av/include/media/stagefright/foundation/AHandlerReflector.h:35
    #23 0xb62d5a53 in android::AHandler::deliverMessage(android::sp const&) frameworks/av/media/libstagefright/foundation/AHandler.cpp:27
    #24 0xb62e20ef in android::AMessage::deliver() frameworks/av/media/libstagefright/foundation/AMessage.cpp:354
    #25 0xb62d95d3 in android::ALooper::loop() frameworks/av/media/libstagefright/foundation/ALooper.cpp:216
    #10 0xb35ff96b  ()
    #11 0xb35ff91f  ()

Thread T3 (le.h264.decoder) created by T0 here:
    #26 0xb6376c4f in __asan_memmove ??:?
    #27 0xb57149fb in androidCreateRawThreadEtc system/core/libutils/Threads.cpp:160
    #28 0xb4f2b38f in SimpleSoftOMXComponent frameworks/av/media/libstagefright/omx/SimpleSoftOMXComponent.cpp:42
    #29 0xb4f352ff in SoftVideoDecoderOMXComponent frameworks/av/media/libstagefright/omx/SoftVideoDecoderOMXComponent.cpp:54
    #30 0xb3718b8b in SoftAVC frameworks/av/media/libstagefright/codecs/avcdec/SoftAVCDec.cpp:112
    #31 0xb3718b8b in createSoftOMXComponent(char const*, OMX_CALLBACKTYPE const*, void*, OMX_COMPONENTTYPE**) frameworks/av/media/libstagefright/codecs/avcdec/SoftAVCDec.cpp:701
    #32 0xb4f34a3b in android::SoftOMXPlugin::makeComponentInstance(char const*, OMX_CALLBACKTYPE const*, void*, OMX_COMPONENTTYPE**) frameworks/av/media/libstagefright/omx/SoftOMXPlugin.cpp:112
    #33 0xb4f0cd37 in android::OMXMaster::makeComponentInstance(char const*, OMX_CALLBACKTYPE const*, void*, OMX_COMPONENTTYPE**) frameworks/av/media/libstagefright/omx/OMXMaster.cpp:138
    #34 0xb4f02c83 in android::OMX::allocateNode(char const*, android::sp const&, unsigned int*) frameworks/av/media/libstagefright/omx/OMX.cpp:243
    #35 0xb5132cfb in android::MuxOMX::allocateNode(char const*, android::sp const&, unsigned int*) frameworks/av/media/libstagefright/OMXClient.cpp:233
    #36 0xb5138e6f in android::OMXCodec::Create(android::sp const&, android::sp const&, bool, android::sp const&, char const*, unsigned int, android::sp const&) frameworks/av/media/libstagefright/OMXCodec.cpp:367
    #37 0xb6fee0c3 in playSource(android::OMXClient*, android::sp&) frameworks/av/cmds/stagefright/stagefright.cpp:184
    #38 0xb6fee0c3 in main frameworks/av/cmds/stagefright/stagefright.cpp:1116
    #39 0xb56448b5 in __libc_init bionic/libc/bionic/libc_init_dynamic.cpp:114
    #12 0x1  ()

SUMMARY: AddressSanitizer: heap-buffer-overflow (/data/lib/libstagefright_soft_avcdec.so+0xa7033)
Shadow bytes around the buggy address:
  0x162c0e70: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x162c0e80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x162c0e90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x162c0ea0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x162c0eb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x162c0ec0: 00 04 fa[fa]fa fa fa fa fa fa fa fa fa fa fa fa
  0x162c0ed0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x162c0ee0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x162c0ef0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x162c0f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x162c0f10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==31645==ABORTING

Mediaserver will generally prefer the hardware H.264 decoder found on most Nexus devices over the vulnerable software decoder, if a failover is not triggered. Applications that perform manual initialization of their decoders may be vulnerable to the above security vulnerability depending on the choices they make.

Testing was carried out on the following devices and software setup:

  • google/shamu/shamu:6.0/MRA58N/2289998:user/release-keys
  • Android/aosp_hammerhead/hammerhead:6.0/MASTER/anestisb11211639:userdebug/test-keys ("frameworks/av "commit [bca41e3], "external/libavc" commit [94bf490])

The stagefright standalone command can be used to trigger the vulnerability using the software codec option, as shown below:

$ stagefright -s /sdcard/Movies/ih264d_read_mmco_commands_POC.mp4

Discussion

The vulnerablity has been fixed for supported Android OS versions as described in the April 2016 Nexus security bulletin. Users are advised to upgrade to the latest stable Android release.

Disclosure Timeline

Vendor Contact:November 23rd, 2015
Vendor Assigned Internal ID:November 23rd, 2015
Vendor Triaged Vulnablity (Critical):December 1st, 2015
Vendor Assigned CVE:December 16th, 2015
CENSUS Provided Updated PoC :January 15th, 2016
Vendor Patch Release:April 4th, 2016
Public Advisory:May 4th, 2016