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

sk_rxeof() Rx walk has no work bound β€” for(;;) loop's only exit is the OWN bit, so a line-rate remote flood keeps the walk running forever under the card-global serializer (DF-3761 family)

Summary

sk_rxeof processes completed RX descriptors in an unbounded for(;;) whose only exit is the NIC-owned OWN bit on the next descriptor; every processed descriptor is immediately reposted, so under a remote line-rate flood the BMU re-completes descriptors behind the walker and the loop never exits - inside sk_intr holding the single card-global serializer shared by both ports. Remote-unauthenticated RX livelock: softclock (incl. the tick), netisr, and the other port of a dual-port NIC all starve. Hardware-gated; budget fix mirrors msk's in-tree process_limit.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3904 Β· 5 files
FileTypeDescriptionSize
README.md β€” 2.5 KB ↓ raw
sk_rxeof_walk_shape.c β€” 1.3 KB view raw
fix.diff β€” 1.2 KB view raw
manifest.json β€” 962 B view raw
verdict.json β€” 2.9 KB view raw

DF-3904 β€” sk_rxeof() Rx walk has no work bound (DF-3761 family, sk(4) instance)

File: sys/dev/netif/sk/if_sk.c (pass 2, GLM 5.3) Hardware-gated: the audit guest has no SysKonnect SK-98xx/Yukon NIC, so the driver path cannot be exercised at runtime. Source-level certainty only; status inconclusive per family convention (identical treatment as the alc(4) original DF-3761).

The bug (source)

sk_rxeof() (sys/dev/netif/sk/if_sk.c:1803-1888) processes completed RX descriptors in an unbounded for (;;) loop:

  • only exit conditions are (a) the next descriptor still has SK_RXCTL_OWN set (:1833-1837) β€” i.e. the walker caught up with the NIC β€” or (b) nothing else: no packet counter, no budget.
  • every processed descriptor is immediately handed back to the NIC with ownership via sk_newbuf()/repost (:1873, :1862, :1875), so under a line-rate remote flood the BMU can re-complete descriptors behind the walker's back; after one lap (256 descriptors) the loop reaches index 0 again, finds it completed, and keeps going β€” indefinitely.
  • the walk runs inside sk_intr() (:2212-2221) which holds the ONE card-global sc->sk_serializer for the whole interrupt (bus_setup_intr(..., &sc->sk_serializer) at :1514), on both ports of a dual-port card.

Consequence (on sk hardware): a remote unauthenticated flood pins the interrupt CPU and the card serializer forever β€” RX livelock / remote DoS, starving softclock (including the very sk_tick DF-3903 is about), netisr, and the other port's TX/RX on the same serializer. This is the same defect class as DF-3761 (alc(4)); the sibling driver msk(4) bounds the identical walk with sc->msk_process_limit (sys/dev/netif/msk/if_msk.c:3225, tunable mskc.process_limit), proving the fix shape in-tree.

Why not runtime-verified

No SysKonnect PCI NIC exists on the audit QEMU guest (pciconf -l has no 0x10b3/0x1148 Yukon/GEnesis IDs), so sk_rxeof is unreachable at runtime. The unbounded loop is however directly visible in source and requires no hardware state to reason about. verdict.json records inconclusive (matching how DF-3761/3806/3850-family hardware-gated findings were persisted), reproduced=0.

Fix

fix.diff: add an SK_RX_PROC_MAX (64) packet budget to sk_rxeof, saving the resume index (cd->sk_rx_prod = i) before breaking β€” exactly what the loop already does at the OWN-bit exit (:1835), so the interrupt re-entry protocol is preserved. Mirrors msk(4)'s msk_process_limit.

Fix verification

inconclusive
baseline no→ patch + rebuild →patched clean

fix.diff authored against the read-only sys/ tree (budget + resume-index exit identical in shape to the existing OWN-bit exit). Not runnable on this guest (no sk NIC); the same fix shape is proven in-tree by msk(4).

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

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-3904/README.md (loop shape + only-exit analysis); findings/poc/DF-3904/sk_rxeof_walk_shape.c (annotated loop shape); findings/poc/DF-3904/fix.diff (budget + resume index, msk-style)

PoC changes

No seed PoC. No runnable trigger exists without SysKonnect hardware; the pack documents the source-level loop shape and the msk(4) in-tree precedent for the fix.

Verified recommended fix

Bound sk_rxeof with a per-interrupt packet budget (SK_RX_PROC_MAX=64) saving cd->sk_rx_prod before break, mirroring msk(4)'s mskc.process_limit.

Verdict

Source-certain, hardware-gated (no SysKonnect NIC on the audit guest) -> inconclusive per family convention, same treatment as the alc(4) original DF-3761. sk_rxeof (sys/dev/netif/sk/if_sk.c:1820-1888) walks completed RX descriptors in a for(;;) loop whose ONLY exit is the NIC-owned (SK_RXCTL_OWN) bit on the next descriptor (:1833-1837); every processed descriptor is immediately reposted to the NIC (:1862/:1873/:1875), so a line-rate remote flood keeps re-completing descriptors behind the walker and the loop never observes OWN - it runs forever inside sk_intr (:2212-2221) holding the ONE card-global sc->sk_serializer (bus_setup_intr with &sc->sk_serializer at :1514), on both ports of dual-port SK-98xx cards. Remote-unauthenticated RX livelock/DoS: the interrupt CPU is pinned, softclock/netisr and the other port starve. The sibling driver msk(4) bounds the identical walk via sc->msk_process_limit (sys/dev/netif/msk/if_msk.c:3225, tunable mskc.process_limit). fix.diff adds an SK_RX_PROC_MAX=64 budget with the same resume-index protocol the OWN exit already uses (cd->sk_rx_prod = i).