β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3680

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3680 Β· 10 files
FileTypeDescriptionSize
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) where hdr is 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 against hdr = 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 (avail increments 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.

VERDICT.md
↓ download raw

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_testable
baseline no→ patch + rebuild →patched clean

fix.diff authored against the read-only sys/ tree; cannot be validated without igb hardware.

findings/poc/DF-3680/fix.diff
↓ fix.diffper-fix-DF-3680

Confirmed kernel references

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).