ixgbe_checksum_ptr_x550/ixgbe_calc_checksum_X550 caller-buffer path: bounds checks ignore the ptr base offset (plus two off-by-ones) β OOB read past the caller's u16 buffer[]
Summary
The exported caller-buffer path of the X550 NVM checksum API validates start+length against buffer_size while reading buffer[ptr+i], so a call that passes every shipped guard walks up to ptr words past the end of the caller's buffer; the ptr==buffer_size and buffer_size==EEPROM_LAST_WORD cases are additional off-by-one OOB reads. PROVEN by an on-guest replica of the exact guards/loop: 'SHIPPED: OOB READ - first out-of-bounds word index = 100' + 'FIXED: rejected (ERR_PARAM)'. Latent: the only in-tree caller passes NULL (the live NULL path is bounded and safe). Same defect verified in Linux master and FreeBSD head (shared-code bug). Fix compile-validated on guest module build -Werror clean. Any future caller handing calc_checksum a user-sized buffer turns this into a kernel OOB read whose 16-bit checksum return is a weak info-leak oracle.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3723 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.1 KB | β raw | |
| VERDICT.md | β | 4.1 KB | β raw | |
| chksum_bounds_demo.c | β | 5.5 KB | view raw | |
| build.sh | β | 254 B | view raw | |
| run.sh | β | 132 B | view raw | |
| build.log | β | 82 B | view raw | |
| run.log | β | 383 B | view raw | |
| fix_build.log | β | 10.5 KB | view raw | |
| env.txt | β | 1.5 KB | view raw | |
| fix.diff | β | 725 B | view raw | |
| manifest.json | β | 1.3 KB | view raw | |
| verdict.json | β | 2.9 KB | view raw |
DF-3723 β ixgbe_checksum_ptr_x550 caller-buffer path: bounds checks ignore the ptr base offset (+ two off-by-ones) β OOB read past caller's u16 buffer[]
File: sys/dev/netif/ix/ixgbe_x550.c (lines 3338β3397, 3407β3433)
Severity: Low (latent β no in-tree caller passes a non-NULL buffer) Confidence: certain (source-proven + on-guest bound-math demo)
Build / Run (bound-math demo)
The guest has no X550 NIC, so the HAL cannot run on hardware. The demo
replicates the exact shipped guards and loop and shows a valid-looking call
reading past the end of the caller's buffer; it also runs the fixed guards
(the exact code in fix.diff) and shows them rejecting the same call.
# on the audit host: ssh -F dfbsd-qemu/config dfbsd 'cat > /tmp/chksum_bounds_demo.c' < chksum_bounds_demo.c dfbsd-qemu/vm.sh run_root 'cc -O2 -o /tmp/chksum_bounds_demo /tmp/chksum_bounds_demo.c && /tmp/chksum_bounds_demo'
Expected (observed, see run.log):
SHIPPED: OOB READ β first out-of-bounds word index = 100 (buffer holds 0..99); reads run to buffer[90+50]=buffer[140] FIXED: rejected (ERR_PARAM) β OOB prevented OFF-BY-ONE (buffer_size == ptr == 90): SHIPPED ACCEPTS and reads buffer[90] (reads length at buffer[90] β one word past end) OFF-BY-ONE: FIXED rejects
Fix compile-validation (see fix_build.log)
fix.diff was applied inside the guest's /usr/src/sys/dev/netif/ix/ copy
(repo sys/ is read-only and was never touched) and the if_ix module rebuilt
with the kernel warning set including -Werror:
cc ... -Werror ... -c /usr/src/sys/dev/netif/ix/ixgbe_x550.c cc -Wl,--build-id=sha1 ... -r -o if_ix.ko ... ixgbe_x550.o # make_rc=0
The patch was then reverted in the guest (md5 restored to baseline).
Artifacts
chksum_bounds_demo.c/build.log/run.logβ on-guest bound-math demofix.diffβ 3-hunk guard fix, git-apply-able againstsys/dev/netif/ix/ixgbe_x550.cfix_build.logβ full module build with the fix applied (clean,-Werror)env.txtβ guest uname + pciconf (no X550 present) + compiler versionVERDICT.md,manifest.json,verdict.json
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():
- ixgbe_x550.c:3358 β
if (buffer_size < ptr) return IXGBE_ERR_PARAM;then :3360local_buffer = &buffer[ptr];and :3368length = local_buffer[0];. Whenbuffer_size == ptr,buffer[ptr]is exactly one word past the end β the check must be<=(off-by-one). - ixgbe_x550.c:3376 β
if (buffer && ((u32)start + (u32)length > buffer_size))validatesstart + lengthagainst the buffer base, but the reads at :3394 arelocal_buffer[i] == buffer[ptr + i]fori β [start, start+length). Theptrbase offset is missing from the bound, so the walk can extendptrwords past the end. Concrete passing case:buffer_size=100, ptr=90, buffer[90]=50β guards pass (91 β€ 100), loop readsbuffer[91..140]β 41 words OOB (demo output inrun.log). - ixgbe_x550.c:3430 β
if (buffer_size < IXGBE_EEPROM_LAST_WORD)but the loop at :3439 isfor (i = 0; i <= IXGBE_EEPROM_LAST_WORD; i++)β withbuffer_size == 0x41exactly,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 passesNULL, 0;ixgbe_api.cexposes noixgbe_calc_checksum(hw, buf, size)wrapper in this tree, andif_ix.cnever toucheseeprom.ops.calc_checksumwith 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, samebuffer_size < ptrandstart + 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.
Fix verification
inconclusivefix.diff applied to guest /usr/src/sys/dev/netif/ix; full if_ix.ko module rebuild clean with -Werror (fix_build.log, make_rc=0); harness shows fixed guards reject both the ptr-offset and off-by-one cases. Runtime fix validation impossible: no X550 hardware, and the buffer path has no in-tree caller. Patch reverted in guest afterwards (md5 restored).
fix_build.log: 'cc ... -Werror ... -c ixgbe_x550.c' + 'cc -r -o if_ix.ko ... ixgbe_x550.o' rc=0; run.log FIXED lines
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log (SHIPPED: OOB READ first OOB index=100; OFF-BY-ONE accepted vs FIXED rejected), fix_build.log (if_ix.ko rebuild with fix.diff clean under -Werror), env.txt (no X550 hardware on guest)
PoC changes
wrote bound-math harness replicating the exact shipped guards + loop (no hardware needed); fix.diff authored and compile-validated on guest /usr/src
Verified recommended fix
Include the ptr base offset in the bounds check ((u32)ptr+(u32)start+(u32)length > buffer_size), make :3358 use <=, and require buffer_size >= IXGBE_EEPROM_LAST_WORD+1 at :3430
Verdict
Source-certain OOB read in the exported caller-buffer path of ixgbe_calc_checksum_X550/ixgbe_checksum_ptr_x550: the guard at ixgbe_x550.c:3376 checks start+length against buffer_size but reads occur at buffer[ptr+i] (:3360,:3394), and :3358/:3430 are off-by-one β proven by an on-guest replica of the exact guards/loop (run.log: shipped code reads buffer[91..140] of a 100-word buffer; fixed guards reject). Runtime inconclusive only because the guest has no X550 NIC (env.txt: pciconf shows no Intel 10G device) and no in-tree caller passes a non-NULL buffer β latent, same defect present in Linux master and FreeBSD head. The live NULL-buffer path used at attach (if_ix.c:615) is bounded by word_size checks (:3454,:3371) and is safe.
No comments yet.