ix(4): ixgbe_read_posted_mbx/ixgbe_write_posted_mbx dispatch to ops.read/ops.write with unvalidated size, bypassing the clamp/reject enforced only by the non-posted wrappers - latent OOB-to-MMIO and cross-VF-mailbox-spill footgun (twin of DF-4638 e1000_mbx); no in-tree caller (DF ix has no SR-IOV)
Summary
The posted wrappers skip the size discipline that lives only in the non-posted wrappers; the raw VF/PF loops trust size verbatim over the fixed 64-byte window (IXGBE_PFMBMEM(vf)=0x13000+64*vf). Deader than the igb twin: DF ix has no SR-IOV and zero in-tree callers of any mailbox op (only init_params), and PF timeout=0 additionally short-circuits read_posted. On the day SR-IOV/ixv lands with size>16: write path reads the caller's buffer OOB and writes MMIO into the NEXT VF's mailbox (cross-VM forgery); read path discloses cross-VF data. Mirror-the-discipline fix (git-apply-verified).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4645 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.6 KB | β raw | |
| VERDICT.md | β | 4.0 KB | β raw | |
| static-proof.txt | β | 2.3 KB | view raw | |
| env.txt | β | 682 B | view raw | |
| fix.diff | β | 729 B | view raw | |
| verdict.json | β | 3.1 KB | view raw | |
| manifest.json | β | 1.0 KB | view raw |
DF-4645 β ixgbe posted-mbx wrappers bypass size discipline (twin of DF-4638)
Severity: Info (latent / dead-in-tree) Β· bucket: memcorrupt Β· confidence: certain (static)
What
sys/dev/netif/ix/ixgbe_mbx.c has the exact twin of the e1000_mbx shape filed as
DF-4638 (ig_hal):
ixgbe_read_mbx()(ixgbe_mbx.c:47-62) clampssizetombx->size(lines 55-56) before dispatching tombx->ops.read.ixgbe_write_mbx()(ixgbe_mbx.c:73-88) rejectssize > mbx->size(lines 80-83) before dispatching tombx->ops.write.
But the posted wrappers, which are what real VF drivers call, dispatch directly to the raw ops with no size validation at all:
ixgbe_read_posted_mbx()(ixgbe_mbx.c:224-241) βmbx->ops.read(hw, msg, size, mbx_id)at line 238 β no clamp.ixgbe_write_posted_mbx()(ixgbe_mbx.c:253-273) βmbx->ops.write(hw, msg, size, mbx_id)at line 266 β no reject.
The raw ops themselves do no validation either:
ixgbe_write_mbx_vf (425-456, loop at 445-446), ixgbe_read_mbx_vf
(467-493, loop at 482-483), ixgbe_write_mbx_pf (665-695, loop at 683-684),
ixgbe_read_mbx_pf (708-733, loop at 722-723) all trust size verbatim.
mbx->size is always IXGBE_VFMAILBOX_SIZE == 16 words / 64 bytes
(ixgbe_mbx.h:40; set at ixgbe_mbx.c:510, 755).
Consequence if a caller ever passes size > 16
Write path (for (i = 0; i < size; i++)
IXGBE_WRITE_REG_ARRAY(hw, IXGBE_PFMBMEM(vf), i, msg[i])):
1. kernel OOB read of the caller's msg[] buffer beyond size-words is
not the issue (caller-sized) β but any caller passing a fixed 16-word
buffer with a bigger size gets an OOB read of adjacent kernel memory
written into device-visible MMIO;
2. IXGBE_PFMBMEM(vf) = 0x13000 + 64*vf (ixgbe_type.h:455) is a 64-byte
window β words 16+ of the loop spill into the next VF's mailbox:
cross-VF mailbox corruption / message forgery on any future SR-IOV use.
Read path: the loop reads past the 16-word window (next VF's mailbox / other registers) into the caller's buffer β cross-VF data disclosure.
Why Info and not higher
The DragonFly ix driver has no SR-IOV/VF support and no caller of any
mailbox message op β the entire message engine is dead code in-tree:
if_ix.ctouches the mbx subsystem exactly once:hw->mbx.ops. init_params(hw)(if_ix.c:535-536).- No
ixvdriver exists in the tree; grep forread_posted|write_postedoversys/finds zero callers outsideixgbe_mbx.citself (seestatic-proof.txt). ixgbe_init_mbx_params_pf(741-770) is registered only by ixgbe_82598.c⦠actually only 82599/x540/x550 attach code (ixgbe_82599.c:388, ixgbe_x540.c:148); PF-sidetimeout = 0(line 752) additionally makesixgbe_poll_for_msg/ackreturnIXGBE_ERR_MBXimmediately, so even a hypotheticalread_postedcaller would never reachops.readon the PF side.
This matches DF-4638's Info classification for the ig_hal twin (there the VF path at least exists in igb; here it does not).
Not runnable β why there is no PoC
- Guest has no ixgbe-class PCI device at all (virtio-only; see
env.txt). - Even with hardware, no in-tree code path invokes the posted wrappers, so there is no trigger to point at; the finding is a latent API-contract defect for the day SR-IOV/VF support lands (or an out-of-tree/ported caller).
- Verification performed instead: exhaustive static caller analysis
(
static-proof.txt) + hardware-absence capture (env.txt).
Fix
fix.diff β mirror the non-posted discipline inside the posted wrappers
(clamp on read, reject on write). Never applied to sys/ (read-only audit
target).
VERDICT β DF-4645 (ixgbe posted-mbx size-discipline bypass, twin of DF-4638)
status: untested Β· reproduced: 0 Β· impact: none (latent) Β· confidence: certain
Bottom line
The twin defect exists verbatim: ixgbe_read_posted_mbx dispatches to
mbx->ops.read with an unvalidated size (sys/dev/netif/ix/ixgbe_mbx.c:238),
bypassing the clamp that exists only in ixgbe_read_mbx
(ixgbe_mbx.c:55-56); ixgbe_write_posted_mbx dispatches to mbx->ops.write
unvalidated (ixgbe_mbx.c:266), bypassing the reject that exists only in
ixgbe_write_mbx (ixgbe_mbx.c:80-83). The raw ops (ixgbe_read_mbx_vf
ixgbe_mbx.c:467-493, ixgbe_write_mbx_vf 425-456, ixgbe_read_mbx_pf
708-733, ixgbe_write_mbx_pf 665-695) trust size verbatim; every mailbox
is a fixed 16-word/64-byte window (IXGBE_VFMAILBOX_SIZE, ixgbe_mbx.h:40;
IXGBE_PFMBMEM(vf)=0x13000+64*vf, ixgbe_type.h:455). A future caller with
size > 16 would (a) read msg[i] OOB from kernel memory into device MMIO
on the write side and (b) spill register writes/reads past the 64-byte window
into the next VF's mailbox β cross-VF corruption/disclosure.
Why untested (honest classification)
- No trigger path exists in-tree. Exhaustive grep over
sys/proves the only reference the ix driver makes to the mbx subsystem ishw->mbx.ops.init_params(hw)at if_ix.c:535-536;read_posted/write_posted/ixgbe_read_mbx/ixgbe_write_mbxhave zero callers outside ixgbe_mbx.c (static-proof.txt, steps 1-2). DragonFly's ix driver has no SR-IOV/VF support at all (static-proof.txt, step 5: zero vf/iov matches in if_ix.c) β unlike igb (DF-4638), where a VF path at least exists. - No hardware on the verification guest.
pciconf -lshows a virtio-only PCI bus β no 82598/82599/X540/X550 device (env.txt).if_ixis compiled into the kernel ("module already loaded or in kernel") so the code is present but dormant: with no device,ixgbe_init_mbx_params_*never runs and nostruct ixgbe_hwmbx ops are ever populated or invoked. - Additional structural dead-end on the PF side:
ixgbe_init_mbx_params_pfsetsmbx->timeout = 0(ixgbe_mbx.c:752), andixgbe_poll_for_msg/ixgbe_poll_for_ackreturnIXGBE_ERR_MBXimmediately when timeout==0 (ixgbe_mbx.c:164-165, 196-197), soixgbe_read_posted_mbxwould fail at the poll step before ever dispatchingops.readβ even a hypothetical PF caller cannot reach the unvalidated read today.
Because there is no reachable primitive on any current DragonFly system, this is filed Info / latent exactly like its ig_hal twin DF-4638 (which at least has a live VF code path). No userβroot route exists to demonstrate; the finding documents the API-contract footgun for the day SR-IOV lands or a ported caller (e.g., an ixv driver) is added.
Verification that WAS done
- Full-file read of ixgbe_mbx.c (770 lines) + ixgbe_mbx.h + relevant ixgbe_type.h register macros; every dispatch/size path traced (see README).
- Exhaustive caller analysis persisted (static-proof.txt).
- Guest environment captured (env.txt): DragonFly 6.5-DEVELOPMENT x86_64, kernel with if_ix compiled in, virtio-only PCI, no /dev/ast*, no ixgbe device. Guest left clean (no state changed beyond read-only probes).
Fix
fix.diff mirrors the non-posted discipline inside both posted wrappers
(read: clamp to mbx->size; write: reject with IXGBE_ERR_MBX +
ERROR_REPORT2). Authored against the read-only sys/ tree, never applied.
A fix-validation kernel build was not performed: the bug has no trigger path
in-tree (see above), so a patched kernel is observationally identical by any
runnable test β fix validation would be theater. fix_status: not_applicable.
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_testablefix.diff mirrors non-posted discipline (clamp read, reject write) at the exact dispatch sites (ixgbe_mbx.c:238, 266); not kernel-build-validated because the bug has no in-tree trigger path and the guest has no ixgbe hardware β a patched kernel is observationally identical under any runnable test.
findings/poc/DF-4645/fix.diff; VERDICT.md 'Fix' section
Confirmed kernel references
- sys/dev/netif/ix/ixgbe_mbx.c:224
- sys/dev/netif/ix/ixgbe_mbx.c:238
- sys/dev/netif/ix/ixgbe_mbx.c:253
- sys/dev/netif/ix/ixgbe_mbx.c:266
- sys/dev/netif/ix/ixgbe_mbx.c:55
- sys/dev/netif/ix/ixgbe_mbx.c:80
- sys/dev/netif/ix/ixgbe_mbx.c:445
- sys/dev/netif/ix/ixgbe_mbx.c:683
- sys/dev/netif/ix/ixgbe_mbx.h:40
- sys/dev/netif/ix/ixgbe_type.h:455
- sys/dev/netif/ix/if_ix.c:535
Detail
Evidence (decisive lines)
static-proof.txt (exhaustive zero-caller grep), env.txt (guest: no ixgbe device, if_ix compiled-in but dormant), VERDICT.md (full path:line trace), fix.diff (clamps read / rejects write inside posted wrappers)
PoC changes
no PoC exists to change: the finding is latent (no in-tree trigger path, no hardware on guest); verification was static caller analysis + guest hardware-absence capture instead
Verified recommended fix
Mirror the non-posted wrappers' size discipline inside ixgbe_read_posted_mbx (clamp size to mbx->size) and ixgbe_write_posted_mbx (reject size > mbx->size with IXGBE_ERR_MBX) β see fix.diff.
Verdict
Twin of DF-4638 confirmed statically: ixgbe_read_posted_mbx (ixgbe_mbx.c:238) and ixgbe_write_posted_mbx (ixgbe_mbx.c:266) dispatch to the raw mbx ops with unvalidated size, bypassing the clamp (ixgbe_mbx.c:55-56) and reject (ixgbe_mbx.c:80-83) enforced only by the non-posted wrappers; the raw VF/PF ops trust size verbatim over a fixed 16-word/64-byte mailbox window, so a future size>16 caller gets kernel-OOB-read-to-MMIO on write and cross-VF mailbox spill on both directions. Not runnable today: the DF ix driver has no SR-IOV/VF support and zero in-tree callers of any mailbox message op (only if_ix.c:535 init_params), and the verification guest has no ixgbe-class PCI device (virtio-only). Filed Info/latent exactly like the ig_hal twin. Guest probed read-only and left clean.
No comments yet.