
Rust exploit PoC for Linux kernel LPE CVE-2026-31431 (AF_ALG page-cache write) plus eBPF runtime defense blocking AF_ALG socket creation via LSM or kprobe without reboot.
CVE-2026-31431 (Copy Fail) is a local privilege escalation vulnerability in the Linux kernel's algif_aead module (AF_ALG subsystem). A logic flaw introduced in August 2017 allows any unprivileged local user to write 4 controlled bytes into the page cache of any readable file via AF_ALG + splice(), then execute a corrupted setuid binary to gain root. The exploit is deterministic — no race conditions, no kernel offsets, no system crash. It affects all major Linux distributions shipping kernels from 4.14 through 7.0-rc.
CVSS: 7.8 | Fixed in: kernel 7.0, 6.19.12, 6.18.22 | Mainline fix: commit a664bf3d603d
Sources: copy.fail, The Hacker News, CloudLinux advisory
crates/
├── exp/ # Exploit PoC (Rust reimplementation)
├── copy_fail_guard/ # Userspace eBPF loader — LSM mode
├── copy_fail_guard-ebpf/ # eBPF LSM program (not in workspace)
├── copy_fail_guard_kprobe/ # Userspace eBPF loader — kprobe mode
└── copy_fail_guard_kprobe-ebpf/ # eBPF kprobe program (not in workspace)
scripts/
└── build_guard.sh # One-click build → outputs to dist/
The root cause is a chain of three independent kernel features interacting unsafely:
AF_ALG socket — exposes the kernel crypto API to unprivileged userspacesplice() — zero-copy transfers file data as page cache references (not copies) into the crypto scatterlistauthencesn AEAD template — uses the caller's output buffer as scratch space, writing 4 bytes at dst[assoclen + cryptlen]In 2017, an in-place optimization in algif_aead.c (72548b093ee3) made req->src == req->dst, chaining page cache pages into the writable destination scatterlist. When authencesn writes its scratch bytes, it walks past the output buffer into the chained page cache pages. The attacker controls:
The corrupted page is never marked dirty — the on-disk file is untouched, but execve() reads from the page cache. Corrupt a setuid binary → root.
Rust reimplementation of the public 732-byte Python PoC. Targets /usr/bin/su, splices its page-cache pages into an AF_ALG AEAD socket, and overwrites them with a compressed shell payload.
Requires Rust 1.85+ (edition 2024).
cargo build --release -p copy_fail
The binary is Linux-only. On other platforms it exits with an Unsupported error.
Warning: This exploits a real kernel flaw. Only run on systems you own and control, ideally a disposable VM.
Boot a VM running a vulnerable kernel (any mainstream distro with kernel < 7.0 / < 6.19.12 / < 6.18.22).
Copy the built binary into the VM and run it as a non-root user:
./target/release/copy_fail
Vulnerable: a root shell (#) appears. Run whoami to confirm root.
Not vulnerable (patched kernel): the AF_ALG operation fails or the su binary behaves normally. You'll see an error or a regular su password prompt.
A runtime kernel defense that blocks CVE-2026-31431 without upgrading the kernel or rebooting. It uses eBPF to intercept AF_ALG socket creation, cutting off the exploit's first step.
Two modes are provided. The one-click loader auto-selects the best available mode:
┌──────────────────────────────────────────────────┐
│ run_guard.sh │
│ • Detects BPF LSM support │
│ • LSM available → copy_fail_guard (EPERM) │
│ • LSM unavailable → copy_fail_guard_kprobe (KILL)│
└──────────────┬───────────────────────────────────┘
│
┌──────────────▼───────────────────────────────────┐
│ eBPF program │
│ if socket family == 38 (AF_ALG) │
│ → block (EPERM or SIGKILL) │
│ else │
│ → allow │
└──────────────────────────────────────────────────┘
Blocking AF_ALG has near-zero impact on typical systems:
AF_ALGAF_ALG (e.g. OpenSSL with the afalg engine enabled, some embedded crypto offload paths)socket(AF_ALG, ...)One-click build (recommended):
./scripts/build_guard.sh
This automatically installs missing toolchains (nightly, bpf-linker), compiles all eBPF programs and userspace loaders, and outputs everything to dist/:
dist/
├── copy_fail_guard.bpf.o # eBPF LSM program
├── copy_fail_guard_kprobe.bpf.o # eBPF kprobe program
├── copy_fail_guard # Userspace loader (LSM)
├── copy_fail_guard_kprobe # Userspace loader (kprobe)
└── run_guard.sh # Auto-select: sudo ./run_guard.sh
Copy the dist/ directory to any target machine and run sudo ./run_guard.sh to activate protection.
Manual build (step by step):
Step 1: Compile the eBPF programs (must be done on Linux):
# LSM variant
cd crates/copy_fail_guard-ebpf
cargo +nightly build --target bpfel-unknown-none -Z build-std=core --release
# kprobe variant
cd crates/copy_fail_guard_kprobe-ebpf
cargo +nightly build --target bpfel-unknown-none -Z build-std=core --release
Step 2: Build the userspace loaders:
cargo build --release -p copy_fail_guard -p copy_fail_guard_kprobe
Recommended — auto-selects the best mode:
sudo ./dist/run_guard.sh
Manual — run a specific mode:
# LSM mode (requires lsm=bpf)
sudo GUARD_BPF_OBJ=path/to/copy_fail_guard.bpf.o RUST_LOG=info ./copy_fail_guard
# kprobe mode (works everywhere)
sudo GUARD_BPF_OBJ=path/to/copy_fail_guard_kprobe.bpf.o RUST_LOG=info ./copy_fail_guard_kprobe
Press Ctrl-C to detach the eBPF program and restore normal behavior.
With the guard running in one terminal:
# In another terminal, try the exploit:
./target/release/copy_fail
# LSM mode: "error: Operation not permitted"
# kprobe mode: "已杀死" / "Killed"
# Or test directly with Python:
python3 -c "import socket; socket.socket(38, 5, 0)"
# LSM mode: PermissionError: [Errno 1] Operation not permitted
# kprobe mode: Killed
AF_ALG socket creation unconditionally. There is no mechanism to exempt specific processes by PID, cgroup, or command name. This is planned for a future version (via eBPF HashMap maps). For the vast majority of systems this is fine since almost nothing uses AF_ALG.bpf-linker (they target bpfel-unknown-none and cannot be normal workspace members).Most distributions do not enable BPF LSM by default. If you want the cleaner LSM mode (EPERM instead of SIGKILL):
# Check current LSMs:
cat /sys/kernel/security/lsm
# If "bpf" is missing, add it:
sudo sed -i 's/^GRUB_CMDLINE_LINUX="\(.*\)"/GRUB_CMDLINE_LINUX="\1 lsm=lockdown,capability,yama,apparmor,bpf"/' /etc/default/grub
sudo update-grub && sudo reboot
The kprobe mode works without this step.
After patching the kernel to ≥ 7.0 / ≥ 6.19.12 / ≥ 6.18.22:
# Confirm kernel version
uname -r
# Re-run the exploit — it should no longer produce a root shell
./target/release/copy_fail
Alternatively, confirm the vulnerable module is neutralized:
modinfo algif_aead | grep filename
# If built-in, blacklist the initcall (requires reboot)
sudo grubby --update-kernel=ALL --args="initcall_blacklist=algif_aead_init"
sudo reboot
| LSM mode | kprobe mode |
|---|
| How it blocks | Returns -EPERM (socket creation denied) | SIGKILL (process killed) |
| Kernel requirement | ≥ 5.7 with lsm=bpf boot parameter | ≥ 5.3, no special parameters |
| Needs reboot to enable | Maybe (if lsm=bpf not already set) | No |
| Hook point | socket_create LSM hook | __sys_socket kprobe |