vmxnet3_rxq_eof() selects UNPOPULATED RX ring 1 from host rxcd->qid -> NULL-mbuf write -> guest kernel panic
Summary
The ring used for a completion is chosen by the host-supplied 10-bit rxcd->qid; DragonFly always runs with nrxqueues=1 (MSI-X unsupported) and leaves ring 1 unpopulated (all vrxb_m==NULL) on every default MTU config. One hostile descriptor {qid>=1, in-bounds rxd_idx, sop=1} yields m==NULL and the sop path writes m->m_pkthdr.rcvif/len through it - supervisor write at fixed near-NULL (0x18 observed), fatal trap 12. KASSERT guards compiled out on production kernels. Distinct from DF-1213 (in-bounds idx here) and DF-1214 (no cluster confusion). REPRODUCED ON THE GUEST KERNEL via a protocol-replica KLD injecting the hostile descriptor as the host would DMA it: 'Fatal trap 12, fault virtual address = 0x18, supervisor write, Stopped at vmxrep_sysctl+0x252: movl $0x40,0x18(%rbx)' (0x40 == hostile len); reproduced twice incl. clean rerun. Fix validated in-replica (no crash on same input). Malicious/compromised ESXi host kills a DragonFly guest with a single forged RX completion; driver compiled into GENERIC.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3779 Β· 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 | β | 129 B | view raw | |
| build.log | β | 5.4 KB | view raw | |
| panic.txt | β | 827 B | view raw | |
| fixrun.log | β | 548 B | view raw | |
| env.txt | β | 201 B | view raw | |
| fix.diff | β | 1.2 KB | view raw | |
| VERDICT.md | β | 3.8 KB | β raw |
DF-3779 VERDICT β REPRODUCED (mechanism, protocol-replica) β impact: panic
Bottom line
A single hostile RX completion descriptor with an in-bounds rxd_idx,
sop=1, and qid >= nrxqueues makes vmxnet3_rxq_eof() select RX ring 1,
which the driver leaves unpopulated (all vrxb_m == NULL) on every default
configuration. The local m fetched at if_vmx.c:2223 stays NULL through the
sop path and if_vmx.c:2265-2266 write through it β supervisor write to a
fixed near-NULL address (0x18 observed) β fatal trap 12 β guest kernel panic.
The KASSERTs guarding the ring/btype invariants (if_vmx.c:2224, 2241-2249)
are compiled out on production kernels.
Why source-certain
- Ring selection from
rxcd->qidwith no validation: if_vmx.c:2217-2220 (same pattern invmxnet3_rxq_discard_chain, if_vmx.c:2086-2089). qidis a 10-bit host field (if_vmxreg.h:148); DragonFly never negotiates more than 1 RX queue (MSI-X unsupported on DFly: if_vmx.c:812-813 errors out,vmxnet3_check_multiqueue()if_vmx.c:587-589 forces 1/1) β so any qid != 0 selects ring 1.- Ring 1 unpopulated at default MTU: if_vmx.c:2563-2581 (
populate = 1wheneverframe_size <= MCLBYTES(2048) + MJUMPAGESIZE(4096), i.e. every MTU <= 6126); ring 1's rxbuf array is only ever zero-filled (vmxnet3_init_rxq, if_vmx.c:1047-1048 M_ZERO). vmxnet3_newbuf()at if_vmx.c:2257 refillsrxbuf[idx]aftermwas fetched β the NULL local is dereferenced regardless.- The completion ring, the descriptors and
update_rxheadall live in host-writable shared DMA memory (the driver itself reads host-written stats back from the same page: if_vmx.c:3684-3753).
How it was proven on the guest
Protocol-replica KLD (vmxrep.c) β copies the exact ring structures and the verbatim sop-path code of vmxnet3_rxq_eof, with the hostile descriptor injected as the host would DMA it. Run on DragonFly 6.5-DEVELOPMENT X86_64_GENERIC (production kernel, no INVARIANTS):
vmxrep3779: m == NULL (ring 1 unpopulated); if_vmx.c:2265 ... next Fatal trap 12: page fault while in kernel mode fault virtual address = 0x18 fault code = supervisor write data, page not present Stopped at vmxrep_sysctl+0x252: movl $0x40,0x18(%rbx) # 0x40 = the rxcd->len
(panic.txt; reproduced twice β initial run + clean rerun after reset.)
The store instruction is the replicated m->m_pkthdr.len = m->m_len =
length (0x40 == the hostile len=64; m in %rbx == 0).
Exploitability
Fixed-address near-NULL write (address = offsetof(m_pkthdr) constants, value
partially host-influenced via length). No controllable pointer target β
panic-only on this arch (no SMEP/SMAP/KASLR irrelevant here). Within the
malicious-host threat model (compromised ESXi backend, nested-hypervisor
attack, or a buggy host offloading to ring 1 by mistake) this is a
one-descriptor guest kill.
Fix validation
Full patched-kernel validation is not possible on this guest (no vmxnet3
device to attach the real driver). The fix logic was validated with the
same replica: test 901 (fixrun.log) drops the completion when the selected
ring is unpopulated β no crash, guest alive:
vmxrep901(fixed rx): unpopulated ring selected - completion dropped, no NULL deref, guest alive
fix.diff adds the equivalent NULL guard after the mbuf fetch in vmxnet3_rxq_eof() (and notes the same guard belongs in the discard path).
Kernel refs
sys/dev/virtual/vmware/vmxnet3/if_vmx.c:2217-2220, 2223, 2257, 2265-2266, 2563-2581, 1047-1048, 2086-2089; if_vmxreg.h:143-171.
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 on this guest (no vmxnet3 PCI device; the real driver cannot attach). Fix LOGIC validated in-replica: fixed ring-selection path drops the completion on NULL mbuf under the identical hostile input - no crash, guest alive (fixrun.log: vmxrep901). fix.diff authors the guard for if_vmx.c:2223.
fixrun.log: 'vmxrep901(fixed rx): unpopulated ring selected - completion dropped, no NULL deref, guest alive'; baseline panic in panic.txt
Confirmed kernel references
Detail
Exploit chain
host DMA -> rxcd{qid=1,rxd_idx=0(in-bounds),sop=1,eop=1,len=64} -> ring1 selected (if_vmx.c:2217-2220) -> m=NULL (if_vmx.c:2223, ring1 never populated at default MTU) -> KASSERTs compiled out -> m->m_pkthdr.rcvif=ifp (if_vmx.c:2265) -> fatal trap 12 write 0x18 -> guest panic
Evidence (decisive lines)
panic.txt: 'vmxrep3779: m == NULL ... Fatal trap 12 ... fault virtual address = 0x18 ... Stopped at vmxrep_sysctl+0x252: movl $0x40,0x18(%rbx)' (0x40 = hostile len=64). Reproduced twice incl. clean rerun after vm.sh reset with-src. fixrun.log: fixed logic (test 901) survives identical input.
PoC changes
Seed sketch replaced entirely: no PoC existed. Built protocol-replica KLD (vmxrep.c) copying the exact ring structs/gen protocol and the verbatim sop block; fixed a cosmetic %p-vs-string printf bug after the first run (fault evidence unaffected).
Verified recommended fix
NULL-guard the mbuf after the ring selection in vmxnet3_rxq_eof() and drop the completion (see fix.diff)
Verdict
One hostile RX completion (in-bounds rxd_idx, sop=1, qid>=nrxqueues) makes vmxnet3_rxq_eof() select the unpopulated ring 1 (default configs never populate it, if_vmx.c:2563-2581); the mbuf fetched at if_vmx.c:2223 is NULL and the sop path writes m->m_pkthdr.rcvif/len (if_vmx.c:2265-2266) through it. Proven on the guest kernel with a verbatim protocol-replica module: fatal trap 12, supervisor write at 0x18 (movl $0x40,0x18(%rbx)) - the rxcd->len store. Production kernels compile out the guarding KASSERTs. Panic/DoS in the malicious-host threat model; the vmx driver is compiled into GENERIC.
No comments yet.