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

usbpf clone create/destroy race: half-published ifnet NULL-derefs in usbpf_clone_destroy (usb_pf.c:191) and bpfdetach (bpf.c:1574)

Summary

clone_create publishes ubus->ifp = if_alloc(...) BEFORE initializing it (softc/attach/bpfattach) with nothing serializing the lock-less usbpf_detach/clone_destroy: a destroy winning the window re-derives softc=NULL -> fault at 'ubus->ifp=NULL' on NULL+0x5c8, or bpfdetach on an unattached ifnet where the guard tests bp->bif_ifp instead of bp -> NULL+0x20 (a latent bpfdetach typo reachable via any double-destroy). Race-harness-proven on 2 independent boots (<45s: Fatal trap 12 at usbpf_clone_destroy+0xf VA 0x5c8) + deterministic sink module for the second fault; publish-after-init + NULL-guard + bp==NULL fix validated on a full rebuild (sink prints 'was not attached', 240s race NOHIT, clones still functional). Root-gated create race vs controller detach.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5530 Β· 17 files
FileTypeDescriptionSize
usbpf_race.c β€” 5.3 KB view raw
bpfdetach_null.c β€” 2.9 KB view raw
build_guest.sh β€” 602 B view raw
build.sh β€” 225 B view raw
run.sh β€” 245 B view raw
Makefile β€” 82 B ↓ download
build.log β€” 575 B view raw
run.log β€” 613 B view raw
panic.txt β€” 1.1 KB view raw
panic_race.txt β€” 1.1 KB view raw
panic_sink.txt β€” 1.0 KB view raw
env.txt β€” 414 B view raw
fix.diff β€” 1002 B view raw
fix_build.log β€” 426 B view raw
fix_run.log β€” 992 B view raw
VERDICT.md β€” 4.9 KB ↓ raw
verdict.json β€” 5.6 KB view raw
VERDICT.md
↓ download raw

DF-5530 β€” VERDICT

Status: reproduced (panic, both sinks) β€” production-shape race + deterministic sink.

Environment

DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026, X86_64_GENERIC, pseudo-device bpf compiled in (kernel, not KLD), xhci.ko/ehci.ko loaded, QEMU -device qemu-xhci so usbus0 exists. Rigs are KLDs built against the guest's /usr/src/sys.

How it was reproduced

Sink 1 β€” usb_pf.c:191 (ubus->ifp = NULL, ubus from ifp->if_softc == NULL)

