# DF-0981 — PoC evidence pack

**Finding:** Heap buffer overflow in `run_bulk_rx_callback()` aggregated-frame
path — `m_getcl` cluster (MCLBYTES == 2048) too small for the device-controlled
per-frame `dmalen` (can reach ~4079 bytes when the bulk RX URB is RUN_MAX_RXSZ
== 4096).

**File:** `sys/bus/u4b/wlan/if_run.c` (the finding's "File" field is correct;
the task prompt's `sys/dev/netif/run/` is a stale path — those are empty
subdirectories).

## Reachability on this guest

This is a **USB WiFi driver bug** in `run(4)` (Ralink RT2770/RT2870/RT3070/
RT3370/...).  The vulnerable callback `run_bulk_rx_callback()` is only entered
when:

1. A `run(4)` USB adapter is present and attached, AND
2. A bulk RX USB transfer (URB) completes with a frame.

The audit guest — DragonFly 6.5-DEVELOPMENT #0 in KVM — has **no USB
controller exposed to the guest and no run(4) device**:

- `usbconfig list` ⇒ `No device match or lack of permissions.`
- `kldstat | grep run` ⇒ empty (module is not loaded; nothing to bind)
- `ifconfig -a` ⇒ only `vtnet0` (virtio) and `lo0`

Therefore the **live trigger is unreachable on this guest** — this is a valid
hard blocker for a live kernel exploit.  No `uid=0` chain can be developed
here because the corrupting write never executes without the hardware.

Per the run instructions, a **deterministic code-level harness** that
replicates the exact `run_bulk_rx_callback` aggregation / `m_copydata` /
`dmalen` logic is the acceptable proof.  `run_aggr_oob.c` is that harness.

## How to reproduce

```sh
./build.sh      # cc -O2 -Wall -o run_aggr_oob run_aggr_oob.c
./run.sh        # prints BEFORE (OOB confirmed) and AFTER (fixed, no OOB)
```

## What the harness does

It re-implements, verbatim from the kernel:

- the per-frame DMA-length extraction `dmalen = le32toh(*mtod(m,uint32_t*)) & 0xffff`
  (if_run.c:2989),
- the `dmalen & 3` / `(dmalen+8) > xferlen` checks (if_run.c:2991-3001),
- the aggregated-frame branch decision `(xferlen -= dmalen+8) <= 8`
  (if_run.c:3003),
- the vulnerable allocation `m0 = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR)` whose
  cluster is MCLBYTES == 2048 (if_run.c:3013),
- the blind `m_copydata(m, 4, dmalen + sizeof(struct rt2870_rxd), mtod(m0,*))`
  with NO destination-bound check (if_run.c:3023-3024 + uipc_mbuf.c:1671-1696).

For a crafted URB of RUN_MAX_RXSZ (4096) bytes with `dmalen = 3000` (a legal
multiple of 4, satisfying `(dmalen+8) <= xferlen`, and leaving `xferlen -
(dmalen+8) = 1088 > 8` to force the aggregated branch), the BEFORE path writes
**3004 bytes into a 2048-byte cluster ⇒ 956-byte heap OOB write**.

The harness then runs the SAME input through the FIXED logic
(`m_getjcl(MJUMPAGESIZE)` + a `dmalen + sizeof(rt2870_rxd) > MCLBYTES` reject)
and shows neither overflows nor even reaches the copy.

## Fix

`fix.diff` (git-apply-able) makes two changes to
`sys/bus/u4b/wlan/if_run.c`:

1. **Allocation fix (root cause):** change `m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR)`
   → `m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR, MJUMPAGESIZE)` at the aggregated
   branch, so the destination cluster is at least as large as the source URB
   (MJUMPAGESIZE == 4096 == RUN_MAX_RXSZ), matching how `sc->rx_m` is already
   allocated at if_run.c:2933.
2. **Defense-in-depth bound:** reject any frame whose
   `dmalen + sizeof(struct rt2870_rxd) > MCLBYTES` before the copy, so even a
   future change to RUN_MAX_RXSZ / cluster sizing cannot re-introduce the
   mismatch.  This is the belt; #1 is the suspenders.

## Validation on this guest

Because `run` is a loadable module (not compiled into X86_64_GENERIC) and
there is no hardware to load it, the fix is validated by:

1. **Module compiles cleanly with `-Werror`:** both baseline (vulnerable) and
   patched `if_run.ko` build with `cc ... -Werror` and `rc=0`.  See
   `build.log` (baseline) and `fix_build.log` (patched).
2. **Harness before/after:** the vulnerable code path overflows (956 B); the
   patched logic does not.  See `run.log` and `fix_run.log`.

A full `nativekernel` rebuild would NOT recompile `if_run.c` (it is a module,
not in GENERIC), so it adds no signal beyond the module build.

## Files

- `run_aggr_oob.c` — the harness (faithful reimplementation).
- `build.sh` / `run.sh` — exact repro commands.
- `build.log` — baseline (unpatched) `if_run.ko` module build.
- `run.log` — harness run (BEFORE OOB / AFTER OK).
- `fix_build.log` — patched `if_run.ko` module build (`-Werror`, `rc=0`).
- `fix_run.log` — harness AFTER-section (fixed, no overflow).
- `fix.diff` — git-apply-able fix (2 hunks).
- `env.txt` — guest environment (uname, cc, kldstat, usbconfig).
- `VERDICT.md` — full narrative + line-by-line trace.
- `manifest.json` — artifact catalog.
