DF-3778 / verdict.json
{ "finding_id": "DF-3778", "status": "inconclusive", "reproduced": 0, "impact": "none", "confidence": "certain", "verdict": "Source-level certain, runtime honestly inconclusive (hardware-gated). jme_rxeofs RX walk (sys/dev/netif/jme/if_jme.c:2494-2562) has exactly one work bound - the count parameter (:2496) - and every interrupt-context call site passes -1: jme_rx_intr (:3500, from jme_intr :2150, registered on the MAIN serializer :3912), jme_msix_rx (:3845, ring serializer :3672), jme_rx_restart (:3882). Each iteration is us-scale (jme_newbuf m_getcl + bus_dmamap_load_mbuf_segment :3061-3082, if_input :2474) vs a 672ns 64-byte 1GbE line-rate inter-arrival budget, so a sustained remote small-packet flood keeps the ring perpetually non-empty and the interrupt handler never returns while holding its serializer: IRQ-thread CPU spin + ring (MSI-X) or whole-interface (legacy/MSI, incl. jme_stop/jme_init recovery) livelock. The bound provably exists and is used by the drivers own ifpoll path (jme_npoll_rx :3341 passes cycle); the driver has no bound knob at all (process_limit: 0 hits, run.log). This is the DF-3761 (alc) family defect independently instantiated in if_jme.c; DragonFly msk(4) bounds its own walk and FreeBSD bounds these loops. Guest has no JMicron NIC (run.log presence probe; if_jme compiled into the running kernel) -> inconclusive per family convention, impact none-on-this-guest.", "exploit_chain": "", "evidence": "run.log: guest-side presence probe (vtnet0 lo0 only, NO_JMICRON_PCI, kldstat -v shows 102 jme/miibus + 103 pci/if_jme) plus the grep of /usr/src/sys/dev/netif/jme/if_jme.c listing all five jme_rxeof(rdata,...) call sites with their arguments (:418 -1 drain, :3341 cycle BOUNDED, :3500 -1, :3845 -1, :3882 -1) and process_limit count 0; trigger.c: fully annotated source excerpt with the per-iteration cost and mask/unmask-restart safety argument; fix_build.log: nativekernel RC=0 with fixes applied.", "kernel_refs": [ "sys/dev/netif/jme/if_jme.c:2494", "sys/dev/netif/jme/if_jme.c:2496", "sys/dev/netif/jme/if_jme.c:3341", "sys/dev/netif/jme/if_jme.c:3500", "sys/dev/netif/jme/if_jme.c:3845", "sys/dev/netif/jme/if_jme.c:3882", "sys/dev/netif/jme/if_jme.c:2150", "sys/dev/netif/jme/if_jme.c:3912", "sys/dev/netif/jme/if_jme.c:3672", "sys/dev/netif/jme/if_jme.c:3061", "sys/dev/netif/jme/if_jme.c:2474" ], "poc_changes": "n/a (source-level finding, hardware-gated runtime; trigger.c is an annotated source-excerpt analysis, following the DF-3761 pack convention)", "attempts": 1, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 30, "guest_dirty": 0, "build_cmd": "n/a (analysis-only trigger; fix compile-validated via nativekernel, see fix_build.log)", "run_cmd": "vm.sh run_root ifconfig -l; pciconf -l | grep -i -E \"jmicron|JMC\" || echo NO_JMICRON_PCI; kldstat -v | grep -E \"jme/miibus|pci/if_jme\"; grep -n \"jme_rxeof(rdata\" /usr/src/sys/dev/netif/jme/if_jme.c; grep -cn process_limit /usr/src/sys/dev/netif/jme/if_jme.c", "code_hash": "2d9e0457db4aa34a5076d9a559d4818aa300aa1755e89557609f15d1fdfc968f", "notes": "No JMicron hardware on the guest. DF-3761 family precedent: source-level certain + hardware-gated runtime => inconclusive. fix_status: not_testable (behavior hardware-gated); fix compile validated (fix_build.log RC=0, 0 warnings, all three jme fixes applied together).", "recommended_fix": "Bound the three interrupt-context jme_rxeof call sites with a tunable rx work budget (hw.jme.rx_proc_limit, default 64), mirroring the ifpoll paths existing count bound (see fix.diff).", "fix_status": "not_testable", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff (git-apply clean) adds a jme_rx_proc_limit work budget (default 64, tunable) at the three interrupt-context call sites (jme_rx_intr :3500, jme_msix_rx :3845, jme_rx_restart :3882), mirroring the bound the drivers own ifpoll path already applies (:3341). Safe: each of those handlers masks its interrupt on entry and re-enables at exit, so a bounded early exit with work remaining raises a fresh interrupt immediately (no frame loss). The jme_miibus_statchg drain (:418) intentionally stays unbounded (interrupts masked, must fully drain before index reset). Compile validated: nativekernel build with all three jme fixes -> RC=0, zero warnings (fix_build.log). Driver-level runtime validation impossible without JMicron hardware.", "fix_evidence": "fix.diff hunks applied to /usr/src (guest): grep shows jme_rx_proc_limit at :253/:255 and passed at :3535/:3880/:3917; fix_build.log: >>> Kernel build for X86_64_GENERIC completed RC=0, grep -cE error:|warning: == 0." } |