usbpf_race.c drives the two production code paths against each other on the live bus:

  • creator thread: if_clone_create("usbus0", …) / if_clone_destroy("usbus0") β€” exactly the SIOCIFCREATE path (net/if.c:2005-2013 β†’ if_clone.c:74-95 β†’ usbpf_clone_create);
  • detacher thread: unlocked read of ubus->ifp (the same read usbpf_detach() performs at usb_pf.c:209) and, whenever the published ifnet has not set IFF_UP yet (usb_pf.c:170 β€” strictly inside the usb_pf.c:157β†’177 window), calls the production usbpf_detach(ubus) (usb_controller.c:240's entry point, usb_pf.c:207).

The detacher's filter keeps it out of the known DF-2347/DF-2607 double-destroy interleaving (striking a completed clone); with the filter, every strike lands on a half-built ifnet. Result β€” twice, on two independent boots, < 45 s each (panic.txt, panic_race.txt):

Fatal trap 12: page fault while in kernel mode
fault virtual address  = 0x5c8
fault code             = supervisor write data, page not present
instruction pointer    = 0x8:0xffffffff80a50a9f
Stopped at usbpf_clone_destroy+0xf:  movq $0,0x5c8(%rax)

0x5c8 is offsetof(struct usb_bus, ifp); the faulting store is ubus->ifp = NULL (usb_pf.c:191) with ubus == ifp->if_softc == NULL β€” i.e. the strike landed between the publish at usb_pf.c:157 and the ifp->if_softc = ubus assignment at usb_pf.c:164.

Sink 2 β€” net/bpf.c:1574 (bp->bif_ifp with bp == NULL)

bpfdetach_null.c constructs exactly the state usbpf_clone_destroy() sees when the strike lands between usb_pf.c:164 and :177 (ifnet named, if_softc set, not yet bpf-attached): if_alloc(IFT_USB) + ksnprintf(if_xname) + production bpfdetach(ifp). Result (panic_sink.txt):

DF5530: calling bpfdetach() on not-bpf-attached usbus0 ...
Fatal user address access from kernel mode from kldload ...
Fatal trap 12: page fault while in kernel mode
fault virtual address  = 0x20
instruction pointer    = 0x8:0xffffffff802aac53
Stopped at bpfdetach.cold.7:  movq 0x20,%rax

0x20 is offsetof(struct bpf_if, bif_ifp); bp is NULL because the search loop (bpf.c:1567-1571) legitimately found no match and the "Interface wasn't attached" guard tests bp->bif_ifp == NULL instead of bp == NULL.

Why this is not the known findings

DF-1100, DF-2347 and DF-2607 all race usbpf_xfertap (the tap hooks) against clone teardown. DF-5530's race is create vs destroy (usbpf_clone_create vs usbpf_detach/usbpf_clone_destroy), no tap involved, with two distinct NULL-deref sinks, one of which (the bpfdetach typo) is a latent defect in net/bpf.c reachable by this file's destroy path.

Threat model

Both race endpoints are privileged: clone creation is SIOCIFCREATE (caps_priv_check(SYSCAP_RESTRICTEDROOT), net/if.c:2005-2013); the detach side is controller detach (kldunload / devd) or SYSUNINIT shutdown. A root user racing ifconfig usbus0 create against kldunload xhci (or unplug-driven detach on hotplug-capable hardware) gets a deterministic kernel panic β€” local DoS, no memory-safety impact demonstrated (both faults are NULL derefs of small offsets). Severity Medium per the rubric ("local DoS … requires unusual privilege").

Fix validation

fix.diff (usb_pf.c: publish-after-init + NULL-softc guard; bpf.c: bp == NULL typo) applied to a clean-source guest, kernel rebuilt with make nativekernel, installed, rebooted:

  • kldload bpfdetach_null.ko β†’ loads cleanly, prints bpfdetach: usbus0 was not attached, no panic;
  • kldload df5530_race.ko β†’ runs its full 240 s window with the same strike filter, no panic (RACE_RESULT=NOHIT).

See fix_build.log, fix_run.log.

Attempts

  1. race v1 (no strike filter) β€” guest died <30 s at usbpf_clone_destroy+0xf (same sink 1 signature; coarse detacher).
  2. race v2 (ifunit-based filter) β€” harness artifact: ifunit() panics without ifnet_lock ("panic: ifnet is not locked"); filter replaced with the IFF_UP check, no production code involved in this artifact.
  3. race v3 (IFF_UP filter) β€” sink 1 reproduced (<45 s), panic.txt.
  4. sink module β€” sink 2 reproduced deterministically, panic_sink.txt.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

Applied fix.diff to a clean-source guest tree (patch -p1, all 3 hunks), rebuilt with 'make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel' (zero errors), rebooted. Baseline: race panic < 45 s (usbpf_clone_destroy+0xf, VA 0x5c8) and sink panic at bpfdetach (VA 0x20). Patched: kldload of the sink module returns 0 printing 'bpfdetach: usbus0 was not attached' with no panic, and the identical race harness completes its full 240 s window (RACE_RESULT=NOHIT) with clone create/destroy still functional. Both sinks eliminated.

fix_run.log: 'bpfdetach: usbus0 was not attached' / 'DF5530: bpfdetach() returned (not reached on vulnerable kernel)' / 'RACE_RESULT=NOHIT (no panic in 240s)' / vm.sh status up throughout; fix_build.log: '>>> Kernel install for X86_64_GENERIC completed on Wed Oct  7 00:26:15 UTC 2026'
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #1: Wed Oct 7 00:21:51 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Evidence (decisive lines)

panic_race.txt (Fatal trap 12, VA 0x5c8, Stopped at usbpf_clone_destroy+0xf: movq $0,0x5c8(%rax) - from the pure production-path race); panic_sink.txt (VA 0x20, Stopped at bpfdetach.cold.7: movq 0x20,%rax); run.log; build.log; fix_run.log (baseline panic vs fixed-kernel 'bpfdetach: usbus0 was not attached' + 240s NOHIT); VERDICT.md narrative

PoC changes

Seed was code-inspection only; no runnable seed existed. Wrote two KLD rigs: (1) usbpf_race.ko - two kernel threads driving the production SIOCIFCREATE path and the production usbpf_detach() entry against each other on the real xhci usbus0; the detacher strikes only when the published ifnet has not yet set IFF_UP (usb_pf.c:170) to stay strictly inside the :157-:177 window and out of the known DF-2347/2607 double-destroy interleaving (an ifunit()-based filter was discarded after discovering ifunit() panics without ifnet_lock - harness artifact, not a finding); (2) bpfdetach_null.ko - if_alloc(IFT_USB) + xname + production bpfdetach(), the exact state of a :164-:177 strike. Guest needed -device qemu-xhci (vm.sh QEMU_EXTRA) since the default QEMU has no USB controller.

Verified recommended fix

Publish ubus->ifp only after bpfattach() completes (usb_pf.c), guard usbpf_clone_destroy() against if_softc == NULL, and fix bpfdetach()'s not-found guard to test bp == NULL (net/bpf.c:1574).

Verdict

usbpf_clone_create() publishes the ifnet on ubus->ifp at sys/bus/u4b/usb_pf.c:157 long before it is initialized (if_softc at :164, if_attach at :169, IFF_UP at :170, bpfattach at :177), and nothing serializes usbpf_detach()/usbpf_clone_destroy() (controller detach, usb_controller.c:240, no ifnet_lock/bus lock) against it. A KLD harness driving only production entry points (if_clone_create()/if_clone_destroy() vs the unlocked ubus->ifp read + usbpf_detach() that usb_pf.c:209 performs) panicked the guest twice in under 45 s at usbpf_clone_destroy+0xf, fault VA 0x5c8 = 'ubus->ifp = NULL' with ubus = ifp->if_softc == NULL (strike inside :157-:164). The second sink was reproduced deterministically with a state-identical module: bpfdetach() on a never-bpf-attached ifnet faults at net/bpf.c:1574 (fault VA 0x20 = bp->bif_ifp on bp == NULL - the 'Interface wasn't attached' guard tests bp->bif_ifp instead of bp). Distinct from known DF-1100/DF-2347/DF-2607 (all tap-vs-teardown; this is create-vs-detach with no tap). Both faults are NULL derefs of small offsets: local DoS via a privileged race (SIOCIFCREATE is SYSCAP_RESTRICTEDROOT-gated, net/if.c:2005-2013); no escalation path. fix.diff (publish-after-init + NULL-softc guard + bp==NULL typo fix) was built into a guest kernel: the deterministic sink now prints 'bpfdetach: usbus0 was not attached' with no panic, and the identical race harness survives its full 240 s window (RACE_RESULT=NOHIT) with the usbus clone machinery still functional.