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:
+21
-1
@@ -43,7 +43,27 @@ merged **2026-07-16** for **7.2-rc4**; stable backports **7.1.4** / **6.18.39**
|
||||
upstream stable fix in the CNA record at time of writing. CWE-362 → CWE-367; NVD
|
||||
published neither a CWE nor a CVSS vector at time of writing; not in CISA KEV.
|
||||
|
||||
🟡 **Trigger (reconstructed) — deliberately under-driven, and VM-VERIFIED.**
|
||||
🟢 **Full chain (`--full-chain`), 🟡 safe trigger by default — VM-verified
|
||||
end-to-end.** `--exploit refluxfs --i-know --full-chain` reflink-clones
|
||||
`/etc/passwd`, races the CoW window, strips root's password field on-disk
|
||||
(`root:x:` → `root::`, the public PoC's technique), evicts the stale page cache,
|
||||
and returns `EXPLOIT_OK`; `su root` with an empty password then yields uid 0.
|
||||
Confirmed on Rocky Linux 9.8 / `5.14.0-687.10.1.el9_8.0.1` — **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,
|
||||
the file backed up first and restored on failure (`--cleanup` restores after the
|
||||
pop). A naive port that truncates the tail would drop `sshd`/the caller and brick
|
||||
login; preserving every line is the implementation's key safety property.
|
||||
|
||||
**Exploitability constraint discovered during verification (not in the Qualys
|
||||
writeup):** the race only fires when the target's extent is **private** going in.
|
||||
An already-reflink-shared file keeps a post-CoW refcount > 1 and is not
|
||||
attackable via that target — some fresh cloud images ship `/etc/passwd`
|
||||
pre-shared (Rocky 9's did, and the attack failed against it across ~41 000
|
||||
rounds), while normal admin churn (`useradd`/`passwd`/`vipw`) rewrites it into
|
||||
the exploitable private-extent state. `detect() --active` now reports which state
|
||||
the target is in. Without `--full-chain` the module runs a safe own-files
|
||||
reachability trigger only (`EXPLOIT_FAIL`), deliberately under-driven.
|
||||
Unlike the corpus's other race
|
||||
modules, `detect()` is **not** a pure version gate: this bug's reachability is
|
||||
safely observable, so it pairs the three-branch version table with a **real
|
||||
|
||||
Reference in New Issue
Block a user