# VERDICT — DF-4270 (wb_start ignores wb_encap failure, if_wb.c:1327)

**Status: reproduced** (deterministic, production code) — **fix validated:
fixed**.

## Method

DF-4266 KLD dispatch rig: resolve the running kernel's real static
`wb_start` (0xffffffff808d9e90; `wb_encap` is inlined into it), fabricate
the proven post-attach softc, enqueue a crafted packet on the production
classic ifq, dispatch. The failure precondition the finding requires
("M_NOWAIT allocations fail under mbuf pressure") is forced exactly:
drain the cluster pool (MGETHDR+MCLGET until MCLGET fails — 34,655
clusters) so the coalesce branch's `MCLGET(m_new, M_NOWAIT)` fails while
`MGETHDR` still succeeds. The crafted chain (1 pkthdr + 16 data mbufs,
128 B each, pkthdr.len 2176) is precisely the >16-fragment shape that
enters the coalesce branch and requires a cluster.

That the `:1255-1258` path was taken is proven three ways: (a)
`mbstat.m_drops` +1 attributable to `m_mclget` (uipc_mbuf.c:1249) at
dispatch time; (b) the post-dispatch probe "MGETHDR ok; MCLGET FAILS"
(the packet pool is fine, only clusters are dry); (c) the descriptor
state is the failure state (`wb_mbuf` NULL — `:1283` never ran; frag[0]
ctl still carries the frag-loop's `m_len`=128, not the coalesce rewrite
of 2176; LASTFRAG nowhere).

## Baseline — stock kernel #0 (run.kld.log)

| claim | observation |
|---|---|
| packet chain leaked | `mbtypes[MT_DATA]` accounting: exactly +17 outstanding vs the chain-free expectation (self-tested ±1-accurate); queue len 0; `chain[0].wb_mbuf == NULL`; mbuf delta across `wb_start` = 0 (driver freed nothing); chain walk: 17 mbufs, links + markers intact (never `m_freem`'d — `m_free` NULLs `m_next`); **`*** DF-4270 LEAK CONFIRMED ***`**; and after the REAL `wb_watchdog` (wb_stop+wb_reset+wb_init): **"leaked mbufs STILL leaked: YES"** |
| half-armed descriptor armed for the NIC | `frag[0].status=0x80000000` (OWN, set `:1354`), `ctl=0xa1000080` (FINT\|FIRSTFRAG\|TLINK\|128), frags 1..15 OWN (`:1232`), **LASTFRAG 0/16**, `frag[15].wb_next == vtophys(&frag[16])` (one-past), **`WB_TXSTART=0xffffffff`** (`:1355`), `if_timer=5` |
| TX completion wedges | REAL `wb_txeof.part.4` dispatched with a chip-completed head frag: `opackets +0`, `wb_tx_head` unchanged, `:1062` walk never entered — **`*** DF-4270 WEDGE CONFIRMED ***`** |

The compiled `wb_start` disassembly (`wb_start.disasm`) confirms the
binary is faithful to the source here (unlike DF-4268's case): MGETHDR-fail
jumps straight back to the caller flow without freeing anything; MCLGET-fail
calls `m_freem(m_new)` only; `m_freem(m_head)` exists solely on the
success path after `m_copydata`. So the leak is real in the shipped binary.

Impact: per-failure leak of a full packet chain (leak spiral toward kernel
memory exhaustion — the same pressure that causes the failures worsens
them), an OWN-marked unterminated fragment chain started on the NIC via
TXSTART (on real hardware the chip DMA-walks stale `wb_next` words past
the one-past `frag[16]`), and TX wedged until the 5 s watchdog. Trigger
for an unprivileged local user: ordinary TX traffic shaped into >16-fragment
chains under cluster pressure (16 small writes per packet is enough shape;
the pressure can be the system's own).

## Fix validation — kernel #1 (fix_validated.diff, combined 3-fix build)

`go4270` re-run on the patched kernel (run.kld_fixed.log):

| observable | stock #0 | fixed #1 |
|---|---|---|
| mbuf delta across `wb_start` | 0 (leak +17) | **−17** (fix frees the chain) |
| `LEAK` accounting | 17 | **0** |
| frags with OWN set | 16/16 | **0/16** (statuses zeroed) |
| `WB_TXSTART` | 0xffffffff | **0** (never issued) |
| `wb_tx_head` / wedge | set, wedged | **NULL** — nothing armed; no wedge possible |
| `if_timer` | 5 | **0** |

Accounting closes exactly: final `mbtypes = baseline − 17 (chain freed by
the fix) − 64 (RX ring freed by wb_stop)`.

fix_status: **fixed** (baseline_reproduced=1, patched_reproduced=0).

## Rig incidents (documented, not the finding)

1. First baseline run's leak arithmetic under-counted by exactly the chain
   (baseline read AFTER chain build); the underlying data was correct and
   the corrected formula prints the LEAK CONFIRMED marker. No kernel
   misbehaviour was involved; v1 cleanup freed the genuinely-leaked chain
   (no double-free — confirmed by exact final accounting).
2. First FIXED-kernel run: the rig dispatched `wb_txeof.part.4` unguarded;
   on the fixed kernel `wb_tx_head` is NULL and the `.part.4` clone's NULL
   guard lives in the CALLER (`wb_intr` checks before each call — visible
   in its disassembly), so the rig faulted AFTER all fixed-kernel evidence
   had printed (`panic_rig_txeof_guard.txt`). Guard added (mirroring
   wb_intr); the clean re-run SURVIVED.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
