The week of June 16 gave Linux sysadmins two separate privilege escalation bugs with working public exploits, both targeting the page cache, neither leaving any trace on disk, and both arriving faster than most patch cycles can handle. One came through the traffic-control subsystem. The other came through the networking stack’s fragment-handling code. They share an attack shape that should, at this point, feel uncomfortably familiar.

pedit COW (CVE-2026-46331): tc rewrites memory it does not own

What it is

CVE-2026-46331 is an out-of-bounds write in act_pedit, the kernel’s packet-editing action inside Linux’s tc (traffic control) subsystem. Red Hat rates it as Important. A public, working exploit appeared within 24 hours of CVE assignment on June 16.

The bug name “pedit COW” comes from the fact that copy-on-write semantics failed. The kernel function responsible, tcf_pedit_act(), is supposed to take a private copy of packet data before editing it. That’s the whole point of COW: you don’t write into shared memory, you copy it first, then write.

The problem is the range check runs too early. tcf_pedit_act() validates the writable range once, before all the pedit key offsets are known. Some keys only resolve their final offset at runtime, after that check has already passed. When the runtime offset lands outside the privately copied region, the write falls through to a shared page-cache page. If that shared page happens to back a cached file – say, a setuid binary, its in-memory image is now corrupted.

The exploit chain

The exploit needs two preconditions in place:

  1. act_pedit has to be loadable. On affected systems it loads as a kernel module automatically when needed.
  2. Unprivileged user namespaces have to be open (they are, by default, on RHEL and Debian).

Those two conditions together let a local user create a namespace, pick up CAP_NET_ADMIN inside it, and use that capability to configure tc pedit actions. From there, the write primitive is live.

The exploit targets /bin/su. It poisons the in-memory cached copy of the binary with a small payload, then executes it. The binary on disk never changes. File-integrity monitors see nothing. A root shell opens anyway.

This is the same general shape as Dirty Pipe from 2022, Copy Fail from April, DirtyFrag from May, and DirtyClone (covered below). Kernel fast path writes into a page it doesn’t exclusively own; the page cache absorbs the hit; privileged code runs from corrupted memory.

What’s different about pedit COW is the entry point. Getting CAP_NET_ADMIN through a user namespace to configure network packet actions is a much simpler path than chaining IPsec or RxRPC subsystems. The attack surface is broader here, not narrower.

Affected distributions

The PoC author tested on RHEL 10 and Debian 13 (trixie). Both were fully exploitable out of the box. Ubuntu 24.04 required routing through AppArmor profiles that still allow namespace creation. Ubuntu 26.04 blocks the default exploit path via AppArmor restrictions on unprivileged user namespaces, though the underlying kernel is still vulnerable – the mitigation is configuration, not a code fix.

Vendor status as of June 25:

Mitigations if you cannot patch

If the patched kernel is not available yet, two controls break the chain:

Option 1: Blacklist the module.

Check whether it’s in use first:

lsmod | grep act_pedit

If it’s not loaded, block it:

echo 'install act_pedit /bin/true' | sudo tee /etc/modprobe.d/disable-act_pedit.conf

This only works if your environment does not depend on tc pedit rules. Check before applying.

Option 2: Disable unprivileged user namespaces.

On RHEL:

sysctl -w user.max_user_namespaces=0

On Debian/Ubuntu:

sysctl -w kernel.unprivileged_userns_clone=0

This removes the capability path the exploit needs. Side effects are real though: rootless containers (Podman, rootless Docker), sandboxed browsers, and some CI sandbox environments depend on unprivileged namespaces. Test before deploying.

One thing worth noting: dropping the page cache (echo 3 > /proc/sys/vm/drop_caches) clears the poisoned in-memory copy, but only if the attacker hasn’t already used it. If a root shell is already open, clearing the cache doesn’t help. At that point treat the host as compromised and respond accordingly.

The disclosure gap

The fix landed on the netdev mailing list in late May, described as a routine data-corruption patch. No CVE, no security advisory. The patch sat in public for weeks before the CVE was assigned on June 16 when it merged into mainline. A weaponized PoC followed the next day.

For page-cache corruption bugs with available exploit primitives, that gap is not acceptable. Waiting for a scanner rule to go red is too slow when an exploit author can work off the same mailing list you’re subscribed to.

