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
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:
@@ -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, 4–30 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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user