# DF-4081 — oce_tso_setup parses IP/TCP headers before m_pullup (sibling of
# DF-1373, different function): reads past m_len of the first mbuf

File: sys/dev/netif/oce/oce_if.c (pass 2)
Class: parsing of attacker-influenced data (out-of-contract mbuf reads)

## Root cause

oce_tso_setup() (oce_if.c:1021-1087) — reached from oce_tx() at oce_if.c:845
whenever `m->m_pkthdr.csum_flags & CSUM_TSO` — dereferences protocol headers
*before* the m_pullup at line 1080:

- 1046: `eh = mtod(m, struct ether_vlan_header *);` (18-byte read, no length
  check against m->m_len)
- 1047-1052: `eh->evl_encap_proto` / `eh->evl_proto` reads
- 1058/1068: `ip` / `ip6` cast at `m->m_data + ehdrlen` — field reads
- 1061/1071: `th = (struct tcphdr *)((caddr_t)ip + (ip->ip_hl << 2));`
- 1063/1073: **`th->th_off`** dereferenced — offset ehdrlen+ihl*4 (up to 74)
  with no guarantee that many bytes are in the first mbuf (m_len).
- only THEN: 1080 `m = m_pullup(m, total_len);`

CSUM_TSO frames are produced by every TCP connection over oce (TSO is in
if_capabilities and if_capenable is set to capabilities at attach,
oce_if.c:1697-1707), and the IP/TCP header split across mbufs is legal for
locally-generated chains (e.g. unusual socket-buffer write patterns). When
the TCP header is not (fully) in the first mbuf, `th->th_off` is read past
m_len — stale cluster bytes within the MCLBYTES allocation (not an
allocation-boundary overread; the same class DF-1373 established for
oce_check_ipv6_ext_hdr). Consequence: garbage `total_len` → m_pullup fails →
packet silently dropped (ENXIO at oce_if.c:849-852), or pullup to a wrong
size. Correctness/robustness bug more than disclosure.

## Fix

Pull up `sizeof(*eh) + max(sizeof(struct ip6_hdr), sizeof(struct ip)) + 
sizeof(struct tcphdr)` (or simply ETHER_HDR_LEN+sizeof(struct ip6_hdr)+64)
*before* reading any header fields, and re-derive pointers afterwards — the
same fix pattern as DF-1373.

## Verification status

UNTESTED — hardware-gated (no oce NIC in QEMU, TSO path cannot be exercised).
