On May 8, 2026 (May 7 UTC), security researcher Hyunwoo Kim (@v4bel) disclosed Dirty Frag on the oss-security mailing list — two chained Linux kernel vulnerabilities (later tracked as CVE-2026–43284 and CVE-2026–43500) that allow any unprivileged local user to gain root on virtually every major Linux distribution. The disclosure was an embargo break — "Because the embargo has now been broken, no patches or CVEs exist for these vulnerabilities." Microsoft reported limited in-the-wild activity shortly after disclosure. The observed attack chain: SSH-based initial access, privilege escalation via Dirty Frag, modification of GLPI LDAP authentication files, and forceful wiping of PHP session evidence to cover tracks.

The PoC was detonated on a default Ubuntu 22.04 GCP instance (kernel 6.8.0-1054-gcp, unpatched); Path A captured first, Path B separately via ./exp --force-rxrpc -v. Every audit record below is real. The detection approach is deliberately atomic — one single audit event per rule, no time windows, no correlation. The central insight: Dirty Frag cannot hide its kernel primitives. What we map below is what the exploit class structurally requires from the kernel, and the audit telemetry each step leaves behind.

What is Dirty Frag?

Dirty Frag belongs to a class of Linux LPE techniques — alongside DirtyCOW, DirtyPipe, and CopyFail — that share one trick: convince the kernel to write attacker-controlled data into memory it believes is read-only, then turn that write into root. The mechanism targets the page cache — the RAM region holding copies of files currently in use. The kernel trusts cached pages implicitly: it will execute code from them without re-reading from disk and authenticate users against them without rechecking the underlying file. Dirty Frag uses this trust two different ways.

Two paths, two targets, one shared sink. Both paths splice a page-cache page into a kernel crypto pipeline that performs an in-place decrypt without checking memory ownership. The cipher and subsystem differ; the structural flaw is identical.

  • Path A — ESP (CVE-2026–43284) — corrupts /usr/bin/su. Poisons the page-cache copy of su with a 192-byte shellcode stub. When the unmodified-on-disk SUID binary executes, the kernel grants root EUID based on disk metadata, then runs the poisoned RAM copy. Shellcode calls setgid(0)setuid(0)setgroups(0, NULL)execve("/bin/sh"). PAM never involved.
  • Path B — RxRPC (CVE-2026–43500) — corrupts /etc/passwd. Rewrites bytes 4-15 of the root entry to produce root::0:0: (empty password field). /usr/bin/su is unmodified. The exploit runs su normally and PAM's pam_unix.so nullok (default in /etc/pam.d/common-auth on Ubuntu) accepts the blank password.

