System Extension sandboxing error during CFMessagePortCreateRemote for an unsandboxed app.

I am working on an MacOS app that I don't plan on releasing it via the app store. So basically it's not sandboxed.
I am using Packet Tunnel as a System extension. I do send the packets from System extension to the App side. I am reusing part of the iOS code base and trying to make it work on the MacOS App. On the iOS side I was using CFMessagePorts to send packets from the Network Extension to the Application, and basically that involved using App Groups as the port name.
So on the mac side, since the app is not sandboxed my understanding is that I don't need to use App groups at all. If the app's bundle id is com.example.transparentproxy then by using the following line, it should create a port

var remotePort : CFMessagePort? = CFMessagePortCreateRemote(kCFAllocatorDefault, "com.example.transparentproxy.out" as CFString)
But it doesn't. It returns nil. The only log that I see corresponding to this in the console is
taskgated-helper Couldn't read values in CFPrefsPlistSource<0x7fa2f3f2e040> (Domain: kCFPreferencesAnyApplication, User: kCFPreferencesAnyUser, ByHost: Yes, Container: (null), Contents Need Refresh: No): accessing preferences outside an application's container requires user-preference-read or file-read-data sandbox access
've made sure I don't have sandbox access on SystemExtension, nor sandbox access on the App itself. Any idea as to how to debug this or if my understanding of sandbox access on System Extension is wrong.
Using the System extension, I did access a file and write to it and it did work. If the System extension is not sandboxed I should be able to create a port remotely right?

var localPort : CFMessagePort? = CFMessagePortCreateLocal(kCFAllocatorDefault, "com.example.transparentproxy.in" as CFString, nil, nil, nil)

I can create a port locally. since the above line does return an object. But it's just the remote port that keeps returning nil.
Also I had added this entitlement to the entitlements of both the app and extension :

<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>

<array>

<string>com.example.transparentproxy.out</string>

<string>com.example.transparentproxy.in</string>

</array>

<key>com.apple.security.temporary-exception.mach-register.global-name</key>

<array>

<string>com.example.transparentproxy.out</string>

<string>com.example.transparentproxy.in</string>

</array>


NE sysexes have dedicated infrastructure for advertising a Mach service (namely the

NEMachServiceName
property). Is there a reason you’re not using that?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

NEMachServiceName would be used to send IPC messages between app and the app extension. I am using a separate launchd process that is not directly part of the app itself. I still copy the process into the app bundle when creating the package, but the process isn't implicitly part of the app bundle, as in when building from Xcode. So basically the process doesn't have access to the App group.
But because the App, the launchd process which is a command line tool and the System extension are not sandboxed, shouldn't I be able to use CFMessagePorts between either of the two with unrestricted access and not be dependent on App groups ?

I am using a separate launchd process that is not directly part of the app itself.

Daemon or agent?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Daemon.
I still use an agent, but in my use case it's the Daemon that needs to send and receive data from the System extension.
Using mach ports I was able to communicate between the App and the Daemon and vice versa. But I've not been able to do it with the Daemon and System Extension.

If you have a

launchd
daemon then you can:
  • Have it advertise a service using the

    MachServices
    property in its
    launchd
    property list.
  • Add the global Mach service temporary exception (

    com.apple.security.temporary-exception.mach-lookup.global-name
    ) to the NE sysex so that it can connect to that service.

Note that this is a Mach service, so you can use any API that works with Mach services, including

CFMessagePort
. However, I strongly recommend that you switch to using XPC (either via the
<xpc/xpc.h>
C API or
NSXPCConnection
) because it’s our long-term direction when it comes to IPC.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for that. I followed the steps you've mentioned, and that has helped in making a lot of progress. I'll switch to XPC once I have a version of this completely working.
So I can now connect to the launchd daemon. I can send packets from the Network Extension and it is being received on the daemon side. But I dunno for sure if the Daemon is able to connect to the Network Extension now. Previously, CFMessagePortCreateRemote would return nil, so it helped me in understanding that on the Daemon side I couldn't connect to the Local Port on the Network Extension.
But now, it does return an object. Then on the Daemon side when I call CFMessagePortSendRequest it does return kCFMessagePortSuccess. So I expect to the callback for CFMessagePortCreateLocal to get triggered on the Network extension side, but that doesn't happen.
When I look at the console, I see the following :

taskgated-helper com.identifier.NetworkExtension: Unsatisfied entitlements: com.apple.security.application-groups

Disallowing:com.identifier.NetworkExtension

trustd Entitlement com.apple.application-identifier=TeamID.com.identifier.NetworkExtension is ignored because of invalid application signature or incorrect provisioning profile

Is it expected for App-Groups to be used on an unsandboxed app or unsandboxed app extension?

But I dunno for sure if the Daemon is able to connect to the Network Extension now.

I recommend that you avoid this because it produces a loop in your dependency graph. If you want the daemon to be able to send asynchronously to the NE, the best approach is to have the NE create an anonymous listener, pass its endpoint to the daemon, and then have the daemon connect to that endpoint. Unfortunately

CFMessagePort
is not up to that task.

If you’re stuck with

CFMessagePort
you can hack around this by using a long polling approach, that is, have NE keep an outstanding request to the daemon that the daemon only replies to when it has async work for the NE.

But, seriously, XPC is so much better for this sort of thing.

Oh, one more thing. No matter what approach you use, you must make sure that both sides of this communication have a strategy for dealing with the remote end going unresponsive. Things will end badly if you a naïvely buffer data without bound.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

WWDC runs Mon, 22 Jun through to Fri, 26 Jun. During that time all of DTS will be busy with conference duties.

I got it to work now. I can send data back and forth between the Daemon and SystemExtension now. Still using CFMessageports for now. Thanks a lot Quinn “The Eskimo!”
System Extension sandboxing error during CFMessagePortCreateRemote for an unsandboxed app.
 
 
Q