# DF-3637 — ath_tx_start() swallows ath_tx_normal_setup() failure: leaked ath_buf + descriptor slab, phantom TIM

## What

`sys/dev/netif/ath/ath/if_ath_tx.c:2081-2082` — `ath_tx_start()` stores the
result of `ath_tx_normal_setup()` in `r`, jumps to `done:` on failure, but
`done:` unconditionally `return 0;`. The failure is never propagated.

## Consequence (source-level chain)

1. `ath_tx_normal_setup()` (if_ath_tx.c:1557) calls `ath_tx_dmasetup()`
   (if_ath_tx.c:294). On `bus_dmamap_load*` failure (ENOMEM with
   BUS_DMA_NOWAIT, or linearize/defrag failure under memory pressure) the
   mbuf is freed inside `ath_tx_dmasetup()` (if_ath_tx.c:320/338/349) and a
   non-zero error is returned. The "bogus frame type" branch
   (if_ath_tx.c:1706-1712) also frees the mbuf and returns EIO (reachable
   with an injected type-3 frame via the params==NULL raw path, which needs
   privileged BPF access).
2. `ath_tx_start()` sees `r != 0`, jumps to `done:`, returns **0**.
3. The `bf` obtained by the caller (e.g. `ath_raw_xmit()` if_ath_tx.c:2407
   `ath_getbuf()`; `ath_transmit()` in if_ath.c) is now: not queued anywhere,
   not returned to `sc->sc_txbuf`, mbuf already freed, `bf_m` never set.
   The caller believes TX succeeded: `ath_raw_xmit()` proceeds to set
   `sc->sc_wd_timer = 5` and — worse — calls `ath_tx_update_tim(sc, ni, 1)`
   (if_ath_tx.c:2444), marking the power-save TIM for the node even though
   nothing is queued.
4. Each occurrence permanently leaks one `ath_buf` **plus its private block
   of ATH_MAX_SCATTER(10) DMA descriptors** (allocated 1:10 in
   `ath_desc_alloc()`, if_ath.c:3813-3816) and its bus_dmamap. ATH_TXBUF is
   a fixed pool (~500); exhaustion wedges TX on the interface (persistent
   local/remote-assisted DoS under memory pressure).

## Reachability

Requires an ath(4) interface and either (a) `bus_dmamap_load_mbuf*` NOWAIT
failure — attacker-assistable by driving the system into mbuf/DMA-map
pressure while aggregated TX retries clone buffers — or (b) an injected
802.11 frame with FC type bits 3 via the legacy raw path (needs write access
to /dev/bpf on a monitor vap, i.e. root). Guest has no ath hardware —
runtime reproduction is impossible here; this pack carries the source-level
proof instead.

## How to verify (what run.sh does)

`verify_df3637.py` walks the exact cited lines in the read-only sys/ tree and
asserts: (1) `r = ath_tx_normal_setup(...)` is followed by
`if (r != 0) goto done;`; (2) the `done:` label returns literal `0`;
(3) every error return of `ath_tx_dmasetup` frees the mbuf first. Exit 0 ==
defect confirmed at source level.

## Files

- `verify_df3637.py` — source-level verification script (the "PoC")
- `run.sh` — runs it, tees to run.log
- `run.log` — decisive output
- `env.txt` — audit-host + guest environment
- `VERDICT.md` — full narrative
- `fix.diff` — the one-line fix, never applied to sys/
- `verdict.json` / `manifest.json`
