While assessing the potential impact of the latest BLASTPASS Zero-Click, Zero-Day Exploit on our Family of Apps, we discovered a feature in ImageIO that moves image parsing to an out-of-process sandbox. This feature mitigates the effects of vulnerabilities related to image parsing on macOS similar to BLASTPASS. App developers can enable this feature on macOS by setting the
IIOEnableOOP preference true. Anyone can enable this feature by setting the environment variable
IIOEnableOOP=YES before launching an app. It is not available on iOS.
In light of the BLASTPASS 0-day being exploited in the wild, we sought to understand how image parsing was performed on Apple devices.
Apple provides the
CGImage* set of APIs that enable developers to conveniently work with various image formats. Although these APIs have a prefix indicating the CoreGraphics framework, the underlying parser code resides in ImageIO.framework. Developers may not use CoreGraphics APIs directly and may instead use UIKit’s UIImage class which wraps
CGImage* APIs and can be used to easily render an image in a UIKit application.
Information about Apple’s image parsing practices is scarce, with the exception of a 2020 article by Project Zero which mentioned that some formats like PSD were parsed out-of-process, while the majority were done in-process. Out-of-process sandboxing of media parsers raise attacker costs by requiring a sandbox escape before exploit code can gain access to app data. This is desirable from a defense perspective.
We wanted to understand which media formats were sandboxed out-of-process, if any.
According to the Project Zero post, out-of-process image parsing is handled by the ImageIOXPCService service
Examining its exports, we discovered that it provides the same
CGImage* APIs as ImageIO. A comparison of the decompiled code for these APIs revealed that they are very similar.
Additionally, both ImageIO and XPCService import libraries like libPNG, libJPEG, etc., suggesting that they share the same capabilities to parse these formats. The list of imports for ImageIO (left) and ImageIOXPCService (right) is provided below:
The sandbox config for ImageIOXPCService is located at
Tracing XPC calls using XPoCe, we didn’t see any calls to
com.apple.imageioxpcservice, but we did see telemetry from all the calls to ImageIO APIs, hinting that image parsing is being done in-process. We also did not see the ImageIOXPCService process show up in Activity Monitor, further confirming that no out-of-process was happening.
Despite signs that ImageIO is capable of out-of-process parsing, all of this pointed towards in-process execution being the default.
At this point we needed clarity and reached for a debugger. There were a couple APIs that were good starting points:
CGImageSourceCreateWithDatasaves the reference to an image buffer, parses the header, and initializes a reader plugin based on the format of the image. The reader plugin is a format-specific plugin that knows how to handle each image format.
CGImageSourceCreateImageAtIndexlazily parses the image buffer when pixel data is accessed.
CGImageSourceCreateWithData we see reader initialization determining the image format.
The following code snippet shows
IIO_ReaderHandler::readerForBytes initializing an XPC client before deciding whether to use an XPC server (out-of-process) or not (in-process) for parsing image header data. This pattern repeats when reader plugins prepare to parse image contents.
In-Proc or Out-of-Proc?
We have determined that ImageIO is capable of parsing media both in-process and out-of-process. However, the question remains: how does the library decide where to parse an image? There exists a set of functions beginning with “
useServerFor*” which determines whether the library will use a remote XPC server (out-of-process) for a specific task.
IIOXPCClient::useServerForIdentification, which determines whether header parsing will be performed locally or in a sandbox. Other
useServerFor* functions are similar:
In our experience,
param_1 is always
0xffffffff, and the deciding factor is the byte at an offset of 0x24. By default, the byte at this offset was set, which caused
useServerForIdentification to return false and made all parsing in-process on macOS.
Modifying this byte in the debugger forced out-of-process parsing, causing ImageIOXPCService to appear in the Activity Monitor. Success!
Tracing what initialized that byte, we discovered
It sets the value at offset 0x24 into
IIOXPCClientObject based on the
IIOEnableOOP preference. Let’s take a look at how it works:
First, it checks if an environment variable called
IIOEnableOOP is set. If so, this takes precedence over any other preference. We can test our application by setting
IIOEnableOOP=YES to verify that parsing is now occurring out-of-process.
If the environment variable is not set, it falls back to reading a CFPreferences value from an app-specific key-value preference store that our application can write to.
To test this, we can call the following to set that app-specific preference:
CFPreferencesSetAppValue(CFSTR("IIOEnableOOP"), kCFBooleanTrue, kCFPreferencesCurrentApplication);
And it works! Now our application is doing OOP sandboxed parsing for all images that use ImageIO APIs. Note that this preference is persistent and will be saved between different application runs.
Does this work on iOS?
Unfortunately, this does not work on iOS. The ImageIOXPCService binary is not present on iOS, and we can see that the
useServerForIdentification function body is
#ifdef’d blank. The
IIOEnableOOP preference has no effect either.
Update as of Nov 2023 (iOS 17):
As of iOS 17 we see this feature show up in code, however, it is gated behind
While this is an undocumented feature, setting the
IIOEnableOOP preference true appears to correctly sandbox ImageIO on macOS and falls back gracefully to in-process parsing on iOS.
Examining the image parsing code in ImageIOXPCService, it resembles the code found in ImageIO itself, and it imports the same libraries used for image parsing
From our testing on macOS 13.5.1, we have not encountered issues with out-of-process parsing. We have not measured the performance impact, but if your application is not heavily reliant on images, enabling this feature is a meaningful security improvement.