Back to Blog
Vulnerability ResearchBreaches
May 4, 202614 min readBySiegePal LLC

CVE-2026-31431 (Copy Fail): 732 Bytes to Root on Nearly Every Mainstream Linux Kernel Since 2017

A 9-year-old logic flaw in the Linux kernel's crypto subsystem lets any unprivileged user gain root with a 732-byte Python script. We break down the AF_ALG + splice() page cache corruption, the container escape implications for Kubernetes, and what your team needs to do now.

On April 29, 2026, the security research firm Theori publicly disclosed a local privilege escalation vulnerability in the Linux kernel's cryptographic subsystem, tracked as CVE-2026-31431. Nicknamed Copy Fail. The vulnerability lives in algif_aead, the kernel module that exposes authenticated encryption (AEAD) primitives to userspace through the AF_ALG socket interface. It was assigned a CVSS 3.1 base score of 7.8. This lands in the High band (Red Hat separately rates it "Important" on its own four-tier scale. The same severity under different labeling, not the "Critical" rating some marketing copy around the disclosure implied). The Cybersecurity and Infrastructure Security Agency added it to the Known Exploited Vulnerabilities catalog on May 1, giving federal agencies until May 15 to remediate.

Why Copy Fail Deserves a Technical Read

What makes Copy Fail worth a careful technical read, rather than a quick patch-and-move-on, is where the bug came from. It is not a single mistake. It is the intersection of three separate, individually defensible kernel changes made across nine years by different people for different reasons. None of whom could have reasonably anticipated how their work would combine. That's a more useful engineering lesson than "patch your kernel," and it's worth walking through in some detail before getting to mitigation.

Three Reasonable Changes, One Bad Intersection

In 2011, the kernel gained authencesn, a cryptographic template used by IPsec to handle 64-bit Extended Sequence Numbers (ESN) alongside standard authenticated encryption. ESN support requires a bit of bookkeeping. Because the wire protocol only carries a 32-bit sequence number, the algorithm has to reconstruct the missing high-order bits. Briefly rearrange bytes in a scratch area to compute the authentication correctly. At the time, this scratch write was harmless. Only the internal IPsec (xfrm) code path called into it, and the associated data lived in its own, separate scatterlist. A scatterlist, for anyone who hasn't worked at this layer of the kernel. Is simply a list of memory buffer fragments the crypto API can walk over without needing them to be physically contiguous. This is how the kernel handles data that's scattered across multiple pages.

2015: AF_ALG Adds AEAD Support

In 2015, AF_ALG gained support for AEAD operations, giving userspace programs a socket-based way to invoke the same cryptographic primitives the kernel uses internally. To make authencesn work through this new interface, the code was adapted to the generic AEAD API. This introduced a specific byte offset, past the end of the associated data and ciphertext, where the ESN reconstruction scratch write happens. This was still safe, because the operation ran out-of-place. The source buffer, which might contain pages the kernel didn't want touched. The destination buffer, which the caller owned and expected to be written to, were kept strictly separate.

2017: The In-Place Optimization

Then, in 2017, kernel 4.14 shipped a performance optimization to algif_aead.c (upstream commit 72548b093ee3, authored by Stephan Mueller. Merged by crypto subsystem maintainer Herbert Xu) that let AEAD operations reuse the source scatterlist as the destination rather than requiring a separate copy. For encryption this is straightforward. The destination buffer is sized to hold both the ciphertext and the tag, so an in-place operation has room to work. Decryption is the asymmetric case. The destination buffer there is sized only for the plaintext, with no room for the tag value the algorithm still needs to read. As a result, the commit creates a small, separate per-request scatterlist holding just the tag. Chains it onto the destination scatterlist with sg_chain() so the in-place operation has somewhere to find it.

Where the Safe Pieces Intersect

That chaining is the step that turns three previously safe pieces of code into one exploitable path. It puts whatever comes after the destination buffer, including, as it turns out, live page cache pages, into something the AEAD scratch-space write can reach.

How an Unprivileged Socket Call Reaches the Page Cache

