Talos Vulnerability Report

TALOS-2016-0171

Apple Image I/O API Tiled TIFF Remote Code Execution Vulnerability

July 18, 2016

Report ID

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  c

Credit

Tyler Bohan

Timeline

2016-05-16 - Vendor Disclosure
2016-07-18 - Public Release