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)
PoC verification
Evidence pack
findings/poc/DF-3670 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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).
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:
pointerread at 2233 from EEPROM wordsIXGBE_PCIE_ANALOG_PTR..IXGBE_FW_PTRlengthread at 2242 at that pointer- loop at 2250
for (j = pointer + 1; j <= pointer + length; j++)withu16 j(2214) β no bound ofpointer/lengthagainsthw->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)fromEEC.SIZE).
Failure modes with crafted NVM:
pointer/lengthchosen sopointer + length >= 65536:jwraps at 65536 while the (int-promoted) comparison still holds β the walk visits unintended words; withword_size > 65536(EEC.SIZE lie) reads "succeed" and the checksum covers the wrong region.- Any
pointer >= word_size: per-wordeeprom.ops.readfails β attach aborts (ixgbe_validate_eeprom_checksum at if_ix.c:615 β "The EEPROM Checksum Is Not Valid" β ENXIO): crafted-NVM NIC-disable DoS. ixgbe_update_eeprom_checksum_generic(2320-2346) writes the wrongly computed checksum to NVM wordIXGBE_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_testableCompile-validated only; runtime validation needs hardware.
build_baseline.log / build_patched.log (-Werror clean with fix).
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.
No comments yet.