Matt Rajca

blog projects github twitter email

Accessing Resources From an XPC Service's Host App in the Sandbox

August 17, 2016

XPC Services are the most commonly-used method for running work out-of-process under the Mac App Sandbox model. Unfortunately, because both the host app and the XPC Service must be sandboxed, one cannot access resources located in the host app from the XPC Service.

While you can solve this problem by storing a copy of the resources you need in the XPC Service, that's not always an option. For one, if your resources are really large, you can easily double the size of the bundle you distribute in the App Store by doing this. In my case, I was working on a developer tool that embeds SourceKit's XPC Service and moving files around would have required refactoring work I wanted to avoid. Fortunately, there's a better way.

If you look at the list of App Sandbox entitlements, you may find the com.apple.security.inherits key intriguing – it allows "child process inheritance of the parent’s sandbox". While setting it this does in fact let you access resources stored in the parent app from a process spawned using POSIX calls (or NSTask), it doesn't work for XPC Services. Though it's a little vague, the document linked above states "... using a child process does not provide the security afforded by using an XPC service" in the Enabling App Sandbox Inheritance section, suggesting XPC Services are more isolated from their parent process.

The next idea I had was using security-scoped bookmarks to pass an encoded version of the app bundle URL to the XPC Service and resolving it on the other end. Unfortunately, this did not work. If we look back in the App Sandbox Design Guide, it states only the process that created the bookmark can access it and "Critically, no other sandboxed app can resolve the bookmark".

Interestingly, if we simply create a normal bookmark (rather than a security-scoped one), things Just Work:

  
    let url = NSBundle.mainBundle().resourcesURL
    let bookmarkData = try url.bookmarkDataWithOptions([], includingResourceValuesForKeys: nil, relativeToURL: nil)
    
    // Ensure we have an XPC connection...
    someXPCRemoteProxy.provideResourceAccessWithBookmarkData(bookmarkData)
    

On the receiving end, we simply resolve the URL. Note this can be done with both CoreFoundation (instead of Foundation) API as well in case your XPC Service is written in C/C++:

  
    CFErrorRef error = nullptr;
    CFURLRef url = CFURLCreateByResolvingBookmarkData(nullptr, bookmark,  kCFBookmarkResolutionWithoutUIMask, nullptr, nullptr, nullptr, &error);

    if (url != nullptr) {
        CFURLStartAccessingSecurityScopedResource(url);
        CFRelease(url);
    } else if (error != nullptr) {
        // Handle error.
    } else {
        // Handle unknown error.
    }
    

In fact, earlier today, I committed code just like this to a fork of Swift that adds Sandboxing support to the SourceKit XPC Service.

Hope this helps!