Copy Fail: Three Commits to Root
CVE-2026-31431 is 732 bytes of Python that roots every Linux distribution since 2017, no race condition required. Here is how three independent kernel commits composed into a silent privilege escalation.
CVE-2026-31431 is a 732-byte Python script that roots every Linux distribution since 2017. Ubuntu, Amazon Linux, RHEL, SUSE — unmodified, same file, same run. No race condition. No per-distro offsets. No compiled payload, nothing outside the standard library. You run it, then run su, and the prompt changes to #.
The vulnerability is called Copy Fail. It spent nine years hiding in the Linux kernel's crypto subsystem. Understanding it is worth a few minutes.
Why This One Is Different
Most Linux local privilege escalation CVEs come with caveats. They need a race window — you trigger a TOCTOU path, hope to win before the kernel proceeds, maybe succeed 40% of the time. They need kernel-specific offsets, so you recompile for each target. They cover a narrow version range.
Copy Fail has none of these properties. It is a straight-line logic flaw: deterministic, portable, single-shot. The combination of those four properties — portable, tiny, stealthy, and 100% reliable — is rare enough that it is worth calling out separately from the typical CVE digest.
The Name
The name is literal. The bug lives in authencesn, the kernel's AEAD cryptographic template for IPsec Extended Sequence Numbers. When authencesn processes output, it needs to rearrange the ESN bytes. To do this, it scribbles 4 bytes into a scratch area past the legitimate end of its output region. The copy of those bytes fails to stay inside the destination buffer.
In a contained environment, those 4 rogue bytes land in memory the kernel controls and nobody cares. The problem is that the environment is not always contained.
Three Commits
No single change introduced this vulnerability. It emerged from the composition of three separate kernel decisions made years apart:
2011: authencesn added. A cryptographic wrapper for IPsec that handles extended sequence number rearrangement. Correct in isolation. No userspace path to it.
2015: AF_ALG AEAD socket support. The kernel crypto API gained a socket interface, letting unprivileged userspace submit AEAD operations directly. Input and output lived in user buffers. The authencesn scratch write still lands harmlessly.
2017: algif_aead in-place optimization. For performance, the source and destination scatterlists for the AEAD operation were collapsed into one. Instead of maintaining separate read and write buffers, req→src and req→dst now point to the same memory. This is the commit that turned the other two into a vulnerability.
With src and dst collapsed, a page-cache page can end up in the writable destination scatterlist. When an unprivileged process uses splice() to hand page-cache pages into the AF_ALG socket, those pages are now fair game for the authencesn scratch write. Four attacker-controlled bytes land inside the kernel's cached representation of any readable file on the system.
The Attack
The target is any setuid-root binary readable by the user. /usr/bin/su is present on every distribution. The exploit sequence:
Open an AF_ALG socket configured with authencesn(hmac(sha256),cbc(aes)). Use splice() to pull the page-cache pages of /usr/bin/su into the socket's input. Trigger an AEAD operation. authencesn writes its 4-byte scratch past the output boundary. Because src and dst are the same, those bytes land in the cached copy of su — not on disk.
The kernel now executes the corrupted cache when su is next called. 732 bytes of Python is enough to orchestrate this entire chain. passwd, chsh, sudo, pkexec are all equally viable targets.
The Stealth Problem
Standard disk forensics misses this entirely. The write never marks the page dirty. Nothing is flushed to disk. inotify does not fire. A forensic image of the disk shows the original binary with matching checksums, because the disk is the original binary.
There is a detection window. While the corrupted page is hot in cache, sha256sum will disagree with the baseline — because sha256sum reads through the page cache, the same path execve uses. AIDE and Tripwire would catch the discrepancy during that window. IMA in enforcing mode catches it at execve time, before the corrupted binary runs.
The corruption is also transient. Memory pressure, a manual cache drop, or a reboot reloads the page clean from disk. Post-exploitation, the evidence evaporates on its own.
This makes Copy Fail a container escape primitive too. The page cache is shared across the host kernel. A pod with the right primitives corrupts a setuid binary on the node and crosses tenant boundaries — this is not just a local privilege escalation.
AI Found This
Copy Fail was discovered by Taeyang Lee at Xint, with an AI-assisted audit. The human contribution was the insight: splice() hands page-cache pages directly into the kernel crypto subsystem, and scatterlist page provenance is an underexplored attack surface class. From there, Xint Code — their AI security research tool — scanned the entire crypto/ subsystem in roughly an hour. Copy Fail was the highest-severity finding.
This is a meaningful data point about where AI-assisted security research is going. The hypothesis was human. The throughput was AI. The result was a 9-year-old root-equivalent bug that manual audits had missed across thousands of kernel revisions.
The Patch
The fix is mainline commit a664bf3d603d. It reverts the 2017 in-place optimization. After the patch, req→src and req→dst are separate scatterlists again. Page-cache pages stay in the read-only source. The authencesn scratch write still happens — but it now lands in a user buffer, where it is harmless.
Before you can patch: disable the algif_aead module. For most systems this changes nothing measurable — dm-crypt, kTLS, IPsec, OpenSSL, and SSH all use the in-kernel crypto API directly and do not go through AF_ALG. Check with lsof | grep AF_ALG if you want to be certain.
# echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
# rmmod algif_aeadThe Lesson
No individual commit here was wrong. authencesn was correct. The AF_ALG socket interface was correct. The in-place optimization made sense as a performance improvement. Each change was reviewed, each had a reasonable rationale, and none of them was a security mistake in isolation.
The bug is in the composition. Security surface lives not just inside features, but in the interactions between features that were designed years apart by different people with different mental models. That is the kind of attack surface that manual audits find slowly, if at all.
Nine years is a long time for four bytes to be in the wrong place.
← Essays & Opinions