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

Unchecked device-supplied rxcd->rxd_idx yields OOB array access, OOB write, arbitrary-pointer write, and infinite loop in RX completion path

Summary

vmxnet3_rxq_eof() and vmxnet3_rxq_discard_chain(): rxcd->rxd_idx is 12-bit (0-4095 per if_vmxreg.h:144) but used to index vxrxr_rxd[]/vxrxr_rxbuf[] at if_vmx.c:2221/2223 with no check against rxr->vxrxr_ndesc(32-2048). Consequences: (a) OOB WRITE rxd->gen at :2057 in discard path, (b) OOB READ vrxb_m at :2223 -> arbitrary write via m->m_pkthdr.rcvif=ifp/:2265/:2266 if non-NULL, (c) infinite loop at :2233 because vxrxr_fill wraps at ndesc can never equal idx>=ndesc. Malicious/buggy VMware hypervisor (or nested-virt L1) injects crafted RX completion. Same defect class fixed in FreeBSD vmxnet3, never ported to DFly. Fix: validate idx<ndesc before any array use.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1213 Β· 9 files
FileTypeDescriptionSize
VERDICT.md verdict full path:line trace + reachability analysis 3.7 KB ↓ raw
README.md readme claim, verdict, threat model, fix 1.9 KB ↓ raw
fix.diff suggested-fix bounds-check rxcd->rxd_idx vs vxrxr_ndesc at both consumption sites (eof + discard_chain) 985 B view raw
run.sh reachability-probe shell probe: is a vmxnet3 interface present? 570 B view raw
build.sh build-noop no userspace build (trigger is device-written rxcd) 283 B view raw
fix_build.log build-log GENERIC kernel build with the fix applied (rc=0; if_vmx.o/if_vmx.ko clean) 5.6 MB ↓ download
env.txt environment guest uname, cc, device topology 718 B view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme claim, verdict, threat model, fix
↓ download raw

DF-1213 β€” Unchecked device-supplied rxcd->rxd_idx β†’ OOB in vmxnet3 RX path

Claim

vmxnet3_rxq_eof() and vmxnet3_rxq_discard_chain() use the device-supplied completion descriptor field rxcd->rxd_idx (12-bit, 0-4095 per if_vmxreg.h:144) to index rxr->vxrxr_rxd[] / rxr->vxrxr_rxbuf[] (if_vmx.c:2221/2223) and vmxnet3_rxq_eof_discard() (if_vmx.c:2056) with no check against rxr->vxrxr_ndesc (32-2048). Consequences of a malicious rxd_idx: (a) OOB WRITE rxd->gen at if_vmx.c:2057 in the discard path; (b) OOB READ vrxb_m at if_vmx.c:2223, then if non-NULL an arbitrary write via m->m_pkthdr.rcvif = ifp (:2265) and m->m_pkthdr.len = m->m_len = length (:2266); (c) infinite loop if idx never equals vxrxr_fill (the catch-up at :2232-2237).

Verdict

NOT TESTABLE on this audit guest (real bug, traced line-by-line; fix authored and compile-validated into the GENERIC kernel). See VERDICT.md.

Why not reproduced here

  • The guest NIC is vtnet0 (virtio-net); vmxnet3 (vmx0) is not attached (ifconfig vmx0 β†’ "does not exist"). device vmx is in GENERIC but only attaches to a VMware vmxnet3 PCI device, which QEMU (virtio machine) does not present.
  • The malicious value comes from the vmxnet3 RX completion ring (device-written), not from any userspace syscall; a benign device always returns valid indices.

Trigger (threat model)

A malicious VM host / hypervisor whose vmxnet3 device model writes RX completion descriptors with rxd_idx >= ndesc. This is the host→guest (guest-escape-direction) attack surface for any VM running on a hostile or compromised host, plus buggy device models. Most directly relevant in multi-tenant virtualization.

Fix

fix.diff validates idx < rxr->vxrxr_ndesc at both consumption sites (discard chain :2084 and eof :2215), logging and breaking out of the RX loop on a bad index (desync with the device β‡’ stop processing).

VERDICT.md verdict full path:line trace + reachability analysis
↓ download raw

DF-1213 β€” VERDICT

