CVE-2026-31431 — Copy Fail: algif_aead authencesn 4-Byte Page-Cache Write → Linux LPE
Last updated: 2026-04-30 Component: Linux kernel —
crypto/algif_aead.c(AF_ALG userspace crypto interface) + theauthencesnAEAD template (crypto/authencesn.c) Bug Class: Logic flaw — in-place scatterlist optimisation letssplice()-delivered page-cache pages enter the writable destination scatterlist of an AEAD operation; theauthencesntemplate then performs a deterministic 4-byte write past the legitimate output boundary into shared file-backed memory. Patch: Mainline commita664bf3d603dreverts the 2017 in-place optimisation. Fixed in6.18.22,6.19.12, and7.0. Exploited ITW: No public ITW attribution at disclosure. Universal across distros — verified on Ubuntu 24.04, Amazon Linux 2023, RHEL 10.1, SUSE 16. Discoverers: Taeyang Lee + Brian Pak (Theori / Xint Code) — discovery assisted by Theori’s Xint Code AI scanner; coordinated disclosure with the Linux kernel security team. Related: Race Conditions, Use-After-Free, Integer Overflows Tags:linux,crypto,algif_aead,authencesn,splice,page-cache,lpe,setuid
Summary
A nine-year-old logic flaw in the Linux kernel’s userspace crypto interface (AF_ALG) gives any local user a deterministic 4-byte write into the page cache of any file the user can read() — including read-only setuid binaries owned by root. The exploit pins the modified page in the cache, executes the (now-corrupted-on-disk-image-but-not-on-disk) setuid binary, and inherits root.
It needs no race window, no info leak, no kernel-offset knowledge, no heap massage. The published Python PoC is 732 bytes using only os, socket, and zlib from the standard library, and works unchanged across every major distribution shipping a kernel from 2017 onward.
Root cause
Two separate, individually defensible kernel design decisions interact catastrophically.
1. The 2017 algif_aead in-place optimisation
algif_aead.c exposes the kernel’s AEAD primitives to userspace via an AF_ALG socket. To avoid copying, an optimisation introduced in 2017 lets the kernel build the AEAD operation’s destination scatterlist from the same pages as the source — the pages delivered via splice() from a file descriptor.
The dangerous consequence:
“The pages are not duplicated; the scatterlist entries point at the same physical pages that back every
read(),mmap(), andexecve()of that file.”
Splicing /usr/bin/su (or any other readable setuid binary) into the AEAD operation thus puts page-cache pages of a root-owned executable into a kernel scatterlist marked writable.
2. authencesn writes past the legitimate output boundary
authencesn is the AEAD wrapper used for IPsec Extended Sequence Number support. Unlike GCM, CCM, or plain authenc, the authencesn template uses memory at offset assoclen + cryptlen as a scratch pad for a 4-byte seqno_lo value during decryption — and never restores it, even when HMAC verification fails.
“The algorithm is using memory it does not own as a scratch pad.”
In any normal user-supplied buffer, this scratch write is harmless. Combined with the algif_aead optimisation above, the scratch write lands directly in a page-cache page of the spliced file.
The two together = an attacker-chosen 4-byte value written to an attacker-chosen offset inside a root-owned file’s cached pages.
The 4-byte primitive
To weaponise:
- Open an
AF_ALGsocket bound toauthencesn(hmac(sha256),cbc(aes)). splice()the target file (e.g./usr/bin/su) into the socket.- Construct AAD where bytes 4–7 encode the desired 4-byte payload (those bytes become the
seqno_lowritten byauthencesn). - Trigger decryption with
recv(). HMAC verification fails (the ciphertext is fabricated) — but the 4-byte scratch write toassoclen + cryptlenalready landed in the page-cache page. The error is silent:recv()returns-EBADMSGand the corrupted page stays cached.
The offset is fully attacker-controlled (via assoclen and cryptlen). The value is fully attacker-controlled (via AAD bytes). The write is deterministic — no race, no retry, no timing window.
Exploitation: 732-byte Python script to root
The published PoC chains the primitive into a setuid escalation:
- Pick a root-owned setuid binary the attacker can
read()(/usr/bin/su,/usr/bin/passwd,/usr/bin/mount, etc.). - Choose a 4-byte patch site inside the binary’s
.text. The PoC overwrites a conditional branch insu’s authentication path with aJMP-equivalent that skips authentication and lands directly on the privileged code path. - Use the AF_ALG + splice + authencesn primitive to write the 4-byte patch into the page-cache copy of the binary.
execve()the now-corrupted binary. The kernel page-cache pages back the executable mapping, so the.textpage at that offset is the attacker-modified one. setuid stays intact (the inode metadata is untouched). The attacker gets a root shell.
The on-disk file is unchanged — the corruption lives only in cached memory. Reboot or echo 3 > /proc/sys/vm/drop_caches (root-only) clears it. From userspace there is no way for a non-root observer to notice.
The PoC’s portability is the striking part. It targets the algorithmic flaw, not kernel offsets — so the same 732-byte script works across Ubuntu, RHEL, Amazon Linux, SUSE, Debian, Arch.
Why it survived 9 years
- The
algif_aeadin-place optimisation was a performance patch reviewed against contemporary AEAD templates (GCM, CCM, authenc). None of those have the post-output scratch write thatauthencesndoes. The bug is interaction between two pieces of code maintained by different sub-teams. authencesnis rare — it exists for IPsec ESN and is otherwise unused. Most fuzzers exercising AEAD APIs concentrate on GCM/ChaCha20-Poly1305.- Page-cache writes are silent. There is no oops, no detectable corruption, no audit trail — the write is to a legitimate kernel data structure (the scatterlist), and the kernel never notices the page is read-only-from-userland.
Theori’s Xint Code AI scanner found it after Taeyang Lee identified AF_ALG + splice as a page-cache exposure vector and pointed the scanner at the crypto subsystem. Roughly an hour of automated analysis surfaced Copy Fail as the highest-severity finding.
Detection / mitigations
| Layer | Action |
|---|---|
| Kernel | Apply 6.18.22 / 6.19.12 / 7.0 or distro-backport of commit a664bf3d603d. |
| Pre-patch | Blacklist algif_aead via /etc/modprobe.d/blacklist-algif_aead.conf (breaks any userspace consumer of the AF_ALG AEAD interface — usually no one). |
| Pre-patch / hardened | Block socket(AF_ALG, ...) via seccomp for untrusted workloads. The kernel-level alternative is CONFIG_CRYPTO_USER_API_AEAD=n. |
| Detection | Audit socket(AF_ALG) + splice() + recv() = -EBADMSG sequences from non-root processes. The PoC is conspicuous because nothing legitimate uses authencesn from userspace. |
Timeline
- 2026-03-23 — Reported to the Linux kernel security team.
- 2026-04-01 — Patch
a664bf3d603dcommitted to mainline; backports begin. - 2026-04-29 — Coordinated public disclosure (copy.fail, Xint blog, kernel.org advisory).
References
- Copy Fail — CVE-2026-31431 — canonical writeup — https://copy.fail/
- Theori / Xint — Copy Fail: 732 Bytes to Root on Every Major Linux Distribution — https://xint.io/blog/copy-fail-linux-distributions
- Bugcrowd — What we know about Copy Fail (CVE-2026-31431) — https://www.bugcrowd.com/blog/what-we-know-about-copy-fail-cve-2026-31431/
- The Register — Linux cryptographic code flaw offers fast route to root — https://www.theregister.com/2026/04/30/linux_cryptographic_code_flaw/
- heise online — “Copy Fail”: Linux root in all major distributions with 732 bytes of Python — https://www.heise.de/en/news/Copy-Fail-Linux-root-in-all-major-distributions-with-732-bytes-of-Python-11277657.html
- C port (cross-platform) — https://github.com/tgies/copy-fail-c
- Detection / response toolkit — https://github.com/rootsecdev/cve_2026_31431
