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

bnx_sysctl_std_refill iterates bnx_tx_ringcnt while indexing bnx_rx_ret_ring[] (copy-paste): only min(tx,rx) rings updated; validation admits negative cntmax that wraps uint16_t

Summary

The dev.bnx.N.std_refill handler loops 'for (i = 0; i < sc->bnx_tx_ringcnt; ++i) sc->bnx_rx_ret_ring[i].bnx_rx_cntmax = cntmax;' - the bound is copied from a TX loop and must be sc->bnx_rx_retcnt. With rx_retcnt>1 and tx_ringcnt==1 (common BCM5719/5720 non-bundled RSS setup), only ring 0 is updated; rings 1..rx_retcnt-1 silently keep the default, so the operator-visible value disagrees with the per-ring thresholds actually used. Not memory-unsafe: tx_ringcnt <= rx_retcnt structurally guaranteed. The validation also admits negative cntmax which truncates into uint16_t bnx_rx_cntmax (e.g. -5 -> 65531), effectively disabling that refill trigger (RX still refills via the independent used>=256 check). Root-only sysctl; correctness/performance only.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3641 Β· 10 files
FileTypeDescriptionSize
README.md β€” 1.6 KB ↓ raw
VERDICT.md β€” 3.5 KB ↓ raw
trigger.c β€” 2.9 KB view raw
build.sh β€” 117 B view raw
run.sh β€” 245 B view raw
build.log β€” 74 B view raw
run.log β€” 1.6 KB view raw
env.txt β€” 928 B view raw
fix.diff β€” 461 B view raw
verdict.json β€” 3.8 KB view raw

DF-3641 β€” bnx_sysctl_std_refill tx/rx ring-count copy-paste β€” evidence pack

What: the handler for dev.bnx.N.std_refill (sys/dev/netif/bnx/if_bnx.c:5684-5685) runs for (i = 0; i < sc->bnx_tx_ringcnt; ++i) sc->bnx_rx_ret_ring[i].bnx_rx_cntmax = cntmax; β€” the loop bound is a copy-paste from a TX loop and must be sc->bnx_rx_retcnt. With rx_retcnt > 1, tx_ringcnt == 1 (common BCM5719/ 5720 RSS topology) only ring 0 is updated. The validation at 5679 also admits negative cntmax, which truncates into the uint16_t field.

Impact: configuration correctness only β€” tx_ringcnt <= rx_retcnt is structurally guaranteed (if_bnx.c:5784-5799 + sys/net/if.c:3944-3953), so there is no OOB; root-only sysctl. Filed Info / base:dev.

Reproduce (audit guest β€” honest negative):

  cc -O -Wall -o trigger trigger.c     # guest; builds clean
  ./run.sh                             # via dfbsd-qemu/vm.sh run_root

run.log: dev.bnx.0.std_refill β†’ unknown oid (no Broadcom hardware), plus a verbatim guest-side dump of lines 5679-5686 showing the wrong bound; src md5 matches the audited tree β†’ runtime verdict inconclusive (source-level certainty).

On real hardware: sysctl dev.bnx.0.rx_rings=4-style setup with 1 TX ring, write dev.bnx.0.std_refill=64 β€” rings 1..3 keep the default (512/4)/4 = 32.

Fix: fix.diff β€” iterate bnx_rx_retcnt, reject cntmax < 1 (git-apply verified).

Files: VERDICT.md (full narrative + structural tx<=rx proof), trigger.c, build.sh/run.sh, build.log/run.log (untrimmed guest transcripts), env.txt, manifest.json, verdict.json.

VERDICT.md
↓ download raw

DF-3641 β€” bnx_sysctl_std_refill() iterates bnx_tx_ringcnt while indexing bnx_rx_ret_ring[]

File: sys/dev/netif/bnx/if_bnx.c (pass 2, GLM 5.3) Class: copy-paste loop-bound defect (CWE-193-adjacent; here correctness only) Severity filed: Info, bucket base:dev, confidence certain (source fact) Verdict: inconclusive at runtime β€” sysctl node requires attached bnx hardware, absent on the audit guest.

1. Root cause (path:line)

bnx_sysctl_std_refill() β€” the handler for dev.bnx.N.std_refill:

The loop must run over sc->bnx_rx_retcnt. On the common BCM5719/5720 RSS setup with rx_retcnt > 1 and tx_ringcnt == 1 (the non-bundled topology; bnx_setup_ring_cnt() allows tx counts from 1..min(4, rx_retcnt)), a write to dev.bnx.N.std_refill updates only ring 0 β€” rings 1..rx_retcnt-1 silently keep the default bnx_rx_cntmax assigned in bnx_dma_alloc() (if_bnx.c:4259-4260: (BGE_STD_RX_RING_CNT / 4) / sc->bnx_rx_retcnt), so the operator-visible value disagrees with the per-ring refill thresholds actually in force (used at if_bnx.c:2825, ret->bnx_rx_cnt >= ret->bnx_rx_cntmax).

