โฌข DragonFlyBSD Kernel Audit
DF-5394 / panic.txt
โ† back to finding โ†“ download raw
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.