A beginner-friendly breakdown of one of the most dangerous Linux kernel vulnerabilities since Dirty COW — with a live demo and mitigation guide.
Introduction
On April 29, 2026, researchers at Xint disclosed a vulnerability in the Linux kernel that sent shockwaves through the security community. It is called CopyFail, tracked as CVE-2026-31431, and it has a CVSS score of 7.8 (HIGH).
Here is what makes it terrifying:
- It affects every mainstream Linux distribution built since 2017
- It requires zero special privileges — any normal user can trigger it
- The entire exploit is 732 bytes of Python
- It leaves no trace on disk — traditional security tools see nothing
- It works on Ubuntu, Amazon Linux, RHEL, SUSE — unmodified, out of the box
I ran this on my own Ubuntu 24.04 VM to understand it, demo it, and show you how to defend against it. This is that story.
Non-Technical Explanation: What is actually happening?
Imagine your computer is a library.
Books on the shelves = files on your hard disk (permanent)
When someone wants to read a book, the librarian makes a photocopy and puts it on the reading table = RAM (temporary, fast)
Next person who wants the same book? Librarian skips the shelf and hands them the copy on the table directly. Faster.
Now imagine someone sneaks into the library at night and scribbles over the photocopy on the reading table — but never touches the original book on the shelf.
Next morning, every reader gets the scribbled version. The original book looks fine. The librarian has no idea anything happened.
That is CopyFail.
The "photocopy on the reading table" is the Linux page cache — RAM copies of files your system is using. CopyFail scribbles shellcode into the RAM copy of /usr/bin/su (a program that lets you switch users). When you run su, Linux executes the scribbled version — and gives the attacker a root shell.
The file on disk? Untouched. sha256sum /usr/bin/su returns the same hash before and after. Your file integrity tools see nothing.
Technical Explanation
The Four Pieces You Need to Understand
1. The Page Cache
User runs /usr/bin/su
↓
First time → kernel reads from disk → stores in RAM (page cache)
↓
Next time → kernel serves from RAM directly (faster)
↓
All processes share the same cached pages
2. Setuid Binaries
/usr/bin/su has permissions: -rwsr-xr-x
↑
's' = setuid bit
Normal binary → runs as YOU (the caller)
Setuid binary → runs as the FILE OWNER (root)
If you inject code into su and run it → your code executes as root
3. AF_ALG — The Kernel Crypto Socket
Normal socket → sends data over network
AF_ALG socket → sends data through kernel's crypto engine
socket(AF_ALG, SOCK_SEQPACKET, 0)
setsockopt → "use authencesn(hmac(sha256),cbc(aes))"
Now feed data in → kernel encrypts it for you
4. splice() — Zero Copy
Normal read():
Disk → page cache → YOUR program's memory → destination
↑ data passes through you
splice():
Disk → page cache → destination (kernel handles it directly)
↑ your program never touches the data
↑ the ACTUAL page cache pages get wired in
The Bug — How These Four Pieces Combine
BEFORE 2017 (safe):
splice(su → AF_ALG socket)
↓
Input pages: [su's page cache]
Output pages: [separate kernel buffer] ← scratch write goes HERE
(safe)
AFTER 2017 optimization (buggy):
splice(su → AF_ALG socket)
↓
Input pages: [su's page cache]
↕ ← src = dst (in-place optimization)
Output pages: [su's page cache] ← scratch write goes HERE
(su's RAM!!!)
The authencesn algorithm needs 4 bytes of scratch space during processing. After the 2017 optimization, those 4 bytes land inside su's page cache instead of a safe kernel buffer.
And you control exactly where those 4 bytes land by choosing assoclen + cryptlen.
The Full Attack Chain
ATTACKER (normaluser) KERNEL
───────────────────── ──────
1. open("/usr/bin/su") → file descriptor to su
2. socket(AF_ALG) → crypto socket opened
setsockopt(authencesn...) → algorithm configured
3. splice(su_fd → socket) → su's page cache pages
wired directly into
crypto engine I/O
4. sendmsg() → crypto runs
carefully chosen assoclen 4-byte scratch write
+ cryptlen lands at target offset
inside su's page cache
5. repeat for each → shellcode injected
4 bytes of shellcode byte by byte into RAM
6. os.system("su") → kernel executes RAM version
(not disk version)
↓
setuid(0) → root
execve("/bin/sh") → shell
The Shellcode (160 bytes, decompressed)
Inside the 732-byte script is a zlib-compressed mini ELF binary. Decompressed, it does exactly three things:
setuid(0) → lock in root privileges
execve("/bin/sh") → replace process with a shell
exit(0) → cleanup if execve fails
The string /bin/sh\0 is appended at the end. That is the entire payload.
Why Forensics Miss It
Disk: sha256(/usr/bin/su) = a1b2c3d4... ✅ clean
RAM: su's page cache = [shellcode] ☠️ compromised
File integrity tools (AIDE, Tripwire) → read from disk → report clean
auditd file watches → watch disk writes → see nothing
Rebooting → RAM wiped → su reloads clean from disk
No persistence. No disk writes. No logs. Rebooting removes all traces — including all evidence.
Live Demo
⚠️ This was performed on my isolated VirtualBox VM running Ubuntu 24.04 LTS, kernel 6.17.0-20-generic. Never run this on a production system or any machine you do not own.
System Info (before exploit)
$ uname -r
6.17.0-20-generic
$ id
uid=1001(normaluser) gid=1001(normaluser) groups=1001(normaluser)
$ sha256sum /usr/bin/su
44900c631391f0d60eb6d271b8374a08dc1d9be....... /usr/bin/su
A completely normal, unprivileged user. No sudo. No special groups.
Running the Exploit
$ python3 copy_fail_exp.py
#
The script ran silently. Then su got triggered. The prompt changed to #.
After Exploit
# id
uid=0(root) gid=0(root) groups=0(root)
# whoami
root

# sha256sum /usr/bin/su
44900c631391f0d60eb6d271b8374a08dc1d9be....... /usr/bin/su
↑
exact same hash — disk was never touched
732 bytes. One command. Root. Disk untouched.
Mitigation
Check if you are vulnerable
uname -r
# Vulnerable if kernel is between 4.14 and 6.18.21
# or 6.19.x below 6.19.12
Layer 1 — Disable algif_aead immediately (no reboot needed)
# Block the module
echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/disable-algif.conf
# Remove from memory if loaded
sudo rmmod algif_aead 2>/dev/null || true
# Verify it is blocked
sudo modprobe algif_aead
# Expected: modprobe: ERROR: could not insert 'algif_aead'
This closes the attack surface immediately. It does not affect dm-crypt, LUKS, SSH, OpenSSL, or GnuTLS.
⚠️ Note: On some distros like (AlmaLinux, Rocky, CentOS),
algif_aeadmay be built into the kernel (CONFIG_CRYPTO_USER_API_AEAD=y). Thermmodapproach will not work there.
Layer 2 — Patch your kernel (permanent fix)
Ubuntu / Debian:
sudo apt update && sudo apt full-upgrade -y
sudo reboot
uname -r # confirm new kernel version
AlmaLinux / Rocky / RHEL:
sudo dnf clean metadata && sudo dnf upgrade -y
sudo reboot
After Mitigation — Exploit Fails
$ python3 copy_fail_exp.py
Traceback (most recent call last):
...
OSError: [Errno 19] No such device
$ id
uid=1001(normaluser) gid=1001(normaluser) groups=1001(normaluser)
Same script. Same machine. One mitigation command = exploit dead.
References and Credits
- Discovery & Technical Writeup: Brian Pak and the team at Xint — xint.io/blog/copy-fail-linux-distributions
- Official PoC: github.com/theori-io/copy-fail-CVE-2026-31431
- Sysdig Analysis & Detection Rules: sysdig.com/blog/cve-2026-31431
This blog was written for educational purposes. The demo was performed in my isolated VM environment. Always patch your systems and never run exploit code on machines you do not own.
If you found this useful, share it — especially with anyone running Ubuntu 24.04 or any Linux server built since 2017.



Top comments (0)