<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: aettern</title>
    <description>The latest articles on DEV Community by aettern (@aettern).</description>
    <link>https://dev.to/aettern</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4025739%2F08a354ad-582e-45e3-b07f-4f538f518aab.jpg</url>
      <title>DEV Community: aettern</title>
      <link>https://dev.to/aettern</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aettern"/>
    <language>en</language>
    <item>
      <title>Fixing LPEs on the Fly: Mitigating CopyFail and DirtyFrag with eBPF</title>
      <dc:creator>aettern</dc:creator>
      <pubDate>Mon, 13 Jul 2026 06:19:01 +0000</pubDate>
      <link>https://dev.to/aettern/fixing-lpes-on-the-fly-mitigating-copyfail-and-dirtyfrag-with-ebpf-1m7f</link>
      <guid>https://dev.to/aettern/fixing-lpes-on-the-fly-mitigating-copyfail-and-dirtyfrag-with-ebpf-1m7f</guid>
      <description>&lt;p&gt;In late April and early May of 2026, two Linux local privilege escalation bugs landed a week apart named CopyFail (CVE-2026-31431, disclosed April 29) and DirtyFrag (CVE-2026-43284 / CVE-2026-43500, disclosed May 7). Both let an unprivileged local user become root, both were deterministic which means that it does not require no race conditions or specific timing or chain of actions for it to work. Both had public PoC released within days of disclosure. That combination of being reliable, public and unpatched is about as bad as LPEs get. &lt;/p&gt;

&lt;p&gt;The "official" fix for both was the same story we always receive for this sort: wait for a patched kernel, reboot. Which is fine if you are running a laptop but not if you are running a fleet of production servers, a Kubernetes cluster full of untrusted tenant workloads, or literally anything where a reboot means a maintenance window, a failover or an outage nobody signed up for that week. &lt;/p&gt;

&lt;p&gt;This caught my interest and I came up with something called &lt;code&gt;copyfrag-fuse&lt;/code&gt;, a small BPF-LSM program that blocks both exploits at runtime, without touching the kernel version and also does not require a reboot. This writeup covers what the bugs actually do, why the standard mitigations are very fragile, how my fix works and the tradeoffs I made along the way. &lt;/p&gt;

&lt;p&gt;Here's link to the repo btw: &lt;a href="https://github.com/aettern/copyfrag-fuse" rel="noopener noreferrer"&gt;github.com/aettern/copyfrag-fuse&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was heavily inspired by the approaches that &lt;a href="https://blog.cloudflare.com/copy-fail-linux-vulnerability-mitigation/" rel="noopener noreferrer"&gt;Cloudflare&lt;/a&gt; brought up with their writeup on CopyFail and also the writeup of &lt;a href="https://medium.com/@miggo-engineering/detecting-copyfail-dirtyfrag-by-thinking-outside-the-box-3cae021ca94c" rel="noopener noreferrer"&gt;Miggo Security&lt;/a&gt; by Rafael David Tinoco and it's genuinely what made it all click for me.&lt;/p&gt;

&lt;p&gt;And at the time of writing this, the patches have already been released by every major distro so make sure your systems are up to date.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Exploits, and why they matter
&lt;/h3&gt;

&lt;p&gt;Both CopyFail and DirtyFrag boil down to the same primitive which is an unprivileged process tricking the linux kernel into performing an in-place cryptographic operation over memory pages that it doesn't own and specifically ones that are sitting in the shared page cache. If you can get to land a controlled write into the page cache of a file like &lt;code&gt;/usr/bin/su&lt;/code&gt;, then congrats you have just modified the binary in memory for every process that runs it next as they all share the same page cache. Run it once, and you get a root shell. And all this happens without ever touching the file on the disk btw. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CopyFail&lt;/strong&gt; does it through &lt;code&gt;AF_ALG&lt;/code&gt;, the socket interface to the kernel's crypto API. It abuses an in-place optimization added to the &lt;code&gt;algif_aead&lt;/code&gt; module that was implemented back in 2017. &lt;/p&gt;

&lt;p&gt;The flow of the attack is as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;splice()&lt;/code&gt; is used to map a target file's page cache into the crypto scatterlist, so the kernel treats those real page-cache pages as its own scratch space.&lt;/li&gt;
&lt;li&gt;The AEAD decrypt operation is triggered. &lt;code&gt;authencesn&lt;/code&gt; performs a 4-byte scratch write past the intended output region during decryption — and because of step 1, that write lands directly in the spliced page-cache page instead of a private buffer.&lt;/li&gt;
&lt;li&gt;This gives the attacker a controlled 4 byte write where he gets to pick the file, the offset, and the value too.&lt;/li&gt;
&lt;li&gt;Do that a few hundred times against &lt;code&gt;/usr/bin/su&lt;/code&gt; and you've written working shellcode into a setuid-root-binary's in memory copy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entire exploit is extremely small as it's just a 732 byte Python script, and it's universal&lt;/p&gt;