None of this matters unless an attacker can get their own choice of memory into that chained scatterlist, and this is where splice() comes in. splice() moves data between a file descriptor and a pipe without copying it through userspace. It works by passing references to the kernel's existing page cache pages, not fresh copies of their contents. This is normally a performance feature. Here, it's the delivery mechanism. An unprivileged process can open any file it has read access to, splice it into a pipe. Then splice that pipe into an AF_ALG socket bound to an AEAD algorithm like authencesn(hmac(sha256),cbc(aes)). Because splice() passes pages by reference, the socket's input scatterlist now holds direct references to the kernel's live. Physical page cache pages backing that file, not a private copy an attacker could safely corrupt without consequence.

Turning a Socket Request Into a Write

From there, the attacker sends a decrypt request over the socket, supplying their own choice of bytes as part of the associated data (AAD). The kernel's in-place logic sets the destination equal to the source, chains the tag-holding scatterlist behind it exactly as the 2017 commit intended. Authencesn does its ESN reconstruction. It reads back the sequence-number bytes it needs from the AAD. In the process temporarily uses the tag position as scratch space, writing four attacker-chosen bytes there. Because of the chaining, that tag position is not really scratch space at all. It's whatever page happens to be chained next in the scatterlist, which, thanks to splice(), is the live page cache page the attacker fed in.

Why Failed Decryption Still Corrupts Memory

The algorithm restores the sequence-number value it needed for its own bookkeeping. The four bytes it briefly wrote at the tag position, into what it doesn't realize is someone else's page, are never put back. Crucially, none of this depends on the decrypt succeeding. The attacker doesn't need a valid authentication tag. The call fails its integrity check and returns an error to userspace, exactly as it should for tampered ciphertext. The kernel's error path only cares about the authentication result, not about undoing a write into a page it never intended to modify. As a result, the failure is reported and the corruption stays.

The Narrow Conditions That Make It Exploitable

One independent technical review of the disclosure put this precisely. The bug is narrower than a generic AEAD flaw specifically because it requires the authencesn decrypt path and splice-backed tag pages. Specifically because it does not require the operation to succeed.

Why the File Looks Untouched, and When It Doesn't

The detail that makes Copy Fail unusually quiet is what happens to the page after it's corrupted. It's worth being precise about what "quiet" does and doesn't mean. Because the obvious shorthand, that checksums miss it because they read from disk, isn't how Linux file reads work. The kernel's page cache exists specifically so that ordinary reads don't have to touch disk every time. A page that's been modified only in memory gets marked "dirty" so the kernel knows to write it back eventually. The scratch-space corruption here never goes through a path that sets that dirty bit. Because as far as the kernel's bookkeeping is concerned, nothing about this page's on-disk representation changed, an AEAD operation failed, full stop.

How Page Cache Corruption Stays Off Disk

That means the underlying disk blocks are never touched. A fresh read of the file straight from those blocks would come back byte-for-byte identical to before the attack.

The catch is that "a fresh read from disk" isn't what happens the moment someone runs a checksum tool against a live system. Ordinary reads, hashing utilities included, are served from the page cache whenever the relevant page is already resident there, exactly the same path execve() uses. If the corrupted page is still sitting in cache when a checksum runs, that checksum reads the corrupted bytes, not the original ones. A comparison against a previously recorded baseline hash would correctly flag a mismatch. What makes the corruption quiet is that it's tied to that one resident page rather than to the file itself.

When Integrity Checks Can Catch It

Once the page is evicted, whether from ordinary memory pressure, an explicit cache drop,. A reboot, the next read faults it back in straight from the untouched disk blocks. The corrupted content is gone as if it had never happened, because nothing was ever written there. A file integrity scan that happens to run while the corrupted page is still cached has a real chance of catching it. One that runs afterward. This is the likelier case for anything scanning on a schedule of hours. Days rather than continuously watching, will see a file that matches its recorded baseline exactly. Because from the disk's perspective, nothing changed.

When the Corruption Disappears

