POSTED BY: Anestis Bechtsoudis / 23.03.2016

Android stagefright libavc ih264d_decode heap overflow

CENSUS ID:CENSUS-2016-0003
CVE ID:CVE-2016-0816
Android ID:25928803
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 libavcodec H.264 software decoder invoked by libstagefright has an OOB write heap overflow at the ih264d_decode_recon_tfr_nmb_thread() procedure.

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. Successfully exploiting the vulnerability can result into executing unauthorized code with the increased permissions of 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 libavc component of Stagefright, the picture decoder thread is invoking function ih264d_decode_recon_tfr_nmb_thread() in a loop until the ps_dec->u4_cur_slice_decode_done flag is set. Functions ih264d_decode_slice_thread() and ih264d_decode_recon_tfr_nmb_thread() are increasing the number of processed macroblocks (MBs). The number of processed MBs is used to offset the ps_dec->pu1_recon_mb_map metadata heap buffer (allocated from ih264d_allocate_dynamic_bufs()). The issue exists in the inner “recon” loop at lines 343-391. Specifically, this inner loop is not verifying that the u4_mb_num offset is constrained within the boundaries of the pu1_recon_mb_map buffer, before setting the metadata flag at line 388.


+android-6.0.0_r2/external/libavc/decoder/ih264d_thread_parse_decode.c
200  WORD32 ih264d_decode_recon_tfr_nmb_thread(dec_struct_t * ps_dec,
201                                            UWORD8 u1_num_mbs,
202                                            UWORD8 u1_num_mbs_next,
203                                            UWORD8 u1_end_of_row)
204  {
...
342      /* N Mb IQ IT RECON  Loop */
343      for(j = 0; j < i; j++)
344      {
...
384          else
385          {
386              UWORD32 u4_mb_num = ps_cur_mb_info->u2_mbx
387                 + ps_dec->u2_frm_wd_in_mbs * ps_cur_mb_info->u2_mby;  \
             /* no boundary check for u4_mb_num offset */

388              UPDATE_MB_MAP_MBNUM_BYTE(ps_dec->pu1_recon_mb_map, u4_mb_num);  \
             /* OOB write happens here from expanded macro if 'u4_mb_num' points \
              * past the end 'pu1_recon_mb_map' buffer \
              */

389          }
390          ps_dec->cur_dec_mb_num++;
391       }

+android-6.0.0_r2/external/libavc/decoder/ih264d_mb_utils.h
136  #define UPDATE_MB_MAP_MBNUM_BYTE(mb_map, u4_mb_number)
137  {                                                                                                  
138          volatile UWORD8 *pu1_mb_flag;                                             
139                                                                                                      
140          pu1_mb_flag    = (UWORD8 *)mb_map + (u4_mb_number);     
141          /*                                                                                         
142           * In case of MbAff, update the mb_map only if the entire MB is 
143           * done. We can check that by checking if Y is odd, implying      
144           * that this is the second row in the MbAff MB                           
145           */                                                                                        
146          (*pu1_mb_flag) = 1;                                                             
147  }

+android-6.0.0_r2/external/libavc/decoder/ih264d_utils.c:
1831  WORD16 ih264d_allocate_dynamic_bufs(dec_struct_t * ps_dec)
1832  {
...
1844      UWORD8 uc_frmOrFld = (1 — ps_dec->ps_cur_sps->u1_frame_mbs_only_flag);
...
1849      UWORD32 u4_total_mbs = ps_sps->u2_total_num_of_mbs << uc_frmOrFld;
...
1867
1868      size = u4_total_mbs;

1869      pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); \
                      /* heap buffer is allocated here */

1870      RETURN_IF((NULL == pv_buf), IV_FAIL);
1871      ps_dec->pu1_recon_mb_map = pv_buf;

We have developed a PoC that can trigger a heap buffer overflow with an OOB write of size 1 due to the u4_cur_slice_decode_done flag never being set, allowing the MBs processing loop to increase the number of MBs past the end of the metadata buffer (in this case the size is 0x252 bytes). The following GDB session demonstrates the u4_mb_num offset pointing 1 byte past the end of the pu1_recon_mb_map buffer.


gdb$ b external/libavc/decoder/ih264d_thread_parse_decode.c:386  \
                # breakpoint at buffer offset calculation

No source file named external/libavc/decoder/ih264d_thread_parse_decode.c.
Breakpoint 1 (external/libavc/decoder/ih264d_thread_parse_decode.c:386) pending.
gdb$ c
Continuing.
[New Thread 29503]
[Switching to Thread 29503]
--------------------------------------------------------------------------[regs]
  R0:  0x00000001  R1: 0x00000003  R2:  0xB5532000  R3:  0x00000000
  R4:  0x00000000  R5: 0xB5CA2000  R6:  0xB5CA3614  R7:  0xB5534274
  R8:  0x00000003  R9: 0xB5CA3658  R10: 0xB5CA362C  R11: 0x00000000
  R12: 0x00000001  SP: 0xB43C4880  LR:  0xB5CA3640  PC:  0xB5C52FB2
[0xB43C4880]------------------------------------------------------[stack]
0xB43C48D0 : 40 36 CA B5 2D DB C5 B5 — 4C 36 CA B5 37 33 C5 B5 @6..-...L6..73..
0xB43C48C0 : 00 20 CA B5 00 00 00 00 — 1E 00 00 00 B0 89 00 00 . ..............
0xB43C48B0 : 01 00 00 00 00 00 00 00 — 30 49 3C B4 00 00 00 00 ........0I<.....
0xB43C48A0 : 94 00 00 00 1E 00 00 00 — 04 00 00 00 00 00 00 00 ................
0xB43C4890 : 3C 36 CA B5 04 00 00 00 — 00 00 00 00 03 00 00 00 <6..............
0xB43C4880 : 02 00 00 00 EC 42 53 B5 — 00 00 00 00 03 00 00 00 .....BS.........
--------------------------------------------------------------------------[code]
   0xb5c52fb3 : ldrh.w  lr, [r7, #30]
   0xb5c52fb7 : ldrh    r0, [r5, #48]   ; 0x30
   0xb5c52fb9 : ldrh    r7, [r7, #32]
   0xb5c52fbb : ldr.w   r1, [r10]
   0xb5c52fbf : mla     r2, r7, r0, lr
   0xb5c52fc3 : strb.w  r12, [r1, r2]
   0xb5c52fc7 : b.n     0xb5c52f3e 
   0xb5c52fc9 : cmp     r1, #255        ; 0xff
--------------------------------------------------------------------------------
Breakpoint 1, ih264d_decode_recon_tfr_nmb_thread (ps_dec=ps_dec@entry=0xb5ca2000,
u1_num_mbs=, u1_num_mbs_next=, \
u1_end_of_row=) at external/libavc/decoder/ih264d_thread_parse_decode.c:387
387       + ps_dec->u2_frm_wd_in_mbs * ps_cur_mb_info->u2_mby;
gdb$ p ps_dec->ps_cur_sps->u2_total_num_of_mbs << \
        (1 — ps_dec->ps_cur_sps->u1_frame_mbs_only_flag)
$1 = 0x2b2 # MAX size of 'pu1_recon_mb_map' buffer
gdb$ p u4_mb_num # value when breakpoint hits for first time
$2 = 0xaf
gdb$ cond 1 u4_mb_num==0x2b2 # stop again if offset points past MAX
gdb$ c
Continuing.
--------------------------------------------------------------------------[regs]
  R0:  0x00000001  R1: 0x00000003  R2:  0xB5532000  R3:  0x00000000
  R4:  0x00000002  R5: 0xB5CA2000  R6:  0xB5CA3614  R7:  0xB553BB28
  R8:  0x0000001E  R9: 0xB5CA3658  R10: 0xB5CA362C  R11: 0x00000000
  R12: 0x00000001  SP: 0xB43C4880  LR:  0x0000001D  PC:  0xB5C52FB2
[0xB43C4880]------------------------------------------------------[stack]
0xB43C48D0 : 40 36 CA B5 2D DB C5 B5 — 4C 36 CA B5 37 33 C5 B5 @6..-...L6..73..
0xB43C48C0 : 00 20 CA B5 00 00 00 00 — 1E 00 00 00 60 32 01 00 . ..........`2..
0xB43C48B0 : 01 00 00 00 00 00 00 00 — 30 49 3C B4 00 00 00 00 ........0I<.....
0xB43C48A0 : 95 02 00 00 1E 00 00 00 — 04 00 00 00 00 00 00 00 ................
0xB43C4890 : 3C 36 CA B5 04 00 00 00 — 00 00 00 00 1E 00 00 00 <6..............
0xB43C4880 : 02 00 00 00 28 BB 53 B5 — 00 00 00 00 1E 00 00 00 ....(.S.........
--------------------------------------------------------------------------[code]
   0xb5c52fb3 : ldrh.w  lr, [r7, #30]
   0xb5c52fb7 : ldrh    r0, [r5, #48]   ; 0x30
   0xb5c52fb9 : ldrh    r7, [r7, #32]
   0xb5c52fbb : ldr.w   r1, [r10]
   0xb5c52fbf : mla     r2, r7, r0, lr
   0xb5c52fc3 : strb.w  r12, [r1, r2]
   0xb5c52fc7 : b.n     0xb5c52f3e 
   0xb5c52fc9 : cmp     r1, #255        ; 0xff
--------------------------------------------------------------------------------

Breakpoint 1, ih264d_decode_recon_tfr_nmb_thread (ps_dec=ps_dec@entry=0xb5ca2000, \
u1_num_mbs=, u1_num_mbs_next=, \
u1_end_of_row=) at external/libavc/decoder/ih264d_thread_parse_decode.c:387
387    + ps_dec->u2_frm_wd_in_mbs * ps_cur_mb_info->u2_mby;
gdb$ p ps_dec->pu1_recon_mb_map + u4_mb_num
$3 = (volatile UWORD8 *) 0xb63335b2 "" \
# u4_mb_num points 1 byte past the end of ps_dec->pu1_recon_mb_map buffer

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


===14639==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xaef03b32 \
   at pc 0xb35b8bc0 bp 0xae3d9808 sp 0xae3d9804
WRITE of size 1 at 0xaef03b32 thread T5
    #0 0xb35b8bbf in ih264d_decode_recon_tfr_nmb_thread
                 external/libavc/decoder/ih264d_thread_parse_decode.c:388
    #1 0xb35b917f in ih264d_decode_slice_thread
                 external/libavc/decoder/ih264d_thread_parse_decode.c:585
    #2 0xb35b9603 in ih264d_decode_picture_thread
                external/libavc/decoder/ih264d_thread_parse_decode.c:602
    #3 0xb4ff1adf in __pthread_start(void*) bionic/libc/bionic/pthread_create.cpp:200
    #4 0xb4fc44bb in __start_thread bionic/libc/bionic/clone.cpp:41

0xaef03b32 is located 0 bytes to the right of 690-byte region [0xaef03880,0xaef03b32)
allocated by thread T3 (le.h264.decoder) here:
    #5 0xb5d1f3d3 in malloc_stats ??:?
    #6 0xb357fe3b in \
       ih264d_allocate_dynamic_bufs external/libavc/decoder/ih264d_utils.c:1869
    #7 0xb357ed13 in \
       ih264d_init_pic external/libavc/decoder/ih264d_utils.c:825
    #8 0xb3596c9f in \
       ih264d_start_of_pic external/libavc/decoder/ih264d_parse_slice.c:338
    #9 0xb359cad3 in ih264d_parse_decode_slice \
       external/libavc/decoder/ih264d_parse_slice.c:1578
    #10 0xb356fab3 in ih264d_parse_nal_unit \
       external/libavc/decoder/ih264d_parse_headers.c:1068
    #11 0xb351ffeb in ih264d_video_decode \
       external/libavc/decoder/ih264d_api.c:2011
    #12 0xb3528c5f in ih264d_api_function \
       external/libavc/decoder/ih264d_api.c:3518
    #13 0xb351716b in android::SoftAVC::onQueueFilled(unsigned int) \
       frameworks/av/media/libstagefright/codecs/avcdec/SoftAVCDec.cpp:586
    #14 0xb628ca37 in \ 
      android::SimpleSoftOMXComponent::onMessageReceived(android::sp \
      const&) frameworks/av/media/libstagefright/omx/SimpleSoftOMXComponent.cpp:375
    #15 0xb62914e3 in android::AHandlerReflector::onMessageReceived(android::sp \
   const&) frameworks/av/include/media/stagefright/foundation/AHandlerReflector.h:35
    #16 0xb68e3a53 in android::AHandler::deliverMessage(android::sp \
         const&) frameworks/av/media/libstagefright/foundation/AHandler.cpp:27
    #17 0xb68f00ef in android::AMessage::deliver() \
         frameworks/av/media/libstagefright/foundation/AMessage.cpp:354
    #18 0xb68e75d3 in \
         android::ALooper::loop() \
         frameworks/av/media/libstagefright/foundation/ALooper.cpp:216
    #14 0xb33ff96b  ()
    #15 0xb33ff91f  ()

Thread T5 created by T3 (le.h264.decoder) here:
    #19 0xb5d03c4f in __asan_memmove ??:?
    #20 0xb359cc1f in \
       ih264d_parse_decode_slice external/libavc/decoder/ih264d_parse_slice.c:1603
    #21 0xb356fab3 in \
       ih264d_parse_nal_unit external/libavc/decoder/ih264d_parse_headers.c:1068
    #22 0xb351ffeb in ih264d_video_decode external/libavc/decoder/ih264d_api.c:2011
    #23 0xb3528c5f in ih264d_api_function external/libavc/decoder/ih264d_api.c:3518
    #24 0xb351716b in \
      android::SoftAVC::onQueueFilled(unsigned int)\
      frameworks/av/media/libstagefright/codecs/avcdec/SoftAVCDec.cpp:586
    #25 0xb628ca37 in \
      android::SimpleSoftOMXComponent::onMessageReceived(android::sp \
      const&) frameworks/av/media/libstagefright/omx/SimpleSoftOMXComponent.cpp:375
    #26 0xb62914e3 in \
     android::AHandlerReflector::onMessageReceived(android::sp\
const&) frameworks/av/include/media/stagefright/foundation/AHandlerReflector.h:35
    #27 0xb68e3a53 in \
      android::AHandler::deliverMessage(android::sp const&) \
      frameworks/av/media/libstagefright/foundation/AHandler.cpp:27
    #28 0xb68f00ef in \
      android::AMessage::deliver() \
      frameworks/av/media/libstagefright/foundation/AMessage.cpp:354
    #29 0xb68e75d3 in \
      android::ALooper::loop() \
      frameworks/av/media/libstagefright/foundation/ALooper.cpp:216
    #11 0xb33ff96b  ()
    #12 0xb33ff91f  ()

Thread T3 (le.h264.decoder) created by T0 here:
    #30 0xb5d03c4f in __asan_memmove ??:?
    #31 0xb69289fb in androidCreateRawThreadEtc system/core/libutils/Threads.cpp:160
    #32 0xb628938f in SimpleSoftOMXComponent \
       frameworks/av/media/libstagefright/omx/SimpleSoftOMXComponent.cpp:42
    #33 0xb62932ff in SoftVideoDecoderOMXComponent \
      frameworks/av/media/libstagefright/omx/SoftVideoDecoderOMXComponent.cpp:54
    #34 0xb3518b8b in SoftAVC \
       frameworks/av/media/libstagefright/codecs/avcdec/SoftAVCDec.cpp:112
    #35 0xb3518b8b in createSoftOMXComponent(char const*, \
       OMX_CALLBACKTYPE const*, void*, OMX_COMPONENTTYPE**) \
       frameworks/av/media/libstagefright/codecs/avcdec/SoftAVCDec.cpp:701
    #36 0xb6292a3b in android::SoftOMXPlugin::makeComponentInstance(char const*, \
       OMX_CALLBACKTYPE const*, void*, OMX_COMPONENTTYPE**) \
       frameworks/av/media/libstagefright/omx/SoftOMXPlugin.cpp:112
    #37 0xb626ad37 in android::OMXMaster::makeComponentInstance(char const*, \
       OMX_CALLBACKTYPE const*, void*, OMX_COMPONENTTYPE**) \
       frameworks/av/media/libstagefright/omx/OMXMaster.cpp:138
    #38 0xb6260c83 in android::OMX::allocateNode(char const*, \
       android::sp const&, unsigned int*) \
       frameworks/av/media/libstagefright/omx/OMX.cpp:243
    #39 0xb653acfb in android::MuxOMX::allocateNode(char const*, \
       android::sp const&, unsigned int*) \
       frameworks/av/media/libstagefright/OMXClient.cpp:233
    #40 0xb6540e6f 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
    #41 0xb6f1f0c3 in
    #42 0xb4fc18b5 in __libc_init bionic/libc/bionic/libc_init_dynamic.cpp:114
    #12 0x1  ()

SUMMARY: AddressSanitizer: heap-buffer-overflow \
                   (/data/lib/libstagefright_soft_avcdec.so+0xb0bbf)
Shadow bytes around the buggy address:
  0x15de0710: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x15de0720: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x15de0730: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x15de0740: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x15de0750: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x15de0760: 00 00 00 00 00 00[02]fa fa fa fa fa fa fa fa fa
  0x15de0770: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x15de0780: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x15de0790: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x15de07a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x15de07b0: 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
==14639==ABORTING

Considering that most Nexus devices have a hardware H.264 decoder, mediaserver will prefer it over the vulnerable software version (if a failover is not triggered). Applications that initialize the decoders manually might be vulnerable to this issue depending on their choices.

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

$ /system/bin/stagefright -s \
        /sdcard/Movies/ih264d_decode_recon_tfr_nmb_thread_POC.mp4

Our 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])

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 release.

Disclosure Timeline

Vendor Contact:November 28th, 2015
Vendor Assigned Internal ID:November 30th, 2015
Vendor Triaged Vulnerability (Critical):December 11th, 2015
Vendor Patch Release:March 7th, 2016
Public Advisory:March 23rd, 2016