&lt;p&gt;For a way more detailed explanation, check out &lt;a href="https://xint.io/blog/copy-fail-linux-distributions" rel="noopener noreferrer"&gt;Xint.io&lt;/a&gt; excellent article on CopyFail and to get the exploit code itself check out the dedicated page created by their team &lt;a href="https://copy.fail/" rel="noopener noreferrer"&gt;copy.fail&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fht68eiiwcokgzj365zch.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fht68eiiwcokgzj365zch.gif" alt="CopyFail Demo" width="800" height="705"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DirtyFrag&lt;/strong&gt; is also kinda similar but instead of using &lt;code&gt;AF_ALG&lt;/code&gt; it goes through the IPsec ESP Platform &lt;code&gt;(esp4/esp6)&lt;/code&gt; or the &lt;code&gt;RxRPC&lt;/code&gt; transport which is used by AFS and the kernel's Kerberos path. The base idea is still the same as this also decrypts over pages the kernel doesn't privately own that is reached through &lt;code&gt;splice()&lt;/code&gt;/&lt;code&gt;sendfile()&lt;/code&gt; and we get the same page cache controlled write. This variant showed up a week after CopyFail, which is not a huge surprise as once an attack vector goes public then people go looking for its siblings.&lt;/p&gt;

&lt;p&gt;To get a look at the exploit code check out the github repo of &lt;a href="https://github.com/v4bel/dirtyfrag" rel="noopener noreferrer"&gt;DirtyFrag&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Impact-wise, this is about as bad as LPE bugs get as they have no race condition to win, no kernel version pinning required, works across basically every mainstream distro shipping a kernel from 2017 onward and on top of that it's very stealthy too because the change never touches the disk as the file integrity monitoring that checksums files on disk won't see it. Getting a LPE on a computer you already own is not a big deal but in a container or multi-tenant host environment, the page cache is shared with the host, so this isn't just an LPE but a potential container-escape primitive too. &lt;/p&gt;

&lt;h3&gt;
  
  
  What were the standard mitigations and why they fall short
&lt;/h3&gt;

&lt;p&gt;The advisories that went out for both bugs came up with mostly the same thing that is disable the vulnerable kernel module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "install algif_aead /bin/false" &amp;gt; /etc/modprobe.d/disable-algif.conf
rmmod algif_aead 2&amp;gt;/dev/null || true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and for DirtyFrag it was &lt;code&gt;esp4&lt;/code&gt;, &lt;code&gt;esp6&lt;/code&gt;, and &lt;code&gt;rxrpc&lt;/code&gt;. These were the recommendation that came up when these went public and it's a completely reasonable thing to suggest as it is simple and it does stop the exploit completely.&lt;/p&gt;

&lt;p&gt;But it has two real problems,&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It silently doesn't work on a lot of distros.&lt;/strong&gt; On RHEL family of distros, &lt;code&gt;algif_aead&lt;/code&gt; is compiled directly into the kernel (&lt;code&gt;CONFIG_CRYPTO_USER_API_AEAD=y&lt;/code&gt;), and is not built as a loadable module. So even if the modprobe blacklisting and rmmod returned without any error are report success still they do nothing because there's no module to block or remove there. This leads to dangerous failure because it's invisible unless someone actually takes a look into it.&lt;/p&gt;

&lt;p&gt;Even if we get the module to get disabled, then it takes down the entire subsystem for everyone. If we disable &lt;code&gt;algif_aead&lt;/code&gt; it then breaks every legitimate userspace program using AEAD ciphers through the kernel crypto API such as disk encryption helpers and so on. Disable &lt;code&gt;esp4&lt;/code&gt;/&lt;code&gt;esp6&lt;/code&gt; and we break IPsec tunnels outright used by VPNs. Disable &lt;code&gt;rxrpc&lt;/code&gt; and AFS clients stop working. If the environment uses any of these especially something like IPsec then we are trading an LPE for an outage. &lt;/p&gt;

&lt;p&gt;Then the second one is the reboot problem. Cloudflare's own write-up on CopyFail has a good point on this as even for a company with a fast kernel release pipeline, the upstream fix had already been merged into stable LTS lines weeks before disclosure but they were still stuck waiting for it to land in their specifc build, because the fix hadn't been backported to the specific LTS series they were actually running. According to that write-up their normal process is to reboot on a rolling 4 week cycle which is not slow at all but it wasn't fast enough for a same-day exploit. If a company such as Cloudflare can have that gap then most of us could have had it worse with shared hosting boxes, long running stateful services etc... where the statement "just reboot it" is much more complex process than a command. &lt;/p&gt;

&lt;h3&gt;
  
  
  How my fix works: behavior, not signatures
&lt;/h3&gt;

&lt;p&gt;The basic approach of a security tooling at most times would be to look for the signature and block it. For example, if some program is using &lt;code&gt;AF_ALG&lt;/code&gt; or splicing specific files such as &lt;code&gt;/etc/passwd&lt;/code&gt; it just gets outright blocked. Both of these are weak counter-measures. This brings the whole socket family down as it is just the module-disable mitigation in a different form. And file tampering detection is totally dodged because the vulnerability does not care about which file backs the target page and if it just points it to a different exploit at a different file then the detection never fires.&lt;/p&gt;

