β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3670

ixgbe_calc_eeprom_checksum_generic walks NVM sections on NVM-resident pointer/length with u16 index and no word_size bounds β€” upstream FreeBSD fix missing in DragonFly

Summary

The checksum walk reads 'pointer' and 'length' from the NVM and iterates 'for (j = pointer + 1; j <= pointer + length; j++)' with u16 j - no validation against hw->eeprom.word_size (itself derived from the hardware EEC.SIZE field). Crafted/corrupt NVM makes the walk wrap j at 65536 and cover unintended words: the attach-time validate then wrongly fails (crafted-NVM NIC-disable DoS) or wrongly passes, and ixgbe_update_eeprom_checksum_generic writes a wrong checksum into NVM word 0x3F - persistent adapter-NVM corruption. Function-level diff against FreeBSD's current shared code proves upstream added exactly these bounds and DragonFly's 2017 snapshot lacks them. Fix: import the upstream bounds (pointer >= word_size; length >= word_size - pointer; u32 j).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3670 Β· 8 files
FileTypeDescriptionSize
README.md β€” 2.4 KB ↓ raw
VERDICT.md β€” 3.2 KB ↓ raw
fix.diff β€” 765 B view raw
build_baseline.log β€” 15.8 KB view raw
build_patched.log β€” 15.8 KB view raw
env.txt β€” 1.0 KB view raw
manifest.json β€” 639 B view raw
verdict.json β€” 2.5 KB view raw

DF-3670 β€” ixgbe_calc_eeprom_checksum_generic: NVM pointer/length walk lacks upstream bounds, u16 index wrap

File: sys/dev/netif/ix/ixgbe_common.c Β· Severity: Low Β· Class: missing-upstream-fix / NVM-trust correctness

Defect

ixgbe_calc_eeprom_checksum_generic() (ixgbe_common.c:2211-2262) walks EEPROM sections whose pointer (2233) and length (2242) are read from the NVM itself, using a u16 j loop index:

u16 j;                                        /* 2214 */
...
for (j = pointer + 1; j <= pointer + length; j++)   /* 2250 β€” u16 wrap */
        hw->eeprom.ops.read(hw, j, &word);

There is no check that pointer < word_size or that pointer + length < word_size. With word_size derived from the hardware-reported EEC.SIZE field (ixgbe_common.c:1277-1280 β€” up to 1 << (15 + IXGBE_EEPROM_WORD_SIZE_SHIFT)), a crafted/corrupted NVM (or a NIC reporting a large EEC.SIZE) makes the walk wrap j at 65536 and traverse unintended words; the checksum is then computed over the wrong set (checksum = IXGBE_EEPROM_SUM - checksum, 2259). Consumers:

  • attach-time validation ixgbe_validate_eeprom_checksum (if_ix.c:615) β€” wrong pass/fail decision from crafted NVM (NIC disabled or bad NVM accepted),
  • ixgbe_update_eeprom_checksum_generic (2320) writes the wrong value into NVM word 0x3F β€” persistent NVM corruption (not called by if_ix.c, exposed via ixgbe_api for fw-update paths).

Upstream divergence (proven)

FreeBSD's copy of this shared code added exactly these bounds and widened j (function-level diff against FreeBSD head sys/dev/ixgbe/ixgbe_common.c, performed during this audit):

        if (pointer == 0xFFFF || pointer == 0)
            continue
+       if (pointer >= hw->eeprom.word_size) {
+           DEBUGOUT("EEPROM pointer outside word range\n");
+           return IXGBE_ERR_EEPROM;
+       }
        ...
        if (length == 0xFFFF || length == 0)
            continue
