Sandboxing ImageIO media parsing in macOS
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.
Background
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.
ImageIOXPCService
According to the Project Zero post, out-of-process image parsing is handled by the ImageIOXPCService service /System/Library/Frameworks/ImageIO.framework/Versions/A/XPCServices/ImageIOXPCService.xpc/Contents/MacOS/ImageIOXPCService
.
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 /System/Library/Sandbox/Profiles/com.apple.ImageIOXPCService.sb
.
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.
Debugging
At this point we needed clarity and reached for a debugger. There were a couple APIs that were good starting points:
CGImageSourceCreateWithData
saves 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.CGImageSourceCreateImageAtIndex
lazily parses the image buffer when pixel data is accessed.
Stepping through 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.
Let’s examine 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.
Forcing Out-of-Proc
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 IIOXPCClient_block_invoke
:
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 IIO_OSAppleInternalBuild()
.
Conclusion
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.