Introduction
Yesterday, Theori disclosed CVE-2026-31431 — a Linux kernel LPE nicknamed Copy Fail that affects nearly every distribution shipped since 2017. The vulnerability lives in the kernel’s algif_aead userspace crypto interface: by combining an AF_ALG socket bound to an AEAD algorithm with splice(), an unprivileged user can write four controlled bytes into the page cache of any readable file — including SUID binaries like /usr/bin/su.
A few things make this one nasty:
- No race condition. Unlike Dirty Cow or Dirty Pipe, Copy Fail is a straight-line logic bug. The exploit succeeds on the first try, every time.
- No on-disk artifacts. The corrupted page lives only in memory. File integrity monitoring will not see it.
- Container escape primitive. The page cache is shared host-wide, so a compromised pod can corrupt SUID binaries visible to other pods and the host kernel.
- The PoC is 732 bytes of Python. Trivial to weaponize.
CVSS is 7.8 (High), which under-represents the real-world impact for any environment running multi-tenant Linux, shared-kernel containers, or CI runners executing untrusted code.
The Primitive That Matters
Strip the exploit down to its essentials and the load-bearing syscall is this:
socket(AF_ALG, SOCK_SEQPACKET, 0);
That single call is what makes Copy Fail detectable cleanly. AF_ALG (family 38) is the kernel crypto interface — almost nothing in userland uses it. The narrow set that does (cryptsetup, systemd-cryptsetup, kcapi-* tools, IPsec daemons) uses SOCK_DGRAM for hashing and symmetric crypto.
SOCK_SEQPACKET is required for AEAD operations, and basically nothing in production legitimately opens an AEAD AF_ALG socket. That’s the signature.
Once the socket is open, the exploit binds it to authencesn(hmac(sha256),cbc(aes)), splices a SUID binary into a pipe, and feeds the pipe into the AEAD socket. The kernel’s broken in-place optimization (introduced in 2017) writes 4 scratch bytes into the spliced page cache pages. Run the now-corrupted SUID binary, get root.
Auditd Rule
The detection lives at the syscall level, so it does not care whether the exploit is written in Python, C, Go, or anything else:
## --------------------------------------------------------------
## CVE-2026-31431 "Copy Fail" — AF_ALG socket creation
## Mandatory primitive of the exploit. Language-independent.
## --------------------------------------------------------------
-a always,exit -F arch=b64 -S socket -F a0=0x26 -k CVE_2026_31431_af_alg
-a always,exit -F arch=b32 -S socket -F a0=0x26 -k CVE_2026_31431_af_alg
-a always,exit -F arch=b32 -S socketcall -k CVE_2026_31431_socketcall
## Correlation aids — SUID binaries the exploit typically targets
-w /usr/bin/su -p r -k T1548.001_suid_read
-w /usr/bin/sudo -p r -k T1548.001_suid_read
-w /usr/bin/passwd -p r -k T1548.001_suid_read
-w /usr/bin/pkexec -p r -k T1548.001_suid_read
-w /usr/bin/mount -p r -k T1548.001_suid_read
I am intentionally not filtering on a1 (socket type) at the kernel level. Auditd’s bitmask handling is finicky, and there are four valid SOCK_SEQPACKET variants (0x5, 0x805, 0x80005, 0x80805) once you account for SOCK_CLOEXEC and SOCK_NONBLOCK. It’s cleaner to capture all AF_ALG sockets — which are very low volume — and apply the type filtering downstream where boolean logic is actually pleasant to write.
Sample Log
Running the public PoC against a vulnerable Ubuntu 24.04 lab box (kernel 6.14.0-27-generic) produces this in Elasticsearch:
{
"@timestamp": "2026-04-30T17:39:47.012Z",
"host": {
"hostname": "LABUBUNTU",
"os": { "kernel": "6.14.0-27-generic", "version": "24.04.3 LTS" }
},
"auditd": {
"data": {
"syscall": "socket",
"a0": "26",
"a1": "80005",
"a2": "0",
"exit": "4",
"tty": "pts4"
},
"result": "success",
"summary": {
"actor": { "primary": "filip", "secondary": "low_priv" },
"how": "python3"
}
},
"process": {
"executable": "/usr/bin/python3.12",
"name": "python3",
"title": "python3 exp.py",
"pid": 21606
},
"user": { "name": "low_priv", "id": "1001" },
"tags": ["CVE_2026_31431_af_alg"]
}
The fields that matter:
auditd.data.a0: "26"→AF_ALGauditd.data.a1: "80005"→SOCK_SEQPACKET | SOCK_CLOEXEC(Python setsSOCK_CLOEXECby default)auditd.data.exit: "4"→ kernel returned a valid file descriptorauditd.summary.actor.primary: "filip"→ auid-resolved login user (sticks acrosssutolow_priv)
That a1 value is the tell. SEQPACKET on AF_ALG = AEAD interface = Copy Fail.
Elastic EQL — The Real Detection
Standalone, the auditd event is high-fidelity but not deterministic. The following sequence seems to be succesful:
sequence by host.id with maxspan=3s
[file where
event.action == "opened-file" and
file.path : ("/usr/bin/su", "/usr/bin/sudo", "/usr/bin/passwd",
"/usr/bin/pkexec", "/usr/bin/mount")]
[process where event.module == "auditd" and
auditd.data.syscall == "socket" and
auditd.data.a0 == "26" and
auditd.data.a1 in ("5", "805", "80005", "80805") and
not process.name in (
"cryptsetup", "systemd-cryptse", "systemd-cryptsetup",
"veritysetup", "integritysetup",
"kcapi-enc", "kcapi-dgst", "kcapi-rng", "kcapi-sym",
"iked", "charon", "pluto"
)]

Elastic Defend note
In my Lab Evironment - Ubuntu 24.04 with ELastic Defend 9.3.3 running in Detection Mode, there was no Elastic Defend alert.
Tuning Notes
- Volume on a real fleet: expect a handful of events per host per day, mostly from boot-time
cryptsetupon encrypted-disk machines. - Run the standalone auditd rule as medium severity (the AF_ALG SEQPACKET socket alone is exploit-stage-1 with very few legitimate consumers).
- Run the EQL sequence as critical — that is the full successful exploit chain.
Mitigation
Until your distro pushes a patched kernel, the cleanest mitigation is blacklisting the algif_aead module:
echo "install algif_aead /bin/true" > /etc/modprobe.d/cve-2026-31431.conf
Validate it does not break anything that actually needs AEAD via the kernel crypto API (most workloads do not — userspace crypto libraries do their own thing). For Kubernetes nodes specifically, blocking socket(AF_ALG) via seccomp profile on tenant pods is the right defense-in-depth move.
Conclusion
Copy Fail is a clean reminder that file integrity monitoring is not enough — page-cache attacks leave nothing on disk. The detection story has to live at the syscall layer, and AF_ALG + SOCK_SEQPACKET happens to be a near-perfect signature for this class of bug.
The rules above are what I deployed in our lab and validated against the public PoC. If you operate a Linux fleet of any size, get these in front of your endpoints today and patch as soon as your distro ships the kernel update.
Stay safe. Patch your kernels.