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

bfe_intr error reinit performs M_WAITOK cluster allocations in interrupt context: BFE_ISTAT_ERRORS -> bfe_init -> bfe_newbuf(init=1) sleeps in the ithread holding the serializer

Summary

On any DMA/RX/TX error interrupt, bfe_intr calls bfe_init from interrupt context; the rx-ring re-init does 511 m_getcl(M_WAITOK) allocations inside the ithread holding the interface serializer. RDU fires exactly under cluster exhaustion, so the sleepable re-arm blocks precisely when the system cannot satisfy it - the IRQ line and every serializer-guarded entry point stall. The watchdog path is equally non-sleepable and hits the same M_WAITOK. NOWAIT-plumbing fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4305 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.4 KB ↓ raw
VERDICT.md β€” 2.7 KB ↓ raw
env.txt β€” 1.2 KB view raw
fix.diff β€” 3.6 KB view raw
manifest.json β€” 905 B view raw
verdict.json β€” 3.1 KB view raw

DF-4305 β€” bfe_intr error reinit performs M_WAITOK cluster allocations in interrupt context

File: sys/dev/netif/bfe/if_bfe.c (pass 2; known DF-1509 not touched) Lines: 1176-1187 (bfe_intr ISTAT_ERRORS β†’ bfe_init), 1186, 1362-1386 (bfe_init), 1368 (bfe_list_rx_init), 538-554 (bfe_list_rx_init β†’ bfe_newbuf(sc, i, 1)), 565 (m = m_getcl(init ? M_WAITOK : M_NOWAIT, ...)), 576 (if_printf in the load-fail path).

The bug

On any DMA/RX/TX error interrupt (BFE_ISTAT_ERRORS: DSCE/DATAE/DPE/RDU/ RFO/TFU), bfe_intr clears IFF_RUNNING and calls bfe_init from interrupt context (if_bfe.c:1186). bfe_init β†’ bfe_list_rx_init (:1368) β†’ bfe_newbuf(sc, i, 1) with init=1 β†’ m_getcl(M_WAITOK) (:565) for 511 clusters, plus bus_dmamap_load_mbuf_segment (BUS_DMA_NOWAIT, safe) and β€” on load failure β€” if_printf (:576).

M_WAITOK may block indefinitely in the mbuf allocator when the cluster zone is exhausted β€” in an interrupt thread holding the interface serializer. The realistic trigger is self-reinforcing: RDU ("receive descriptor underflow") fires precisely when the system is out of buffers (the driver failed to refill the ring), i.e. exactly when the M_WAITOK re-arm is most likely to sleep. A sleeping ithread stops servicing its IRQ line; with the serializer held, every other entry point on the interface blocks too; if the allocation depends on network progress to free clusters, the wait can persist indefinitely (soft deadlock), otherwise it stalls the interrupt pipeline for the allocation latency.

The TX-watchdog reinit path (bfe_watchdog :1470-1483 β†’ bfe_init :1480) runs in netisr context β€” equally non-sleepable β€” and hits the same M_WAITOK.

Severity Low: requires the error interrupt (hardware/error conditions, typically under system-wide memory exhaustion); impact is a stalled interface/interrupt line rather than memory corruption.

Guest status

No BCM4401 hardware exists on this guest (env.txt: NO_BROADCOM_PCI, hw.bfe unknown oid; QEMU has no device model), so the path cannot be executed β€” not_reproduced per family convention (cf. DF-3827). The defect is certain from source.

Build/run (nothing runnable without hardware)

See env.txt for the presence probes; fix.diff is the waitok plumbing.

Fix

Plumb a waitok flag: interrupt/watchdog reinit paths call bfe_init_waitok(sc, 0) β†’ bfe_newbuf uses M_NOWAIT (see fix.diff, patch -p1 --dry-run clean).

VERDICT.md
↓ download raw

DF-4305 VERDICT β€” sleepable allocation in bfe's interrupt-context reinit

Bottom line

Source-certain defect; runtime not reproducible on this guest (no BCM4401; QEMU cannot emulate one β€” env.txt). Recorded not_reproduced per family convention (DF-3827). Impact ceiling on real hardware: interrupt-thread stall / soft interface deadlock under cluster exhaustion β€” availability only, Low severity, no corruption, no leak.

