{
 "finding_id": "DF-2748",
 "status": "reproduced",
 "reproduced": 1,
 "impact": "dos",
 "confidence": "certain",
 "verdict": "With a full-duplex journal installed (root, mountctl -2), journal_rthread exits silently on the first fp_read EOF/error/short-read (vfs_journal.c:330-336) or bad ack magic (:341-346), after which nothing ever advances fifo.xindex; journal_reserve's stall tsleep(&jo->fifo.windex,0,\"jwrite\",0) (:543-548) is uninterruptible, unbounded, has no STOP_REQ escape (STOP_IMM is unimplemented), so every journaled VOP wedges in an unkillable D-state once the memfifo fills with un-acked bytes. Demonstrated deterministically on the stock guest: rthread gone at install; both writer children D4/D5 wchan=jwrite from t+5s through t+60s, SIGKILL at t+15s ineffective; membufused/membufunacked pinned at 63K (frozen xindex). mountctl -d then SUCCEEDED, freeing struct journal and the fifo while the sleepers persisted inside freed memory; installing a new same-size journal and generating load (15 stalls) woke the zombies through the recycled wchan address - they executed journal_reserve on freed->reused state and only then took the pending SIGKILL. Wake-through-reuse demonstrated; the arbitrary-object UAF write under a hostile heap layout is traced but not demonstrated. Fix (wthread gates full-duplex behavior on a live MC_JOURNAL_RACTIVE, degrading to half-duplex so xindex keeps advancing) validated on patched kernel #1: same PoC never wedges (children ran past file #24 indefinitely), killable, fifo drained (membufused=0), clean delete.",
 "exploit_chain": "root: mountctl -a -2 -w <plain-file> -o memfifo=64k <mp>:wedge -> rthread reads EOF, exits (xindex frozen forever) -> unpriv writers fill 64K un-acked -> permanent uninterruptible 'jwrite' sleep (unkillable, holds vnode locks; every user of the mount affected) -> root 'mountctl -d' frees jo+membase under the sleepers -> heap reuse at the same addresses (e.g. new journal, same size classes) -> new journal's wakeup(&jo->fifo.windex) wakes the zombie -> journal_reserve/commit resume on freed/reallocated state; rawp = membase+(windex&mask) writes attacker-payload records into whatever now occupies the old addresses (UAF write; demonstrated only in the benign same-journal-reuse layout).",
 "evidence": [
  "run.log: 60 s transcript, children 1009 D4 jwrite / 1010 D5 jwrite, SIGKILL ineffective t+15s..t+60s",
  "run2.log: membufused=63K membufunacked=63K stalls=2; mountctl -d completes (memory freed under sleepers); new journal + 15 stalls wake the zombies; ps empty afterwards",
  "run3.log: patched kernel - no wedge at all, killable, fifo drains",
  "fix.diff: DF-2748 gating hunks (MC_JOURNAL_RACTIVE), validated"
 ],
 "kernel_refs": [
  "sys/kern/vfs_journal.c:543-548 (uninterruptible stall sleep, no STOP_REQ escape)",
  "sys/kern/vfs_journal.c:330-336 (rthread exits on EOF/error/short read)",
  "sys/kern/vfs_journal.c:341-346 (rthread exits on bad ack magic)",
  "sys/kern/vfs_journal.c:383,397 (only xindex advancers, rthread-only in fullduplex)",
  "sys/kern/vfs_journal.c:204-207 (wthread jpad loop ignores MC_JOURNAL_STOP_REQ)",
  "sys/kern/vfs_journal.c:544 (MC_JOURNAL_STOP_IMM unimplemented)",
  "sys/kern/vfs_journal.c:140-158 (journal_destroy_threads jwait loop)",
  "sys/kern/vfs_jops.c:433-452 (journal_destroy frees jo/fifo without draining reservers)"
 ],
 "poc_changes": "No seed; written fresh. Guest notes: -2 for two-way, memfifo=64k, mountpt:tag argument order; plain-file target makes rthread death deterministic (shared-offset fp_read returns our own journal bytes or EOF -> break).",
 "attempts": 4,
 "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0 X86_64_GENERIC x86_64 (baseline) / #1 Mon Aug 31 16:22:33 (fix kernel)",
 "runtime_sec": 3600,
 "guest_dirty": 0,
 "build_cmd": "cc -O -o df2748 df2748.c   [fix validated in DF-2747's kernel build]",
 "run_cmd": "mountctl -a -2 -w /root/j2.bin -o memfifo=64k /tmp:wedge ; ./df2748 /tmp/jstress",
 "code_hash": "803d6fa41dd95e7d7fd224411bb80270ec463c98820bc53a07b0092b7fedcf9d",
 "notes": "Severity High: unkillable-process wedge of arbitrary length for unprivileged users plus demonstrated post-free execution through stale journal pointers (memcorrupt bucket). The journal wire protocol is unauthenticated (magic constants), so a dead/lying remote mirror triggers the same rthread exit over the network.",
 "recommended_fix": "Gate full-duplex behavior on a live rthread (MC_JOURNAL_RACTIVE) so the wthread takes over xindex advancement when the ack path dies (validated in fix.diff); upstream should additionally drain/abort outstanding reservers in journal_destroy before kfree.",
 "fix_status": "fixed",
 "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Mon Aug 31 16:22:33 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
 "fix_baseline_reproduced": 1,
 "fix_patched_reproduced": 0,
 "fix_verdict": "Patched kernel #1: exact same PoC never wedges - children ran continuously past the baseline wedge point (file #11) for the entire window, no D-state leftovers, SIGKILL effective, fifo fully drained (membufused=0/membufunacked=0), journal deleted cleanly. Stock baseline: unkillable D jwrite wedge >= 45 s + free-under-sleeper + wake-through-reuse.",
 "fix_evidence": "run3.log (patched transcript + status counters) vs run.log/run2.log (baseline)"
}