I’m designing a least-privilege system LaunchDaemon registered with SMAppService, and I’d like to clarify whether the following architecture is supported by public macOS contracts.
The LaunchDaemon declares a MachServices entry. Its steady-state service must run as a dedicated non-root account and later creates an NSXPCListener for that Mach service.
We currently launch the daemon directly using UserName, GroupName, and InitGroups=false.
However, InitGroups=false does not appear to guarantee that the resulting process supplementary-group list is limited to the service’s intended group. In testing, the daemon received a supplementary group outside our accepted set.
We therefore do not want to depend on incidental inherited launch-time group state.
We are considering this alternative:
launchd starts a small, fixed, code-signed bootstrap executable as root.
The bootstrap reads the target UID/GID from an existing protected root-owned binding record.
It establishes an exact credential state using public BSD APIs, conceptually:
setgroups(...)
setgid(...)
setuid(...)
It verifies the resulting non-root credentials.
It creates no XPC listener or storage connection while privileged.
Without forking, it permanently replaces itself using execve() (or possibly POSIX_SPAWN_SETEXEC) with another fixed, separately signed executable in the same bundle.
That non-root executable independently validates its security state and then creates NSXPCListener(machServiceName:) for the Mach service declared by the original LaunchDaemon job.
The bootstrap would not remain as a privileged parent or supervisor.
My main questions are:
Is a same-PID exec after permanent UID/GID/supplementary-group reduction supported for an SMAppService system LaunchDaemon before it checks in to its declared Mach service?
Does the exec-replaced process retain the launchd/bootstrap context required for NSXPCListener(machServiceName:) to check in to that Mach service?
If so, what execution context must be preserved across exec (for example bootstrap context, environment, file descriptors, or Mach rights)?
Is there a documented way to preserve only the context required for the LaunchDaemon/Mach-service relationship without carrying unintended root-derived capabilities into the non-root executable?
Would SMAppService.unregister() / normal launchd termination continue to treat the exec-replaced process as the same LaunchDaemon job?
If this topology is not supported, is there an Apple-supported way to establish an exact supplementary-group set before a non-root SMAppService LaunchDaemon begins handling its Mach service?
The goal is to avoid relying on undocumented launchd behavior, incidental supplementary groups, private APIs, or a long-lived privileged helper.
I’m specifically looking for the supported contract here rather than whether this happens to work on a particular macOS release.
0
0
20