Alternative to auditToken
Kentzo opened this issue · 8 comments
Earlier this month an Apple employee posted an update to ADC where he suggested an alternative to using the audit token:
You can use the public API to get the process ID from the connection (processIdentifier or xpc_connection_get_pid) and create your code object from that using kSecGuestAttributePid. In this case, design your IPC protocol to not accept any security-critical requests as the first message on the connection.
The idea being only the 1st message over the connection is vulnerable.
What's your opinion on that?
IMO it's a bad idea to use the PID because an attacker can fork a lot of times, enqueue a lot of XPC messages and then replace the image to the right one. (The attacker doesn't have to wrap PIDs)
Let's take a look at the following example:
XPC Messages Queue | Connection 1: Message 1 | Connection 1: Message 2 | Connection n-1: Message 1 | Connection n-1: Message 2 | Connection n: Message 1 | Connection n: Message 2 |
---|---|---|---|---|---|---|
Validation | Failed | Failed | Failed/Passed | Failed/Passed | Passed | Passed |
Even if the security-critical request
is sent in the 2nd message, the attacker will still be able to win the race.
I talked with a guy from Apple Security about the non-public audit_token APIs. We have to wait for them. 😊 But until Apple releases public APIs, I'd recommend using the private ones...
As I expressed in that thread, I'm completely puzzled in what "reliance on the n-the message being from the original connection" has to do with a race for the PID at all.
Do you see how "validity" of an XPC connection can help mitigate the attack?
Probably the Apple's employee assumed that the attacker will not be able to enqueue more than 1 message in the same connection. So the assumption was that in the 2nd message you will make another validation that will prove that the client's process is signed with another certificate (so you will invalidate the whole connection).
Is it something that was proved to be wrong? Assuming the OS enqueues exactly 1 messages for user's handler and validates the XPC connection by checking whether the peer is still alive, it should be sufficient.
I realize that in the example above you implied that no such validation happens, but is it just a theory?
Assuming the OS enqueues exactly 1 messages
AFAIK if you make a lot of XPC connections the OS will enqueue more than 1 message in 1 connection (if you of course send more messages)
checking whether the peer is still alive
The peer will be still alive always in that attack. I'm considering the case where the peer uses the posix_spawn()
with POSIX_SPAWN_SETEXEC
flag instead of the PID wrapping
(dying and getting a PID of the died process)
You can read more about this attack in my post
AFAIK if you make a lot of XPC connections the OS will enqueue more than 1 message in 1 connection (if you of course send more messages)
I'm not aware of the internals, but I assume there are at least 2 queues involved:
- Queue in front of OS XPC processing facilities
- Queue in front of user's XPC event handler
The peer will be still alive always in that attack. I'm considering the case where the peer uses the posix_spawn() with POSIX_SPAWN_SETEXEC flag instead of the PID wrapping (dying and getting a PID of the died process)
Wouldn't it invalidate connection preventing the OS from delivering the 2nd message?
I guess the only way to find out is to conduct a test to see whether it's possible to deliver 2 message with originating peer dying somewhere before the 1st message is received by user's XPC event handler.
I found this issue accidentally in a Google search (I have an XPC-based privileged helper, ergo have similar concerns), ad am happy to share two recent-ish developments:
- In macOS 11.0,
SecCodeCreateWithXPCMessage
was introduced which can be used to validate code-signing requirements of the sender of a specific inbound XPC message; internally, it uses the connection's audit token. - in macOS 12.0, the XPC C API gained a cool function
xpc_connection_set_peer_code_signing_requirement
which takes a code-signing requirement string, applies it to a connection, and validates every incoming message against that requirement.
Hopefully this proves useful here.
That's great!
I was looking for some sample code for these functions and found a discussion on Apple's Developer Forums