POSTED BY: Anestis Bechtsoudis / 23.03.2016

Android stagefright libmpeg2 impeg2d_dec_user_data heap overflow

CENSUS ID:CENSUS-2016-0008
CVE ID:CVE-2016-0824
Android ID:25765591
Affected Products:Android OS 6.0 — 6.0.1
Class:Out-of-bounds Read (CWE-125)
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 MPEG-2 software decoder invoked by libstagefright has an out-of-bounds read at the impeg2d_dec_user_data() procedure.

The vulnerability can be triggered remotely when a victim Android device starts decoding an MPEG-2 compressed video resource (MMS, chat apps, browser, etc.) using the vulnerable built-in software decoder. Successfully exploiting the vulnerability might result into information disclosure depending on the memory layout of the decoding process, which is for most cases the mediaserver daemon.

Details

This section provides further technical details about the vulnerability. The issue was discovered by means of fuzz testing (fuzzing). More information about our Android fuzzing work can be found here and here.

In the impeg2d_dec_user_data function of libmpeg2, the impeg2d_bit_stream_nxt() loop (lines 1357-1360) does not verify that u4_offset increased by impeg2d_bit_stream_flush() will be within the u4_max_offset limits. When u4_offset reaches the size of pv_bs_buf (allocated from SoftMPEG2::initDecoder), an out of bounds read of size 4 will occur.


+android-6.0.0_r2/external/libmpeg2/decoder/impeg2d_dec_hdr.c:
1345 *******************************************************************************/
1346 void impeg2d_dec_user_data(dec_state_t *ps_dec)
1347 {
1348     UWORD32 u4_start_code;
1349     stream_t *ps_stream;
1350
1351     ps_stream    = &ps_dec->s_bit_stream;
1352     u4_start_code = impeg2d_bit_stream_nxt(ps_stream,START_CODE_LEN);
1353
1354     while(u4_start_code == USER_DATA_START_CODE)
1355     {
1356         impeg2d_bit_stream_flush(ps_stream,START_CODE_LEN);
1357         while(impeg2d_bit_stream_nxt(ps_stream,START_CODE_PREFIX_LEN) != START_CODE_PREFIX)   /* u4_offset not checked being < u4_max_offset */
1358         {
1359             impeg2d_bit_stream_flush(ps_stream,8);  /* updates u4_offset (+8 / iter) & pu4_buf_aligned (+4 / align barrier hit) -- will cause OOB read */
1360         }
1361         u4_start_code = impeg2d_bit_stream_nxt(ps_stream,START_CODE_LEN);
1362     }
1363 }

+android-6.0.0_r2/external/libmpeg2/decoder/impeg2d_bitstream.c
187 *******************************************************************************/
188 INLINE void impeg2d_bit_stream_flush(void* pv_ctxt, UWORD32 u4_no_of_bits)
189 {
190     stream_t *ps_stream = (stream_t *)pv_ctxt;
191
192     FLUSH_BITS(ps_stream->u4_offset, ps_stream->u4_buf, ps_stream->u4_buf_nxt, u4_no_of_bits, ps_stream->pu4_buf_aligned)
193     return;
194 }

+android-6.0.0_r2/external/libmpeg2/decoder/impeg2d_bitstream.h
68 /* Define A macro for inlining of FlushBits */
69 #define     FLUSH_BITS(u4_offset, u4_buf, u4_buf_nxt, u4_no_bits, pu4_buf_aligned) \
70 {                                                                              \
71         UWORD32     u4_temp;                                                   \
72                                                                                \
73         if (((u4_offset & 0x1f) + u4_no_bits)>= 32)                            \
74         {                                                                      \
75             u4_buf              = u4_buf_nxt;                                  \
76                                                                                \
77             u4_temp             = *(pu4_buf_aligned)++;                        \
78                                                                                \
79             CONV_LE_TO_BE(u4_buf_nxt,u4_temp)                                  \
80         }                                                                      \
81         u4_offset               += u4_no_bits;                                 \
82}

OOB read might result into information disclosure by leaking data of nearby memory. Part of the decoder data that are read from the bitstream are stored in the output ivd_video_decode_op_t buffer allocated and passed from the SoftMPEG2 component. A subset of these output data are processed locally from SimpleSoftOMXComponent and some of them (e.g. u4_pic_wd / u4_pic_ht width / height) are delivered to the media player remote process. The exploitability and impact of this information leak will highly depend on the memory layout of the decoder process.

