A 4-byte write into RAM that hands you a root shell on Ubuntu, RHEL, Amazon Linux, SUSE — and every other Linux distro shipped since 2017. Here is exactly how it works, from first principles.

Patch status: Fixed in kernel 6.18.22, 6.19.12, and 7.0. Added to CISA Known Exploited Vulnerabilities list on May 1, 2026. A public PoC exploit exists and works.

On April 29, 2026, security firm Theori published a vulnerability called Copy Fail (CVE-2026–31431). It is a logic bug hiding inside Linux's cryptography subsystem since 2017. A 732-byte Python script using only standard library imports exploits it to give any unprivileged local user a root shell — on every major Linux distribution, with 100% reliability, leaving no trace on disk.

This post explains exactly how it works, step by step, from the ground up.

Three Concepts You Need to Know

Before we get into the bug, you need to understand three things about how Linux works.

Concept 1: The Page Cache

When Linux reads a file from disk, it stores the contents in RAM in a structure called the page cache. The next time anything reads that file — your program, the cat command, the shell — Linux serves it from the page cache, not from disk. Disk is slow. RAM is fast.

Here is the crucial part: when the kernel runs a program with execve(), it also reads the binary from the page cache. If you modify the page cache copy of /usr/bin/su, and then run su, the kernel executes your modified version — not what is on disk.

DISK                      PAGE CACHE (RAM)             WHAT RUNS
  ─────                     ────────────────             ──────────
  /usr/bin/su  ──reads──>  [cached copy of su]  ──>  execve() uses THIS
  (untouched)               (can be modified)          not the disk copy

This is why Copy Fail is stealthy. It never writes to disk. Your integrity checker, your disk hash, your forensics image — they all show the original, unmodified file. The page cache is what actually gets executed.

Concept 2: Setuid Binaries

Some programs on Linux are marked setuid-root. When any user runs them, they temporarily run as root, regardless of who launched them. You can see them with:

find /usr/bin -perm -4000
# Output examples:
# /usr/bin/su
# /usr/bin/sudo
# /usr/bin/passwd
# /usr/bin/newgrp

The s in the permissions means setuid:

-rwsr-xr-x 1 root root 67816 /usr/bin/su
     ^
     This 's' = "when any user runs this, it runs as root"

These binaries are readable by everyone (so the kernel can load them), but owned by root. If you can corrupt the page cache copy of /usr/bin/su, you control what code runs as root when any user types su.

Concept 3: AF_ALG — Kernel Crypto from Userspace

Linux provides a socket interface called AF_ALG that lets ordinary, unprivileged programs use the kernel's cryptographic functions (AES, ChaCha20, HMAC, etc.) without needing root. It works like a network socket, but instead of sending data across the network, you send data into the kernel crypto engine and get it back encrypted or decrypted.

AF_ALG is enabled by default on essentially every Linux distribution. Any user on the system can use it.

A Scratch Write That Goes Out of Bounds

What is authencesn?

Deep inside the Linux kernel's crypto subsystem lives a component called authencesn. It handles a specific kind of encryption used by IPsec (the protocol behind VPNs) that deals with 64-bit sequence numbers split across two 32-bit fields.

To authenticate messages correctly, authencesn needs to rearrange some bytes before computing its hash. Specifically, it needs to swap around two 4-byte fields in the header. It does this rearrangement by using the output buffer as scratch space — it writes some temporary values there, reads them back, and is supposed to clean up.

The problem: it never fully cleans up. It writes 4 bytes past the end of the legitimate output region and never restores them. Those 4 bytes are just left there, overwritten, with whatever value authencesn happened to need for its internal calculation.

Here is a simplified view:

Legitimate output region:
[  AAD data  ][  ciphertext  ][ auth tag ]
                                          ^
                                          authencesn writes 4 bytes HERE
                                          (past the auth tag, outside its
                                           own output region)
                                          and never restores the original bytes

In isolation, this would be a minor bug — writing 4 bytes to some memory that you own anyway. But combined with the next piece, it becomes catastrophic.

The Amplifier: Page Cache Pages in the Scatterlist

Here is where splice() comes in.

splice() is a Linux system call for moving data between file descriptors without copying it. Instead of read-then-write, it passes references to the underlying memory pages. It is a performance optimization.

