Every hacker who graduates beyond script kiddie status eventually faces the same temptation: breaking the kernel. Not because it’s easy—it’s not—but because that’s where the real power lives. Userland is a sandbox. The kernel is the box itself. If you’re reading this, you already know that buffer overflows against outdated FTP servers are a solved problem. The frontier is in the plumbing of the operating system. This guide walks through the mindset, the mechanics, and the method of turning a kernel bug into arbitrary ring 0 code execution. No hand-holding, but no gatekeeping either. Just the raw process, laid bare.
Understanding the Attack Surface
The Linux kernel isn’t some monolithic mystery—it’s a set of interfaces exposed to userland, each one a potential door. System calls are the obvious entry point. Every read(), ioctl(), mmap(), or clone() transitions from ring 3 to ring 0, and any mistake in argument validation inside the kernel’s handler is a vulnerability waiting to happen. Syscall fuzzing with tools like syzkaller has turned this surface into a bloodbath of CVEs, but the real trick is knowing which bugs are actually exploitable.
Beyond syscalls, there are less obvious vectors. Virtual filesystems like /proc, /sys, and debugfs expose kernel internals through read/write operations. Each read or write is a kernel context switch. Race conditions in these paths are common, especially when kernel developers assume atomicity where none exists. Then there’s netlink sockets, which carry structured messages between userland and kernel subsystems—a rich target for type confusion and heap corruption bugs. And don’t overlook eBPF. The extended Berkeley Packet Filter runs verified code inside the kernel, but the verifier itself has a history of flaws that let attackers slip malicious instructions through. If you’re mapping attack surface, draw boxes around every door from userland to kernel, then start knocking.

Setting Up a Development and Debugging Lab
You can’t exploit what you can’t observe. A minimal lab consists of a virtual machine running a vulnerable kernel, a debugging host, and a reliable communication channel between them. I use QEMU with a custom kernel build, booted with a Debian-based initramfs. The kernel is compiled with debug symbols and aggressive sanitizers: KASAN for detecting memory corruption, KCOV for coverage-guided fuzzing, and lockdep for catching race conditions. A typical QEMU invocation looks like this: qemu-system-x86_64 -kernel bzImage -initrd initramfs.cpio.gz -append "console=ttyS0 nokaslr" -s -S. The -s flag opens a GDB stub on port 1234, and -S freezes the CPU at startup so you can attach before anything runs.
On the host, I use GDB with the Python Exploit Development plugin (peda or pwndbg) to script analysis. A common workflow: trigger the bug in the VM, catch the crash via the GDB stub, then examine registers, stack frames, and kernel memory. If you’re working with heap vulnerabilities, the slab allocator’s debugging features are invaluable—boot with slub_debug=FPZU to enable redzoning, poisoning, and use-after-free detection. The goal here is repeatability. If you can’t trigger the bug on demand with a deterministic testcase, you’re not ready to write an exploit.

From Bug to Primitive: Classes of Kernel Vulnerabilities
Not all bugs are created equal. The Linux kernel’s memory model—with its distinction between virtual and physical addresses, direct mapping of all physical memory, and the slab/slub allocators—means the exploit path depends heavily on the type of corruption you can achieve. Stack buffer overflows are rare in modern kernels due to stack canaries and CONFIG_VMAP_STACK, but they still surface in obscure drivers or old code paths. More common are heap overflows and use-after-free (UAF) bugs in dynamically allocated objects. The slab allocator groups objects of similar sizes into caches, and a UAF in a struct file or struct cred is gold because those structures hold security-critical data.
Integer overflows leading to undersized allocations are another classic. If a calculation wraps and the kernel allocates less memory than expected, a subsequent copy can corrupt adjacent objects. Uninitialized memory reads leak kernel pointers, breaking KASLR and making the exploit deterministic. Race conditions, especially in file system or network paths, can create use-after-free windows by tricking the kernel into freeing an object while another thread still holds a reference. And don’t forget the dark art of type confusion: convincing the kernel that a slab object is a different type than it really is, often by corrupting a type field or reallocating a freed object with a controlled structure. Each bug class demands a different strategy, but they all converge on the same endgame: gaining a write-what-where primitive or a controlled call to an attacker-chosen address.
Heap Feng Shui in the Kernel
In userland, heap grooming is about arranging the heap to place a vulnerable buffer near a target. In the kernel, it’s about controlling the slab allocator’s state. The slab caches are per-CPU and highly deterministic once you understand the allocation and free patterns. Objects of the same size reside in the same cache, and freed objects are placed on a freelist. If you can spray objects of a target size—say, by opening many file descriptors to force allocation of struct file—then trigger a free, you can reclaim that slot with a controlled payload. The classic keyctl spray or msg_msg spraying via System V IPC are reliable ways to place attacker data in kernel memory. The trick is knowing the exact size of the target object so your spray lands in the same cache. Tools like pahole (part of the dwarves package) reveal structure layouts and sizes from debug symbols. Once you own a slab slot, corrupting it is straightforward; the art is in choosing which field to overwrite to maximize impact.
Bypassing Mitigations
Modern kernels are fortresses, but fortresses have cracks. KASLR randomizes the kernel’s base address at boot. Without a leak, your exploit is blind. The easiest leaks come from uninitialized memory or information disclosure bugs that reveal kernel pointers. The /proc/kallsyms file is sometimes readable by unprivileged users on misconfigured systems, but more often you’ll need a real bug. A single leaked kernel text address gives you the base, and from there you can calculate the addresses of any exported symbol. SMEP and SMAP are hardware features that prevent the kernel from executing userspace code or accessing userspace memory directly. They kill the old technique of mapping shellcode in userland and pointing the instruction pointer at it. Instead, you need to build a ROP chain from kernel gadgets or pivot to a kernel region where you’ve written your shellcode.
KPTI (Kernel Page Table Isolation) separates user and kernel page tables, so even if you hijack kernel execution, you can’t simply return to a userland address. Exploits now often use a technique called “signal handler return” or modify the kernel’s page tables directly to map a userland page as executable kernel memory—but that requires a deep understanding of the MMU. The newest threat is Control Flow Integrity (CFI) and indirect branch tracking, which limit the targets of indirect calls and jumps. But these are often coarse-grained and can be bypassed by targeting allowed call targets that happen to be useful (like a gadget inside a function that eventually calls usermodehelper). Mitigation bypass is a cat-and-mouse game, and staying current means reading the kernel’s hardening patches as they land.
The Exploit Execution Flow
With a primitive in hand, the objective is privilege escalation. The most direct path is to overwrite the credential structure of the current process. The struct cred holds the UID, GID, and capability sets. Overwriting the UID to 0 gives root. But finding the cred structure in memory requires knowing the task_struct and cred pointers. A common trick: if you have an arbitrary read primitive, traverse the current pointer to find task_struct, then follow the cred pointer. With write-what-where, you can overwrite it directly. Alternatively, you can overwrite a function pointer in a structure like struct file_operations or struct tty_operations so that a subsequent syscall from userland executes your controlled function.
Another classic technique is to overwrite the modprobe_path, a kernel string that points to the binary executed when a file with an unknown extension is run. If you overwrite it with the path to your own script, then trigger modprobe by attempting to execute a dummy file, your script runs as root. This bypasses SMEP/SMAP because it’s a legitimate kernel path to userland execution. More sophisticated exploits modify kernel code itself—patching the syscall table or the setuid code path—but these require knowledge of write-protected memory and page table manipulation. The cleanest modern method is to escalate privileges, then execute a userland shell. Once you have root, the kernel is yours to trojan, hide, or simply use as a launchpad for persistence.

