Open the live, source-backed model
Incident anatomy · Code anatomy
The flag that outlived the data.
Three lines from Dirty Pipe’s original exploit PoC drain a pipe. A live bit-level model shows why security-relevant state can survive—and how initialization breaks the chain.
Kinwilder research desk · · AI-assisted analysis
Code specimen
Original disclosed C · drain loop
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
read(p[0], buffer, n);
r -= n;- Line 1: Choose the next bounded chunk — This declaration appears in both fill and drain loops. The source record deliberately selects the second occurrence: the loop that consumes data.
- Line 2: The bytes leave; metadata can remain — This is the read call in the original PoC’s preparation phase. Consuming the pipe data does not, by itself, establish a fresh policy for a later backing page.
- Line 3: Accounting is not reinitialization — Reducing the remaining byte count advances the loop. The missing descriptor initialization belongs to a later kernel lifecycle boundary, which the live model isolates.
What is established
This is a historical case: CVE-2022-0847, publicly disclosed by Max Kellermann on 7 March 2022. The specimen is an exact excerpt from his original GPL-licensed exploit proof of concept, not a toy substitute. Its three statements come from the loop that drains a prepared pipe. Read the original disclosure and C listing.
The Linux header assigns 0x10 to the merge flag. The historical header also shows that the page reference and flags occupy separate fields of a pipe-buffer descriptor. CISA added Dirty Pipe to its Known Exploited Vulnerabilities catalog on 25 April 2022. That establishes exploitation of the vulnerability; it does not establish that a particular attacker used this exact listing.
The reporter's explanation identifies a missing flags initialization when a descriptor begins referring to a file-backed page. The accompanying kernel fix initializes that state. The live model compares the decision before and after that initialization; it does not run the exploit.
Our analysis
The surprising part is the apparently helpful operation: draining a pipe. The visible data has gone away. A later operation can still inherit a decision from the descriptor that used to describe it.
Think of a descriptor as a small dossier rather than a bag of bytes. It records what page is involved, how much of it is represented, and what operations are allowed. Emptying the data path does not necessarily erase every field of that dossier. Reusing its storage for a different kind of page creates a new lifecycle boundary.
Three statements, two different histories
The first statement chooses a bounded chunk size. The second consumes that chunk from the pipe. The third advances the loop's remaining-byte count. Those are ordinary data-transfer operations; none of the three establishes the provenance of a later page reference or reinitializes all descriptor metadata.
This is why reading the excerpt as “a loop that empties something” misses the security question. The important state is outside the visible buffer of consumed bytes. Ask what the descriptor means after its next assignment, and which previous fields can survive that assignment.
The live model watches one deliberately controlled slot. First, an anonymous buffer receives a merge flag. Next, its data is drained. Then the watched slot refers to a file-backed page. Finally, a later append considers the retained flag. You can jump between phases and compare the historical path with explicit initialization.
This is a state reconstruction, not a claim that the three displayed lines alone exploit a machine. The original proof of concept supplies other operations and preconditions. Keeping that distinction visible is part of understanding the mechanism rather than treating a copied command as knowledge.
One bit can carry too much authority
The binary display marks bit four: decimal sixteen, hexadecimal 0x10. The arithmetic is elementary. Its interpretation is not. A bit that was reasonable for one backing object can become inappropriate when the same descriptor storage refers to something else.
Our broader lesson is to treat security-relevant flags as claims with a lifetime. A flag should not be trusted merely because it sits beside a fresh pointer. The pointer and the flag need a valid relationship, established for the current object and operation.
That review habit transfers to pooled request objects, recycled message descriptors and cached authorization decisions. These are analogies, not assertions that those systems have Dirty Pipe. In each case, an engineer should be able to explain which initialization establishes the new object's policy and which previous state is intentionally retained.
Watch the comparison, not a victory message
At the reuse phase, the model's historical path retains 0x10 while the initialized path shows zero. At the decision phase, they choose differently under the fixture's stated assumptions. The patched comparison does not say that every later write is forbidden: it says that this file-backed page does not inherit the old permission to merge. A separate buffer is a different object with its own lifecycle.
The model deliberately omits scheduling, real memory allocation and filesystem operations. Its value is that every displayed field has an explicit meaning. You can inspect the actual JavaScript module running in the worker and check that the diagram is derived from state, not a prerecorded animation.
Defensive actions
For a system owner, use the current vendor's kernel guidance and verify that the running kernel includes the applicable fix. A package download without the required transition to the corrected running kernel is not enough evidence by itself. Vendor backports make a casual upstream version-string comparison an incomplete check.
For a low-level maintainer, inventory fields whose meaning depends on the backing object. Review reuse paths as carefully as allocation paths. A useful regression test should construct the old state, cross the reuse boundary, and assert the new invariant. Testing only a freshly zeroed object can miss the interesting history.
For a detection engineer, distinguish an indication of exploit behavior from proof of successful corruption. Event context and the affected kernel matter. This article and model do not supply a device scanner, claim a compromise, or replace incident response evidence.
A safe exercise is to write two small state tables: one where flags survive reassignment and one where initialization establishes the new policy. Then change a precondition. If the watched page is not file-backed, or the merge flag is clear, which conclusion changes? A useful analysis explains its failed cases as clearly as its successful one.
Limits
The live model assumes a readable source, relevant descriptor reuse, compatible offsets and a write contained within one page, along with the other PoC conditions. It does not probe your kernel, execute the original C, or independently reproduce exploitation. The exact code, date, license and fingerprints are disclosed so readers can separate the real public artifact from our explanatory model.
AI-assisted source analysis and original educational model. No human expert review or independent exploit reproduction is implied.