# DF-4108 — vge(4): TX short-frame pad path writes one fragment beyond `vge_frag[7]` when a sub-60-byte frame arrives with 7 DMA segments (maxsegs off-by-one)

File: `sys/dev/netif/vge/if_vge.c` (pass 2)
Class: intra-ring OOB write past the descriptor's fragment array
(CWE-787, bounded to the ring allocation) → next descriptor's
`vge_sts`/`vge_ctl` corruption.
Hardware gate: VT612x NIC (absent on the audit guest). Reachability
additionally requires a pathological TX mbuf shape; see confidence note.

## The defect

`vge_encap` allows the full fragment array: if_vge.c:1622
`arg.vge_maxsegs = VGE_TX_FRAGS;` (= 7, if_vgereg.h:584,606 — the array is
`vge_frag[VGE_TX_FRAGS]`).

The map callback `vge_dma_map_tx_desc` (if_vge.c:690-706) fills frag[0..nseg-1]
(the guard at 676 rejects only `nseg > 7`), then for short frames:

```c
if (ctx->vge_m0->m_pkthdr.len < VGE_MIN_FRAMELEN) {   /* < 60 bytes */
        f = &d->vge_frag[i];        /* i == nseg == 7  -> frag[7] OOB */
        f->vge_buflen = ...;        /* offset 70-71 of the descriptor */
        f->vge_addrlo = segs[0].ds_addr;   /* offset 64-67 */
        f->vge_addrhi = ...;        /* offset 68-69 */
```

`struct vge_tx_desc` = {u32 sts; u32 ctl; frag frag[7]} = 64 bytes, so
frag[7] lands at ring offset 64..71 = **the next descriptor's vge_sts
(bytes 0-3, fully overwritten with segs[0].ds_addr) and vge_ctl (bytes 4-7)**.
If the next descriptor is chip-owned (in flight), its status/owner
accounting is corrupted → `vge_txeof` may then reclaim the wrong
descriptor / leak or double-free its mbuf (`m_freem` on a still-pending
slot frees an mbuf the chip is still reading).

## Reachability caveat (why Low / likely)

A frame under 60 bytes spanning exactly 7+ DMA segments is not producible
by the ordinary TCP/IP output path (segment headers alone exceed 60 bytes
well before 7 segments); it requires pathological mbuf chopping (e.g. via
carefully constructed sendmsg/UDP with tiny buffers hitting coincident
page boundaries, or a BPF write, which is privileged). The bounds-contract
defect itself is certain: the pad write needs `nseg+1 <= VGE_TX_FRAGS` but
the guard admits `nseg == VGE_TX_FRAGS`.

## Fix
Reserve one fragment for the pad: `arg.vge_maxsegs = VGE_TX_FRAGS - 1;`
(matching FreeBSD's later fix) — see fix.diff.