Reliability and Cross-Version Considerations
An exploit that works only on a specific kernel build in a specific configuration is a lab toy. Real-world exploits need to be sturdy. This means handling structure layout changes across kernel versions. The offsets of fields within struct cred or struct task_struct shift with compiler flags and kernel configs. You can hardcode offsets for known distributions and versions, but a smarter approach is to dynamically resolve them at runtime by pattern-scanning kernel memory or using exported symbols. The /proc/kallsyms or /sys/kernel/notes can provide symbol addresses if readable, but on hardened systems you may need to scan the kernel’s ELF header in memory.
Another reliability factor is the kernel’s randomness. Even without KASLR, the slab allocator’s state is influenced by prior system activity. A technique called “deterministic kernel state” involves triggering the exploit immediately after boot, before noise accumulates. For race conditions, you often need to win a narrow window—techniques like scheduler priority manipulation (using sched_setscheduler to set real-time priority) can tilt the odds in your favor. And always, always test on the exact target kernel. Differences in compiler optimization, kernel config, and CPU microarchitecture can turn a 100% reliable exploit into a 0% one. Build a library of kernel images and test harnesses, and treat exploit reliability as an engineering problem, not a guessing game.
Real-World Case Study: CVE-2022-0847 (Dirty Pipe)
No guide is complete without dissecting a real bug. Dirty Pipe, disclosed in early 2022, was a logic flaw in the pipe subsystem that allowed writing to page cache pages that were still marked as writable even after the pipe was closed. The vulnerability existed since kernel 5.8 and affected a massive number of systems. The exploit was elegant: create a pipe, fill it with data, drain it, then use splice() to map a read-only file’s page cache into the pipe. Because the pipe buffer flags weren’t properly cleared, a subsequent write to the pipe would modify the file’s page cache, effectively allowing arbitrary writes to any file the user could read—including /etc/passwd.
The exploit path: open a read-only file, splice it into a pipe, then write your payload to the pipe. The payload (a new line in /etc/passwd with a root user) lands in the page cache, and the kernel flushes it to disk. The primitives were simple: no memory corruption, no ROP, just a logic bug that gave a write-what-where to page cache pages. This is a reminder that the most devastating bugs are often not complex buffer overflows but subtle logic errors that subvert the kernel’s own security guarantees. Studying public exploits like this teaches more about kernel internals than any textbook.
FAQ
Do I need to be a kernel developer to write kernel exploits?
Not necessarily, but it helps. You need to understand kernel memory management, the slab allocator, and the locking model. You don’t need to write production drivers, but you should be comfortable reading kernel source code and navigating the LXR cross-referencer. Start by reading the exploit code for public CVEs and tracing how they interact with the kernel.
What’s the best way to practice without breaking the law?
Use intentionally vulnerable kernels. The vuln-kernel project provides a series of QEMU-ready kernel images with introduced bugs. Also, Capture the Flag (CTF) events frequently feature kernel exploitation challenges. The Linux Kernel Module (LKM) challenges from past CTFs are excellent training material.
How do I keep up with new kernel mitigations?
Follow the kernel-hardening mailing list and the patches from Kees Cook’s team. Read the kernel security documentation for the official word on new features. And watch the conference talks from Linux Security Summit—they’re often the first public discussion of upcoming mitigations.
Why do so many exploits target the slab allocator?
The slab is where the kernel stores most dynamically allocated objects, including security-critical structures. Its internal freelist and metadata are predictable once you understand the cache layout. That predictability makes it possible to engineer use-after-free and overflow attacks with high reliability.