What separates Dirty Frag from prior bugs in the class:

  • No race condition. Both paths are deterministic — same result every run.
  • Nearly universal reach for Path A. The xfrm-ESP flaw was introduced in 2017. Per Tenable, Linux distros released in the last nine years are likely affected. Path B has been present since 2023.
  • Asymmetric prerequisites. Path A needs a user namespace (unshare(CLONE_NEWUSER|CLONE_NEWNET)) to obtain CAP_NET_ADMIN. Path B runs entirely unprivileged in the host namespace. (Some vendor coverage describes Path B as also requiring a namespace; the public PoC's Path B does not — unshare is opt-in via POC_UNSHARE=1, exp.c:1238-1241.)
  • Bypasses CopyFail mitigations. Per Elastic Security Labs, "DirtyFrag does not depend on the algif_aead module, meaning systems that only applied the Copy Fail mitigation may still be exposed" — same in-place-decrypt sink, different kernel subsystems (algif_aead vs xfrm/rxkad).

Detection asymmetry — the most important takeaway of this post. Path A leaves an unconditional kernel-native audit event (MAC_IPSEC_EVENT SA-icv-failure) that fires regardless of auditd rule configuration — once per 4-byte write, 48 times per exploitation. Path B leaves no equivalent: rxkad emits nothing on verification failure. Detection of Path B depends entirely on rules deployed before the attack. If you take one thing from this post: assume Path A will catch itself; pre-deploy rules for Path B.

How It Works

Two paths reach the same primitive through different kernel subsystems. Both abuse the kernel performing in-place crypto on splice-targeted page-cache pages without checking memory ownership. Path A uses the xfrm/ESP receive pipeline; Path B uses the rxkad authentication subsystem.

Step 1 — Borrow a network key (Path A only). The ESP path needs CAP_NET_ADMIN to register XFRM Security Associations. unshare(CLONE_NEWUSER|CLONE_NEWNET) creates an isolated sandbox that grants it. Path B doesn't need a namespace — AF_RXRPC, add_key, AF_ALG, and splice triggers all work unprivileged in the host namespace.

Step 2 — Arm

Step 3 — Target the page cache

Step 4 — Write

Step 5 — Escalate

Lab Setup

Telemetry below was captured on a default Ubuntu 22.04 GCP instance running kernel 6.8.0-1054-gcp (unpatched, auditd loaded) with the audit backlog raised to 8192 (auditctl -b 8192 — without this, Path B captures lose records to the syscall burst). Eight auditd rules cover both paths' kill-chain primitives (the SA-icv-failure and SAD-add events emit kernel-side without needing rules — see the note below the ruleset). Add to /etc/audit/rules.d/dirtyfrag.rules, reload with sudo augenrules --load:

# 1. Path A — Namespace escape (earliest indicator)
# Three flag values: NEWUSER alone, NEWUSER|NEWNET (V4bel), NEWUSER|NEWPID|NEWNET (defensive variant)
-a always,exit -F arch=b64 -S unshare -F a0=268435456  -k net_userns_escape
-a always,exit -F arch=b64 -S unshare -F a0=1342177280 -k net_userns_escape
-a always,exit -F arch=b64 -S unshare -F a0=1879048192 -k net_userns_escape
-a always,exit -F arch=b32 -S unshare -F a0=268435456  -k net_userns_escape
-a always,exit -F arch=b32 -S unshare -F a0=1342177280 -k net_userns_escape
-a always,exit -F arch=b32 -S unshare -F a0=1879048192 -k net_userns_escape
# 2. Path A — XFRM SA registration channel
-a always,exit -F arch=b64 -S socket -F a0=16 -F a2=6 -k dirtyfrag_xfrm_netlink
-a always,exit -F arch=b32 -S socket -F a0=16 -F a2=6 -k dirtyfrag_xfrm_netlink
# 3. Both paths — page-cache write primitive
-a always,exit -F arch=b64 -S vmsplice -k pagecache_write_primitive
-a always,exit -F arch=b64 -S splice   -k pagecache_write_primitive
-a always,exit -F arch=b32 -S vmsplice -k pagecache_write_primitive
-a always,exit -F arch=b32 -S splice   -k pagecache_write_primitive
# 4. Both paths — privilege escalation confirmation
-a always,exit -F arch=b64 -S setuid -S setreuid -S setresuid -F auid!=4294967295 -k priv_esc
-a always,exit -F arch=b64 -S setgid -S setregid -S setresgid -F auid!=4294967295 -k priv_esc
-a always,exit -F arch=b32 -S setuid -S setreuid -S setresuid -F auid!=4294967295 -k priv_esc
-a always,exit -F arch=b32 -S setgid -S setregid -S setresgid -F auid!=4294967295 -k priv_esc
# 5. Path B — AF_RXRPC socket (anchor)
-a always,exit -F arch=b64 -S socket -F a0=33 -k dirtyfrag_rxrpc_socket
-a always,exit -F arch=b32 -S socket -F a0=33 -k dirtyfrag_rxrpc_socket
# 6. Path B — RxRPC session-key registration
-a always,exit -F arch=b64 -S add_key -k dirtyfrag_add_key
-a always,exit -F arch=b32 -S add_key -k dirtyfrag_add_key
# 7. Both paths — kernel module load monitoring
-a always,exit -F arch=b64 -S init_module -S finit_module -k kernel_module_load
-a always,exit -F arch=b32 -S init_module -S finit_module -k kernel_module_load
# 8. Path B — AF_ALG socket (pcbc(fcrypt) checksum prep)
-a always,exit -F arch=b64 -S socket -F a0=38 -k dirtyfrag_af_alg_socket
-a always,exit -F arch=b32 -S socket -F a0=38 -k dirtyfrag_af_alg_socket

Audit records below show the key= value from the -k flag in each rule above; these are defender-chosen tags, not anything the exploit sets.

MAC_IPSEC_EVENT records require no rule. SAD-add and SA-icv-failure are mandatory kernel audit events emitted directly by the XFRM subsystem via xfrm_audit_state_add() and xfrm_audit_state_icvfail() (net/xfrm/xfrm_state.c), using audit type AUDIT_MAC_IPSEC_EVENT (1415, include/uapi/linux/audit.h). They appear independent of any rule configuration. Suppressing them requires auditctl -a exclude,always -F msgtype=MAC_IPSEC_EVENT.

For the nerds — full telemetry walkthrough

For detection engineers who want the receipts — every captured audit record from both paths, phase by phase, syscall by syscall, cross-referenced to the exp.c line that produced it and the kernel mechanism that emitted it — there's a companion post that walks the full audit trail:

I'll Catch a Grenade for You — Dirty Frag Telemetry Walkthrough

Covered there:

  • Path A (Phases 1–4): namespace trick, XFRM SA registration, splice chain to page cache, in-place ESP decrypt poisoning the page, MAC_IPSEC_EVENT loopback ICV failure, privileged execution
  • Path B (Phases 1–5): rxrpc.ko module autoload, /etc/passwd open + mmap, offline pcbc(fcrypt) brute-force, three-trigger A→B→C kernel chain with AF_ALG checksum prep, PAM nullok escalation

Detection

The detection approach is atomic — every rule below matches a single audit event in isolation. No time windows, no event-count thresholds, no cross-event correlation. The exploit's syscall sequence is structural; the timing is fully attacker-controlled, and single-event invariants survive arbitrary pacing.

Each rule covers a kernel mechanism the public DirtyFrag exploit invokes during its run — namespace escape, SA registration, page-cache routing, in-place crypto setup, write trigger. Rules 4 and 9 match MAC_IPSEC_EVENT records (kernel-native, no auditd rule required); the rest need their auditd rules deployed before the attack.

Recommendations

Prevention

Disable unprivileged user namespaces (blocks Path A only). Removes the unshare(CLONE_NEWUSER) prerequisite without touching IPsec or kernel modules (Red Hat RHSB-2026-003):

# Debian/Ubuntu
echo "kernel.unprivileged_userns_clone=0" | sudo tee /etc/sysctl.d/99-dirtyfrag.conf && sudo sysctl --system

# RHEL/Fedora
echo "user.max_user_namespaces=0" | sudo tee /etc/sysctl.d/99-dirtyfrag.conf && sudo sysctl --system

Red Hat's bulletin notes the sysctl approach blocks the ESP variant only; CVE-2026–43500 (the rxrpc variant) is reported as not affecting Red Hat products (because rxrpc.ko isn't shipped on RHEL by default). The RxRPC path runs in the host namespace and is unaffected by these sysctls regardless of distribution. To close both paths on systems where rxrpc IS affected, pair with the module blacklist below. Cost: rootless Docker/Podman, Flatpak, Firefox/Chrome sandboxes break.

