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

ig_hal VF: E1000_VF_SET_MULTICAST_OVERFLOW flag overwritten before send β€” PF never told to enable multicast promiscuous, >30 joined groups silently dropped (IPv6 ND breakage)

Summary

msgbuf[0] receives the OVERFLOW flag at :422 then is unconditionally reassigned SET_MULTICAST at :426, destroying the overflow indication - the PF programs only 30 hash bits and traffic for every other group (typically IPv6 solicited-node) is silently dropped. Upstream FreeBSD preserves the flag. Logic-level demo on guest: PF-visible 0x001e0003 (flag LOST) vs fixed 0x009e0003; fix compile-validated. Unpriv group joins trigger it on any SR-IOV igb VF. Reorder fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4794 Β· 11 files
FileTypeDescriptionSize
README.md β€” 2.6 KB ↓ raw
VERDICT.md β€” 5.0 KB ↓ raw
mcmsg_demo.c β€” 2.9 KB view raw
build.sh β€” 133 B view raw
run.sh β€” 104 B view raw
build.log β€” 1.5 KB view raw
run.log β€” 1.7 KB view raw
env.txt β€” 1.4 KB view raw
fix.diff β€” 793 B view raw
manifest.json β€” 1.4 KB view raw
verdict.json β€” 3.9 KB view raw

DF-4794 β€” ig_hal VF: E1000_VF_SET_MULTICAST_OVERFLOW flag overwritten before send β€” PF never told to enable multicast promiscuous

File: sys/dev/netif/ig_hal/e1000_vf.c (pass 2) Function: e1000_update_mc_addr_list_vf() β€” lines 400-437

What happens

When an interface served by an igb VF (82576 VF 8086:10ca / I350 VF 8086:1520, bound by if_igb.c β€” IGB_DEVICE(82576_VF)/IGB_DEVICE(I350_VF), sys/dev/netif/igb/if_igb.c:108,123) has more than 30 multicast groups joined, the VF can only forward 30 hash values to the PF; the protocol bit E1000_VF_SET_MULTICAST_OVERFLOW is what tells the PF "enable multicast promiscuous for me". In DragonFly's ordering the flag is dead:

if (mc_addr_count > 30) {
        msgbuf[0] |= E1000_VF_SET_MULTICAST_OVERFLOW;   /* :422 set */
        mc_addr_count = 30;
}

msgbuf[0] = E1000_VF_SET_MULTICAST;                     /* :426 KILLS it */
msgbuf[0] |= mc_addr_count << E1000_VT_MSGINFO_SHIFT;

e1000_write_msg_read_ack(hw, msgbuf, E1000_VFMAILBOX_SIZE) (:436) then sends msgbuf[0] = 0x001E0003 β€” count 30, no OVERFLOW. The PF therefore programs only 30 hash bits and never enables multicast promiscuous: traffic for every group whose solicited hash is not among the 30 forwarded bits is silently dropped. With >30 groups joined, IPv6 solicited-node multicast for later addresses is a typical casualty (broken ND β†’ connectivity loss).

Upstream FreeBSD fixed the ordering (cgit sys/dev/e1000/e1000_vf.c, e1000_update_mc_addr_list_vf): msgbuf[0] = E1000_VF_SET_MULTICAST; is assigned before the overflow OR, keeping the flag.

Reproduce (logic-level; hardware path documented below)

cc -Wall -O2 -o mcmsg_demo mcmsg_demo.c && ./mcmsg_demo

Expected decisive output:

=== CURRENT DragonFly ordering ===
  mc_addr_count= 31: PF-visible msgbuf[0]=0x001e0003 (OVERFLOW flag LOST)
=== FIXED (upstream) ordering ===
  mc_addr_count= 31: PF-visible msgbuf[0]=0x009e0003 (OVERFLOW flag SET)

The demo replays the exact msgbuf[0] arithmetic with the real e1000_mbx.h macro values; it is a deterministic logic check, not a kernel-hardware reproduction (no SR-IOV igb VF device exists on the audit guest β€” see env.txt).

Fix

fix.diff hunk 2 (git-apply-clean against sys/; compile-validated on the audit guest: BASE_CC_RC=0 / FIXED_CC_RC=0 β€” build.log).

