Share
## https://sploitus.com/exploit?id=BFDF0769-E209-5815-82DD-727C5F47C101
# CVE-2026-31431 โ€” Copy Fail (Linux Kernel LPE)

Educational rewrite of the Theori/Xint proof-of-concept for CVE-2026-31431,
a local privilege escalation vulnerability in the Linux kernel's `algif_aead`
crypto socket interface. Affects every mainline kernel from 4.14 (July 2017)
through 6.18.21, and the 6.19 stable branch through 6.19.11.

---

## Disclaimer

**For educational and authorized security research only.**
Do NOT run on systems you do not own or do not have explicit written permission
to test. Running this on a vulnerable host gives any unprivileged local user
immediate root access. The authors take no responsibility for misuse.

---

## Vulnerability Summary

The Linux kernel has exposed an in-kernel cryptography API to userspace since
kernel 3.2 via the `AF_ALG` socket family (``). Userspace
opens a `SOCK_SEQPACKET` socket, binds it to an algorithm template (e.g.
`"aead"` / `"authencesn(hmac(sha256),cbc(aes))"`), and sends data for
encryption or decryption.

In 2017, a performance optimisation was merged that allows AEAD algorithms to
encrypt or decrypt data *in-place* when the kernel detects that the source and
destination share the same underlying pages. This avoids a redundant memory
copy on each operation.

The `authencesn` template (which adds Extended Sequence Number support to
authenticated encryption) contains a scratch-write during decryption: it
writes ESN bytes into a specific offset within the operation's data buffer
before the authentication check runs. When the operation's source pages were
obtained from the *page cache* of a regular file via `splice()`, and the
in-place optimisation is active, this scratch write goes directly back into
those cached pages.

Because the kernel's page cache is a shared mapping โ€” any process that opens
the same file sees the same physical pages โ€” an unprivileged user can:

1. Open any readable file (e.g. `/usr/bin/su`) with `O_RDONLY`.
2. Splice its pages into an `AF_ALG` operation socket.
3. Trigger an `authencesn` decryption; the ESN scratch write corrupts the
   page at a controlled offset with attacker-controlled bytes.
4. The *disk* is never modified; only the in-memory page cache is patched.

Off-host integrity scanners (AIDE, Tripwire, IMA/EVM) that compare checksums
of on-disk files are completely blind to this attack. The write vanishes after
a page-cache eviction or reboot, leaving no forensic trace on disk.

By targeting `/usr/bin/su` (a `setuid root` binary) and overwriting a branch
that guards the PAM authentication check, the attacker obtains a passwordless
root shell.

---

## Affected Kernels

| Branch       | First Vulnerable | Last Vulnerable | Fixed In  |
|--------------|-----------------|-----------------|-----------|
| mainline     | 4.14            | 6.18.21         | 6.18.22   |
| 6.19 stable  | 6.19.0          | 6.19.11         | 6.19.12   |
| 7.0-rc       | 7.0-rc1         | 7.0-rc6         | 7.0-rc7   |

The optimisation commit that introduced the bug landed in the 4.14 development
cycle (July 2017). Any kernel compiled with `CONFIG_CRYPTO_USER_API_AEAD=y`
and `CONFIG_CRYPTO_AUTHENC=y` (both common defaults) in the above ranges is
vulnerable.

---

## Affected Distributions (Non-Exhaustive)

| Distribution        | Shipped Kernel (approx.)    | Patched? |
|--------------------|-----------------------------|----------|
| Debian 12 (Bookworm) | 6.1.x                     | Yes (backport) |
| Debian 13 (Trixie) | 6.12.x / 6.14.x            | Yes (backport) |
| Ubuntu 24.04 LTS   | 6.8.x                       | Yes (USN-7xxx) |
| Ubuntu 24.10       | 6.11.x                      | Yes             |
| RHEL 9.x           | 5.14.x (rebased)            | Yes (RHSA)      |
| Alpine 3.20        | 6.6.x                       | Yes (backport)  |
| Arch Linux         | rolling (โ‰ฅ 6.18.22 now)     | Yes             |
| Kali Linux 2026.1  | 6.18.12                     | Vulnerable      |
| Kali Linux 2026.2+ | 6.18.22+                    | Fixed           |
| Linux Mint 22      | 6.8.x (Ubuntu base)         | Yes (Ubuntu USN)|

