# DF-3306 — expire_upcalls() frees bw meters without unschedule (latent UAF)

* File: `sys/net/ip_mroute/ip_mroute.c:1423-1428`
* Class: latent use-after-free (unreachable today — see below)
* Bucket: memcorrupt (hardening)

## Analysis (no runnable PoC — the code is currently unreachable)

`expire_upcalls()` frees an mfc's bandwidth meters with a bare loop:

```c
while (mfc->mfc_bw_meter != NULL) {
    struct bw_meter *x = mfc->mfc_bw_meter;
    mfc->mfc_bw_meter = x->bm_mfc_next;
    kfree(x, M_BWMETER);            /* <-- no unschedule_bw_meter(x) */
}
```

FreeBSD's equivalent calls `unschedule_bw_meter(x)` before `kfree`.
Without it, any *scheduled* ("<=" type) meter would stay linked in the
global `bw_meter_timers[]` hash after being freed → the 1 Hz
`bw_meter_process()` would then walk freed memory
(ip_mroute.c:2715-2741) and relink it — a textbook UAF.

**Reachability today: none.** All code paths that attach bw meters go
through `mfc_find()` (ip_mroute.c:327-332), which only returns entries
with `mfc_stall == NULL`; `expire_upcalls()` only frees entries with
`mfc_stall != NULL` (pending NOCACHE upcalls, created at
ip_mroute.c:1290-1343 with `mfc_bw_meter = NULL` and never given
meters). The invariant "stalled ⇒ no meters" currently holds, so the
kfree loop is a no-op in practice. It is one refactor away from a real
UAF, and it diverges from FreeBSD for no reason.

## Fix
Add `unschedule_bw_meter(x);` before `kfree(x, M_BWMETER);`
(match FreeBSD; included in this pack's fix.diff).
