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

netmap_sw_to_nic() double-increments k1: skipped TX rings and latent kring array OOB (currently dead code β€” no d_poll wired)

Summary

The forwarding loop advances k1 twice per iteration (for-increment plus a stray 'k1++; // XXX why?'), so iteration i processes tx_rings[2i]: half the TX rings never receive forwarded host buffers, and k1 reaches tx_rings + 2*(num_tx_rings-1) - past the krings allocation whenever ntx >= nrx+2 (heap OOB kring deref) and into the rx krings otherwise. Unreachable today: the only caller is netmap_poll() and netmap_cdevsw wires no d_poll; the subsystem also does not compile on current DragonFly. Filed Info: becomes live the moment d_poll is wired or on FreeBSD-lineage builds. Fix: one deleted line.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3335 Β· 3 files
FileTypeDescriptionSize
README.md doc summary 1.4 KB ↓ raw
VERDICT.md verdict arithmetic + reachability 1.7 KB ↓ raw
fix.diff suggested-fix delete stray k1++ 224 B view raw
README.md doc summary
↓ download raw

DF-3335 β€” netmap_sw_to_nic() double-increments k1: skipped rings / latent kring OOB

Target: sys/net/netmap/netmap.c:798-824 (netmap_sw_to_nic).

The for loop header already advances k1 (i++, k1++, netmap.c:798); line 823 increments it again inside the body (k1++; // XXX why?). Net effect: iteration i processes tx_rings[2*i] β€” every second TX ring is silently skipped, and k1 runs to tx_rings + 2*(num_tx_rings-1), i.e. past the end of the krings allocation whenever 2*(num_tx_rings-1) >= num_tx_rings + num_rx_rings (ntx >= nrx+2): the k1->ring / k1->ring->avail accesses are then heap out-of-bounds kring type-confusion. For realistic NICs (ntx <= nrx+1) k1 stays inside the combined allocation but dereferences rx krings as tx (wrong-ring buffer swapping between the host RX ring and an RX ring).

Currently dead code on this platform: netmap_sw_to_nic is called only from netmap_poll() (netmap.c:1797), and netmap_cdevsw has no d_poll entry (netmap_freebsd.c:397-404) β€” netmap_poll itself is unreachable, so neither the skip bug nor the OOB can execute on a stock DragonFly. It becomes live the moment d_poll is wired (or on FreeBSD-lineage builds where poll exists). Filed Info for that reason; the fix is one deleted line.

Verification: skipped (Info severity, dead code). Analysis-verified by reading the loop arithmetic; fix.diff deletes the stray increment.

VERDICT.md verdict arithmetic + reachability
↓ download raw

DF-3335 β€” VERDICT (analysis-verified; no runtime PoC β€” dead code)

Loop arithmetic: k1 = &tx_rings[0] before the loop (netmap.c:783); each iteration executes k1++ at netmap.c:823 AND the for-increment k1++ (netmap.c:798) -> k1 advances 2 per iteration, so iteration i touches tx_rings[2i]. Consequences:

  1. odd TX rings never receive forwarded buffers (functional bug);
  2. last touched index = 2*(num_tx_rings-1); the krings allocation is (ntx+1 + nrx+1) entries (netmap_krings_create, netmap.c:416-423), so for ntx >= nrx+2 the accesses k1->nkr_num_slots, k1->ring, k1->ring->avail (netmap.c:800-801, 804, 819-820) read/write past the allocation (heap OOB kring deref; k1->ring is a pointer loaded OOB -> subsequent slot writes through it are wild); for typical ntx<=nrx+1 k1 aliases rx krings as tx (in-allocation type confusion, buffer-index swapping into rx rings).

Reachability: ONLY from netmap_poll() (netmap.c:1797) in transparent mode (netmap_fwd sysctl or user-settable ring->flags NR_FORWARD). netmap_poll is unreachable on DragonFly: netmap_cdevsw (netmap_freebsd.c:397-404) has no .d_poll entry, so dev_dpoll falls through to the default. Verified by grep: no other caller exists. Additionally the whole netmap subsystem does not compile on current DragonFly (if_unused7 bit-rot, see DF-3332/env.txt).

Status: Info/likely, dead code today, one-line fix.

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

not testable: function is dead code (no d_poll in cdevsw) and the subsystem does not compile on this tree

fix.diff (one deleted line)
↓ fix.diffper-fix-DF-3335

Confirmed kernel references

Detail

Evidence (decisive lines)

['VERDICT.md: loop-arithmetic derivation + reachability analysis', 'fix.diff: deletes the stray increment']

PoC changes

not built (Info, dead code)

Verified recommended fix

delete the stray k1++ at netmap.c:823

Verdict

netmap_sw_to_nic() advances k1 twice per loop iteration (for-increment at netmap.c:798 plus the stray 'k1++; // XXX why?' at netmap.c:823), so iteration i processes tx_rings[2i]: half the TX rings are skipped and k1 runs to tx_rings+2*(num_tx_rings-1) β€” past the krings allocation for ntx >= nrx+2 (heap-OOB kring deref / pointer-load) and into rx krings otherwise (wrong-ring buffer swapping). DEAD CODE on this platform: the only caller is netmap_poll() (netmap.c:1797) and netmap_cdevsw wires no d_poll (netmap_freebsd.c:397-404); the whole subsystem additionally does not compile on current DragonFly. Info severity; one-line fix; Phase V skipped.