So if an attacker targets a setuid-root binary the current process can read, commonly /usr/bin/su. The exploit's own splice() call is what pulls that binary's pages into the cache in the first place, if they weren't resident already. From there, a single request only overwrites four bytes. As a result, the published proof-of-concept repeats the primitive across several requests at chosen offsets. This builds up a short run of attacker-controlled bytes inside the cached copy. Enough to splice in a small piece of shellcode without needing to touch the rest of the binary. The kernel then loads that corrupted in-memory version on execve(), and the setuid bit does what it's supposed to do: runs that code as root.

Turning Four-Byte Writes Into Root Access

The privilege escalation isn't a separate step bolted onto the memory corruption. It's the same corrupted page being read by whichever consumer asks for it next, which for the attacker is execve(). Which, for a defender checking at the wrong moment, might be a checksum tool that reports everything as fine.

Sorting Out the "732 Bytes" and "Every Distro" Claims

The proof-of-concept Theori published is a 732-byte Python script using only the standard library. That figure is well corroborated across the original write-up, Microsoft's own analysis. Independent reporting, so it holds up as a factual description of the PoC's size. It's also a genuinely notable property of the bug. This is a deterministic, straight-line logic flaw with no race window to win and no per-kernel-build offset to calculate. This is unusual for a kernel memory corruption bug and is why it reproduces so consistently once the preconditions are met.

Testing the Every-Distro Claim

The claim that it works on "every Linux distribution" deserves more scrutiny than the disclosure's own framing gives it. What Theori's team verified and published, specifically, were four named combinations. Ubuntu 24.04 LTS on kernel 6.17.0-1007-aws, Amazon Linux 2023 on 6.18.8-9.213.amzn2023, RHEL 10.1 on 6.12.0-124.45.1.el10_1. SUSE 16 on 6.12.0-160000.9-default, all rooted with the identical, unmodified script. Beyond those four. "every distro" is an extrapolation from the fact that the vulnerable code lives in mainline Linux itself rather than in any distribution's patches. As a result, any distribution running a kernel derived from 4.14 or later without the fix is exposed in principle.

A More Defensible Scope of Exposure

Microsoft's own advisory is more careful about this distinction. Describing the affected population as "virtually all Linux distributions running kernels released from 2017 until patched versions are applied,". Naming Debian, Fedora. Arch Linux as further examples rather than asserting universal coverage. That's the more defensible way to say it. The vulnerable path is present in the great majority of general-purpose Linux systems running a standard, mainline-derived kernel from that window. Meanwhile, distributions running hardened, heavily modified, or non-standard kernel configurations may or may not include the affected code path. Nobody has published testing that covers every distribution in existence. Theori's own characterization of the exploit as "100% reliable" should be read the same way. Reliable against the combinations they tested, not an independently audited universal figure.

Patch Status and Affected Versions

The Linux kernel's own CVE announcement is unambiguous about where the bug lives and where it was fixed. Introduced in kernel 4.14 by commit 72548b093ee3. Resolved by three separate commits landing in parallel across the 6.18.22, 6.19.12, and mainline 7.0 upstream stable branches. Those three version numbers describe kernel.org's own stable trees, not what any given distribution ships. Distributions maintain their own long-term kernel branches, built on an older upstream base with security fixes backported in. As a result, a patched Ubuntu or RHEL kernel will carry its own distribution-specific version string rather than showing "6.18.22" anywhere. The fix itself, in the kernel team's own words, "mostly reverts commit 72548b093ee3 except for the copying of the associated data." It restores out-of-place operation for AEAD decryption, while keeping the parts of the 2017 change that were never the problem.

Distribution-Specific Patch Timelines

The report reached the kernel security team roughly five weeks before public disclosure. With the mainline patch merging on April 1, 2026, ahead of the CVE assignment on April 22. The public write-up on April 29.

Distribution-level timing varied. Ubuntu's advisory states that all releases before 26.04 (Resolute) are affected and that 26.04 ships a fixed kernel by default. Because the fix landed upstream shortly before disclosure. Several distributions were still working through their own kernel package updates in the days immediately after the write-up went public. This is part of why interim mitigations mattered in the first week or two more than they normally would for a patched vulnerability.

Containers, Kubernetes, and the Limits of Namespace Isolation

