DEV Community

itsmegsg
itsmegsg

Posted on

CopyFail (CVE-2026-31431): How a 732-Byte Python Script Gets Root on Almost Every Linux Machine

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!!!)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

A completely normal, unprivileged user. No sudo. No special groups.

Running the Exploit

Before running the exploit

$ python3 copy_fail_exp.py
#
Enter fullscreen mode Exit fullscreen mode

The script ran silently. Then su got triggered. The prompt changed to #.

After Exploit

After running the exploit

# id
uid=0(root) gid=0(root) groups=0(root)

# whoami
root


![SHA remains the same](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6r2s0sx7q1ankvmhnsih.png)



# sha256sum /usr/bin/su
44900c631391f0d60eb6d271b8374a08dc1d9be.......  /usr/bin/su
                ↑
         exact same hash — disk was never touched
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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_aead may be built into the kernel (CONFIG_CRYPTO_USER_API_AEAD=y). The rmmod approach 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
Enter fullscreen mode Exit fullscreen mode

AlmaLinux / Rocky / RHEL:

sudo dnf clean metadata && sudo dnf upgrade -y
sudo reboot
Enter fullscreen mode Exit fullscreen mode

After Mitigation — Exploit Fails

Script fails after updating

$ 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)
Enter fullscreen mode Exit fullscreen mode

Same script. Same machine. One mitigation command = exploit dead.


References and Credits


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)