6960d2076d247003c5fa0862c1f8c60afdfb4769
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c55adc1840 |
refluxfs: drop the redundant standalone verify harness
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
The own-files reachability harness tools/verify-vm/refluxfs_verify.c is superseded by the module itself: the safe default --exploit does the same own-files reachability confirmation, and --full-chain provides the stronger end-to-end proof (actual root). Remove the standalone and repoint the three doc references (MODULE.md, RELEASE_NOTES.md, targets.yaml) at the module's full-chain verification. The 4/4 own-files measurement it produced is kept as a historical data point, just without the now-deleted reproducer. No code depended on it; 148-test unit harness still green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0118iUgHY44hdRtANgyCmu7y |
||
|
|
5c18b678a5 |
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 |
||
|
|
e46a32f11e |
modules: add refluxfs (CVE-2026-64600, "RefluXFS" XFS reflink CoW ILOCK race)
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
Adds the corpus's first XFS module and its first data-oriented kernel bug —
every other kernel entry corrupts memory; this one corrupts file contents.
xfs_direct_write_iomap_begin() reads the data-fork extent map under ILOCK,
then xfs_reflink_fill_cow_hole() drops ILOCK to wait for transaction log
space. On reacquiring it, the code re-queries the refcount btree at the
ORIGINAL imap->br_startblock and never re-reads the data fork. A second
O_DIRECT writer holding only IOLOCK completes a whole CoW cycle in that
window, so the first writer's stale mapping sees refcount 1, treats a
still-shared block as private, and writes to it in place — landing its data
on the reflink source file's on-disk blocks.
The primitive is an arbitrary overwrite of the on-disk contents of any
readable file, which has three consequences that drive the design:
- No offsets, no ROP, no KASLR/SMEP/SMAP; SELinux, containers and seccomp
are all irrelevant.
- The victim's inode is never written, so mtime/ctime/size never change
and nothing is logged — FIM and `-w /etc/passwd -p wa` cannot see it.
- The change persists across reboots.
Introduced 4.11 (3c68d44a2b49); fixed 2f4acd0fcd86 (mainline 7.2-rc4,
merged 2026-07-16), stable backports 7.1.4 / 6.18.39 / 6.12.96. Exposure is
distro-shaped: RHEL/CentOS/Rocky/Alma/Oracle/CloudLinux 8-10, Fedora Server
>= 31 and Amazon Linux 2023 ship XFS+reflink by default.
detect() is not a pure version gate — reachability here is safely
observable, so it pairs the backport table with a real storage precondition
(writable XFS via statfs XFS_SUPER_MAGIC, deliberately not via a successful
FICLONE since btrfs implements that too and is unaffected). --active
confirms reflink via FICLONE; SKELETONKEY_XFS_ASSUME_REFLINK=1/0 overrides.
On rpm-family hosts it warns that vendors backport without bumping the
upstream version, so the verdict speaks only to the upstream base.
exploit() forks a child that works only in a private mkdtemp scratch dir on
two files it owns: it establishes a shared extent (FICLONE, corroborated by
FIEMAP_EXTENT_SHARED) plus an O_DIRECT gate, then races a hard-bounded
8 writers / 2 helpers / 16 rounds / 2s and stops, reading the donor back
with O_DIRECT. Deliberately under-driven, and it never clones or targets a
file it does not own — the /etc/passwd overwrite -> su -> root step is
documented but NOT bundled. Always returns EXPLOIT_FAIL.
Safety rank 55, far above bad_epoll (12) and ghostlock (11): a won race
corrupts 4 KiB of our own scratch file and cannot touch kernel memory, so
there is no oops/KASAN/panic path.
Detection inverts the usual advice. auditd/sigma anchor on ioctl request
0x40049409 (FICLONE, matched exactly) and openat O_DIRECT; falco adds the
cross-uid reflink condition; and the yara rule is genuinely the right tool
here, matching the on-disk artifact because FIM is structurally blind.
VM-VERIFIED 2026-07-23 — the corpus's first rpm-family verification, taking
the empirical count to 29 of 41 CVEs. Rocky Linux 9.8 /
5.14.0-687.10.1.el9_8.0.1.x86_64 under qemu/KVM, stock GenericCloud layout
with no provisioner changes (root is XFS with reflink=1 out of the box).
detect() -> VULNERABLE, --active FICLONE witness confirmed reflink, phase A
observed FIEMAP_EXTENT_SHARED on a real shared extent, scratch self-cleaned,
clean build on el9 gcc. The underlying bug was separately confirmed winnable
on that kernel via tools/verify-vm/refluxfs_verify.c at the public PoC's
parameters (32 writers / 8 helpers, 60s): 4/4 runs won, first divergence
after 69/114/170/494 rounds. The shipped under-driven trigger did NOT win in
its 2s budget on that same vulnerable kernel — intended behaviour, and
exactly why a non-win must never be read as "patched".
14 new detect() unit rows (148 tests total, 0 failures). Bumps to v0.9.14.
Credit: Qualys Threat Research Unit (blog by Saeed Abbasi; the technical
advisory credits model-assisted kernel analysis performed with Anthropic),
and the upstream XFS maintainers who fixed it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118iUgHY44hdRtANgyCmu7y
|