Validating an XPC connection in a sandboxed process

I'm working on a macOS app that I'd like to sandbox along with a login item that I'd also like to sandbox. Login items implicitly have an XPC Mach service created for them which my app is able to successfully use communicate with the sandboxed login item (because they're in the same application group).

The issue is that any non-sandboxed process can also connect to my login item's XPC Mach service, and I'd really rather that wasn't the case. I realize there's no privilege escalation going on, but this feels unnecessarily insecure. My attempts to secure the connection keep failing due to sandboxing. Is there a way to do what I'm attempting or is Apple's intention that any non-sandboxed process on the system ought to be able to successfully communicate with my login item?

If I don't sandbox my login item it's trivial for me to secure this connection.

Here's what I've tried so far:

Path based

  1. Retrieve the SecCode using SecCodeCreateWithXPCMessage
  2. Retrieve the SecStaticCode using SecCodeCopyStaticCode
  3. Retrieve the path of the static code using SecCodeCopyPath
  4. Compare this path with my login item's path based on Bundle.main.bundleURL

This fails on step 2, the SecCodeCopyStaticCode function gets back a "UNIX error exception: 1". This kind of makes sense to me as it needs to read from the file system in order to get the static code of the running process.

Code requirements based

  1. Retrieve the SecCode using SecCodeCreateWithXPCMessage
  2. Construct a SecRequirement including amongst other things that certificate leaf[subject.OU] = <my team id>
  3. Use SecCodeCheckValidity on the code instance from step #1 and the requirement from step #2

This fails on step 3, SecCodeCheckValidity also results in a "UNIX error exception: 1". Looking at the logs generated by sandboxd it looks like under the hood that function calls _CFBundleGetBundleVersionForURL and fails. The violation is:

deny(1) file-read-data ~/Library/Developer/Xcode/DerivedData/LoginItemSample-ajfwjiwmyuphdbeyugmssxszdlyq/Build/Products/Debug/LoginItemSample.app

Is there perhaps some combination of SecCSFlags values I can pass to prevent any file system access?

Answered by DTS Engineer in 711541022

I'm working on a macOS app that I'd like to sandbox along with a login item that I'd also like to sandbox.

Is this because it’s a good idea in general? Or because you’re targeting the Mac App Store?

The issue is that any non-sandboxed process can also connect to my login item's XPC Mach service

Yeah, that’s a weird wrinkle.

and I'd really rather that wasn't the case.

Understandable, because…

I realize there's no privilege escalation going on

It depends on how you look at it. If you look at this from a traditional Unix privilege model, you’re absolutely correct. However, when you look at it from the per-process privilege model now supported on the Mac, this may well represent a privilege escalation. For example, the user might have granted your app a TCC privilege (in System Preferences > Security & Privacy > Privacy) and you don’t want to hand that out to anyone.

The best way to solve this problem is to have the XPC subsystem do this validation for you. See this thread. Sadly, this is not currently possible if you use NSXPCConnection )-:

Barring that, the way I’d solve this problem is to have the client process grant access to its bundle to the login item as part of the XPC request. That’ll dynamically extended the login item’s sandbox so that it can read the code in order to validate its signature.

I believe that you can do this simply by passing a security-scoped URL in the XPC message. The login item can then call -startAccessingSecurityScopedResource, do the validation, and then call -stopAccessingSecurityScopedResource when it’s done.

This should be secure because it only grants the login item access to the content of the client’s bundle, which shouldn’t contain anything sensitive.

Having said that, I’ve never actually tried this [1] so lemme know how you get along (-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] I know that the basic strategy is sound because we use it a lot in system code. However, it’s possible that you might run into a snag caused by limitations of the public API or the App Sandbox itself.

Accepted Answer

I'm working on a macOS app that I'd like to sandbox along with a login item that I'd also like to sandbox.

Is this because it’s a good idea in general? Or because you’re targeting the Mac App Store?

The issue is that any non-sandboxed process can also connect to my login item's XPC Mach service

Yeah, that’s a weird wrinkle.

and I'd really rather that wasn't the case.

Understandable, because…

I realize there's no privilege escalation going on

It depends on how you look at it. If you look at this from a traditional Unix privilege model, you’re absolutely correct. However, when you look at it from the per-process privilege model now supported on the Mac, this may well represent a privilege escalation. For example, the user might have granted your app a TCC privilege (in System Preferences > Security & Privacy > Privacy) and you don’t want to hand that out to anyone.

The best way to solve this problem is to have the XPC subsystem do this validation for you. See this thread. Sadly, this is not currently possible if you use NSXPCConnection )-:

