# DF-4068 — VERDICT

**Status: inconclusive (hardware-gated) · source-level: certain · severity: Medium · bucket: dos (livelock)**

## Root cause

The Tigon firmware publishes completion progress by DMA-writing 32-bit
index words into host memory (`struct ti_producer.ti_idx`,
sys/dev/netif/ti/if_tireg.h:485-488; pointers handed to the NIC at
if_ti.c:1248-1249, 1323-1324, 1357-1358).  All three interrupt-path
walks use these words as *raw* loop terminators:

```c
while (sc->ti_rx_saved_considx != sc->ti_return_prodidx.ti_idx)   /* if_ti.c:1629 */
while (sc->ti_tx_saved_considx != sc->ti_tx_considx.ti_idx)       /* if_ti.c:1729 */
while (sc->ti_ev_saved_considx != sc->ti_ev_prodidx.ti_idx)       /* if_ti.c:511  */
```

The driver-side indices are `uint16_t` (if_tireg.h:1141-1143) and
`TI_INC(x, y) = (x + 1) % y` (if_tireg.h:965) keeps them inside
[0, 2048) / [0, 512) / [0, 256).  A DMA'd word whose value lies
outside that modular range — high bit set, or simply >= ring size as a
raw integer (e.g. 4096 = 2×2048, 0x8000, 0xFFFFFFFF) — can never equal
the consumer, so the loop spins forever.

Where it spins: inside `ti_intr()` *after* host interrupts were masked
(`CSR_WRITE_4(sc, TI_MB_HOSTINTR, 1)`, if_ti.c:1778) and while holding
`ifp->if_serializer` (intr set up with the ifnet serializer,
if_ti.c:1567-1569).  Effect: the servicing CPU never returns from the
IRQ — hard livelock / soft lockup; the serializer is never released, so
anything else touching the interface stalls too.  `ti_txeof` compounds
it by decrementing `sc->ti_txcnt` (int, if_ti.c:1755) once per spin.

This is the DF-3761 (alc unbounded-walk) class: a completion walk with
no work bound, keyed on device-fed state.  Note the alc instance was
"perpetually non-empty ring"; here the stronger form holds — the
termination condition itself is unsatisfiable, so even ONE poisoned
word (no flood needed) wedges the loop permanently.

## Why the "firmware always writes mod ring size" assumption fails

Nothing in the driver or the DMA contract enforces the range: the word
is 32 bits, the modulus is 2048/512/256.  A firmware bug (index
corruption, ring-size misconfiguration after a partial reset), a
malicious peripheral, or PCIe/Thunderbolt DMA tampering writes an
out-of-range value once and the CPU is wedged.  Same trust boundary as
DF-4066/DF-4067 — the kernel must bound its own work regardless of
what the device claims.

## Verification narrative

- `trigger.c` (guest, RC=0): simulates both the benign walks (producer
  inside the modular range — terminate in 37/511/3 iterations) and the
  poisoned ones (prod_raw = 0x8000 / 4096 / 0x10000 / 0xFFFFFFFF —
  NEVER terminates within a budget of 8× ring size; run.log).
- env.txt: no Tigon-family PCI device on the audit guest, so the real
  DMA'd word cannot be instantiated; per family convention the runtime
  verdict is `inconclusive` with source-level confidence `certain`.

## Fix

`fix.diff`: wrap each of the three walks with a ring-sized iteration
budget (`for (int budget = TI_..._RING_CNT; cond && budget > 0;
budget--)`).  This is the standard fix shape for this class (it is
what FreeBSD bounds alc(4)/msk(4) walks with): a poisoned word then
costs at most one ring's worth of work per interrupt instead of the
CPU.  `git apply --check` clean; patched module builds on the guest
with `-Werror` (fix_build.log FIXBUILD_f3_RC=0); runtime validation
needs hardware (fix_status=not_testable).

## Cleared in this pass (adjacent walk/bound surfaces)

- `ti_handle_events` array indexing is masked (consumer mod 256) — the
  event-ring *access* is in-bounds; only termination is unbounded.
- `ti_start`'s `prodidx` comes from a host-written PCI register
  (TI_MB_SENDPROD_IDX), not DMA — values are driver-maintained in
  [0,512).
- `ti_encap` TX walk is bounded by the 16-descriptor safety margin and
  masked indices (if_ti.c:1844-1895).
- Tigon-1 banked TX window mapping (WINBASE remap + `idx % 128`) is
  in-bounds in both txeof and encap (if_ti.c:1733-1748, 1846-1859).

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