DirtyClone (CVE-2026-43503): the fourth act of a running series

Context

DirtyClone is not a standalone bug. It’s the fourth member of the DirtyFrag vulnerability family, a set of Linux kernel LPEs that all share one failure mode: file-backed memory gets treated as writable packet buffer, and a kernel in-place operation writes into it.

The timeline matters here:

Each patch closed one path. Each time, another path remained open. CVE-2026-43503 is rated CVSS 8.8.

The core bug

Linux’s zero-copy networking lets file-backed memory serve as packet data through vmsplice and splice. The kernel attaches page-cache-backed memory into an skb (socket buffer) instead of copying it. This is a deliberate performance optimization.

To prevent in-place network operations from overwriting file-backed pages, the kernel uses a metadata flag: SKBFL_SHARED_FRAG. When this flag is set on an skb, subsystems that perform in-place decryption (like IPsec’s ESP path) are supposed to force a copy before writing.

The DirtyFrag original fix set this flag for spliced UDP packets. Fragnesia bypassed it because skb_try_coalesce() dropped the flag during coalescing. DirtyClone bypasses the remaining fixes because __pskb_copy_fclone(), triggered by netfilter’s TEE target, creates a cloned skb without propagating the flag.

The result: the original skb carries SKBFL_SHARED_FRAG intact and gets handled safely. The cloned skb loses the flag and enters the IPsec receive path without the safety marker. ESP decryption runs in-place on the clone’s payload, which still references the same page-cache page as the original.

Exploitation walkthrough

JFrog published a full exploit walkthrough on June 25. The attack proceeds in seven stages.

Stage 1: Map the target binary.

The attacker opens /usr/bin/su read-only and maps it into memory. This loads its pages into the page cache. These are the pages the exploit will corrupt.

int fd = open("/usr/bin/su", O_RDONLY);
char *p = mmap(NULL, mmap_size, PROT_READ, MAP_SHARED, fd, 0);

Stage 2: Wire those pages into a network packet.

Using vmsplice and splice, the attacker attaches page-cache-backed memory as the payload of a UDP packet. The skb is now backed by the same physical pages as /usr/bin/su.

struct iovec iov = { .iov_base = p + patch_offset, .iov_len = 16 };
vmsplice(pipefd[1], &iov, 1, 0);
splice(pipefd[0], NULL, sockfd, NULL, 16, 0);

Stage 3: Set up a loopback IPsec environment.

The attacker creates a fresh network namespace with unshare -Urn, acquiring CAP_NET_ADMIN inside it. They configure a loopback IPsec tunnel:

ip link set lo up
ip addr add 10.99.0.2/24 dev lo
ip xfrm state add src 127.0.0.1 dst 127.0.0.1 proto esp spi 0x12345678 \
reqid 1 mode transport enc 'cbc(aes)' ... auth 'hmac(sha1)' ...
ip xfrm policy add src 127.0.0.1 dst 127.0.0.1 dir out \
tmpl src 127.0.0.1 dst 127.0.0.1 proto esp reqid 1 mode transport

All traffic stays local. The same kernel instance processes the packet both ways.

Stage 4: Trigger the vulnerable clone via TEE.

A netfilter rule using the TEE target duplicates outgoing packets inside the kernel:

iptables -t mangle -A OUTPUT -p udp --dport 4500 \
-j TEE --gateway 10.99.0.2

When the UDP packet is sent, nf_dup_ipv4 clones it via __pskb_copy_fclone(). The clone loses SKBFL_SHARED_FRAG. The original is processed safely. The clone continues into the IPsec receive path without the flag.

Stage 5: In-place decryption writes into the page cache.

When the cloned skb reaches esp_input(), IPsec decrypts the payload in-place. The input buffer and output buffer are the same allocation. That allocation is backed by the page-cache page holding /usr/bin/su.

Decrypted bytes get written directly into file-backed memory.

Stage 6: Turn decryption into a controlled write.

The attacker controls the AES-CBC key (via the XFRM state configuration), the IV per packet, and the packet layout. In AES-CBC, each decrypted block is:

P[i] = AES_decrypt(C[i]) XOR C[i-1]

