# DF-3723 — VERDICT

**Status: inconclusive at runtime (no X550 hardware on the guest), source-certain, bound-math violation demonstrated on the guest, fix compiles clean.**

## Root cause (path:line into sys/dev/netif/ix/ixgbe_x550.c)

`ixgbe_calc_checksum_X550(hw, buffer, buffer_size)` is the exported
shared-code API for checksumming a caller-held NVM image. In the
`buffer != NULL` path its helper `ixgbe_checksum_ptr_x550()`:

1. **ixgbe_x550.c:3358** — `if (buffer_size < ptr) return IXGBE_ERR_PARAM;`
   then **:3360** `local_buffer = &buffer[ptr];` and **:3368**
   `length = local_buffer[0];`. When `buffer_size == ptr`, `buffer[ptr]` is
   exactly one word past the end — the check must be `<=` (off-by-one).
2. **ixgbe_x550.c:3376** — `if (buffer && ((u32)start + (u32)length > buffer_size))`
   validates `start + length` **against the buffer base**, but the reads at
   **:3394** are `local_buffer[i] == buffer[ptr + i]` for
   `i ∈ [start, start+length)`. The `ptr` base offset is missing from the
   bound, so the walk can extend `ptr` words past the end. Concrete passing
   case: `buffer_size=100, ptr=90, buffer[90]=50` → guards pass (91 ≤ 100),
   loop reads `buffer[91..140]` → 41 words OOB (demo output in `run.log`).
3. **ixgbe_x550.c:3430** — `if (buffer_size < IXGBE_EEPROM_LAST_WORD)` but the
   loop at **:3439** is `for (i = 0; i <= IXGBE_EEPROM_LAST_WORD; i++)` —
   with `buffer_size == 0x41` exactly, `local_buffer[0x41]` is one word OOB
   (second off-by-one).

DF-3670 family (NVM-walk bounds), X550 variant: the defect is confined to the
caller-buffer path. The **live** path taken at attach time
(`if_ix.c:615` → `validate_eeprom_checksum` → `calc_eeprom_checksum` →
`ixgbe_calc_checksum_X550(hw, NULL, 0)`) is **safe**: the NVM-walk there is
bounded by `pointer >= hw->eeprom.word_size` (:3454-3456) and
`(ptr + length) >= hw->eeprom.word_size` (:3371-3373), and the 256-word stack
chunk buffer (:3342) is correctly refilled (:3380-3393) — verified by
trace, no OOB.

## Why only Low

- **No in-tree caller passes a non-NULL buffer.** The only caller is
  `ixgbe_calc_eeprom_checksum_X550()` (ixgbe_x550.c:3488-3491) which passes
  `NULL, 0`; `ixgbe_api.c` exposes no `ixgbe_calc_checksum(hw, buf, size)`
  wrapper in this tree, and `if_ix.c` never touches
  `eeprom.ops.calc_checksum` with a buffer. Dead code in the kernel as built
  (compiled in, never executed) — same posture as DF-1862 (latent).
- **Upstream-identical defect**: verified 2026-09-15 against Linux master
  (`drivers/net/ethernet/intel/ixgbe/ixgbe_x550.c`,
  `ixgbe_checksum_ptr_x550`, same `buffer_size < ptr` and
  `start + length > buffer_size`) and FreeBSD head
  (`sys/dev/ixgbe/ixgbe_x550.c:3325,3343`). Not a DragonFly regression.
- If a future caller (e.g. an NVM-tool ioctl or ported FreeBSD
  `ixgbe_ndd`/eeprom-dump sysctl) passes a user-sized buffer, this becomes a
  kernel OOB **read** whose 16-bit checksum return is a (weak) info-leak
  oracle over adjacent kernel memory.

## Verification performed

| Step | Result |
|---|---|
| Guest hardware check (`env.txt`) | no Intel X550/82598/82599 device on PCI — HAL paths cannot execute |
| On-guest bound-math demo (`run.log`) | shipped guards accept `ptr=90,len=50,bs=100` → reads buffer[91..140]; `buffer_size==ptr` off-by-one accepted; fixed guards reject both |
| Fix compile (`fix_build.log`) | `fix.diff` applied to guest `/usr/src` copy; full `if_ix.ko` module rebuild clean with `-Werror`; patch reverted after |
| Repo `sys/` tree | never modified (git clean) |

## Recommended fix

`fix.diff` in this pack (3 hunks):
- :3358 `buffer_size < ptr` → `buffer_size <= ptr`
- :3376 add `(u32)ptr +` to the bound
- :3430 `buffer_size < IXGBE_EEPROM_LAST_WORD` → `< IXGBE_EEPROM_LAST_WORD + 1`

Worth carrying upstream (Linux/FREEBSD share the bug).

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