refluxfs: wire the --full-chain /etc/passwd root pop (🟡 trigger -> 🟢 full chain)
build / build (clang / debug) (push) Waiting to run
build / build (clang / default) (push) Waiting to run
build / build (gcc / debug) (push) Waiting to run
build / build (gcc / default) (push) Waiting to run
build / sanitizers (ASan + UBSan) (push) Waiting to run
build / clang-tidy (push) Waiting to run
build / drift-check (CISA KEV + Debian tracker) (push) Waiting to run
build / static-build (push) Waiting to run

Promotes refluxfs (CVE-2026-64600) to a 🟢 full-chain module. VM-verified
end-to-end on Rocky Linux 9.8 / 5.14.0-687.10.1.el9_8.0.1.x86_64 under
qemu/KVM, unprivileged uid=1000, SELinux Enforcing:

  skeletonkey --exploit refluxfs --i-know --full-chain

reflink-clones /etc/passwd, races the CoW window (32 writers / 8 helpers),
strips root's password field on-disk (root -> root::, the public PoC's
technique), evicts the stale page cache, verifies via O_DIRECT and returns
EXPLOIT_OK. `su root` (empty password) then gives uid 0. 3/3 wins on a
private-extent target (1244/3716/7913 rounds, 4-30s).

Safety properties (this bug rewrites the block device permanently, unlike
the page-cache 🟢 modules):
  - Crafts the payload FIRST and refuses unless it can preserve both root
    and the invoking user's line; every other passwd line is kept
    byte-for-byte. A tail-truncating port drops sshd/nobody/the caller and
    bricks login (hit exactly this during development).
  - Backs /etc/passwd up before the race; restores on failure; cleanup()
    restores it (run as root after the pop).
  - Destructive path gated behind --full-chain. Plain --exploit / --auto
    run only the safe own-files trigger (EXPLOIT_FAIL), unchanged.

Exploitability constraint discovered during verification (NOT in the Qualys
writeup): the race only fires when the target's extent is PRIVATE going in.
The block starts at refcount 2 (target + attacker clone), the concurrent CoW
drops it to 1, and the stale writer reads "1 -> private". A file already
reflink-shared with a third file keeps a post-CoW refcount > 1 and is NOT
attackable via that target. Stock Rocky 9 ships /etc/passwd pre-shared and
was unattackable across ~41,000 rounds; rewriting it to a private extent
(byte-identical content, as any useradd/passwd/vipw does) made it fall in
~2,000 rounds. So the exploitable state is the normal administered state.

Also fixed a page-cache staleness bug in the win path: the overwrite
bypasses the target inode, so its clean cached pages are never invalidated
and a buffered read (getpwnam in `su`, or the module's own verify) would see
the OLD passwd. The module now issues POSIX_FADV_DONTNEED on a win and
verifies via O_DIRECT.

detect() --active gains a per-target extent-privacy check: it reports
whether /etc/passwd is private (attackable) or already-shared (not).

88-test unit harness still green (148 total). Docs updated: MODULE.md
(full-chain flow, private-extent precondition, verification tables),
NOTICE.md, CVES.md (🟢 + tier/ops tables), README (15 full-chain / 13
primitive; lands-root list), RELEASE_NOTES, targets.yaml.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118iUgHY44hdRtANgyCmu7y
This commit is contained in:
KaraZajac
2026-07-23 18:45:32 -04:00
parent e46a32f11e
commit 5c18b678a5
8 changed files with 661 additions and 63 deletions
+17 -12
View File
@@ -72,18 +72,23 @@ SKELETONKEY is the bundling and bookkeeping layer only.
## SKELETONKEY role
🟡 **Trigger (reconstructed) — VM-verified, and deliberately not weaponised.**
Confirmed 2026-07-23 on **Rocky Linux 9.8 / `5.14.0-687.10.1.el9_8.0.1.x86_64`**
(stock GenericCloud layout, root on XFS with `reflink=1`) under qemu/KVM:
`detect()` returns `VULNERABLE`, the `--active` FICLONE witness confirms
reflink, and phase A observes `FIEMAP_EXTENT_SHARED` on a real shared extent.
The underlying bug was separately confirmed winnable on that kernel — **4/4
runs, first divergence after 69-494 rounds** in a 60 s budget — via a VM-only
harness at the public PoC's parameters (`tools/verify-vm/refluxfs_verify.c`).
The shipped trigger, deliberately under-driven, did **not** win in its 2 s
budget on that same vulnerable kernel; that is intended, and is why a non-win
must never be read as "patched". See `MODULE.md` for the full result table.
This is the corpus's first XFS
🟢 **Full chain (`--full-chain`), 🟡 safe trigger by default — VM-verified
end-to-end.** Confirmed 2026-07-23 on **Rocky Linux 9.8 /
`5.14.0-687.10.1.el9_8.0.1.x86_64`** (stock GenericCloud layout, root on XFS
with `reflink=1`) under qemu/KVM. `--exploit refluxfs --i-know --full-chain`
reflink-clones `/etc/passwd`, races the CoW window, strips root's password field
on-disk, evicts the stale page cache, and returns `EXPLOIT_OK`; `su root` (empty
password) then gives uid 0 — verified **3/3 wins** on a private-extent target
(1244 / 3716 / 7913 rounds, 430 s) as unprivileged `uid=1000` under SELinux
Enforcing, with every other passwd line preserved and the file backed up +
restorable. A key exploitability constraint surfaced in testing (not in the
public writeup): the target's extent must be **private** going in — an
already-reflink-shared file keeps a post-CoW refcount > 1 and is not attackable
via that target; normal admin churn (`useradd`/`passwd`/`vipw`) produces the
exploitable private-extent state. Without `--full-chain` the module runs a safe
own-files reachability trigger only (`EXPLOIT_FAIL`), deliberately under-driven
so a non-win is never read as "patched". See `MODULE.md` for the full result
tables. This is the corpus's first XFS
module and its first **data-oriented** kernel bug — every other kernel entry
corrupts memory; this one corrupts file contents.