Barring that, the way I’d solve this problem is to have the client process grant access to its bundle to the login item as part of the XPC request. That’ll dynamically extended the login item’s sandbox so that it can read the code in order to validate its signature.

I believe that you can do this simply by passing a security-scoped URL in the XPC message. The login item can then call -startAccessingSecurityScopedResource, do the validation, and then call -stopAccessingSecurityScopedResource when it’s done.

This should be secure because it only grants the login item access to the content of the client’s bundle, which shouldn’t contain anything sensitive.

Having said that, I’ve never actually tried this [1] so lemme know how you get along (-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] I know that the basic strategy is sound because we use it a lot in system code. However, it’s possible that you might run into a snag caused by limitations of the public API or the App Sandbox itself.

Wow, thanks for the speedy reply! Now I'm wondering where in the world you're located 🤔


Is this because it’s a good idea in general? Or because you’re targeting the Mac App Store?

Kind of both. For my own case I'm not targeting the App Store. However, I've been writing an open source Swift package that encapsulates all of the general XPC security, routing, error handling, etc. So ideally I'd come up with a solution that's acceptable for Mac App Store apps as well as my own needs.

However, when you look at it from the per-process privilege model now supported on the Mac, this may well represent a privilege escalation.

That's an excellent point.


I'm using the C API (I found the Objective-C one to be quite an awkward fit with Swift) so xpc_connection_set_peer_code_signing_requirement looks promising. Unfortunately since it was just released in macOS 12, I don't think I'll be able to make use of that for at least a couple years.

The security-scoped URL sounds promising, I'll make a go at that approach. Much appreciated, it never would've occurred to me do to that. Am I correct in saying this would be an app-scoped bookmark, the app won't need any additional entitlements, and the login item would need the com.apple.security.files.bookmarks.app-scope entitlement in order to call startAccessingSecurityScopedResource and stopAccessingSecurityScopedResource?

Hmm, in trying to implement this I'm getting the sense this may not be possible as it seems neither an app-scope nor a document-scoped bookmark matches what you're describing. Possibly there are undocumented/under documented ways of making one or both of these work?

From bookmarkData(options:includingResourceValuesForKeys:relativeTo:):

For an app-scoped bookmark, no sandboxed app other than the one that created the bookmark can obtain access to the file-system resource that the URL (obtained from the bookmark) points to. Specifically, a bookmark created with security scope fails to resolve if the caller does not have the same code signing identity as the caller that created the bookmark.

For a document-scoped bookmark, any sandboxed app that has access to the bookmark data itself, and has access to the document that owns the bookmark, can obtain access to the resource.

and then elsewhere on that same page:

If you are creating a security-scoped bookmark to support App Sandbox, use this parameter as follows:

  • To create an app-scoped bookmark, use a value of nil. 
  • To create a document-scoped bookmark, use the absolute path (despite this parameter’s name) to the document file that is to own the new security-scoped bookmark.

My interpretation of this is that I must create a document-scoped bookmark so that the login item process is capable of obtaining file-system access. However, if I use a document-scoped bookmark what absolute path would I be providing since there is no document file?

Huh, okay so what actually worked was creating a bookmark that doesn't have either of these scopes. Is this supposed to work?

In the app:

let bookmark = try Bundle.main.bundleURL.bookmarkData()

Send that over the XPC connection, and then in the login item:

var isStale = Bool()
 _ = try URL(resolvingBookmarkData: bookmark, bookmarkDataIsStale: &isStale)

Neither the app nor the login item have any additional entitlements and as you can see I'm doing nothing at all with the URL instance that's created.

This works perfectly, but I don't understand why and I'm concerned it could break at any time in the future.

I'm concerned it could break at any time in the future.

That’s understandable.

Just to set expectations, one of my colleagues here in DTS is the expert on the issue of passing file system access across sandbox boundaries. If you want a definitive answer, I recommend that you open a DTS tech support incident and speak to him.

I suspect what’s happening here is that you’re benefitting from the bookmark’s implicit security scope. See the discussion of NSURLBookmarkCreationWithoutImplicitSecurityScope and NSURLBookmarkResolutionWithoutImplicitStartAccessing in <Foundation/NSURL.h>. Fortunately this is easy to test:

  • On the receiving side, pass in NSURLBookmarkResolutionWithoutImplicitStartAccessing and see if you then have to call -startAccessingSecurityScopedResource.

  • On the sending side, pass in NSURLBookmarkCreationWithoutImplicitSecurityScope and see if that breaks things completely (-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I suspect what’s happening here is that you’re benefitting from the bookmark’s implicit security scope.

Thanks for the explanation! I didn't realize such behavior was implicit.

Validating an XPC connection in a sandboxed process
 
 
Q