CVE-2026-31431 Copy Fail
A study-while-writing post on a juicy Linux kernel bug. Corrections welcome via email.
TL;DR
- CVE-2026-31431 (Copy Fail) is a logic bug in the Linux kernel’s
algif_aead(the AEAD interface of AF_ALG, the userspace kernel crypto API). - An unprivileged local user can trigger a deterministic, controlled 4-byte write into the page cache of any readable file.
- Used against
/usr/bin/suor similar setuid binaries, this gives root. - The bug was introduced by an “in-place optimization” commit in 2017 — it sat there for around 8 years.
1. Introduction
On April 29, 2026, the Korean offensive security firm Theori and their research team Xint Code disclosed a vulnerability they nicknamed “Copy Fail.”
This is a descendant of Dirty Pipe (CVE-2022-0847) — a local privilege escalation that achieves root by directly modifying the page cache. The difference: the stage isn’t pipes this time, it’s the kernel crypto subsystem (AF_ALG).
There’s also a quietly significant subplot here: this bug was reportedly discovered using an AI-assisted vulnerability discovery system called “Xint Code.” If Theori’s account is accurate, it surfaced after roughly one hour of scanning against Linux’s crypto/ subsystem.
2. Background: AF_ALG and algif_aead
First, a primer on the attack surface is AF_ALG.
- AF_ALG is the kernel mechanism that exposes in-kernel cryptographic algorithms to userspace via a socket interface.
- Primary use cases: backing dm-crypt/LUKS, tools like
cryptsetup,kcapi-*, etc. - Each algorithm family has its own module:
algif_hash,algif_skcipher,algif_aead, … - The one that bit us this time is
algif_aead(for AEAD — Authenticated Encryption with Associated Data).
A typical usage flow looks like:
socket(AF_ALG, SOCK_SEQPACKET, 0)
└─> bind(...) // pick algorithm, e.g. authencesn(hmac(sha256),cbc(aes))
└─> accept(...)
└─> sendmsg() / splice() // feed input data
└─> recvmsg() // receive output
The critical piece is splice(). It enables zero-copy transfer from a file descriptor into the socket, which means page cache pages of a file end up directly in the kernel’s crypto-operation scatterlist.
3. Root Cause
History
AF_ALG gained AEAD support in 2015. At that point req->src and req->dst were separate scatterlists:
src(input — could contain page cache pages) was read-only-dst(output — pointing to user-supplied buffers) was the write target
This separation made page-cache writes structurally impossible.
The 2017 optimization
The offending commit is 72548b093ee3, landed in 2017. It changed AEAD operations to run in-place (same buffer for input and output).
Concretely:
- AAD (Associated Data) and ciphertext are memcpy’d from the TX scatterlist into the RX buffer (actual copies).
- However, the tag pages are chained by reference into the RX side using
sg_chain(). - The code then sets
req->src = req->dst. Now page cache pages live inside the writable destination scatterlist.
So the code looks safe, but the tag portion was the part that wasn’t copied. That omission is fatal and it’s where the name “Copy Fail” comes from.
The trigger: authencesn’s scratch write
Having page cache pages in a writable scatterlist is dangerous, but on its own it doesn’t yet cause a write. The trigger is the authencesn(hmac(sha256), cbc(aes)) AEAD algorithm.
authencesn performs a 4-byte “scratch write” at offset assoclen + cryptlen for ESN (Extended Sequence Number) bookkeeping. This was supposed to land in a safe area inside the output buffer — but because the output scatterlist now extends into page cache pages, those 4 bytes land inside the cached page of someone else’s file.
The 4 bytes written come from AAD bytes 4–7 (the seqno_lo field) of the sendmsg() payload — fully attacker-controlled.
So in one sentence:
Bind an AF_ALG socket to
authencesn, feed it the target file viasplice(), callrecvmsg()— and 4 attacker-controlled bytes land in the target file’s page cache.
4. Aren’t there any safety mechanisms guarding page cache writes?
Practically speaking, Linux has no general mechanism that stops kernel code from writing to the page cache. A normal write(2) goes through VFS, where permissions get checked — but Copy Fail never touches a file-write API. It reaches the page cache through the crypto API, so VFS never gets a say. This is a big part of why page-cache-write bugs keep recurring: the safety of the page cache rests almost entirely on the correctness of the code that touches it.
References
- Theori / Xint official writeup (xint.io)
- PoC: theori-io/copy-fail-CVE-2026-31431 (GitHub)
- NVD - CVE-2026-31431
- Related: Dirty Pipe (CVE-2022-0847)
See you later.