# DF-2763 — VERDICT

**Status: reproduced (3/3 baseline runs), fix validated on guest.**

## Baseline (stock INVARIANTS kernel, DragonFly 6.5-DEVELOPMENT #0, Thu Jul 2 06:02:54 UTC 2026)

Three independent runs panicked with the identical signature:

    panic: assertion "mp->mnt_jbitmap != NULL" failed in journal_detach at vfs_jops.c:248
    journal_detach() <- journal_mountctl() <- vop_mountctl() <- kern_mountctl() <- sys_mountctl()

- RUN 1 (two churn loops overlapping + 3 unprivileged writers, <=150 s)
- RUN 3 (deliberate: two churn loops + 3 unprivileged writers, <=110 s)
- RUN 4 (MINIMAL: two concurrent root mountctl install/remove churn
  loops on /tmp, NO other VOP traffic, <=90 s) — proves the race is
  purely the journal_mountctl attach/detach state machine, not VOP load.

Mechanism (path:line):
- journal_mountctl() decides attach-vs-journaled on a lock-free read of
  mnt_vn_journal_ops (sys/kern/vfs_jops.c:162/:185).
- journal_attach() :235-243 allocates mnt_jbitmap + installs ops.
- journal_detach() :245-253 frees both; the decision to call it is a
  TAILQ_EMPTY(&mp->mnt_jlist) check that can run long after the journal
  list state changed (:172-173/:208-209).
- Interleave reproduced: thread A's MOUNTCTL_REMOVE is inside
  journal_destroy (its final jrecord + journal_destroy_threads sleep),
  thread B runs a full -a/-d pair, drains the list, detaches (frees
  bitmap, NULLs it); A wakes, sees an empty list, detaches again ->
  KKASSERT fires. On release kernels the both-see-non-NULL interleaving
  is a double kfree() of the 1024-byte M_JOURNAL bitmap block; the
  detach-vs-in-flight-jreclist variants give UAF bit RMW at
  jreclist_init :547/:556 and jreclist_done :613.

## Fix iterations
- v1 (token around journal_mountctl only): FAILED — the panic persisted
  at the shifted :257 because lwkt tokens are dropped across tsleep,
  and journal_destroy's destroy_threads tsleeps; the detach decision
  still straddled a sleep. Honest failure kept: fixrun_v1_console.log.
- v2 (validated): (a) token pair around journal_mountctl; (b)
  journal_detach made idempotent (NULL-guard return instead of
  KKASSERT); (c) token around jreclist_init's streamid bitmap RMW
  (:545-558 region, released around the exhaustion tsleep); (d) token +
  NULL-guard around jreclist_done's bit clear. Plus the DF-2765 and
  DF-2766 hunks (same file; independently validated).

## Fix validation (kernel #2, DragonFly 6.5-DEVELOPMENT #1..#2 Mon Aug 31 2026, make nativekernel rc=0)
- Exact PoC config re-run on the patched kernel: both churn loops
  completed (85 + 85 cycles = 170 install/remove pairs), no panic,
  guest stayed up, vmstat -m journal in-use returned to 0.
- Baseline: panic <=90 s, 3/3.

## Impact classification
Root-gated trigger (mountctl is SYSCAP_RESTRICTEDROOT), so uid0
escalation is not the threat model; impact = kernel panic on INVARIANTS
/ double-free or 1-byte UAF bit write on release kernels (memcorrupt
bucket). Severity Low.
