# DF-3777 — jme_rxeof() admits (nsegs=0, pktlen=0): descriptor never re-armed, unbounded handler spin (faulty-device robustness hole; DF-3604-family OOB is otherwise CLOSED by the :2513 cross-check)

**File:** `sys/dev/netif/jme/if_jme.c` (pass 2)
**Severity:** Low · **CWE:** CWE-670 / CWE-20 · **Confidence:** certain (loop math, demonstrated on guest via verbatim-loop replica)

## What

`jme_rxeof` (if_jme.c:2494-2562) validates the controller-DMA'd RX
descriptor with a single cross-check:

```c
nsegs = JME_RX_NSEGS(le32toh(desc->buflen));	/* 7-bit  :2511 */
pktlen = JME_RX_BYTES(le32toh(desc->buflen));	/* 16-bit :2512 */
if (nsegs != howmany(pktlen, MCLBYTES))		/* :2513 */
	break;
```

This closes the DF-3604/3639/3663/3679/3713/3759 OOB-read family for
jme (pktlen is bound to nsegs×2048 posted clusters) — **but the pair
(nsegs=0, pktlen=0) passes it** (howmany(0,2048)==0). With nsegs==0,
`jme_rxpkt` (:2304-2486) executes neither the `jme_newbuf()` repost loop
(:2361) nor `jme_discard_rxbufs()` (:2350), and advances `jme_rx_cons`
by 0 (:2484-2485) — so the OWN-clear, VALID-set descriptor is never
re-armed and `jme_rxeof`'s `for(;;)` re-reads the same descriptor
**forever**: the MSI-X RX interrupt thread (or legacy handler / netisr
poll) spins holding the ring serializer; RX halts; that CPU softlocks.

Sub-case (benign, noted for completeness): pktlen ∈ [1,9] yields a
negative `jme_rxlen` (:2360) and negative `m_len`/`m_pkthdr.len`
(:2403/:2412) — `ether_input` drops it (`m->m_len < sizeof(struct
ether_header)`, sys/net/if_ethersubr.c:1501), so no memory-safety issue.

## Threat model

Compliant JMicron silicon always writes nsegs ≥ 1 for a completed frame;
triggering requires a faulty or malicious controller (misbehaving DMA
writes — the same threat model as the DF-3604 family findings, e.g.
Thunderbolt/evil-peripheral or buggy silicon). No unprivileged-software
or remote-packet trigger exists: wire traffic cannot make the NIC report
a zero-segment completed descriptor.

## Reproduce (loop-math replica, no hardware needed)

The `jme_callout_replica.ko` module (shared with DF-3776) contains the
**verbatim** `jme_rxeof` loop logic against a simulated ring:

```
vm.sh run_root 'kldload /root/kmod/jme_callout_replica.ko; sysctl kern.jmereplica.rxspin=1; \
                sysctl kern.jmereplica.rxspin_fix=1; dmesg | grep rxspin | tail -2'
```

**Expected (observed, run.log):**
* broken shape: `iterations=200000000 reposts=0 rx_cons 0 -> 0` — the
  full guard consumed, zero reposts, consumer unchanged → unbounded;
* fix shape (fix.diff): `iterations=2 reposts=1 rx_cons 0 -> 1` —
  terminates, degenerate descriptor reposted.

## Fix

`fix.diff` — reject `nsegs < 1` before the consistency check: repost the
one degenerate descriptor (`jme_discard_rxbufs(..., 1)`) and advance
(`JME_DESC_INC`), `continue`. Compile-validated (fix_build.log).
