# DF-0287 — VERDICT

**Verdict: NOT RUNTIME-TESTABLE on this guest (no WiFi hardware) — source trace DEFINITIVELY CONFIRMS the bug is real (div-by-zero DoS).**

## The claim
`sys/netproto/802_11/wlan/ieee80211_mesh.c`, `mesh_airtime_calc()` (line 3354):

```c
3354: uint32_t
3355: mesh_airtime_calc(struct ieee80211_node *ni)
3356: {
...
3366:     rate = ni->ni_txrate;
3367:     overhead = ieee80211_compute_duration(ic->ic_rt,
3368:         ifp->if_mtu + IEEE80211_MESH_MAXOVERHEAD, rate, 0) << M_BITS;
...
3383:     res = (overhead + (nbits / rate)) *       /* <-- div by rate */
3384:         ((1 << S_FACTOR) / ((1 << M_BITS) - errrate));
...
3386:     return (uint32_t)(res >> S_FACTOR);
3387: }
```

There is **no guard** against `rate == 0`. `rate` is `ni->ni_txrate`, which is 0
for a freshly discovered mesh neighbour whose transmit rate has not yet been set.

## Reachability (confirmed from source)

`mesh_airtime_calc(ni)` is called from `mesh_recv_action_meshlmetric()` at line
2560:

```c
2558:     if (ie->lm_flags & IEEE80211_MESH_LMETRIC_FLAGS_REQ) {
2559:         lm_rep.lm_flags = 0;
2560:         lm_rep.lm_metric = mesh_airtime_calc(ni);
```

This is the mesh Link-Metric *request* action handler — invoked when a remote
mesh peer sends a Link Metric Report Request action frame. The handler is
registered unconditionally in the mesh action dispatch table
(`ieee80211_mesh.c:586`) and is reached via `ieee80211_recv_mgmt` →
`ieee80211_parse_action` for any incoming mesh-category action frame. There is
**no established-peer requirement** gating the `mesh_airtime_calc` call — a
single crafted action frame from a neighbour whose `ni_txrate` is still 0 drives
`nbits / rate` with `rate == 0` → **divide-by-zero → kernel panic**.

(Note: `ieee80211_compute_duration(..., rate=0, ...)` at line 3367 is reached
first; if it too divides by `rate` it panics there. Either way a remote single
frame → panic.)

## Why not runtime-tested here
No 802.11 radio hardware → no mesh vap → the action-frame receive path (and thus
`mesh_airtime_calc`) cannot be exercised from a real frame. The div-by-zero is
**definitively confirmed by the source trace**: `rate = ni->ni_txrate` with no
zero guard before `nbits / rate`.

## Realistic impact ceiling
**Remote, unauthenticated kernel panic (DoS).** A single Mesh Link-Metric
Request action frame addressed to a node with `ni_txrate == 0` divides by zero
and panics the kernel. This is the worst of the mesh cluster (DF-0286 is the
read-only gateway; DF-0287 is the panic).

## Fix
`findings/poc/DF-0287/fix.diff` adds `if (rate == 0) return
IEEE80211_MESHLMETRIC_INITIALVAL;` immediately after `rate = ni->ni_txrate;`,
bailing with the initial metric until a real rate is set. Verified
`git apply --check` clean and compiles into the base kernel (nativekernel, with
the DF-0275/0286 fixes). `fix_status: not_testable` (diff applies + compiles;
runtime not exercisable without WiFi HW).
