root@themittenmac:~/blog$ cat process-coalitions-in-the-xnu-kernel.md

Process Coalitions in the XNU Kernel

Estimating Process Responsibility Without Root
Published Oct 20 2026 by jaron.bradley — macOS Internals / TrueTree
root@themittenmac: ~/blog/process-coalitions-in-the-xnu-kernel
root@themittenmac:~/blog$ less process-coalitions-in-the-xnu-kernel.md

Introduction

I’ve wanted to revisit TrueTree for a while, particularly to remove its requirement for root privileges when building an enhanced process tree. Some fellow researchers have worked around this by configuring TrueTree as a root-owned, setuid executable. That allows an unprivileged user to launch it, but the tool still runs with elevated privileges. It also requires privileged setup on the target system.

While TrueTree is useful for research, I originally created it with incident response in mind. During an investigation, you may have access to a system without having root privileges, or the ability to arrange privileged setup beforehand. I wanted to explore how much of TrueTree’s enhanced process tree I could build using only the permissions already available to the investigating user.

The Problem

Most of the information TrueTree uses to build its enhanced process tree can be collected without root privileges. The challenge is collecting responsible-process information across the system.

TrueTree’s original implementation queried this information through responsibility_get_pid_responsible_for_pid, a private API exposed by libquarantine.dylib. An unprivileged caller can retrieve responsibility information for some processes, but permission restrictions can prevent lookups for processes belonging to other users. The goal is therefore not simply to make TrueTree run without root, but to preserve useful process relationships when those lookups are unavailable.

Coalitions

Coalitions are kernel-level objects that group processes for resource accounting and management. It's easiest to think of them as a way for the operating system to track groups of processes performing related work, rather than treating each process in isolation.

The coalition system-call interfaces are implemented in bsd/kern/sys_coalition.c, while the core implementation lives in osfmk/kern/coalition.c. The associated definitions and data structures can be found in osfmk/mach/coalition.h.

That header defines two coalition types:

#define COALITION_TYPE_RESOURCE  (0)
#define COALITION_TYPE_JETSAM    (1)

These coalitions primarily support resource accounting and management, allowing the operating system to track resource usage and apply policies to groups of related processes.

Jetsam Coalitions

While discussing possible sources of responsible-process information with AI, I was pointed toward jetsam events. Jetsam allows the operating system to terminate selected processes to reclaim memory under pressure. Processes can also be terminated for exceeding their individual memory limits. Apple has one writeup on these events in its Xcode documentation on identifying high memory use.

Jetsam coalitions group related processes, including applications and their supporting services or extensions. These relationships can influence termination order: XNU includes logic that orders coalition members by their roles within a jetsam priority band, placing the coalition leader after its supporting processes. You can see this behavior in kern_memorystatus.c.

That grouping made jetsam coalitions an interesting candidate for approximating responsible-process relationships. However, belonging to the same coalition does not necessarily establish which process macOS considers responsible for another.

On my system running macOS 26, my coalition-based approach matched the responsible PID returned by the original approach (responsibility_get_pid_responsible_for_pid) for 259 of the 285 processes on my system.

That was promising, but the remaining mismatches meant it was not a complete replacement.

Resource Coalitions

Resource coalitions provide broader resource accounting for groups of related processes. They track CPU usage, but XNU’s coalition_resource_usage structure also includes wakeups, disk I/O, GPU time, energy, and physical memory footprint.

Public documentation is limited, but Apple briefly describes process coalitions in its crash-report documentation. Coalition-level accounting is also exposed through the lesser-known --show-process-coalition option in the powermetrics man page:

sudo powermetrics --samplers tasks --show-process-coalition -n 1
powermetrics output grouping macOS processes by coalition

This command groups processes by coalition and displays their aggregate activity. It is useful for inspecting coalition relationships, but requires elevated privileges to run.

When I examined resource-coalition membership on my macOS 26 system, selecting the earliest member of each coalition produced a much closer match to the responsible-process information returned by responsibility_get_pid_responsible_for_pid. This approach matched for 284 of the 285 processes I compared, approximately 99.6% agreement in that sample.

The only mismatch involved an XPC service named com.apple.dock.extra. The coalition-based approach grouped it under the Dock, while the responsibility API reported launchd. For the purpose of visualizing related activity, that grouping arguably provides more useful context, even though it differs from the reported responsible PID.

Rather than directly querying the responsible PID, TrueTree now uses a two-step inference: identify a process’s resource coalition, then select the earliest-started member of that coalition as the candidate responsible process. Earliest-started means the process with the oldest start timestamp among the coalition members successfully identified in the current process-table snapshot.

Running TrueTree As Basic User vs Root

When executed without root permissions, TrueTree will build the process tree using information gleaned from coalition data.

Interestingly, XNU explicitly allows coalition-membership queries without requiring the caller to own the target process or have root privileges. This means an ordinary user can query coalition IDs for processes belonging to other users, including root, provided no additional security policy blocks the request. This exception is visible in the permission-checking code for PROC_PIDCOALITIONINFO.

Coalition membership is available through libproc, the same library TrueTree already used to collect process information such as parent PIDs, start times, and executable paths. The coalition query uses the familiar proc_pidinfo() function, but requests a different kind of information through the private PROC_PIDCOALITIONINFO request type. This logic applied in Swift code can be seen in TrueTree's responsibility.swift.

This does not expose the responsible PID directly. Instead, TrueTree combines coalition membership with process start times to infer a likely responsible process. The interesting part is that these accessible pieces of metadata can approximate a relationship whose direct lookup is more restricted.

When running TrueTree with root permissions, TrueTree will use the approach that it has always used as I consider the coalition approach to be a bit more fickle and don't want to break something that isn't broken. If you'd like to run TrueTree with root permissions and also use the new approach of gathering process coalitions you can use the --acquire-responsible-via-coalition when running with root.

Note that running as root gains you additional benefits such as collecting all network connections for processes versus ones just owned by the user. There may be other similar small differences as well.

← back to blog