โฌข DragonFlyBSD Kernel Audit
DF-3850 / run.log
โ† back to finding โ†“ download raw
# DF-3850 run log โ€” dc(4) AB-BA callout deadlock, mechanism replica (guest)

Guest: DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 x86_64
(X86_64_GENERIC), 6 CPUs. Guest /usr/src verified byte-identical to the
audited tree before the run:

    MD5 (/usr/src/sys/dev/netif/dc/if_dc.c) = f396b494478178deff15f9ba999ef0b9

## Step 1 โ€” load replica, arm the dc_tick-shaped timer, fire dc_stop shape

    # kldload /root/kmod/dc_callout_replica.ko && echo KLD_OK
    KLD_OK
    # sysctl kern.dcreplica_arm=1
    kern.dcreplica_arm: 0 -> 0        (handler ran; callout armed at 1 tick)
    # (sysctl kern.dcreplica_stop=1 >/dev/null 2>&1 &)   <- run in background

Serial console (dfbsd-qemu/boot.log) โ€” decisive marker lines:

    dcreplica: loaded (if_dc.c callout protocol replica)
    dcreplica[arm]: dc_stat_timer armed (1 tick), waiting
    dcreplica[stop]: tick INPROG and PARKED on the serializer (fired=21 ran=20)
    dcreplica[stop]: IF THIS IS THE LAST dcreplica LINE callout_stop NEVER RETURNED (AB-BA deadlock)

fired=21 vs ran=20: the 21st tick was dequeued (INPROG set,
kern_timeout.c:567) and is parked in lwkt_serialize_enter() โ€” it never
completed, exactly like dc_tick parked on ifp->if_serializer while a
dc_stop caller holds it.

## Step 2 โ€” wedged-thread evidence (captured from a second session)

    # ps -o pid,stat,wchan,command -ax | grep dcreplica_stop | grep -v grep
     1530 D1     costp    sysctl kern.dcreplica_stop=1

    # ps -axH -o pid,tdnam,stat,wchan,command | grep -iE "softclock|dcreplica"
        -1 B0     slize    (softclock 0)        <- parked in lwkt_serialize sleep,
                                                   holding the Giant MP lock
                                                   (non-MPSAFE callout,
                                                   kern_timeout.c:583)
        -1 B1     -        (softclock 1)        <- other CPUs still deliver
        -1 B2     -        (softclock 2)
        -1 B3     -        (softclock 3)
        -1 B4     -        (softclock 4)
        -1 B5     -        (softclock 5)
     1530 D1     costp    sysctl kern.dcreplica_stop=1   <- parked inside
                                                   callout_stop() at
                                                   kern_timeout.c:920

Both halves of the cycle are visible:
  * stopper thread: state D1, wchan `costp`  == ssleep() inside the
    synchronous callout_stop() (sys/kern/kern_timeout.c:920)
  * softclock 0: state B, wchan `slize`      == the tick callback blocked in
    lwkt_serialize_enter() (sys/kern/lwkt_serialize.c:105 ->
    atomic_intr_cond_enter(..., lwkt_serialize_sleep)), INPROG set
    (kern_timeout.c:567), Giant MP lock held (kern_timeout.c:583,
    plain callout_init, if_dc.c:1847)

## Step 3 โ€” collateral: the wedge survives a clean shutdown attempt

`vm.sh down` (ssh `shutdown`): guest never reached poweroff โ€” the shutdown
path itself hung behind the parked mplock/serializer state; the qemu
process had to be SIGKILLed and the disk reverted
(`vm.sh reset with-src`). This matches the DF-3715 family blast radius
(permanent kernel degradation; here aggravated by the MP lock because
dc_stat_timer is a non-MPSAFE callout, unlike em/al/jme/msk).

## Step 4 โ€” fix-shape validation (fixed protocol replica, fresh guest)

    # kldload dc_callout_replica_fixed.ko
    # sysctl kern.dcreplica_armfx=1 ; sleep 1
    # time sysctl kern.dcreplica_stopfx=1
    kern.dcreplica_stopfx: 0 -> 0
            3.09 real         0.00 user         3.09 sys
    dmesg:
    dcreplica-fx: loaded (FIXED if_dc.c protocol replica)
    dcreplica-fx[arm]: fixed-shape dc_stat_timer armed
    dcreplica-fx[stop]: FIXED shape: callout_stop RETURNED, pending=0 fired=318 retried=308 ran=10

With the fix shape (lwkt_serialize_try + 1-tick retry re-arm), the tick
never parks while the stopper holds the serializer: 318 fires, 308 cheap
retries, and callout_stop() returns in exactly the 3 s the handler held
the serializer, with callout_pending()==0 โ€” the STOP post-processing
unloaded the pending retry, so the timer is genuinely dead. No deadlock.

(guest reset to with-src snapshot afterwards)