I have two privileged service(s) and a desktop app. The privileged services are packaged into /Library/*** and are run using launchd at runtime. The desktop app is just dropped into /Applications.
The desktop app connects to one of the services (let's say service "B") via XPC. That is, B is running an XPC listener (using libxpc). Both applications are written in golang with xpc interaction via CGO.
This is all working fine: The desktop app is receiving notifications over XPC from service B. However, during our build we dump the built and signed apps (before .pkg'ing) into a dist folder. When we run the app (using a makefile target), we copy the services from dist to another location as root, then execute the binaries directly. This is problematic for the desktop app, because my understanding is that XPC requires launchd to assert the namespace it's under. Thus, when service B is launched this way, it says "operation not permitted." We also want to reserve the ability to run a production version of our app on the same machine (drink our own champagne and all that), and I would like to avoid having development versions running on startup, so I don't want to use the same launch configurations.
MacOS is one of three platforms we support (linux, windows as well). Our IPC implementation under MacOS uses XPC via golang build tags.
Questions:
- Is it possible to start the XPC server without using launchd, or by using launchd but without registering it as an actual service?
- Is this a use case where using a unix domain socket would be better (albeit i feel like securing the socket between the privileged / unprivileged process would be ... fun).
Additional / somewhat unrelated questions:
- is it possible for me to somehow restrict another process from chatting with service B over XPC (restrict to my other desktop app)?
- This is an app bundle question, so very unrelated: The service "app" that contains services A and B is in /Library, with the plist pointing to A, but B resides in Contents/MacOS next to A. Should this be split out into its own app bundle under Frameworks, or is this fine?
OK, a lot of questions here. Let’s start with the ancillary ones, because they have direct answers.
With XPC, yes. See Validating Signature Of XPC Process.
Note You can find a link to this and a bunch of other helpful XPC stuff in XPC Resources.
I’m not sure I fully understand your layout here. I suspect you have this:
/Library
A.app/
Contents/
Info.plist
MacOS/
A
B
If so, that’s fine. In this layout, A
is the main executable for A.app
and B
is a ‘helper tool’. The location it’s in, Contents/MacOS
, is one of the standard locations appropriate for helper tools, per the advice in Placing Content in a Bundle.
The only reason you might need to be B
into a bundle is if it needs a restricted entitlement. See Signing a daemon with a restricted entitlement. But you still don’t need to mess around with a framework. Rather, you can do this:
/Library
A.app/
Contents/
Info.plist
MacOS/
A
B.app
Contents/
Info.plist
MacOS/
B
That is also following the rules.
That’s not going to work. To listen on a named XPC endpoint, you must be started by launchd
with a configuration that includes that endpoint name. I talk about this in a lot more detail in XPC and App-to-App Communication.
You could do that as follows:
-
Have your development daemon launch on demand. This is the default, but my experience is that lots of folks disable launch on demand by setting
RunAtLoad
or via theKeepAlive
property. -
Use a different endpoint name. That ensures that your production client won’t connect to development daemon, and vice versa.
Another possibility is to run your development version as a launchd
agent, but that’ll only work if dosen’t require root privileges.
Oh, there’s one final option I can think of: Submit your daemon to launchd
directly, using SMJobSubmit
. See BSD Privilege Escalation on macOS for links to docs.
I’m not a fan of this approach in general — not least because that API is deprecated — but it’s not too bad in this case because you’re only using it during development.
Unix domain sockets aren’t a terrible idea, but they’re substantially less flexible than XPC [1]. So, it kinda depends on whether you need that flexibility.
Yep. My go-to tool for that is LOCAL_PEERTOKEN
(see this thread) but it’s not as secure as XPC. Specifically, it tells you who created the socket, not who sent the message, and it’s the latter that really matters.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] And probably slower too but, as with anything related to performance, the devil is in the details.