# DF-0351 — HWMP uint32 metric-accumulation overflow (route poisoning)

**Severity:** High · **Class:** integer-overflow → protocol/logic (route poisoning / hijacking)
**File:** `sys/netproto/802_11/wlan/ieee80211_hwmp.c`
**Sites:** `:1089` (PREQ recv), `:1325` (PREQ fwd), `:1453` (PREP recv),
`:1557` (PREP fwd), `:1972` (RANN recv) — all plain `uint32_t` add, no saturation.

## Bug

HWMP (IEEE 802.11s Hybrid Wireless Mesh Protocol) accumulates the airtime path
metric by adding each hop's link metric onto the value carried in the frame:

```c
/* ieee80211_hwmp.c:1089 */
metric = preq->preq_metric + ms->ms_pmetric->mpm_metric(ni);
```

`preq->preq_metric` is a `uint32_t` populated **verbatim** from the on-air frame
at `ieee80211_hwmp.c:458`:

```c
preq->preq_metric = le32dec(iefrm_t); iefrm_t += 4;   /* no range check */
```

The addition is plain `uint32_t` with **no saturation**, so an attacker
transmitting a crafted PREQ with `preq_metric` near `UINT32_MAX` makes the sum
wrap modulo 2³² to a small value.  That wrapped tiny metric then beats every
honest path metric in the route-accept comparison (`:1090-1102`) and is stored
into the route table (`:1095`), installing the attacker as the nexthop for the
originator → **route poisoning / hijacking / MITM / blackhole / partition.**

The same unsaturated add is used in **five** places (PREQ/PREP/RANN receive and
PREQ/PREP forward), so the wrap is reachable on every HWMP message type and
also **propagates**: a forwarder does `ppreq.preq_metric += link` (`:1325`) and
re-broadcasts the wrapped value, poisoning every downstream mesh node.

The 802.11s spec mandates a monotonically-increasing, non-wrapping metric
accumulation; the code has no saturation.

## Why this is not a memory-corruption primitive

The overflow stores a *valid* `uint32_t` into a `uint32_t` field (`rt_metric`).
There is no heap/stack write of attacker bytes, no UAF, no type confusion. The
damage is **routing state**: the mesh node routes traffic for the spoofed
originator through the attacker.  Impact ceiling = route poisoning / hijacking
in an 802.11s mesh (requires WiFi HW + mesh-mode VAP).  No `uid=0` chain
applies; the exploit chain field is `none`.

## Reproduction on this guest

The audit guest has **no WiFi hardware** and no mesh-mode VAP, so the live
in-kernel HWMP receive path cannot be driven end-to-end here (the action-frame
handler `hwmp_recv_action_meshpath` is only invoked from the net80211 RX path
for an association in `IEEE80211_M_MBSS`).  Per the finding's own note
("No WiFi HW → harness"), this pack ships a **userspace harness** that
replicates the *exact* arithmetic and route-accept logic of the cited lines,
proving the overflow is real and attacker-controlled, and proving the
saturating-add fix closes it.  This is the same harness-validated pattern used
for HW/remote-only findings across the audit.

### Build & run (on the guest, as `maxx`)

```sh
./build.sh   # cc -O2 -Wall -o hwmp_metric_overflow hwmp_metric_overflow.c
./run.sh     # ./hwmp_metric_overflow
```

### Expected output (decisive lines)

```
[fresh-seq  ] preq_metric=0xffffffff link=50000  => accumulated=49999       (0x0000c34f)  *** ROUTE POISONED ***
[equal-seq  ] preq_metric=0xffffffff link=50000  => accumulated=49999       (0x0000c34f)  *** ROUTE POISONED ***
hop1: 0xFFFFFFFE + 50000 = 49998 (0x0000c34e)  <-- WRAPS    (PREQ forward accumulation)
-- FIXED kernel --
[equal-seq  ] preq_metric=0xffffffff link=50000  => accumulated=4294967295  (0xffffffff)  rejected (legit route preserved)
```

## Fix

`fix.diff` introduces a `static __inline` saturating-add helper
(`hwmp_metric_add`) right after the `HWMP_SEQ_*` macros and replaces all five
plain-add sites with calls to it.  The sum clamps at `UINT32_MAX`, so the
wrapped tiny value never appears and the legitimate route is always preserved.

**Fix validation (Phase 8):** the harness's "FIXED" path mirrors the helper
byte-for-byte; additionally, `fix.diff` is applied to the in-guest source, a
single-fix `X86_64_GENERIC` kernel is built and booted, and the harness is
re-run to confirm the kernel compiles/links/runs with the fix in place.  See
`VERDICT.md` and `fix_build.log` / `fix_run.log`.
