Build1 publisher2 min readPublished
Copy Fail chains AF_ALG, splice and authencesn into a repeatable page-cache write
Copy Fail, tracked as CVE-2026-31431, lets any unprivileged local user write a chosen four bytes into the page cache of any readable file. Because it fires every time the right syscalls run in order, timing-based mitigations do not apply.
The Engineer · Build desk

What happened
- In 2017, commit 72548b093ee3 added an in-place optimization that collapsed the algif_aead decrypt path's separate source and destination scatterlists into shared memory.
- Before 2017 that stray write hit the tail of the user's own receive buffer; after the optimization it walks into the spliced file's live page cache.
- GCM, CCM and plain authenc all keep their writes inside the output region, and authencesn is the only kernel AEAD that reaches past it.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- exposure AF_ALG needs no privileges to open, so on a shared or multi-tenant host every local account can reach the vulnerable path, not only privileged ones.
- constraint Because the trigger is deterministic, timing or probabilistic mitigations reduce nothing; the exposure stands until the code path is patched or made unreachable.
- precedent The in-place design trusts every AEAD to stay inside its output region with nothing enforcing it, so another algorithm with the same scratch-write habit would reopen the same hole.
AF_ALG, splice() and authencesn each look reasonable in isolation. According to a dev.to deep dive on the flaw, the bug lives where they meet. [14]
splice() is what makes the combination dangerous. Instead of copying a file's contents into your buffer, it hands a pipe or socket a reference to the kernel's page cache, so the receiver holds a pointer into the live in-memory cache of that file. [5]
On decrypt, algif_aead lays the request out as AAD, then ciphertext, then the auth tag. The AAD and ciphertext are copied into your receive buffer for real. The tag is not copied. Its scatterlist entries are chained onto the output by reference with sg_chain(), and the kernel then sets req->src and req->dst to the same combined chain. [8][9] When the input was spliced from a file, those chained tag pages are the file's page cache. [5][13]
That layout is safe only if the algorithm writes nowhere but its declared output region, and nothing in the API enforces it. [9] authencesn breaks the assumption. Its decrypt routine uses the destination as scratch space to shuffle the ESN counter, and one of those writes puts seqno_lo's four bytes at offset assoclen + cryptlen, exactly where the auth tag begins. [6][10] Those bytes land four past the AEAD contract of AAD followed by plaintext, and nothing puts the originals back: the tail routine reads seqno_lo out again but never restores what was there. [10][11]
Before the 2017 in-place optimization the stray write hit the end of your own receive buffer and did nothing. After it, the same write walks into the spliced file's page cache. [13] GCM, CCM and plain authenc all keep their writes inside the output region; authencesn is the only kernel AEAD that reaches past it. [12]
The write is deterministic. It does not hinge on winning a race, so mitigations built on timing or retries are beside the point. [1] The dev.to write-up characterizes the step from this 4-byte primitive to a root shell as something that "just takes patience." [3]
That write-up documents how the bug works, and its text ends mid-sentence; it stops short of an affected-kernel range, a CVE severity, a fix commit, or mitigation steps. Two responses follow from how the bug works: patch the code so authencesn stops writing past its output region, or cut off reach to authencesn through AF_ALG, the crypto socket family that needs no privileges to open. [4] The shared-buffer path has been in mainline since commit 72548b093ee3 in 2017, so kernels built on it over roughly nine years are candidates. [7][1]
What to watch
- Whether kernel maintainers land a fix that restores the overwritten bytes or bounds authencesn's scratch write.
- Whether distributions publish affected-version ranges and a CVE severity, which the write-up does not include.
- A working local-privilege-escalation proof of concept, since the write-up stops at the 4-byte write primitive.