# DF-0351 — Verdict

**REPRODUCED** (source-confirmed + harness-demonstrated; live in-kernel path needs
WiFi HW, which the audit guest lacks — validated via the harness-arith equivalence
pattern, per the finding's own "No WiFi HW → harness" directive).

| Field      | Value |
|------------|-------|
| Class      | Integer-overflow → protocol/logic (route poisoning / hijacking). **Not** memory corruption. |
| Impact     | Route poisoning / hijacking in an 802.11s mesh (MITM / blackhole / partition). Realistic ceiling = mesh-network integrity + availability; **no `uid=0` chain** (the overflow stores a valid `uint32_t` into a `uint32_t` routing field — no arbitrary kernel write). |
| Severity   | High (matches finding CVSS `AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H`). |
| Confidence | certain |
| Reachability | Remote (adjacent), unauthenticated, over 802.11s HWMP action frames. Requires a mesh-mode VAP (`IEEE80211_M_MBSS`). |

## Mechanism (every hop cited `path:line`)

1. **Attacker-controlled input.** `hwmp_recv_action_meshpath()` parses each HWMP
   PREQ IE from the on-air frame and reads the metric field verbatim with no
   range/sanity check:
   - `sys/netproto/802_11/wlan/ieee80211_hwmp.c:458` → `preq->preq_metric = le32dec(iefrm_t);`
   - PREP and RANN metrics are parsed analogously (same `le32dec` pattern).
   An attacker transmitting a crafted PREQ sets `preq_metric` to any `uint32_t`,
   including values near `UINT32_MAX`.

2. **Unsaturated accumulation.** The receive path adds the per-hop link metric
   onto the frame value in plain `uint32_t` (wraps modulo 2³²):
   - `:1089` (PREQ recv)  `metric = preq->preq_metric + ms->ms_pmetric->mpm_metric(ni);`
   - `:1325` (PREQ fwd)   `ppreq.preq_metric += ms->ms_pmetric->mpm_metric(ni);`
   - `:1453` (PREP recv)  `metric = prep->prep_metric + ms->ms_pmetric->mpm_metric(ni);`
   - `:1557` (PREP fwd)   `pprep.prep_metric += ms->ms_pmetric->mpm_metric(ni);`
   - `:1972` (RANN recv)  `metric = rann->rann_metric + ms->ms_pmetric->mpm_metric(ni);`
   All operands and the LHS are `uint32_t`
   (`ieee80211_mesh.h:225 rann_metric`, `:250 preq_metric`, `:276 prep_metric`,
   `:429 rt_metric`, `:487 mpm_metric` return type).

3. **Fresh-seq bypass + poisoned store.** With a fresh originator sequence
   number the first clause of `:1090-1092` is true and the route is updated
   **unconditionally**; even on the equal-seq path the wrapped (tiny) metric
   beats the stored honest metric (`metric < rtorig->rt_metric`). The wrapped
   value is stored and the attacker becomes the nexthop:
   - `:1093` `hrorig->hr_seq = preq->preq_origseq;`
   - `:1094` `IEEE80211_ADDR_COPY(rtorig->rt_nexthop, wh->i_addr2);`  ← attacker
   - `:1095` `rtorig->rt_metric = metric;`  ← poisoned wrapped value
   - `:1102` `rtorig->rt_flags = IEEE80211_MESHRT_FLAGS_VALID;`

4. **Propagation.** The forward paths (`:1325`, `:1557`) re-broadcast the
   wrapped metric, so every downstream mesh node that forwards or receives the
   frame is also poisoned — a mesh-wide hijack primitive.

The 802.11s HWMP spec mandates a monotonically-increasing, **non-wrapping**
metric accumulation; the code has no saturation, so the wrap is real.

## Harness proof (`hwmp_metric_overflow.c`)

The harness replicates the exact arithmetic + route-accept logic of `:1089-1102`
and the forward accumulation of `:1325`. Decisive output (guest `maxx`, gcc 8.3):

```
[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, :1325)
-- FIXED kernel (saturating add) --
[equal-seq] preq_metric=0xffffffff link=50000 => accumulated=4294967295 (0xffffffff) rejected (legit route preserved)
```

`0xFFFFFFFF + 50000` wraps to `49999`; the route is poisoned and the attacker's
nexthop installed. Under the saturating-add fix the sum clamps to `UINT32_MAX`,
the comparison `UINT32_MAX < 5000000` is false, and the legitimate route is
preserved. `RUN_EXIT=0`, deterministic across runs.

## Why no escalation chain

The primitive is **not memory corruption**: a valid `uint32_t` is written to a
`uint32_t` routing field. There is no heap/stack write, UAF, double-free, or
type confusion — no `uid=0` chain exists. The impact ceiling is mesh routing
integrity/availability (route poisoning / hijacking). `exploit_chain = none`.

## PoC changes vs. seeded scaffolding

The seeded `findings/poc/DF-0351/` folder was empty; this run authored the full
evidence pack: `hwmp_metric_overflow.c` (harness), `build.sh`, `run.sh`,
`README.md`, `VERDICT.md`, `manifest.json`, `fix.diff`, and all logs.

## Fix (`fix.diff`)

Adds a `static __inline` saturating-add helper `hwmp_metric_add()` (clamp at
`UINT32_MAX`, `uint64_t` intermediate) right after the `HWMP_SEQ_*` macros
(`:134`) and replaces **all five** plain-add sites (`:1089`, `:1325`, `:1453`,
`:1557`, `:1972`) with calls to it. The fix is **broader than the finding's
three cited lines** — the finding missed the two forward-accumulation sites
(`:1325`, `:1557`), which are equally reachable and equally critical (they
propagate the wrap mesh-wide). One logical change, minimal, HWMP-spec-aligned.

`git apply --check -p1 fix.diff` ⇒ **APPLIES_CLEAN**.

## Fix validation (Phase 8)

- **Baseline (#0, `Thu Jul 2 06:02:54`):** unpatched source; harness vulnerable
  path shows `accumulated=49999 *** ROUTE POISONED ***`. `baseline_reproduced=1`.
- **Patch applied:** all 6 hunks (`patch -p1` ⇒ `PATCH_RC=0`); the patched TU
  `ieee80211_hwmp.o` rebuilt (timestamp 10:28, after the 10:17 apply); the helper
  appears at `:149` and all 5 call sites at `:1109/:1346/:1475/:1580/:1996`.
- **Single-fix kernel built:** `make -j6 nativekernel KERNCONF=X86_64_GENERIC`
  ⇒ `NK_DONE rc=0`, `Kernel build for X86_64_GENERIC completed`, **zero
  diagnostic errors** in the 35 537-line build log.
- **Installed + booted:** `/boot/kernel/kernel` overwritten with the freshly
  stripped build (sha256 `8bb331f6…`); new `kern.version` =
  `DragonFly 6.5-DEVELOPMENT #1: Thu Jul 16 10:24:22 UTC 2026`; clean boot to
  login prompt, **no panic, no hwmp/wlan dmesg errors**.
- **Fixed arithmetic verified:** the harness's FIXED path (byte-for-byte the
  in-kernel `hwmp_metric_add`) rejects the attack
  (`accumulated=4294967295 rejected, legit route preserved`), `RUN_EXIT=0`.

`fix_status = fixed`. The live in-kernel HWMP path is not drivable on this guest
(no WiFi HW / no mesh VAP — only `vtnet0`+`lo0`), so validation is at the
**compile + boot + arithmetic-equivalence** level (the harness-validated pattern
for HW/remote-only findings): the fix demonstrably closes every cited code path,
compiles, and boots with no regression, and its arithmetic is proven correct.
