Damaged metadata let the program read and write far outside its permitted memory. I used that access to find a process credential and change it to root.
When Linux safety checks
fail.
Linux checks eBPF programs before allowing them to run with high privileges. I reproduced four cases where that check was wrong and showed that two could be turned into administrator access in isolated virtual machines.
From a software bug to real security impact.
Before an eBPF program runs, the Linux verifier tries to prove that it cannot access unsafe memory or lock the system. A bug in that reasoning is serious, but it does not automatically give an attacker full control.
I studied four verifier mistakes and followed each one from the incorrect safety decision to its real effect. For every case, I recorded the affected kernel, the conditions required, and the strongest result I could reproduce.
Four bugs. Four different failure modes.
A network buffer remained accessible after it had been freed. By controlling how that memory was reused, I redirected a kernel callback and obtained root access.
An incorrect code transformation changed a limited jump into an endless loop. Running it on every CPU made the virtual machine unresponsive.
The verifier reused a previous safety result when it should not have. This exposed useful memory addresses, but not enough information for a complete exploit.
Use out-of-bounds access to find a process credential.
CVE-2023-39191 leaves damaged metadata marked as trustworthy. The eBPF program can then perform repeatable 8-byte reads and writes outside its assigned memory, while a normal userspace process controls each operation.
Adaptive calibration
The exploit fills memory with recognizable patterns, first in large steps and then in smaller ones. Finding those patterns through the illegal read reveals where useful process data is likely to be located. In the documented run, this calibration converged around 4034 MiB.
Credential spray and scan
Thousands of paused child processes keep separate Linux credential records in memory. The exploit searches for the current user ID, checks nearby fields to avoid a false match, and changes one record to user ID 0. That child can then create a root-owned helper.
The first 8-byte write also clears the credential reference counter. The system remained alive long enough to persist the root helper, but this is an exploit-specific stability weakness-not a property required by the vulnerability.
Turn freed memory into controlled memory.
The first strategy tried to replace a freed network buffer during a very short race between CPU cores. It confirmed the use-after-free bug, but it was not reliable enough to complete the privilege escalation.
I redesigned the chain as separate, controllable stages. The stale eBPF pointer changes the kernel allocator's next free address. A later allocation is then placed in memory controlled by userspace, where I replace a callback pointer and use it to change modprobe_path.
The official EXT/freplace compatibility defect was established through source and patch analysis. The local program reproduces the equivalent post-UAF primitive and validates the complete exploitation strategy. The successful chain runs from a non-root but capability-bearing account and relies on exposed kernel symbols, the expected allocator layout, and disabled freelist hardening.
Build a safe and repeatable laboratory.
Every target used a minimal Buildroot filesystem and the exact vulnerable Linux kernel under QEMU. PoCs and exploits were statically cross-compiled and deployed through filesystem overlays. Crashes, soft lockups, and allocator corruption stayed contained inside disposable virtual machines.
- Per-CVE kernel versions and configurations
- Explicit BPF permissions, hardening settings, and symbol visibility
- Verifier logs, readback sentinels, crash behavior, and effective UID as evidence
- Reproduction steps that distinguish laboratory convenience from vulnerability-derived capability
The main lesson is architectural: verifier safety depends on preserving object lifetime, bounds, and side-effect contracts across every transformation and boundary. Once the abstract model drifts from runtime state, “verified” code can become a kernel memory primitive.