CVE-2016-4631
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.
OSX El Capitan - 10.11.4 iOS - 9.3.1
https://developer.apple.com/osx/download/
8.1 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
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.
```
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
```
2016-05-17 - Vendor Disclosure
2016-07-18 - Public Release
Discovered by Tyler Bohan of Cisco Talos