Modern privileged helper tools using SMJobBless + XPC

If you’ve ever needed to run a privileged task from a Mac application, you’ll probably be familiar with Apple’s BetterAuthorizationSample & SMJobBless sample code.

Unfortunately neither project really showcases a modern, sane, secure way of adding a priveleged helper tool to your application.

BetterAuthorizationSample

With BetterAuthorizationSample (BAS), your application includes an installer tool and a helper tool within the application bundle. Your application then jumps through the following hoops to use the helper tool:

  1. Your application runs the installer tool as root via AuthorizationExecuteWithPrivileges(), specifying the helper tool as an argument.
  2. Installer tool copies the helper tool into /Library/PrivilegedHelperTools.
  3. Installer tool creates a launchd.plist file for the helper tool in /Library/LaunchDaemons. The launchd.plist file specifies the path to the tool and that it should be launched when a connection is made to a UNIX domain socket located at /var/run/<application bundle ID>.socket.
  4. Installer tool executes launchctl load -w <path to helper tool launchd.plist>.

At this point your application can connect to the socket at /var/run/<application bundle ID>.socket and start sending commands to the privileged helper tool. Your application might ask the helper tool to “open TCP port 80”, to which the helper tool might respond with “here’s the descriptor that I opened”.

Note that while the BAS sample code’s README claims that it’s “the recommended way to access privileged functionality from a non-privileged application on Mac OS X”, that’s no longer the case. AuthorizationExecuteWithPrivileges() was deprecated in in Mac OS X v10.7, and has been replaced with SMJobBless().

SMJobBless

Introduced in Mac OS X v10.6’s Service Management framework, the SMJobBless() API provides a more modern, secure replacement for the AuthorizationExecuteWithPrivileges() technique used in the BAS sample. Improvements include:

  1. The installer tool is unnecessary, so there’s fewer moving parts.
  2. The helper tool & launchd.plist are automatically copied into correct system folders and registered with launchd.
  3. A trust relationship is established between the application and helper tool via code-signing identities. This means that the helper tool can only be used by explicitely identified applications.
  4. Subsequent calls to SMJobBless() will check the version of your helper tool and automatically upgrade it if necessary.

The SMJobBless sample code does a good job of outlining registration & execution of a privileged helper tool, but unlike the BAS sample it does include any form of inter-process communication (IPC) between the application and helper tool. Most developers combine the UNIX domain socket communication code from the BAS sample with the SMJobBless sample code and call it a day.

But surely there’s a more modern IPC mechanism than defining your own custom command protocol over raw UNIX domain sockets?

XPC

Apple introduced the XPC services API in Mac OS X v10.7, providing a modern, lightweight IPC mechanism integrated with GCD and launchd. The typical way of using XPC is by embedding lightweight helper tools in your application (called “XPC services”), which perform work on behalf of your application.

Sound familiar? That’s exactly what we’re after.

So if XPC helps us create these “lightweight helper tool” bundles, can we simply run an XPC service in a privileged context? Apparently not:

Elevating a [XPC] service’s privileges to root is not supported. Further, an XPC service is private, and is available only to the main application that contains it.

So XPC services are out if your helper needs elevated privileges, but that doesn’t mean we can’t use the XPC APIs independently of XPC services.

A quick look at the XPC APIs for creating an XPC connection reveals this:

1
xpc_connection_t xpc_connection_create_mach_service(const char *name, dispatch_queue_t targetq, uint64_t flags);

By registering a Mach service (instead of a UNIX domain socket as per the BAS sample code) with launchd (see man launchd.plist(5)), we can connect to it from our application using xpc_connection_create_mach_service(). Neat!

SMJobBless + XPC

To demonstrate how this works, I’ve modified the SMJobBless sample code and added XPC-based IPC support which you can find on Github.

The initial revision is the original SMJobBless sample code, so you can see all the modifications I’ve made by looking at the commit history.

Summing Up

Neither SMJobBless or BetterAuthorizationSample provide the full picture of how you would create a privileged helper tool and communicate with it from your application. SMJobBlessXPC is a step in the right direction.

Let me know if you have any suggestions for improvements.