# DF-0612 — PoC: ieee80211_parse_tdma() join-path heap OOB write via setbit()

Unauthenticated adjacent-network (WiFi) heap OOB write PoC.

## Status — VERIFIED (code-level) + FIX VALIDATED

The bug is **real and present in the default GENERIC kernel**
(`ieee80211_parse_tdma` is compiled directly into `/boot/kernel/kernel`). The
**live 802.11 receive/join path is unreachable on this QEMU guest** (only
`vtnet0`/`lo0`, no WiFi radio, no wlan module loaded). Per the per-PoC
procedure, a deterministic **code-level harness** (`harness.c`) replicates the
exact kernel structs and the buggy `setbit()` line and proves the OOB; a
**binary-level before/after** on the actual shipped kernel proves the fix
(`VERDICT.md`, `logs/*.disasm`). The `inject_tdma_beacon.py` injector is kept
for real WiFi hardware (not runnable on QEMU).

The fix was validated end-to-end: `fix.diff` applied to in-guest `/usr/src`,
`make -j6 nativekernel` built a single-fix kernel (`#1`, sha `8048ccf4…`),
installed + rebooted; `ieee80211_parse_tdma` in the booted kernel now contains
the length guard (`cmp $0x15`) and slot guard (`cmp $0x1`) and the harness's
OOB case is blocked. See `VERDICT.md` and `fix_run.log`.

## Files

- `inject_tdma_beacon.py` — scapy-based beacon injector carrying a forged
  TDMA vendor IE with `tdma_slot=64` (OOB). When a DragonFlyBSD TDMA-mode
  vap selects this forged BSS as a join candidate,
  `ieee80211_parse_tdma()` runs `setbit(ts->tdma_inuse, 64)` which writes
  at `ts->tdma_inuse[8]` — 8 bytes past the 1-byte `tdma_inuse[]` array —
  corrupting the `tdma_peer` pointer in `struct ieee80211_tdma_state`.

## Build & run

There are two artifacts:

1. **`harness.c`** — a self-contained, deterministic userspace harness (runnable
   on this guest) that replicates the exact kernel struct layouts (verbatim
   header) and the buggy `setbit()` line. It proves the OOB write for several
   crafted IEs (slot=64 hits `tdma_peer`; slot=8 hits `tdma_active`; slot=255
   hits `tdma_lastprint`) and the secondary short-IE OOB read, and shows the
   FIXED-function variant rejects them. **This is what reproduces on QEMU.**

   Build (guest, as root — needs `/usr/src` for the header):
   ```
   ./build.sh          # or: cc -O2 -Wall -o harness harness.c  (with the header in -I)
   ./run.sh            # or: ./harness
   ```
   Expected: prints `OOB WRITE CONFIRMED` for the UNFIXED cases and
   `FIX HOLDS` / `OOB write confirmed on UNFIXED, blocked on FIXED`.

2. **`inject_tdma_beacon.py`** — the live-frame injector for real WiFi
   hardware. **Not reproducible in QEMU.**

The victim host must have a TDMA-mode vap that is scanning for SSID:

```
ifconfig wlanX create wlandev ath0 wlanmode adhoc tdmaslot 1
ifconfig wlanX up scan            # triggers ieee80211_parse_tdma on join
```

## Expected outcome

- TDMA link desynchronization,
- the victim's beacons carry a corrupted `tdma_inuse` mask,
- the active-slot refresh cadence (`tdma_count`) is disrupted.

With `SLOT` values in `32..63` the master's slot-mask refresh stalls or
fires every beacon.

## Slot-to-offset table

| tdma_slot | setbit offset (past tdma_inuse) | hits field          |
|-----------|---------------------------------|---------------------|
| 0-7       | 0                               | `tdma_inuse[0]` (in-bounds) |
| 8-15      | 1                               | `tdma_active` byte 0 |
| 16-23     | 2                               | `tdma_active`/`tdma_count` |
| 24-31     | 3                               | `tdma_count`        |
| 32-39     | 4                               | `tdma_count`/`tdma_peer` |
| 40-47     | 5                               | `tdma_peer`         |
| 48-55     | 6                               | `tdma_peer`/`tdma_lastprint` |
| 56-63     | 7                               | `tdma_lastprint`    |
| 64-71     | 8                               | `tdma_lastprint`    |
| ...       | ...                             | ...                 |
| 248-255   | 31                              | past struct         |

(Struct layout per `sys/netproto/802_11/ieee80211_tdma.h:67-87`.)

## Notes for the per-PoC verifier

- Cannot be reproduced in QEMU (no physical WiFi radio). The runner should
  confirm the code path statically:
  `ieee80211_sta_join` (`ieee80211_node.c:798`)
  -> `ieee80211_ies_expand` (`:833`)
  -> `ieee80211_parse_tdma` (`:848`)
  -> `setbit` (`ieee80211_tdma.c:657`).
- If a net80211 loopback/inject test harness exists in-tree, feed the
  crafted IE directly to the join path.
- The fix adds the same IE-length and `tdma_slot >= TDMA_MAXSLOTS` guards
  that `tdma_process_params()` (`:536,:555`) already has. Verify with
  `git apply findings/poc/DF-0612/fix.diff`.
