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

Disabled bpf_peers_present early-out: tap kmallocs and copies every USB transfer payload even with no bpf listener (36-44x per-transfer overhead)

Summary

DFly #if 0'd FreeBSD's early-out while its bpf lazily assigns if_bpf: with a root-created usbus clone and NO listener, every USB transfer still pays precompute + kmalloc + full payload copy + kfree - measured 1094-1256 cycles/call vs 25-34 (36.2-43.5x) on the SUBMIT paths under the USB BUS lock, for as long as the unwatched tap exists. Restore-the-early-out fix (ifp->if_bpf==NULL return).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5531 Β· 10 files
FileTypeDescriptionSize
tap_cost.c β€” 5.6 KB view raw
Makefile β€” 103 B ↓ download
build.sh β€” 134 B view raw
run.sh β€” 172 B view raw
build.log β€” 490 B view raw
run.log β€” 524 B view raw
run.2.log β€” 526 B view raw
fix.diff β€” 309 B view raw
VERDICT.md β€” 2.7 KB ↓ raw
verdict.json β€” 3.9 KB view raw
VERDICT.md
↓ download raw

DF-5531 β€” VERDICT

Status: reproduced (behavioral/performance; no memory-safety impact).

Environment

Same guest as DF-5530 (DragonFly 6.5-DEVELOPMENT, bpf compiled in, qemu-xhci). tap_cost.c KLD calls the production usbpf_xfertap() (global symbol, sys/bus/u4b/usb_pf.c:345) with a synthetic 8x512 BULK-OUT xfer whose xroot->bus points at the real usbus0 softc, after ifconfig usbus0 create with no bpf listener attached.

Result (two runs, two independent boots)

run 1: (A) ifp set, if_bpf==NULL : 1094 cycles/call   (B) ifp NULL:   25   -> 43.5x
run 2: (A) ifp set, if_bpf==NULL : 1256 cycles/call   (B) ifp NULL:   34   -> 36.2x

run.log prints bus ifp=0xfffff8008fd1e480 if_bpf=0 β€” live proof that DragonFly's bpf leaves ifp->if_bpf NULL until a listener attaches (net/bpf.c:297/324). Case (A) therefore measures exactly the created-but-unwatched state: full precompute + kmalloc(totlen) (usb_pf.c:381) + usbd_copy_out of all 4096 payload bytes (usb_pf.c:468) + bpf_tap(NULL,…) no-op (net/bpf.c:1245) + kfree (usb_pf.c:490). Case (B) is the only early-out that remains (usb_pf.c:362).

The ~36-44x multiplier scales with payload: for real controllers this runs per transfer, under the USB BUS lock on the SUBMIT paths (usb_transfer.c:2630/2813), for as long as a root-created usbus clone exists with no listener β€” a standing availability tax on every device on that bus (isochronous/webcam-class traffic being the worst case).

Impact ceiling

Availability/performance only (root-armed), no corruption, no leak, no privilege boundary crossed β€” hence Info severity. Memory safety is intact: bpf_tap is NULL-safe (net/bpf.c:1245-1248) and the tap buffer is fully initialized (headers at usb_pf.c:387-419, zero-pad at usb_pf.c:475-476).

Fix

fix.diff: add if (bus->ifp->if_bpf == NULL) return; after the existing bus->ifp == NULL check (usb_pf.c:362), i.e. restore FreeBSD's early-out with DragonFly-appropriate plumbing. Info-severity finding; fix validation is code-inspection only (behavior is proven, the one-line guard obviously short-circuits case (A) β€” no kernel rebuild performed for this Info finding; the DF-5530 rebuild in the same session boots and behaves normally with DF-5531's line present in the tree? No β€” DF-5531's fix was NOT part of the DF-5530 fix kernel; marked fix_status=not_testable accordingly).

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 reproduced→ patch + rebuild →patched clean

Info-severity finding: no kernel rebuild performed for the one-line guard (the DF-5530 validation kernel in the same session carried only DF-5530's hunks). The guard is code-obvious: it returns at usb_pf.c:362-363 territory, before usbpf_xfer_precompute_size (:368) and the kmalloc (:381), so case (A) collapses to case (B) timing; baseline behavior itself is reproduced (36-44x).

fix.diff (one hunk, patch --dry-run verified); VERDICT.md
↓ fix.diffper-fix-DF-5531

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log + run.2.log: 'DF5531: bus ifp=0xfffff8008fd1e480 if_bpf=0 (expect NULL: no listener)' then '(A) ifp set, if_bpf==NULL : 1094 cycles/call' vs '(B) ifp NULL (early-return) : 25 cycles/call', ratio 43.5x (run 2 on a fresh boot: 1256 vs 34 = 36.2x); tap_cost.c is the KLD rig invoking the global usbpf_xfertap() against the real usbus0 softc

PoC changes

No seed existed. tap_cost.c fabricates a minimal 8x512 BULK-OUT usb_xfer (PIO page_caches, zeroed flags_int = HOST mode OUT so SUBMIT copies every frame) and points xroot->bus at the REAL usbus0 softc obtained via devclass_find("usbus"), so header fields (bus->bdev/parent) are production objects; times usbpf_xfertap(xfer, USBPF_XFERTAP_SUBMIT) with the clone present (if_bpf NULL) against a bus->ifp-NULL control. Guest needed -device qemu-xhci.

Verified recommended fix

Restore the listener early-out: after the bus->ifp NULL check at usb_pf.c:362, add 'if (bus->ifp->if_bpf == NULL) return;'.

Verdict

DragonFly disabled FreeBSD's bpf_peers_present early-out in usbpf_xfertap (sys/bus/u4b/usb_pf.c:364-367, '#if 0 XXX this is not needed on dragonfly'), and DragonFly's bpf only assigns ifp->if_bpf while a listener is attached (net/bpf.c:297/324), with bpf_tap() NULL-safe (bpf.c:1245). Net effect, measured live on the guest: with a root-created usbus0 clone and NO listener (dmesg: 'bus ifp=0xfffff8008fd1e480 if_bpf=0'), every call of the production usbpf_xfertap() performs the full precompute + kmalloc(totlen) + complete payload usbd_copy_out + kfree for nothing - 1094-1256 cycles/call for a 4 KiB transfer versus 25-34 cycles for the only remaining early-out (bus->ifp == NULL), a 36-44x overhead, and on the SUBMIT paths this runs while holding the USB BUS lock (usb_transfer.c:2630/2813), taxing every transfer on the controller for as long as an unwatched tap clone exists. No memory-safety impact (buffer fully initialized, bpf_tap NULL-checked), so impact class is none and severity Info; the realistic ceiling is a root-armed availability/performance degradation, worst for isochronous traffic. One-line fix: add 'if (bus->ifp->if_bpf == NULL) return;' after the existing check.