Blacklist vulnerable modules. If IPsec (esp4/esp6) and AFS (rxrpc) are not in active use:

printf 'install esp4 /bin/false\ninstall esp6 /bin/false\ninstall rxrpc /bin/false\n' \
  | sudo tee /etc/modprobe.d/dirtyfrag.conf
sudo rmmod esp4 esp6 rxrpc 2>/dev/null
lsmod | grep -E 'esp4|esp6|rxrpc'  # confirm what's loaded before committing

esp4/esp6 break IPsec VPNs. rxrpc breaks AFS. Confirm neither is in use.

Container environments. Coverage varies sharply by orchestrator default:

Any container running with --privileged or seccomp=unconfined bypasses these defaults entirely — both paths permitted regardless of orchestrator. Sysdig recommends restricting AF_KEY, AF_RXRPC, and XFRM netlink syscalls in container runtimes via seccomp. For Kubernetes, deploy an explicit SeccompProfile covering both paths — Docker's defaults are NOT inherited by K8s pods.

Conclusion

Dirty Frag is the second universal Linux LPE in eight days — following CopyFail's algif_aead page-cache write — and both share the same root pattern: a kernel performance optimization that skips a safety check, turned into a deterministic write primitive. DirtyCOW, DirtyPipe, CopyFail, Dirty Frag — separate bugs, discovered years apart, all reducing to the same question: can an attacker get the kernel to write attacker-controlled data into a page-cache page it was never supposed to touch? The detection approach here focuses on what is architecturally required: unshare, XFRM SA registration, the vmsplice/splice chain, AF_RXRPC from a non-AFS process, AF_ALG pcbc(fcrypt), and the loopback SA-icv-failure. These are structural requirements of the vulnerability class and surface in the kernel's own audit infrastructure regardless of how the exploit is packaged.

References