2. Not memory-unsafe (why Info)

tx_ringcnt <= rx_retcnt is structurally guaranteed before any of this code runs: bnx_setup_ring_cnt() (if_bnx.c:5784-5799) clamps the TX ring map to min(BNX_TX_RING_MAX, rx_retcnt) and if_ringcnt_fixup() (sys/net/if.c:3944-3953) clamps the count; the KASSERT at if_bnx.c:5797 documents the invariant (if_ringmap_align() only equalizes CPU grids, not counts β€” verified in sys/net/if.c:4029-4036). The loop therefore can never run past the bnx_rx_ret_ring[] allocation.

Related wart (also fixed in fix.diff): the validation at 5679 admits negative cntmax (n * retcnt is negative, never >= 256), which then truncates into the uint16_t bnx_rx_cntmax (if_bnxvar.h:204) β€” e.g. βˆ’5 β†’ 65531, making the bnx_rx_cnt >= bnx_rx_cntmax test at 2825 almost never fire (refill scheduling from that path stops; RX still refills via the independent bnx_rx_std_used >= BGE_STD_RX_RING_CNT/2 check at 2890). Root-only sysctl, performance/correctness only.

3. Guest evidence (why inconclusive)

run.log (guest, byte-identical source): - pci/if_bnx in kernel; NO_BROADCOM_DEVICE; ifconfig -l = vtnet0 lo0; sysctl dev.bnx.0.std_refill β†’ unknown oid β†’ the handler never executes on this guest. - Guest-side dump of lines 5679-5686 shows the mismatch verbatim. - md5 if_bnx.c == audited tree.

trigger.c probes dev.bnx.0.std_refill via sysctlbyname and correctly reports it absent on the guest β†’ honest inconclusive / reproduced: 0.

4. Fix

fix.diff β€” iterate sc->bnx_rx_retcnt and reject cntmax < 1. git-apply-able (verified). Not runtime-validatable on this guest (fix_status: not_testable).

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

fix.diff corrects the loop bound to sc->bnx_rx_retcnt and adds the cntmax<1 rejection; git apply --check passes against the audited tree. Cannot be runtime-validated: the sysctl handler never executes on the audit guest (no Broadcom device), so neither baseline nor patched behavior is observable there.

fix.diff; run.log (dev.bnx.0.std_refill unknown oid => handler unreachable on guest)
↓ fix.diffper-fix-DF-3641

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (guest: sysctl dev.bnx.0.std_refill unknown oid, NO_BROADCOM_DEVICE, md5 match, verbatim sed dump of the wrong loop bound), VERDICT.md (path:line trace + the structural tx<=rx proof), trigger.c (sysctlbyname probe, builds clean -O -Wall), fix.diff (iterate bnx_rx_retcnt + reject cntmax<1, git-apply verified)

PoC changes

No seed existed (new pass-2 finding); trigger written as a sysctl probe that self-reports the absent OID on the audit guest.

Verified recommended fix

In bnx_sysctl_std_refill, iterate sc->bnx_rx_retcnt (not bnx_tx_ringcnt) and reject cntmax < 1 (see fix.diff).

Verdict

Source-level certain: bnx_sysctl_std_refill (sys/dev/netif/bnx/if_bnx.c:5684-5685) iterates sc->bnx_tx_ringcnt while indexing sc->bnx_rx_ret_ring[i] β€” a copy-paste of the loop bound from a TX loop; it must iterate sc->bnx_rx_retcnt. On BCM5719/5720 with rx_retcnt>1 and tx_ringcnt==1 (common non-bundled RSS setup), writing dev.bnx.N.std_refill updates only ring 0; rings 1..rx_retcnt-1 silently keep the default bnx_rx_cntmax from bnx_dma_alloc (if_bnx.c:4259-4260), so the operator-visible value disagrees with the per-ring refill thresholds in force at if_bnx.c:2825. Not memory-unsafe: tx_ringcnt <= rx_retcnt is structurally guaranteed (if_bnx.c:5784-5799 clamps TX ring_max to min(BNX_TX_RING_MAX, rx_retcnt); if_ringcnt_fixup clamps the count, sys/net/if.c:3944-3953; if_ringmap_align only equalizes grids, sys/net/if.c:4029-4036). Also fixed: validation at 5679 admits negative cntmax, which truncates into uint16_t bnx_rx_cntmax (-5 -> 65531, effectively disabling that refill trigger; RX still refills via the bnx_rx_std_used>=256 check at 2890). Runtime verification honestly inconclusive: sysctl dev.bnx.0.std_refill is unknown oid on the audit guest (no Broadcom device, NO_BROADCOM_DEVICE), so the handler cannot execute there; guest /usr/src if_bnx.c md5 49a18c4b7642b9b2ebd1d02ad11542e4 matches the audited tree and the guest-side dump of lines 5679-5686 shows the mismatch verbatim.