&lt;p&gt;But one thing that is uncommon in these exploits is the the pattern of how the socket options et configured. This is an amazing find by Miggo Engineering btw. So, Legitimate users of these subsytems only behave like these,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A real crypto application sets &lt;code&gt;ALG_SET_KEY&lt;/code&gt; once per socket, with an actual key.&lt;/li&gt;
&lt;li&gt;A real VPN or IPsec stack configures &lt;code&gt;UDP_ENCAP&lt;/code&gt; on a very few long-lived sockets.&lt;/li&gt;
&lt;li&gt;A real AFS client or Kerberos session sets &lt;code&gt;RXRPC_SECURITY_KEY&lt;/code&gt; and &lt;code&gt;RXRPC_MIN_SECURITY_LEVEL&lt;/code&gt; once per connection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these look anything like what these exploits do, which is spray the same option, with a value across many short-lived sockets, over and over in a tight loop as the write primitive is only 4 bytes at a time and it needs a lot of repetitions to land a useful payload.&lt;/p&gt;

&lt;p&gt;My eBPF LSM module solution &lt;a href="https://github.com/aettern/copyfrag-fuse" rel="noopener noreferrer"&gt;CopyFrag-Fuse&lt;/a&gt; hooks &lt;code&gt;security_socket_setsockopt&lt;/code&gt;, the LSM hook that SELinux and AppArmor have used for years via &lt;code&gt;BPF_PROG_TYPE_LSM&lt;/code&gt;. This is a really good attachment point for a couple of reasons as it fires at the exact decison point cause when the &lt;code&gt;setsockopt()&lt;/code&gt; is called, this hook runs before the kernel even requests that socket change. At that moment, this program can exactly see what's being requested with data such as which socket, which level and what specific option it is trying to set. As it runs before the completion of the change, the program can return a negative value like &lt;code&gt;-EPERM&lt;/code&gt; which mean "permission denied"  and the kernel never let's it goes through. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fongc2l9gqdjr9z1pop2u.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fongc2l9gqdjr9z1pop2u.gif" alt="copyfrag-fuse demo" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And for each process, the program tracks the state per relevant socket option such as a timestamp of the last call and a count. So, if a process makes four or more of the flagged option calls: &lt;code&gt;ALG_SET_KEY&lt;/code&gt;, &lt;code&gt;UDP_ENCAP&lt;/code&gt;, &lt;code&gt;RXRPC_SECURITY_KEY&lt;/code&gt;, &lt;code&gt;RXRPC_MIN_SECURITY_LEVEL&lt;/code&gt; within a one second window then it gets outright blocked with &lt;code&gt;-EPERM&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;During normal usage, it gets set once, maybe twice if it retries the connection but most of the times it never gets close to that threshold. Both CopyFail and DirtyFrag which needs rapid repetition to build up its write, hits it almost immediately. &lt;/p&gt;

&lt;p&gt;One good property here is that this needs zero knowledge of which file or binary is being targeted. It doesn't care if the attacker point the exploit at &lt;code&gt;/usr/bin/su&lt;/code&gt; or some other file that we have never heard of as the detection is entirely in the calling pattern and not the target.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why these design decisions specifically
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why BPF-LSM and not a tracepoint or kprobe?&lt;/strong&gt; A kprobe/tracepoint program can only observe a syscall taking place but not change it's outcome so you only get visibility not prevention. We'll need a separate mechanism for that which just makes it more messier. Meanwhile attaching a LSM Hook and returning &lt;code&gt;-EPERM&lt;/code&gt; blocks the call just like SELinux and AppArmor before the damage even happens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why &lt;code&gt;socket_setsockopt&lt;/code&gt; specifically?&lt;/strong&gt; I did consider hooking it to &lt;code&gt;splice()&lt;/code&gt; directly since that's what's used to do the page-cache write itself. The tradeoff is that it's being used by many legitimate code so it will just end up with a lot of false-positives only. &lt;/p&gt;

&lt;h3&gt;
  
  
  Setup &amp;amp; Usage
&lt;/h3&gt;

&lt;p&gt;For step by step instructions for setup and usage of this check out the github repo which has the code and all the instructions necessary.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aettern/copyfrag-fuse" rel="noopener noreferrer"&gt;github.com/aettern/copyfrag-fuse&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Where this is headed
&lt;/h3&gt;

&lt;p&gt;The thing that struck me the most isn't the exploit mechanincs but instead the fact that CopyFail was reportedly found within an hour of using an AI assisited process. This bug sat in the code for almost a decade and it's been shipped across every major distro since 2017 and it only took an hour for the AI to find it.&lt;/p&gt;

&lt;p&gt;This completely changes the math on old legacy kernel code. If discovery gets faster and cheaper then the gap between disclosure and patch matters more. I don't think runtime mitigations like &lt;code&gt;copyfrag-fuse&lt;/code&gt; will replace patching. But if the next one of these drops on a Friday night and the vendor kernel is three weeks out, then having something we can drop in without a reboot will come in handy always. &lt;/p&gt;

</description>
      <category>linux</category>
      <category>cybersecurity</category>
      <category>ebpf</category>
    </item>
  </channel>
</rss>
