# DF-4645 — ixgbe posted-mbx wrappers bypass size discipline (twin of DF-4638)

**Severity: Info (latent / dead-in-tree) · bucket: memcorrupt · confidence: certain (static)**

## What

`sys/dev/netif/ix/ixgbe_mbx.c` has the exact twin of the e1000_mbx shape filed as
DF-4638 (ig_hal):

- `ixgbe_read_mbx()` (ixgbe_mbx.c:47-62) clamps `size` to `mbx->size`
  (lines 55-56) before dispatching to `mbx->ops.read`.
- `ixgbe_write_mbx()` (ixgbe_mbx.c:73-88) *rejects* `size > mbx->size`
  (lines 80-83) before dispatching to `mbx->ops.write`.

But the **posted** wrappers, which are what real VF drivers call, dispatch
directly to the raw ops with **no size validation at all**:

- `ixgbe_read_posted_mbx()` (ixgbe_mbx.c:224-241) → `mbx->ops.read(hw, msg,
  size, mbx_id)` at **line 238** — no clamp.
- `ixgbe_write_posted_mbx()` (ixgbe_mbx.c:253-273) → `mbx->ops.write(hw, msg,
  size, mbx_id)` at **line 266** — no reject.

The raw ops themselves do no validation either:
`ixgbe_write_mbx_vf` (425-456, loop at 445-446), `ixgbe_read_mbx_vf`
(467-493, loop at 482-483), `ixgbe_write_mbx_pf` (665-695, loop at 683-684),
`ixgbe_read_mbx_pf` (708-733, loop at 722-723) all trust `size` verbatim.
`mbx->size` is always `IXGBE_VFMAILBOX_SIZE` == 16 words / 64 bytes
(ixgbe_mbx.h:40; set at ixgbe_mbx.c:510, 755).

## Consequence if a caller ever passes size > 16

Write path (`for (i = 0; i < size; i++)
IXGBE_WRITE_REG_ARRAY(hw, IXGBE_PFMBMEM(vf), i, msg[i])`):
1. **kernel OOB read** of the caller's `msg[]` buffer beyond `size`-words is
   not the issue (caller-sized) — but any caller passing a fixed 16-word
   buffer with a bigger `size` gets an OOB read of adjacent kernel memory
   *written into device-visible MMIO*;
2. `IXGBE_PFMBMEM(vf) = 0x13000 + 64*vf` (ixgbe_type.h:455) is a 64-byte
   window — words 16+ of the loop **spill into the next VF's mailbox**:
   cross-VF mailbox corruption / message forgery on any future SR-IOV use.

Read path: the loop reads past the 16-word window (next VF's mailbox / other
registers) into the caller's buffer — cross-VF data disclosure.

## Why Info and not higher

The DragonFly `ix` driver has **no SR-IOV/VF support and no caller of any
mailbox message op** — the entire message engine is dead code in-tree:

- `if_ix.c` touches the mbx subsystem exactly once: `hw->mbx.ops.
  init_params(hw)` (if_ix.c:535-536).
- No `ixv` driver exists in the tree; grep for `read_posted|write_posted`
  over `sys/` finds zero callers outside `ixgbe_mbx.c` itself (see
  `static-proof.txt`).
- `ixgbe_init_mbx_params_pf` (741-770) is registered only by ixgbe_82598.c…
  actually only 82599/x540/x550 attach code (ixgbe_82599.c:388,
  ixgbe_x540.c:148); PF-side `timeout = 0` (line 752) additionally makes
  `ixgbe_poll_for_msg/ack` return `IXGBE_ERR_MBX` immediately, so even a
  hypothetical `read_posted` caller would never reach `ops.read` on the PF
  side.

This matches DF-4638's Info classification for the ig_hal twin (there the VF
path at least exists in igb; here it does not).

## Not runnable — why there is no PoC

- Guest has **no ixgbe-class PCI device at all** (virtio-only; see `env.txt`).
- Even with hardware, **no in-tree code path invokes the posted wrappers**, so
  there is no trigger to point at; the finding is a latent API-contract defect
  for the day SR-IOV/VF support lands (or an out-of-tree/ported caller).
- Verification performed instead: exhaustive static caller analysis
  (`static-proof.txt`) + hardware-absence capture (`env.txt`).

## Fix

`fix.diff` — mirror the non-posted discipline inside the posted wrappers
(clamp on read, reject on write). Never applied to `sys/` (read-only audit
target).
