# DF-3776 — VERDICT

**Status: mechanism PROVEN on the guest kernel by deterministic replica of
if_jme.c's exact callout/serializer protocol; jme-specific runtime
hardware-gated → inconclusive per family convention (DF-3715/DF-3738/DF-3760
precedent), reproduced=0, impact=none-on-this-guest. Class of impact when it
fires on JMicron JMC250/JMC260 hardware: permanent kernel deadlock (DoS)
triggered by a privileged local user (ifconfig down/up/MTU/media, detach,
suspend) and opportunistically by the watchdog path; collateral: the CPU0
softclock thread stops delivering ALL callouts queued on CPU0.**

## Root cause (path:line)

1. `sys/dev/netif/jme/if_jme.c:772` — `callout_init_mp(&sc->jme_tick_ch)`;
   armed at `:2881` / `:483` with `callout_reset_bycpu(..., hz, jme_tick,
   sc, JME_TICK_CPUID)` (CPU0-pinned, if_jme.c:77 "DO NOT CHANGE THIS").
2. `sys/dev/netif/jme/if_jme.c:2571` — `jme_tick()` does a **blocking**
   `lwkt_serialize_enter(&sc->jme_serialize)` (the first/`main` serializer
   of `sc->jme_serialize_arr[]`, if_jme.c:733); `:2579` — self re-arm
   `callout_reset(&sc->jme_tick_ch, hz, jme_tick, sc)` inside the
   serialized section. The `jme_in_tick` / `jme_serialize_skipmain` dance
   (if_jme.c:362-363/:3971-3982) only manages the *additional* serializers
   inside `jme_miibus_statchg`; the initial blocking enter at `:2571` is
   the deadlock site.
3. `sys/dev/netif/jme/if_jme.c:2907` — `jme_stop()` calls
   `callout_stop(&sc->jme_tick_ch)` **while holding ALL driver
   serializers** (`ASSERT_IFNET_SERIALIZED_ALL` at `:2898`; entered via
   `ifnet_serialize_all` from `jme_detach` `:1092-1093`, `jme_suspend`
   `:1589-1590`, and every `jme_ioctl` path — SIOCSIFFLAGS down `:1954`,
   SIOCSIFMTU→`jme_init` `:1939`→`jme_stop` `:2672`, SIOCSIFMEDIA→
   `jme_miibus_statchg`'s own `callout_stop` at `:409`, watchdog
   `:1885`/`:1900`).
4. `sys/kern/kern_timeout.c:1091` — `callout_stop()` is the synchronous
   variant on this kernel (`_callout_cancel_or_stop(..., sync=1)`); with
   the callback INPROG the caller `ssleep(c, &c->spin, 0, "costp", 0)`
   (`sys/kern/kern_timeout.c:920`) **until the callback completes**.
   Callouts execute on the per-CPU `softclock N` lwkt thread
   (`kern_timeout.c:423`), so the blocking `lwkt_serialize_enter` in the
   callback legitimately sleeps — that is what parks it.
5. ⇒ A tick callback dequeued by the CPU0 softclock thread and parked in
   `lwkt_serialize_enter` (if_jme.c:2571) on the serializer held by the
   `jme_stop()` caller deadlocks against the stopper (AB-BA): both threads
   wedge permanently. Collateral: the interface's serializers are held
   forever (all later driver entry points block — ioctls, RX/TX interrupts,
   tick) and the wedged CPU0 softclock thread stops delivering every
   callout queued on CPU0 (other drivers' timers, protocol timers,
   taskqueue timeouts).

Race window: the tick must be INPROG at the moment the stopper is between
its serializer acquire and `callout_stop()`. For `jme_stop` the direct
window is small (flag updates at `:2903-2905`), but it is widened without
bound whenever the stopper's `ifnet_serialize_all` blocks acquiring the
ring serializers *after* taking main — e.g. behind a busy RX interrupt
handler performing the unbounded `jme_rxeof` walk (DF-3778) — while the
1 Hz tick fires and parks. The `jme_miibus_statchg` path (`:409`) has a
wider intrinsic window: it is reached from `mii_mediachg` after PHY
register DELAY loops. Every trigger (`ifconfig jme0 down|mtu|media`,
detach, suspend, watchdog reinit) is repeatable by root, so eventual
occurrence on long-lived boxes is plausible (same analysis as
DF-3760/alc).

