Source is not enough
Reading XNU source at 2am and finding a bug in ETXTBSY write-count enforcement. Apple closed the first submission for lack of runtime evidence — and they were right. A note on research methodology.
There is a particular quality to the 2am kernel session that is easy to confuse with certainty. The house is quiet. The source is open in front of you. The code does what the code does, and for a few hours in the small hours you have persuaded yourself that what the code says and what the running system does are the same thing. They are not, always. That lesson cost me a filing.
This post is about an early submission to Apple's security research framework — a finding about ETXTBSY enforcement in XNU that was, as source-level analysis, correct. As a security finding, it was incomplete. Apple's response was that they needed on-device execution evidence, not a source reading. They were right to say so.
The finding (as submitted)
Component: XNU kernel, bsd/kern/kern_exec.c, execve path
Claim: The write-count check enforcing ETXTBSY (preventing writes to an executing binary) was not correctly paired in certain fat-binary re-entry paths through exec_activate_image(), potentially allowing a write to a binary while it was being executed.
Evidence provided: Source-level analysis only. No runtime demonstration.
Apple's response: Source-level analysis only; need on-device execution evidence. Closed.
Note: A subsequent submission on the same finding, with proper on-device execution evidence, was accepted and is in Apple's fix queue. This post concerns only the original, closed submission.
The analysis was not wrong. In XNU's exec path, when a process calls execve, the kernel locks the target vnode and checks v_writecount before proceeding. If v_writecount != 0, the call returns ETXTBSY. For fat binaries — universal Mach-O images containing multiple architecture slices — the exec path may need to re-enter exec_activate_image() after the initial image type check, once the appropriate slice is selected. The question I was asking at 2am was whether the write-count state was correctly managed across that re-entry: whether a window existed in which a concurrent writer could open the file between the first and second entry into the check.
The source read suggested a window. The source read was not sufficient to establish that the window was exploitable, or that the kernel's runtime behaviour matched the source's implied path. Compiler optimisation, runtime locking, and subtle ordering guarantees that are not visible in source can all close windows that look open on paper. The proper response to a source-level hypothesis is a runtime test. I did not produce one before submitting.
What the source says
/* XNU source reference -- bsd/kern/kern_exec.c
* The ETXTBSY enforcement pattern being analysed:
*
* At exec time, the vnode is locked and checked for writers:
* if (vp->v_writecount != 0) {
* error = ETXTBSY;
* goto bad;
* }
*
* The source-only observation: in certain exec paths that go through
* exec_activate_image() -> exec_mach_imgact(), the write-count check
* occurs before the image type is confirmed. A binary that requires
* a second exec attempt (e.g. fat binary slice selection) re-enters
* the path, and the write-count state from the first attempt may not
* be correctly restored.
*
* Source: github.com/apple-oss-distributions/xnu/bsd/kern/kern_exec.c
*
* This analysis was correct as a code-reading exercise.
* It was not sufficient as a security finding without runtime evidence.
* The subsequent finding (with runtime evidence) is a separate matter.
*/
Why Apple's closure was correct
A source-level analysis tells you that a code path exists. It does not tell you that the code path is reachable at runtime under the conditions you need. It does not tell you whether other kernel mechanisms close the window before it can be exploited. It does not tell you whether the compiler has generated code that serialises the operations differently from the source's apparent order.
Apple's security research framework asks, correctly, for demonstrable impact. A finding that says "the source suggests a window may exist" is not the same as a finding that says "I ran this test case on a real machine and observed this behaviour". The first is a research hypothesis. The second is a finding. I submitted the first as if it were the second.
This closure was entirely correct, and the lesson cost me a filing. I had spent long enough reading XNU source that I had begun to treat source analysis as equivalent to runtime evidence. It is not. The source is a model of what the code intends to do; the running system is what it actually does; and the gap between those two things is where security research either succeeds or fails. The subsequent submission on the same finding — with runtime evidence — was accepted. The source analysis was not wrong. It was just not enough. That distinction is worth writing down.
This is not a comfortable post to write. It would be easier to say nothing about the closed submission and let the accepted one stand alone. But the closed one is the more instructive, and I think researchers who are earlier in their careers than I am will find it useful to know that a source read is a starting hypothesis, not a conclusion.
The distinction from the active finding
The active finding — the one accepted and in Apple's fix queue — is not this post's subject, and nothing in this post discloses it. The source-level hypothesis described above is visible in the public XNU mirror at github.com/apple-oss-distributions/xnu. This post describes the methodology failure of the original submission: the mistake of treating source analysis as runtime evidence. The evidenced finding has its own path and will be disclosed in due course through appropriate channels.
Disclosure note
The original submission described here was closed by Apple for lack of runtime evidence. Apple's response is characterised accurately. The source reference above is to public code in Apple's open-source distribution. Nothing in this post discloses an unpatched vulnerability — the source-level observation is a hypothesis without demonstrated runtime impact. The active finding is separate and is not disclosed here.
Are you there? The source said yes. The system had not yet been asked.