ix_txeof/ix_txgc loop on unvalidated NIC TX-head index: out-of-range write-back β infinite interrupt-context spin (KKASSERT panic on INVARIANTS, soft-lockup otherwise)
Summary
TX completion is driven by the NIC-DMA'd head write-back *(txr->tx_hdr) (TDWBA-mapped) fed verbatim as 'int hdr' from four call sites plus the TDH register in ix_txgc. 'while (first != hdr)' assumes 0 <= hdr < tx_ndesc; first wraps inside the ring, so any out-of-range value makes the exit unreachable: INVARIANTS kernels panic at KKASSERT(avail < tx_ndesc); production kernels spin forever in ISR context (soft-lockup, box DoS) with unbounded avail increments. Malicious/faulty NIC (reset-during-DMA artifact, stuck-bus 0xFFFFFFFF, or DMA adversary) converts one bogus write-back into a permanent kernel hang; DoS-only (first stays ring-bounded, no memory corruption). Fix: reject out-of-range head indices.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3668 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.5 KB | β raw | |
| VERDICT.md | β | 3.4 KB | β raw | |
| fix.diff | β | 801 B | view raw | |
| build_baseline.log | β | 15.8 KB | view raw | |
| build_patched.log | β | 15.8 KB | view raw | |
| env.txt | β | 1.0 KB | view raw | |
| manifest.json | β | 626 B | view raw | |
| verdict.json | β | 2.3 KB | view raw |
DF-3668 β ix(4) TX completion trusts NIC-DMA'd head index with no ring bound
File: sys/dev/netif/ix/if_ix.c Β· Severity: Low Β· Class: hardware-trust DoS
PoC
No runnable PoC exists: the trigger requires an ixgbe NIC whose TX head
write-back (*(txr->tx_hdr), DMA'd by the NIC) or TDH register reports a value
outside [0, tx_ndesc). The audit guest has no such device (env.txt).
Source-level argument (see VERDICT.md):
if_ix.c:2368βwhile (first != hdr) { ... }inix_txeof():firstwraps within the ring,hdris an unmaskedintfrom*(txr->tx_hdr)(declaredvolatile uint32_t *atif_ix.h:223) or fromIXGBE_READ_REG(hw, IXGBE_TDH(...)). Ifhdr >= tx_ndescorhdr < 0, the equality is unreachable: the loop spins forever in interrupt context (INVARIANTS kernels tripKKASSERT(avail < txr->tx_ndesc)at 2371 and panic; production kernels soft-lockup withavailincrementing pasttx_ndesc).- Callers feeding the untrusted value:
if_ix.c:1203(legacyix_intr),if_ix.c:4788(ix_msix_tx),if_ix.c:4810(ix_msix_rxtx),if_ix.c:4957(ix_npoll_tx), andix_txgcreads TDH directly at 2400.
Fix validation (compile-level)
fix.diff rejects out-of-range hdr in both ix_txeof() and ix_txgc().
Applied on the guest to /usr/src (md5-identical to the audit tree) β
if_ix.ko rebuilt with -Werror, clean (build_patched.log).
Run (build validation only)
# on the audit guest: cd /usr/src && patch -p1 < fix.diff cd /usr/src/sys/dev/netif/ix && make obj && make
DF-3668 β VERDICT
Status: inconclusive (runtime) / source-level certain. Impact class: interrupt-context soft-lockup DoS (INVARIANTS: panic). Hardware-gated β no ixgbe NIC on the guest.
Root cause
ix_txeof() (sys/dev/netif/ix/if_ix.c:2355-2387) is the TX completion path fed
directly by hardware-provided head indices:
first = txr->tx_next_clean; /* if_ix.c:2363 β software, ring-bounded */
if (first == hdr)
return;
avail = txr->tx_avail;
while (first != hdr) { /* if_ix.c:2368 */
...
KKASSERT(avail < txr->tx_ndesc); /* 2371: INVARIANTS only */
++avail; /* unbounded on bogus hdr */
...
if (++first == txr->tx_ndesc)
first = 0; /* first wraps inside [0, ndesc) */
}
hdr arrives from:
*(txr->tx_hdr)β the TX head write-back buffer, a cacheline of DMA memory the NIC writes (if_ix.h:223, mapped atif_ix.c:2192-2195):- legacy interrupt:
ix_intrβif_ix.c:1203 - MSI-X TX vector:
ix_msix_txβif_ix.c:4788 - MSI-X combined RX/TX:
ix_msix_rxtxβif_ix.c:4810 - polling:
ix_npoll_txβif_ix.c:4957 IXGBE_READ_REG(hw, IXGBE_TDH(idx))inix_txgc()βif_ix.c:2400, same unboundedwhile (first != hdr)loop at 2409.
hdr is an int cast of a full 32-bit DMA'd word. The loop's exit condition
assumes 0 <= hdr < tx_ndesc. A hostile or hung NIC that reports e.g.
0xFFFFFFFF (a plausible stuck-bus / reset-during-DMA artifact, or an
attacker-controlled DMA write) makes the condition unreachable:
- INVARIANTS kernels:
KKASSERT(avail < txr->tx_ndesc)fires on the first wrap β kernel panic. - production kernels: the loop never exits β the interrupt/polling thread
spins in ISR context at 100% β soft lockup / permanent DoS of the box
(RX on that vector stops, watchdog cannot run);
avail(int) eventually overflows (UB) after 2^31 iterations.
There is no masking/clamping of the head anywhere in this driver β compare the
RX path which at least wraps i against rx_ndesc (if_ix.c:3085).
Threat model
Requires control of (or a fault in) the NIC's descriptor write-back β the same
malicious-peripheral / broken-FWIW trust boundary as DF-3667. Not triggerable
by network packets alone. DoS-only: no memory is corrupted (tx_buf[first]
stays inside the ring because first wraps).
Verification performed
- Full source read of ix_txeof/ix_txgc/ix_msix_* / ix_intr (cited lines).
env.txt: no Intel 10GbE PCI device on the audit guest;ix(4)compiled into the kernel but never attached β runtime reproduction impossible.fix.diff(rejecthdroutside[0, tx_ndesc)) applied to the guest's md5-identical/usr/src;if_ix.korebuilt with-Werror, zero errors (build_patched.log). Localsys/untouched.
Recommended fix
fix.diff: early-return on hdr < 0 || hdr >= txr->tx_ndesc in both
ix_txeof() and ix_txgc() (treat as spurious interrupt; the watchdog/txgc
path will re-run).
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_testableCompile-validated only (build_patched.log, -Werror clean); runtime comparison needs hardware.
build_baseline.log / build_patched.log; fix.diff applies with patch -p1 from /usr/src.
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (path:line); build_baseline.log vs build_patched.log (fix compiles clean); env.txt (no device)
PoC changes
New pass-2 finding, no seed. Fix authored directly from the audited source.
Verified recommended fix
Reject hdr outside [0, tx_ndesc) in ix_txeof/ix_txgc (fix.diff).
Verdict
Source-level certain, runtime-inconclusive: ix_txeof (if_ix.c:2368) and ix_txgc (if_ix.c:2409) loop 'while (first != hdr)' on a hardware-provided TX head index (DMA write-back *(txr->tx_hdr) at if_ix.c:1203/4788/4810/4957, or TDH register at 2400) with no range validation against tx_ndesc. A value outside [0, tx_ndesc) makes the exit condition unreachable: INVARIANTS kernels panic on KKASSERT at 2371; production kernels spin forever in interrupt context (soft-lockup DoS) with unbounded avail increments. Hardware-gated (malicious/faulty NIC); guest has no ixgbe device (env.txt). fix.diff (reject out-of-range hdr) compile-validated with -Werror on md5-identical guest source.
No comments yet.