Sandboxed app extensions connecting to not sandboxed XPC service

Hi,

I'm developing an macOS app which includes an agent (registered as login item) which is intended to run for entire duration of user's login session.

The agent's bundle also includes few application extensions. The application is intended to be distributed outside of App Store. It is not going to be sandboxed but rather notarized, so I'm using hardened runtime.

I want to add some application extensions (for example: a file provider etension) to be bundled with the agent.

The extensions are sandboxed and belong to the same application group as main application and the agent.

Extensions should do some IPC calls on the running agent process.

I think the most convenient solution for the IPC would be to use the XPC service, so, in the agent, I'am creating an XPC listener registered to mach service name.

Now I want to connect to the XPC service provided by the agent from the app extensions. This, however, fails with the following error:

Error Domain=NSCocoaErrorDomain Code=4099 "The connection to service named abcd.service was invalidated: failed at lookup with error 159 - Sandbox restriction."

My application bundle has now the following structure:

MainApp.app                 --- main application bundle (hardened runtime)
 Contents/Library/LoginItems/agent.app --- agent with XPC service (hardened runtime)
   .../agent.app/Contents/PlugIns/AppExt1.appex  --- app extension 1 (sandboxed)
   .../agent.app/Contents/Plugins/AppExt2.appex  --- app extension 2 (sandboxed)

Searching through the forum I've found another post, which advises to bundle the XPC service within the app extension to mitigate this issue. This, however, does not seem like a viable solution for my problem: what I need is to call the code on the running agent instance.

As an alternative I considered to create a unix domain socket in the application group container and use that for communication between the agent and its extensions. XPC however is more convenient so if there is any way to make it working for the above scenario, I would be interested to learn about it.

Accepted Reply

The agent creates XPC listeners with mach service names: S1J6DZ9E7U.com.myapp.agent.srv1 and S1J6DZ9E7U.com.myapp.agent.srv2.

This won’t work. A Service Management login item can only run a single XPC listener and its name must match the name of the login item. For (a very old and very crufty)-: example of how to set this up, see AppSandboxLoginItemXPCDemo.

When debugging, I run the agent direcly from Xcode instead of relying upon it to be started by launch services.

That’s fine for day-to-day debugging but it’s a bad idea during this bring up phase. Xcode has special magic to help you debug XPC Services and that magic allows you to do things that you won’t otherwise work, for example, register a XPC listener with an unexpected name.

My general advice on this front:

  1. Use the technique in TN3113 Testing and debugging XPC code with an anonymous listener to bring up your core XPC functionality within a single process.

  2. Once you have that working, move the listener to your Service Management login item and test it from a non-sandboxed client, like your app. Make sure to start the login item by calling SMLoginItemSetEnabled, not be running it from Xcode.

  3. Then try to get things working in a sandboxed client.

The last step may require you to apply the com.apple.security.temporary-exception.mach-lookup.global-name temporary exception entitlement but I don’t think it’ll necessary because of the App Group Mach service name exception discussed here and here.