Where C[-1] is the IV for the first block. The attacker knows the original bytes at the target offset (they’re readable from the mmap). They choose ciphertext and IV such that the decrypted output equals their desired patch bytes. The result is a controlled overwrite of specific instruction bytes inside /usr/bin/su‘s in-memory image.

Only a few bytes need to change. Conditional branch instructions controlling authentication checks are typical targets.

Stage 7: Execute the patched binary.

When /usr/bin/su is next executed, the kernel loads it from the page cache. The modified page is still resident. The patched instructions run instead of the originals. Authentication is bypassed. Root access obtained.

The file on disk is unchanged throughout. No kernel log entries are generated. No audit trail exists. The modification disappears on reboot but so does the root shell the attacker already has.

DirtyClone (CVE-2026-43503) PoC. Credits – JFrog Security

The structural problem

This is not a story about one bad function. Every patch in the DirtyFrag series closed a specific path where SKBFL_SHARED_FRAG was dropped. Each time, a different path remained.

The underlying contract is: every function that moves skb fragment descriptors must propagate the shared-frag flag. Without exception. The original DirtyFrag patch set the flag correctly for spliced UDP. Fragnesia found coalescing. DirtyClone found fclone. The CVE-2026-43503 patch covers __pskb_copy_fclone()skb_shift()skb_segment()skb_gro_receive()skb_gro_receive_list(), and tcp_clone_payload().

JFrog’s assessment on this point is blunt: the DirtyFrag class is probably not finished. Any frag-transfer function that doesn’t propagate the flag is a potential new CVE. An audit of every path touching skb_shinfo()->flags during fragment transfer is the only way to close this, and that audit has to be complete, not incremental.

Affected distributions and scope

Systems running unpatched kernels are exposed on any distribution where local users can obtain CAP_NET_ADMIN through unprivileged user namespaces. JFrog confirmed exploitation on Debian, Ubuntu, and Fedora with default namespace configurations.

The patch landed in Linux v7.1-rc5 on May 24. Distributions shipping kernels without the complete chain of fixes for the entire DirtyFrag family remain exposed. A system is only fully protected once every patch in the series is applied:

Ubuntu, Debian, and SUSE have published advisories. Red Hat has a tracking entry in Bugzilla.

Who is most at risk: Multi-tenant servers, CI/CD runners, container hosts, and Kubernetes clusters where untrusted users can create namespaces or where privileged containers are deployed. Cloud environments with shared tenancy and default namespace configurations should treat this as a priority.

Workarounds if you cannot patch

Restrict unprivileged user namespaces. On Debian and Ubuntu:

sysctl -w kernel.unprivileged_userns_clone=0

Other distributions have equivalent controls. This removes the CAP_NET_ADMIN acquisition path but breaks rootless containers and some browser sandboxes.

Blacklist ESP modules. If your environment doesn’t need IPsec or AFS:

echo 'install esp4 /bin/true' | sudo tee /etc/modprobe.d/disable-esp.conf
echo 'install esp6 /bin/true' | sudo tee -a /etc/modprobe.d/disable-esp.conf
echo 'install rxrpc /bin/true' | sudo tee -a /etc/modprobe.d/disable-esp.conf

This only works when the affected modules are loadable, not compiled into the kernel. Check your kernel configuration before relying on this.

Both controls are temporary. Neither is a fix.

What these two bugs have in common

Both CVE-2026-46331 and CVE-2026-43503 exploit the same architectural tension: the Linux kernel shares physical memory between multiple subsystems for performance, and the safety mechanisms preventing one subsystem from corrupting another are enforced through metadata flags, COW invariants, and range checks. When any of those checks fail, runs early, or fail to propagate across a code path, shared read-only memory becomes a write target.

Both exploits avoid the disk entirely. Both bypass file-integrity monitoring because those tools check on-disk state. Both leave no kernel audit log. Both require only a local unprivileged user account to start.

The entry points differ. pedit COW gets there through the traffic-control subsystem’s packet-editing actions. DirtyClone gets there through a netfilter packet duplication and IPsec decryption chain. The destination is the same: a root shell from a page-cache write.

What to do

