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)
PoC verification
Evidence pack
findings/poc/DF-4794 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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).
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_typemaps those IDs toe1000_vfadapt/e1000_vfadapt_i350(sys/dev/netif/ig_hal/e1000_api.c:392-397), ande1000_init_function_pointers_vfinstalls the VF op table (e1000_api.c:510-514).igb_set_multi()(if_igb.c:1267-1310) counts joined AF_LINK multicast addresses intomtaand callse1000_update_mc_addr_list(&sc->hw, mta, mcnt)whenevermcnt < MAX_NUM_MULTICAST_ADDRESSES; a VF therefore reachese1000_update_mc_addr_list_vfwith 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_demooutput on the audit guest: current ordering β PF-visiblemsgbuf[0]=0x001e0003(flag LOST); fixed ordering β0x009e0003(flag SET). Deterministic arithmetic with the real macro values frome1000_mbx.h:62-82.- Upstream divergence: FreeBSD current
sys/dev/e1000/e1000_vf.c(e1000_update_mc_addr_list_vf) assignsmsgbuf[0] = E1000_VF_SET_MULTICAST;beforemsgbuf[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-existingsystm.h:216builtin warning). Guest/usr/srccopy verified bit-identical to the audited tree (sha256 c0cdce6bβ¦).env.txtβ guest is virtio-only; no e1000/VF device β runtime path not exercisable there (henceuntested, 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_vfread_posted 3 intou32 msgbuf[3](:263,288);rar_set_vf3 into[3](:327,337);promisc_set_vf1 into 1 word (:498-501);write_msg_read_ack16 into[E1000_VFMAILBOX_SIZE](:384-388).e1000_read_mbx_vfcopies exactlysizewords 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)withaddr=&msgbuf[1]β 6 bytes inside the 12-byte buffer; perm_addr isETH_ADDR_LEN(6). In-bounds.hash_list[i]writes: clampedi<30, bytes 4..63 of the 64-byte msgbuf β exactly fits (E1000_VFMAILBOX_SIZE=16, e1000_mbx.h:62).e1000_hash_mc_addr_vfshift loop terminates for the hard-codedmta_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_testablecompile-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
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.
No comments yet.