And yes, this is a very tangled web we’re weaving here (-:

Share and Enjoy

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

  • That explains a lot. Thank you. Your posts are always very helpful.

Add a Comment

Replies

It is not going to be sandboxed but rather notarized, so I'm using hardened runtime.

Just FYI, I recommend that you use enable the hardened runtime in all your code. While the only place it’s required right now is for notarisation, it’s a good security practice in all situations.

My application bundle has now the following structure:

That’s a really helpful summary. Thanks!

I presume that your login item is actually a Service Management login item, that is, you install it using SMLoginItemSetEnabled. If so, standard practice is to prefix your XPC service name with your Team ID. That will be accessible to your app and your app extensions. If any of the client code is sandboxed, sign it with an app group entitlement that’s a prefix of the XPC service name. For example, if your service name is SKMME9E2Y8.com.example.test703702.service, add an app group ``SKMME9E2Y8.com.example.test703702`.

Share and Enjoy

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

Thanks for your answer, Quinn. Yes, I'm using SMLoginItemSetEnabled to register the agent. When debugging, I run the agent direcly from Xcode instead of relying upon it to be started by launch services.

I have tried to follow your advice, but apparently I am still doing something wrong, because NSXPCConnection.remoteObjectProxyWithErrorHandler() calls made in app extension do fail with:

Error Domain=NSCocoaErrorDomain Code=4099 "The connection to service named S1J6DZ9E7U.com.myapp.agent.srv2 was invalidated: failed at lookup with error 159 - Sandbox restriction.

Looking at the application bundle I think I am using the correct application group

% codesign -dv --entitlements - agent.app/Contents/MacOS/agent 
Executable=/Users/user/Library/Developer/Xcode/DerivedData/MyApp-eylcetoqyczzehhcpvnjldtuqcay/Build/Products/Debug/agent.app/Contents/MacOS/agent
Identifier=com.myapp.agent
Format=app bundle with Mach-O thin (x86_64)
CodeDirectory v=20500 size=114401 flags=0x10000(runtime) hashes=3564+7 location=embedded
Signature size=4783
Signed Time=7 Apr 2022 at 11:53:59
Info.plist entries=24
TeamIdentifier=S1J6DZ9E7U
Runtime Version=12.1.0
Sealed Resources version=2 rules=13 files=4
Internal requirements count=1 size=184
[Dict]
	[Key] com.apple.security.application-groups
	[Value]
		[Array]
			[String] S1J6DZ9E7U.group.com.myapp.agent
	[Key] com.apple.security.get-task-allow
	[Value]
[Bool] true


% codesign -dv --entitlements - agent.app/Contents/PlugIns/FPExt.appex/Contents/MacOS/FPExt 
Executable=/Users/user/Library/Developer/Xcode/DerivedData/MyApp-eylcetoqyczzehhcpvnjldtuqcay/Build/Products/Debug/agent.app/Contents/PlugIns/FPExt.appex/Contents/MacOS/FPExt
Identifier=com.myapp.agent.FPExt
Format=bundle with Mach-O thin (x86_64)
CodeDirectory v=20500 size=5095 flags=0x10000(runtime) hashes=148+7 location=embedded
Signature size=4783
Signed Time=7 Apr 2022 at 11:53:55
Info.plist entries=22
TeamIdentifier=S1J6DZ9E7U
Runtime Version=12.1.0
Sealed Resources version=2 rules=13 files=0
Internal requirements count=1 size=188
[Dict]
	[Key] com.apple.application-identifier
	[Value]
		[String] S1J6DZ9E7U.com.myapp.agent.FPExt
	[Key] com.apple.security.app-sandbox
	[Value]
		[Bool] true
	[Key] com.apple.security.application-groups
	[Value]
		[Array]
			[String] S1J6DZ9E7U.group.com.myapp.agent
	[Key] com.apple.security.get-task-allow
	[Value]
		[Bool] true
	[Key] com.apple.security.network.client
	[Value]
		[Bool] true
	[Key] com.apple.security.network.server
	[Value]
		[Bool] true


Where com.myapp.agent is budle id of my launch agent, com.myapp.agent.FPExt is a bundled file provider extension.

The agent creates XPC listeners with mach service names: S1J6DZ9E7U.com.myapp.agent.srv1 and S1J6DZ9E7U.com.myapp.agent.srv2.

I try to connect from the extension to S1J6DZ9E7U.com.myapp.agent.srv2, but I'm getting the error as mentioned above.

I have also tried to define dedicated application groups for each of the XPC services and added them to target entitlements, but it doesn't help either.

Am I doing something wrong? Or maybe you could advise how to diagnose the problem?

The agent creates XPC listeners with mach service names: S1J6DZ9E7U.com.myapp.agent.srv1 and S1J6DZ9E7U.com.myapp.agent.srv2.

This won’t work. A Service Management login item can only run a single XPC listener and its name must match the name of the login item. For (a very old and very crufty)-: example of how to set this up, see AppSandboxLoginItemXPCDemo.

When debugging, I run the agent direcly from Xcode instead of relying upon it to be started by launch services.

That’s fine for day-to-day debugging but it’s a bad idea during this bring up phase. Xcode has special magic to help you debug XPC Services and that magic allows you to do things that you won’t otherwise work, for example, register a XPC listener with an unexpected name.

My general advice on this front:

  1. Use the technique in TN3113 Testing and debugging XPC code with an anonymous listener to bring up your core XPC functionality within a single process.

  2. Once you have that working, move the listener to your Service Management login item and test it from a non-sandboxed client, like your app. Make sure to start the login item by calling SMLoginItemSetEnabled, not be running it from Xcode.

  3. Then try to get things working in a sandboxed client.

The last step may require you to apply the com.apple.security.temporary-exception.mach-lookup.global-name temporary exception entitlement but I don’t think it’ll necessary because of the App Group Mach service name exception discussed here and here.

And yes, this is a very tangled web we’re weaving here (-:

Share and Enjoy

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

  • That explains a lot. Thank you. Your posts are always very helpful.

Add a Comment