Trace

  1. bfe_intr (:1152-1200) processes istat & BFE_ISTAT_ERRORS (if_bfereg.h:40-41) β†’ bfe_init(sc) at :1186 β€” this is the hardware interrupt handler (registered via bus_setup_intr :418-420, serializer passed).
  2. bfe_init (:1355-1387) β†’ bfe_list_rx_init(sc) :1368.
  3. bfe_list_rx_init (:538-554) loops bfe_newbuf(sc, i, 1) β€” init=1.
  4. bfe_newbuf (:557-595): m = m_getcl(init ? M_WAITOK : M_NOWAIT, MT_DATA, M_PKTHDR) :565 β€” M_WAITOK in ithread context.
  5. On bus_dmamap_load_mbuf_segment failure: if_printf :576 (console output from interrupt context β€” tolerated but noisy).

Sibling path: bfe_watchdog :1470-1483 β†’ bfe_init :1480 runs from if_slowtimo_dispatch (netisr, sys/net/if.c:1900-1912) β€” also non-sleepable.

Why it matters (threat)

BFE_ISTAT_RDU (receive descriptor underflow) is raised when the driver runs out of posted RX buffers β€” i.e. under cluster shortage. The recovery path then demands 511 new clusters with M_WAITOK from the ithread. Under the same shortage the allocator sleeps; the ithread (holding the serializer, pinning its IRQ) sleeps with it. Until clusters free up (possible only if other CPUs/network progress releases them) the NIC and its interrupt line are dead; if nothing frees clusters (all freeing paths blocked behind the same serializer), the stall is permanent. Realistic but narrow: Low severity.

Negative results (adjacent, this file)

  • bfe_init from bfe_ioctl (SIOCSIFFLAGS :1448) is process context β€” M_WAITOK fine there.
  • bfe_list_rx_init error path (== ENOBUFS :1368-1373) stops and calls bfe_stop β€” clean.
  • The map-swap in bfe_newbuf (:583-588) is correct on both load-success and retained-mbuf paths.

Fix validation

fix.diff plumbs waitok (intr/watchdog pass 0 β†’ M_NOWAIT; process paths keep M_WAITOK), patch -p1 --dry-run clean. Cannot be runtime-validated without hardware β†’ 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 (patch -p1 --dry-run clean); cannot be runtime-validated without BCM4401 hardware.

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

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (path:line trace incl. the RDU self-reinforcement argument and the netisr watchdog sibling); env.txt (no Broadcom PCI, hw.bfe unknown oid, guest src md5 == audited tree); fix.diff (waitok plumbing, patch -p1 --dry-run clean)

PoC changes

No seed; no runnable PoC exists without BCM4401 hardware - pack documents the source proof, the trigger analysis, and the fix.

Verified recommended fix

Plumb a waitok flag through bfe_init/bfe_list_rx_init/bfe_newbuf and pass 0 (M_NOWAIT) from the interrupt (bfe_intr :1186) and watchdog (:1480) reinit paths; see fix.diff.

Verdict

Source-certain sleepable-allocation defect: the bfe_intr error path (sys/dev/netif/bfe/if_bfe.c:1176-1187) calls bfe_init at :1186 from interrupt context; bfe_init -> bfe_list_rx_init (:1368) -> bfe_newbuf(sc, i, 1) -> m_getcl(M_WAITOK) at :565 allocates 511 clusters sleepably inside the ithread while it holds the interface serializer, and the load-failure branch calls if_printf (:576). BFE_ISTAT_RDU (receive descriptor underflow, if_bfereg.h:32) - the most likely error to fire - occurs exactly under cluster exhaustion, making the M_WAITOK re-arm sleep in the interrupt thread precisely when the system cannot satisfy it: the NIC's IRQ line and every serializer-guarded entry point stall until (unless) clusters free up elsewhere. The TX-watchdog reinit (bfe_watchdog :1470-1483 -> bfe_init :1480, netisr context) is equally non-sleepable and hits the same path. Kernel-path reproduction is impossible on this guest: no Broadcom BCM4401/4402 device exists and QEMU provides no model (env.txt: NO_BROADCOM_PCI, hw.bfe unknown oid); verdict not_reproduced with the defect certain from the source trace, per family convention (DF-3827). Impact ceiling: interface/interrupt-line stall (soft deadlock) under memory pressure - availability only, no memory corruption.