# DF-3760 — alc_stop()/alc_tick() callout AB-BA deadlock (DF-3715 family)

**Severity: Medium (privileged local permanent kernel deadlock / DoS) —
DragonFly `sys/dev/netif/alc/if_alc.c`, pass 2 (GLM 5.3).**

## What

`alc_tick` is an MP-safe, self-re-arming callout whose handler takes the
driver serializer **blocking**:

```c
static void
alc_tick(void *arg)
{
	...
	lwkt_serialize_enter(ifp->if_serializer);	/* :3414 BLOCKING */
	mii_tick(mii); alc_stats_update(sc); alc_txeof(sc); alc_watchdog(sc);
	callout_reset(&sc->alc_tick_ch, hz, alc_tick, sc);	/* :3428 */
	lwkt_serialize_exit(ifp->if_serializer);
}
```

`alc_stop` tears it down **while holding that same serializer**:

```c
static void
alc_stop(struct alc_softc *sc)
{
	ASSERT_SERIALIZED(ifp->if_serializer);		/* :3965 */
	...
	callout_stop(&sc->alc_tick_ch);			/* :3973 */
```

On this kernel `callout_stop()` is the **synchronous** variant
(`sys/kern/kern_timeout.c:1091` → `_callout_cancel_or_stop(..., sync=1)`);
when the callback is INPROG it `ssleep`s on `"costp"`
(`sys/kern/kern_timeout.c:923`) until the callback completes.

⇒ If `alc_tick` fired and is parked inside `lwkt_serialize_enter`
(if_alc.c:3414) on the serializer held by the `alc_stop()` caller, the
stopper sleeps in `callout_stop()` waiting for the callback, and the
callback sleeps waiting for the serializer: **permanent AB-BA deadlock**.
Both threads wedge forever, the interface serializer is never released
(every later driver path on that interface blocks: ioctls, interrupts,
tick), and the affected CPU's softclock thread stops delivering **all**
callouts.

Callers of `alc_stop` with the serializer held: `alc_init`
(if_alc.c:3580 — every reinit: ifconfig up/down `alc_ioctl` :2824/:2826,
watchdog :2774/:2779, DMA-error interrupt `alc_intr` :3087/:3103),
`alc_detach` (:1616), `alc_suspend` (:2437).

Trigger: `ifconfig alc0 down` (or MTU change / detach / suspend-resume)
while the 1 Hz tick callback is dequeued-and-parked — a ~1% per-stop race
window, repeatable by a privileged local user.

Same defect as DF-3715 (if_em.c), independent instance in if_alc.c.
DF-3715's guest replica also proved the negative: on this `kern_timeout.c`
`callout_stop`'s STOP *unloads* a racing re-arm, so the defect here is the
sync-wait deadlock, **not** a post-stop re-arm UAF.

## Reproduce (mechanism, on this guest — deterministic)

```
scp alc_callout_replica.c + Makefile to guest:/root/kmod
cd /root/kmod && make                       # build.log
kldload /root/kmod/alc_callout_replica.ko
sysctl kern.alcreplica.arm=1
sysctl kern.alcreplica.stop_try=1           # FIX-shape control: returns, pending=0
(sysctl kern.alcreplica.stop_blk=1 &)       # shipped-shape: NEVER returns
dmesg | grep 'alcreplica\[blk\]'            # "IF THIS IS THE LAST alcreplica LINE,"
ps -o pid,stat,wchan,command -ax | grep stop_blk
#  -> <pid> D5  costp  sysctl kern.alcreplica.stop_blk=1   == kern_timeout.c:923
```

The replica reproduces if_alc.c's exact protocol
(blocking `lwkt_serialize_enter` + `callout_reset(hz)` self re-arm vs
`callout_stop` under the serializer). Expected: the last dmesg line is the
marker, the sysctl thread sits **D-state, wchan `costp`** forever, and the
guest must be reset. The alc-specific instance itself needs AR81xx
hardware, which the audit guest lacks (env.txt).

## Fix

`fix.diff` — mirror the DF-3715 fix: `alc_tick` uses
`lwkt_serialize_try()`; on failure it re-arms for the next tick and
returns (never blocks on the serializer, so `callout_stop` under the
serializer always completes; `alc_stop`'s STOP unloads the racing re-arm —
**validated live** by the replica's `stop_try` control: `callout_stop
returned, pending=0`).
