Thanks for getting back (particularly at the weekend!)
My service shows up as: XXXXXXXXX.com.example.endpoint-test.Extension with status 0 (where XXXXXXXXX is my team identifier). In addition
- I see the corresponding process running as root using
ps: /Library/SystemExtensions/1049F8B0-4BEC-44AD-B0D0-AE8EBCBC431A/com.example.endpoint-test.Extension.systemextension/Contents/MacOS/com.example.endpoint-test.Extension. systemextensionsctl list shows my extension as [activated enabled]
I have also looked at the output from sudo launchctl procinfo $PID and I see: XPC_SERVICE_NAME => XXXXXXXXX.com.example.endpoint-test.Extension (not sure if this is relevant).
In order to help debug this, I'll give you a dump of any key information I can think of which might be useful:
- I have given the extension permission to run and Full Disk Access via System Preferences->Security & Privacy
- My container app Bundle ID is
com.example.endpoint-test - My Extension Bundle ID is
com.example.endpoint-test.Extension - I have distinct provisioning profiles for both the app and the extension. Each has the System Extension entitlement. N.B. My developer account has been granted the right to use Endpoint Security for development only.
- Both bundles have App Groups capabilities enabled with group
$(TeamIdentifierPrefix)com.example.mygroup - In the container app entitlements plist I have:
com.apple.security.temporary-exception.mach-lookup.global-name = com.example.endpoint-test.Extension - In the extension entitlements plist I have:
com.apple.developer.endpoint-security.client=1 - In the extension Info.plist I have:
NSEndpointSecurityMachServiceName= com.example.endpoint-test.Extension - I am debugging the app from the Xcode DerivedData directory. The documentation says I need to copy the app to /Applications in order for System Extensions to work, however, I've not found any behavioural changes if I do that. The only downside is that if I delete the app from DerivedData then the system doesn't uninstall the extension for me and it gets "stuck". In order to delete I just rebuild the app, copy to /Applications and delete from there.
My code roughly looks like the following
- Container app requests user interaction to install the extension
- System extension creates and
resumes an NSXPCListener on startup using initWithMachServiceName with "com.example.endpoint-test.Extension" - Container app tries to connect a few seconds after the system extension API callback returns success
- Container app creates an
NSXPCListener on startup using initWithMachServiceName with "com.example.endpoint-test.Extension", sets the remoteObjectInterface and resumes the connection. - Container app calls
remoteObjectProxyWithErrorHandler to get the remote interface - Container app calls a method on the remote interface. N.B. This call then causes the error handler passed to
remoteObjectProxyWithErrorHandler to fire. Without this call the error handler doesn't fire. - Note that the
shouldAcceptNewConnection method in the extension's listener delegate never fires during this.
Actual code
(Sorry about the objC - don't ask)
Server listen:
NSXPCListener *listener;
void extensionListen(void) {
NSLog(@"Extension Service Started");
ServiceDelegate *delegate = [ServiceDelegate new];
listener = [[NSXPCListener alloc] initWithMachServiceName: @"com.example.endpoint-test.Extension"];
listener.delegate = delegate;
[listener resume];
}
App Connect
void appConnect(void) {
NSLog(@"appConnect");
_connectionToService = [[NSXPCConnection alloc] initWithMachServiceName:@"com.example.endpoint-test.Extension" options:NSXPCConnectionPrivileged];
_connectionToService.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(ServiceProtocol)];
[_connectionToService resume];
id rop = [_connectionToService remoteObjectProxyWithErrorHandler:^(NSError* error) {
NSLog(@"Error: %@", error);
}];
[rop upperCaseString:@"hello" withReply:^(NSString *aString) {
NSLog(@"Result string was: %@", aString);
}];
// [_connectionToService invalidate];
}
One particular area where I lack confidence that what I'm doing is correct is around the various strings I've put into plists and initWithMachServiceName. There are so many possible combinations, but none seem to work. Quite possibly there's a bug there.