Overview
| Attribute | Value |
|---|---|
| CVE ID | CVE-2026-31431 |
| Name | Copy Fail |
| CVSS Score | ~8.4 (High) |
| Type | Local Privilege Escalation (LPE) |
| Affected | All major Linux distributions since 2017 |
| Tested | Ubuntu 24.04, Amazon Linux 2023, RHEL 14.3, SUSE 16 |
| Kernels | 6.12, 6.17, 6.18 (and all back to 2017) |
| Exploit Size | 732 bytes (Python script) |
| Dependencies | Python 3.10+ (for os.splice), standard library only |
| Race Condition | None — deterministic, straight-line exploit |
| Stealth | Page cache corrupted; on-disk file and checksums unchanged |
What Makes Copy Fail Different
Linux kernel privilege escalation bugs are not new. But Copy Fail stands apart from its predecessors in four critical ways:
| Bug | Race Required? | Crash Risk? | Version-Specific? | Size |
|---|---|---|---|---|
| Dirty Cow (CVE-2016-5195) | ✅ Yes | High | No | Large |
| Dirty Pipe (CVE-2022-0847) | ❌ No | Low | Yes | Medium |
| Copy Fail (CVE-2026-31431) | ❌ No | None | No | Tiny |
Portable. The same exact script works on every tested distribution and architecture. No per-distro offsets. No recompilation. No version checks.
Tiny. The entire exploit is a short Python script using only standard library modules (os, socket, zlib). No compiled payloads. No dependency installation.
Stealthy. The write bypasses the ordinary VFS write path. The corrupted page is never marked dirty by the kernel’s writeback machinery. Standard file integrity tools comparing on-disk checksums will miss it — the on-disk file is unchanged. Only the in-memory page cache is corrupted.
Cross-container impact. The page cache is shared across all processes on a system, including across container boundaries. Copy Fail is not just a local privilege escalation. It is a container escape primitive.
The Root Cause: Three Changes That Intersected
This bug is the result of three separate kernel changes, each innocent in isolation, combining into a critical vulnerability.
2011: authencesn Added to Kernel
authencesn was introduced (a5079d084f8b) to support IPsec ESP’s 64-bit Extended Sequence Numbers (RFC 4303). From the start, it used the caller’s destination buffer as scratch space for ESN byte rearrangement. At the time, this was harmless — the only caller was the kernel’s internal xfrm layer.
2015: AF_ALG Gained AEAD Support with splice()
AF_ALG sockets gained the ability to perform AEAD operations, with a splice() path that could deliver page cache pages directly into crypto scatterlists. However, req->src and req->dst were separate scatterlists — page cache pages were read-only. Not yet exploitable.
2017: The In-Place Optimization
A performance optimization (72548b093ee3) set req->src = req->dst, making the operation in-place. Page cache pages from splice() were now in the writable destination scatterlist. And authencesn’s scratch write at dst[assoclen + cryptlen] — past the AEAD tag boundary — now walked directly into those page cache pages.
Input SGL: AAD || CT || Tag
| | ^
| copy | | sg_chain (references page cache pages)
v v |
Output SGL: AAD || CT -----+
|
+-- chained from TX SGL (file's page cache)
The Trigger: authencesn’s Scratch Write
For AEAD decryption, the output contract is clear: the destination buffer receives AAD || plaintext, exactly assoclen + (cryptlen - authsize) bytes.
authencesn breaks this contract. In crypto_authenc_esn_decrypt():
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0); // read AAD bytes 0–7
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1); // overwrite dst[4..7]
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1); // write past tag
The third call writes 4 bytes at assoclen + cryptlen — past the AEAD tag. In the in-place path with page cache pages chained into the output scatterlist, this writes directly into the kernel’s cached copy of the target file.
The HMAC computation then fails (the ciphertext is fabricated), recvmsg() returns an error, but the 4-byte controlled write persists.
The attacker controls:
- Which file: Any file readable by the current user
- Which offset: Determined by splice file offset, splice length, and assoclen
- Which value: The 4-byte overwrite comes from bytes 4–7 of the attacker-constructed AAD
The Exploit
Target
Default target: /usr/bin/su (setuid-root binary present on all tested distributions)
Steps
- Socket setup — Open
AF_ALGsocket, bind toauthencesn(hmac(sha256),cbc(aes)), set key - Construct the write — For each 4-byte chunk:
sendmsg()provides AAD (bytes 4-7 = payload chunk)splice()provides target file’s page cache pages as ciphertext/tag- AEAD parameters chosen so
dst[assoclen + cryptlen]falls on target offset
- Trigger —
recv()triggers decrypt → 4-byte write to page cache - Execute —
execve("/usr/bin/su")loads corrupted page cache → shellcode runs as root
PoC Structure
# Open AF_ALG socket
a = socket.socket(38, 5, 0) # AF_ALG, SOCK_SEQPACKET
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
# ... set key, accept request socket u ...
# Construct AAD with payload chunk in bytes 4-7
u.sendmsg([b"A"*4 + payload_chunk], [cmsg_headers], MSG_MORE)
# Splice target file into pipe, then into AF_ALG socket
os.splice(target_fd, pipe_wr, offset)
os.splice(pipe_rd, alg_fd, offset)
# Trigger decrypt — recvmsg() returns error but write persists
u.recv(...)
Demo Results
| Distribution | Kernel | Result |
|---|---|---|
| Ubuntu 24.04 LTS | 6.17.0-1007-aws | ✅ Root |
| Amazon Linux 2023 | 6.18.8-9.213.amzn2023 | ✅ Root |
| RHEL 14.3 | 6.12.0-124.45.1.el10_1 | ✅ Root |
| SUSE 16 | 6.12.0-160000.9-default | ✅ Root |
The Fix
Patch: a664bf3d603d
Reverts AF_ALG AEAD to out-of-place operation:
req->src→ TX SGL (may contain page cache pages from splice)req->dst→ RX SGL (user’s recvmsg buffer)- Removes
sg_chain()mechanism that linked page cache tag pages into writable destination
// Before (vulnerable):
req->src = req->dst // same scatterlist, page cache pages writable
// After (fixed):
req->src = tsgl_src // TX SGL (read-only)
req->dst = rsgl_dst // RX SGL (user buffer)
Commit message: “There is no benefit in operating in-place in algif_aead since the source and destination come from different mappings.”
Remediation
Immediate Mitigation
# Block AF_ALG socket creation via seccomp
# OR blacklist the algif_aead module:
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead 2>/dev/null
Proper Fix
- Patch the kernel — Update to kernel with fix commit
a664bf3d603d - Update distribution kernel packages — All major distros should ship fixes
Disclosure Timeline
| Date | Event |
|---|---|
| 2026-03-23 | Vulnerability reported to Linux kernel security team |
| 2026-03-24 | Initial acknowledgment |
| 2026-03-25 | Patches proposed and reviewed |
| 2026-04-01 | Patches committed to mainline kernel |
| 2026-04-22 | CVE-2026-31431 assigned |
| 2026-04-29 | Public disclosure |
Impact Assessment
For Attackers
- Extremely valuable for engagements with Linux targets
- Works on virtually all unpatched Linux systems
- No crash risk, no race conditions — highly reliable
- Cross-container impact makes it valuable for cloud/container assessments
- The 732-byte Python PoC means it can be embedded in other tools or delivered via simple download
For Defenders
- Patch immediately — kernel update required
- Detection difficulty — page cache corruption leaves no on-disk traces
- Monitor for
AF_ALGsocket usage by unprivileged users - Container isolation — page cache sharing means this escapes containers
- File integrity monitoring — traditional checksum-based FIM won’t catch this; consider runtime integrity monitoring
How It Was Found
This finding was AI-assisted. Theori researcher Taeyang Lee had mapped out the AF_ALG attack surface from earlier kernelCTF work. He suspected scatterlist page provenance was an underexplored vulnerability source.
Using Xint Code, the team scaled analysis across the entire crypto subsystem. The operator prompt was simple:
This is the linux crypto/ subsystem. Please examine all codepaths reachable from userspace syscalls. Note: splice() can deliver page-cache references of read-only files to crypto TX scatterlists.
After about an hour, Copy Fail was the highest severity output.
References
- Original disclosure: https://xint.io/blog/copy-fail-linux-distributions
- Linux kernel fix: a664bf3d603d
- Xint Code: https://code.xint.io/
- Theori Research: https://theori.io/
Last Updated: 2026-04-29 Source: Xint Code Research Team / Theori (Taeyang Lee)