Files
SKELETONKEY/modules/dirtydecrypt_cve_2026_31635/MODULE.md
T
leviathan a8c8d5ef1f modules: add dirtydecrypt (CVE-2026-31635) + fragnesia (CVE-2026-46300)
Two new page-cache-write LPE modules, both ported from the public V12
security PoCs (github.com/v12-security/pocs):

- dirtydecrypt (CVE-2026-31635): rxgk missing-COW in-place decrypt.
  rxgk_decrypt_skb() decrypts spliced page-cache pages before the HMAC
  check, corrupting the page cache of a read-only file. Sibling of
  Copy Fail / Dirty Frag in the rxrpc subsystem.

- fragnesia (CVE-2026-46300): XFRM ESP-in-TCP skb_try_coalesce() loses
  the SHARED_FRAG marker, so the ESP-in-TCP receive path decrypts
  page-cache pages in place. A latent bug exposed by the Dirty Frag
  fix (f4c50a4034e6). Retires the old _stubs/fragnesia_TBD stub.

Both wrap the PoC exploit primitive in the skeletonkey_module
interface: detect/exploit/cleanup, an --active /tmp sentinel probe,
--no-shell support, and embedded auditd + sigma rules. The exploit
body runs in a forked child so the PoC's exit()/die() paths cannot
tear down the dispatcher. The fragnesia port drops the upstream PoC's
ANSI TUI (incompatible with a shared dispatcher); the exploit
mechanism is reproduced faithfully. Linux-only code is guarded with
#ifdef __linux__ so the modules still compile on non-Linux dev boxes.

VERIFICATION: ported, NOT yet validated end-to-end on a
vulnerable-kernel VM. The CVE fix commits are not pinned, so detect()
is precondition-only (PRECOND_FAIL / TEST_ERROR, never a blind
VULNERABLE) and --auto will not fire them unless --active confirms.
macOS stub-path compiles verified locally; the Linux exploit-path
build is covered by CI (build.yml, ubuntu) only. See each MODULE.md.

Wiring: core/registry.h, skeletonkey.c, Makefile, CVES.md, ROADMAP.md.
2026-05-22 18:22:30 -04:00

3.5 KiB

dirtydecrypt — CVE-2026-31635

🟡 PRIMITIVE / ported. Faithful port of the public V12 PoC into the skeletonkey_module interface. Not yet validated end-to-end on a vulnerable-kernel VM — see Verification status below.

Summary

DirtyDecrypt (a.k.a. DirtyCBC) is a missing copy-on-write guard in rxgk_decrypt_skb() (net/rxrpc/rxgk_common.h). The function decrypts incoming rxgk socket buffers in place before the HMAC is verified. When the skb fragment pages are page-cache pages — spliced in via MSG_SPLICE_PAGES over loopback — the in-place AES decrypt corrupts the page cache of a read-only file.

It is a sibling of Copy Fail (CVE-2026-31431) and Dirty Frag (CVE-2026-43284 / 43500): same bug class, different kernel subsystem (rxgk / AFS-style rxrpc encryption rather than algif_aead or xfrm-ESP).

Primitive

Each fire():

  1. Adds an rxrpc security key holding a crafted rxgk XDR token.
  2. Opens an AF_RXRPC client + a fake UDP server on loopback and completes the rxgk handshake.
  3. Forges a DATA packet whose wire header comes from userspace and whose payload pages come from the target file's page cache (splice + vmsplice).
  4. The kernel decrypts the spliced page-cache pages in place — the HMAC check then fails (expected), but the page cache is already mutated.

pagecache_write() drives a sliding-window technique: byte[0] of each corrupted 16-byte AES block is uniformly random (≈1/256 chance of the wanted value), and round i+1 at offset S+i+1 overwrites the 15-byte collateral of round i without disturbing the byte round i fixed. Net cost ≈ 256 fires per byte.

The exploit rewrites the first 120 bytes of a setuid-root binary (/usr/bin/su and friends) with a tiny ET_DYN ELF that calls setuid(0) + execve("/bin/sh").

Operations

Op Behaviour
--scan Checks AF_RXRPC reachability + a readable setuid carrier. With --active, fires the primitive against a disposable /tmp file and reports VULNERABLE/OK empirically.
--exploit … --i-know Forks a child that corrupts the carrier's page cache and execs it for a root shell. --no-shell stops after the page-cache write.
--cleanup Evicts the carrier from the page cache (POSIX_FADV_DONTNEED + drop_caches). The on-disk binary is never written.
--detect-rules Emits embedded auditd + sigma rules.

Preconditions

  • AF_RXRPC reachable (the rxrpc module loadable / built in).
  • A readable setuid-root binary to use as the payload carrier.
  • x86_64 (the embedded ELF payload is x86_64 shellcode).

Verification status

This module is a faithful port of https://github.com/v12-security/pocs/tree/main/dirtydecrypt, compiled into the SKELETONKEY module interface. It has not been validated end-to-end against a known-vulnerable kernel inside the SKELETONKEY CI matrix.

detect() deliberately does not return a kernel-version-based patched/vulnerable verdict: the CVE-2026-31635 fix commit is not yet pinned here, and fabricating a kernel_patched_from table would violate the project's no-fabrication rule (CVES.md). Instead:

  • preconditions missing → PRECOND_FAIL
  • preconditions present, no --activeTEST_ERROR ("cannot determine passively") so --auto does not fire it blind
  • --active → empirical VULNERABLE / OK via the /tmp sentinel probe

Before promoting to 🟢: pin the fix commit + branch-backport thresholds, add a kernel_range, and validate on a vulnerable VM.