DF-3118 / verdict.json
{ "finding_id": "DF-3118", "status": "inconclusive", "reproduced": 0, "impact": "dos", "confidence": "likely", "verdict": "Signed 32-bit flusher sequence ring (hmp->flusher.{done,next}, flg->seq, resv->flg_no) misorders all difference comparisons once the ring distance reaches 2^31; the allocating side is an unprivileged fire-and-forget HAMMERIOC_SYNCTID ioctl (hammer_ioctl.c:117 overwrites the caps_priv_check error). Mechanism proven on the guest: with next wrapped negative while done stayed positive (state injected by a root setup helper, identical to a successful 2^31 lap), hammer_flusher_flush():338-357 mis-sequences the pending flush group (difference wraps, KKASSERT passes, flg=NULL), the group is stuck forever, and the master runs a synchronous finalize per dummy sequence - the guest wedged past ssh recovery and needed a hard reset. Organic unprivileged reachability NOT demonstrated and refuted on this configuration: 12 unpriv burners allocated 914,588,507 sequences in 22 min with the master holding the gap at 4707, and with 4 heavy writers dirtying 11.3 GB the gap stayed <= 9k and drained to 2 within seconds of stopping. End-to-end unpriv trigger would require ~2000 s of cumulative master stalls (slow storage), which the test config does not allow.", "exploit_chain": "", "evidence": [ "wedge_boot.log: last serial lines show the injected state (done=914583800 next=-1233099849) after which the guest wedged (no further output, ssh banner timeouts, QEMU alive)", "disasm.txt: stock hammer.ko hammer_flusher_wait = sub;test;jle (wrapping signed 32-bit difference)", "gapseries.txt + run.log: organic gap measurements (4707 after 914.59M allocations; <=9k under heavy dirty load; drains to 2 in 2 s)", "fix_build.log: patched module compiles -Werror clean and emits unsigned ring codegen (cmp/je)", "VERDICT.md: full narrative with path:line citations" ], "kernel_refs": [ "sys/vfs/hammer/hammer_flusher.c:114", "sys/vfs/hammer/hammer_flusher.c:166", "sys/vfs/hammer/hammer_flusher.c:178", "sys/vfs/hammer/hammer_flusher.c:331", "sys/vfs/hammer/hammer_flusher.c:338", "sys/vfs/hammer/hammer_flusher.c:356", "sys/vfs/hammer/hammer_flusher.c:447", "sys/vfs/hammer/hammer.h:772", "sys/vfs/hammer/hammer.h:292", "sys/vfs/hammer/hammer.h:740", "sys/vfs/hammer/hammer_blockmap.c:730", "sys/vfs/hammer/hammer_ioctl.c:117" ], "poc_changes": "burn.c/seqshift.c/seqstat.c/writer.c written fresh for this pack (no seed existed): unpriv trigger, root setup injector, read-only gap probe, dirty-load writer.", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 4700, "guest_dirty": 1, "build_cmd": "cc -O2 -I/usr/src/sys -o burn burn.c (plus KLD modules via bsd.kmod.mk, see build.sh)", "run_cmd": "12x ./burn burn f ; ./burn group g ; [4x ./writer wN] ; mechanism run adds: kldload seqshift.ko (root setup)", "code_hash": "172bad5817a65a200bfdb70005788a38a97bb9c4c0a5553fc37153f374d745e2", "notes": "No kernel memory corruption involved (pure sequencing/DoS); no panic text because the wrapped difference evaluates positive so the KKASSERT at hammer_flusher.c:356 passes - the wedge is the stuck-group + per-dummy synchronous-finalize storm. Guest reset with-src performed after evidence collection.", "recommended_fix": "Make the flusher sequence counters and derived locals unsigned (done/next, flg->seq, resv->flg_no) so the ring differences are modular; see fix.diff.", "fix_status": "not_testable", "fix_kernel_uname": "", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff applies clean to sys/ and the hammer module compiles -Werror clean with it in the guests /usr/src copy; patched hammer_flusher_wait emits unsigned ring codegen (cmp/je). Full kernel A/B not run: there is no organic baseline reproduction to compare against (organic trigger refuted on this config), so fixed-kernel behaviour could not be meaningfully A/Bd.", "fix_evidence": "fix_build.log" } |