Choosing between xpc or an af_unix / domain socket

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:

  1. Is it possible to start the XPC server without using launchd, or by using launchd but without registering it as an actual service?
  2. 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?
Answered by DTS Engineer in 826666022

OK, a lot of questions here. Let’s start with the ancillary ones, because they have direct answers.

Written by dabdine in 775267021
is it possible for me to somehow restrict another process from chatting with service B over XPC (restrict to my other desktop app)?

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.

Written by dabdine in 775267021
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?

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.


Written by dabdine in 775267021
then execute the binaries directly.

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.

Written by dabdine in 775267021
I would like to avoid having development versions running on startup

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 the KeepAlive 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.

Written by dabdine in 775267021
Is this a use case where using a unix domain socket would be better

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.

Written by dabdine in 775267021
albeit i feel like securing the socket … would be … fun

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.

Accepted Answer

OK, a lot of questions here. Let’s start with the ancillary ones, because they have direct answers.

Written by dabdine in 775267021
is it possible for me to somehow restrict another process from chatting with service B over XPC (restrict to my other desktop app)?

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.

Written by dabdine in 775267021
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?

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.


Written by dabdine in 775267021
then execute the binaries directly.

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.

Written by dabdine in 775267021
I would like to avoid having development versions running on startup

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 the KeepAlive 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.

Written by dabdine in 775267021
Is this a use case where using a unix domain socket would be better

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.

Written by dabdine in 775267021
albeit i feel like securing the socket … would be … fun

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.

With XPC, yes. See Validating Signature Of XPC Process.

Ok perfect, this is exactly what I was looking for. Thanks!

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.

Spot on. I actually have services A, B and C (I omitted info about C because it wasn't extremely relevant, but I'll bring it up here to relate it to what you're saying). C does have a restricted entitlement (endpoint security), so I placed it into an app bundle under Contents/Frameworks. Sounds like I can move it under Contents/MacOS, and have A, B, and C all under there (with C being a bundle). Also sounds like it won't really make a difference at all if I don't (unsure if it being under Frameworks has any unforseen consequences but sounds like it would not, other than just being incorrectly classified in app bundle ontology).

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.

Yeah this makes sense. I had briefly thought to do this, but wasn't sure if there was a different way that may be easier.

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 the KeepAlive property.

Ah perfect, yeah I have RunOnLoad set in the launchd config (doh). This makes sense. I think i'll try to use this approach over SMJobSubmit as it's a bit less effort to maintain config for our local development environment than an entirely new branch of code.

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.

Yeah I'll stick with XPC. I have it working (other than getting signing authN up).

Thank you! D

Written by dabdine in 826715022
Also sounds like it won't really make a difference at all if I don't

Probably not. When setting up your bundle it’s critical to put code in locations reserved for code and data in locations reserved for data. The actual type of code doesn’t matter too much.

However, as it’s easy to do the right thing then, yeah, you should do that. To quote the Important callout at the top of Placing Content in a Bundle:

If you put content in the wrong location, you may encounter hard-to-debug code signing and distribution problems.

No truer words have ever been included in our docs |-:

Share and Enjoy

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

Choosing between xpc or an af_unix / domain socket
 
 
Q