
A structured explanation of CVE-2026-31431 (Copy Fail), connecting the three kernel changes that introduced the vulnerability and enabled its exploitation.
A logic flaw in authencesn chained through AF_ALG and splice() into a controlled 4-byte write in the page cache of any readable file on the system. No race condition, no offsets, no compiled payload. The same 732-byte script gains root on every Linux distribution since 2017.
CVE-2026-31431 - Copy Fail is a logic flaw in the Linux kernel's authencesn cryptographic template. It allows an unprivileged local user to perform a controlled 4-byte write into the page cache of any readable file on the system, without modifying the file on disk.
The bug does not exist in any of the three components individually. It emerges from their interaction:
2011 ──────────────────────────────────────────────────────────────────────
- authencesn added to the kernel (a5079d084f8b).
- Uses the caller's destination scatterlist as scratch space.
- Reorder ESN bytes before HMAC computation.
- Only caller: internal xfrm layer. Harmless.
2015 ──────────────────────────────────────────────────────────────────────
- algif_aead.c gains AEAD support with splice() path (104880a6b470).
- splice() can deliver page cache pages to the TX scatterlist.
- AF_ALG uses out-of-place operation: req->src != req->dst.
- Page cache pages remain read-only. Not exploitable.
2017 ──────────────────────────────────────────────────────────────────────
- In-place optimization in algif_aead.c (72548b093ee3).
- Copies AAD+CT to RX buffer but chains authentication tag pages via sg_chain().
- Sets req->src = req->dst.
- Page cache pages now reside in WRITABLE dst.
- authencesn writes past boundary → page cache corruption.
2026 ──────────────────────────────────────────────────────────────────────
- Copy Fail - CVE-2026-31431. Discovered by Theori / Xint Code.
- Exploitable across all distros since 2017.
AF_ALG (AF_ALG = 38) is a socket type that exposes the kernel cryptographic API to unprivileged userspace. An unprivileged process can:
It is enabled by default in the kernel configuration of all major distributions (CONFIG_CRYPTO_USER_API_AEAD=y).
splice(2) transfers data between file descriptors without copying - it passes references to pages, not copies. The relevant flow:
open("/usr/bin/su") -> fd_file
pipe() -> pipe_rd, pipe_wr
# moves N bytes from the file into the pipe
# the pipe buffer now contains a reference to the same physical page in the page cache
splice(fd_file, pipe_wr, N)
# delivers that reference to the AF_ALG socket
# the TX scatterlist of algif_aead now points to the page cache page of /usr/bin/su
splice(pipe_rd, alg_fd, N)
The TX scatterlist of the AF_ALG socket contains direct references to the same physical pages used by the kernel for every read(), mmap(), and execve() of the file. No copy is involved.
Commit 72548b093ee3, algif_aead.c. For decryption, the implementation:
TX SGL (input from splice):
[ page cache page: AAD || CT || Tag ]
In-place operation:
RX SGL (req->dst):
[ user buffer: AAD (copy) || CT (copy) ] --sg_chain--> [ Tag (page cache pages) ]
req->src = req->dst = RX SGL
Result: page cache pages from /usr/bin/su are now part of the WRITABLE scatterlist passed to the crypto algorithm.
authencesn is the kernel AEAD wrapper used by IPsec with Extended Sequence Numbers (RFC 4303). IPsec uses 64-bit sequence numbers:
Only seqno_lo is transmitted on the wire; seqno_hi is implicit context. For HMAC computation, authencesn needs to rearrange these bytes: seqno_hi at the beginning and seqno_lo at the end of the hash input.
It performs this rearrangement by using the caller's destination scatterlist as scratch space:
/* crypto/authencesn.c - crypto_authenc_esn_decrypt() */
// [1] Read bytes 0-7 of the AAD from dst
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0);
// [2] Overwrite dst[4..7] with seqno_hi (temporary modification for HMAC)
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1);