DF-3778 / trigger.c
/* * DF-3778 trigger analysis - jme_rxeof() unbounded walk in interrupt context. * Annotated excerpt of sys/dev/netif/jme/if_jme.c (verbatim, 2026-09-16 tree). * * jme_rxeof() walks completed RX descriptors in an unbounded for(;;): * * sys/dev/netif/jme/if_jme.c:2488-2562 * * static void * jme_rxeof(struct jme_rxdata *rdata, int count, int cpuid) * { * struct jme_desc *desc; * int nsegs, pktlen; * * for (;;) { * #ifdef IFPOLL_ENABLE * if (count >= 0 && count-- == 0) // <-- the ONLY bound * break; // (:2496-2497) * #endif * desc = &rdata->jme_rx_ring[rdata->jme_rx_cons]; * if ((le32toh(desc->flags) & JME_RD_OWN) == JME_RD_OWN) * break; // ring drained * ... * jme_rxpkt(rdata, cpuid); // m_getcl + dmamap * load + if_input * per frame * } * } * * The bound exists ONLY when count >= 0. Call sites: * * :418 jme_miibus_statchg jme_rxeof(rdata, -1, -1) // drain @reconfig * :3341 jme_npoll_rx jme_rxeof(rdata, cycle, mycpuid) // BOUNDED * :3500 jme_rx_intr jme_rxeof(rdata, -1, cpuid) // legacy/MSI * :3845 jme_msix_rx jme_rxeof(rdata, -1, mycpuid) // MSI-X RX * :3882 jme_rx_restart jme_rxeof(rdata, -1, cpuid) // MSI-X/poll sts * * The interrupt-context call sites (3500/3845/3882) all pass -1, i.e. the * walk exits only when the ring drains (OWN set) - and each iteration is * us-scale (jme_newbuf: m_getcl + bus_dmamap_load_mbuf_segment, if_jme.c:3061- * 3074; if_input dispatch), which is SLOWER than 64-byte line-rate arrival * on 1GbE (1.488 Mpps = 672ns/packet budget). Under a sustained small- * packet flood the ring is perpetually non-empty: * * - jme_msix_rx holds rdata->jme_rx_serialize for the whole walk * (ASSERT_SERIALIZED :3837; registered with that serializer at :3672), * so that ring's netisr/IRQ CPU spins at 100% and every other entry * point on the ring blocks; * - jme_rx_intr (from jme_intr :2150, registered on sc->jme_serialize * :3912) enters each ring serializer in turn (:3499) after taking the * MAIN serializer -> the whole interface wedges behind the walk; * - jme_msix_status / jme_npoll_status -> jme_rx_restart likewise. * * The driver itself proves the loop is boundable: the ifpoll path passes * `cycle` (:3341). DragonFly's sibling alc(4) driver had the same defect * filed as DF-3761 (process_limit knob existed but was never read); * msk(4) bounds its own walk (sys/dev/netif/msk/if_msk.c rxprog vs * msk_process_limit). jme has NO bound knob at all for the interrupt * path (grep process_limit -> 0 hits, see run.log). * * Mitigating difference vs alc(4): jme is 1GbE-only (JMC250; JMC260 is * 100MbE), so the flood rate ceiling is 1.488 Mpps; the livelock * reasoning still holds because per-frame kernel work exceeds the 672ns * inter-arrival budget (m_getcl + dmamap load + if_input alone). * * Trigger: remote, unauthenticated, default configuration - flood the * interface with minimum-size frames at line rate while it is up. * * Fix (fix.diff): bound the three interrupt-context call sites with a * jme_rx_proc_limit knob (default 64, tunable hw.jme.rx_proc_limit), * exactly the bound the ifpoll path already applies. Safe because every * one 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): a bounded early exit with work remaining re-interrupts * immediately. The :418 statchg drain is intentionally left unbounded * (link reconfiguration quiesce path, interrupts masked, must fully * drain before the ring pointers are reset). */ |