Copy Fail requires nothing that container isolation is designed to withhold. It needs a readable target file, a socket call. A splice call, none of which require elevated capabilities, root inside the container,. Any interaction with the host beyond what the shared kernel already provides. That means a process running as an unprivileged user inside a rootless container,. Inside a pod built to a restrictive security profile, can still reach the vulnerable path. Because the isolation those setups provide is about namespaces, capabilities. Mount visibility, not about which syscalls a locally-authenticated process can issue against the kernel it's running on.

The Container Escalation Boundary

The most direct consequence is escalation to root within the attacker's own container or namespace. This by itself is already a meaningful breach of the isolation most container deployments assume. An application compromised through some unrelated bug, say a vulnerable web framework. No longer stays confined to whatever limited user the container was configured to run as. Whether that extends further, into genuine cross-container impact. Host compromise, depends on whether the specific file being corrupted is backed by page cache that's shared beyond the attacker's own container. That sharing is common in practice. Container images built from shared base layers are frequently mounted so that multiple containers on the same node reference the same underlying files. Anything bind-mounted from the host is trivially shared by definition.

When Cached Pages Are Shared

But it isn't automatic or universal. Claiming that any instance of Copy Fail is a guaranteed one-step host escape overstates what the mechanism itself guarantees. It's better described as a serious escalation primitive. Its blast radius depends on how much of the affected file's page cache is shared with other tenants.

Docker and Kubernetes Defaults

One control worth checking specifically. Docker's own advisory states that Engine's default seccomp profile, prior to version 29.4.3, did not block AF_ALG socket creation. That upgrading the Engine or patching the host kernel closes the exposure either way. Kubernetes' Pod Security Standards don't add syscall filtering of their own. The Restricted tier requires that some seccomp profile be set. It inherits whatever that profile blocks, which for a container runtime's unmodified default meant AF_ALG remained reachable. Independent testing reported alongside community mitigation tooling found exactly that: pods admitted under the Restricted profile could still open AF_ALG sockets on an unpatched host.

Blocking AF_ALG Deliberately

Blocking socket(AF_ALG, ...) explicitly, whether via a custom seccomp profile at the container runtime. An LSM policy enforced cluster-wide, closes off the exploit's mandatory first step. Doesn't require a reboot. It has to be added deliberately. Neither the runtime's out-of-the-box defaults nor the strictest standard Kubernetes pod policy did this on their own at the time of disclosure.

Mitigation, and Where the Obvious Advice Falls Short

The upstream fix is the actual resolution here, and applying your distribution's kernel security update is the priority. Where that isn't immediately possible, several interim measures get suggested, and it's worth being specific about which ones work on which systems.

The Limits of the modprobe Blacklist

The commonly circulated modprobe blacklist, adding an entry that redirects algif_aead to /bin/false so it can't load, works only where the module is genuinely loadable. Independent analysis published alongside detection tooling for this CVE reported that on RHEL. Several of its downstream derivatives, algif_aead is compiled directly into the kernel rather than shipped as a loadable module on typical configurations. In which case the blacklist command runs without any error while leaving the vulnerable code path fully reachable, a worse outcome than doing nothing. Because it creates false confidence. Anyone relying on the modprobe approach should confirm on their own build whether the module is genuinely loadable before trusting it as a control. Where it isn't, the interim measure that has an effect is a kernel boot parameter, initcall_blacklist=algif_aead_init, applied through the bootloader configuration. Requiring a reboot to take effect.

What Disabling AF_ALG Affects

Either way, disabling the module or its init call has a scope. It does not affect dm-crypt, LUKS, kernel TLS, IPsec/XFRM, or userspace crypto libraries like OpenSSL, GnuTLS,. NSS, none of which route through this socket interface. It will affect anything explicitly configured to use the kernel's AF_ALG engine or that binds AEAD, symmetric-cipher, or hash sockets directly. This is uncommon but not unheard of.

Seccomp and LSM Controls

