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)
PoC verification
Evidence pack
findings/poc/DF-5530 Β· 17 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 readusbpf_detach()performs at usb_pf.c:209) and, whenever the published ifnet has not setIFF_UPyet (usb_pf.c:170 β strictly inside the usb_pf.c:157β177 window), calls the productionusbpf_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, printsbpfdetach: 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
- race v1 (no strike filter) β guest died <30 s at
usbpf_clone_destroy+0xf(same sink 1 signature; coarse detacher). - 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. - race v3 (IFF_UP filter) β sink 1 reproduced (<45 s),
panic.txt. - 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
fixedApplied 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'
Confirmed kernel references
- sys/bus/u4b/usb_pf.c:157
- sys/bus/u4b/usb_pf.c:164
- sys/bus/u4b/usb_pf.c:169
- sys/bus/u4b/usb_pf.c:177
- sys/bus/u4b/usb_pf.c:188
- sys/bus/u4b/usb_pf.c:191
- sys/bus/u4b/usb_pf.c:192
- sys/bus/u4b/usb_pf.c:207
- sys/bus/u4b/usb_pf.c:209
- sys/bus/u4b/usb_pf.c:114
- sys/bus/u4b/controller/usb_controller.c:240
- sys/net/bpf.c:1567
- sys/net/bpf.c:1574
- sys/net/if.c:2005
- sys/net/if.c:2013
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.
No comments yet.