
Detector + PoC for Linux page-cache write vulnerabilities: Copy Fail (CVE-2026-31431) and Dirty Frag (CVE-2026-43284/43500). Authorized security research only.
A unified detector and PoC harness for the Copy Fail and Dirty Frag Linux page-cache write vulnerability families.
██████╗ ██╗██████╗ ████████╗██╗ ██╗███████╗ █████╗ ██╗██╗
██╔══██╗██║██╔══██╗╚══██╔══╝╚██╗ ██╔╝██╔════╝██╔══██╗██║██║
██║ ██║██║██████╔╝ ██║ ╚████╔╝ █████╗ ███████║██║██║
██║ ██║██║██╔══██╗ ██║ ╚██╔╝ ██╔══╝ ██╔══██║██║██║
██████╔╝██║██║ ██║ ██║ ██║ ██║ ██║ ██║██║███████╗
╚═════╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚══════╝
DIRTYFAIL is a small, well-documented C tool for security researchers. It detects whether a Linux host is vulnerable to the three CVEs in this family, and — with explicit, typed confirmation — runs a real proof-of-concept that drops the caller into a root shell on a vulnerable system.
| CVE / variant | Name | DIRTYFAIL coverage |
|---|---|---|
| CVE-2026-31431 | Copy Fail (algif_aead authencesn page-cache write) | Detect + full PoC |
| CVE-2026-43284 v4 | Dirty Frag — IPv4 xfrm-ESP page-cache write | Detect + full PoC |
| CVE-2026-43284 v6 | Dirty Frag — IPv6 xfrm-ESP page-cache write (esp6) | Detect + full PoC |
| CVE-2026-43500 | Dirty Frag — RxRPC page-cache write | Detect + full PoC |
| Copy Fail GCM variant | xfrm-ESP rfc4106(gcm(aes)) page-cache write | Detect + full PoC |
Bonus modes:
--scan --active — sentinel-STORE active probes. Default --scan
reports per-CVE preconditions (kernel, modules, LSM state) plus an
active probe of the Copy Fail primitive. Adding --active extends
the sentinel-file STORE probe to all four other primitives (ESP v4,
ESP v6, RxRPC, GCM): each fires the kernel trigger against a /tmp
sentinel and reports VULNERABLE only if the marker bytes actually
land. This is the only way to distinguish a backported-patched
kernel (preconds say vulnerable but probe says intact) from an
unpatched one without running the full exploit. /etc/passwd is
never touched. Auto-calibrates V6 STORE shift per kernel build.--exploit-backdoor — persistent uid-0 backdoor: length-matched
overwrite of a nologin/false/sync line in /etc/passwd with
dirtyfail::0:0:<pad>:/:/bin/bash. Survives shell exit until page
is evicted. State stashed at /var/tmp/.dirtyfail.state for
--cleanup-backdoor. The dirtyfail username is deliberately
matched to this project so it's instantly identifiable in any
audit — change NEW_USER in src/backdoor.c if you need a
different identifier for an authorized red-team engagement.apparmor_restrict_unprivileged_userns=1 policy via a single-hop
change_onexec("crun") re-exec into an unconfined profile that
retains userns capabilities. Each exploit mode handles this
internally via a fork: parent stays in init namespace, child does
the bypass dance, parent reads global page cache and runs su for
REAL init-ns root. The legacy --aa-bypass flag still exists for
debugging the bypass mechanics in isolation. See §8.5 Architecture.DIRTYFAIL has been empirically validated end-to-end across multiple
distros and kernel versions. The matrix below reflects per-mode test
results from running each --exploit-* mode against a fresh install
of each distro.
| Distro | Kernel | LSM | Copy Fail | xfrm-ESP v4 | xfrm-ESP v6 | RxRPC | GCM | Backdoor | SU shellcode |
|---|---|---|---|---|---|---|---|---|---|
| Ubuntu 24.04 LTS | 6.8.0-111-generic | AppArmor | 🛡² | ✅ | ✅ | ✅ | ✅¹ | ✅¹ | (not tested) |
| Debian 13.4 | 6.12.86+deb13 | none | 🛡 | 🛡 | 🛡 | 🛡 | 🛡 | 🛡 | 🛡⁵ |
| AlmaLinux 10.1 | 6.12.0-124.8.1.el10_1 | SELinux | ✅ | ✅ | ✅ | ⏭³ | ✅ | ✅ | ✅ |
| Fedora 44 (Server) | 6.19.10-300.fc44 | SELinux | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Ubuntu 26.04 LTS | 7.0.0-15-generic | AppArmor (hardened) | 🛡 | 🛡⁴ | 🛡⁴ | 🛡⁴ | 🛡⁴ | 🛡⁴ | 🛡⁵ |
Legend: ✅ exploit landed and produced real init-ns root · 🛡 mitigated — exploit cannot reach kernel bug (kernel patched OR LSM blocks unprivileged path) · ⏭ not applicable (precondition missing)
--scan --active)The --active flag adds a sentinel-file STORE probe per CVE during
detection. We validated the probe outputs against the same 4 distros
above (Debian, Fedora, AlmaLinux, Ubuntu 26.04) — the matrix below
shows the per-mode probe verdict and matches the full-exploit
ground-truth one-for-one:
| Distro | Copy Fail probe | ESP v4 probe | ESP v6 probe | RxRPC probe | GCM probe |
|---|---|---|---|---|---|
| Debian 13.4 | intact 🛡 | intact 🛡 | intact 🛡 | intact 🛡 | intact 🛡 |
| Fedora 44 | marker @0 ✅ | STORE @0 ✅ | STORE @8 ✅ | byte change ✅ | sentinel[0] 0x41→0x27 ✅ |
| AlmaLinux 10.1 | marker @0 ✅ | STORE @0 ✅ | STORE @8 ✅ | preconds ⏭ | sentinel changed ✅ |
| Ubuntu 26.04 | intact 🛡 | LSM-blocked 🛡 | LSM-blocked 🛡 | LSM-blocked 🛡 | LSM-blocked 🛡 |
The V6 probe's STORE landing offset (8 on Fedora and Alma) matches the
empirical V6_STORE_SHIFT that calibrate_v6_shift() discovers at
runtime — confirming the auto-calibration replaces the previously
hard-coded constant correctly across kernel builds.
¹ GCM and Backdoor require algif_aead to be loadable. Ubuntu 24.04
ships /etc/modprobe.d/disable-algif_aead.conf blacklisting it as a
Copy Fail mitigation. With the blacklist removed (e.g. on a kernel
predating the mitigation), both modes work end-to-end.
² Copy Fail's algif_aead path is mitigated by the modprobe blacklist;
the underlying CVE primitive in the kernel is the same whether
authencesn is reachable. xfrm-ESP, RxRPC, and the GCM variant all
land on the same kernel because they don't go through algif_aead.
³ AlmaLinux 10's kernel-modules-extra package is not installed by
default on a Minimal install, so rxrpc.ko is missing on disk.
Installing kernel-modules-extra-$(uname -r) from EPEL or the AlmaLinux
extras repo brings the module back; on a stock minimal install RxRPC is
unreachable.
⁴ Ubuntu 26.04 LTS comprehensively blocks unprivileged exploitation.
The shipping kernel 7.0.0-15.15 (released 2026-04-22) predates the
mainline patch f4c50a4034e6 (merged 2026-05-07) by ~2 weeks — so
the bug IS still present in the kernel. Ubuntu's defense is
defense-in-depth via AppArmor hardening, not a kernel patch:
apparmor_restrict_unprivileged_userns=1 is enabled by default.unshare(CLONE_NEWUSER), the kernel-level AppArmor enforcement
auto-transitions ANY profile (including (unconfined)-flagged ones
like crun, chrome, default unconfined) to a
<profile>//&unprivileged_userns (mixed) sub-profile that has
audit deny capability. uid 0 inside the new userns gets no caps.change_onexec to a different profile doesn't help — even the
crun profile (which has explicit userns, permission and
flags=(unconfined)) auto-transitions on unshare. Verified via
aa-exec -p crun bash -c 'unshare -U -n cat /proc/self/attr/current'
→ crun//&unprivileged_userns (mixed).newuidmap/newgidmap (setuid root) successfully writes uid_map,
but setresuid(0) then succeeds while ioctl(SIOCSIFFLAGS) and
every other CAP_NET_ADMIN-gated syscall returns EPERM because the
capability denial is per-namespace, not per-uid.The DIRTYFAIL binary correctly armes its bypass and reaches stage 2,
but cannot acquire CAP_NET_ADMIN inside the new userns. The exploit
infrastructure is blocked at the LSM layer regardless of bypass
technique. We tested change_onexec(crun), change_onexec(chrome),
aa-exec -p <profile>, and direct unshare(USER|NET) + newuidmap
— all produce the same unprivileged_userns sub-profile.