*Check your distribution's security tracker for the exact advisory status.*

---

## Requirements

- Linux kernel in a vulnerable range (see table above)
- Python 3.10+ (`os.splice` was added in Python 3.10)
- Read access to the target binary (default: `/usr/bin/su`, world-readable
  as it is setuid)
- No special privileges required โ€” this is a **local** privilege escalation

---

## Usage

```bash
# Show what would be written without touching the kernel (safe):
python3 copy_fail_exploit.py --dry-run

# Patch /usr/bin/su page cache (vulnerable kernel required):
# WARNING: vulnerable kernel required -- use only on systems you own
python3 copy_fail_exploit.py

# Patch and immediately escalate to root:
python3 copy_fail_exploit.py --spawn-shell

# Use a custom target binary and payload:
python3 copy_fail_exploit.py --target /usr/bin/sudo --payload-file ./custom_patch.bin

# Verbose output (print each 4-byte write):
python3 copy_fail_exploit.py --verbose --dry-run
```

By default `--spawn-shell` is **off**. After patching, the script prints:

```
[+] Patch applied to page cache of '/usr/bin/su' (disk unchanged).
    Run `su` to escalate, or re-run with --spawn-shell.
    To restore: echo 3 | sudo tee /proc/sys/vm/drop_caches
```

You must opt in to the shell spawn with `--spawn-shell`. This is intentional:
it forces you to understand what the exploit did before executing the result.

---

## How It Works

**Setting up the primitive.**
The exploit opens an `AF_ALG` socket and binds it to the
`authencesn(hmac(sha256),cbc(aes))` template with a dummy all-zero key. It
then configures the authentication tag size to 4 bytes via `ALG_SET_AEAD_AUTHSIZE`.
`accept()` on the control socket yields an *operation socket* that can submit
individual decrypt requests.

**Triggering the page-cache write.**
For each 4-byte chunk of the patch payload, the exploit calls `sendmsg()` on
the operation socket with `MSG_MORE` set, passing 8 bytes of associated data
(AAD): 4 bytes of filler followed by the 4 payload bytes. `MSG_MORE` tells the
kernel to hold the operation until more data arrives. It then creates a pipe
and issues two `splice()` calls โ€” `file_fd โ†’ pipe โ†’ op_sock` โ€” donating the
file's own page-cache pages as the input to the decryption operation. When
`recv()` is finally called, the kernel runs `authencesn` decryption. The
in-place optimisation fires (source and destination are the same pages), and
the ESN scratch write copies our payload bytes into the cache page at the
target offset. `recv()` returns `EBADMSG` because the authentication tag
cannot verify โ€” that is expected and harmless. The write has already happened.

**Patching the binary.**
The embedded payload (taken verbatim from the Theori PoC) overwrites a branch
instruction in `/usr/bin/su` from a stock `util-linux` build, turning a
conditional jump that enforces PAM authentication into an unconditional
fall-through. After patching, running `su` without a password succeeds and
drops a root shell. The on-disk binary is untouched; dropping page-cache
entries (`echo 3 > /proc/sys/vm/drop_caches`) reverts the change instantly.

For deeper technical analysis see the references below, in particular the Xint
blog post and the Theori PoC repository.

---

## Detection

### Falco rule

