# DF-3778 — VERDICT

**Status: source-level certain (loop provably unbounded on the interrupt
path; bound provably available and used elsewhere in the same function);
runtime honestly inconclusive — the trigger is a remote line-rate flood
against JMC250/JMC260 hardware the audit guest does not have.
reproduced=0, impact=none-on-this-guest; class ceiling on real hardware:
remote-triggerable NIC + single-CPU (legacy/MSI: whole-interface)
livelock DoS on default config. DF-3761 (alc) family, independent
if_jme.c instance.**

## Root cause (path:line)

1. `sys/dev/netif/jme/if_jme.c:2494-2497` — the RX walk's only work
   bound is the `count` parameter: `if (count >= 0 && count-- == 0)
   break;`.  With `count == -1` the loop exits only when the ring drains
   (OWN set, :2500) or a consistency break fires (:2502/:2513/:2551).
2. Interrupt-context call sites all pass -1:
   * `jme_rx_intr` — `sys/dev/netif/jme/if_jme.c:3500` (from
     `jme_intr` :2150; handler registered on the MAIN serializer
     `sc->jme_serialize`, :3912, asserted at :2118 — the whole interface
     is behind the walk; `jme_intr` masks all interrupts at :2125 and
     re-enables at :2174);
   * `jme_msix_rx` — `:3845` (handler registered on the ring serializer,
     :3672; masks `jme_rx_coal` at :3839, unmasks at :3847);
   * `jme_rx_restart` — `:3882` (from `jme_msix_status` :3866 /
     `jme_npoll_status` :3330; INTR_RXQ_DESC_EMPTY masked at entry).
3. Per-iteration cost: `jme_rxpkt` → `jme_newbuf`
   (`m_getcl` + `bus_dmamap_load_mbuf_segment`, :3061-3082) + `if_input`
   dispatch (:2474) — µs-scale, i.e. slower than 1.488 Mpps 64-byte
   line-rate arrival (672ns budget) on the 1GbE JMC250, so a sustained
   small-packet flood keeps the ring perpetually non-empty and the walk
   never terminates while the handler holds its serializer.
4. The bound is provably available and intended: the same function is
   called WITH a bound by the driver's own ifpoll path
   (`jme_npoll_rx`, `:3341`, passes `cycle`); DragonFly's msk(4) bounds
   its own walk; the alc(4) twin of this defect is filed as DF-3761;
   FreeBSD bounds these loops.  jme has no bound knob at all
   (`grep -c process_limit if_jme.c` == 0, run.log).

## Impact analysis (on hardware)

Remote, unauthenticated, default configuration (interface up): flood at
line rate →

* MSI-X RX vector: that ring's `jme_rx_serialize` is held forever → the
  IRQ thread spins at 100% on its CPU; every other entry point on the
  ring (ioctls, npoll, RX restart) blocks indefinitely;
* legacy/MSI interrupt: `sc->jme_serialize` (main) held → the whole
  interface (including `jme_stop`/`jme_init`, i.e. recovery via
  `ifconfig jme0 down`) blocks — the livelock cannot be administratively
  cleared without a reboot on legacy/MSI configurations;
* lower-priority kernel threads on the pinned CPU starve.

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

run.log (2026-09-16): presence probe — **no JMicron PCI device**
(`vtnet0 lo0` only); driver compiled into the running kernel
(`kldstat -v`: `102 jme/miibus`, `103 pci/if_jme`); guest-side grep of
/usr/src — the five `jme_rxeof(rdata, ...)` call sites with their
arguments (three × -1 interrupt-path, one × -1 drain-path, one ×
`cycle` bounded ifpoll) and zero `process_limit` references.  Runtime
reproduction requires a JMicron NIC under line-rate flood → hardware-
gated, inconclusive per family convention (DF-3761 precedent).

## Fix validation

* `fix.diff` (git-apply clean): introduces `jme_rx_proc_limit` (default
  64, `TUNABLE_INT("hw.jme.rx_proc_limit", ...)`) and passes it at the
  three interrupt-context call sites (:3500/:3845/:3882).
* Safety argument: each of those handlers masks its interrupt on entry
  and re-enables it at exit (jme_msix_rx :3839/:3847, jme_intr
  :2125/:2174, jme_msix_status :3859/:3869), so a bounded early exit
  with work remaining raises a fresh interrupt immediately — no frame
  loss, only the unbounded spin is removed.  The `jme_miibus_statchg`
  drain (:418) is deliberately left unbounded: it runs at link
  reconfiguration with all interrupts masked (:403) and must fully drain
  the ring before the indices are reset (:427).
* Compile-validated on the guest: all three jme fixes (DF-3776/3777/
  3778) applied together to /usr/src, incremental `nativekernel`
  X86_64_GENERIC build → RC=0, zero warnings (fix_build.log).
* Driver-level runtime validation impossible without JMicron hardware →
  fix_status: not_testable.

## Bottom line

jme(4)'s interrupt-path RX walk is unbounded (`count=-1` at all three
interrupt call sites) in a driver whose ifpoll path proves the bound
exists; a remote line-rate small-packet flood livelocks the handler
holding the ring serializer (legacy/MSI: the main serializer — the whole
interface, un-recoverable by ifconfig).  The DF-3761/alc family defect,
independently instantiated in if_jme.c; fix is a 64-frame budget knob at
three call sites, compile-clean.

## 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.
