Copying my comment from the earlier submission that didn't gain much traction here:<p>What an absolutely amazing tour-de-force of a devastating design flaw in all versions of macOS and iOS and tvOS and watchOS!<p>The negotiations detailed in the bug report timeline about meetings between "senior apple and google leadership" for keeping this secret past the general deadline really underlines that.
Ever since installing 10.12.1, I've been having a bunch of processes randomly entering a quasi-paused SIGSTOP-ish state (neither closable, apps not "bouncing" (loading) and just not responding. Running Instruments, correlating logs and such doesn't identify any clear cause. I'm having to `sudo kill -CONT -1` in order to get things moving again. I'm wondering if it's related to XNU mitigations or just some spurious "system configuration entropy" on my box.
I'm still with 10.11. I don't plan to update soon, since the benefit of Siri, Photos and the other major features is quite small, compared to the risk that I might loose working days if something goes wrong ( I'm a freelancer ).<p>As far as I read in the article there will be 10.12.1 ( the final fix ) which will have that part of the kernel refactored. I hope Apple will also support 10.11 and issue an update with the same fix.
I would argue that the original underlying problem here is the idea that having execve() increase privilege is acceptable. It's necessary for legacy reasons (sudo, anyone?), but even then, it's barely necessary. "sudo foo" could be implemented by asking a privileged daemon to run foo and handing off access to the console to the daemon.<p>On Linux, you can do PR_SET_NO_NEW_PRIVS to turn off this type of privilege gain, and it's even required for certain purposes. I would <i>love</i> to see someone develop a distribution that enables no_new_privs for all processes.
Bug 1: Many XNU drivers save task_t's on the heap without bumping their refcount.<p><pre><code> 1. Attacker creates process A and B
2. B->A send task port Bt
3. A->XNU request IOKit framebuffer client for Bt
4. A ditches Bt, retains client
5. Kill B; Bt in client now dangling
6. Trigger creation of privileged C, unrelated to A & B
7. C inherits memory once used by Bt
8. A use retained framebuffer client to write C's memory
</code></pre>
What's important to understand is that this is not just a single UAF, but a pattern of UAFs scattered throughout XNU.<p>Fix: at step 3, check to make sure the task being given to IOKit is owned by the task making the IOKit request.<p>Bug 2: IOKit drivers cache task details on their stack; the lifetime of that cached task is the lifetime of the IOKit kernel object, not of the program that made the request. In particular: if you execve() an SUID, the task_t is repurposed.<p><pre><code> 1. Attacker creates process A and B
2. B->XNU request IOKit framebuffer for Bt, Bc
3. B->A send client Bc
4. B execve /bin/su. B is now running as root.
5. A use retained framebuffer client to write B's memory
</code></pre>
The tricky thing here is that this isn't just one bug, but a pattern of bugs: every place where a driver stashes a task_t on the heap and exposes functionality through a passable object is a place where colluding processes can potentially take advantage of SUIDs to raise privileges.<p>Fix: Lifetime of IOKit clients now tied to lifetime of creating process.<p>Bug 3: Even if a driver doesn't save a task_t on the heap, they're saved on the stack during the servicing of system calls and kernel mach message handlers, so there are race conditions.<p><pre><code> 1. Attacker creates process A and B
2. B->A send task port Bt
3a. A->XNU task_threads(Bt), retrieving thread ports for Bt
3b. (simultaneously) execve /bin/su. B is now running as root.
4a. task_threads converts Bt to a task_t
4b. execve modifies the same task_t to replace thread ports
4c. task_threads retrieves the (now privileged) thread ports.
5. A uses thread ports to overwrite registers and take control of B.
</code></pre>
Fix: Kernel objects now check to see if a task_t has been touched by execve before returning them to userland. Even if you win the race, that failsafe prevents the kernel from giving you privileged objects.<p>Bug 4: You don't need the kernel to give you a privileged object directly; all you need is to be able to influence a privileged object.<p><pre><code> 1. Attacker creates process A and B
2. B->A send task port Bt
3a. A->XNU task_set_exception_port(Bt), wiring A to B's exceptions
3b. (simultaneously) execve /bin/su with rlimited stack. B is now running as root, briefly.
4a. task_set_exception_port converts Bt to a task_t
4b. execve modifies the same task_t to replace thread ports
4c. task_set_exception_port rewrites the exception port.
5. stack access in B, running /bin/su as root, causes a SEGV
6. XNU generates an exception message, passing with it the thread ports, to A
7. A uses thread ports to overwrite registers and take control of B.
</code></pre>
Fix: table flip. Rewrite execve so it generates entirely new task_ts when loading binaries, rather than repurposing old task_t.<p>This is all pretty magnificent. What's best about it is that it totally justifies the title of the post: pretty much every place in XNU where they save a task_t creates a TOCTTOU bug.