# DF-4303 VERDICT — bfe_tick / bfe_stop callout AB-BA deadlock (DF-3715 family)

## Bottom line

**Mechanism PROVEN on the guest kernel** by a deterministic replica of
if_bfe.c's exact callout/serializer protocol; bfe-specific runtime is
hardware-gated (no Broadcom BCM4401 exists on this guest — QEMU has no such
device model — and none can be added), so per family convention (DF-3715)
the overall verdict is **inconclusive** with the defect **certain** from
source and the **mechanism certain from execution**.

## Source trace (sys/dev/netif/bfe/if_bfe.c)

1. `callout_init(&sc->bfe_stat_timer)` — :323, **non-MP** flavor.
2. Armed `callout_reset(&sc->bfe_stat_timer, hz, bfe_tick, sc)` — :1386
   (from `bfe_init`).
3. `bfe_tick` — :1486-1508:
   - `mii = device_get_softc(sc->bfe_miibus)` :1492 (outside serializer)
   - **`lwkt_serialize_enter(ifp->if_serializer)` :1494 — BLOCKING**
     (sys/kern/lwkt_serialize.c:105 → adaptive spin up to
     SLZ_ADAPTIVE_SPINMAX then `lwkt_serialize_sleep`)
   - `callout_reset(..., hz, bfe_tick, sc)` **:1497 — self re-arm INSIDE**
   - `lwkt_serialize_exit` :1508.
4. `bfe_stop` — :1516-1530: `ASSERT_SERIALIZED` :1520 → **`callout_stop`
   :1522** (the sync variant, sys/kern/kern_timeout.c:1091 →
   `_callout_cancel_or_stop(cc, CALLOUT_STOP, 1)` :859 — waits
   `ssleep(c, &c->spin, 0, "costp", 0)` :920 until the INPROG callback
   completes).
5. `bfe_stop` callers holding the serializer: `bfe_ioctl` SIOCSIFFLAGS
   :1449-1450, `bfe_init` :1365 ← `bfe_watchdog` :1480 ←
   `if_slowtimo_dispatch` (sys/net/if.c:1900-1912,
   `ifnet_tryserialize_all`), `bfe_intr` ISTAT_ERRORS :1186, `bfe_detach`
   :441, `bfe_shutdown` :475.

Interleaving: tick dequeued → parks sleeping on the serializer → stopper
enters serializer → `callout_stop` parks sleeping on "costp" → neither can
ever proceed. The ifnet serializer is held forever by a sleeping thread;
the softclock thread of the callback's CPU is consumed forever (no further
callouts on that CPU).

## Guest execution (this run, evidence in run.log / build.log)

- `make` in /root/kmod: BUILD_RC=0, `-Werror` clean (build.log).
- `kldload` OK; `kern.bferplica.arm=1`.
- **Control (fix shape):** `kern.bferplica.stop_fixed=1` →
  `bferplica[fixed]: callout_stop RETURNED (no deadlock), pending=0` —
  try-enter handlers are safely stopped under a held serializer → the
  family fix (fix.diff) is validated at mechanism level on this kernel.
- **AB-BA (bfe shape):** `kern.bferplica.stop_blk=1` in background →
  last dmesg lines:
  `bferplica[blk]: bfe_tick handler BLOCKED on our serializer (fired=16 ran=15) - entering callout_stop()`
  `bferplica[blk]: IF THIS IS THE LAST bferplica LINE, AB-BA DEADLOCK IS PROVEN (bfe_tick vs bfe_stop)`
  and nothing after. `ps`: `1048 D2 costp sysctl kern.bferplica.stop_blk=1`
  — D-state thread parked on the kern_timeout.c:920 wait channel. The
  launching ssh session stalled (timer starvation on the affected CPU);
  guest required `vm.sh reset with-src` (performed; guest restored clean).

## Threat model

Local DoS. The watchdog entry (TX timeout) is drivable by an unprivileged
local user pumping traffic at a bfe interface whose link is stalled
(`bfe_start` sets `if_timer=5` :1351; watchdog → `bfe_init` → `bfe_stop`).
Every `bfe_stop` races the 1 Hz tick; the race window is the callback's
park-on-serializer interval. Root paths (`ifconfig down`, detach, shutdown)
hit the same race. On hit: interface permanently wedged + one CPU's callout
pipeline dead → system-wide degradation (observed: ssh stall).

No memory corruption: availability only. No uid=0 route (not a corruption
class).

## Why not false-positive / alternatives

- Not the "racing re-arm survives callout_stop" family: the DF-3715
  control (and my `stop_fixed` control here, `pending=0`) shows this
  kern_timeout.c unloads a racing re-arm after STOP — the defect is purely
  the AB-BA.
- `bfe_stop` cannot simply switch to `callout_stop_async`: `bfe_tick`
  re-arms itself unconditionally (:1497), so an async stop leaves the
  timer cycling on a stopped/detached device. The complete fix is the
  try-enter handler (fix.diff), matching the DF-3715 fix merged for em(4).

## Fix validation

`fix.diff` (try-enter + unconditional outside re-arm) — the fixed shape is
exactly the `stop_fixed` positive control, which **returns cleanly with
pending=0 on this guest kernel**. The driver-level diff cannot be runtime
tested without BCM4401 hardware → fix_status not_testable (driver),
mechanism validated (control).

## Artifacts

- `bfe_tick_replica.c` + `Makefile` — the bfe-protocol replica module.
- `build.log` — full module build (+ harness build/run) output, BUILD_RC=0.
- `run.log` — load/arm/control/AB-BA session: dmesg lines + `ps` capture
  (`1048 D2 costp`).
- `env.txt` — guest uname, NIC inventory (vtnet0/lo0 only), no Broadcom
  PCI, md5(guest if_bfe.c) == md5(audited tree).
- `fix.diff` — bfe_tick try-enter fix (patch -p1 --dry-run clean).

## 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.
