CVE-2016-4631

SUMMARY

An exploitable heap based buffer overflow exists in the handling of TIFF images on Apple OS X and iOS operating systems. A crafted TIFF document can lead to a heap based buffer overflow resulting in remote code execution. This vulnerability can be triggered via malicious web page, MMS message, iMessage or a file attachment delivered by other means when opened in applications using the Apple Image I/O API.

TESTED VERSIONS

OSX El Capitan - 10.11.4 iOS - 9.3.1

PRODUCT URLs

https://developer.apple.com/osx/download/

CVSSv3 SCORE

8.1 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N

DETAILS

This vulnerability is present in the Apple Image IO API which is used for all image handling on OS X including rendering images in Preview and Safari.

There exists a vulnerability in the parsing and handling of Tiled TIFF images. A specially crafted TIFF image file can lead to an out of bounds write and ultimately to remote code execution.

TIFF (Tagged Image File Format) images consist of data describing the image in the header then tags throughout the image file describing how and where the data should be displayed. Running the attached test case through a Tiff analyzer shows us the following output:

``` TIFFFetchNormalTag: Warning, Nonstandard tile width 255, convert file. TIFFFetchNormalTag: Warning, IO error during reading of "DocumentName"; tag ignored. TIFFFetchNormalTag: Warning, Incompatible type for "ResolutionUnit"; tag ignored. TIFFReadDirectory: Warning, Incorrect count for "ColorMap"; tag ignored. TIFFReadDirectory: Warning, TIFF directory is missing required "StripByteCounts" field, calculating from imagelength. TIFF Directory at offset 0xa0 (160) Image Width: 2 Image Length: 1 Tile Width: 255 Tile Length: 1024 Resolution: 72, 72 Bits/Sample: 8 Compression Scheme: None Photometric Interpretation: separated FillOrder: msb-to-lsb Orientation: row 0 bottom, col 0 rhs Samples/Pixel: 4 Rows/Strip: 1024 Planar Configuration: separate image planes Page Number: 0-1 ```

Each piece of information shown here is derived from a tag with a specific identifier in the image file. Take note of the warning alerting the user that the tile width is invalid.

Running this through Preview the following output is shown:

``` Exception Type: EXC_CRASH (SIGABRT) Exception Codes: 0x0000000000000000, 0x0000000000000000 Exception Note: EXC_CORPSE_NOTIFY Application Specific Information: abort() called *** error for object 0x7f8a89f4e6b8: incorrect checksum for freed object - object was probably modified after being freed. 0 libsystem_kernel.dylib 0x00007fff9242df06 __pthread_kill + 10 1 libsystem_pthread.dylib 0x00007fff911794ec pthread_kill + 90 2 libsystem_c.dylib 0x00007fff95b436e7 abort + 129 3 libsystem_malloc.dylib 0x00007fff97f53396 szone_error + 626 4 libsystem_malloc.dylib 0x00007fff97f46c03 tiny_malloc_from_free_list + 1351 5 libsystem_malloc.dylib 0x00007fff97f45705 szone_malloc_should_clear + 292 6 libsystem_malloc.dylib 0x00007fff97f455a1 malloc_zone_malloc + 71 7 libsystem_malloc.dylib 0x00007fff97f440cc malloc + 42 8 libsystem_c.dylib 0x00007fff95b29e7b _vasprintf + 354 9 libsystem_c.dylib 0x00007fff95b211a8 asprintf + 186 10 com.apple.ImageIO.framework 0x00007fff8d394b05 ImageIOLogger + 97 11 com.apple.ImageIO.framework 0x00007fff8d33d43e myErrorHandler + 9 12 libTIFF.dylib 0x00007fff9d4667aa TIFFErrorExt + 179 13 libTIFF.dylib 0x00007fff9d480214 TIFFReadRawTile1 + 336 14 libTIFF.dylib 0x00007fff9d47fe78 TIFFFillTile + 384 15 libTIFF.dylib 0x00007fff9d47fc8a TIFFReadEncodedTile + 95 16 com.apple.ImageIO.framework 0x00007fff8d33d9b8 copyImageBlockSetTiledTIFF + 1372 17 com.apple.ImageIO.framework 0x00007fff8d2f40f4 ImageProviderCopyImageBlockSetCallback + 651 18 com.apple.CoreGraphics 0x00007fff93f15cb4 CGImageProviderCopyImageBlockSetWithOptions + 132 19 com.apple.CoreGraphics 0x00007fff93f1739c CGImageProviderCopyImageBlockSet + 205 20 com.apple.CoreGraphics 0x00007fff93f4e7fd img_blocks_create + 517 ```

It can be seen that this is corrupting a free heap block header so this is most likely a heap overflow. Now turning on guard malloc and reproducing the crash gives us the following output:

