Security News

Cybersecurity news aggregator

🐧
CRITICAL Vulnerabilities Reddit r/netsec

Copy Fail (CVE-2026-31431): A Technical Deep Dive

CVE-2026-31431 (CVSS 7.8) is a local privilege escalation vulnerability in the Linux kernel where a logic flaw at the intersection of the AF_ALG crypto API, the `splice()` system call, and the `authencesn` algorithm allows an unprivileged user to write four attacker-controlled bytes into the kernel's page cache copy of any readable file, ultimately leading to root access. Affected versions include Linux kernels from 4.14 through 5.10.253, 5.11 through 5.15.203, 5.16 through 6.1.169, 6.2 through 6.6.136, and 6.7 through 6.12.84. The issue is fixed in kernel versions 5.10.254, 5.15.204, 6.1.170, 6.6.137, 6.12.85, 6.18.22, and 6.19.12.
Read Full Article →

Copy Fail (CVE-2026-31431): A Technical Deep Dive @Author: Fred Raynal @Date: May, 3rd 2026 On April 29, 2026, Xint Code disclosed CVE-2026-31431, a logic bug in the Linux kernel that gives root to any local user on essentially every major Linux distribution shipped since 2017. The exploit is a 732-byte Python script using only standard library modules. This write-up is a companion to the original disclosure . Instead of repeating it, we break down the kernel subsystems involved, show how their collision creates the vulnerability, and walk through the exploit step by step. The Bug in 30 Seconds Copy Fail exists at the intersection of three independent kernel changes, spread over six years. Each one was reasonable in isolation. 2011. A developer adds authencesn , a crypto algorithm for IPsec. It needs to rearrange 4 bytes temporarily during a computation. It uses a spot just past the output area in the destination buffer as scratch space. The buffer is a dedicated kernel allocation with slack at the end. No one reads that spot. Harmless. 2015. Another developer exposes the kernel's crypto API to unprivileged users via AF_ALG sockets. Users can now send files to the kernel for encryption. The data arrives from the page cache, the kernel's in-memory copy of files, via splice() , a zero-copy syscall that passes pages by reference. 2017. A third developer optimizes the AEAD decryption path in algif_aead.c . Instead of separate input and output buffers, the operation is done in-place: req->src = req->dst . To avoid copying the auth tag, the code chains page cache pages directly into the end of the destination scatterlist. Now authencesn's scratch write lands on page cache pages of the target file. Four attacker-controlled bytes, written into the kernel's cached copy of any readable file, without privileges. The following sections explain each component in detail, then trace the exact collision path. 1. Understanding the Key Components Copy Fail sits at the intersection of four independent kernel mechanisms. None of them is buggy on its own. To understand why their combination is lethal, we first need to understand each one separately. 1.1 The splice() System Call What it does splice() moves data between two file descriptors through a pipe, without copying the data into userspace . It was added to Linux in 2006 as a performance optimization for I/O-heavy workloads (web servers, file proxies, media streaming). The traditional way to copy data from one file descriptor to another is read() + write() : buf = os . read ( src_fd , 4096 ) # copy: kernel β†’ userspace os . write ( dst_fd , buf ) # copy: userspace β†’ kernel This involves two full copies of the data: one from kernel space into a userspace buffer (the read() , and one back from userspace into kernel space (the write() ). read() + write(): β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ File β”‚ ───► β”‚ User buffer β”‚ ───► β”‚ Dest β”‚ β”‚ (kernel) β”‚ copy β”‚ (userspace) β”‚ copy β”‚ (kernel) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ data touches RAM twice splice() eliminates both copies. Instead of moving bytes, the kernel passes references to the memory pages directly through a pipe: pipe_rd , pipe_wr = os . pipe () os . splice ( src_fd , pipe_wr , 4096 ) # no copy: pass page references into pipe os . splice ( pipe_rd , dst_fd , 4096 ) # no copy: pass page references to dest splice(): β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ File β”‚ ────────► β”‚ Pipe β”‚ ────────► β”‚ Dest β”‚ β”‚ (kernel) β”‚ ref only β”‚ (kernel) β”‚ ref only β”‚ (kernel) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ data never leaves kernel no userspace buffer allocated The page cache connection When the kernel reads a file from disk, it stores the contents in page cache , a system-wide memory cache of file data. Any subsequent read of the same file hits the cache instead of the disk. The page cache is what read() , mmap() , or execve() all use to access file contents. When you splice() from a file, the kernel doesn't create new pages. It passes references to the existing page cache pages of that file. The pipe, and whatever receives from the pipe, gets pointers to the same physical memory that backs every access to that file system-wide. splice() from a file: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Page Cache β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Page 0 β”‚ β”‚ Page 1 β”‚ β”‚ Page 2 β”‚ β”‚ β”‚ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β–Ό β–Ό β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Pipe buffer (references only) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β–Ό β–Ό β–Ό Same physical pages. No copy. If anyone modifies them, every reader of this file sees the change. This is the key property that Copy Fail exploits. splice() creates a path where page cache pages, the shared, authoritative copy of file contents, can be passed by reference into another kernel subsystem. If that subsystem writes to them, the file's contents change in memory system-wide, without any write to disk, without any VFS permi...

Share this article