On 7 March 2022, the Linux kernel acquired its most elegantly named flaw since Dirty Cow — and its most overdue. Security researcher Max Kellermann of CMU disclosed CVE-2022-0847, “Dirty Pipe,” a local privilege-escalation vulnerability in the kernel’s pipe buffer implementation that let an unprivileged user write data into files they could only read — including read-only files, immutable btrfs/XFS/ZFS mount files, page-cache-backed files being written by other processes, and most devastatingly, setuid-root binaries and system configuration like /etc/passwd. The bug had shipped in kernel 5.8 (August 2020) and lived undocumented for a year and a half before one researcher’s curiosity — sparked by a corrupt log file a support ticket had flagged — traced a one-byte file corruption back to a pipe-buffer flag-handling flaw that should never have allowed writes at all. Exploitation was immediate: publicly released PoCs exploited /usr/bin/sudo-style targets and /etc/passwd rewrites within the disclosure week, achieving reliable root with a few dozen lines of C and no memory-corruption gymnastics. The kernel’s response (fix landed in 5.16.11, 5.15.25, 5.10.102 within days, plus fixes backported everywhere) closed a nearly 18-month exposure window on every modern Linux system — tablets, phones (Android 12 and kernel-5.10 devices), servers, and containers alike — and re-taught the ecosystem a lesson it keeps relearning: the kernel’s most trusted subsystems are exactly where nobody looks, and file-adjacent primitives are crown-jewel attack surface.
Dirty Pipe (CVE-2022-0847, disclosed 2022-03-07 by Max Kellermann, CMU) is a Linux-kernel local privilege escalation in the pipe subsystem: a flaw in how pipe buffers handle the PIPE_BUF_FLAG_CAN_MERGE flag lets a user splice a page-cache page into a pipe with the merge flag erroneously set, then write() arbitrary bytes directly into that page-cache page — bypassing file permissions entirely. Practical result: unprivileged users can modify any readable file’s cached content, including read-only-mounted files and (transiently, until cache eviction) even files they could not ordinarily write; classic exploits overwrite /etc/passwd to add a uid-0 account or patch a setuid binary’s cached pages to spawn root shells. Scope: kernels 5.8 → 5.16.10 / 5.15.24 / 5.10.101 (introduced ~2020-08 with pipe flag rework; fixed 2022-03 in 5.16.11, 5.15.25, 5.10.102 and backports) affecting servers, desktops, Android 12-era devices, and container hosts/roots. Constraints: attacker needs local code execution (or malicious local user) — it composes with any RCE, SSRF-to-exec, or multiuser environment. Root cause to remember: performance optimization (page-cache splice) + stale flag state on reused pipe buffers = write primitive into read-only memory. Legacy: named for its lineage to Dirty Cow (2016, similar read-only-file write via page-cache race); both proved that “file permissions enforced at write() time” assumptions die inside the page cache — and both shipped ~1.5+ years before discovery, roadmap-setting for kernel-fuzzing investment that later found the 2024 pipe/soc_ UAF cluster.
What happened
Kellermann’s disclosure essay reads like a detective story precisely because the bug presented as a mystery, not a vulnerability. A customer’s application kept observing one corrupted byte in a log file it had just written; standard suspects (disk, filesystem, the app) cleared. Kellermann chased the anomaly into the kernel, reproducing it only when a specific sequence — splice pipe from file, then write into the pipe — overlapped. The culprit sat in the pipe-buffer code’s lifecycle: when a pipe’s buffers were recycled, the PIPE_BUF_FLAG_CAN_MERGE flag from a previous use could persist into a buffer that now held a page-cache page spliced from a file; writes intended to append to the pipe instead merged into the file’s page cache — unprivileged, and invisible to permission checks.
The disclosure shipped responsibly (coordinated with kernel/distro maintainers; fixes merged before the writeup went live), but the essay’s detail level made exploitation trivial for anyone with a compiler. Public PoCs within days demonstrated the two canonical paths: overwrite /etc/passwd root-entry (instant root login/add-user) and patch the page cache of a setuid binary so its next execution runs attacker logic. Detecting exploitation retroactively was hard by nature: the file on disk never changed, only its cached pages — a elegance attackers loved and forensics hated.
Impact-wise, exposure was enormous but quiet. No mass-exploitation campaign was ever publicly attributed to Dirty Pipe (patch windows moved fast across major distros and Android OEMs), but security teams assumed nation-state and criminal toolkits bundled it; its local-only prerequisite made it a chain-component (post-phishing, post-RCE) rather than a primary vector — the same composite role PwnKit had occupied six weeks earlier — and together the two spelled 2022 as the year the industry stopped dismissing LPEs as “low” severity.
How it worked
Dirty Pipe mechanics (CVE-2022-0847):
BACKGROUND: pipes + page cache
- Linux pipes move data via
"pipe buffers" pointing at pages
- splice() can attach a FILE's
page-cache page directly into
a pipe (zero-copy performance
path)
- pipe buffers have flags; the
relevant one:
PIPE_BUF_FLAG_CAN_MERGE = "new
write() data may merge into
this buffer's page"
THE BUG (flag-state staleness)
1. fill a pipe completely (so
every buffer gets initialised
with CAN_MERGE set)
2. drain the pipe (buffers freed
for reuse BUT flag state can
persist on the ring)
3. splice() one byte from the
target file into the pipe
- creates a pipe buffer
pointing at the file's
page-cache page
- STALE CAN_MERGE flag rides
along on this buffer
4. write() attacker data to the
pipe
- kernel sees CAN_MERGE ->
merges data INTO the file's
page-cache page instead of
a fresh pipe page
- NO permission check ever
consults the file's write
mode: p->slab object belongs
to the PIPE, not the file
RESULT
- arbitrary byte overwrite of any
readable file's cached content
- works on read-only mounts,
immutable files, ((almost))
everything page-cache-backed
- on-disk file unchanged: changes
vanish on cache eviction
(attackers don't care - root
shell spawns long before)
EXPLOIT SHAPE (~50 lines of C)
a) overwrite /etc/passwd entry
to add uid-0 user
b) page-cache-patch a setuid
binary then execute it
Impact and numbers
| Metric | Value | Source |
|---|---|---|
| CVE / disclosed | CVE-2022-0847, 2022-03-07 | Kellermann disclosure |
| Reporter | Max Kellermann (CMU SEI) | disclosure essay |
| Subsystem | pipe buffer flag handling | kernel commit analysis |
| Introduced | kernel 5.8 (~2020-08) | kernel git history |
| Fixed | 5.16.11 / 5.15.25 / 5.10.102 + backports | kernel releases |
| Exposure window | ~18 months | derived from above |
| Platform reach | Servers, desktops, Android 12/kernel-5.10 devices, container hosts | distro/OEM advisories |
| Exploit complexity | Low (logic flaw, no memory corruption) | public PoC record |
Timeline
| Date | Event |
|---|---|
| 2020-08 | Kernel 5.8 ships with the pipe-flag rework introducing the flaw |
| 2021 (early) | Kellermann begins investigating a mysterious one-byte file corruption |
| 2022-02 | Root cause identified; coordinated disclosure to kernel/distros; patches merged |
| 2022-03-07 | Public disclosure “Dirty Pipe”; PoCs spread within days |
| 2022-03 onward | Mass patching across distros/Android; exploitation attempts logged opportunistically; no major campaign attributed |
Why it still matters in 2026
Because Dirty Cow was not a one-off, and Dirty Pipe made the page cache’s immunity from permission enforcement a permanent lesson. The 2024 kernel pipe-adjacent and network-driver use-after-free cluster (found by the same Syzcaller-era fuzzing investment this class of bug justified) confirmed the pattern: performance fast-paths that reuse kernel objects across trust boundaries are where decade-scale bugs live. Container estates keep the stakes current — a Dirty-Pipe-class flaw inside a container host’s kernel is a container-escape primitive whenever the workload can reach local exec, and the 2020s’ explosion of CI runners, devcontainers, and multi-tenant nodes multiplied exactly that population. For defenders, the durable takeaways are operational: treat kernel LPE patches as emergency-class regardless of “local” labels (chains compose — the 2022 pairing with PwnKit proved it); treat file-integrity monitoring that ignores page-cache state as incomplete (disk-verification tells you nothing about what a process just read); and respect the discovery story — one curious engineer chasing one wrong byte uncovered an 18-month universal root. Curiosity-driven investigation of anomalies remains the single highest-yield vulnerability-discovery method in history, and every SOC metric that punishes “non-ticket” digging should read this essay and repent.
Detection and hardening takeaways
- Patch kernel LPEs on emergency cadence. PwnKit (January) then Dirty Pipe (March) made 2022 the year “local-only” excuse died; kernel-point-release patch SLAs of days, not weeks, are the baseline they forced.
- Constrain local execution surfaces. The prerequisite is local code execution: seccomp profiles, no-new-privileges, restricted shells on appliance-like systems, and gVisor/Kata isolation for untrusted workloads all shrink the population that can even attempt the primitive.
- Monitor setuid execution + /etc/passwd integrity together. The classic exploit shapes show up as anomalous setuid-binary executions immediately following credential-file page-cache anomalies — pair the signals in detection rules.
- Verify integrity from the disk for high-value files. Since page-cache tampering evades on-disk checksums, forensics and integrity tooling on crown-jewel files should periodically force re-read (cache-bypassing) verification rather than trusting cached hashes.
- Fund the anomaly-chasers. The bug was found because an engineer refused to say “probably a flake” about one corrupted byte; engineering cultures and security budgets that resource such pursuits get Dirty Pipes found in months, not years.
FAQ
Is Dirty Pipe related to Dirty Cow?
In spirit, not in code. Dirty Cow (CVE-2016-5195, 2016) was a race condition in the memory-subsystem’s copy-on-write handling that similarly permitted writing to read-only files via the page cache; Dirty Pipe (CVE-2022-0847) is a flag-handling logic flaw in pipes achieving the same effect with far simpler mechanics and 100% reliability. Both expose the same lesson: the page cache sits between processes and files, and any kernel path that writes into it without consulting file permissions is a privilege-escalation primitive.
Could files be permanently corrupted?
Surprisingly, mostly not — and that was the elegance. Writes landed in the page cache, not on disk; for most filesystems the modifications never hit storage and disappeared when the page was evicted/re-read from disk. Attackers lost nothing by that transience (root shells spawned instantly), while forensics lost nearly everything (disk artifacts stayed clean). This cache-vs-disk asymmetry is why the bug was a detection nightmare despite easy patching.
Were containers or Android affected?
Both. Any container whose host kernel fell in the affected version range inherited the flaw — and escaping to host-root requires only local exec inside the container (with device/host-path caveats per configuration). Android 12 devices running affected 5.10/5.16-series kernels were in scope, which made OEM patch velocity — chronically slow — the practical risk driver for mobile fleets; enterprise Android deployments spent 2022 adjusting maturity timelines to kernel-patch reality.
