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 |