# DF-3990 VERDICT — source-level certain, runtime hardware-gated (inconclusive)

## Root cause (path:line)

### (1) ale_rxeof work-unbounded walk — if_ale.c:2153-2254

```c
2176: prod = *rx_page->cmb_addr;          /* controller-DMA'd CMB word */
2177: for (prog = 0; ; prog++) {
2178:         if (rx_page->cons >= prod)
2179:                 break;
```

The only termination condition is the device-fed producer offset, refreshed
from the NEW page's CMB on every page flip (`ale_rx_update_page` :2091).
There is no per-interrupt frame budget: `ALE_PROC_MIN/ALE_PROC_MAX/
ALE_PROC_DEFAULT` are defined at if_alevar.h:118-121 and referenced NOWHERE
(grep confirms) — dead knobs, exactly the DF-3934 (age) pattern. Each
iteration does `m_devget` + `ifp->if_input` while holding
`ifp->if_serializer` (ale_intr is serialized, :1969). A line-rate small-
frame flood — precisely the workload the driver's own comment at :2224
calls its bottleneck — keeps `prod` ahead of `cons` indefinitely:
the interrupt handler never returns, the serializer is never released,
ifconfig/ioctl/watchdog on ale0 wedge, and the CPU spins in softint
context. Remote-triggerable DoS/livelock (no malformed packets needed —
just rate).

### (2) ale_txeof unmasked 16-bit producer — if_ale.c:2026-2047

```c
2026: prod = CSR_READ_2(sc, ALE_TPD_CONS_IDX);   /* 16-bit device reg */
2027: cons = sc->ale_cdata.ale_tx_cons;
2032: for (prog = 0; cons != prod; prog++, ALE_DESC_INC(cons, ALE_TX_RING_CNT)) {
2034:         if (sc->ale_cdata.ale_tx_cnt <= 0)
2035:                 break;
...
2042:         bus_dmamap_unload(sc->ale_cdata.ale_tx_tag, txd->tx_dmamap);
2044:         m_freem(txd->tx_m);
```

`prod` is not masked into the 256-entry ring. A device-fed value outside
0..255 makes `cons != prod` unconditionally true; the walk is bounded only
by `ale_tx_cnt` reaching 0, freeing the mbuf AND unloading the dmamap of
every queued EOP descriptor — reclaiming buffers whose descriptors the NIC
may still own (premature m_freem → NIC DMAs from freed/reused memory →
info leak to the wire, or double-unload class confusion). Same fold as
DF-3934's "16-bit CMB producer indices not masked to ring sizes ...
serializer-held livelock DoS and TX over-reclaim".

## Verification on the guest (honest)

- trigger.c simulates both walks; builds and runs on the guest (run.log
  RC=0: 10^6 iterations with no budget consumed; txeof walk frees all 40
  queued frames on an out-of-ring prod).
- Hardware-gated: no 1969:1026 device, no hw.ale sysctl; if_ale code is in
  the kernel but ale_intr/ale_rxeof never execute. Inconclusive per family
  convention.

## Impact

(1) is REMOTELY triggerable on real hardware (flood) — interface + CPU
wedge; Medium. (2) requires a faulty/malicious controller — over-reclaim /
leak; folded into the same finding per the DF-3934 precedent.

## Fix

fix.diff: (1) wire up ALE_PROC_DEFAULT as a per-interrupt budget in
ale_rxeof (break; INTR_RX_PKT / IM timer re-arms — caveat: if the device
needs a kick for backlog, a follow-up should re-arm the moderation timer
explicitly); (2) mask `CSR_READ_2(ALE_TPD_CONS_IDX) & (ALE_TX_RING_CNT-1)`.
git-apply clean; not runtime-validable on this guest.

## 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.
