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.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
# fragnesia — CVE-2026-46300
|
||||
|
||||
> 🟡 **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
|
||||
|
||||
Fragnesia ("Fragment Amnesia") is an XFRM ESP-in-TCP local privilege
|
||||
escalation. `skb_try_coalesce()` fails to propagate the
|
||||
`SKBFL_SHARED_FRAG` marker when moving paged fragments between socket
|
||||
buffers — so the kernel forgets that a fragment is externally backed by
|
||||
page-cache pages spliced in from a file. The ESP-in-TCP receive path
|
||||
then decrypts in place, corrupting the page cache of a read-only file.
|
||||
|
||||
Fragnesia is a **latent bug exposed by the Dirty Frag fix**: the
|
||||
candidate patch cites the Dirty Frag remediation (`f4c50a4034e6`) as a
|
||||
commit it "fixes". It is the same page-cache-write bug class as Copy
|
||||
Fail / Dirty Frag, reached through a different code path.
|
||||
|
||||
## Primitive
|
||||
|
||||
1. Build a 256-entry **AES-GCM keystream-byte table** via `AF_ALG`
|
||||
`ecb(aes)` — for any wanted output byte, this yields the ESP IV
|
||||
whose keystream byte XORs the current byte to the target.
|
||||
2. Enter a mapped **user namespace** + **network namespace**, bring
|
||||
loopback up, and install an XFRM **ESP-in-TCP** state
|
||||
(`rfc4106(gcm(aes))`, `TCP_ENCAP_ESPINTCP`).
|
||||
3. A **receiver** accepts a loopback TCP connection and flips it to the
|
||||
`espintcp` ULP; a **sender** `splice()`s page-cache pages of the
|
||||
target file into that TCP stream behind a crafted ESP prefix.
|
||||
4. The coalesce bug makes the kernel decrypt the spliced page-cache
|
||||
pages in place — one chosen byte per trigger.
|
||||
|
||||
The exploit rewrites the first 192 bytes of a setuid-root binary
|
||||
(`/usr/bin/su` and friends) with an ET_DYN ELF that drops privileges to
|
||||
0 and `execve`s `/bin/sh`.
|
||||
|
||||
## Operations
|
||||
|
||||
| Op | Behaviour |
|
||||
|---|---|
|
||||
| `--scan` | Checks unprivileged-userns availability + a readable setuid carrier ≥ 4096 bytes. With `--active`, runs the full ESP-in-TCP primitive against a disposable `/tmp` file and reports VULNERABLE/OK empirically. |
|
||||
| `--exploit … --i-know` | Forks a child that places the payload into 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. The on-disk binary is never written. |
|
||||
| `--detect-rules` | Emits embedded auditd + sigma rules. |
|
||||
|
||||
## Preconditions
|
||||
|
||||
- **Unprivileged user namespaces enabled.** On Ubuntu, AppArmor blocks
|
||||
this by default — `sysctl kernel.apparmor_restrict_unprivileged_userns=0`
|
||||
(or chain a separate bypass). This is the scoping question the old
|
||||
`_stubs/fragnesia_TBD` raised; the module ships and reports
|
||||
`PRECOND_FAIL` cleanly when the userns gate is closed.
|
||||
- `CONFIG_INET_ESPINTCP` built into the kernel.
|
||||
- A readable setuid-root binary ≥ 4096 bytes as the payload carrier.
|
||||
- x86_64 (the embedded ELF payload is x86_64 shellcode).
|
||||
|
||||
## Port notes
|
||||
|
||||
The upstream PoC renders a full-screen ANSI "smash frame" TUI
|
||||
(`draw_smash_frame` + terminal scroll-region escapes). That is **not**
|
||||
ported — it cannot coexist with a shared multi-module dispatcher.
|
||||
Progress is logged with `[*]`/`[+]`/`[-]` prefixes, gated on `--json`.
|
||||
The exploit mechanism itself is reproduced faithfully.
|
||||
|
||||
## Verification status
|
||||
|
||||
This module is a **faithful port** of
|
||||
<https://github.com/v12-security/pocs/tree/main/fragnesia>, 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-46300 fix commit is not yet
|
||||
pinned here. Instead:
|
||||
|
||||
- preconditions missing → `PRECOND_FAIL`
|
||||
- preconditions present, no `--active` → `TEST_ERROR` 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.
|
||||
@@ -0,0 +1,48 @@
|
||||
# NOTICE — fragnesia
|
||||
|
||||
## Vulnerability
|
||||
|
||||
**CVE-2026-46300** — "Fragnesia" ("Fragment Amnesia"). XFRM ESP-in-TCP
|
||||
local privilege escalation. `skb_try_coalesce()` fails to propagate the
|
||||
`SKBFL_SHARED_FRAG` marker when moving paged fragments between socket
|
||||
buffers, so the kernel loses track of the fact that a fragment is
|
||||
externally backed by page-cache pages spliced in from a file. The
|
||||
ESP-in-TCP receive path then decrypts in place, corrupting the page
|
||||
cache of a read-only file.
|
||||
|
||||
Fragnesia is a **latent bug exposed by the Dirty Frag remediation**:
|
||||
the candidate fix explicitly cites the Dirty Frag patch
|
||||
(`f4c50a4034e6`) as a commit it "fixes" — the Dirty Frag remediation
|
||||
made a previously latent flaw practically exploitable.
|
||||
|
||||
## Research credit
|
||||
|
||||
Discovered by **William Bowling** with the **V12 security** team.
|
||||
|
||||
> Reference PoC: <https://github.com/v12-security/pocs/tree/main/fragnesia>
|
||||
> Patch thread: <https://lists.openwall.net/netdev/2026/05/13/79>
|
||||
|
||||
## SKELETONKEY role
|
||||
|
||||
`skeletonkey_modules.c` is a port of the V12 PoC
|
||||
(`xfrm_espintcp_pagecache_replace`) into the `skeletonkey_module`
|
||||
interface. The exploit primitive — the AES-GCM keystream-byte table
|
||||
built via AF_ALG, the per-byte IV selection, the userns + netns + XFRM
|
||||
ESP-in-TCP setup, the splice-driven sender/receiver trigger pair, the
|
||||
192-byte ELF payload — is reproduced from that PoC.
|
||||
|
||||
**Port adaptation:** the PoC's ANSI "smash frame" TUI
|
||||
(`draw_smash_frame` + terminal scroll-region escape sequences) is
|
||||
**not** carried over — it is incompatible with running as one module
|
||||
among many under a shared dispatcher. Progress is reported with
|
||||
SKELETONKEY's `[*]`/`[+]`/`[-]` log prefixes instead. SKELETONKEY also
|
||||
adds the detect/cleanup lifecycle, an `--active` probe, `--no-shell`
|
||||
support, and the embedded detection rules. Research credit belongs to
|
||||
the people above.
|
||||
|
||||
## Verification status
|
||||
|
||||
**Ported, not yet validated end-to-end on a vulnerable-kernel VM.**
|
||||
Requires `CONFIG_INET_ESPINTCP` and unprivileged user-namespace
|
||||
creation. The CVE-2026-46300 fix commit is not yet pinned in this
|
||||
module — see `MODULE.md`.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Fragnesia (CVE-2026-46300) — auditd detection rules
|
||||
#
|
||||
# The XFRM ESP-in-TCP coalesce bug corrupts the page cache of a
|
||||
# read-only file. These rules flag the syscall surface the exploit
|
||||
# drives and writes to the setuid binaries it targets.
|
||||
#
|
||||
# Install: copy into /etc/audit/rules.d/ and `augenrules --load`, or
|
||||
# skeletonkey --detect-rules --format=auditd | sudo tee \
|
||||
# /etc/audit/rules.d/99-skeletonkey.rules
|
||||
|
||||
# Modification of common payload carriers / credential files
|
||||
-w /usr/bin/su -p wa -k skeletonkey-fragnesia
|
||||
-w /bin/su -p wa -k skeletonkey-fragnesia
|
||||
-w /usr/bin/mount -p wa -k skeletonkey-fragnesia
|
||||
-w /usr/bin/passwd -p wa -k skeletonkey-fragnesia
|
||||
-w /usr/bin/chsh -p wa -k skeletonkey-fragnesia
|
||||
-w /etc/passwd -p wa -k skeletonkey-fragnesia
|
||||
-w /etc/shadow -p wa -k skeletonkey-fragnesia
|
||||
|
||||
# AF_ALG socket creation (family 38) — builds the GCM keystream table
|
||||
-a always,exit -F arch=b64 -S socket -F a0=38 -k skeletonkey-fragnesia-afalg
|
||||
|
||||
# XFRM state setup over NETLINK_XFRM
|
||||
-a always,exit -F arch=b64 -S sendto -k skeletonkey-fragnesia-xfrm
|
||||
|
||||
# TCP_ULP espintcp + ESP setsockopt surface
|
||||
-a always,exit -F arch=b64 -S setsockopt -k skeletonkey-fragnesia-sockopt
|
||||
|
||||
# splice() drives page-cache pages into the ESP-in-TCP stream
|
||||
-a always,exit -F arch=b64 -S splice -k skeletonkey-fragnesia-splice
|
||||
-a always,exit -F arch=b32 -S splice -k skeletonkey-fragnesia-splice
|
||||
@@ -0,0 +1,30 @@
|
||||
title: Possible Fragnesia exploitation (CVE-2026-46300)
|
||||
id: 9b3d2e71-skeletonkey-fragnesia
|
||||
status: experimental
|
||||
description: |
|
||||
Detects the file-modification footprint of the Fragnesia XFRM
|
||||
ESP-in-TCP page-cache write (CVE-2026-46300): non-root modification
|
||||
of a setuid-root binary or credential file, typically inside a
|
||||
freshly created user + network namespace.
|
||||
references:
|
||||
- https://github.com/v12-security/pocs/tree/main/fragnesia
|
||||
- https://lists.openwall.net/netdev/2026/05/13/79
|
||||
logsource:
|
||||
product: linux
|
||||
service: auditd
|
||||
detection:
|
||||
modification:
|
||||
type: 'PATH'
|
||||
name|startswith:
|
||||
- '/usr/bin/su'
|
||||
- '/bin/su'
|
||||
- '/etc/passwd'
|
||||
- '/etc/shadow'
|
||||
not_root:
|
||||
auid|expression: '!= 0'
|
||||
condition: modification and not_root
|
||||
level: high
|
||||
tags:
|
||||
- attack.privilege_escalation
|
||||
- attack.t1068
|
||||
- cve.2026.46300
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* fragnesia_cve_2026_46300 — SKELETONKEY module registry hook
|
||||
*/
|
||||
|
||||
#ifndef FRAGNESIA_SKELETONKEY_MODULES_H
|
||||
#define FRAGNESIA_SKELETONKEY_MODULES_H
|
||||
|
||||
#include "../../core/module.h"
|
||||
|
||||
extern const struct skeletonkey_module fragnesia_module;
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user