We have developed a PoC that can trigger the OOB read by emulating a condition where the START_CODE_PREFIX (0x000001) delimiter is absent in the input bitstream, after impeg2d_dec_pic_ext_data() has started processing the data of the first picture.

The following GDB session demonstrates the OOB read. It leads to a crash due to an access to an unmapped memory page near 0xb5940000.


Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 14859]
--------------------------------------------------------------------------[regs]
  R0:  0xB5B421D8  R1: 0x00000008  R2:  0x047FFFD8  R3:  0xB5940000
  R4:  0xB5B421D8  R5: 0x000001B2  R6:  0x00000001  R7:  0xB5B42000
  R8:  0x00000000  R9: 0x00000000  R10: 0x000002D0  R11: 0xB5C9D104
  R12: 0x00000000  SP: 0xB5C7F4C0  LR:  0xB5D15628  PC:  0xB5D138D0 
[0xB5C7F4C0]------------------------------------------------------[stack]
0xB5C7F510 : D0 02 00 00 1C D3 D0 B5 — F0 FF 31 B6 00 00 00 00 ..........1.....
0xB5C7F500 : F0 F5 C7 B5 34 00 00 00 — 68 01 00 00 00 00 00 00 ....4...h.......
0xB5C7F4F0 : 68 01 00 00 78 DF D0 B5 — 80 F5 C7 B5 00 20 B4 B5 h...x........ ..
0xB5C7F4E0 : 00 20 B4 B5 80 F5 C7 B5 — F0 F5 C7 B5 00 A0 B4 B5 . ..............
0xB5C7F4D0 : B3 01 00 00 B5 01 00 00 — B2 01 00 00 B0 5C D1 B5 .............\..
0xB5C7F4C0 : D8 21 B4 B5 60 58 D1 B5 — 00 20 B4 B5 D8 21 B4 B5 .!..`X... ...!..
--------------------------------------------------------------------------[code]
=> 0xb5d138d0 : ldr     r12, [r3]
   0xb5d138d4 :    add     r3, r3, #4
   0xb5d138d8 :    str     r3, [r0, #4]
   0xb5d138dc :    rev     r12, r12
   0xb5d138e0 :    str     r12, [r0, #16]
   0xb5d138e4 :    add     r1, r1, r2
   0xb5d138e8 :    str     r1, [r0, #8]
   0xb5d138ec :    bx      lr
--------------------------------------------------------------------------------
0xb5d138d0 in impeg2d_bit_stream_flush (pv_ctxt=0xb5b421d8, u4_no_of_bits=0x8) \
       at external/libmpeg2/decoder/impeg2d_bitstream.c:192
192         FLUSH_BITS(ps_stream->u4_offset,ps_stream->u4_buf,\
       ps_stream->u4_buf_nxt,u4_no_of_bits,ps_stream->pu4_buf_aligned)
gdb$ bt
#0  0xb5d138d0 in impeg2d_bit_stream_flush (pv_ctxt=0xb5b421d8, u4_no_of_bits=0x8) at external/libmpeg2/decoder/impeg2d_bitstream.c:192
#1  0xb5d15628 in impeg2d_dec_user_data (ps_dec=ps_dec@entry=0xb5b42000) at external/libmpeg2/decoder/impeg2d_dec_hdr.c:1359
#2  0xb5d15860 in impeg2d_dec_pic_ext_data (ps_dec=ps_dec@entry=0xb5b42000) at external/libmpeg2/decoder/impeg2d_dec_hdr.c:1443
#3  0xb5d15cb0 in impeg2d_process_video_bit_stream (ps_dec=ps_dec@entry=0xb5b42000) at external/libmpeg2/decoder/impeg2d_dec_hdr.c:1639
#4  0xb5d0df78 in impeg2d_dec_frm (pv_dec=pv_dec@entry=0xb5b42000, ps_ip=ps_ip@entry=0xb5c7f5f0, ps_op=ps_op@entry=0xb5c7f580) at external/libmpeg2/decoder/impeg2d_decoder.c:198
#5  0xb5d0d31c in impeg2d_api_entity (ps_dechdl=, pv_api_ip=0xb5c7f5f0, pv_api_op=0xb5c7f580) at external/libmpeg2/decoder/impeg2d_api_main.c:3200
#6  0xb5d0b580 in android::SoftMPEG2::onQueueFilled (this=0xb5c96000, portIndex=) at frameworks/av/media/libstagefright/codecs/mpeg2dec/SoftMPEG2.cpp:638
#7  0xb66d14e0 in android::SimpleSoftOMXComponent::onMessageReceived (this=0xb5c96000, msg=...) at frameworks/av/media/libstagefright/omx/SimpleSoftOMXComponent.cpp:375
#8  0xb66d23d6 in android::AHandlerReflector::onMessageReceived (this=, msg=...) at frameworks/av/include/media/stagefright/foundation/AHandlerReflector.h:35
#9  0xb6c9c2ca in android::AHandler::deliverMessage (this=0xb62c21c0, msg=...) at frameworks/av/media/libstagefright/foundation/AHandler.cpp:27
#10 0xb6c9e256 in android::AMessage::deliver (this=0xb5cf7200) at frameworks/av/media/libstagefright/foundation/AMessage.cpp:354
#11 0xb6c9ccba in android::ALooper::loop (this=0xb62c2188) at frameworks/av/media/libstagefright/foundation/ALooper.cpp:216
#12 0xb6cef06e in android::Thread::_threadLoop (user=0xb62c21f8) at system/core/libutils/Threads.cpp:758
#13 0xb6b103e8 in __pthread_start (arg=0xb5c7f930, arg@entry=) at bionic/libc/bionic/pthread_create.cpp:199
#14 0xb6aeab44 in __start_thread (fn=, arg=) at bionic/libc/bionic/clone.cpp:41
#15 0x00000000 in ?? ()
gdb$ p ps_stream->pv_bs_buf
$1 = (void *) 0xb5040000
gdb$ p ps_stream->pu4_buf_aligned
$2 = (UWORD32 *) 0xb5940000
gdb$ p ps_stream->u4_offset
$3 = 0x47fffd8
gdb$ p ps_stream->u4_max_offset
$4 = 0x1e0
gdb$ info proc mappings
process 14847
Mapped address spaces:

        Start Addr   End Addr       Size     Offset objfile
        0xb4b4b000 0xb4f40000   0x3f5000        0x0 /dev/ashmem/OMXCodec (deleted)
        0xb4f40000 0xb5740000   0x800000        0x0 /dev/ashmem/OMXCodec (deleted)
        0xb5740000 0xb5940000   0x200000        0x0 [anon:libc_malloc]
        0xb5941000 0xb5942000     0x1000        0x0
        0xb5942000 0xb5a40000    0xfe000        0x0 [stack:14860]
...

The following ASan report against a master branch build is also demonstrating the heap OOB read.


==22312==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xb0000000 at pc 0xb6d020b8 bp 0xb07fecd8 sp 0xb07fecd0
READ of size 4 at 0xb0000000 thread T3 (e.mpeg2.decoder)
#0 0xb6d020b7 in impeg2d_bit_stream_flush external/libmpeg2/decoder/impeg2d_bitstream.c:192 (discriminator 1)
#1 0xb6d0823b in impeg2d_dec_user_data external/libmpeg2/decoder/impeg2d_dec_hdr.c:1402
#2 0xb6d08447 in impeg2d_dec_pic_ext_data external/libmpeg2/decoder/impeg2d_dec_hdr.c:1486
#3 0xb6d09307 in impeg2d_process_video_bit_stream external/libmpeg2/decoder/impeg2d_dec_hdr.c:1682
#4 0xb6cf130f in impeg2d_dec_frm external/libmpeg2/decoder/impeg2d_decoder.c:198
#5 0xb6ceebf3 in impeg2d_api_entity external/libmpeg2/decoder/impeg2d_api_main.c:3295
#6 0xb6cddab7 in android::SoftMPEG2::onQueueFilled(unsigned int) frameworks/av/media/libstagefright/codecs/mpeg2dec/SoftMPEG2.cpp:639
#7 0xb5aa3b6b in android::SimpleSoftOMXComponent::onMessageReceived(android::sp const&) frameworks/av/media/libstagefright/omx/SimpleSoftOMXComponent.cpp:375
#8 0xb5aa8617 in android::AHandlerReflector::onMessageReceived(android::sp const&) frameworks/av/include/media/stagefright/foundation/AHandlerReflector.h:35
#9 0xb4d7694f in android::AHandler::deliverMessage(android::sp const&) frameworks/av/media/libstagefright/foundation/AHandler.cpp:27
#10 0xb4d82ff3 in android::AMessage::deliver() frameworks/av/media/libstagefright/foundation/AMessage.cpp:354
#11 0xb4d7a4b7 in android::ALooper::loop() frameworks/av/media/libstagefright/foundation/ALooper.cpp:216
#12 0xb540ead9 in android::Thread::_threadLoop(void*) system/core/libutils/Threads.cpp:752
#13 0xb5b25853 in __pthread_start(void*) bionic/libc/bionic/pthread_create.cpp:200
#14 0xb5af825f in __start_thread bionic/libc/bionic/clone.cpp:41

0xb0000000 is located 2048 bytes to the left of 115200-byte region [0xb0000800,0xb001ca00)
freed by thread T0 here:
#15 0xb552e973 in __interceptor_free [asan_rtl] (discriminator 2)
#16 0xb6cdbc2f in android::SoftMPEG2::deInitDecoder() frameworks/av/media/libstagefright/codecs/mpeg2dec/SoftMPEG2.cpp:417
#17 0xb6cdbc2f in android::SoftMPEG2::reInitDecoder() frameworks/av/media/libstagefright/codecs/mpeg2dec/SoftMPEG2.cpp:439
#18 0xb6cdc1bb in android::SoftMPEG2::internalSetParameter(OMX_INDEXTYPE, void*) frameworks/av/media/libstagefright/codecs/mpeg2dec/SoftMPEG2.cpp:464
#19 0xb5aa0ef3 in android::SimpleSoftOMXComponent::setParameter(OMX_INDEXTYPE, void*) frameworks/av/media/libstagefright/omx/SimpleSoftOMXComponent.cpp:117
#20 0xb5a873ff in android::OMXNodeInstance::setParameter(OMX_INDEXTYPE, void const*, unsigned int) frameworks/av/media/libstagefright/omx/OMXNodeInstance.cpp:417
#21 0xb5a7a52b in android::OMX::setParameter(unsigned int, OMX_INDEXTYPE, void const*, unsigned int) frameworks/av/media/libstagefright/omx/OMX.cpp:311
#22 0xb5213e07 in android::MuxOMX::setParameter(unsigned int, OMX_INDEXTYPE, void const*, unsigned int) frameworks/av/media/libstagefright/OMXClient.cpp:274
#23 0xb52244af in android::OMXCodec::setVideoOutputFormat(char const*, android::sp const&) frameworks/av/media/libstagefright/OMXCodec.cpp:1417
#24 0xb521bc5b in android::OMXCodec::configureCodec(android::sp const&) frameworks/av/media/libstagefright/OMXCodec.cpp:662
#25 0xb521950b 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:378
#26 0xb6f830bb in playSource(android::OMXClient*, android::sp&) frameworks/av/cmds/stagefright/stagefright.cpp:184
#27 0xb6f830bb in main frameworks/av/cmds/stagefright/stagefright.cpp:1116
#28 0xb5af5661 in __libc_init bionic/libc/bionic/libc_init_dynamic.cpp:114

previously allocated by thread T0 here:
#29 0xb552f133 in __interceptor_memalign [asan_rtl] (discriminator 2)
#30 0xb6cd905b in android::SoftMPEG2::initDecoder() frameworks/av/media/libstagefright/codecs/mpeg2dec/SoftMPEG2.cpp:337
#31 0xb6cd87af in SoftMPEG2 frameworks/av/media/libstagefright/codecs/mpeg2dec/SoftMPEG2.cpp:84 #32 0xb6cdfb33 in createSoftOMXComponent(char const*, OMX_CALLBACKTYPE const*, void*, OMX_COMPONENTTYPE**) frameworks/av/media/libstagefright/codecs/mpeg2dec/SoftMPEG2.cpp:770
#33 0xb5aa9bc3 in android::SoftOMXPlugin::makeComponentInstance(char const*, OMX_CALLBACKTYPE const*, void*, OMX_COMPONENTTYPE**) frameworks/av/media/libstagefright/omx/SoftOMXPlugin.cpp:112
#34 0xb5a81eb3 in android::OMXMaster::makeComponentInstance(char const*, OMX_CALLBACKTYPE const*, void*, OMX_COMPONENTTYPE**) frameworks/av/media/libstagefright/omx/OMXMaster.cpp:138
#35 0xb5a77e0b in android::OMX::allocateNode(char const*, android::sp const&, unsigned int*) frameworks/av/media/libstagefright/omx/OMX.cpp:243
#36 0xb521314f in android::MuxOMX::allocateNode(char const*, android::sp const&, unsigned int*) frameworks/av/media/libstagefright/OMXClient.cpp:233
#37 0xb52192b7 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
#38 0xb6f830bb in playSource(android::OMXClient*, android::sp&) frameworks/av/cmds/stagefright/stagefright.cpp:184
#39 0xb6f830bb in main frameworks/av/cmds/stagefright/stagefright.cpp:1116
#40 0xb5af5661 in __libc_init bionic/libc/bionic/libc_init_dynamic.cpp:114

Thread T3 (e.mpeg2.decoder) created by T0 here:
#41 0xb5512dbf in __interceptor_pthread_create [asan_rtl]
#42 0xb540e5ff in androidCreateRawThreadEtc system/core/libutils/Threads.cpp:160
#43 0xb5aa04c3 in SimpleSoftOMXComponent frameworks/av/media/libstagefright/omx/SimpleSoftOMXComponent.cpp:42
#44 0xb5aaa47b in SoftVideoDecoderOMXComponent frameworks/av/media/libstagefright/omx/SoftVideoDecoderOMXComponent.cpp:54
#45 0xb6cd861b in SoftMPEG2 frameworks/av/media/libstagefright/codecs/mpeg2dec/SoftMPEG2.cpp:66
#46 0xb6cdfb33 in createSoftOMXComponent(char const*, OMX_CALLBACKTYPE const*, void*, OMX_COMPONENTTYPE**) frameworks/av/media/libstagefright/codecs/mpeg2dec/SoftMPEG2.cpp:770
#47 0xb5aa9bc3 in android::SoftOMXPlugin::makeComponentInstance(char const*, OMX_CALLBACKTYPE const*, void*, OMX_COMPONENTTYPE**) frameworks/av/media/libstagefright/omx/SoftOMXPlugin.cpp:112
#48 0xb5a81eb3 in android::OMXMaster::makeComponentInstance(char const*, OMX_CALLBACKTYPE const*, void*, OMX_COMPONENTTYPE**) frameworks/av/media/libstagefright/omx/OMXMaster.cpp:138
#49 0xb5a77e0b in android::OMX::allocateNode(char const*, android::sp const&, unsigned int*) frameworks/av/media/libstagefright/omx/OMX.cpp:243
#50 0xb521314f in android::MuxOMX::allocateNode(char const*, android::sp const&, unsigned int*) frameworks/av/media/libstagefright/OMXClient.cpp:233
#51 0xb52192b7 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
#52 0xb6f830bb in playSource(android::OMXClient*, android::sp&) frameworks/av/cmds/stagefright/stagefright.cpp:184
#53 0xb6f830bb in main frameworks/av/cmds/stagefright/stagefright.cpp:1116
#54 0xb5af5661 in __libc_init bionic/libc/bionic/libc_init_dynamic.cpp:114

SUMMARY: AddressSanitizer: heap-buffer-overflow (/data/lib/libstagefright_soft_mpeg2dec.so+0x300b7)
Shadow bytes around the buggy address:
  0x15ffffb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x15ffffc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x15ffffd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x15ffffe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x15fffff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x16000000:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x16000010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x16000020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x16000030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x16000040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x16000050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
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
==22312==ABORTING

Various Android OS devices (e.g. Nexus 6) have been identified not to implement a hardware MPEG-2 decoder and are thus directly exposed to this issue, when a vulnerable version of the software decoder is in use.

During testing, the vulnerability was triggered in the mediaserver daemon through the Nexus default video player.

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.60/MASTER/anestisb10301216:userdebug/test-keys (frameworks/av commit 9cc6bb6)

Discussion

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

Disclosure Timeline

Vendor Contact:November 18th, 2015
Vendor Assigned Internal ID:November 18th, 2015
CENSUS Provided Additional Impact Details:November 24th, 2015
Vendor Triaged Vulnerability (High):December 8th, 2015
Vendor Patch Release:March 7th, 2016
Public Advisory:March 23rd, 2016