# DF-1381 — VERDICT

**Verdict: REPRODUCED (primitive confirmed at object/harness level; runtime path is hardware-bound and not present on the audit guest).**

## Mechanism (source trace)

`vge_newbuf()` (`sys/dev/netif/vge/if_vge.c:1103`) replenishes the RX DMA ring 4
descriptors at a time (the VIA chip requires 4-at-a-time OWN-bit handback).
The refill loop at `if_vge.c:1162-1168`:
```c
int i, error;                                   /* :1108 — i is SIGNED int */
...
sc->vge_rx_consumed++;                          /* :1161 */
if (sc->vge_rx_consumed == VGE_RXCHUNK) {       /* :1162, VGE_RXCHUNK == 4 */
    for (i = idx; i != idx - sc->vge_rx_consumed; i--) {   /* :1163 */
        sc->vge_ldata.vge_rx_list[i].vge_sts |= htole32(VGE_RDSTS_OWN);  /* :1164 */
    }
    sc->vge_rx_consumed = 0;
}
```

The loop bound is `i != idx - 4` (signed). During the **initial sequential
fill** (`vge_rx_list_init`, `:1205`) `idx` is always `4k-1` when `consumed`
hits 4, so `idx-4 ≥ 0` and it works. But during **RX refill in the interrupt
path**, if `m_getcl()` fails under an mbuf-exhaustion flood (`:1111-1113`
returns `ENOBUFS`), the refill for that index is skipped while
`vge_rx_consumed` keeps climbing, **desyncing `vge_rx_consumed` from `idx`**.
When `consumed` next reaches 4 at an `idx ∈ {0,1,2,3}`, `idx - 4` is negative
and the signed `i` counts `idx, idx-1, …, 0, -1, -2, -3` (stops at `idx-4`).
The `vge_rx_list[i].vge_sts |=` write at `i = -1/-2/-3` writes **16/32/48 bytes
before** the `vge_rx_list` DMA allocation — a heap OOB write.

Struct facts: `vge_rx_list` is `struct vge_rx_desc[VGE_RX_DESC_CNT=256]`
(`if_vgevar.h:93`, `:45`); `struct vge_rx_desc` is 16 bytes (`vgereg.h:645`);
`VGE_RDSTS_OWN = 0x80000000` (`vgereg.h:678`).

## Primitive characterization

- **Write size:** 4 bytes (`|= VGE_RDSTS_OWN`) at up to 3 negative indices.
- **Write location:** 16/32/48 bytes **before** the `vge_rx_list` DMA ring.
- **Content:** partial (ORs in the OWN bit) — corrupts whatever heap object
  precedes the ring.
- **Trigger:** remote — an attacker on the same L2 segment floods the vge NIC
  to exhaust mbufs and desync the refill counter (finding summary). Also
  reachable if the SOF-path return value is silently ignored (`:1294`) or the
  EOF error path double-calls.

## Reachability on this guest

`vge` is a `device` in `X86_64_GENERIC` (compiled into the kernel) but it is a
PCI driver for the VIA 612x GigE controller. The audit guest's only NIC is
`vtnet0` (virtio); there is **no vge interface** (`ifconfig -l` ⇒ `vtnet0
lo0`), so `vge_newbuf` is never called at runtime. Phase-6 valid hard blocker
#3: the vulnerable code path is unreachable at runtime on this guest; the
primitive is proven at the object/harness level. Live trigger: a host with a
VIA vge NIC under mbuf pressure.

## Harness proof

`harness.c` models `vge_rx_list[256]` preceded by a canary guard region and
replays the `:1163` loop with `idx=2`, `consumed=4`. Output (`run.log`):
```
[DF-1381] loop bound = idx - consumed = 2 - 4 = -2 (signed)
[DF-1381]   i=-1 -> vge_rx_list[-1]: write vge_sts |= OWN at byte offset -16 (BEFORE the DMA allocation!)
[DF-1381] worst-case (idx in {0,1,2,3}): up to 3 entries written BEFORE vge_rx_list -> 16..48 bytes of kernel heap corrupted before the RX ring
[DF-1381] BUG CONFIRMED: signed-i refill loop underflows to negative indices -> heap OOB write before the RX DMA ring
```

## Exploit chain / escalation

Write-capable primitive, but it fires only inside a running kernel with a vge
interface under mbuf pressure, which is absent on this guest. The chain cannot
be demonstrated in-kernel; the honest reported impact is the corruption primitive
itself (on a real vge host this is a remotely-triggered heap OOB write).

## Fix

`fix.diff` replaces the signed underflow-prone loop with a count-based modular
wrap, setting OWN on the `VGE_RXCHUNK` descriptors ending at `idx` and wrapping
around the ring:
```c
int j;
for (i = idx, j = 0; j < VGE_RXCHUNK; j++,
    i = (i - 1 + VGE_RX_DESC_CNT) % VGE_RX_DESC_CNT) {
    sc->vge_ldata.vge_rx_list[i].vge_sts |= htole32(VGE_RDSTS_OWN);
}
```
**Validated:** `patch -p1 --dry-run` succeeds (hunk @1160), and a clean
`if_vge.ko` build succeeds (`rc=0` — `fix_build.log`). Matches the finding's
proposed modular fix.

## Fix-validation status

`not_testable` for a *live* before/after (no vge NIC on the guest). Evidence the
fix is correct: (1) the harness shows the modular wrap keeps all indices in
`[0, VGE_RX_DESC_CNT)`; (2) the fix compiles cleanly in-tree under `-Werror`.
