# VERDICT — DF-2704

**status: untested** (Phase V deliberately skipped: Low, speculative, root-gated
trigger; deterministic reproduction would need a root mountctl storm plus
scheduler manipulation).

## Analysis

Race protocol violation spanning three files; the *reader* is in the audited file:

1. Reader: `vop_cache_operate_ap()` sys/kern/vfs_vopops.c:1774-1786 —
   `ops->head.vv_mount->mnt_vn_journal_ops` loaded twice (:1781 test, :1782 call)
   with no token/refcount. (`vop_journal_operate_ap` :1793-1803 has the same shape
   but only reads `mnt_vn_norm_ops`, which is never freed while the mount lives.)
2. Swapper/free-er: `journal_attach`/`journal_detach` sys/kern/vfs_jops.c:233-252 →
   `vfs_add_vnodeops`/`vfs_rm_vnodeops` sys/kern/vfs_init.c:113-162 —
   `*ops_pp = NULL` then `kfree(ops, M_VNODEOP)`, no synchronization with
   in-flight VOP threads (vnodes reach the ops via `*(vp)->v_ops` which points at
   `&mp->mnt_vn_use_ops`, sys/kern/vfs_mount.c:209, re-read per call, so the
   *use* pointer swap itself is safe; it is the journal_ops pointer lifetime that
   is not).
3. Gate: `sys_mountctl` root-only (sys/kern/vfs_syscalls.c:1277-1283,
   SYSCAP_RESTRICTEDROOT + no-jail), so only root can start the free side.

Worst case is a function-pointer call through freed M_VNODEOP memory
(`VOCALL` = cast-of-offset indirect call, sys/sys/vnode.h:407) — i.e. a
kernel-controlled-but-not-attacker-shaped UAF dispatch, triggered by root action
racing user-triggerable VOP traffic.

## Honest classification

- Real protocol flaw: yes — read the three call sites; nothing serializes them.
- Exploitable by an unprivileged user alone: no — the free requires root
  mountctl (journal remove or failed install auto-detach).
- Deterministic PoC cost vs. severity class: disproportionate (needs root helper
  + schedule-injection). Per audit contract, Low speculative findings are not
  taken to the guest.

Recommended follow-up: file the serialization fix (mnt_token or ops refcount)
with upstream; if the team wants a live demo, a root loop of
`mountctl` install/remove journalling on a tmpfs while a user hammers
`write(2)` under `debug.cpumask` pinning is the cheapest stress shape.