When you splice() a file into an AF_ALG crypto socket, you are telling the kernel: "use the file's page cache pages directly as the input data for this crypto operation." The kernel does not copy the file's contents into a new buffer. It sets up a scatterlist (a list of memory regions for the crypto engine to operate on) that points directly at the file's cached pages in RAM.

For AEAD (Authenticated Encryption with Associated Data) operations, the kernel's algif_aead.c code sets this up as an in-place operation — meaning the same scatterlist serves as both input and output.

The input scatterlist looks like this:

[ AAD region ] [ ciphertext region ] [ auth tag region ]
                                      ^^^^^^^^^^^^^^^^^
                                      These entries point directly at
                                      the PAGE CACHE PAGES of your file
                                      (e.g. /usr/bin/su)

The output scatterlist is chained to use the same pages:

[ user's output buffer ]  →→→  [ chained: auth tag pages from page cache ]
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                               Still pointing at /usr/bin/su's cached pages
                               Now in a WRITABLE position in the output chain

The page cache pages of your target file are now sitting in the writable part of the output scatterlist.

And then authencesn runs its decryption — writing 4 bytes past the end of the legitimate output into exactly those page cache pages.

Result: 4 controlled bytes are written into the in-RAM copy of any file you can read. Including /usr/bin/su.

The Full Exploit Chain, Step by Step

STEP 1: Open an AF_ALG socket
────────────────────────────
Any user can do this. No privileges required.
Bind to the 'authencesn' AEAD template.
s = socket(AF_ALG, SOCK_SEQPACKET, 0)
  bind(s, {type="aead", name="authencesn(hmac(sha1),cbc(aes))", ...})
  afd = accept(s, ...)   # operation file descriptor

STEP 2: Open the target setuid binary
─────────────────────────────────────
Open /usr/bin/su for reading. Readable by everyone.
  file_fd = open("/usr/bin/su", O_RDONLY)

STEP 3: splice() the file into a pipe, then into the AF_ALG socket
───────────────────────────────────────────────────────────────────
This is the key step. splice() passes the file's page cache pages
*by reference* into the AF_ALG socket's input scatterlist.
No copy is made. The crypto engine will operate directly on
/usr/bin/su's cached pages in RAM.
  pipe(pipe_fds)                            # create a pipe
  splice(file_fd, offset, pipe_write, ...)  # file pages -> pipe
  splice(pipe_read, afd, ...)               # pipe -> AF_ALG input
  # Now: afd's input scatterlist contains direct references to
  # /usr/bin/su's page cache pages.

STEP 4: Craft the decryption request to trigger authencesn
────────────────────────────────────────────────────────────
Send a carefully crafted decryption request through the socket.
The request is intentionally invalid - we WANT the operation
to reach the authencesn scratch-write code path.
  # The 4 bytes written by authencesn's scratch operation
  # land at a specific offset in /usr/bin/su's page cache.
  # We choose our request parameters so those 4 bytes
  # overwrite a specific instruction in the su binary.

STEP 5: Execute the corrupted binary
──────────────────────────────────────
The kernel runs execve("/usr/bin/su").
It loads /usr/bin/su from the page cache - our modified version.
The 4 overwritten bytes skip the privilege check.
We get a root shell.
  os.execv("/usr/bin/su", ["su"])
  # Output:
  # root@hostname:/#

The entire script is 732 bytes. It uses only Python's built-in os, socket, and zlib modules. It either works or it doesn't — and it always works.

Why Your Disk Checksums Don't Catch It

This is the part that trips people up.

When you run sha256sum /usr/bin/su, Linux reads the file. Reading a file goes through the page cache. So sha256sum is actually hashing the page cache copy — which, right after exploitation, is the corrupted version. In that sense, during the attack window, your checksum tool WILL see a different hash.

But here is why this doesn't help:

  1. Nothing is written to disk. The kernel's writeback mechanism (which syncs page cache changes to disk) is never triggered. The corrupted page is never marked "dirty" for writeback.
  2. After a reboot, the attack disappears. The page cache is cleared on reboot. The next read of /usr/bin/su loads the original, unmodified file from disk. A forensics disk image shows nothing.
  3. The attacker's goal is already achieved. By the time a defender might notice, the attacker already has a root shell and has likely written persistence (SSH keys, cron jobs, new user accounts) — those ARE on disk, but the exploit trace itself is gone.
TIMELINE:
T+0s:   Exploit runs. Page cache of /usr/bin/su is modified in RAM.
T+1s:   Attacker runs /usr/bin/su. Gets root shell.
T+2s:   Attacker writes SSH key to /root/.ssh/authorized_keys.
T+10s:  sha256sum /usr/bin/su  <-- shows DIFFERENT hash (reading corrupt cache)
T+30s:  Attacker exits. Shell closed.
Reboot: Page cache cleared. /usr/bin/su reloads from disk. Hash matches baseline.
Forensics image shows:
 /usr/bin/su           = CLEAN (original file, untouched)
 /root/.ssh/auth_keys  = ATTACKER KEY (this is what survives)

The Container Escape Problem

This is what makes Copy Fail especially dangerous in cloud and Kubernetes environments.

Containers on the same host share the host kernel — they do not have their own kernel. And because the page cache is a kernel structure, containers on the same host share the same page cache.

This means:

  1. Attacker compromises one low-privilege container on a Kubernetes node (via web vulnerability, malicious CI job, etc.)
  2. From inside that container, attacker runs Copy Fail against /usr/bin/su on the host's page cache
  3. Attacker gets root on the host node — not just inside their container
  4. From the host node, attacker can access every other container's filesystem via /proc/<pid>/root/
KUBERNETES NODE
  ┌─────────────────────────────────────────────────────────────┐
  │  HOST KERNEL + HOST PAGE CACHE (shared by everyone)        │
  │                                                             │
  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
  │  │  Container A │  │  Container B │  │  Container C │     │
  │  │  (attacker)  │  │  (victim)    │  │  (victim)    │     │
  │  └──────┬───────┘  └──────────────┘  └──────────────┘     │
  │         │                                                   │
  │         │  splice() host /usr/bin/su into AF_ALG            │
  │         │  → corrupt host page cache                        │
  │         │  → run su → ROOT ON HOST NODE                     │
  │         └──────────────────────────────────────────────────>│
  └─────────────────────────────────────────────────────────────┘

One compromised container = compromised node = all containers on that node exposed.

This is why Theori flags CI runners, Kubernetes clusters, multi-tenant build farms, and cloud SaaS environments as the highest priority for patching.

Who Is Affected

Any system running a Linux kernel between version 4.14 (released 2017) and 6.18.21 is vulnerable if AF_ALG is available (it is, by default, on all mainstream distributions).

Other affected distributions (same kernel, same bug): Debian, Fedora, Arch Linux, Rocky Linux, AlmaLinux, Oracle Linux — and any embedded or custom Linux using a kernel in the 4.14–6.18.21 range.

What to Do Right Now

Option 1: Patch Your Kernel (Do This First)

The fix reverts the 2017 in-place optimization in algif_aead. After patching, the page cache pages and the output buffer are kept separate — authencesn's scratch write goes to the output buffer, never the page cache.

# Check your current kernel version
uname -r
# If below 6.18.22 / 6.19.12 / 7.0 — you are vulnerable
# Ubuntu / Debian
sudo apt update && sudo apt upgrade
sudo reboot
# RHEL / Amazon Linux / Fedora
sudo dnf update kernel
sudo reboot
# After reboot, verify:
uname -r   # should show patched version

Option 2: Disable algif_aead (If You Cannot Patch Immediately)

This blocks the vulnerable code path. For the vast majority of systems, this breaks nothingAF_ALG AEAD is not used by default by OpenSSL, SSH, LUKS, or most other common applications.

# Immediate effect (does not survive reboot):
sudo rmmod algif_aead
# Permanent (survives reboot):
echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/disable-algif.conf
# For Debian/Ubuntu, update initramfs:
sudo update-initramfs -u
# For RHEL/Fedora:
sudo dracut -f
# Verify it is blocked:
sudo modprobe algif_aead
# Expected: modprobe: ERROR: could not insert 'algif_aead': Operation not permitted

Will this break anything? Check first:

# See if anything on your system is currently using AF_ALG:
lsof | grep AF_ALG
# or:
ss -xa 2>/dev/null | grep alg
# If empty — safe to disable immediately.

Option 3: Block AF_ALG in Containers (Kubernetes / Docker)

Even after patching, block AF_ALG socket creation inside untrusted containers as a defense-in-depth measure:

# Kubernetes seccomp profile — deny AF_ALG socket creation
# Add to your Pod's securityContext:
apiVersion: v1
kind: Pod
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: deny-af-alg.json
// deny-af-alg.json
{
  "defaultAction": "SCMP_ACT_ALLOW",
  "syscalls": [
    {
      "names": ["socket"],
      "action": "SCMP_ACT_ERRNO",
      "args": [{"index": 0, "value": 38, "op": "SCMP_CMP_EQ"}]
    }
  ]
}
// value 38 = AF_ALG address family

Detection: What to Look For

Because the on-disk file is never modified, traditional file integrity monitoring is not reliable for detecting the active exploit. You need behavioral detection.

auditd Rules

# Flag AF_ALG socket creation (unusual for non-crypto applications)
auditctl -a always,exit -F arch=b64 -S socket -F a0=38 -k af_alg_create
# a0=38 = AF_ALG address family
# Flag splice() calls (used in the exploit chain)
auditctl -a always,exit -F arch=b64 -S splice -k splice_watch
# Flag setuid binary execution that results in uid=0
auditctl -a always,exit -F arch=b64 -S execve -F euid=0 -k root_exec
# Review (look for: AF_ALG socket → splice → su/sudo/passwd execution in sequence)
ausearch -k af_alg_create --start today

Behavioral Red Flags

Suspicious pattern:
  1. Unprivileged user (uid != 0) creates AF_ALG socket
  2. Same process calls splice() referencing a setuid binary
  3. Same process (or child) executes /usr/bin/su with uid=0 result
  4. No PAM authentication log entry for the su call
     (the normal password check never ran — the binary was corrupted)
Post-exploitation disk artifacts (what DOES survive reboot):
  - New entries in /root/.ssh/authorized_keys
  - New cron entries for root (crontab -l -u root)
  - New user accounts in /etc/passwd
  - New systemd services in /etc/systemd/system/
  - Unusual binaries in /usr/local/bin or /tmp

How It Was Found

Copy Fail was discovered by Theori researcher Taeyang Lee, who was studying how Linux's crypto subsystem interacts with page-cache-backed data. He noticed that AF_ALG + splice() could place page cache pages into the writable position of an AEAD operation's scatterlist — and then used Theori's AI-powered code analysis platform Xint Code to scan the entire kernel crypto subsystem for algorithms that might write past their intended output region.

authencesn was the hit. The scan took approximately one hour.

The bug had been sitting in the Linux kernel for nine years — introduced in 2017 with the in-place optimization — undetected through millions of kernel developer hours, countless security audits, and extensive static analysis runs.

This outcome is a strong signal that AI-assisted code scanning is going to find vulnerability classes that traditional methods consistently miss.

Summary

  • The bug: authencesn, a kernel AEAD crypto algorithm, writes 4 bytes as scratch space past the end of its legitimate output region. When splice() is used to feed page-cache pages into an AF_ALG socket, those 4 bytes land in the RAM copy of any file the attacker can read — including setuid binaries like /usr/bin/su.
  • The impact: Any unprivileged local user gets root. One 732-byte Python script. Works on every Linux distro built since 2017.
  • The stealth: Nothing is written to disk. The page cache is volatile. After a reboot, the exploit's direct trace is gone.
  • The cloud risk: Page cache is shared across containers on the same host. Container foothold = host node compromise.
  • The fix: Disable algif_aead module immediately. Patch kernel to ≥ 6.18.22 / 6.19.12 / 7.0.

References

  • Theori / Xint Code — Copy Fail: 732 Bytes to Root on Every Major Linux Distribution (April 29, 2026) — primary source, technical write-up by Juno Im
  • Official exploit site — copy.fail — FAQ, mitigation, and PoC hash
  • GitHub PoC — theori-io/copy-fail-CVE-2026–31431
  • BleepingComputer — New Linux 'Copy Fail' flaw gives hackers root on major distros (April 30, 2026)
  • Microsoft Security Blog — CVE-2026–31431: Copy Fail vulnerability enables Linux root privilege escalation across cloud environments (May 1, 2026)
  • CISA KEV — CVE-2026–31431 added May 1, 2026
  • NVD — CVE-2026–31431 — CVSS 7.8 High
  • Linux kernel fix — mainline commit a664bf3d603d — reverts algif_aead in-place optimization
  • MITRE ATT&CK — T1068 (Exploitation for Privilege Escalation), T1611 (Escape to Host)