XPC between sandboxed launchd helper and non-sandboxed daemon

We have a daemon that is launched by launchd, always running in the background and running as root. The daemon is installed to

/Library/LaunchDaemons.


To reduce its attack surface we want to move some functionality into another helper process. It doesn't have to be running as root, but since the client (the LaunchDaemon) is running as root and in the launchd context, we created another LaunchDaemon that is launched on-demand and uses the MachService key to advertise its Mach service. It is also installed to

/Library/LaunchDaemons.


The sandboxed daemon has little functionality in it, and its entitlements are just com.apple.security.app-sandbox. We use NSXPC to communicate between the the non-sandboxed daemon and the sandboxed daemon. The sandboxed helper daemon launches as expected.


However the sandboxed application exits immediately on 10.14 with the following crash:


Crashed Thread:        0  Dispatch queue: com.apple.main-thread


Exception Type:        EXC_BAD_INSTRUCTION (SIGILL)
Exception Codes:       0x0000000000000001, 0x0000000000000000
Exception Note:        EXC_CORPSE_NOTIFY


Termination Signal:    Illegal instruction: 4
Termination Reason:    Namespace SIGNAL, Code 0x4
Terminating Process:   exc handler [1721]


External Modification Warnings:
Debugger attached to process.


Application Specific Information:
dyld: launch, running initializers
/usr/lib/libSystem.B.dylib
Sandbox registration internal error: Incoming message euid:1 does not match secinitd uid:0.


Application Specific Signatures:
Internal error: Incoming message euid:1 does not match secinitd uid:0.


Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libsystem_secinit.dylib        0x00007fff5b6e6b2a _libsecinit_setup_secinitd_client + 1929
1   libsystem_secinit.dylib        0x00007fff5b6e6340 _libsecinit_initialize_once + 13
2   libdispatch.dylib              0x00007fff5b49e63d _dispatch_client_callout + 8
3   libdispatch.dylib              0x00007fff5b49fd4c _dispatch_once_callout + 20
4   libsystem_secinit.dylib        0x00007fff5b6e6331 _libsecinit_initializer + 79
5   libSystem.B.dylib              0x00007fff582b09d4 libSystem_initializer + 136
6   dyld                          0x0000000108408592 ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 506


If we remove the entielements from the sandboxed helper, thus making it non-sandboxed, it works fine but this is obviously not the intent.


Any ideas?

Replies

The App Sandbox is… well… an app sandbox. It’s designed for user programs (app, app extensions, XPC Services) not for daemons. You may be able to get it to work in that context, but it’s not a well-trodden path (most Apple daemons use their own custom sandbox profile).

Internal error: Incoming message euid:1 does not match secinitd uid:0.

This message is generated by the

secinitd
(see its man page) when an App Sandbox client’s UID doesn’t match the session for which that instance of
secinitd
was started.

It doesn't have to be running as root, but since the client (the LaunchDaemon) is running as root and in the launchd context, we created another LaunchDaemon that is launched on-demand and uses the

MachService
key to advertise its Mach service.

What UID does this helper run as? The error message you’re seeing indicates that you’ve configured the helper to run as the

daemon
user (UID 1)?

Share and Enjoy

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

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

Unfortunately the `sandbox_init` family of APIs are deprecated, otherwise I'd have take that route.


What UID does this helper run as? The error message you’re seeing indicates that you’ve configured the helper to run as the
daemon
user (UID 1)?

The helper does not set any specific UID in its LaunchDaemon.plist, and on the filesystem it's owned by root:admin, so presumably it is spawned as root. Maybe xpcproxy is UID 1?


Not necessarily what we want, but since its parent is root and launched by launchd, it needs to be able to lookup the Mach service for the helper so making the helper a LaunchDaemon was easiest. Making it a LaunchAgent would make it very hard for it to be launched on-demand by the root LaunchDaemon.

Unfortunately the

sandbox_init
family of APIs are deprecated, otherwise I'd have take that route.

Even if they weren’t, it wouldn’t help you because the format of the file you pass in has never been documented for third-party use )-:

Making it a LaunchAgent would make it very hard for it to be launched on-demand by the root LaunchDaemon.

Right. And that’s not the only problem. Another issue is that an agent has to run in a per-user session of some form, and there can be multiple sessions active at a time, making it hard for the daemon to know what session to target.

It also introduces the possibility of a security inversion, where the user who owns the session can affect the behaviour of the daemon indirectly, by fiddling with the agent.

The helper does not set any specific UID in its

LaunchDaemon.plist
, and on the filesystem it’s owned by
root:admin
, so presumably it is spawned as root.

OK.

Maybe

xpcproxy
is UID 1?

I don’t think so. Hmmm, that’s a bit of a mystery really.

Regardless, I don’t think this is a viable path. As I mentioned earlier, the App Sandbox just wasn’t designed for daemons. Moreover, if you run your helper as root, you’re extended a bunch of privileges to it than significantly undermine any extra security you might get from sandboxing it.

Share and Enjoy

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

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