vmxnet3_txq_eof() advances TX ring from unchecked host txcd->eop_idx -> permanent mbuf/cluster leak (unbounded) and TXRING_AVAIL desync
Summary
txr->vxtxr_next = (txcd->eop_idx + 1) % ndesc trusts the 12-bit host field with no check against the outstanding window. A forged completion naming a later packet's EOP hops vxtxr_next past intermediate SOP slots whose vtxb_m mbufs become unreachable; on ring wrap vmxnet3_txq_encap() overwrites txbuf[head].vtxb_m, leaking the mbuf and its 2KB cluster forever. TXRING_AVAIL simultaneously reports the ring empty while packets are outstanding. VERIFIED ON THE GUEST via a replica of txq_eof + encap slot bookkeeping: per cycle (2 packets sent, one forged completion) - 1000 cycles: clusters 256->1256, 'alloc=2000 freed=1000 LEAKED=1000', 'avail=511 (ring claims EMPTY) while P2 mbuf still outstanding'; leaked clusters persist across module unload; fixed-logic test LEAKED=0 on identical inputs. Malicious host permanently drains the guest's mbuf cluster pool; no recovery short of reboot.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3780 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| vmxrep.c | β | 17.5 KB | view raw | |
| Makefile | β | 73 B | β download | |
| build.sh | β | 98 B | view raw | |
| run.sh | β | 204 B | view raw | |
| build.log | β | 5.4 KB | view raw | |
| run.log | β | 1.5 KB | view raw | |
| fixrun.log | β | 548 B | view raw | |
| env.txt | β | 201 B | view raw | |
| fix.diff | β | 2.4 KB | view raw | |
| VERDICT.md | β | 3.0 KB | β raw |
DF-3780 VERDICT β REPRODUCED (mechanism, protocol-replica) β impact: dos (unbounded kernel memory leak)
Bottom line
Every field of the TX completion descriptor is host-controlled, and
vmxnet3_txq_eof() uses txcd->eop_idx to advance vxtxr_next with no
validation against the outstanding-descriptor window (if_vmx.c:1966). A
completion naming a later packet's EOP hops vxtxr_next over intermediate
SOP slots; their vtxb_m mbufs become unreachable and are leaked forever
when vmxnet3_txq_encap() overwrites the slot at if_vmx.c:2904.
VMXNET3_TXRING_AVAIL() (if_vmxvar.h:76-81) simultaneously reports the ring
as empty (avail=511/512 while a packet is outstanding), so the desync also
breaks the flow-control invariant between guest and host.
Proof on the guest (DragonFly 6.5-DEVELOPMENT X86_64_GENERIC)
Verbatim replica of txq_eof + encap slot bookkeeping, hostile host forges one completion per two transmitted packets (eop_idx = head-1). 1000 cycles:
| measurement | before | after run1 | after run2 |
|---|---|---|---|
| mbuf clusters in use | 256 | 1256 | 2256 |
| network memory | 515 K | 2515 K | 4515 K |
| replica counters | β | alloc=2000 freed=1000 LEAKED=1000 | alloc=4000 freed=2000 LEAKED=2000 |
(run.log; dmesg: "after cycle 1: avail=511 (ring claims EMPTY) while P2 mbuf still outstanding".) Exactly one 2048-byte cluster leaked per forged completion; linear and unbounded; leaked clusters persist across module unload (permanent until reboot). At guest line rate this drains the 33k cluster pool (mb_map) in seconds-to-minutes β "requests for memory denied" β network DoS.
Why not worse
- The index stays in-bounds (
% vxtxr_ndesc), so no OOB access from eop_idx itself β impact is leak/livelock, not corruption. - Double-load of a still-loaded dmamap on slot reuse: DFly x86_64
bus_dmamap_load_mbuf_segment()(busdma_machdep.c:870-912) has no "already loaded" guard and simply overwrites the mapping β no panic on this platform (verified by source); on bounce-buffer platforms this would additionally corrupt bounce-page accounting.
Fix validation
Kernel-build validation not possible (no vmxnet3 device on the KVM guest).
Fix LOGIC validated in the same replica: test 900 (fixrun.log) adds the
window check and frees every mbuf in [next .. eop_idx] exactly once β
same hostile inputs, fresh module load: alloc=2000 freed=2000 LEAKED=0,
cluster count unchanged. fix.diff authors that logic for if_vmx.c.
Kernel refs
sys/dev/virtual/vmware/vmxnet3/if_vmx.c:1938-1967 (txq_eof), if_vmx.c:2878-2928 (encap slot bookkeeping; the overwrite at 2904), if_vmxvar.h:76-81 (TXRING_AVAIL), if_vmxreg.h:115-125 (txcompdesc).
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_testablePatched-kernel validation impossible (no vmxnet3 device on KVM guest). Fix LOGIC validated in-replica on identical hostile inputs: fresh module load, test 900 -> 'packets sent=2000 alloc=2000 freed=2000 LEAKED=0', cluster count unchanged (fixrun.log).
fixrun.log: 'vmxrep900(fixed tx): packets sent=2000 alloc=2000 freed=2000 LEAKED=0 (expect 0)'; baseline leak in run.log
Confirmed kernel references
Detail
Exploit chain
host DMA -> txcd{gen ok, eop_idx=head-1} while vxtxr_next points at an earlier SOP -> txq_eof frees only the slot at next and sets next=eop_idx+1 (if_vmx.c:1966) -> intermediate SOP mbufs stranded -> ring wrap: encap overwrites txbuf[head].vtxb_m (if_vmx.c:2904) -> leak of mbuf+cluster per packet -> mb_map exhaustion -> network/kernel DoS
Evidence (decisive lines)
run.log: netstat -m clusters 256->1256->2256 across two runs (+1000 each, 515K->4515K); dmesg 'vmxrep3780: ... alloc=2000 freed=1000 LEAKED=1000' and 'avail=511 (ring claims EMPTY) while P2 mbuf still outstanding'. fixrun.log: fixed logic same inputs -> 'LEAKED=0'.
PoC changes
No seed PoC existed; built protocol-replica KLD (vmxrep.c) with verbatim vmxnet3_txq_eof copy + encap slot bookkeeping; fixed-variant test 900 added for fix-logic validation.
Verified recommended fix
Validate eop_idx against the outstanding window and free every mbuf in [next..eop_idx] exactly once in vmxnet3_txq_eof() (see fix.diff)
Verdict
vmxnet3_txq_eof() advances vxtxr_next from the unchecked host field txcd->eop_idx (if_vmx.c:1966); a forged completion naming a later packet's EOP hops next past intermediate SOP slots, whose mbufs are then unreachable and permanently leaked when vmxnet3_txq_encap() overwrites txbuf[head].vtxb_m (if_vmx.c:2904). Demonstrated on the guest kernel with a verbatim replica: exactly 1000 leaked 2048-byte clusters per 1000 forged completions, linear across runs, clusters pinned across module unload, TXRING_AVAIL simultaneously reporting the ring empty. Unbounded kernel memory exhaustion -> mbuf starvation DoS from a malicious host.
No comments yet.