a26f471ecf
Both modules' detect() was precondition-only because we didn't know the
mainline fix commits at port time. Debian's security tracker now
provides them — pinning here turns detect() into a proper version-
based verdict (still with --active for empirical override).
dirtydecrypt (CVE-2026-31635):
- Fix commit a2567217ade970ecc458144b6be469bc015b23e5 in mainline 7.0
('rxrpc: fix oversized RESPONSE authenticator length check').
- Debian tracker confirms older stable branches (5.10 / 6.1 / 6.12) as
<not-affected, vulnerable code not present>: the rxgk RESPONSE-
handling code was added in 7.0.
- kernel_range table: { {7, 0, 0} }
- detect() pre-checks 'kernel < 7.0 -> SKELETONKEY_OK (predates)' then
consults the table. With --active, the /tmp sentinel probe overrides
empirically (catches pre-fix 7.0-rc kernels the version check
reports as patched).
fragnesia (CVE-2026-46300):
- Fix in mainline 7.0.9 per Debian tracker ('linux unstable: 7.0.9-1
fixed'). Older Debian-stable branches (bullseye 5.10 / bookworm 6.1
/ trixie 6.12) are still marked vulnerable as of 2026-05-22 - no
backports yet.
- kernel_range table: { {7, 0, 9} }
- detect() keeps the userns + carrier preconditions, then consults
the table: 7.0.9+ -> OK; older branches without an explicit backport
entry -> VULNERABLE (version-only). --active confirms empirically.
- Table is intentionally minimal so distros that DO backport in the
future flow into 'patched' once their branch lands an entry; until
then, the conservative VULNERABLE verdict on unfixed branches is
correct.
Other changes:
- module struct .kernel_range strings updated from 'fix commit not
yet pinned' to the actual pinned-version prose.
- module_safety_rank bumped 86 -> 87 for both modules (version-pinned
detect is now real; still below the verified copy_fail family at
88 so --auto prefers verified modules when both apply).
- Both modules now #include core/kernel_range.h inside their
#ifdef __linux__ block.
- MODULE.md verification-status sections rewritten: detect() is now
version-pinned; only the exploit body remains unverified.
- CVES.md note + inventory rows updated: dropped the 'precondition-
only' language for the pair; all three ported modules now have
pinned fix references.
- README ⚪ tier description + module list aligned to the new state.
Both detect()s smoke-tested in docker gcc:latest on kernel 6.12.76-
linuxkit: dirtydecrypt correctly reports OK ('predates the rxgk code
added in 7.0'); fragnesia + pack2theroot correctly report
PRECOND_FAIL (no userns / no D-Bus in container). Local macOS + Linux
builds both clean.
88 lines
4.2 KiB
Markdown
88 lines
4.2 KiB
Markdown
# 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. The **exploit body** has not
|
|
been validated end-to-end against a known-vulnerable kernel inside the
|
|
SKELETONKEY CI matrix.
|
|
|
|
**`detect()` is now version-pinned**: the Fragnesia fix ships in
|
|
mainline Linux **7.0.9** (Debian tracker source-of-truth, `linux
|
|
unstable: 7.0.9-1 fixed`). The `kernel_range` table marks the 7.0.x
|
|
branch patched at `7.0.9`; older Debian-stable branches (5.10 / 6.1 /
|
|
6.12) are currently still vulnerable per the tracker. With `--active`,
|
|
the detector runs the full ESP-in-TCP primitive against a `/tmp` file
|
|
and reports empirically — catches stable-branch backports the version
|
|
table doesn't know about, and CONFIG_INET_ESPINTCP=n kernels where the
|
|
primitive is structurally unreachable.
|
|
|
|
**Before promoting to 🟢:** validate the exploit end-to-end on a
|
|
≤ 7.0.8 kernel. Extend the `kernel_range` table with backport
|
|
thresholds for 5.10 / 6.1 / 6.12 as distros publish them.
|