# DF-0350 — Unbounded mesh route-table growth + attacker-controlled lifetime

**Finding:** DF-0350 (High) — `sys/netproto/802_11/wlan/ieee80211_hwmp.c:1057-1097`
**Class:** CWE-400 unbounded resource consumption (kernel memory exhaustion DoS)
**Verdict:** REPRODUCED (code-level harness) + FIX VALIDATED (built & booted)

## What the bug is

Every HWMP PREQ/PREP/RANN frame carrying a **new** originator/target address
calls `ieee80211_mesh_rt_add` → `mesh_rt_add_locked()`
(`ieee80211_mesh.c:194-228`) which `kmalloc`s a `struct ieee80211_mesh_route`
(~100–150 B) and `TAILQ_INSERT_TAIL`s it into `ms->ms_routes` with **no upper
bound** on the number of entries. Additionally, `preq->preq_lifetime`
(`ieee80211_hwmp.c:457`, a uint32 straight off the wire) flows **unvalidated**
into `rt->rt_lifetime` via `ieee80211_mesh_rt_update` (`ieee80211_mesh.c:266`,
`hwmp.c:1097`), so a single spoofed PREQ pins its entry for up to
`0xFFFFFFFF` msec ≈ **49.7 days**.

An unauthenticated attacker within WiFi radio range of a mesh-mode vap
(`wlanmode mesh`) floods PREQs with distinct spoofed originator MACs + maximal
lifetime; the route table grows without bound (~2^46 distinct MACs available)
until `kmem_map` is exhausted → kernel panic / memory-pressure stall (DoS).

## Why a harness (no runtime PoC)

This KVM guest has **no WiFi radio** (`ifconfig -l` = `vtnet0 lo0`;
`ifconfig wlan create wlandev … wlanmode mesh` → `Device not configured`), so
the live mesh RX path is unreachable. This matches the settled no-HW findings
DF-0393 / DF-0594 / DF-0616. The harness embeds the **verbatim** kernel
allocation/insertion/lifetime code and drives it with an attacker frame stream
to prove both claims; a `-DFIXED` build mirrors the `fix.diff` to prove the
closure. The vulnerable code IS in the default GENERIC kernel
(`net.wlan.hwmp.*` sysctls present); only the runtime *trigger* (a radio) is
absent.

## Build & run

```
ssh dfbsd-maxx   # maxx uid 1001 (unprivileged)
cd poc/DF-0350
./build.sh        # builds: harness (BUG PRESENT) + harness_fixed (FIXED)
./run.sh          # runs both, prints the before/after contrast
```

### Expected output

**BUG PRESENT (`harness`):**
```
routes successfully added      : 8000
routes rejected (NULL)         : 0
final ms_routes table size     : 8000 entries
first added route rt_lifetime  : 0xFFFFFFFF msec (49.7 days)
VERDICT: UNBOUNDED GROWTH CONFIRMED — all 8000 PREQs added a new entry; no cap, no NULL, lifetime = attacker uint32.
```

**FIXED (`harness_fixed`, mirrors fix.diff):**
```
routes successfully added      : 4096
routes rejected (NULL)         : 3904
final ms_routes table size     : 4096 entries
first added route rt_lifetime  : 0x0000EA60 msec (60 s)
VERDICT: FIXED — table capped at 4096 entries (rejected 3904 over-cap PREQs); lifetime clamped to 60000 msec (60 s, was 49.7 days)
```

## Harness arguments

`./harness [Nattack] [lifetime]` — default `8000` distinct originators,
`0xFFFFFFFF` lifetime. `Nattack > 4096` exercises the cap in the `-DFIXED`
build.

## Files

- `harness.c` — faithful in-process model (verbatim kernel snippets) of the
  bug and the fix. `-DFIXED` toggles the cap+clamp.
- `build.sh` / `run.sh` — exact build/run.
- `fix.diff` — the validated kernel fix (cap + clamp), `git apply`-able.
- `VERDICT.md` — full narrative + line-by-line trace.
- `build.log` / `run.log` — BUG-PRESENT harness on `#0` unpatched.
- `fix_build.log` — single-fix kernel build (full).
- `fix_run.log` — FIXED harness on `#1` patched kernel.
- `env.txt` — guest environment.
- `manifest.json` — artifact catalog.
