igb_txeof/igb_txgc loop on unvalidated NIC TX-head index: out-of-range 32-bit write-back β infinite interrupt-context spin holding tx_serialize (KKASSERT panic on INVARIANTS after one ring wrap; permanent soft-lockup otherwise)
Summary
Every TX cleanup path feeds the raw TX-head write-back word (*(txr->tx_hdr), uint32_t, DMA-written by the NIC in TDWBA head-write-back mode) into igb_txeof(txr, hdr) as an int with no range validation - five call sites. The cleanup loop 'while (first != hdr)' iterates first modulo num_tx_desc; a value outside [0, num_tx_desc) - including values >= 2^31 that turn negative - never equals first, so the loop never exits. avail increments per iteration and trips KKASSERT(avail < num_tx_desc) after one wrap on INVARIANTS kernels; production kernels spin forever inside the ISR while holding txr->tx_serialize (CPU wedge + interrupt storm via EIMS re-arm). igb_txgc has the identical loop against the TDH register read. Same defect as DF-3668 (ix). The driver itself documents observed spurious interrupts on these LOMs, and a spurious interrupt after stop/reset can consume a stale write-back.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3680 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.8 KB | β raw | |
| VERDICT.md | β | 3.1 KB | β raw | |
| trigger.c | β | 1.1 KB | view raw | |
| build.sh | β | 95 B | view raw | |
| run.sh | β | 163 B | view raw | |
| build.log | β | 133 B | view raw | |
| run.log | β | 98 B | view raw | |
| env.txt | β | 894 B | view raw | |
| fix.diff | β | 748 B | view raw | |
| verdict.json | β | 2.4 KB | view raw |
DF-3680 β igb_txeof/igb_txgc loop on unvalidated NIC TX-head index
What
The TX cleanup loop terminates on equality with an index taken from NIC-controlled storage, with no range validation:
igb_txeof(txr, hdr)βsys/dev/netif/igb/if_igb.c:2174-2217:while (first != hdr)wherehdris the 32-bit TX-head write-back*(txr->tx_hdr)at every call site::3361(igb_npoll_tx),:3454(igb_intr),:3516(igb_intr_shared),:4737(igb_msix_tx),:4938(igb_msix_rxtx)igb_txgc(txr)βsys/dev/netif/igb/if_igb.c:2220-2255: same loop againsthdr = E1000_READ_REG(&txr->sc->hw, E1000_TDH(txr->me))(:2230).
tx_hdr is uint32_t * (sys/dev/netif/igb/if_igb.h:250) pointing at a
DMA buffer the NIC writes (TDWBAH/TDWBAL head write-back mode,
igb_init_tx_unit:2072-2075). It is cast to int and used unvalidated.
If the device writes back a value outside [0, num_tx_desc) (device fault,
erratum, descriptor/DMA corruption, or malicious device), first wraps
0..num_tx_desc-1 forever without ever equaling hdr:
- INVARIANTS kernels:
KKASSERT(avail < txr->num_tx_desc)(:2189) fires after one full wrap (availincrements per iteration) β panic. - production kernels: infinite spin inside the ISR / poll callback while
holding
txr->tx_serializeβ CPU soft-lockup, interrupt storm (EIMS re-arms), system hang.
Same defect as DF-3668 (ix_txeof/ix_txgc, sys/dev/netif/ix/if_ix.c:2356).
Reproduce / verify
Requires igb hardware. The audit guest has none (run.log: only vtnet0,
driver compiled into kernel). Source-level verification only.
Expected result (on hardware)
Out-of-range TX-head write-back β panic (INVARIANTS) or permanent interrupt-context spin (production). With the fix, out-of-range head is ignored and cleaned up on the next valid interrupt.
DF-3680 VERDICT β unvalidated TX-head index in cleanup loops
Status: inconclusive on guest (no igb hardware) β source-certain
Root cause (path:line)
igb_txeof() (sys/dev/netif/igb/if_igb.c:2174-2217):
2174: igb_txeof(struct igb_tx_ring *txr, int hdr)
...
2181: first = txr->next_to_clean;
2182: if (first == hdr)
2183: return;
2185: avail = txr->tx_avail;
2186: while (first != hdr) { /* hdr NIC-controlled, unvalidated */
2189: KKASSERT(avail < txr->num_tx_desc);
2190: ++avail;
...
2195: if (++first == txr->num_tx_desc)
2196: first = 0;
2197: }
hdr comes verbatim from the DMA'd TX-head write-back buffer at every call
site: igb_npoll_tx :3361, igb_intr :3454, igb_intr_shared :3516,
igb_msix_tx :4737, igb_msix_rxtx :4938 β all igb_tx_intr(txr,
*(txr->tx_hdr)). tx_hdr is uint32_t * (if_igb.h:250), written by the
NIC in head write-back mode (igb_init_tx_unit:2072-2075 configures
TDWBAH/TDWBAL | E1000_TX_HEAD_WB_ENABLE). No call site masks or range-checks
the value; igb_txgc() (:2220-2251) has the identical loop against the
TDH register read (:2230).
Consequence arithmetic
first iterates modulo num_tx_desc (256..4096). For hdr β
[0, num_tx_desc) β including values β₯ 2^31 which become negative int β the
loop never terminates. Each iteration increments avail; after
(num_tx_desc - tx_avail) iterations avail == num_tx_desc β KKASSERT at
:2189 panics INVARIANTS kernels; production kernels spin forever holding
txr->tx_serialize inside the ISR β the serializer is never released, that
CPU wedges, and the interrupt is re-armed (EIMS write) in a tight storm.
Mbufs are freed on the first pass only (txbuf->m_head set NULL by
igb_free_txbuf), so the tail of the loop is a pure spin.
Threat model
The write-back value is device-controlled. Legit hardware always writes
valid indices; the exposure is device fault / erratum / DMA corruption /
malicious-PCIe (or VF-assigned) scenarios β identical premise to DF-3668
(ix), filed Low/certain. Note the driver itself documents observed spurious
interrupts on these LOMs (if_igb.c:723-727), and after igb_stop() +
NIC reset a stale/garbage write-back can be consumed by a spurious interrupt
(igb_init re-zeroes *tx_hdr only at init, :2019).
Why not reproduced on the guest
No igb hardware: ifconfig -a shows only vtnet0/lo0; driver compiled
into the kernel; /boot/kernel/if_igb.ko present. igb_attach() never runs,
so no TX ring or write-back buffer exists. See run.log.
Fix
Range-check hdr before the loops in both igb_txeof() and igb_txgc()
(fix.diff). Not hardware-testable: fix_status = not_testable.
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.
Fix verification
not_testablefix.diff authored against the read-only sys/ tree; cannot be validated without igb hardware.
findings/poc/DF-3680/fix.diff
Confirmed kernel references
- sys/dev/netif/igb/if_igb.c:2174
- sys/dev/netif/igb/if_igb.c:2186
- sys/dev/netif/igb/if_igb.c:2189
- sys/dev/netif/igb/if_igb.c:2220
- sys/dev/netif/igb/if_igb.c:2230
- sys/dev/netif/igb/if_igb.c:2239
- sys/dev/netif/igb/if_igb.c:3454
- sys/dev/netif/igb/if_igb.c:4737
- sys/dev/netif/igb/if_igb.c:4938
- sys/dev/netif/igb/if_igb.h:250
Detail
Evidence (decisive lines)
findings/poc/DF-3680/VERDICT.md (loop at if_igb.c:2186/:2239; unvalidated feed at :3361,:3454,:3516,:4737,:4938); run.log (no igb device on guest); fix.diff (range checks in both loops)
PoC changes
No runnable PoC without igb hardware; trigger.c documents the on-hardware write-back corruption procedure and sink lines; run.sh performs the module-presence/device-absence probe.
Verified recommended fix
Range-check hdr against num_tx_desc in igb_txeof() and igb_txgc() before the cleanup loops and bail out on out-of-range values.
Verdict
Source-certain, runtime-inconclusive: igb_txeof() and igb_txgc() terminate their cleanup loops on an unvalidated NIC-controlled index (32-bit TX-head write-back *(txr->tx_hdr) or TDH register read). An out-of-range value spins the ISR loop forever while holding tx_serialize β KKASSERT panic on INVARIANTS builds at if_igb.c:2189 after one ring wrap, permanent interrupt-context hang on production builds. Guest has no igb hardware (vtnet0 only; driver compiled into kernel), so the path cannot be exercised; same defect as DF-3668 (ix).
No comments yet.