``` * thread #1: tid = 0x918fde, 0x00007fff8bd3f01c libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell + 252, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x12d9cc000) frame #0: 0x00007fff8bd3f01c libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell + 252 libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell: -> 0x7fff8bd3f01c <+252>: vmovups %ymm0, (%rax) 0x7fff8bd3f020 <+256>: vmovups 0x20(%rsi), %ymm2 0x7fff8bd3f025 <+261>: addq $0x40, %rsi 0x7fff8bd3f029 <+265>: subq $0x80, %rdx (lldb) register read General Purpose Registers: rax = 0x000000012d9cbfff rbx = 0x000000012c62bb30 rcx = 0x0000000000000001 rdx = 0x00000000000000fe rdi = 0x000000012c64c000 rsi = 0x000000012c659c01 rbp = 0x00007fff5fbf9d90 rsp = 0x00007fff5fbf9d90 r8 = 0x00000000000000ff r9 = 0x00000000000000ff r10 = 0x00000000ffffff01 r11 = 0xffffffffffff23ff r12 = 0x000000012c64bfff r13 = 0x0000000000000001 r14 = 0x00000000000000ff r15 = 0x00000000000000ff rip = 0x00007fff8bd3f01c rflags = 0x0000000000010202 (lldb) bt frame #0: 0x00007fff8bd3f01c libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell + 252 frame #1: 0x00007fff9d459283 libTIFF.dylib`_TIFFmemcpy + 9 frame #2: 0x00007fff9d461870 libTIFF.dylib`DumpModeDecode + 92 frame #3: 0x00007fff9d47fcaf libTIFF.dylib`TIFFReadEncodedTile + 132 frame #4: 0x00007fff8d33d9b8 ImageIO`copyImageBlockSetTiledTIFF + 1372 frame #5: 0x00007fff8d2f40f4 ImageIO`ImageProviderCopyImageBlockSetCallback + 651 frame #6: 0x00007fff93f15cb4 CoreGraphics`CGImageProviderCopyImageBlockSetWithOptions + 132 frame #7: 0x00007fff93f1739c CoreGraphics`CGImageProviderCopyImageBlockSet + 205 (lldb) malloc_info $rax 0x000000012d9cbfff: malloc( 256) -> 0x12d9cbf00 + 255 (lldb) malloc_info -S $rax ColorSync Utility(54051,0x7fff79aef000) malloc: process 54687 no longer exists, stack logs deleted from /tmp/stack-logs.54687.100084000.ColorSync Utility.2vS0Sg.index 0x000000012d9cbfff: malloc( 256) -> 0x12d9cbf00 + 255 stack[0]: addr = 0x12d9cbf00, type=malloc, frames: [0] 0x00007fff97f489ce libsystem_malloc.dylib`malloc_zone_calloc + 118 [1] 0x00007fff97f49462 libsystem_malloc.dylib`calloc + 49 [2] 0x00007fff8d33d815 ImageIO`copyImageBlockSetTiledTIFF + 953 [3] 0x00007fff8d2f40f4 ImageIO`ImageProviderCopyImageBlockSetCallback + 651 ```

Here we see a crash on an attempt to write to memory at the address held in RAX. Investigating further we see RAX is pointing to the very end of a heap block yet there is still a lot of data to be written as RDX (counter) is still 0xFE. It is also noted that the malloced buffer is the same size as the tile width which becomes of interest when looking further into what is causing this. Decompiling the code near where the malloc block is allocated in ImageIO`copyImageBlockSetTiledTIFF is shown below:

``` _cg_TIFFGetField(tiff, 322LL, &tile_width); _cg_TIFFGetField(tiff, 323LL, &tile_length); LODWORD(v16) = _cg_TIFFTileSize(tiff); if ( v16 < *(_QWORD *)(tiff + 816) ) v16 = *(_QWORD *)(tiff + 816); tile_size = v16; // 255 _cg_TIFFTileRowSize(tiff); num_samples = *(_QWORD *)(a2 + 264); ... width = tile_width; if ( tile_width > image_width ) [1] { tile_width = image_width; width = image_width; } tiff_Structure = tiff; length = tile_length; if ( tile_length > image_length ) [2] { tile_length = image_length; length = image_length; } width_ = width; samples_X_width = num_samples * width; [3] ... calloc_size_1 = samples_X_width * length; [4] if ( samples_X_width * (unsigned __int64)length < tile_size )[5] calloc_size_1 = tile_size; *(_QWORD *)(v157 + 128) = samples_X_width; vuln_buffer = (char *)calloc(calloc_size_1, 1uLL); ```

The information is read in from the tiff image then some calculations are performed on it. At [1] it compares the tile width and the image width and defaults to the smaller value. It does the same check for image length at [2]. The tile width and length, 255 and 1024, are both longer than the image width and length, 2 and 1, thus the code defaults to the smaller values. It then calculates the needed size by multiplying the calculated width from above, times the number of samples[4]. If this is less than the size of a tile, one tile size is allocated, 255, which is what happens in this case[5]. Further into this function we see where our overwrite is happening:

