Overview

AttributeValue
CVE IDCVE-2026-31431
NameCopy Fail
CVSS Score~8.4 (High)
TypeLocal Privilege Escalation (LPE)
AffectedAll major Linux distributions since 2017
TestedUbuntu 24.04, Amazon Linux 2023, RHEL 14.3, SUSE 16
Kernels6.12, 6.17, 6.18 (and all back to 2017)
Exploit Size732 bytes (Python script)
DependenciesPython 3.10+ (for os.splice), standard library only
Race ConditionNone — deterministic, straight-line exploit
StealthPage cache corrupted; on-disk file and checksums unchanged

What Makes Copy Fail Different

Linux kernel privilege escalation bugs are not new. But Copy Fail stands apart from its predecessors in four critical ways:

BugRace Required?Crash Risk?Version-Specific?Size
Dirty Cow (CVE-2016-5195)✅ YesHighNoLarge
Dirty Pipe (CVE-2022-0847)❌ NoLowYesMedium
Copy Fail (CVE-2026-31431)❌ NoNoneNoTiny

Portable. The same exact script works on every tested distribution and architecture. No per-distro offsets. No recompilation. No version checks.

Tiny. The entire exploit is a short Python script using only standard library modules (os, socket, zlib). No compiled payloads. No dependency installation.

Stealthy. The write bypasses the ordinary VFS write path. The corrupted page is never marked dirty by the kernel’s writeback machinery. Standard file integrity tools comparing on-disk checksums will miss it — the on-disk file is unchanged. Only the in-memory page cache is corrupted.

Cross-container impact. The page cache is shared across all processes on a system, including across container boundaries. Copy Fail is not just a local privilege escalation. It is a container escape primitive.


The Root Cause: Three Changes That Intersected

This bug is the result of three separate kernel changes, each innocent in isolation, combining into a critical vulnerability.

2011: authencesn Added to Kernel

authencesn was introduced (a5079d084f8b) to support IPsec ESP’s 64-bit Extended Sequence Numbers (RFC 4303). From the start, it used the caller’s destination buffer as scratch space for ESN byte rearrangement. At the time, this was harmless — the only caller was the kernel’s internal xfrm layer.

2015: AF_ALG Gained AEAD Support with splice()

AF_ALG sockets gained the ability to perform AEAD operations, with a splice() path that could deliver page cache pages directly into crypto scatterlists. However, req->src and req->dst were separate scatterlists — page cache pages were read-only. Not yet exploitable.

2017: The In-Place Optimization

A performance optimization (72548b093ee3) set req->src = req->dst, making the operation in-place. Page cache pages from splice() were now in the writable destination scatterlist. And authencesn’s scratch write at dst[assoclen + cryptlen] — past the AEAD tag boundary — now walked directly into those page cache pages.

Input SGL:     AAD  ||  CT  ||  Tag
                |       |        ^
                | copy  |        | sg_chain (references page cache pages)
                v       v        |
Output SGL:    AAD  ||  CT  -----+
                              |
                              +-- chained from TX SGL (file's page cache)

The Trigger: authencesn’s Scratch Write

For AEAD decryption, the output contract is clear: the destination buffer receives AAD || plaintext, exactly assoclen + (cryptlen - authsize) bytes.

authencesn breaks this contract. In crypto_authenc_esn_decrypt():

scatterwalk_map_and_copy(tmp, dst, 0, 8, 0);                           // read AAD bytes 0–7
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1);                           // overwrite dst[4..7]
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);     // write past tag

The third call writes 4 bytes at assoclen + cryptlen — past the AEAD tag. In the in-place path with page cache pages chained into the output scatterlist, this writes directly into the kernel’s cached copy of the target file.

The HMAC computation then fails (the ciphertext is fabricated), recvmsg() returns an error, but the 4-byte controlled write persists.

The attacker controls:

  • Which file: Any file readable by the current user
  • Which offset: Determined by splice file offset, splice length, and assoclen
  • Which value: The 4-byte overwrite comes from bytes 4–7 of the attacker-constructed AAD

The Exploit

Target

Default target: /usr/bin/su (setuid-root binary present on all tested distributions)