```yaml
- rule: AF_ALG socket opened by unprivileged process
  desc: >
    CVE-2026-31431 โ€” an unprivileged process opened an AF_ALG (family 38)
    socket, which is the first step of the Copy Fail exploit.
  condition: >
    evt.type = socket and
    evt.arg.domain = 38 and
    not user.uid = 0
  output: >
    AF_ALG socket opened (user=%user.name uid=%user.uid
    pid=%proc.pid comm=%proc.name)
  priority: WARNING
  tags: [host, network, privilege_escalation, CVE-2026-31431]
```

### YARA (memory scan for the exploit script)

```yara
rule CopyFail_CVE_2026_31431 {
    meta:
        description = "Detects Copy Fail exploit script in memory or on disk"
        cve         = "CVE-2026-31431"
    strings:
        $template = "authencesn(hmac(sha256),cbc(aes))" ascii
        $splice   = "os.splice" ascii
        $aflag    = "AF_ALG" ascii
    condition:
        all of them
}
```

### auditd โ€” detect AF_ALG socket creation

Add to `/etc/audit/rules.d/cve-2026-31431.rules`:

```
-a always,exit -F arch=b64 -S socket -F a0=38 -k cve_2026_31431_afalg
```

Then watch logs with:

```bash
ausearch -k cve_2026_31431_afalg --interpret
```

---

## Mitigation

**1. Apply the kernel patch** (recommended)
Upgrade to kernel 6.18.22, 6.19.12, 7.0-rc7 or later, or apply your
distribution's security update.

**2. Disable CONFIG_CRYPTO_USER_API_AEAD at build time**
If you build your own kernel, set:
```
# CONFIG_CRYPTO_USER_API_AEAD is not set
```
This removes the entire `AF_ALG` AEAD interface and eliminates the attack
surface. Most embedded/hardened deployments do not need it.

**3. seccomp โ€” deny AF_ALG socket family**
Add a seccomp filter to sensitive processes (or system-wide via a LSM policy)
that rejects `socket(AF_ALG, ...)`:

```c
// Deny socket() when domain == AF_ALG (38)
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 38, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | EACCES),
```

**4. Drop page-cache privileges via kernel hardening**
`vm.unprivileged_userfaultfd=0` and similar tunables reduce the attack surface
of user-accessible kernel memory interfaces, though they do not directly block
this CVE.

---

## Cleanup After Testing

The exploit modifies only the in-memory page cache; the on-disk binary is
never touched. To restore the original file content in memory, drop the
kernel's page cache:

```bash
echo 3 | sudo tee /proc/sys/vm/drop_caches
```

This evicts all clean pages from the cache. The next read of `/usr/bin/su`
(or whichever binary was patched) will reload the original bytes from disk.

**Rebooting** also clears the page cache completely.

---

## References

- [Original PoC โ€” Theori](https://github.com/theori-io/copy-fail-CVE-2026-31431)
- [Technical writeup โ€” Xint](https://xint.io/blog/copy-fail-linux-distributions)
- [Sysdig threat analysis](https://www.sysdig.com/blog/cve-2026-31431-copy-fail-linux-kernel-flaw-lets-local-users-gain-root-in-seconds)
- [Microsoft Security Blog](https://www.microsoft.com/en-us/security/blog/2026/05/01/cve-2026-31431-copy-fail-vulnerability-enables-linux-root-privilege-escalation/)
- [NVD entry](https://nvd.nist.gov/vuln/detail/CVE-2026-31431)
- [Debian security tracker](https://security-tracker.debian.org/tracker/CVE-2026-31431)

---

## Credits

- **Original discovery and disclosure**: Theori (theori.io) and Xint (xint.io)
- **Educational rewrite**: n0kk

---

## License

MIT โ€” see [LICENSE](LICENSE).

MIT was chosen over a custom "educational use only" license because no such
license exists as a recognized OSI/SPDX identifier. MIT is permissive, legally
well-understood, and compatible with responsible disclosure repositories. The
educational intent is expressed in the module docstring, this README's
Disclaimer section, and the `--help` output โ€” not in a legally dubious
custom license clause.