β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3780

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3780 Β· 10 files
FileTypeDescriptionSize
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
VERDICT.md
↓ download 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_testable
baseline reproduced→ patch + rebuild →patched clean

Patched-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
↓ fix.diffper-fix-DF-3780

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.