DF-5394 / panic.txt
usb_detach() races asynchronous usb_bus_attach() โ NULL-softc guard bypassed,
wild-pointer write via dev->softc (observed -8) in usb_callout_drain
GUEST : DragonFly 6.5-DEVELOPMENT x86_64 (X86_64_GENERIC), QEMU/KVM 6 vCPU,
-device qemu-xhci,id=x0, xhci.ko modular (not in GENERIC)
TRIGGER : root# kldload xhci; kldunload xhci (zero delay โ one cycle is enough)
RESULT : kernel panic (trap 0xc, supervisor WRITE at VA 0x498), crash dump,
reboot. Reproduced 4/4 zero-delay attempts.
== Exact crash (crash dump vmcore.0, kgdb) ==
panic: page fault
Trace beginning at frame 0xfffff8011846d4a8
trap_fatal() at trap_fatal+0x301
trap_pfault() at trap_pfault+0x431
trap() at trap+0x17c
calltrap() at calltrap+0x9
--- trap 000000000000000c, rip = ffffffff8068734a, rsp = ..., rbp = ... ---
_callout_cancel_or_stop() at _callout_cancel_or_stop+0x1a
usb_detach() at usb_detach+0x3c
boot() called on cpu#2 -> dump to /dev/vbd0s1b
trap frame (decoded from stack, frame at 0xfffff8011846d5e8):
tf_rdi = 0x488 -> &bus->power_wdog
tf_rbx = 0xfffffffffffffff8 (-8) -> usb_detach's `bus`
tf_addr = 0x498 -> fault VA = rdi + 0x10
tf_err = 0x2 -> supervisor WRITE
tf_rip = _callout_cancel_or_stop+0x1a: lock andl $0xffffefff,0x10(%rdi)
usb_detach disassembly (kern.0):
callq device_get_softc
test %rax,%rax ; je early_return <- usb_controller.c:195 NULL check
mov %rax,%rbx <- bus = -8 (wild, non-NULL)
lea 0x490(%rax),%rdi <- &bus->power_wdog = -8+0x490 = 0x488
callq callout_drain <- usb_controller.c:200 -> fault
== Concurrency proof ==
The panic message is character-interleaved on the serial console with
usbus0: 5.0Gbps Super Speed USB v3.0
which is usb_bus_attach()'s device_printf at usb_controller.c:736 โ the
explore kthread was INSIDE usb_bus_attach (before the device_set_softc at
:791) on another CPU at the moment usb_detach faulted.
Dump cross-check: the usbus device object (0xfffff80117958500, ivars == bus
0xfffff801188e0528, matching the intrhook warning "usb: func=... arg=0xfffff801188e0528")
has its softc field equal to `bus` at dump time โ the async :791
device_set_softc() had landed in the same device object the detach path is
tearing down.
== Why the race is nearly deterministic on DragonFly ==
usb_attach_sub() calls config_intrhook_establish() at runtime
(usb_controller.c:871). On DragonFly run_interrupt_driven_config_hooks()
then sleeps ~10s in "conifhk" ("**WARNING** waiting for the following device
to finish configuring ... Giving up") before returning, so the attach_msg is
queued only ~10s into kldload; when kldload() returns, usb_bus_attach() has
only just started in the explore kthread. An immediately following kldunload
(devclass_delete_driver -> xhci_pci_detach -> device_delete_children ->
usb_detach) therefore always lands inside the async window.