ig_hal VF: uninitialized kernel-stack tail (up to 50 of 60 payload bytes) of the E1000_VF_SET_MULTICAST mailbox message transmitted verbatim to the PF (hypervisor)
Summary
msgbuf[16] is never zeroed; only msgbuf[0] and count*2 hash bytes are written, yet write_msg_read_ack transmits all 16 words to VMBMEM which the host PF reads - for the common <30-group case up to 50 of 60 payload bytes are uninitialized guest kernel stack disclosed to the host (guest->host kernleak, hostile-PF class). Upstream initializes the array. Demo: count=5 -> 50/60 uninit bytes; zero-init fix compile-validated.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4795 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.4 KB | β raw | |
| VERDICT.md | β | 3.7 KB | β raw | |
| mcmsg_demo.c | β | 2.9 KB | view raw | |
| build.sh | β | 107 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.3 KB | view raw | |
| verdict.json | β | 3.7 KB | view raw |
DF-4795 β ig_hal VF: uninitialized kernel-stack tail of the multicast mailbox message transmitted to the PF
File: sys/dev/netif/ig_hal/e1000_vf.c (pass 2)
Function: e1000_update_mc_addr_list_vf() β lines 400-437
What happens
u32 msgbuf[E1000_VFMAILBOX_SIZE]; /* :403 β never initialized */
u16 *hash_list = (u16 *)&msgbuf[1];
...
for (i = 0; i < mc_addr_count; i++) /* :429-434 β writes count*2 bytes
starting at byte 4 */
hash_list[i] = hash_value & 0x0FFF;
e1000_write_msg_read_ack(hw, msgbuf, E1000_VFMAILBOX_SIZE); /* :436 β 16 words */
e1000_write_msg_read_ack (:380-389) forwards all
E1000_VFMAILBOX_SIZE(16) words via write_posted, and
e1000_write_mbx_vf (sys/dev/netif/ig_hal/e1000_mbx.c:476-477) copies all
size words verbatim into the E1000_VMBMEM mailbox registers that the PF
(hypervisor / host OS) reads. With mc_addr_count < 30, the words past the
written hash entries β up to 50 of the 60 payload bytes for small counts,
e.g. count=5 β bytes 14..63 β are uninitialized kernel stack from the
guest, disclosed to the host-side PF driver. (The |= at :422 also reads
msgbuf[0] uninitialized before it is overwritten at :426 β dead value, no
transmit.)
Upstream FreeBSD initializes the buffer:
u32 msgbuf[E1000_VFMAILBOX_SIZE] = {}; (cgit sys/dev/e1000/e1000_vf.c).
Threat model: the classic guestβhost boundary. The recipient is the
PF driver in the more-privileged host, so this is a disclosure of guest
kernel stack residue across a privilege boundary that hostile-PF/VF
hardening (mxgpu/ena class) normally wants hermetic. Any unprivileged local
user in a guest can trigger it by joining multicast groups on an igb VF
interface (no privilege needed for group joins; igb_set_multi() β
e1000_update_mc_addr_list β this function).
Reproduce (logic-level)
cc -Wall -O2 -o mcmsg_demo mcmsg_demo.c && ./mcmsg_demo
Decisive line (deterministic byte-initialization model of the exact write pattern):
mc_addr_count= 5: ... uninit stack bytes sent to PF: 50 of 60
Fix
fix.diff hunk 1 β u32 msgbuf[E1000_VFMAILBOX_SIZE] = { 0 }; (git-apply
clean; compile-validated on the audit guest, build.log).
Runtime PoC status
untested β the transmit requires an SR-IOV igb VF (audit guest is
virtio-only, see env.txt). The byte-level write/forward pattern is proven
from source; see VERDICT.md.
DF-4795 β VERDICT
Status: untested (byte-level transmit pattern proven from source; guest has no SR-IOV igb VF hardware). Severity: Low (kernleak bucket β guest kernel-stack disclosure to the host PF only). Confidence: certain.
Root cause (path:line)
sys/dev/netif/ig_hal/e1000_vf.c:403 β u32 msgbuf[E1000_VFMAILBOX_SIZE];
is never initialized in e1000_update_mc_addr_list_vf(). The function
initializes only:
msgbuf[0](:426-427), andhash_list[0..mc_addr_count-1]=msgbufbytes4 .. 4+2*count-1(:429-434), count clamped to 30.
It then sends the entire 16-word buffer:
e1000_write_msg_read_ack(hw, msgbuf, E1000_VFMAILBOX_SIZE) (:436) β
mbx->ops.write_posted(hw, msg, size, 0) (:385) β e1000_write_mbx_vf,
which copies for (i = 0; i < size; i++) E1000_WRITE_REG_ARRAY(hw,
E1000_VMBMEM(0), i, msg[i]); (sys/dev/netif/ig_hal/e1000_mbx.c:476-477)
into the mailbox registers the PF reads. For count < 30 the tail (up to
50 of 60 payload bytes; count=5 β bytes 14..63 = words 1(partial)..15) is
uninitialized kernel stack transmitted to the host-side PF driver.
Secondary dead-read of the same array: msgbuf[0] |= β¦ at :422 reads
msgbuf[0] uninitialized (value discarded by :426) β folded into this
finding, no separate transmit.
Reachability (live path)
Same chain as DF-4794: igb VF devices (if_igb.c:108,123) β
e1000_vfadapt* (e1000_api.c:392-397) β VF op table (e1000_api.c:510)
β igb_set_multi() calls e1000_update_mc_addr_list with the joined-group
count (if_igb.c:1304); group joins need no privilege. Every multicast
update with fewer than 30 groups (the overwhelmingly common case β e.g. a
plain IPv6 host with a handful of solicited-node groups) transmits
uninitialized stack words to the PF.
Impact
Disclosure of guest kernel-stack residue across the guestβhost boundary (CWE-908 / CWE-457 family). Recipient is the host PF driver (more privileged than the guest); exposure beyond the PF driver depends on host behavior (driver logging / mailbox capture). No guest-internal privilege crossing; no remote exposure (mailbox is VFβPF only). Severity Low; CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:N.
Evidence
run.logβmcmsg_demo(deterministic model of the exact write pattern) on the audit guest:mc_addr_count=5 β uninit stack bytes sent to PF: 50 of 60; count=31/128 β 0 (clamp covers the tail only at exactly 30).- Upstream divergence: FreeBSD current initializes
u32 msgbuf[E1000_VFMAILBOX_SIZE] = {};ine1000_update_mc_addr_list_vf(cgit.freebsd.org, fetched 2026-09-23). build.logβ fix compile-validation on the guest: BASE_CC_RC=0, FIXED_CC_RC=0; guest source verified bit-identical to the audited tree (sha256 c0cdce6bβ¦).env.txtβ virtio-only guest, no e1000/VF device β runtime transmit not exercisable (statusuntested, attempts 0).
Pass-2 kill list for the rest of e1000_vf.c
See DF-4794/VERDICT.md (covers all mailbox sizes vs buffers, hash bounds, shift-loop termination, wait-loop bounds, NULL-op dispatch, perm_addr memcpy bounds). Sibling DF-4638 (posted-wrapper size validation) remains the canonical mbx footgun finding β no in-tree caller from this file overflows it (sizes 1/2/3/16 against correctly sized buffers).
Fix
fix.diff hunk 1: u32 msgbuf[E1000_VFMAILBOX_SIZE] = { 0 };
(git-apply clean; guest compile-validated). Hunk 2 belongs to DF-4794.
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; both hunks applied on guest copy; 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 (count=5 line: 'uninit stack bytes sent to PF: 50 of 60'); VERDICT.md (write-pattern proof path:line, reachability chain, upstream divergence); build.log (guest compile validation + source sha256 match); env.txt (no e1000 hardware on guest); fix.diff (git-apply-clean, hunk 1 = this finding)
PoC changes
no seeded PoC; authored mcmsg_demo.c (deterministic byte-initialization model of the exact write/forward pattern) + combined fix.diff
Verified recommended fix
Zero-initialize the message buffer: u32 msgbuf[E1000_VFMAILBOX_SIZE] = { 0 }; (fix.diff hunk 1)
Verdict
not runnable on the audit guest by hardware absence (virtio-only, env.txt); the byte-level write/forward pattern is certain from source: msgbuf[16] declared uninitialized at e1000_vf.c:403, only msgbuf[0] (:426-427) and count*2 bytes at offset 4 (:429-434) are written, yet e1000_write_msg_read_ack (:436 -> :385) forwards all E1000_VFMAILBOX_SIZE=16 words and e1000_write_mbx_vf copies all size words into VMBMEM (e1000_mbx.c:476-477) which the PF reads. FreeBSD current initializes the array (= {}), confirming upstream treats this as a bug. Path is live for igb VF devices (if_igb.c:108/123 -> e1000_api.c:510 -> igb_set_multi if_igb.c:1304); unprivileged users trigger it by joining multicast groups. Deterministic demo on guest (run.log): count=5 -> 50/60 bytes uninitialized. fix.diff hunk 1 (= { 0 }); compile-validated BASE_CC_RC=0/FIXED_CC_RC=0 (build.log), git apply --check clean.
No comments yet.