highCVE-2022-0847CVSS 7.8Linux

Dirty Pipe — overwriting the unwritable, and putting a stop to it

A kernel pipe bug lets unprivileged users scribble over read-only files — including SUID binaries. Here's how to find it and patch the kernel.

Marko ZivanovicApr 29, 20268 min read

01 The Big PictureFor leadership · no jargon

Okay, so this one is Dirty Pipe, and the tl;dr is: a bug in the Linux kernel — the core program that decides who is allowed to do what — lets a regular, unprivileged user rewrite the contents of files the system considers untouchable. That includes the password file and the programs that run as administrator. The classic analogy is a hotel guest who can't open any of the locked doors, but can slip a forged note under the manager's door that changes the master key.

In business terms, this is a local privilege-escalation bug. Anyone who already has a toehold — a compromised web application account, a shell inside a container, a contractor's low-level login — can turn it into full administrator control of the machine. From there: read every file on disk, steal customer data and credentials, plant ransomware, or use the server as a staging ground to attack the rest of your network. Public exploit code has existed since March 2022, works reliably, and CISA has confirmed real-world exploitation. This is not theoretical.

The urgency rule: if any of your Linux hosts run an unpatched kernel from the 2020–2022 era, treat them as a standing risk — especially anything multi-tenant, internet-facing, or running containers where users share the host kernel. The good news (there is some) is that the fix itself is routine: a standard kernel package update and a reboot per host, minutes of actual work, no data loss. Fleets with livepatch support can close the hole without rebooting at all.

02 Technical BreakdownFor engineers

So what's actually gone wrong? The root cause is an uninitialised flags member in struct pipe_buffer. When data is spliced from a file into a pipe, the kernel creates a pipe buffer that references the page cache of that file instead of copying the data (zero-copy — neat optimisation, right up until it isn't). Since commit 241699cd72a8 (Linux 4.9, 2016), the code path in copy_page_to_iter_pipe() failed to initialise the buffer's flags. That was harmless until commit f6dd975583bd (Linux 5.8, 2020) converted the mergeability check into a per-buffer flag, PIPE_BUF_FLAG_CAN_MERGE. From 5.8 onward, a stale flag could survive on a page-cache reference, letting a subsequent write() to the pipe append directly into the page cache of the spliced file — bypassing every file permission, including read-only mounts, immutable files, and SUID binaries. Oof.

Exploitation is fully deterministic — no races, no luck involved. The attacker creates a pipe, fills and drains it to stamp PIPE_BUF_FLAG_CAN_MERGE onto every ring slot, splices one byte from the target file (opened read-only) just before the target offset, then writes attacker-controlled bytes into the pipe. Those bytes land in the page cache, not the pipe. There are practical constraints: the attacker needs read permission on the target file, the write cannot start on a page boundary, cannot cross a page boundary, and cannot grow the file. That is, unfortunately, more than enough — the public PoCs overwrite entries in /etc/passwd or inject code into a SUID binary like /usr/bin/su, yielding a root shell. One subtlety that matters for forensics: the overwrite does not mark the page dirty, so it may never hit disk — the change is in-memory until something else dirties the file, and can vanish on reboot. Do not assume a clean-looking file on disk means the system was not exploited.

