DragonFlyBSD Kernel Audit
DF-2747 / verdict.json
← back to finding ↓ download raw
{
 "finding_id": "DF-2747",
 "status": "reproduced",
 "reproduced": 1,
 "impact": "panic",
 "confidence": "certain",
 "verdict": "The journal memfifo reservation protocol (journal_reserve/extend/abort/commit plus the worker-thread index updates) performs unsynchronized RMW on jo->fifo.{w,x,r}index and jo->transid, and journal VOP shims run concurrently on SMP (vop_write_ap is a bare DO_OPS), so unprivileged concurrent VOPs on a root-journaled mount overlap reservations and corrupt the raw record chain. Reproduced twice from an unprivileged user on the stock INVARIANTS guest: KKASSERT 'bytes >= 0 && bytes <= rawp->recsize - 24' failed in journal_commit (vfs_journal.c:741) via journal_extend<-jrecord_data<-jrecord_leaf_uio<-journal_write (CPU1) and via jrecord_write_path<-jrecord_write_vnode_ref (CPU2 of run 2), both within 90 s. On release kernels the same overlap yields a desynced record chain that journal_wthread parses: attacker-poisoned payload headers (begmagic 0x1234) drive res past the physical buffer end (fp_write OOB heap read to the journal fd) or desync rindex/livelock the worker. Not escalated to uid=0: corruption is largely fifo-contained; demonstrated hard impact is the reproducible unprivileged panic. Fix (global spinlock serializing the protocol, plus DF-2748 gating and DF-2749 accounting) built as kernel #1 and validated: 4/4 identical hammer runs completed cleanly with a perfect 29,880-record stream chain (0 monotonic violations, 0 duplicates) vs 2/2 panics on stock.",
 "exploit_chain": "root: mountctl -a -w <file> -o memfifo=64k <mp>:race (one-time) -> unpriv user: 12 concurrent processes doing open/write/close+mkdir/rmdir on the mount -> two CPUs interleave journal_reserve's unlocked windex/transid RMW -> overlapping raw records, corrupted recsize chain -> journal_wthread walks attacker-poisoned payload as headers (forged begmagic 0x1234 + recsize 0x7ffffff0 in file content) -> INVARIANTS: KKASSERT panic (demonstrated 2/2); release: res>avail fp_write reads past the 64KB fifo into kernel heap and streams it to the journal fd (info disclosure to the mirror target), or rindex desync/livelock. uid=0 not achieved.",
 "evidence": [
  "panic.txt: two independent panic captures, journal_commit:741 assertion via journal_extend on CPU1 and CPU0",
  "run.log: baseline 2/2 panics (<90s, unpriv user); patched kernel 4/4 HAMMER DONE + parse 29880 records chain_break=no violations=0",
  "df2747.c: hammer with forged journal_rawrecbeg payload (0x1234/0x7ffffff0)",
  "parse_stream.py + out/j1_fixed.bin (pack run.log): clean chain on patched kernel",
  "fix.diff: validated global-spinlock serialization",
  "build.log: make nativekernel BUILD_RC=0 with the patch (-Werror clean)"
 ],
 "kernel_refs": [
  "sys/kern/vfs_journal.c:527-585 (journal_reserve unlocked windex/transid RMW)",
  "sys/kern/vfs_journal.c:625-652 (journal_extend fast paths RMW windex+recsize)",
  "sys/kern/vfs_journal.c:685-696 (journal_abort windex rewind)",
  "sys/kern/vfs_journal.c:742-756 (journal_commit backindex + dead-space pad)",
  "sys/kern/vfs_journal.c:741 (panicked assertion)",
  "sys/kern/vfs_journal.c:236-243 (journal_wthread chain walk sink)",
  "sys/kern/vfs_journal.c:220,261,279,383,397 (worker rindex/xindex RMW)",
  "sys/kern/vfs_vopops.c:1869 (vop_write_ap bare DO_OPS - no serialization)",
  "sys/kern/vfs_jops.c:545-556 (adjacent: mnt_jbitmap/mnt_streamid RMW, same class)"
 ],
 "poc_changes": "Orchestrator gave no seed for this pass-2 finding; PoC written fresh. Guest quirks handled: mountctl CLI wants 'mountpt:tag' order and memfifo=64k suffix; scratch dir must be chmod 777 for the unprivileged user; guest lacks base64/mdconfig/procstat (heredoc/scp transfer, ps -o wchan for sleep-channel evidence).",
 "attempts": 6,
 "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": 4800,
 "guest_dirty": 0,
 "build_cmd": "cc -O -o df2747 df2747.c   [fix kernel: cd /usr/src && make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel KERNCONF=X86_64_GENERIC]",
 "run_cmd": "mountctl -a -w /root/j1.bin -o memfifo=64k /tmp:race ; ./df2747 12 400 /tmp/jstress   (as unprivileged user)",
 "code_hash": "bd00e051454c94afb61362cddfc0fd835b2daeb687b58860b1419d0a5de2db07",
 "notes": "Impact panic on INVARIANTS (demonstrated); release-kernel primitives (fp_write OOB read into journal fd, rindex desync, livelock) traced to code, not demonstrated end-to-end. mnt_jbitmap stream-id race (vfs_jops.c:545) noted for upstream: needs the same serialization.",
 "recommended_fix": "Serialize the memfifo protocol: per-journal (or global interim) spinlock around journal_reserve/extend/abort/commit critical sections and worker index updates; see validated fix.diff.",
 "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 (fix.diff applied to /usr/src/sys/kern/vfs_journal.c, nativekernel BUILD_RC=0, installkernel, reboot): the exact baseline trigger (12x400 unpriv hammer, x4 runs) completed 4/4 with no panic and a fully valid stream chain (29,880 records, chain_break=no, transid_monotonic_violations=0, duplicate_transids=0). Baseline stock kernel panicked 2/2.",
 "fix_evidence": "run.log (baseline panics + patched RUN1..RUN4_OK); parse output in run.log; build.log BUILD_RC=0"
}