Runtime PoC status

untested β€” reproducing the data path needs an actual 82576/I350 VF presented to the guest (SR-IOV passthrough); the QEMU audit guest is virtio-only (pciconf -l shows no e1000 device). The logic-level behavior is proven from source ordering + upstream divergence (VERDICT.md).

VERDICT.md
↓ download raw

DF-4794 β€” VERDICT

Status: untested (logic-level proven from source + upstream divergence; guest has no SR-IOV igb VF hardware). Severity: Low. Confidence: certain.

Root cause (path:line)

sys/dev/netif/ig_hal/e1000_vf.c:421-427 β€” in e1000_update_mc_addr_list_vf(), when mc_addr_count > 30 the code first ORs E1000_VF_SET_MULTICAST_OVERFLOW (0x00800000, e1000_mbx.h:82) into msgbuf[0] (:422), then unconditionally assigns msgbuf[0] = E1000_VF_SET_MULTICAST (:426), destroying the overflow indication before e1000_write_msg_read_ack(hw, msgbuf, E1000_VFMAILBOX_SIZE) (:436) hands the message to the PF.

(Line :422 also reads uninitialized msgbuf[0] β€” the array is never initialized β€” but that value is immediately overwritten, so the read is dead; the live uninitialized-tail transmit issue is DF-4795.)

Reachability (live path, not dead code)

  • igb driver binds VF devices: sys/dev/netif/igb/if_igb.c:108,123 (IGB_DEVICE(82576_VF), IGB_DEVICE(I350_VF)).
  • e1000_set_mac_type maps those IDs to e1000_vfadapt / e1000_vfadapt_i350 (sys/dev/netif/ig_hal/e1000_api.c:392-397), and e1000_init_function_pointers_vf installs the VF op table (e1000_api.c:510-514).
  • igb_set_multi() (if_igb.c:1267-1310) counts joined AF_LINK multicast addresses into mta and calls e1000_update_mc_addr_list(&sc->hw, mta, mcnt) whenever mcnt < MAX_NUM_MULTICAST_ADDRESSES; a VF therefore reaches e1000_update_mc_addr_list_vf with any count up to that cap (well above 30). Joining multicast groups requires no privilege.

Impact

Multicast availability on igb VF interfaces only: with >30 groups joined, groups beyond the 30 forwarded hashes stop receiving traffic (the PF never enables multicast promiscuous). Typical victim: IPv6 solicited-node multicast for addresses beyond the forwarded hash set β†’ neighbor discovery fails β†’ connectivity loss for those addresses. No memory corruption, no info disclosure, no privilege boundary crossed. Severity Low (CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L).

Evidence

  • run.log β€” mcmsg_demo output on the audit guest: current ordering β†’ PF-visible msgbuf[0]=0x001e0003 (flag LOST); fixed ordering β†’ 0x009e0003 (flag SET). Deterministic arithmetic with the real macro values from e1000_mbx.h:62-82.
  • Upstream divergence: FreeBSD current sys/dev/e1000/e1000_vf.c (e1000_update_mc_addr_list_vf) assigns msgbuf[0] = E1000_VF_SET_MULTICAST; before msgbuf[0] |= E1000_VF_SET_MULTICAST_OVERFLOW; β€” the opposite order of DragonFly β€” i.e. upstream keeps the flag. Fetched 2026-09-23 from cgit.freebsd.org.
  • build.log β€” fix compile-validation on the audit guest: BASE_CC_RC=0, FIXED_CC_RC=0 (only pre-existing systm.h:216 builtin warning). Guest /usr/src copy verified bit-identical to the audited tree (sha256 c0cdce6b…).
  • env.txt β€” guest is virtio-only; no e1000/VF device β‡’ runtime path not exercisable there (hence untested, attempts 0).