Finding: Unchecked device-supplied rxcd->rxd_idx yields OOB array access, OOB write, arbitrary-pointer write, and a possible infinite loop in the vmxnet3 RX completion path (sys/dev/virtual/vmware/vmxnet3/if_vmx.c). Status: NOT TESTABLE on this audit guest. Confidence (bug is real): certain. Impact ceiling: OOB read + OOB/arbitrary write via device-controlled mbuf pointer deref + DoS loop; device-controlled (malicious vmxnet3 device). Fix: authored in fix.diff, applied clean, compile-validated into GENERIC.

Mechanism (confirmed line-by-line in sys/)

RX completion descriptors are written by the vmxnet3 device into a ring the kernel reads. The per-descriptor rxd_idx selects which RX buffer the descriptor refers to.

  1. vmxnet3_rxq_eof() (if_vmx.c): - if_vmx.c:2202 reads rxcd = &rxc->vxcr_u.rxcd[rxc->vxcr_next]. - if_vmx.c:2215 β€” idx = rxcd->rxd_idx; (12-bit device value, 0-4095). - if_vmx.c:2217-2220 picks rxr by rxcd->qid. - if_vmx.c:2221 β€” rxd = &rxr->vxrxr_rxd[idx]; NO bounds check (vxrxr_rxd has vxrxr_ndesc = 32/64/128/256/512/1024/2048 entries). - if_vmx.c:2223 β€” m = rxr->vxrxr_rxbuf[idx].vrxb_m; OOB read. - if_vmx.c:2232-2237 β€” catch-up loop while (rxr->vxrxr_fill != idx) writes vxrxr_rxd[fill].gen (more OOB writes) and can loop forever if idx is out of the fill range. - if_vmx.c:2265-2266 β€” m->m_pkthdr.rcvif = ifp; m->m_pkthdr.len = m->m_len = length; β€” if the OOB m is a non-NULL attacker-influenced pointer, this is an arbitrary write through m_pkthdr/m_len.

  2. vmxnet3_rxq_discard_chain() (if_vmx.c:2062): - if_vmx.c:2084 β€” idx = rxcd->rxd_idx; - if_vmx.c:2090 β€” vmxnet3_rxq_eof_discard(rxq, rxr, idx); - if_vmx.c:2056-2057 β€” rxd = &rxr->vxrxr_rxd[idx]; rxd->gen = ...; OOB write of rxd->gen at an attacker-chosen idx.

vxrxr_ndesc (the valid bound) is u_int (if_vmxvar.h:92); it is set at ring init to one of the power-of-two ring sizes and is the natural check.

Why it is NOT TESTABLE on this guest

  • ifconfig shows only vtnet0 (virtio-net) and lo0; ifconfig vmx0 β†’ "interface does not exist". device vmx is in X86_64_GENERIC but vmxnet3 only attaches to the VMware vmxnet3 PCI device (vendor 0x15ad), which the QEMU virtio machine does not present.
  • The malicious rxd_idx is written by the device into the completion ring; there is no userspace syscall that makes a benign device emit a bad index.

Valid "device-controlled, not reachable from an unprivileged user on this guest" case. Bug is genuine (no bounds check at either idx consumption site); threat model = malicious vmxnet3 device model (host→guest).

Exploit chain

None developed: device-controlled primitive with no unprivileged syscall path, and no vmxnet3 device on this guest. Documented impact ceiling: OOB write of rxd->gen (discard), OOB read of vrxb_m + arbitrary write through the returned mbuf pointer (eof), and a possible infinite loop.

Fix

fix.diff adds if (idx >= (int)rxr->vxrxr_ndesc) { device_printf(...); break; } at both consumption sites β€” in vmxnet3_rxq_discard_chain before calling vmxnet3_rxq_eof_discard, and in vmxnet3_rxq_eof before indexing vxrxr_rxd[]. A bad index means desync with the device; the safest action is to stop processing that ring (break), matching the existing "host skip" handling philosophy. Uses sc->vmx_dev (the real softc field, if_vmxvar.h:202).

Build / run on this guest

./run.sh is a reachability probe; on this guest it reports "no vmx0 β†’ vmxnet3 RX path unreachable". Bug confirmed by the trace above; fix compile-validated into GENERIC.

Fix verification

not_testable

compile validated -Werror

module/kernel build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. vmxnet3 rxcd->rxd_idx 12-bit no bounds vs vxrxr_ndesc. No vmxnet3 NIC. (Finding said bnx2x but file is vmxnet3.)