XPC between endpoint security and host application

Hello, I am having some issues with running an XPC server on an endpoint security and connecting to it from the sandboxed host application.

I tried doing the following:

setting xpc server in endpoint security extension entitlements:

<key>com.apple.developer.endpoint-security.client</key>
<true/>
<key>com.apple.security.xpc.server</key>
<true/>

Adding the mach service with the plist:

<dict>
	<key>NSExtension</key>
	<dict>
		<key>NSExtensionPointIdentifier</key>
		<string>com.apple.system-extension-endpoint-security</string>
		<key>NSExtensionPrincipalClass</key>
		<string>$(PRODUCT_MODULE_NAME).ESFExtension</string>
	</dict>
	<key>NSEndpointSecurityMachServiceName</key>
	<string>[TEAMID]com.[UNIQUE_ID]</string>
</dict>
</plist>

Putting a mach-lookup in sandboxed host application entitlements

<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
	<key>com.apple.security.files.user-selected.read-only</key>
	<true/>
	<key>com.apple.developer.system-extension.install</key>
	<true/>
	<key>com.apple.security.exception.mach-lookup.global-name</key>
	<array>
		<string>[TEAMID]com.[UNIQUE_ID]</string>
	</array>
</dict>
Creating the server in the system extension using xpc_connection_create_mach_service(_service_name.c_str(), dispatch_get_main_queue(), XPC_CONNECTION_MACH_SERVICE_LISTENER); 

with _service_name being the same as in the mach-lookup entitlement.

And connecting to it in the host app with:

xpc_connection_create_mach_service([self.serviceName UTF8String], dispatch_get_main_queue(), 0);

My problem is I get an xpc error 159 (sandbox restriction) in the lookup

(libxpc.dylib) [com.apple.xpc:connection] [0x600001a7db30] failed to do a bootstrap look-up: xpc_error=[159: Unknown error: 159]

I tried putting the sysex and the host app in the same app group, and it didn't help and I also read this is bad practice to have an app group between a sandboxed app and a system extension so I removed it.

I tried adding a temporary-exception and with it, the code works properly.

I tried with the XPC_CONNECTION_MACH_SERVICE_PRIVILEGED flag but it still didn't work.

Is it possible to have an XPC connection between a ES sysex and it's host app? Should the service name have a prefix of the bundle name or does it must have a certain pattern? Do I need to add some capability in the Certificates, Identifiers & Profiles?

Thanks for helping.

Answered by DTS Engineer in 845139022

First up, terminology. When talking about extensions on Apple platforms:

  • The container application is the application in which the extension is embedded.

  • The host application is the app uses the extension.

In the case of an ES sysex, the host application is the system itself.

It seems like you’re trying to sandbox your container app. That’s pretty rare, because the ES sysex itself can’t be sandboxed and thus ES clients can’t be distributed on the Mac App Store. The simplest option here would be to not sandbox your container app.

Beyond that, your description of how your ES sysex is constructed highlights a couple of oddities:

  • I’m not sure what you’re expecting com.apple.security.xpc.server to do, but it’s not a known entitlement and thus won’t do anything useful [1].

  • NSExtensionPrincipalClass doesn’t make sense for an ES sysex because they don’t use Objective-C.

I tried with the XPC_CONNECTION_MACH_SERVICE_PRIVILEGED flag but it still didn't work.

You should set this flag because it prevents some other program in the GUI login session namespace from spoofing your named XPC endpoint. However, it applies more restrictions, so it’s fine to leave it off during the initial bring up.

I tried putting the sysex and the host app in the same app group …

Right. This is a common path forward.

The issue you’re having here is that the App Sandox is blocking the container app from connecting to the named XPC endpoint. There are three standard ways around this:

  • Disable App sandbox.

  • Punch a whole through the sandbox using a temporary exception entitlement.

  • Prefix the named XPC endpoint with an app group ID. The App Sandbox will not block connections to x.y.z if your app has access to the app group x.y.

I also read this is bad practice to have an app group between a sandboxed app and a system extension so I removed it.

You need to distinguish between app group IDs and app group containers. You can’t share an app group container between your container app and your ES sysex because they run as different users (the logged in user and root, respectively) and thus get different container paths.

It’s perfectly reasonable to share an app group ID between the two.

Note, however, that App Groups are tricky on macOS. App Groups: macOS vs iOS: Working Towards Harmony has all the details, but I suspect you’ll be OK because you’re building an ES sysex (rather than a daemon) and are working with modern tools.

Share and Enjoy

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

[1] You’re lucky this is in the com.apple.security. namespace, making it an unrestricted entitlement. See TN3125 Inside Code Signing: Provisioning Profiles for a definition of that term. If it were in any other namespace it’d block the process from launching.

First up, terminology. When talking about extensions on Apple platforms:

  • The container application is the application in which the extension is embedded.

  • The host application is the app uses the extension.

In the case of an ES sysex, the host application is the system itself.

It seems like you’re trying to sandbox your container app. That’s pretty rare, because the ES sysex itself can’t be sandboxed and thus ES clients can’t be distributed on the Mac App Store. The simplest option here would be to not sandbox your container app.

