xl_rxeof() interrupt-mode Rx walk has no work bound β the count>=0 budget exists but every interrupt-path caller passes -1, and non-905B chips goto-again restart the walk on uplist stall (DF-3761 family)
Summary
xl_rxeof only honors a budget when count >= 0 (ifpoll-only); xl_intr passes -1, the resync retry loop is an unbounded while over resync + rxeof(-1), start/watchdog pass -1, and for boomerang chips the stall/reload/goto-again re-runs the whole walk under exactly the line-rate-flood state. Remote-unauthenticated RX livelock holding ifp->if_serializer inside xl_intr, starving softclock/netisr (and enabling the DF-3906 wedge window). The ifpoll path proves the bound plumbing works when given a real count. Pass-the-budget fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3907 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.2 KB | β raw | |
| fix.diff | β | 1.5 KB | view raw | |
| manifest.json | β | 993 B | view raw | |
| verdict.json | β | 3.0 KB | view raw |
DF-3907 β xl_rxeof() interrupt-mode Rx walk has no work bound (DF-3761 family, xl(4) instance)
File: sys/dev/netif/xl/if_xl.c (pass 2, GLM 5.3)
Hardware-gated: the audit guest has no 3Com 3c90x NIC; source-level
certainty; status inconclusive per family convention (same treatment as
DF-3761/DF-3904).
The bug (source)
xl_rxeof(struct xl_softc *sc, int count) (sys/dev/netif/xl/if_xl.c:1948)
already has the plumbing for a work bound β but only the ifpoll(4) path
uses it:
while ((rxstat = le32toh(sc->xl_cdata.xl_rx_head->xl_ptr->xl_status))) {
#ifdef IFPOLL_ENABLE
if (count >= 0 && count-- == 0) /* :1960 - bound ONLY when count >= 0 */
break;
#endif
...
}
Every interrupt-path caller passes -1, disabling the bound entirely:
xl_intr():2335βxl_rxeof(sc, -1)(main RX path)xl_intr():2340βwhile (xl_rx_resync(sc)) xl_rxeof(sc, -1);(unbounded resync retry loop)xl_start_body():2616βxl_rxeof(sc, -1)(TX-completion RX assist)xl_watchdog():3123βxl_rxeof(sc, -1)
Additionally, for non-905B (boomerang) chips the function ends with a
stall/reload/goto again (:2041-2063) that restarts the whole unbounded
walk whenever the upload engine hit the end of the ring β precisely the
state a line-rate flood produces.
The walk runs inside xl_intr() holding ifp->if_serializer
(bus_setup_intr(..., ifp->if_serializer) at :1529-1531).
Consequence (on xl hardware): a remote unauthenticated line-rate flood
keeps xl_status non-zero forever β the interrupt handler never returns,
pinning its CPU and the serializer (RX livelock / remote DoS; DF-3761
family, alc(4) twin).
Why not runtime-verified
No 3c90x NIC on the audit QEMU guest. The bound plumbing exists and is
proven correct in the ifpoll path (xl_npoll_compat passes a real
count, :2273); the finding is that the default interrupt mode never
uses it. verdict.json records inconclusive, reproduced=0.
Fix
fix.diff: pass a XL_RX_PROC_MAX (64) budget from the interrupt-path
callers (the count plumbing already implements the bound β count >= 0
enables it), and cap the while (xl_rx_resync(...)) retry loop. The
ifpoll path is unchanged.
Fix verification
inconclusivefix.diff authored against the read-only sys/ tree: enables the function's existing count>=0 bound from the interrupt path. Not runnable on this guest (no 3c90x NIC); the same bound is proven live by the ifpoll path in-tree.
findings/poc/DF-3907/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-3907/README.md (all four count=-1 call sites + goto-again analysis); findings/poc/DF-3907/fix.diff (budget via the existing count plumbing)
PoC changes
No seed PoC; no runnable trigger exists without 3c90x hardware. The defect is the count=-1 constant at four call sites against a bound that the same function already implements for ifpoll.
Verified recommended fix
Pass XL_RX_PROC_MAX=64 (instead of -1) from the interrupt-path xl_rxeof call sites and cap the xl_rx_resync retry loop; the count>=0 bound already exists in xl_rxeof.
Verdict
Source-certain, hardware-gated (no 3Com 3c90x NIC on the audit guest) -> inconclusive per family convention, same treatment as DF-3761 (alc) and DF-3904 (sk). xl_rxeof (sys/dev/netif/xl/if_xl.c:1948) HAS a work-bound parameter but it is only honored when count >= 0 (:1959-1962) and every interrupt-path caller passes -1: xl_intr :2335 (main RX), xl_intr :2340 (the unbounded while(xl_rx_resync(sc)) xl_rxeof(sc,-1) retry loop), xl_start_body :2616 (TX-completion RX assist), xl_watchdog :3123; for non-905B chips a goto-again restart (:2041-2063) further re-runs the walk whenever the uplist stalled - the exact state a line-rate flood produces. The walk executes inside xl_intr holding ifp->if_serializer (bus_setup_intr at :1529-1531), so a remote unauthenticated flood pins the interrupt CPU and serializer indefinitely (RX livelock / remote DoS). The ifpoll path (xl_npoll_compat :2273) proves the bound plumbing works when given a real count. fix.diff passes XL_RX_PROC_MAX=64 from the interrupt-path callers and caps the resync retry loop; ifpoll behavior unchanged.
No comments yet.