CVE-2026-31431
In my previous kernel exploitation notes, I mostly focused on memory corruption vulnerabilities such as Use-After-Free, along with the general workflow behind building exploitation chains.
CVE-2026-31431, also known as Copy Fail, is different.
There is no heap feng shui, no race condition, no information leak, and no kernel address guessing. The vulnerability is a logic flaw in the Linux kernel crypto interface that can turn a small controlled write into a local privilege escalation.
The interesting part is not only the impact. It is the primitive:
A local unprivileged process can influence the page cache of a readable file through an unexpected interaction between
AF_ALG,splice(), and thealgif_aead/authencesncode path.
TL;DR
CVE-2026-31431 is a Linux kernel local privilege escalation vulnerability affecting the userspace crypto API exposed through AF_ALG.
At a high level, the exploit chain is:
| |
flowchart LR
A["Unprivileged user"]
--> B["Create AF_ALG socket"]
--> C["Use AEAD/authencesn path"]
--> D["splice() readable file"]
--> E["4-byte page cache write"]
--> F["Corrupt cached setuid binary"]
--> G["Privilege escalation"]flowchart LR
A["Unprivileged user"]
--> B["Create AF_ALG socket"]
--> C["Use AEAD/authencesn path"]
--> D["splice() readable file"]
--> E["4-byte page cache write"]
--> F["Corrupt cached setuid binary"]
--> G["Privilege escalation"]flowchart LR
A["Unprivileged user"]
--> B["Create AF_ALG socket"]
--> C["Use AEAD/authencesn path"]
--> D["splice() readable file"]
--> E["4-byte page cache write"]
--> F["Corrupt cached setuid binary"]
--> G["Privilege escalation"] | |
About
The goal of this post is not to publish a weaponized exploit.
The goal is to understand why this bug is dangerous, why it is reliable, and why logic flaws can sometimes be as powerful as classic memory corruption vulnerabilities.
Note
This case study focuses on the exploitation model and defensive analysis of CVE-2026-31431 / Copy Fail. The lab helper referenced later is read-only and does not attempt exploitation.
Why This Vulnerability Matters
CVE-2026-31431 stands out because the attacker does not need a fragile memory corruption chain.
The bug provides a narrow but powerful primitive:
| |
That primitive is especially dangerous because the target can be a file the attacker is only allowed to read. When that file is a setuid-root binary, corrupting its cached content can change what gets executed without modifying the file on disk.
Vulnerable Area
The vulnerable area is the Linux kernel crypto userspace interface:
AF_ALGsocketsalgif_aeadauthencesn(hmac(sha256),cbc(aes))splice()- page cache backed file data
AF_ALG allows userspace programs to use kernel cryptographic algorithms through sockets. splice() can move data between file descriptors without copying data through userspace.
That combination is normally useful for performance. In this case, it becomes the dangerous part.
flowchart TD
A["User process"] --> B["AF_ALG socket"]
B --> C["algif_aead"]
C --> D["authencesn"]
A --> E["splice(readable file)"]
E --> F["File page cache"]
F --> C
C --> G["Unexpected write into cached file page"]flowchart TD
A["User process"] --> B["AF_ALG socket"]
B --> C["algif_aead"]
C --> D["authencesn"]
A --> E["splice(readable file)"]
E --> F["File page cache"]
F --> C
C --> G["Unexpected write into cached file page"]flowchart TD
A["User process"] --> B["AF_ALG socket"]
B --> C["algif_aead"]
C --> D["authencesn"]
A --> E["splice(readable file)"]
E --> F["File page cache"]
F --> C
C --> G["Unexpected write into cached file page"] | |
Root Cause
The simplified root cause is:
Data from a file-backed page cache page is used in a crypto operation where the kernel later performs a write into a buffer that should not be writable from the attacker’s point of view.
The bug is tied to an optimization where AEAD operations can work in-place. In practice, the source and destination handling becomes unsafe when pages obtained through splice() are chained into the crypto scatterlist.
A simplified model looks like this:
| |
The important detail is not the size of the write. It is where the write lands.
A four-byte write sounds small, but if the attacker controls:
- the target file,
- the offset,
- and the four bytes,
then the primitive becomes useful enough to patch cached executable content in memory.
Why Page Cache Corruption Is Powerful
The page cache stores file content in RAM.
When a process executes or reads a file, the kernel may serve that content from the page cache instead of reading it again from disk.
flowchart LR
A["Disk file"] --> B["Page cache"] --> C["Process reads/executes file"]
D["Attacker primitive"] --> Bflowchart LR
A["Disk file"] --> B["Page cache"] --> C["Process reads/executes file"]
D["Attacker primitive"] --> Bflowchart LR
A["Disk file"] --> B["Page cache"] --> C["Process reads/executes file"]
D["Attacker primitive"] --> B | |
This matters because page cache corruption can be temporary, memory-only, and hard to see with tools that only verify files on disk.
For example, a disk integrity checker may hash /usr/bin/su on disk and see no change, while the kernel may still serve modified bytes from memory until the cache is dropped or overwritten.
Exploitation Model
The public exploitation idea can be summarized as three primitives:
| |
sequenceDiagram
participant U as Userland
participant K as Kernel AF_ALG
participant P as Page Cache
participant S as setuid binary
U->>K: create AF_ALG socket
U->>K: configure AEAD/authencesn
U->>P: splice() readable target file
P->>K: file-backed pages enter crypto path
K->>P: unintended 4-byte write
U->>S: execute target binary
S->>P: reads modified cached bytessequenceDiagram
participant U as Userland
participant K as Kernel AF_ALG
participant P as Page Cache
participant S as setuid binary
U->>K: create AF_ALG socket
U->>K: configure AEAD/authencesn
U->>P: splice() readable target file
P->>K: file-backed pages enter crypto path
K->>P: unintended 4-byte write
U->>S: execute target binary
S->>P: reads modified cached bytessequenceDiagram
participant U as Userland
participant K as Kernel AF_ALG
participant P as Page Cache
participant S as setuid binary
U->>K: create AF_ALG socket
U->>K: configure AEAD/authencesn
U->>P: splice() readable target file
P->>K: file-backed pages enter crypto path
K->>P: unintended 4-byte write
U->>S: execute target binary
S->>P: reads modified cached bytes | |
A realistic target is a setuid-root binary such as:
| |
The attacker does not need write permission to the binary on disk. The primitive targets the cached file content in memory.
Important
The core exploitation value is not arbitrary file write on disk. It is controlled corruption of file-backed memory through the page cache.
Why A Small Write Can Be Enough
A four-byte write is limited, but it can still be chained.
Repeated writes can modify multiple offsets. When the target is executable code, even a tiny patch can change control flow, alter a conditional branch, or stage a minimal payload.
This is why the vulnerability is more practical than it first appears:
| Constraint | Why it is still useful |
|---|---|
| Only 4 bytes per write | Can be repeated at chosen offsets |
| Memory-only corruption | Enough for execution after cache corruption |
| Requires local access | Still critical on shared systems, containers, CI runners, and multi-user servers |
| No disk modification | Harder for disk-based integrity tools to detect |
Safe Lab Inspector
I also wrote a read-only helper script for lab validation:
| |
The script does not exploit the vulnerability. It only collects environment information and checks whether the lab matches public PoC prerequisites.
It reports things such as:
- current kernel version,
- whether the script runs as an unprivileged user,
- virtualization/container indicators,
- selected kernel hardening sysctls,
- availability of relevant crypto primitives,
- presence of setuid-root
su, - a static prerequisite score.
Example usage:
| |
With JSON output:
| |
Example checks performed by the script:
| |
Warning
A matching prerequisite score does not prove that the host is vulnerable. It only means the environment looks suitable for controlled manual validation.
This distinction is important. A safe inspector is useful for documenting a lab, but it should not claim exploitation unless a separate controlled PoC has actually been executed and validated.
Commands For Defensive Triage
These commands are useful for documentation and defensive triage. They do not exploit the vulnerability.
Check kernel version:
| |
Check whether relevant crypto primitives appear in /proc/crypto:
| |
Check whether algif_aead is loaded:
| |
Check common setuid-root targets:
| |
Check selected hardening values:
| |
Detection Ideas
Detection should focus on behavior rather than disk changes.
Useful signals include:
- unusual
AF_ALGsocket creation, SOCK_SEQPACKETAF_ALG usage by unexpected processes,- suspicious use of
splice()involving setuid binaries, - unprivileged processes interacting with crypto AEAD paths,
- execution of setuid binaries shortly after unusual AF_ALG activity.
A high-level detection model:
| |
flowchart LR
A["AF_ALG socket"] --> B["AEAD/authencesn"]
B --> C["splice() from setuid binary"]
C --> D["recvmsg()/crypto operation"]
D --> E["execve() target binary"]
E --> F["Alert candidate"]flowchart LR
A["AF_ALG socket"] --> B["AEAD/authencesn"]
B --> C["splice() from setuid binary"]
C --> D["recvmsg()/crypto operation"]
D --> E["execve() target binary"]
E --> F["Alert candidate"]flowchart LR
A["AF_ALG socket"] --> B["AEAD/authencesn"]
B --> C["splice() from setuid binary"]
C --> D["recvmsg()/crypto operation"]
D --> E["execve() target binary"]
E --> F["Alert candidate"] | |
Disk-only file integrity monitoring may miss this class of issue because the interesting modification happens in memory.
Mitigations
The real fix is to update the kernel to a patched version from the distribution vendor.
Temporary hardening ideas may include:
| Category | Mitigation |
|---|---|
| Patching | Install vendor kernel updates as soon as available |
| Exposure reduction | Restrict untrusted local users and shared shell access |
| Container hardening | Treat containers as affected when they share a vulnerable host kernel |
| Syscall filtering | Use seccomp profiles to reduce access to dangerous syscall combinations where possible |
| LSM policy | Use AppArmor/SELinux policy to restrict unusual AF_ALG usage where practical |
| Monitoring | Alert on unexpected AF_ALG + splice + setuid execution patterns |
| Incident response | Reboot after patching to clear potentially corrupted page cache state |
Important
Containers do not bring their own kernel. If the host kernel is vulnerable, container boundaries may not be enough.
Comparison With Memory Corruption Bugs
| Aspect | Classic UAF | Copy Fail |
|---|---|---|
| Bug class | Memory corruption | Logic flaw |
| Primitive | Object reuse / control flow corruption | Controlled page cache write |
| Reliability | Often fragile | High when prerequisites match |
| Requires leak | Often yes | No, in the common public model |
| Requires race | Sometimes | No, based on public descriptions |
| Disk modification | Not necessarily | No, memory-backed page cache corruption |
Key Insight
Important
This is not powerful because it writes many bytes. It is powerful because it writes controlled bytes into the wrong trust boundary.
Kernel exploitation is often about breaking assumptions.
Here, the broken assumption is that a readable file page entering a crypto operation cannot become a writable destination buffer from an unprivileged attacker’s perspective.
What I Learned
This vulnerability is a good reminder that exploitation is not always about complexity.
A logic flaw can be more reliable than a memory corruption bug when it exposes a clean primitive.
In this case, the primitive is small but dangerous:
| |
The main lesson is simple:
Controlling where data ends up can be as powerful as controlling a pointer.
Conclusion
CVE-2026-31431 shows that kernel exploitation is not limited to heap manipulation, information leaks, or race conditions.
A performance optimization, a complex scatterlist path, and an unexpected interaction with splice() were enough to turn a crypto API path into a page-cache write primitive.
No disk write is needed. No setuid binary permission change is needed. No kernel address leak is needed.
Just a broken trust boundary between readable file-backed memory and writable crypto output.
References
- https://xint.io/blog/copy-fail-linux-distributions
- https://copy.fail/
- https://github.com/pullsec/cve-deep-dive/blob/main/CVE-2026-31431/scripts/cve_2026_31431.py
- https://www.sysdig.com/blog/cve-2026-31431-copy-fail-linux-kernel-flaw-lets-local-users-gain-root-in-seconds
- https://ubuntu.com/blog/copy-fail-vulnerability-fixes-available
- https://cert.europa.eu/publications/security-advisories/2026-005/
kofi