Beyond that, your description of how your ES sysex is constructed highlights a couple of oddities:

  • I’m not sure what you’re expecting com.apple.security.xpc.server to do, but it’s not a known entitlement and thus won’t do anything useful [1].

  • NSExtensionPrincipalClass doesn’t make sense for an ES sysex because they don’t use Objective-C.

I tried with the XPC_CONNECTION_MACH_SERVICE_PRIVILEGED flag but it still didn't work.

You should set this flag because it prevents some other program in the GUI login session namespace from spoofing your named XPC endpoint. However, it applies more restrictions, so it’s fine to leave it off during the initial bring up.

I tried putting the sysex and the host app in the same app group …

Right. This is a common path forward.

The issue you’re having here is that the App Sandox is blocking the container app from connecting to the named XPC endpoint. There are three standard ways around this:

  • Disable App sandbox.

  • Punch a whole through the sandbox using a temporary exception entitlement.

  • Prefix the named XPC endpoint with an app group ID. The App Sandbox will not block connections to x.y.z if your app has access to the app group x.y.

I also read this is bad practice to have an app group between a sandboxed app and a system extension so I removed it.

You need to distinguish between app group IDs and app group containers. You can’t share an app group container between your container app and your ES sysex because they run as different users (the logged in user and root, respectively) and thus get different container paths.

It’s perfectly reasonable to share an app group ID between the two.

Note, however, that App Groups are tricky on macOS. App Groups: macOS vs iOS: Working Towards Harmony has all the details, but I suspect you’ll be OK because you’re building an ES sysex (rather than a daemon) and are working with modern tools.

Share and Enjoy

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

[1] You’re lucky this is in the com.apple.security. namespace, making it an unrestricted entitlement. See TN3125 Inside Code Signing: Provisioning Profiles for a definition of that term. If it were in any other namespace it’d block the process from launching.

Hey Quinn, thanks for answering.

I will try prefixing the XPC endpoint with an app group ID. In the meanwhile regarding the other options, I think I don't understand them fully.

  1. Is disabling sandbox good practice? This app is distributed via MDM so as I understand it will work and solve it, but I don't know if it is the correct usage.

  2. Temporary exception works fine, but I think I don't understand temporary-exceptions properly, are apps with temporary exceptions allowed by Apple? I currently can notarize and launch them but should I ask apple for a non temporary mach lookup entitlement? because I see mixed usage of com.apple.security.temporary-exception.mach-lookup.global-name and com.apple.security.exception.mach-lookup.global-name, Are temporary exception allowed in production usage?

Thanks, David.

Also, regarding prefixing, given my container app bundle id is com.***.YYY and my ES extension bundle id is com.***.YYY.esextension, and my NSEndpointSecurityMachServiceName is {TEAM_ID}.com.***.YYY.status, with an app group both in the extension and the container app: <key>com.apple.security.application-groups</key> <array> <string>group.{TEAM_ID}.com.***.YYY</string> </array> Is this incorrect?

Also, regarding prefixing, given my container app bundle id is com.***.YYY and my ES extension bundle id is com.***.YYY.esextension, and my NSEndpointSecurityMachServiceName is {TEAM_ID}.com.***.YYY.status, with an app group both in the extension and the container app:

	<key>com.apple.security.application-groups</key>
	<array>
		<string>group.{TEAM_ID}.com.***.YYY</string>
	</array>

Is this incorrect? because a group must start with group. and now it is a prefix of the XPC endpoint but it still blocks it with the same error (159).

Accepted Answer
Is disabling sandbox good practice?

*shrug*

There are valid arguments either way. I actually have a long post, The Case for Sandboxing a Directly Distributed App, that tackles this directly.

are apps with temporary exceptions allowed by Apple?

That depends on the context. Temporary exception entitlements are tightly controlled on the App Store, but they’re just fine to use outside of the App Store. Again, The Case for Sandboxing a Directly Distributed App talks about this.

Regarding your app group ID choice, I recommend that you use an iOS-style app group ID because you can authorise that with a provisioning profile [1]

If you use an iOS-style app group ID then there’s no need to encode your Team ID in it. It just lengthens the string for no reason.

There’s also no specific need to use the com.x.y.z notation. iOS-style app group IDs don’t need that because their uniqueness is guaranteed by the Developer website via the provisioning profile mechanism.

So, you could get away with a much shorter app group ID, like group.my-product. However, it can make sense to use a slightly longer one just so that the XPC named endpoint makes sense when you look at it in launchctl and so on. So group.com.example.my-product, and then use group.com.example.my-product.my-service as the XPC endpoint name.

IMPORTANT NSEndpointSecurityMachServiceName has to be prefixed by the group name, so in the above example it’d be group.com.example.my-product.my-service.

Share and Enjoy

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

[1] Which avoids you falling out of the entitlement-validated state, as explained in the Entitlements-Validated Flag section of App Groups: macOS vs iOS: Working Towards Harmony.

Thank you very much. I will keep the temporary-exception for now as we are outside of App Store, and will try the IOS-style app group with a matching NSEndpointSecurityMachServiceName later on.

Also, without a temporary-exception, prefixing the NSEndpointSecurityMachServiceName with group. was what I was missing :)

Have a nice day :)

XPC between endpoint security and host application
 
 
Q