Apps you run have the user’s level of privileges, and must never be run as root. That poses a problem if that app needs to be able to do something requiring elevated privileges, such as those of the root user. The best solution to that is for the code that needs to be run as root to do so inside a completely separate process, a helper, specially created for that purpose. That in turn requires the app and its helper to communicate, so the app can pass its requests, and receive the responses when the helper has performed them – and that’s what XPC does.
XPC stands for cross-Process Communication, and is based on Inter-Process Communication (ICP), part of macOS’s Mach inheritance. XPC works between a client, often an app although it could be any suitable binary executable like a command tool, and a service. Services are run by launchd, either according to a property list for a Launch Agent or Launch Daemon, or when requested by the client. While some services are helpers that work at higher privileges, most perform all sorts of other tasks that are more conveniently provided as a service.
The client and service communicate by sending one another data structured in dictionaries. Typically, the client starts with a request, perhaps for the service to obtain some information that’s only accessible from a process with elevated privileges or with certain entitlements. The service might call a macOS API to fetch that, then returns the result to the client.
The traditional arrangement of components put property lists for Launch Agents and Launch Daemons into their respective folders in /Library or ~/Library, but those are now most likely to remain inside the app bundle. That not only makes apps more self-contained and easier to handle, but it keeps those property lists within the protection of the whole app. Other XPC services for an app are kept within the app bundle in a dedicated XPCServices folder.
macOS provides its own XPC services that are extensively used by all apps. A fine example is the Open and Save panel used by the great majority of those that handle documents. That’s run as a separate XPC process, transparently to the app’s code. The client app passes it the specifications to use in the panel such as the type of document, and the Open and Save Panel Service returns the URL of the file selected by the user in that dialog. You can see that in the list of processes in Activity Monitor, where all active panels will be listed as Open and Save Panel Service followed by the name of the client.
Services accessed using XPC inevitably run asynchronously with the client app, and on Apple silicon Macs can be run in the background on their Efficiency cores. This makes them ideal for maintenance and other tasks that are likely to be time-consuming. macOS has a whole scheduling and dispatch system built around XPC, in Duet Activity Scheduler and Centralised Task Scheduling, DAS-CTS, which manages Time Machine backups and over 500 other activities.
This is all relatively straightforward in a world you can trust, but many XPC services are ripe for exploitation. Consider a service that has privileged access to passwords or other sensitive information: wouldn’t that be an ideal target for stealer malware to abuse? As a result, good XPC services and their communications may need to go to lengths to ensure that they’re not abused, and only work with their intended client(s). For this, the service must verify the identity of the client, check its code signature is valid, that it’s using the hardened runtime, and doesn’t have entitlements that could bypass that, before accepting any XPC request. Plenty of apps have been found to fall short of being suitably robust.
XPC and the services it connects are at the heart of macOS.
Further reading
XPC framework, Apple
XPC Programming on macOS, Karol Mazurek (Medium)
Abusing & Securing XPC in macOS apps, slides from a presentation at OBTSv3 by Wojciech Reguła
