The root prerequisite

A TOCTOU race in XNU's exec_activate_image() around stale ip_origvattr. The race requires root to set up. If you already have root, the finding gives you nothing. Apple's closure is correct.


A race condition that requires root to set up and, when won, gives the winner root-equivalent capability is not an elevation of privilege. It is a tautology dressed as a vulnerability. This post is about finding a genuine pattern in the XNU kernel's exec path, understanding exactly why it doesn't matter, and coming to terms with the fact that Apple's closure was not only correct but was the only reasonable outcome.

Situation

In 1994, in my first weeks at University College Scarborough, I found a SUID binary on the university's Unix systems. I knew what SUID meant — the Set User ID bit causes an executable to run with the permissions of its owner rather than the caller — but I had not previously thought hard about what it implied for the operating system's trust model. If the kernel is going to honour those bits, it has to be right about what it is executing and who set the bits. That realisation — that the kernel's exec path is a site of significant trust — took root then and has informed a lot of my subsequent work.

Thirty years later, reading through bsd/kern/kern_exec.c in the XNU source, I noticed something that brought that 1994 moment back.

Investigation

XNU's exec_activate_image() is the function responsible for loading and activating an executable image during an execve(2) call. Early in its execution, it fetches the vnode attributes for the target file — including the SUID and SGID permission bits — and caches them in a structure called ip_origvattr. These cached attributes are later consulted when the kernel decides whether to honour the SUID bit and elevate the effective UID of the new process.

Technical finding — SUID-TOCTOU (as submitted)

In XNU's exec_activate_image() at bsd/kern/kern_exec.c, vnode attributes (including SUID/SGID bits) are fetched and cached in ip_origvattr early in the exec path. Between this attribute fetch and the final activation of the image, a race window exists. An attacker able to atomically swap the target file — for example via a crafted mount point — between the attribute check and the exec could cause the kernel to activate a non-SUID binary while believing it carries SUID authority, or vice versa. Source reference: bsd/kern/kern_exec.cip_origvattr cached at entry, not re-validated before image activation.

The pattern is real. The stale-vattr window exists. TOCTOU vulnerabilities in exec paths have a genuine history — they are a well-understood class of kernel bug, and instances of them in other operating systems have led to exploitable local privilege escalation. The instinct to look here was not unreasonable.

Finding

Apple's response was precise: "Root prerequisite exceeds exploit; no security boundary crossed."

To exploit the race, an attacker needs to arrange an atomic file swap between the attribute fetch and the exec. On macOS, the mechanisms available for doing this — bind mounts, union mounts, or crafted volume structures — require root to set up. There is no unprivileged path to the necessary mount manipulation on a production macOS system. If you can set up the race, you already have root. If you already have root, winning the race gives you nothing you do not already have.

Why Apple closed it correctly

Reproducing the TOCTOU race in exec_activate_image() requires root-level access to configure the necessary mount point for atomic file swapping. If an attacker already has root, the race window cannot be used to cross a security boundary — it provides root-equivalent capability to someone who already has root-equivalent capability. Apple's closure reason, "root prerequisite exceeds exploit," is exactly correct.

Implication

The stale ip_origvattr pattern is architecturally interesting as a code-quality observation. In a future system where root's privileges are more tightly partitioned — where specific capabilities are separated from general root access — the same race window could become significant. On macOS as it stands today, it does not cross a security boundary.

There is also a broader point about how to evaluate TOCTOU findings before submission. A race condition in a privileged path is not automatically a privilege escalation. The question is always: what does an attacker need to enter the race, and what do they gain if they win? If the entry cost equals or exceeds the prize, the finding is closed before it begins. I did not work through that arithmetic carefully enough before submitting.

As a code-quality note

The ip_origvattr caching pattern in exec_activate_image() would benefit from re-validation immediately before image activation, independent of its current security impact. Defensive re-checks in exec paths are cheap, and the absence of one here is a pattern worth noting for future architectural work — particularly if root's privilege surface narrows over time.

In my view, Apple's threshold for closing this was entirely reasonable. The research community has long understood that root-prerequisite bugs do not generally constitute actionable security findings on consumer operating systems, and macOS is no exception. Acknowledging this honestly is more useful than arguing at the margins.

Proof of concept

suid-toctou-concept.sh Own hardware only — requires root
#!/bin/bash
# SUID TOCTOU concept demonstration
# REQUIRES ROOT to set up the mount manipulation
# This script demonstrates why Apple closed the report:
# root is a prerequisite, and the exploit gains nothing
# that root does not already provide.
# Own hardware only. All testing on SRDP device.

if [ "$(id -u)" != "0" ]; then
    echo "This PoC requires root to set up the mount manipulation."
    echo ""
    echo "If you already have root, this race gives you nothing."
    echo "That is precisely why Apple closed the report:"
    echo "  'Root prerequisite exceeds exploit; no security boundary crossed.'"
    echo ""
    echo "Entry cost = prize. Finding closed."
    exit 1
fi

# Concept:
# 1. Create a non-SUID binary at path A.
# 2. Create a SUID binary at path B.
# 3. Use a root-controlled bind mount to swap A -> B atomically.
# 4. Time the swap to occur after exec_activate_image() caches ip_origvattr
#    (reads A's non-SUID attributes) but before image activation.
# 5. Kernel activates B (SUID) while ip_origvattr reflects A (non-SUID).
#    OR: kernel activates A (non-SUID) while ip_origvattr reflects B (SUID).
#
# Source reference: bsd/kern/kern_exec.c
# ip_origvattr is populated at exec_activate_image() entry.
# It is not re-validated before image activation.
# The race window is narrow (microseconds) and requires root to exploit.
#
# Security boundary analysis:
#   Entry cost:  root (required for mount manipulation)
#   Prize:       root-equivalent (SUID execution or SUID suppression)
#   Delta:       zero — no boundary crossed
#   Conclusion:  not an actionable vulnerability

echo "Root prerequisite confirmed."
echo "The ip_origvattr stale-vattr pattern is a code-quality observation."
echo "It is not an exploitable vulnerability on current macOS."
echo ""
echo "csrutil status:"
csrutil status

Disclosure note

Disclosure

Finding SUID-TOCTOU was submitted to Apple Product Security via the Apple Security Research portal under the standard coordinated disclosure process.

Apple's closure reason: "Root prerequisite exceeds exploit; no security boundary crossed." Apple's position is that exploiting the race in exec_activate_image() requires root, and that a root-prerequisite finding does not constitute an elevation of privilege on macOS.

In my view, Apple's assessment is correct. The stale ip_origvattr pattern is a genuine architectural observation but not a current vulnerability. This post is published as a record of the investigation and for the benefit of other researchers examining the XNU exec path.

All testing was performed on my own hardware under the Security Research Device Programme (SRDP).

Final thought

In 1994 I learned that SUID bits have to be set by something the kernel trusts. That trust runs all the way down to the exec path, to the vnode attributes, to the decision the kernel makes about who is allowed to set those bits. The interesting question is always: what does it cost to interfere with that chain? In this case, it costs root. And root, as Apple correctly observed, is already the answer.

Are you there?