Steps

  1. Socket setup — Open AF_ALG socket, bind to authencesn(hmac(sha256),cbc(aes)), set key
  2. Construct the write — For each 4-byte chunk:
    • sendmsg() provides AAD (bytes 4-7 = payload chunk)
    • splice() provides target file’s page cache pages as ciphertext/tag
    • AEAD parameters chosen so dst[assoclen + cryptlen] falls on target offset
  3. Trigger — recv() triggers decrypt → 4-byte write to page cache
  4. Execute — execve("/usr/bin/su") loads corrupted page cache → shellcode runs as root

PoC Structure

# Open AF_ALG socket
a = socket.socket(38, 5, 0)  # AF_ALG, SOCK_SEQPACKET
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))

# ... set key, accept request socket u ...

# Construct AAD with payload chunk in bytes 4-7
u.sendmsg([b"A"*4 + payload_chunk], [cmsg_headers], MSG_MORE)

# Splice target file into pipe, then into AF_ALG socket
os.splice(target_fd, pipe_wr, offset)
os.splice(pipe_rd, alg_fd, offset)

# Trigger decrypt — recvmsg() returns error but write persists
u.recv(...)

Demo Results

DistributionKernelResult
Ubuntu 24.04 LTS6.17.0-1007-aws✅ Root
Amazon Linux 20236.18.8-9.213.amzn2023✅ Root
RHEL 14.36.12.0-124.45.1.el10_1✅ Root
SUSE 166.12.0-160000.9-default✅ Root

The Fix

Patch: a664bf3d603d

Reverts AF_ALG AEAD to out-of-place operation:

  • req->src → TX SGL (may contain page cache pages from splice)
  • req->dst → RX SGL (user’s recvmsg buffer)
  • Removes sg_chain() mechanism that linked page cache tag pages into writable destination
// Before (vulnerable):
req->src = req->dst  // same scatterlist, page cache pages writable

// After (fixed):
req->src = tsgl_src   // TX SGL (read-only)
req->dst = rsgl_dst   // RX SGL (user buffer)

Commit message: “There is no benefit in operating in-place in algif_aead since the source and destination come from different mappings.”


Remediation

Immediate Mitigation

# Block AF_ALG socket creation via seccomp
# OR blacklist the algif_aead module:
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead 2>/dev/null

Proper Fix

  • Patch the kernel — Update to kernel with fix commit a664bf3d603d
  • Update distribution kernel packages — All major distros should ship fixes

Disclosure Timeline

DateEvent
2026-03-23Vulnerability reported to Linux kernel security team
2026-03-24Initial acknowledgment
2026-03-25Patches proposed and reviewed
2026-04-01Patches committed to mainline kernel
2026-04-22CVE-2026-31431 assigned
2026-04-29Public disclosure

Impact Assessment

For Attackers

  • Extremely valuable for engagements with Linux targets
  • Works on virtually all unpatched Linux systems
  • No crash risk, no race conditions — highly reliable
  • Cross-container impact makes it valuable for cloud/container assessments
  • The 732-byte Python PoC means it can be embedded in other tools or delivered via simple download

For Defenders

  • Patch immediately — kernel update required
  • Detection difficulty — page cache corruption leaves no on-disk traces
  • Monitor for AF_ALG socket usage by unprivileged users
  • Container isolation — page cache sharing means this escapes containers
  • File integrity monitoring — traditional checksum-based FIM won’t catch this; consider runtime integrity monitoring

How It Was Found

This finding was AI-assisted. Theori researcher Taeyang Lee had mapped out the AF_ALG attack surface from earlier kernelCTF work. He suspected scatterlist page provenance was an underexplored vulnerability source.

Using Xint Code, the team scaled analysis across the entire crypto subsystem. The operator prompt was simple:

This is the linux crypto/ subsystem. Please examine all codepaths reachable from userspace syscalls. Note: splice() can deliver page-cache references of read-only files to crypto TX scatterlists.

After about an hour, Copy Fail was the highest severity output.


References


Last Updated: 2026-04-29 Source: Xint Code Research Team / Theori (Taeyang Lee)