
Proof-of-concept exploit for CVE-2026-31431, a Linux kernel local privilege escalation via AF_ALG page cache write, achieving root on major distributions.
algif_aead Page Cache Write ("Copy Fail")
Author: Van Glenndon Enad
Original Discovery: Theori / Xint Code Research Team (Taeyang Lee)
Published: April 29, 2026
Severity: High
CVSS v3.1 Score: 7.8
CVSS v3.1 Vector: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE: CWE-787 (Out-of-bounds Write), CWE-269 (Improper Privilege Management)
CVE-2026-31431, publicly nicknamed "Copy Fail," is a high-severity local privilege escalation (LPE) vulnerability in the Linux kernel's algif_aead module — the AEAD cipher interface of the kernel's userspace cryptographic API (AF_ALG). The flaw originates from a performance optimization (in-place operation) introduced in 2017 via commit 72548b093ee3, which inadvertently allowed page-cache-backed file pages to be placed into the writable destination scatterlist during an AEAD cryptographic operation.
By chaining three kernel subsystems — AF_ALG sockets, the splice() system call, and the authencesn algorithm's scratch-write behavior — an unprivileged local user can perform a controlled 4-byte write into the page cache of any readable file. Targeting a setuid binary such as /usr/bin/su, this write corrupts the in-memory executable image without modifying the file on disk, thereby bypassing on-disk file integrity tools. The resulting privilege escalation to root is deterministic — no race condition, no per-distribution kernel offsets, and no special privileges are required. A publicly released 732-byte Python PoC exploit delivers root shells on Ubuntu, Amazon Linux, RHEL, and SUSE in a single unmodified run.
| Component | Details |
|---|---|
| Affected Subsystem | crypto/algif_aead.c — Linux kernel AF_ALG AEAD interface |
| Vulnerability Introduced | Linux kernel 4.14 (2017), commit 72548b093ee38a6d4f2a19e6ef1948ae05c181f7 |
| Fixed Versions | 6.18.22, 6.19.12, 7.0 |
| Fix Commit | a664bf3d603dc3bdcf9ae47cc21e0daec706d7a5 |
| Verified Distros | Ubuntu 24.04 LTS, Amazon Linux 2023, RHEL 10.1, SUSE 16 |
| Implicitly Affected | Debian, Arch, Fedora, Rocky, AlmaLinux, Oracle Linux, and any distro running an unpatched kernel built since 2017 |
The vulnerability has been silently present in every mainstream Linux distribution for nearly nine years. According to Theori, AF_ALG is enabled in virtually every distro's default kernel configuration, meaning no special build flags or configurations are needed for a system to be vulnerable.
The Linux kernel exposes cryptographic primitives to userspace through the AF_ALG socket interface (crypto/algif_aead.c). In 2017, a performance optimization was merged that allowed algif_aead to perform AEAD operations in-place — reusing the source memory buffer as the destination — to avoid unnecessary data copying.
The flaw emerges when userspace feeds input into the AF_ALG socket via the splice() system call. In this case, the pages placed into the source scatterlist are page cache pages — shared, kernel-managed memory backing the spliced file. Due to the in-place optimization setting req->src = req->dst, these page cache pages end up in the writable destination scatterlist. The authencesn algorithm subsequently performs a scratch write at dst[assoclen + cryptlen], which resolves to an offset within those page cache pages — effectively writing attacker-controlled data into the in-memory image of the spliced file.
Because page cache is shared across the entire host including containers, a write from one process affects the cached pages of that file for every process and container on the same kernel.
The offending change in algif_aead.c set req->src = req->dst and chained tag pages from the source scatterlist into the output scatterlist via sg_chain():
/* 2017 in-place optimization — commit 72548b093ee3 */
req->src = req->dst; /* source == destination */
sg_chain(dst, n + 1, src_tag); /* tag pages chained into writable dst */
When splice() is used to feed a file into the socket, the scatterlist pages are page-cache-backed, not private anonymous memory. Chaining them into the writable dst scatterlist violates the assumption that the destination is writable private memory.
authencesn Scratch WriteThe authencesn template writes a sequence number scratch value (seqno_lo, bytes 4–7 of the AAD) at dst[assoclen + cryptlen]. Because dst now contains page cache pages from the spliced file, this write lands at an attacker-controlled offset within the file's in-memory image:
/* authencesn scratch write — offset determined by assoclen + cryptlen */
scatterwalk_map_and_copy(seqno, dst,
req->assoclen + req->cryptlen,
sizeof(seqno), 1); /* writes into page cache */
The 4 bytes written correspond to seqno_lo, which the attacker controls via the AAD payload sent through sendmsg().
AF_ALG socket (SOCK_SEQPACKET)
│
│ splice() — delivers file-backed pages into socket
▼
algif_aead in-place optimization
│ req->src = req->dst
│ page-cache pages land in writable scatterlist
▼
authencesn scratch write
│ writes seqno_lo at dst[assoclen + cryptlen]
│ = attacker-chosen 4 bytes at attacker-chosen file offset
▼
page cache corruption (no on-disk change)
The fix (a664bf3d603d) reverts the in-place optimization entirely — algif_aead now always operates out-of-place, allocating a separate destination buffer. Since source and destination now come from different mappings, page-cache pages in src can never be reached by the dst write path.
| Requirement | Notes |
|---|---|
| Local unprivileged user account | No elevated permissions needed |
| Kernel built from 2017 onward (≥ 4.14) | Covers effectively all mainstream distros |
AF_ALG (CONFIG_CRYPTO_USER_API) enabled | Default in virtually all distro kernel configs |
algif_aead module loadable/loaded | Autoloaded on first AF_ALG socket creation |
At least one readable setuid binary | e.g., /usr/bin/su, /usr/bin/sudo |
| Python 3.10+ (for the public PoC) | Only os, socket, zlib from stdlib |
Notably absent from the prerequisites: network access, kernel debugging features, CAP_SYS_ADMIN, pre-loaded kernel modules, or any pre-existing primitives. The attack surface is entirely local and self-contained.