ig_hal: e1000_read_posted_mbx/e1000_write_posted_mbx dispatch to ops.read/ops.write with unvalidated size, bypassing the read-clamp/write-reject discipline enforced only by the non-posted wrappers β latent kernel-buffer overflow footgun on the live igb VF path
Summary
The size clamp/reject lives only in the non-posted wrappers; the posted variants call ops directly with the caller's size unvalidated - and one future caller with a message-derived size gets a kernel overflow whose content is PF-controlled mailbox words (running past caller buffers and the 16-word register window). Every in-tree caller passes <=16 with correctly sized buffers, so latent today; the API is live on igb VF attach. Mirror-the-sibling-checks fix, compile-validated on guest.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4638 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.0 KB | β raw | |
| VERDICT.md | β | 4.3 KB | β raw | |
| fix.diff | β | 692 B | view raw | |
| env.txt | β | 1.1 KB | view raw | |
| build.log | β | 1.6 KB | view raw | |
| verdict.json | β | 3.2 KB | view raw | |
| manifest.json | β | 960 B | view raw |
DF-4638 β ig_hal: posted mailbox wrappers bypass the size clamps their non-posted siblings enforce
File: sys/dev/netif/ig_hal/e1000_mbx.c (pass 2, GLM 5.3)
What
e1000_read_mbx()(e1000_mbx.c:80-81) clampssizedown tombx->sizebefore dispatching toops.readβ this is what preventsops.read(thefor (i = 0; i < size; i++) msg[i] = E1000_READ_REG_ARRAY(...)loop at e1000_mbx.c:736-737 / :512-513) from walking past the caller's buffer.e1000_write_mbx()(e1000_mbx.c:105-106) rejectssize > mbx->size.- The posted variants call
ops.read/ops.writedirectly, with no size handling at all:e1000_read_posted_mbx()at e1000_mbx.c:260 ande1000_write_posted_mbx()at e1000_mbx.c:287.
Today every in-tree caller (all in e1000_vf.c: :283/:288/:334/:337/:385-388/:436/
:498/:501/:559) passes size β {1,2,3,16} β€ mbx->size = 16 (E1000_VFMAILBOX_SIZE,
set at e1000_mbx.c:540/:766), with correctly sized stack buffers β so there is no
demonstrable overflow on the current tree. The hazard is latent: the VF mailbox API is
live code (the igb driver attaches to 82576_VF/I350_VF PCI IDs β if_igb.c:108/:123 β and
dispatches through e1000_api.c:511/:514 to e1000_init_function_pointers_vf), and any
future caller of the posted API with size > mbx->size gets a kernel stack/heap
overflow filled with PF-controlled mailbox words, plus out-of-range E1000_VMBMEM
register-array writes.
Severity: Info (defense-in-depth / API hardening). Confidence: certain (the missing checks are source facts; the impact statement is explicitly latent).
Reproduce
Nothing to run β this is a hardening gap. The fix was compile-validated on the audit guest against the running kernel's headers:
# build.log (guest session):
cc -fsyntax-only -D_KERNEL -I/usr/obj/usr/src/sys/X86_64_GENERIC \
-I/usr/src/sys -I/usr/src/sys/dev/netif/ig_hal e1000_mbx_fixed.c # FIXED_CC_RC=0
fix.diff mirrors the sibling checks into the posted wrappers (read clamps, write
rejects) β git-apply-clean against sys/.
DF-4638 VERDICT β posted mailbox wrappers bypass the size clamps
Status: untested (nothing runnable: no igb/SR-IOV hardware on the audit guest β env.txt; and there is no in-tree caller that overflows today, by design of the finding). Fix: compile-validated on guest (FIXED_CC_RC=0, BASE_CC_RC=0 β build.log).
Analysis
The non-posted wrappers implement the mailbox size discipline:
e1000_read_mbx(e1000_mbx.c:72-87):if (size > mbx->size) size = mbx->size;(:80-81) β protects the destination buffer, since the per-MACops.readimplementations copysizewords unconditionally (e1000_read_mbx_vf:512-513,e1000_read_mbx_pf:736-737).e1000_write_mbx(e1000_mbx.c:98-112):if (size > mbx->size) ret_val = -E1000_ERR_MBX;(:105-106) β protects both the source buffer read (msg[i]ine1000_write_mbx_vf:476-477 /_pf:697-698) and theE1000_VMBMEM(_n) + iregister-array window (16 words per VF, e1000_regs.h:589).
The posted wrappers dispatch around that discipline:
e1000_read_posted_mbx(:246-263): aftere1000_poll_for_msgsucceeds, callsmbx->ops.read(hw, msg, size, mbx_id)(:260) with the caller'ssizeunmodified β no clamp.e1000_write_posted_mbx(:275-294): checks!mbx->ops.write || !mbx->timeout(:283) then callsmbx->ops.write(hw, msg, size, mbx_id)(:287) β no size check.
Reachability today (why Info, not higher)
All in-tree callers of the posted API are in sys/dev/netif/ig_hal/e1000_vf.c:
write/read 1 (:283/:288), 3 (:334/:337), 16 via e1000_write_msg_read_ack
(:385-388, retmsg[E1000_VFMAILBOX_SIZE]), 16 (:436), 1 (:498/:501); plus one
non-posted ops.read(hw, &in_msg, 1, 0) (:559). Every size β€ mbx->size = 16
(e1000_mbx.h:62; installed at e1000_mbx.c:540/:766), every buffer correctly sized.
No overflow is constructible on the current tree β the gap is a loaded footgun on a
live API (igb's VF attach path: if_igb.c:108/:123 β e1000_api.c:511/:514 β
e1000_vf.c:151 β e1000_mbx.c:542-545), aimed at exactly the "trust the VF/PF message
size" family. A future caller passing a size derived from a message field or a larger
constant gets a kernel-memory overflow whose content is PF-controlled mailbox words.
Note the API inconsistency is also a correctness trap in the other direction:
e1000_read_mbx silently truncates while e1000_write_mbx errors β the posted
variants do neither.
Classes hunted and killed on this file (pass-2 negative result)
e1000_poll_for_msg/e1000_poll_for_ack(:181-234):countdown = mbx->timeout(0 or 2000, u32 in e1000_hw.h:983 β no sign issue); bounded 2000 Γ 500 Β΅s; on exhaustionmbx->timeoutlatches 0 = permanent-fail-until-reset (conservative, not a bypass).e1000_obtain_mbx_lock_vf/pf(:426-446/:644-668): bounded 10 Γ 1 ms.e1000_check_for_msg_pf/check_for_ack_pf(:585/:606):E1000_MBVFICR_VFREQ_VF1 << vf_number/E1000_MBVFICR_VFACK_VF1 << vf_numberis UB forvf_number β₯ 32, but the PF ops have no in-tree caller (DFly igb has no PF-side SR-IOV) β dead code; would deserve a bound (vf_number = vf_number % 32) the day a PF driver appears.e1000_init_mbx_params_pf(:755-784): missingbreakbeforedefault:is cosmetic (case body completes then falls intoreturn E1000_SUCCESS); unsupported MACs leaveopsNULL-zeroed and every dispatcher NULL-checks (:83,:108,:128,:148,:168,:253,:283).e1000_read_v2p_mailbox/check_for_bit_vf(:322-351): shadow-register merge is idempotent; only reachable on VF dispatch wheredev_spec.vfis the valid union member.- No fw/VF-controlled count reaches any loop bound in this file β all sizes are caller-supplied (that is precisely why the missing caller-side discipline matters).
Fix
fix.diff (git-apply-clean; compile-validated on the guest, build.log):
mirror the sibling checks into the posted wrappers β read clamps to mbx->size,
write rejects size > mbx->size.
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 applies cleanly (git apply --check) and cc -fsyntax-only passes against the running kernel's headers on the guest for both baseline and patched file (BASE_CC_RC=0/FIXED_CC_RC=0, build.log); runtime validation impossible on this virtio-only guest (no igb/VF device)
fix.diff; build.log
Confirmed kernel references
Detail
Exploit chain
[]
Evidence (decisive lines)
VERDICT.md (clamp/reject vs direct-dispatch line cites; caller sweep; killed-class list); build.log (on-guest baseline+patched cc -fsyntax-only RC=0); env.txt (no e1000 hardware on guest); fix.diff (git-apply-clean)
PoC changes
n/a β no seeded PoC; the actionable artifact is fix.diff, compile-validated on the guest
Verified recommended fix
Mirror the sibling checks into the posted wrappers: clamp size to mbx->size in e1000_read_posted_mbx before ops.read, and reject size > mbx->size in e1000_write_posted_mbx (fix.diff).
Verdict
not_run as PoC by design: this is a hardening gap with no overflowing caller in the current tree (verified by exhaustive caller sweep β e1000_vf.c is the only user of the posted API; e1000_api.c dispatches VF ops for igb's 82576_VF/I350_VF attach path). The size discipline exists only in the non-posted wrappers (read clamp e1000_mbx.c:80-81, write reject :105-106); the posted wrappers call ops.read/ops.write directly (:260, :287) with the caller's size unvalidated. No igb/SR-IOV hardware on the guest (virtio-only, env.txt). fix.diff (mirror sibling checks) is git-apply-clean and compile-validated against X86_64_GENERIC headers on the guest (FIXED_CC_RC=0, build.log).
No comments yet.