A seccomp profile that denies socket(AF_ALG, ...) at the container runtime or. For a cluster-wide control that doesn't depend on getting every workload's profile right, an LSM-based policy that denies the same syscall regardless of namespace. Capabilities, addresses the exploit's precondition directly. Doesn't require a reboot. SELinux in enforcing mode and AppArmor profiles are worth having as part of a broader defense-in-depth posture. Red Hat lists enforcing SELinux alongside non-root workloads and restricted debug access as sensible hardening steps. Neither is a targeted control for this specific syscall path on a stock policy. Neither should be treated as a substitute for the kernel patch or an explicit AF_ALG block.

Detection Is Harder Than Usual, and Still Developing

Because the corruption lives in a page-cache page rather than on disk. File integrity monitoring only catches it if the scan happens to run while that page is still resident. This pushes most of the practical detection burden onto the syscall layer instead. An unprivileged process opening an AF_ALG socket bound to an AEAD algorithm, followed by a splice() involving a setuid binary. In a short window, is the behavioral pattern worth watching for through auditd rules, eBPF tracing,. Equivalent EDR telemetry. In the first day or two after disclosure, formal Sigma, YARA. Vendor detection rules were not yet available, and defenders were working from inferred syscall patterns rather than published signatures. Community-contributed rules for SIEM platforms and tools like Falco followed in the weeks after, including behavioral rules submitted to the public SigmaHQ ruleset.

What Defenders Can Monitor

Microsoft Defender has since shipped named detections for known exploitation patterns. As with any signature or behavioral rule built against a specific public proof-of-concept, coverage is strongest against implementations that resemble the original Python script. Weaker against independently reimplemented versions. Kaspersky reported Go. Rust reimplementations of the exploit logic circulating in public repositories within days of disclosure. A reminder that detection built solely around one reference implementation ages quickly.

How Seriously to Take the "Actively Exploited" Label

CISA's Known Exploited Vulnerabilities catalog only adds a CVE when the agency has evidence of active exploitation. Copy Fail's inclusion on May 1 reflects that standard being met, even though CISA's public entry doesn't detail what that exploitation looked like. Separately, Microsoft's own research team characterized what it was observing at the same time as limited. Primarily proof-of-concept testing activity rather than large-scale campaigns. Meanwhile, flagging that the public availability of a working exploit made an increase in real exploitation likely in the near term. Those two statements aren't in conflict. KEV inclusion confirms exploitation happened, not that it happened at scale. The more measured read is that Copy Fail moved from disclosed to confirmed-exploited quickly. Without necessarily becoming a widely deployed mass-exploitation tool in that same window.

Where This Fits for SiegePal

Copy Fail is the kind of vulnerability that continuous vulnerability management programs exist to catch quickly. Kernel-level, exploitable across a wide population of standard Linux builds. Consequential enough in containerized and Kubernetes environments to warrant node-level prioritization rather than a routine patch cycle. This analysis reflects our engineering read of the publicly disclosed research and vendor advisories. SiegePal has not independently reproduced the exploit or observed it in a client environment, and nothing above should be read as a claim otherwise. If kernel patching cadence, container runtime hardening,. Workload-level seccomp policy for something like this is an open question in your environment, that overlaps with our continuous vulnerability management work.

References

  • Linux Kernel CVE Team, official vulnerability announcement for CVE-2026-31431, lore.kernel.org/linux-cve-announce
  • Linux kernel commit 72548b093ee3 ("crypto: algif_aead - copy AAD from src to dst"), git.kernel.org
  • NVD, CVE-2026-31431 record, nvd.nist.gov
  • CISA, Known Exploited Vulnerabilities Catalog entry for CVE-2026-31431 (added May 1, 2026)
  • Theori / Xint, "Copy Fail: 732 Bytes to Root on Every Major Linux Distribution," xint.io
  • Microsoft Security Blog, "CVE-2026-31431: Copy Fail vulnerability enables Linux root privilege escalation across cloud environments"
  • Ubuntu Security Team, "Fixes available for CVE-2026-31431 (Copy Fail)"
  • Red Hat, RHSB-2026-002 / CVE-2026-31431 vulnerability page, access.redhat.com
  • CERT-EU, security advisory 2026-005
  • oss-security mailing list, original disclosure thread, openwall.com

Need Help With This Topic?

Schedule a free consultation with our team to discuss your specific needs.

Book a Free Consultation