Why not panics/corruption classes on this file (pass-2 kill list)

  • All mailbox reads into fixed stack arrays use sizes ≀ buffer size: e1000_reset_hw_vf read_posted 3 into u32 msgbuf[3] (:263,288); rar_set_vf 3 into [3] (:327,337); promisc_set_vf 1 into 1 word (:498-501); write_msg_read_ack 16 into [E1000_VFMAILBOX_SIZE] (:384-388). e1000_read_mbx_vf copies exactly size words from VMBMEM registers (e1000_mbx.c:497-513) β€” no PF-controlled length can exceed the caller's buffer. (Posted-wrapper size-validation gap itself is DF-4638.)
  • memcpy(hw->mac.perm_addr, addr, 6) with addr=&msgbuf[1] β€” 6 bytes inside the 12-byte buffer; perm_addr is ETH_ADDR_LEN (6). In-bounds.
  • hash_list[i] writes: clamped i<30, bytes 4..63 of the 64-byte msgbuf β€” exactly fits (E1000_VFMAILBOX_SIZE=16, e1000_mbx.h:62).
  • e1000_hash_mc_addr_vf shift loop terminates for the hard-coded mta_reg_count=128 (bit_shift=4); upstream bounded it defensively (start 1, <4) but no reachable input misbehaves here.
  • All wait loops bounded: reset wait 200Γ—5Β΅s (E1000_VF_INIT_TIMEOUT, e1000_82575.h:456); posted read/write bounded by mbx->timeout (E1000_VF_MBX_INIT_TIMEOUT=2000, e1000_mbx.h:92) inside e1000_mbx.c.
  • No NULL-op dispatch from this op table found in-tree (vf hal consumed only via e1000_api dispatchers, all NULL-guarded β€” e1000_api.c pass).

Fix

fix.diff hunk 2 mirrors upstream ordering (assign SET_MULTICAST, OR OVERFLOW in the >30 branch, else-branch plain assign). Hunk 1 (the = { 0 } initializer) addresses sibling DF-4795. Combined diff is git-apply-clean (git apply --check OK) and compile-validated on the guest.

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: fix.diff git apply --check clean; patch(1) applied both hunks on guest copy of pristine file; cc -fsyntax-only BASE_CC_RC=0 / FIXED_CC_RC=0 with only the pre-existing systm.h:216 warning (build.log); runtime fix-validation impossible on this virtio-only guest (no igb/VF device)

fix.diff; build.log; env.txt
↓ fix.diffper-fix-DF-4794

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (mcmsg_demo decisive lines: CURRENT 0x001e0003 flag-not-set vs FIXED 0x009e0003 flag-SET); VERDICT.md (root cause path:line, reachability chain, kill list); build.log (guest compile validation, source sha256 match c0cdce6b...); env.txt (no e1000 hardware); fix.diff (git-apply-clean, hunk 2 = this finding)

PoC changes

no seeded PoC; authored mcmsg_demo.c (deterministic logic demo of the msgbuf[0] arithmetic with real e1000_mbx.h macro values) + combined fix.diff

Verified recommended fix

Mirror upstream ordering: assign msgbuf[0]=E1000_VF_SET_MULTICAST before OR-ing E1000_VF_SET_MULTICAST_OVERFLOW in the >30 branch (else-branch plain assign), and zero-initialize msgbuf (fix.diff hunk 2; hunk 1 belongs to DF-4795).

Verdict

not runnable on the audit guest by hardware absence: e1000_update_mc_addr_list_vf() (e1000_vf.c:400-437) is live code for igb VF attach (if_igb.c:108/123 -> e1000_api.c:392-397/510-514 -> igb_set_multi if_igb.c:1304), but the mailbox data path needs an actual 82576/I350 VF behind an SR-IOV PF; guest pciconf shows no e1000 device (env.txt). Root cause is certain from source: e1000_vf.c:422 ORs E1000_VF_SET_MULTICAST_OVERFLOW into msgbuf[0], then :426 unconditionally assigns msgbuf[0]=E1000_VF_SET_MULTICAST, destroying the flag before e1000_write_msg_read_ack (:436) sends 16 words; FreeBSD current assigns SET_MULTICAST first and ORs OVERFLOW after (upstream keeps the flag). Deterministic arithmetic demo on the guest (run.log): current 0x001E0003 vs fixed 0x009E0003 for count>30. fix.diff hunk 2 mirrors upstream; compile-validated on guest BASE_CC_RC=0/FIXED_CC_RC=0 (build.log) and git apply --check clean.