The vulnerable range is Linux 5.8 through 5.16.10, fixed upstream in 5.16.11, 5.15.25, and 5.10.102 by initialising the flags field on every new pipe buffer (Max Kellermann's patch). Distros backported that one-line fix into their own kernel packages. The precondition is local code execution — but note that includes any process inside a container on the host, since containers share the kernel. There is no meaningful configuration mitigation here; the only fix is a patched kernel.

03 Affected

Linux kernel 5.8 through 5.16.10 — i.e., any kernel older than 5.16.11, 5.15.25, or 5.10.102 on those stable branches. That covers Ubuntu 20.04 HWE and 21.10, Debian 11 (bullseye, kernel 5.10), RHEL 9 / Rocky 9 (5.14), Fedora, and any container host on those kernels — container images do not matter, the host kernel is the vulnerable component. Kernels older than 5.8 (Ubuntu 18.04's 4.15, RHEL 8's 4.18, Debian 10's 4.19) carry the latent bug but not the exploitable path, which is a rare stroke of luck. If your distro shipped a patched build, trust the distro package version over the upstream number — they backported.

04 Detection

Right, let's find out where we stand. Start with the obvious:

uname -r

Running kernel version. Vulnerable if it's 5.8–5.16.10 and below your branch's fix (5.16.11 / 5.15.25 / 5.10.102).

dpkg -l 'linux-image-*' | grep ^ii | awk '{print $2, $3}'

On Debian/Ubuntu, list installed kernel packages and compare against the patched build for your release (e.g. Ubuntu 20.04: 5.13.0-35.40 or later).

ansible all -m command -a 'uname -r' -f 50

Fan out across the fleet; collect every host's kernel in one pass and flag anything in the vulnerable range.

kubectl get nodes -o wide

Kubernetes: the IMAGE column shows the node OS and the KERNEL-VERSION column shows the kernel — every node kernel must be patched, since pods share it.

find /tmp /var/tmp /dev/shm -type f -newermt '2022-03-07' 2>/dev/null | head

Weak signal hunt for dropped PoC binaries in world-writable dirs. Absence proves nothing, mind — the attack leaves almost no artefacts.

05 Remediation

01 Patch the kernel package

First, install the fixed kernel through the normal package manager so the distro-backported fix lands. The upstream fix is a one-line initialisation, backported into every supported distro kernel — no heroics required.

sudo apt-get update && sudo apt-get install --only-upgrade linux-image-generic

Debian/Ubuntu: upgrade the kernel metapackage only, without touching the rest of the system.

02 Boot into the new kernel

Here's the bit people trip over: the running kernel is still vulnerable after the package install — the fix is only live once the host boots the new build. On hosts with kernel livepatch (Ubuntu Advantage, kpatch) the fix can be applied without a reboot, but verify the livepatch module actually covers CVE-2022-0847 before trusting it.

sudo reboot

Boot the patched kernel. Schedule it; this is the only non-negotiable step.

03 Contain containers until the host is patched

On shared kernel hosts (Kubernetes nodes, CI runners, shared dev boxes), an unpatched host means every container tenant can take over the node. Until the reboot lands, treat pods running untrusted code as the highest-risk surface: restrict scheduling of new untrusted workloads and tighten runAsUser/privilege settings so the blast radius is contained.

kubectl cordon <node> && kubectl drain <node> --ignore-daemonsets --delete-emptydir-data

Evict workloads from a node before rebooting it; repeat per node. Bit of a faff on a big cluster, but so is a breach.

06 Verification

uname -r

Must show a kernel at or above the fixed release for your branch (5.16.11 / 5.15.25 / 5.10.102) or the distro-patched build.

cat /etc/passwd | head -3 && ls -l /usr/bin/su

Sanity check: critical account files are intact and SUID binaries show expected ownership and timestamps.

kubectl get nodes -o wide | awk '{print $1,$3}'

On nodes, confirm every host rebooted onto the patched kernel — one straggler node keeps the whole cluster exposed.

sudo apt-get install -y linux-image-generic && apt-cache policy linux-image-generic

Confirm the installed candidate matches the distro security advisory version for your release.

07 Rollback

If the new kernel misbehaves, you can boot back into the previous one:

ls /boot/vmlinuz-* | sort -V

List available kernels; pick the previous entry at the GRUB menu (Advanced options) and reboot into it.

08 References

---

Spotted a mistake or stuck on a step? Corrections and questions are always welcome.

// Patched, or stuck?

Ship the fix. Prove it’s closed.

If this runbook doesn’t fit your stack, send the CVE — an operator writes a verified path, usually within a business day. Or contribute your own and get co-authored credit.

// Dispatch

New guides, straight to your inbox.