## https://sploitus.com/exploit?id=085D9715-58A9-5159-9C8A-387F4C3BCBD6
# CVE-2026-31431 "Copy Fail" โ Technical Deep Dive
**Severity:** CVSS 7.8 (High)
**Class:** Local Privilege Escalation
**Introduced:** Linux 4.14 (2017)
**Fixed:** commit `a664bf3d603d`
## 1. Background
CVE-2026-31431 is a local privilege escalation flaw in the Linux kernel's `algif_aead` module โ the AEAD socket interface of the kernel's userspace crypto API (`AF_ALG`).
The vulnerability lets an unprivileged user make use of the address family `AF_ALG` in the Crypto API of the Linux kernel to perform controlled 4-byte writes into the page cache, which backs the in-memory copies of files. By replacing code in the in-memory copy of a readable executable stored in the page cache, an attacker can escalate user privileges when any privileged process later runs that corrupted version of the file.
Unlike Dirty Cow (CVE-2016-5195), which required winning a race condition, Copy Fail is a straight-line logic flaw. It triggers without races, retries, or crash-prone timing windows.
## 2. Root Cause โ The 2017 In-Place Optimization
### 2.1 What Changed
Commit `72548b093ee3` ("crypto: algif_aead - switch to in-place processing") introduced an optimization that set `req->src == req->dst` inside `aead_recvmsg()`, eliminating a separate output buffer allocation.
This optimization caused `req->src` and `req->dst` to point to a combined scatterlist. Because of this, page cache pages from the `splice()` call were improperly chained directly into the writable destination scatterlist.
### 2.2 The Silent Invariant
The kernel's AEAD API defines a clear output contract: the destination buffer receives `AAD || plaintext`, exactly `assoclen + (cryptlen - authsize)` bytes. Nothing in the API enforces this contract, and nothing documents it as a requirement. The design assumes every AEAD algorithm will confine its writes to the intended destination. One algorithm did not.
## 3. Affected Files
```
crypto/
โโโ algif_aead.c โ in-place scatterlist bug (primary)
โโโ authencesn.c โ out-of-bounds scratch write (trigger)
```
## 4. Vulnerable Code
### 4.1 `crypto/algif_aead.c` โ The In-Place Assignment
```c
/* VULNERABLE โ post-2017 in-place path */
static int aead_recvmsg(struct socket *sock, struct msghdr *msg,
size_t ignored, int flags)
{
/* ... */
/*
* When userspace calls splice(2) on a file, areq->tsgl contains
* page cache pages of that file. Setting dst = src hands those
* page cache pages to the crypto engine as a writable output
* buffer.
*/
aead_request_set_crypt(req,
areq->tsgl, /* src */
areq->tsgl, /* dst โ identical pointer; root cause */
areq->cryptlen, iv);
}
```
### 4.2 `crypto/authencesn.c` โ The Out-of-Bounds Scratch Write
The `authencesn` template is an AEAD wrapper used by IPsec for Extended Sequence Number (ESN) support. IPsec uses 64-bit sequence numbers split into a high half (`seqno_hi`, bytes 0โ3 of the AAD) and a low half (`seqno_lo`, bytes 4โ7). The wire format carries only `seqno_lo`; `seqno_hi` is implicit.
```c
/* crypto/authencesn.c โ scratch write past output boundary */
static int crypto_authenc_esn_decrypt(struct aead_request *req)
{
/* ... */
/*
* authencesn temporarily stores seqno_hi at offset
* (assoclen + cryptlen) in req->dst โ one word past the
* legitimate output region.
*
* Under normal operation: hits kernel-owned memory.
* With in-place scatterlist: hits a page cache page.
*/
scatterwalk_map_and_copy(&seqno_hi,
req->dst,
req->assoclen + req->cryptlen, /* out-of-bounds offset */
sizeof(seqno_hi), /* 4 bytes */
1 /* write */);
/* authencesn does not restore these bytes on the failing path */
}
```
## 5. The Fix
**Upstream fix:** commit `a664bf3d603d` โ reverts the 2017 optimization.
### 5.1 `crypto/algif_aead.c` โ Separated Scatterlists
```c
/* FIXED โ separate src and dst */
aead_request_set_crypt(req,
areq->tsgl, /* src โ may contain page cache pages; read-only */
areq->rsgl, /* dst โ freshly allocated kernel buffer; writable */
areq->cryptlen, iv);
```
**Effect:** Page cache pages remain in `src`, which the crypto engine only reads. The `authencesn` scratch write at `assoclen + cryptlen` now lands in a kernel-allocated buffer that the process owns. No page cache page is ever reachable from the write path.
## 6. Exploitation Path
The following describes how the 4-byte write achieves full privilege escalation.
| Step | Action |
|------|--------|
| 1 | Open an `AF_ALG` socket configured for `authencesn(hmac(sha256),cbc(aes))` |
| 2 | `splice()` a readable setuid binary (e.g., `/usr/bin/sudo`) into the socket โ this places its page cache pages into `areq->tsgl` |
| 3 | Craft `assoclen` + `cryptlen` so the scratch write offset aligns with a target byte inside the binary's cached image |
| 4 | Call `recvmsg()` โ triggers the decrypt path, `authencesn` writes 4 attacker-controlled bytes into the page cache |
| 5 | The on-disk file is **unchanged**; integrity checkers, `inotify`, and `inode` timestamps are unaffected |
| 6 | Any subsequent execution of the binary maps the already-modified page cache pages โ the patched code runs with `euid=0` |
The write ends up inside the spliced file's cached data in memory, bypassing the file's permissions entirely.
## 7. Submitting a Patch to LKML
### 7.1 Identify the Maintainer
```bash
./scripts/get_maintainer.pl crypto/algif_aead.c
# Herbert Xu
# linux-crypto@vger.kernel.org
```
### 7.2 Validate the Patch
```bash
./scripts/checkpatch.pl 0001-crypto-algif_aead-revert-inplace.patch
```
### 7.3 Commit Message Format
```
crypto: algif_aead - restore separate src/dst scatterlists in recvmsg
Commit 72548b093ee3 ("crypto: algif_aead - switch to in-place
processing") set req->src == req->dst inside aead_recvmsg(). When
the caller uses splice(2), the source scatterlist contains page
cache pages of the spliced file. With the in-place optimization,
those same pages become the writable destination passed to the
AEAD transform.
authencesn writes 4 bytes at offset (assoclen + cryptlen) into
req->dst as scratch space for Extended Sequence Number
rearrangement and does not restore them on the error path. With
page cache pages present in dst, this write bypasses the VFS
permission layer and corrupts the in-memory image of the spliced
file without modifying the on-disk inode.
An unprivileged local user can exploit this to overwrite
arbitrary bytes in the page cache of any readable setuid binary
and escalate privileges to root.
Restore separate src and dst scatterlists so that page cache
pages are confined to the read-only source path.
Fixes: 72548b093ee3 ("crypto: algif_aead - switch to in-place processing")
Cc: stable@vger.kernel.org # 4.14+
Reported-by: Taeyang Lee
Signed-off-by: Your Name
```
### 7.4 Send via `git send-email`
```bash
git send-email \
--to=herbert@gondor.apana.org.au \
--cc=linux-crypto@vger.kernel.org \
--cc=stable@vger.kernel.org \
0001-crypto-algif_aead-revert-inplace.patch
```
### 7.5 Commit Message Conventions
| Element | Purpose |
|---------|---------|
| `subsystem: file - verb phrase` subject | Required format; under 72 characters |
| `Fixes: ("original title")` | Triggers automatic stable backport tooling |
| `Cc: stable@vger.kernel.org` | Flags patch for LTS branch backports |
| `Reported-by:` | Attribution to the discoverer |
| `Signed-off-by:` | Developer Certificate of Origin โ mandatory |
## 8. Affected Versions and Mitigation
Vulnerable kernels span Linux 4.14 through 7.0-rc, all 6.18.x prior to 6.18.22, and 6.19.x prior to 6.19.12.
**Interim mitigation** (no reboot required on most systems):
```bash
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
rmmod algif_aead 2>/dev/null || true
```
This workaround does not affect dm-crypt/LUKS, kTLS, IPsec/XFRM, OpenSSL, GnuTLS, NSS, or SSH.
## 9. References
- Theori/Xint advisory: `xint.io/blog/copy-fail-linux-distributions`
- NVD entry: `nvd.nist.gov/vuln/detail/CVE-2026-31431`
- Upstream fix: commit `a664bf3d603d` (mainline)
- Introducing commit: `72548b093ee3` (Linux 4.14, 2017)