This is the if_jme.c instance of the class proven for if_em.c (DF-3715),
if_emx.c (DF-3738) and if_alc.c (DF-3760); identical primitives,
independent file, not previously filed for jme.

## Not a re-arm UAF

DF-3715's replica (same kernel) proved the negative control: after the
callback returns, `callout_stop`'s STOP post-processing *unloads* a racing
re-arm (`_callout_update_spinlocked`). No post-stop re-arm UAF — the
defect is purely the sync-wait deadlock. Re-verified for the jme/fix
shapes by this pack's `stop_try` control (`pending=0` after stop).

## Verification performed (guest, DragonFly 6.5-DEVELOPMENT x86_64)

Presence probe (run.log): **no JMicron PCI device** (`vtnet0 lo0` only) —
driver present in the running kernel (`kldstat -v`: `102 jme/miibus`,
`103 pci/if_jme`) but attach never runs without hardware → runtime
hardware-gated, honest inconclusive per family convention.

Mechanism, proven by `jme_callout_replica.ko` (jme_callout_replica.c —
replica of jme's exact protocol: MP callout pinned to CPU0, callback =
blocking enter of `ser_main` + `jme_serialize_skipmain` array dance
(array_enter/array_exit from index 1, if_jme.c:3971-3982) + self re-arm
at hz; stopper = enter of the full serializer array (all, index 0) +
`callout_stop`):

1. `sysctl kern.jmereplica.arm=1` — arms shipped-shape and fix-shape
   replicas (dmesg: "armed (hz=100)").
2. `sysctl kern.jmereplica.stop_try=1` — fix-shape control (DF-3776
   fix.diff protocol: `lwkt_serialize_try` + 1-tick retry):
   `callout_stop returned, pending=0`, no deadlock (run.log).
3. `sysctl kern.jmereplica.stop_blk=1` — shipped shape: the sysctl
   thread takes ALL serializers, waits until the tick handler is BLOCKED
   on the main serializer (`fired=16 ran=15`), then calls
   `callout_stop()`:
   * last dmesg line = "… THE if_jme.c AB-BA DEADLOCK IS PROVEN" —
     `callout_stop()` **never returned**;
   * `ps -o pid,stat,wchan,command -ax` shows the caller
     `1514 D5 costp  sysctl kern.jmereplica.stop_blk=1` (run.log) —
     D-state, wchan `costp` == sys/kern/kern_timeout.c:920;
   * CPU0 softclock thread wedged executing the parked callback
     (run.log).
   Guest force-killed afterwards; `vm.sh reset with-src` restored a clean
   guest (verified: no /root/kmod, no kern.jmereplica sysctl).

## Fix validation

* `fix.diff` (git-apply clean against sys/) switches `jme_tick` to
  `lwkt_serialize_try` + 1-tick retry (pinned via `callout_reset_bycpu`
  to `JME_TICK_CPUID`), mirroring the DF-3715/DF-3738/DF-3760 family fix.
* Mechanism validated live: the replica's fix-shape control (identical
  try-enter + retry protocol) shows `callout_stop` under ALL serializers
  returns with `pending=0`, no deadlock (run.log).
* Compile-validated on the guest: all three jme fix.diff files
  (DF-3776/DF-3777/DF-3778) applied together to /usr/src, incremental
  `nativekernel` build of X86_64_GENERIC → RC=0, zero warnings/errors
  (fix_build.log, 2026-09-16 04:41 UTC).
* Full driver-level validation is not possible on a guest without
  JMicron hardware → fix_status: not_testable (family convention).

## Bottom line

The if_jme.c instance of the proven em/emx/alc AB-BA: `jme_tick`'s
blocking serializer acquire under a self-re-arming callout vs
`callout_stop()` under `ifnet_serialize_all` in `jme_stop`/statchg.
On hardware: permanent kernel deadlock + CPU0 callout blackout from a
root-repeatable ioctl (and opportunistically the watchdog path).
Fix is one hunk (try-enter + retry), compile-clean, mechanism-proven.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
