Setting up mach ports of launch daemon children

Hi,

In our system we have a launch daemon that manages a child process that performs networking. We also have launch agents that perform GUI work in each user session. We'd like those agents to connect to the child process of the launch daemon via XPC. In the launchd.plist we can expose a MachServices port for the launch daemon itself but how do we expose a port for the child process? Is it possible for the Launch daemon to smuggle a declared port to its child process?

Thanks!

Johan

Is it possible for the launchd daemon to smuggle a declared port to its child process?

No ‘smuggling’ is necessary here (-: [1] The child processes of your launchd daemon can ‘see’ XPC services published by the daemon, so they can check in with the daemon. Likewise for your launchd agents. If you want to set up a connection directly between a child process and an agent:

  1. Have the child process create an anonymous listener and get the endpoint from that.

  2. Then have the child process pass that to the daemon.

  3. And have the agent get it from the daemon.

  4. The agent can then start a connection directly to that endpoint.

If you’re using NSXPCConnection, here’s some doc links:

The same basic technique works with the low-level C API.

Share and Enjoy

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

[1] Actually, there are other sneaky ways to achieve this goal, ones that feel much more like smuggling, but you might as well follow the easy path.

Thanks for the info!

I've got some follow up questions:

  1. I already have a TLS connection between the child process and the daemon. Can I serialize the anonymous listener endpoint and funnel it through that? Or does it need to pass through an XPC connection in order for the right sharing blessings to occur in the kernel?

  2. I'm having trouble understanding the security model for XPC. All of my executables are in the same bundle (hardened runtime, signed deep etc) and I intend only those within the bundle to communicate with each other. When exposing mach services via launchd.plist, is this the default behavior or can any executable outside of the bundle connect as well? I've seen people validate code signatures etc in the shouldAcceptNewConnection method in the listener.

  3. In the spirit of [1], is it possible for me to use my existing TLS connection between the child process and the daemon to smuggle the metadata and then open a listener in the child process? Or would that invariably require XPC communication?

I could also remove the child process and have it spawned via launchd and instead. If I do that, I'd have to configure it as Disabled and manually launch it using the daemon. I need the lifecycle of the child process to be tied to the lifecycle of the daemon.

  1. I already have a TLS connection between the child process and the daemon. Can I serialize the anonymous listener endpoint and funnel it through that?

Presuming that you mean TLS as in Transport Layer Security, then the answer is “No.”

  1. I'm having trouble understanding the security model for XPC. All of my executables are in the same bundle (hardened runtime, signed deep etc) and I intend only those within the bundle to communicate with each other. When exposing mach services via launchd.plist, is this the default behavior or can any executable outside of the bundle connect as well?

Yes. There are two ways to access an XPC listener:

  • Passing listener endpoints over an XPC connection, as I described in my previous post

  • Looking them up by name

The second option is the interesting one, because it’s how you ‘bootstrap’. macOS has a hierarchy of Mach bootstrap namespaces, as described in Technote 2083 Daemons and Agents. If you create a launchd daemon then its service is registered in the global namespace, where any code on the system can access it [1]. If you want to restrict your clients, you’ll need to add code to do that.

This has been a persistent pain point for third-party XPC services. We’ve made some progress in recent releases — see Validating Signature Of XPC Process for more — but, as of macOS 12, the best solution is only available to folks using the XPC C API )-:

  1. In the spirit of [1], is it possible for me to use my existing TLS connection between the child process and the daemon to smuggle the metadata and then open a listener in the child process?

I’m not sure how this question differs from question 1.

I could also remove the child process and have it spawned via launchd and instead.

OK. That’s a viable solution if there’s only a single child process of any given type. Your thread title says “launch daemon children”, so I assumed that you wanted multiple child processes of the same type. If there’s only one child per-type, managing them via launchd is fine.

If I do that, I'd have to configure it as Disabled and manually launch it using the daemon.

Do not do that. If you’re going to have launchd manage the job, you should let it do its thing. That is, the job should start on demand.

I need the lifecycle of the child process to be tied to the lifecycle of the daemon.

Can you explain more about this requirement?

Share and Enjoy

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

[1] Assuming it’s not blocked by the sandbox.

Thank you for the insightful answers!

Since the child process needs to be an XPC listener (in my case to pass things like IOSurfaces from gui agents), I will rewrite the architecture for our processes. I could pass an anonymous listener via the daemon acting as a relay, but that would mean that my gui agents would have to connect to the daemon which is not preferred. The daemon's functionality is very limited and simple and I would like complexity to be low there. The child process is very complex and is expected to crash, hang and restart sporadically.

This has been a persistent pain point for third-party XPC services. We’ve made some progress in recent releases — see Validating Signature Of XPC Process for more — but, as of macOS 12, the best solution is only available to folks using the XPC C API )-:

Thank you. I am using the C api of XPC, so this should work for me. It would've been great if launchd.plist could define this requirement as an option. I think most applications would want this out of the box. For other platforms, we spawn the child process with TLS credentials and we manage authentication between processes using private/public key certificates.

Yes, there's only one child, so having a separate launchd.plist definition is the proper way of implementing this. The structure is: system namespace: 1 daemon, 1 child process
gui namespace: one agent per user

Can you explain more about this requirement?

The reason for this requirement is primarily due to support of other platforms. Whenever I break an abstraction, I have to make sure that the new abstraction works in all domains. It currently performs tasks usually done by launchd, so transferring the lifecycle managerial reins to launchd makes sense. The daemon updates the bundle and acts as a watchdog for the child process. The executable for the child process does not always exist on disk though, so its creation is deferred until one or more updates have been performed. I can solve this by using a file watch in the launchd.plist for the child process.

It would've been great if launchd.plist could define this requirement as an option. I think most applications would want this out of the box.

Actually, it depends. Some folks combine a launchd daemon or agent with a client framework that can be linked into many different apps, and thus it accepts connections from anyone.

Regardless, I agree that having this option available to third-parties would be cool [1], and I’d appreciate you filing an enhancement request along those lines. Please post your bug number, just for the record.

The executable for the child process does not always exist on disk though

If you’re writing executables to disk, and especially if you’re updating them on disk, be aware of the gotcha described in Updating Mac Software.

Share and Enjoy

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

[1] If you rummage through the built-in launchd property lists you’ll see that many of them use a private version of this feature. Don’t try to use that in your product though!

Thank you Eskimo!

I have filed an enhancement request with the following bug number: FB9794587

Setting up mach ports of launch daemon children
 
 
Q