POSTED BY: Anestis Bechtsoudis / 25.07.2016

Android stagefright impeg2d_vld_decode stack buffer overflows

CENSUS ID:CENSUS-2016-0006
CVE ID:CVE-2016-0836
Android ID:25812590
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 MPEG-2 software decoder invoked by libstagefright has multiple stack buffer overflows at the impeg2d_vld_decode() procedure. The vulnerabilities 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 executing unauthorized code with the increased permissions of the mediaserver daemon.

Details

The impeg2d_vld_decode() procedure uses the pi4_num_coeffs variable to index the pi2_coeffs[] and pu1_pos[] stack allocated buffers (allocated from caller impeg2d_vld_inv_quant_mpeg1() at lines 307-308 of impeg2d_vld.c). The pi4_num_coeffs variable is increased at multiple places (e.g. lines 655, 828) within local (while) loops and indexes the two buffers without any boundary checks taking place. A malformed input stream that keeps satisfying the local loop conditions (e.g. don’t terminate intra blocks) will eventually overflow the two buffers resulting into an OOB write.

+android-6.0.0_r2/external/libmpeg2/decoder/impeg2d_vld.c:
294 IMPEG2D_ERROR_CODES_T impeg2d_vld_inv_quant_mpeg1(
295                              void  *pv_dec,           /* Decoder State */
296                              WORD16       *pi2_out_addr,       /*!< Address where decoded symbols will be stored */
297                              const UWORD8 *pu1_scan,          /*!< Scan table to be used */
298                              UWORD16      u2_intra_flag,      /*!< Intra Macroblock or not */
299                              UWORD16      u2_colr_comp,      /*!< 0 — Luma,1 — U comp, 2 — V comp */
300                              UWORD16      u2_d_picture        /*!< D Picture or not */
301                              )
302 {
303     UWORD8  *pu1_weighting_matrix;
304     dec_state_t *ps_dec    = (dec_state_t *) pv_dec;
305     IMPEG2D_ERROR_CODES_T e_error   = (IMPEG2D_ERROR_CODES_T)IVD_ERROR_NONE;
306 
307     WORD16  pi2_coeffs[NUM_COEFFS];   /* stack allocated array for decoded symbols output */
308     UWORD8  pu1_pos[NUM_COEFFS];      /* stack allocated scan table */
309     WORD32  i4_num_coeffs;
310 
311     /* Perform VLD on the stream to get the coefficients and their positions */
312     e_error = impeg2d_vld_decode(ps_dec, pi2_coeffs, pu1_scan, pu1_pos, u2_intra_flag,
313                                  u2_colr_comp, u2_d_picture, ps_dec->u2_intra_vlc_format,
314                                  ps_dec->u2_is_mpeg2, &i4_num_coeffs);

...
464 IMPEG2D_ERROR_CODES_T impeg2d_vld_decode(
465     dec_state_t *ps_dec,
466     WORD16      *pi2_outAddr,       /*!< Address where decoded symbols will be stored */
467     const UWORD8 *pu1_scan,         /*!< Scan table to be used */
468     UWORD8      *pu1_pos,       /*!< Scan table to be used */
469     UWORD16     u2_intra_flag,      /*!< Intra Macroblock or not */
470     UWORD16     u2_chroma_flag,     /*!< Chroma Block or not */
471     UWORD16     u2_d_picture,       /*!< D Picture or not */
472     UWORD16     u2_intra_vlc_format, /*!< Intra VLC format */
473     UWORD16     u2_mpeg2,          /*!< MPEG-2 or not */
474     WORD32      *pi4_num_coeffs /*!< Returns the number of coeffs in block */
475     )
476 {
477
....
494     *pi4_num_coeffs = 0;      /* buffers index */
...
627
628        if (1 == u2_intra_vlc_format && u2_intra_flag)
629        {
630
631            while(1)     /* loop breaks only at line 663 */
632            {
633                //Putting the impeg2d_dec_ac_coeff_one function inline.
634
635                UWORD32 lead_zeros;
636                WORD16 DecodedValue;
637
638                u4_sym_len = 17;
639                IBITS_NXT(u4_buf,u4_buf_nxt,u4_offset,u4_bits,u4_sym_len)
640
641                DecodedValue = gau2_impeg2d_tab_one_1_9[u4_bits >> 8];
642                u4_sym_len = (DecodedValue & 0xf);
643                u4_level = DecodedValue >> 9;
644                /* One table lookup */
645                if(0 != u4_level)     /* branch will be execute while parsing !u4_level from stream buffer */
646                {
647                    u4_run = ((DecodedValue >> 4) & 0x1f);
648                    u4_numCoeffs       += u4_run;
649                    u4_pos             = pu1_scan[u4_numCoeffs++ & 63];
650                    pu1_pos[*pi4_num_coeffs]    = u4_pos;                   /* overflow can happen here for input buffer */
651
652                    FLUSH_BITS(u4_offset,u4_buf,u4_buf_nxt,u4_sym_len,pu4_buf_aligned)
653                    pi2_outAddr[*pi4_num_coeffs]    = u4_level;             /* overflow can happen here for output buffer */
654
655                    (*pi4_num_coeffs)++;             /* no boundary checks */
656                }
657                else
658                {
659                    if (DecodedValue == END_OF_BLOCK_ONE)
660                    {
661                        u4_sym_len = 4;
662
663                        break;
664                    }
665                    else
666                    {
667

A PoC MPEG-2 VOB file that triggers the stack buffer overflow of the pu1_pos[] buffer at line 650 can be found here. The accompanying ASan report demonstrating the stack OOB write is shown below:

=================================================================
==29899==AddressSanitizer: while reporting a bug found another one. Ignoring.
==29899==ERROR: AddressSanitizer: stack-buffer-overflow on address 0xb06fe910 at pc 0xb6c81894 bp 0xb06fe780 sp 0xb06fe778
WRITE of size 1 at 0xb06fe910 thread T3 (e.mpeg2.decoder)
    #0 0xb6c81893 in impeg2d_vld_decode external/libmpeg2/decoder/impeg2d_vld.c:650
    #1 0xb6c81e33 in impeg2d_vld_inv_quant_mpeg2 external/libmpeg2/decoder/impeg2d_vld.c:386
    #2 0xb6c92ad7 in impeg2d_dec_i_slice external/libmpeg2/decoder/impeg2d_i_pic.c:211
    #3 0xb6c8c477 in impeg2d_dec_slice external/libmpeg2/decoder/impeg2d_dec_hdr.c:824
    #4 0xb6c8ca1b in impeg2d_dec_pic_data_thread external/libmpeg2/decoder/impeg2d_dec_hdr.c:914
    #5 0xb6c8fcc7 in impeg2d_dec_pic_data external/libmpeg2/decoder/impeg2d_dec_hdr.c:1333
    #6 0xb6c91217 in impeg2d_process_video_bit_stream external/libmpeg2/decoder/impeg2d_dec_hdr.c:1748
    #7 0xb6c7930f in impeg2d_dec_frm external/libmpeg2/decoder/impeg2d_decoder.c:198
    #8 0xb6c76bf3 in impeg2d_api_entity external/libmpeg2/decoder/impeg2d_api_main.c:3295
    #9 0xb6c65ab7 in android::SoftMPEG2::onQueueFilled(unsigned int) frameworks/av/media/libstagefright/codecs/mpeg2dec/SoftMPEG2.cpp:639
    #10 0xb6940b6b in android::SimpleSoftOMXComponent::onMessageReceived(android::sp const&) frameworks/av/media/libstagefright/omx/SimpleSoftOMXComponent.cpp:375
    #11 0xb6945617 in android::AHandlerReflector::onMessageReceived(android::sp const&) frameworks/av/include/media/stagefright/foundation/AHandlerReflector.h:35
    #12 0xb650894f in android::AHandler::deliverMessage(android::sp const&) frameworks/av/media/libstagefright/foundation/AHandler.cpp:27
    #13 0xb6514ff3 in android::AMessage::deliver() frameworks/av/media/libstagefright/foundation/AMessage.cpp:354
    #14 0xb650c4b7 in android::ALooper::loop() frameworks/av/media/libstagefright/foundation/ALooper.cpp:216
    #15 0xb4ca5ad9 in android::Thread::_threadLoop(void*) system/core/libutils/Threads.cpp:752
    #16 0xb5981853 in __pthread_start(void*) bionic/libc/bionic/pthread_create.cpp:200
    #17 0xb595425f in __start_thread bionic/libc/bionic/clone.cpp:41

Address 0xb06fe910 is located in stack of thread T3 (e.mpeg2.decoder) at offset 240 in frame
    #18 0xb6c81cd3 in impeg2d_vld_inv_quant_mpeg2 external/libmpeg2/decoder/impeg2d_vld.c:368

  This frame has 3 object(s):
    [16, 144) 'pi2_coeffs'
    [176, 240) 'pi4_pos' <== Memory access at offset 240 overflows this variable
    [272, 276) 'i4_num_coeffs'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
      (longjmp and C++ exceptions *are* supported)
Thread T3 (e.mpeg2.decoder) created by T0 here:
    #19 0xb5016dbf in __interceptor_pthread_create [asan_rtl]
    #20 0xb4ca55ff in androidCreateRawThreadEtc system/core/libutils/Threads.cpp:160
    #21 0xb693d4c3 in SimpleSoftOMXComponent frameworks/av/media/libstagefright/omx/SimpleSoftOMXComponent.cpp:42
    #22 0xb694747b in SoftVideoDecoderOMXComponent frameworks/av/media/libstagefright/omx/SoftVideoDecoderOMXComponent.cpp:54
    #23 0xb6c6061b in SoftMPEG2 frameworks/av/media/libstagefright/codecs/mpeg2dec/SoftMPEG2.cpp:66
    #24 0xb6c67b33 in createSoftOMXComponent(char const*, OMX_CALLBACKTYPE const*, void*, OMX_COMPONENTTYPE**) frameworks/av/media/libstagefright/codecs/mpeg2dec/SoftMPEG2.cpp:770
    #25 0xb6946bc3 in android::SoftOMXPlugin::makeComponentInstance(char const*, OMX_CALLBACKTYPE const*, void*, OMX_COMPONENTTYPE**) frameworks/av/media/libstagefright/omx/SoftOMXPlugin.cpp:112
    #26 0xb691eeb3 in android::OMXMaster::makeComponentInstance(char const*, OMX_CALLBACKTYPE const*, void*, OMX_COMPONENTTYPE**) frameworks/av/media/libstagefright/omx/OMXMaster.cpp:138
    #27 0xb6914e0b in android::OMX::allocateNode(char const*, android::sp const&, unsigned int*) frameworks/av/media/libstagefright/omx/OMX.cpp:243
    #28 0xb574f14f in android::MuxOMX::allocateNode(char const*, android::sp const&, unsigned int*) frameworks/av/media/libstagefright/OMXClient.cpp:233
    #29 0xb57552b7 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
    #30 0xb6f0b0bb in playSource(android::OMXClient*, android::sp&) frameworks/av/cmds/stagefright/stagefright.cpp:184
    #31 0xb6f0b0bb in main frameworks/av/cmds/stagefright/stagefright.cpp:1116
    #32 0xb5951661 in __libc_init bionic/libc/bionic/libc_init_dynamic.cpp:114
    #13 0x1  ()

SUMMARY: AddressSanitizer: stack-buffer-overflow (/data/lib/libstagefright_soft_mpeg2dec.so+0x27893)
Shadow bytes around the buggy address:
  0x160dfcd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x160dfce0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x160dfcf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x160dfd00: 00 00 00 00 f1 f1 00 00 00 00 00 00 00 00 00 00
  0x160dfd10: 00 00 00 00 00 00 f2 f2 f2 f2 00 00 00 00 00 00
=>0x160dfd20: 00 00[f2]f2 f2 f2 04 f3 00 00 00 00 00 00 00 00
  0x160dfd30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x160dfd40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x160dfd50: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 00 04
  0x160dfd60: f2 f2 00 04 f3 f3 00 00 00 00 00 00 00 00 00 00
  0x160dfd70: 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
==29899==ABORTING

Various Android OS devices (e.g. Nexus 6) have been identified not to implement a hardware MPEG-2 decoder and thus are directly exposed when running vulnerable versions of the software decoder.

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

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

  • 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 April 2016 Nexus Security bulletin. Users are advised to upgrade to the latest stable Android release.

Disclosure Timeline

Vendor Contact:November 19th, 2015
Vendor Assigned Internal ID:November 20th, 2015
Vendor Triaged Vulnablity (Critical):December 1st, 2015
Vendor Assigned CVE:December 16th, 2015
Vendor Patch Release:April 4th, 2016
Public Advisory:July 25th, 2016