Patch. Both vulnerabilities have fixes available in vendor security channels. For CVE-2026-43503 specifically, the fix is a series, not a single patch. Verify that your updated kernel contains all four CVEs in the DirtyFrag chain (CVE-2026-43284, CVE-2026-43500, CVE-2026-46300, CVE-2026-43503), not just the most recent one. A partial backport may leave gaps.

Prioritize exposure. CI/CD runners, shared development hosts, Kubernetes nodes, and any multi-tenant system where “local user” is not synonymous with “trusted user” are the highest-priority targets for attackers exploiting either bug. A developer workstation used by one person is lower risk than a Jenkins node used by many.

Don’t rely on file-integrity checks alone. Both exploits work entirely through in-memory corruption. AIDE, Tripwire, and similar tools verify on-disk state. They will not catch these attacks. Kernel-level monitoring (eBPF-based, auditd for setuid execution, anomaly detection on namespace creation) gives better visibility into this attack class.

If you suspect compromise: A root shell opened through either exploit leaves no trace in the kernel’s audit log. If you have reason to believe exploitation occurred and cannot immediately patch and reboot, treat the host as compromised. Clearing the page cache removes the poisoned in-memory copy but cannot undo privilege escalation that already happened.

FAQs

What is pedit COW (CVE-2026-46331)?

CVE-2026-46331 is an out-of-bounds write vulnerability in the Linux kernel’s traffic-control packet-editing subsystem (act_pedit). The bug causes the kernel to write into a shared page-cache page instead of a private copy, corrupting in-memory cached files. An unprivileged local user can exploit it to gain root access. A public working exploit appeared within 24 hours of CVE assignment.

What is DirtyClone (CVE-2026-43503)?

DirtyClone is the fourth member of the DirtyFrag family of Linux kernel privilege escalation vulnerabilities. It exploits a missing SKBFL_SHARED_FRAG flag propagation in __pskb_copy_fclone(), triggered through a netfilter TEE rule. The resulting unflagged skb clone passes through IPsec in-place decryption, which overwrites the file-backed page-cache page backing /usr/bin/su. JFrog Security Research published a full exploit walkthrough on June 25.

Why don’t file-integrity tools catch these attacks?

Both exploits operate entirely in RAM. They corrupt the kernel’s in-memory copy of a setuid binary through the page cache without touching the on-disk file. Tools like AIDE or Tripwire compare file hashes against what’s on disk, not against what’s in memory, so they see nothing unusual while a root shell is open.

What is the DirtyFrag vulnerability family?

DirtyFrag is a series of Linux kernel privilege escalation bugs where file-backed page-cache memory gets treated as writable network buffer memory, allowing in-place network operations (primarily IPsec decryption) to corrupt cached executable files. The family includes Copy Fail (CVE-2026-31431), DirtyFrag (CVE-2026-43284, CVE-2026-43500), Fragnesia (CVE-2026-46300), and DirtyClone (CVE-2026-43503). Each patch closed one code path; each subsequent CVE found another.

Which Linux distributions are affected?

For CVE-2026-46331: RHEL 8/9/10, Debian 11/12/13, and Ubuntu 18.04 through 26.04. Debian trixie has been patched. For CVE-2026-43503: any distribution running a kernel without the complete DirtyFrag patch chain, including Debian, Ubuntu, and Fedora. Ubuntu 24.04 and later have partial mitigation through AppArmor namespace restrictions but the underlying kernel vulnerability remains.

What should I do if I cannot patch immediately?

For pedit COW: blacklist the act_pedit module if your environment does not use tc pedit rules, or disable unprivileged user namespaces. For DirtyClone: disable unprivileged user namespaces or blacklist the esp4esp6, and rxrpc modules if IPsec and AFS are not needed. Both controls are temporary workarounds, not fixes. Test for side effects (rootless containers, browser sandboxes) before deploying.

Is the complete DirtyFrag patch series required, or just the latest CVE?

The complete series is required. A kernel that applied the original DirtyFrag fix but not Fragnesia is still vulnerable to Fragnesia’s bypass. A kernel that applied through Fragnesia but not CVE-2026-43503 is still vulnerable to DirtyClone. Full protection requires all four CVEs in the chain to be patched.

This post first appeared at - The CyberSec Guru