There's a general bug type on macOS. When a privileged (or loosely sandboxed) user space process accepts an IPC message from an unprivileged or sandboxed client, it decides whether the operation is valid by enforcing code signature (bundle id, authority or entitlements). If such security check is based on process id, it can be bypassed via pid reuse attack.
Background
An unprivileged client can send an IPC message, then spawns an entitled process to reuse current pid. The privileged service will then validate on the new process and accept the previous IPC request, leading to privilege escalation or even sandbox escape. The attacker can stably win the race by spawning multiple child processes to fill up the message queue.
Security checks based on pid, like sandbox_check and SecTaskCreateWithPID suffer from this attack.
The idea and the initial PoC was borrowed from Ian Beer:
Samuel GroΓ has also been aware of this senario:
- Don't Trust the PID! Stories of a simple logic bug and where to find it
- Pwn2Own: Safari sandbox part 2 β Wrap your way around to root
Put another way, the IPC server should never use xpc_connection_get_pid or [NSXPCConnection processIdentifier] to check the validity of incoming clients. It should use the audit_token_t instead (note: there was an exception).
Unfortunately these functions are undocumented and private:
xpc_connection_get_audit_token[NSXPCConnection auditToken]
Since, as noted, these methods are private, third-party developers are trapped in this issue repeatedly:
Apple please consider opening these functions to developers!
Oh wait. Actually audit_token_t was not so trustworthy. @5aelo has just pointed out another bug before iOS 12.2 / macOS 10.14.4: Issue 1757: XNU: pidversion increment during execve is unsafe π€¦ββ
The bug
The privileged XPC service com.apple.appleseed.fbahelperd has exported the following interface:
Look at the implementation of -[FBAPrivilegedDaemon listener:shouldAcceptNewConnection:] method. It only allows XPC messages from one client: /System/Library/CoreServices/Applications/Feedback Assistant.app/Contents/MacOS/Feedback Assistant
But since it performs the security check based on process id, we can bypass it. You can now refer to the proof of concept by Ian Beer entitlement_spoof.c or see my full exploit at the end.
The steps to trigger the race condition are as follows:
-
Create multiple client processes via
posix_spawnorNSTask(note: you can't do this on iOS). -
Avoid using
forkbecause Objective-C runtime may crash betweenforkandexec, which is required by this attack. -
On 10.13 you can add an
OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YESenvironment variable before process creation, or add a__DATA,__objc_fork_oksection to your executable as a workaround. But these workarounds are not compatible with previous macOS. For more information, please refer to Objective-C and fork() in macOS 10.13. -
Send multiple XPC messages to the server to block the message queue.
Ian Beer uses execve to replace the binary to a trusted one and write to its its buffer to prevent the new process from terminating. Instead, I chose to pass these flags POSIX_SPAWN_SETEXEC | POSIX_SPAWN_START_SUSPENDED to posix_spawn to create a suspended child process and reuse the pid of the parent.
Since the child process has been replaced, there won't be any callback. You have to use a "canary" to detect whether the race is successful based on the server's behavior, e.g., the existence of a newly created file.
From the console output, the server accepts our request:

Now the check is bypassed.
Give Me Root
Now continue code auditing on FBAPrivilegedDaemon.
The method copyLogFiles: accepts one NSDictionary argument, whose keys as the sources and the correspond NSString as destination to perform file copy. It supports multiple tasks at once, and the path can be both directory or file.
-[FBAPrivilegedDaemon copyLogFiles:]
-[FBAPrivilegedDaemon canModifyPath:]
The source must start with /Library/Logs or /var/log, and the destination must match one the following patterns:
- ^/var/folders
- ^/private/var/
- ^/tmp
- Library/Caches/com.apple.appleseed.FeedbackAssistant
It will not override an existing destination.
These constraints can be bypassed throuth path traversal. So now we can copy arbitrary file or folder to anywhere unless rootless protected.
Additionally, after each copy, it will call -[FBAPrivilegedDaemon fixPermissionsOfURL:recursively:] to set the copied files' owner to the XPC client process's gid and uid. This is extremely ideal for macOS LPE CTF challenges. I used this zero day exploit during #35C3 CTF to simply copy the flag and read it, lol.
If you don't mind reboot, getting root privilege is simple. Copy the executable to the places that will be automatically launched with privilege during startup. For example, the bundles in /Library/DirectoryServices/PlugIns will be loaded by the process /usr/libexec/dspluginhelperd, who has root privilege and is not sandboxed.
Can we have an instant trigger solution?
Since it will never override existing file, we can not:
- override administrator account's password digest (
/var/db/dslocal/nodes/Default/users) β - override suid binaries (not to mention file permission and rootless) β
- override one of the PrivilegedHelpers β
As it will fix file permissions, none of these would work:
- add sudoer β
- add an entry to
/Library/LaunchDaemonsto register a new XPC service β
We need more primitives.
The daemon has other methods named run*diagnoseWithDestination. They are various external command wrappers just like those diagnose helpers mentioned from my previous post. What's interesting is that runTMDiagnoseWithDestination: acts the same as timemachinehelper, thus we can trigger the CVE-2019-8513 command injection.
At first I was looking at runMDSDiagnoseWithDestination: , who launches /usr/bin/mddiagnose that will eventually spawn /usr/local/bin/ddt after around 10 seconds, waiting for the /usr/bin/top command to end. Remember the previous post? This location does not exist by default and we can put custom executables with the arbitrary file copy bug.
Another code path is method runMobilityReportWithDestination:. It invokes this shell script: /System/Library/Frameworks/SystemConfiguration.framework/Versions/A/Resources/get-mobility-info
The script checks the existence of /usr/local/bin/netdiagnose. If so, it executes as root. The exploit will succeed within milliseconds.
By the way, I was surprised by how many diagnostic tools depending on the non-existing directory /usr/local/bin.

The bug has been fixed in macOS 10.14.4 and iOS 12.2.
- About the security content of iOS 12.2
- About the security content of macOS Mojave 10.14.4, Security Update 2019-002 High Sierra, Security...
PoC
https://github.com/ChiChou/sploits/tree/main/macOS/CVE-2019-8565-fbahelperd
