Most of this series is about corrupting kernel memory. Dirty Pipe is the opposite kind of bug — no overflow, no UAF, no gadgets. A single struct field went uninitialised, and the consequence is that you can write into files you have no permission to write. It's worth reading precisely because the root cause is so small and the impact so total.
- cve
- CVE-2022-0847 ("Dirty Pipe")
- affected
- Linux 5.8 .. 5.16.11 / 5.15.25 / 5.10.102
- primitive
- overwrite the page cache of any readable file
- impact
- LPE — clobber /etc/passwd, a SUID binary, a root-owned config
the moving parts: pipe buffers and CAN_MERGE#
A pipe is a ring of pipe_buffer structs, each pointing at a page and carrying
flags:
PIPE_BUF_FLAG_CAN_MERGE (added in 5.8) means "a following write() may append
into this buffer's existing page instead of allocating a new one." Anonymous pipe
writes set it. The slots are reused as the ring cycles — and that reuse is the
whole bug.
the root cause: a flag nobody cleared#
When you splice() a file into a pipe, the kernel makes a pipe buffer that points
straight at the file's page-cache page (a read-only reference — no copy). The
function that builds that buffer set page, offset, len, ops … and forgot
flags:
Without that buf->flags = 0, the buffer inherits whatever the slot held last
time. Cycle the pipe through anonymous writes first and every slot carries a stale
CAN_MERGE. Now the spliced buffer — pointing at a file's page cache — is also
marked mergeable. The next write() takes the merge path:
|
That copy_page_from_iter writes your bytes into the page cache of a file you
opened O_RDONLY. No write permission was checked, the page isn't even marked
dirty. Readers immediately see the modified content; it can be written back to
disk later.
the exploit, in five syscalls#
|
root from a read-only open
Splice just before the root password hash, write a known one, su:
the three constraints#
The bug is powerful but not unlimited — and the limits fall straight out of how the merge works:
- Can't write the first byte of a page. The splice has to leave a non-zero
offset/lenfor the merge to append after, so the overwrite starts at offset ≥ 1 within each page. (Pick targets where byte 0 of the page is untouched.) - Can't cross a page boundary in one shot — each page-cache page is its own buffer. Overwrite page by page.
- Can't extend the file. You overwrite existing bytes in the cache; you can't
grow it. So you edit
/etc/passwdin place, or patch bytes of a SUID binary.
The lesson Dirty Pipe drives home: a kernel bug doesn't need corrupted pointers or
a single ROP gadget. One field left uninitialised on a hot path — a flags the
compiler will happily leave holding old data — bridges "I can read this file" to "I
can rewrite it," and on Linux that's root. Read the patch: it's one line, and that
one line is the whole vulnerability.