+       if (length >= hw->eeprom.word_size - pointer) {
+           DEBUGOUT("EEPROM section outside word range\n");
+           return IXGBE_ERR_EEPROM;
+       }
-       for (j = pointer + 1; j <= pointer + length; j++) {
+       u32 j; ... word_end = (u32)pointer + length;

DragonFly (2017 shared-code snapshot) is missing that fix.

PoC / verification

Hardware-gated (NVM contents + attach path). No runnable PoC; guest has no ixgbe device (env.txt). fix.diff mirrors the upstream bounds and was compile-validated on the guest's md5-identical /usr/src (build_patched.log, -Werror clean).

VERDICT.md
↓ download raw

DF-3670 β€” VERDICT

Status: inconclusive (runtime) / source-level certain. Impact class: wrong NVM checksum decisions from crafted NVM (attach-time DoS of the NIC / bad-NVM acceptance; persistent NVM corruption via update path). Hardware-gated.

Root cause

ixgbe_calc_eeprom_checksum_generic (sys/dev/netif/ix/ixgbe_common.c:2211): the section walk is driven entirely by NVM-resident values:

  • pointer read at 2233 from EEPROM words IXGBE_PCIE_ANALOG_PTR..IXGBE_FW_PTR
  • length read at 2242 at that pointer
  • loop at 2250 for (j = pointer + 1; j <= pointer + length; j++) with u16 j (2214) β€” no bound of pointer/length against hw->eeprom.word_size, which itself is hardware/NVM-reported (ixgbe_init_eeprom_params_generic, 1277-1280: word_size = 1 << (eeprom_size + IXGBE_EEPROM_WORD_SIZE_SHIFT) from EEC.SIZE).

Failure modes with crafted NVM:

  1. pointer/length chosen so pointer + length >= 65536: j wraps at 65536 while the (int-promoted) comparison still holds β€” the walk visits unintended words; with word_size > 65536 (EEC.SIZE lie) reads "succeed" and the checksum covers the wrong region.
  2. Any pointer >= word_size: per-word eeprom.ops.read fails β†’ attach aborts (ixgbe_validate_eeprom_checksum at if_ix.c:615 β†’ "The EEPROM Checksum Is Not Valid" β†’ ENXIO): crafted-NVM NIC-disable DoS.
  3. ixgbe_update_eeprom_checksum_generic (2320-2346) writes the wrongly computed checksum to NVM word IXGBE_EEPROM_CHECKSUM β€” persistent corruption of the adapter NVM (persisted across reboots).

Upstream divergence β€” the fix exists in FreeBSD, missing here

Function-level diff against FreeBSD sys/dev/ixgbe/ixgbe_common.c (fetched during this audit to /tmp/opencode/fx_ixgbe_common.c) shows upstream added:

if (pointer >= hw->eeprom.word_size) { ... return IXGBE_ERR_EEPROM; }
if (length  >= hw->eeprom.word_size - pointer) { ... return IXGBE_ERR_EEPROM; }
u32 j; u32 word_end = (u32)pointer + length;

DragonFly's file (md5 1a6c0a6673928d2b95a53dfd55c308d2, 2017 shared-code snapshot) predates it. fix.diff in this pack imports the upstream bounds (minimal form, keeping the local code style).

Why inconclusive at runtime

The walk executes during ix_attach β†’ ixgbe_validate_eeprom_checksum (if_ix.c:614-619). The audit guest has no ixgbe PCI device (env.txt: pciconf shows only i440FX-bridge Intel devices; dev.ix.* count 0), so the path never runs. Crafting an NVM requires physical access to the adapter (or a hostile NIC), placing this in the malicious-hardware trust class β€” consistent with DF-3642 (Low, certain, same "flash/NVM correctness" family).

Fix validation

fix.diff applied to guest's md5-identical /usr/src; if_ix.ko (containing ixgbe_common.c) rebuilt with -Werror, zero errors (build_patched.log). Runtime before/after needs hardware β€” not available.

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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

Compile-validated only; runtime validation needs hardware.

build_baseline.log / build_patched.log (-Werror clean with fix).
↓ fix.diffper-fix-DF-3670

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (walk-through + upstream diff excerpt); build_patched.log; env.txt

PoC changes

New pass-2 finding, no seed. fix.diff authored from upstream FreeBSD's bound checks, minimized to local style.

Verified recommended fix

Import upstream bounds: reject pointer >= word_size and length >= word_size - pointer before the walk (fix.diff).

Verdict

Source-level certain (upstream divergence proven by diff): ixgbe_calc_eeprom_checksum_generic (ixgbe_common.c:2211-2262) walks NVM sections using NVM-resident pointer (2233) and length (2242) with a u16 loop index (2214, loop at 2250) and no bound against hw->eeprom.word_size (itself EEC.SIZE-derived, 1277-1280). FreeBSD's copy of the same shared code added pointer/length range checks and u32 indexing; DragonFly's 2017 snapshot lacks them. Crafted/corrupt NVM -> checksum computed over wrong words: attach-time validate (if_ix.c:615) wrongly fails (NIC-disable DoS) or wrongly passes; update path (ixgbe_common.c:2320-2346) writes a wrong checksum into NVM (persistent corruption). Hardware-gated: guest has no ixgbe device (env.txt). fix.diff (upstream bounds) compile-validated -Werror-clean on md5-identical guest source.