``` cur_sample = 0LL; if ( num_samples ) { do { ... bytes_read = _cg_TIFFReadTile( tiff_Structure, vuln_buffer, (unsigned int)x, (unsigned int)y, 0LL, (unsigned __int16)cur_sample); vuln_buffer += bytes_read; [1] ++cur_sample; } while ( num_samples != cur_sample ); } ```

The code is reading in tile data from the tiff image, one tile per sample, and moving the buffer forward the size of one tile[1]. The problem comes in when we look at the previous code and see it defaulted to the smaller width and only allocated 255 bytes, enough for one sample. Every subsequent sample writes out of bounds and further corrupts memory. The number of samples is easily controlled in the tiff image allowing for further heap corruption quite easily.

CRASH INFORMATION

``` Testing Quick Look preview with files: bunny.tif May 6 16:00:54 qlmanage[55235] <Warning>: ImageIO: readAndCreateASCIIString Unable to read ASCII TIFF Tag #269 with reported length (8) May 6 16:00:54 qlmanage[55235] <Warning>: ImageIO: readAndCreateASCIIString Unable to read ASCII TIFF Tag #269 with reported length (8) May 6 16:00:54 qlmanage[55235] <Warning>: ImageIO: readAndCreateASCIIString Unable to read ASCII TIFF Tag #269 with reported length (8) Crashed thread log = : Dispatch queue: com.apple.root.default-qos 0 com.apple.ColorSync 0x00007fff9357070f CollectFlattenedConversion(CMMConvNode*, CMMMemMgr*, bool, __CFArray*) + 49 1 com.apple.ColorSync 0x00007fff935707d4 DoFlattenFullConversion + 71 2 com.apple.ColorSync 0x00007fff93571b69 AppleCMMCreateTransformProperty + 174 3 com.apple.CoreGraphics 0x00007fff93efc8bd __get_full_conversion_code_fragment_block_invoke + 97 4 libdispatch.dylib 0x00007fff9c52b40b _dispatch_client_callout + 8 5 libdispatch.dylib 0x00007fff9c52b303 dispatch_once_f + 67 6 com.apple.CoreGraphics 0x00007fff93efc55a convert_icc + 2557 7 com.apple.CoreGraphics 0x00007fff93efbb4e CGCMSConverterConvertData + 91 8 com.apple.CoreGraphics 0x00007fff93f28b88 CGColorTransformConvertData + 381 9 com.apple.CoreGraphics 0x00007fff93f1d9ca img_colormatch_read + 582 10 com.apple.CoreGraphics 0x00007fff93f1b837 img_data_lock + 8852 11 com.apple.CoreGraphics 0x00007fff93f186c7 CGSImageDataLock + 151 12 libRIP.A.dylib 0x00007fff933ee1d4 ripc_AcquireImage + 972 13 libRIP.A.dylib 0x00007fff933ecc7e ripc_DrawImage + 1011 14 com.apple.CoreGraphics 0x00007fff93f17c48 CGContextDrawImageWithOptions + 571 15 com.apple.CoreGraphics 0x00007fff93f179f1 CGContextDrawImage + 51 16 com.apple.ImageIO.framework 0x00007fff8d31579e CGImageCreateCopyWithParametersNew + 2575 17 com.apple.ImageIO.framework 0x00007fff8d314b95 CGImageSourceCreateThumbnailAtIndex + 3821 18 com.apple.imageKit 0x00007fff8b1ce044 -[IKImageContentView _newCGImageFromImgSrc:index:displayProperties:imageScale:createBitmapImmediately:] + 747 19 com.apple.imageKit 0x00007fff8b1ce49c __69-[IKImageContentView setImageURL:imageAtIndex:withDisplayProperties:]_block_invoke + 57 20 com.apple.imageKit 0x00007fff8b1ce883 __69-[IKImageContentView setImageURL:imageAtIndex:withDisplayProperties:]_block_invoke290 + 21 21 libdispatch.dylib 0x00007fff9c53693d _dispatch_call_block_and_release + 12 22 libdispatch.dylib 0x00007fff9c52b40b _dispatch_client_callout + 8 23 libdispatch.dylib 0x00007fff9c52f29b _dispatch_root_queue_drain + 1890 24 libdispatch.dylib 0x00007fff9c52eb00 _dispatch_worker_thread3 + 91 25 libsystem_pthread.dylib 0x00007fff911764de _pthread_wqthread + 1129 26 libsystem_pthread.dylib 0x00007fff91174341 start_wqthread + 13 --- exception=EXC_BAD_ACCESS:signal=11:is_exploitable=yes:instruction_disassembly=call *CONSTANT(%rax):instruction_address=0x00007fff9357070f:access_type=exec:access_address=0x0000000000000000: The exception code indicates that the access address was invalid in the 64-bit ABI (it was > 0x0000800000000000). Crash accessing invalid address. Consider running it again with libgmalloc(3) to see if the log changes. + EXIT_VALUE=111 ```

TIMELINE

2016-05-17 - Vendor Disclosure

2016-07-18 - Public Release

