Hook the audience immediately — no context, straight to the impact.
HOOK0:00 – 0:45
[VISUAL: Terminal. User prompt shows uid=1001(xint). No special tools visible.]
This is a regular Linux server. No root access. No special privileges. The user you're looking at is uid 1001 — a totally normal account.
Watch what happens when I run a single Python script. 732 bytes. Standard library only. No compilation. No binary payloads.
[VISUAL: Run the script. Pause for effect. Root shell appears.]
That's a root shell. From an unprivileged user. On Ubuntu 24.04. The same script — no changes — also works on Amazon Linux, RHEL, and SUSE.
This is Copy Fail. CVE-2026-31431. A bug that lived silently in the Linux kernel for nine years.
INTRODUCE0:45 – 1:30
[VISUAL: Title card with CVE number. Channel art. Stats.]
Today we're doing a complete technical breakdown. We'll cover the kernel internals that made this possible, the exact sequence of commits that accidentally created the vulnerability, and why standard file integrity tools would have completely missed it — because this bug doesn't touch the disk at all.
There's a lot of depth here. Grab a coffee. By the end, you'll understand AF_ALG sockets, the Linux page cache, cryptographic scatterlists, and one of the most elegant — and terrifying — exploit chains in recent kernel history.
Let's go.
Segment 01 · 1:30 – 3:30
What Makes This Different
Context vs prior Linux kernel exploits — set the stakes.
You've probably heard of Dirty Cow — CVE-2016-5195. Devastating privilege escalation, but it required you to win a race condition in the VM subsystem's copy-on-write path. It sometimes needed multiple retries. It sometimes crashed the machine.
Dirty Pipe — CVE-2022-0847 — was version-specific. You needed precise pipe buffer manipulation and it only worked on certain kernel versions.
Copy Fail has none of those caveats. It is a straight-line logic flaw. No racing. No retries. No version-specific offsets. No compilation. Same script. Every major distribution. Every time.
STEALTHINESS2:45 – 3:30
[VISUAL: Diagram — disk file vs page cache, showing divergence]
Here's what makes this especially nasty from a defense standpoint: the bug never touches the disk. The on-disk file is completely unchanged. If you were running tripwire, AIDE, or any file integrity monitor that checks SHA256 hashes of files on disk, it would report everything as clean.
What gets corrupted is the page cache — the in-memory representation of the file that the kernel hands to every process that reads or executes it. The page is never marked dirty for writeback. The kernel has no idea anything happened.
And because the page cache is shared across the entire host, including across container boundaries — this is also a container escape. We'll get to that in Part 2 of this series.
Segment 02 · 3:30 – 7:00
Background: AF_ALG & splice()
Kernel internals tutorial — the two primitives that make the bug possible.
To understand Copy Fail, you need to understand two Linux kernel features that most developers have never touched.
The first is AF_ALG — the kernel crypto socket interface. It's a special socket type, address family 38, that exposes the kernel's cryptographic subsystem directly to unprivileged userspace. You can open an AF_ALG socket, bind it to any cryptographic algorithm — AES-GCM, HMAC-SHA256, or in our case, an AEAD mode called authencesn — and then send data through it to be encrypted or decrypted. All without root. All from a normal user account.
This is a legitimate feature. It exists so applications can do hardware-accelerated cryptography using kernel drivers without needing special privileges. But it also means any user can interact with the kernel's entire crypto subsystem.
The second piece is splice() — a Linux syscall designed for zero-copy data transfer. Normally when you move data between file descriptors, the kernel copies it through a userspace buffer. splice() skips that. It moves page references — pointers to the same physical memory pages — instead of copying bytes.
So if you splice from a file into a pipe, and then splice from that pipe into an AF_ALG socket, what ends up in the socket's input buffer are direct references to the kernel's page cache pages for that file. Not copies. The actual pages.
This is powerful and efficient. It's also the first half of our vulnerability. An unprivileged user can feed page cache pages — including pages from setuid binaries they can read but not write — directly into the crypto subsystem's input buffers.
VISUAL CUE6:30 – 7:00
[VISUAL: Hold on the splice diagram — emphasize "page cache pages by reference" in the scatterlist]
Keep that mental model locked in. Page cache pages. By reference. In the kernel's scatterlist. That's the setup. The exploit is what happens when a crypto algorithm doesn't respect the boundary of where it's allowed to write within that scatterlist.
Segment 03 · 7:00 – 12:00
Root Cause: Page Cache in the Writable Scatterlist
The structural flaw in algif_aead.c — in-place AEAD operation with page cache pages.
Now we get into the actual bug. For AEAD — Authenticated Encryption with Associated Data — decryption, the input format is: Associated Data, followed by ciphertext, followed by an authentication tag. The kernel's AF_ALG socket processes this in algif_aead.c.
In 2017, a performance optimization was added: instead of using separate source and destination scatterlists, algif_aead.c would do the operation in-place. It copies the AAD and ciphertext into the output buffer, but chains the authentication tag pages onto the end of the output scatterlist using sg_chain(). Then it sets req->src equal to req->dst. Both point to the same scatterlist.
DIAGRAM — scatterlist layout9:00 – 11:00
[VISUAL: Annotated scatterlist diagram — show RX buffer then chained page cache pages]
AEAD In-Place Scatterlist Layout (the vulnerable design):Input SGL:[ AAD ][ Ciphertext ][ Auth Tag ]copy ↓ copy ↓ ref ↓ (sg_chain)Output SGL:[ AAD ][ Ciphertext ][ Tag pages ]|←── RX buffer (user mem) ──→| |←── PAGE CACHE ──→|req->src ──┐req->dst ──┘──→ combined output SGL⚠ page cache pages now sit in a writable scatterlist⚠ only an offset boundary separates them from the write region⚠ nothing enforces this boundary at the API level
The result: page cache pages from your target file are now sitting inside the writable output scatterlist. The only thing protecting them is an implicit assumption — that every AEAD algorithm will confine its writes to the portion it owns. The API doesn't enforce this. The documentation doesn't mandate it. It's a silent contract.
And one algorithm breaks it.
Segment 04 · 12:00 – 17:00
The Trigger: authencesn's Scratch Write
How one AEAD algorithm's ESN byte shuffle writes past its boundary.
authencesn is an AEAD wrapper used by IPsec for Extended Sequence Number support. IPsec uses 64-bit sequence numbers to prevent replay attacks. These are split: a high half and a low half. The wire format only carries the low half — the high half is reconstructed on both ends.
For HMAC verification, authencesn needs to hash the sequence number in a specific order: high half first, low half appended at the end. To do this rearrangement, the kernel uses the destination scatterlist as scratch space — a temporary workspace for shuffling these bytes before computing the HMAC.
CODE — the three scatterwalk calls13:30 – 15:30
[VISUAL: Show crypto/authencesn.c, highlight the three scatterwalk_map_and_copy calls]
// In crypto_authenc_esn_decrypt()// AAD layout: [ seqno_hi (4B) | seqno_lo (4B) | ... ]// Call 1: read bytes 0-7 from dst into tmp[]scatterwalk_map_and_copy(tmp, dst, 0, 8, 0);
// Call 2: overwrite dst[4..7] with seqno_hi (byte swap for HMAC)scatterwalk_map_and_copy(tmp, dst, 4, 4, 1);
// Call 3: write seqno_lo at offset (assoclen + cryptlen)// ⚠ THIS IS PAST THE END OF THE OUTPUT BUFFERscatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);
// After HMAC fails, seqno_lo is read back from that position// BUT the original bytes there are NEVER restored// The write is permanent — even if decryption fails
Call 3 is the bug. It writes 4 bytes at assoclen + cryptlen — which is past the end of the ciphertext, into the authentication tag region. In the vulnerable in-place design, that tag region contains page cache pages chained from the input. So this write lands directly on the kernel's cached copy of whatever file you spliced in.
ATTACKER CONTROL15:30 – 17:00
The attacker controls three things completely:
✦
Which file — any file readable by the current user, including setuid binaries
✦
Which offset — by choosing the splice offset, assoclen, and cryptlen, the attacker selects exactly which 4 bytes in the file's page cache get overwritten
✦
Which value — the 4-byte write value comes from bytes 4–7 of the AAD, which the attacker constructs in sendmsg()
Controlled 4-byte write. Arbitrary offset. Any readable file. From an unprivileged account. That's a complete, deterministic write primitive.
Segment 05 · 17:00 – 22:00
How Three Commits Collided
The 9-year history of how reasonable changes stacked into a critical vulnerability.
HISTORY17:00 – 22:00
[VISUAL: Git commit timeline — three commits highlighted]
Here's what I find most fascinating about Copy Fail: no single commit is obviously wrong. The vulnerability emerges from the interaction between three separate changes made over six years by different developers solving different problems.
2011 — commit a5079d084f8b
authencesn added to support IPsec ESP Extended Sequence Numbers (RFC 4303)
Uses dst scatterlist as scratch space for ESN byte rearrangement. Safe at the time — only called from kernel's internal xfrm layer. Nobody else ever sees the scratch write.
2015 — commit 104880a6b470
authencesn converted to new AEAD interface. Introduces the assoclen + cryptlen offset write.
AF_ALG gains AEAD support the same year, but uses out-of-place operation. Page cache pages are in src (read-only). Scratch write goes to dst (user buffer). Not yet exploitable.
2017 — commit 72548b093ee3
algif_aead.c optimized for in-place AEAD operation
The 2017 commit set req->src = req->dst. Page cache pages from splice() are now chained into the writable destination scatterlist. authencesn's scratch write now crosses into them. The vulnerability is born. Nobody connects these three separate changes.
2026 — CVE-2026-31431
Copy Fail discovered, disclosed, and patched
9 years of silent exploitability. Fixed by reverting the 2017 in-place optimization.
This is a classic example of a "weird machine" bug — individually reasonable design decisions that interact to create unexpected, dangerous behavior at their intersection. No single commit is a bug. Together, they are.
Segment 06 · 22:00 – 28:00
The Exploit Walk-Through
How 732 bytes of Python turns a 4-byte write into root — conceptually.
CONCEPTUAL — 4 steps22:00 – 25:00
[VISUAL: Step-by-step diagram of the exploit chain]
The exploit itself is conceptually clean. Four steps:
1.
Socket setup: Open an AF_ALG socket, bind to authencesn(hmac(sha256),cbc(aes)). Set a key. Accept a request socket. No privileges required.
2.
Construct the write: For each 4-byte chunk of a shellcode payload, craft a sendmsg() + splice() pair. The AAD carries the bytes to write; the splice parameters determine the target offset in the file.
3.
Trigger: Call recv(). The kernel runs authencesn decrypt. It fails (the ciphertext is fake). But the 4-byte scratch write already landed on the page cache. The error is returned. The page cache corruption persists.
4.
Execute: Call execve("/usr/bin/su"). The kernel loads from page cache. The page cache now contains injected shellcode. Because su is setuid-root, the shellcode runs as uid 0. Root.
CODE — the core socket pattern25:00 – 27:00
[VISUAL: Annotated version of the core loop — educational, not operational]
# Conceptual structure of the write primitive:# 1. Open AF_ALG socket bound to authencesn
a = socket(38, 5, 0) # AF_ALG, SOCK_SEQPACKET
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
# 2. For each 4-byte chunk of payload:# sendmsg: AAD bytes 4-7 = the 4 bytes to write (seqno_lo)
u.sendmsg([b"A"*4 + payload_chunk], [cmsg_headers], MSG_MORE)
# splice: pipe the target file at the chosen offset# this puts page cache pages in the scatterlist
os.splice(target_fd, pipe_wr, offset)
os.splice(pipe_rd, alg_fd, offset)
# recv: triggers decrypt → authencesn scratch write → page cache write# recv returns an error (bad ciphertext) — but write already happenedtry: u.recv(8 + target_len)
except: pass# error expected and irrelevant# 3. After all chunks written, execute the corrupted setuid binary
os.execve("/usr/bin/su", [], {}) # shellcode runs as uid 0
STEALTH NOTE27:00 – 28:00
Notice: the kernel returns an error from recv() every single time. From the kernel's perspective, these are failed decryption attempts. No alarm is raised. No log entries about memory corruption. The 4-byte write happens silently, the HMAC check fails as expected, and execution continues normally.
The disk never changes. The page cache never gets flagged dirty. Standard monitoring sees nothing unusual. Only the in-memory execution environment is corrupted — and that's all you need.
Segment 07 · 28:00 – 33:00
The Fix & Remediation
The patch, affected distros, and immediate mitigations.
THE PATCH28:00 – 30:00
[VISUAL: Diff of the fix — before and after]
The fix is elegant and correct: revert algif_aead.c to out-of-place operation. Remove the 2017 optimization entirely.
// BEFORE (vulnerable): req->src == req->dst// page cache pages chained into the writable destinationaead_request_set_crypt(&areq->cra_u.aead_req,
rsgl_src, // RX SGL (same list)
areq->first_rsgl.sgl.sgt.sgl, // RX SGL
used, ctx->iv);
// AFTER (fixed): req->src = TX SGL, req->dst = RX SGL// page cache pages stay in the read-only sourceaead_request_set_crypt(&areq->cra_u.aead_req,
tsgl_src, // TX SGL (read from here)
areq->first_rsgl.sgl.sgt.sgl, // RX SGL (write here only)
used, ctx->iv);
req->src now points to the TX scatterlist — which may contain page cache pages from splice(). req->dst points to the RX scatterlist — the user's output buffer. The sg_chain mechanism is gone. Page cache pages are no longer in the writable destination. authencesn's scratch write can't reach them.
The commit message from Linus Torvalds' tree: "There is no benefit in operating in-place in algif_aead since the source and destination come from different mappings."
AFFECTED DISTROS30:00 – 31:30
Ubuntu 24.04 LTS
kernel 6.17.0-1007-aws
⬤ Confirmed exploitable
Amazon Linux 2023
kernel 6.18.8-9.213.amzn2023
⬤ Confirmed exploitable
RHEL 10.1
kernel 6.12.0-124.45.1.el10_1
⬤ Confirmed exploitable
SUSE Linux 16
kernel 6.12.0-160000.9-default
⬤ Confirmed exploitable
Any kernel from 2017 onward running an unpatched algif_aead is potentially vulnerable. Kernel lines 6.12, 6.17, and 6.18 all confirmed.
REMEDIATION STEPS31:30 – 33:00
✓
Update your kernel. The patch (commit a664bf3d603d) is in mainline. Update your distribution's kernel package immediately.
✓
Immediate mitigation: Disable the algif_aead module if you don't use AF_ALG AEAD in your applications: echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
✓
Seccomp: Block AF_ALG socket creation for containers that don't need cryptographic acceleration.
✗
File integrity monitoring alone is NOT sufficient. The on-disk file is never modified. sha256sum and AIDE will report files as clean even on a compromised system.
Segment 08 · 33:00 – 37:00
AI-Assisted Discovery
How Xint Code found this in ~1 hour, and what that means for security research.
HOW IT WAS FOUND33:00 – 36:00
[VISUAL: Xint Code scan result screenshot]
The researcher behind this, Taeyang Lee from Theori, had prior experience with kernelCTF research and had already mapped the AF_ALG attack surface. He had a hypothesis: the splice() path that delivers page cache references into the crypto subsystem might be an underexplored source of vulnerabilities if any algorithm didn't respect page provenance.
He pointed Xint Code — an AI security research tool — at the Linux crypto/ subsystem, with a simple operator prompt:
"This is the linux crypto/ subsystem. Please examine all codepaths reachable from userspace syscalls. Note one key observation: splice() can deliver page-cache references of read-only files (including setuid binaries) to crypto TX scatterlists."
One hour later, Copy Fail was the highest-severity output. The AI connected the splice() input path in algif_aead.c, the in-place optimization, and authencesn's out-of-bounds scratch write — a cross-file, multi-year connection that a human might not have made without deep familiarity with all three subsystems simultaneously.
IMPLICATIONS36:00 – 37:00
This matters for the future of security research. The insight — the hypothesis about page provenance in the splice path — came from a human with deep domain knowledge. The scaling across an entire subsystem, connecting behaviors in files that were never designed to interact, came from AI. The combination found something that had been hiding for nine years.
The blog post notes the same scan also found other high-severity vulnerabilities still in responsible disclosure. We'll be watching for those.
Segment 09 · 37:00 – 40:00
Outro & What's Next
SUMMARY37:00 – 38:30
Let's bring it together. Copy Fail — CVE-2026-31431 — is a logic bug born from three independent kernel changes made between 2011 and 2017:
1
authencesn using the destination scatterlist as scratch space for ESN byte shuffling (2011)
2
authencesn writing 4 bytes at assoclen + cryptlen — past its legitimate output region (2015)
3
algif_aead.c switching to in-place operation, chaining page cache pages into the writable destination (2017)
Each change was individually reasonable. Together, they created a deterministic, portable, stealthy, 4-byte controlled write into the page cache of any readable file. From any unprivileged account. On essentially all Linux distributions since 2017.
Patch your kernels. Disable algif_aead if you don't need it. And remember — your on-disk file hashes mean nothing if the page cache is compromised.
CTA & PART 2 TEASE38:30 – 40:00
[VISUAL: Subscribe CTA. Part 2 preview clip of Kubernetes node.]
Part 2 is coming: how Copy Fail escapes every major cloud Kubernetes platform. We'll walk through how the shared page cache means a container workload can corrupt binaries on the host — and what that means for your cloud security posture.
If you want to go deeper on the kernel internals we covered today — AF_ALG, scatterlists, splice() — I'll put resources in the description. Links to the original Xint blog post, the GitHub PoC repo, and the kernel commit that introduced the 2017 in-place optimization.
If this was useful, subscribe, and drop a comment — particularly if you're in cloud security or kernel development. I want to know what questions you have heading into Part 2.
See you in the next one.
Script complete. Total runtime ~40 minutes. 10 segments. Suitable for a long-form technical deep-dive aimed at security engineers, kernel developers, and informed enthusiasts.
Technical Reference
Kernel Internals Deep-Dive
Key concepts, syscalls, and data structures for the script's technical segments.
algif_aead_recvmsg() — entry point; sets up in-place scatterlist, calls aead_request_set_crypt()
▶
sg_chain() — links auth tag pages from TX SGL onto the end of RX SGL
▶
crypto_authenc_esn_decrypt() — the authencesn algorithm entry; calls scatterwalk_map_and_copy() 3 times
▶
scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, 1) — the out-of-bounds write; maps page cache page via kmap_local_page() and writes directly
▶
crypto_authenc_esn_decrypt_tail() — reads seqno_lo back but never restores original bytes; HMAC fails; error returned
Why File Integrity Monitoring Misses This
Normal write path (detected by FIM):
write() → VFS → mark page dirty → writeback → on-disk file changessha256sum changes ← FIM detectsCopy Fail write path (NOT detected):
authencesn scratch → kmap_local_page() → write directly to page cache memorypage NEVER marked dirtywriteback NEVER triggeredon-disk file UNCHANGEDsha256sum UNCHANGED ← FIM sees nothingBut execve() reads from page cache, not disk.Shellcode in page cache → runs as uid 0 when su is executed.
Disclosure Timeline
From Discovery to Patch
2011
bug root introduced
2017
became exploitable
~1hr
AI scan to find it
37d
report to patch
Coordinated Disclosure
2026-03-23
Vulnerability reported to Linux kernel security team
2026-03-24
Initial acknowledgment received
2026-03-25
Patches proposed and reviewed
2026-04-01
Patch committed to mainline kernel (commit a664bf3d603d)
Reverts algif_aead.c to out-of-place operation
2026-04-22
CVE-2026-31431 assigned
2026-04-29
Public disclosure — xint.io blog post, GitHub PoC, copy.fail site
Key Links for Video Description
🔗
Original blog post: xint.io/blog/copy-fail-linux-distributions