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

update_rxhead path: unchecked 10-bit rxcd->qid produces OOB MMIO write up to 0x29F0 past the 4KB vmxnet3 BAR0 (panic / cross-device MMIO corruption)

Summary

When the host sets the host-writable rxq_shared.update_rxhead flag, vmxnet3_rxq_eof() computes the BAR0 offset from raw rxcd->qid after only subtracting nrxqueues: qid=1023 -> r = 0xA00+1022*8 = 0x29F0, far past the register file (RXH2 ends at 0xA78) and a 4KB BAR0, then executes vmxnet3_write_bar0 with a host-influenced value. Guest kernel page fault -> panic when the target is unmapped; foreign-device MMIO corruption when the adjacent page is mapped - both variants observed on the audit guest across runs (replica models BAR0 as a 1-page pmap_mapdev: 'Fatal trap 12 ... supervisor write ... Stopped at vmxrep_sysctl+0x62f: movl $0x1,(%rbx)' for the unmapped variant; 'OOB target is mapped FOREIGN MMIO' in an earlier placement). Fix validated in-replica (write suppressed, guest alive). Malicious host: deterministic guest panic or a write gadget into adjacent device MMIO, on every RX completion while update_rxhead stays set.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3781 Β· 10 files
FileTypeDescriptionSize
vmxrep.c β€” 17.5 KB view raw
Makefile β€” 73 B ↓ download
build.sh β€” 98 B view raw
run.sh β€” 159 B view raw
build.log β€” 5.4 KB view raw
panic.txt β€” 828 B view raw
fixrun.log β€” 548 B view raw
env.txt β€” 201 B view raw
fix.diff β€” 1.1 KB view raw
VERDICT.md β€” 2.8 KB ↓ raw
VERDICT.md
↓ download raw

DF-3781 VERDICT β€” REPRODUCED (mechanism, protocol-replica) β€” impact: panic (OOB MMIO write; cross-device corruption variant also demonstrated)

Bottom line

In the update_rxhead path of vmxnet3_rxq_eof() (if_vmx.c:2315-2327) the guest computes a BAR0 MMIO register offset from the raw 10-bit host field rxcd->qid after only subtracting nrxqueues. The derived index is never range-checked, so offsets up to 0xA00 + 1022*8 = 0x29F0 are written β€” past the 4KB vmxnet3 BAR0 (defined RXH2 space ends at 0xA78). The guest kernel executes an out-of-BAR MMIO write with a host-chosen offset and a host-influenced value, on every RX completion while the host keeps update_rxhead set (host-writable shared DMA).

Proof on the guest

  1. Probe (sysctl hw.vmxrep.run=37811): pages at BAR0+0x1000 and BAR0+0x2000 are unmapped KVA in the tested layout β†’ any qid mapping there faults.
  2. Write (hw.vmxrep.run=3781, hostile qid=705 β†’ offset 0x2000): Fatal trap 12, supervisor write, fault virtual address 0xffffffff82605000 (= BAR0 page + 0x2000), Stopped at vmxrep_sysctl: movl $0x1,(%rbx) β€” the exact vmxnet3_write_bar0(sc, r, idx) store (if_vmx.c:2326/3867-3871). panic.txt.
  3. Cross-device variant: in an earlier run (different mapdev placement) the target at +0x29F0 resolved to a mapped foreign MMIO page β€” the write would have corrupted another device's registers. Both outcomes observed on this guest across two boots; the host chooses qid and therefore the outcome.

Threat model / severity

Malicious (compromised ESXi backend / nested-hypervisor) or buggy host. The fault variant is a deterministic guest panic (DoS); the foreign-MMIO variant is an OOB write gadget into adjacent device MMIO (write value 0..ndesc-1, host-chosen offset within 0xA78..0x2FF0), whose ceiling depends on the platform's MMIO layout. Medium within the paravirtual trust boundary.

Fix validation

Kernel-build validation impossible (no vmxnet3 device on the KVM guest). Fix LOGIC validated in the replica (test 902, fixrun.log): with the range check if (qid >= nrxqueues) suppress, the identical hostile qid=705 produces "MMIO write suppressed, guest alive". fix.diff authors the check.

Kernel refs

sys/dev/virtual/vmware/vmxnet3/if_vmx.c:2315-2327 (the block), if_vmx.c:2320 (idx from rxd_idx), if_vmx.c:3867-3871 (write_bar0), if_vmxreg.h:63-64 (RXH1/RXH2 register file), if_vmxreg.h:148 (qid:10), if_vmxreg.h:298-300 (update_rxhead in host-writable rxq_shared).

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 (test 902, fixrun.log): identical hostile qid=705 -> write suppressed, guest alive.

fixrun.log: 'vmxrep902(fixed rxhead): derived qid 704 >= nrxqueues 1 - MMIO write suppressed, guest alive'; baseline panic in panic.txt
↓ fix.diffper-fix-DF-3781

Confirmed kernel references

Detail

Exploit chain

host sets rxq_shared.update_rxhead=1 (host-writable DMA) -> delivers rxcd{qid=705..1023, rxd_idx controlling idx value} -> if_vmx.c:2321-2325 subtracts nrxqueues with no bound -> r = 0xA00 + qid*8 up to 0x2FF0 -> vmxnet3_write_bar0 (if_vmx.c:3867-3871) writes past the 4KB BAR -> unmapped: fatal trap 12 panic; mapped: foreign device MMIO corruption

Evidence (decisive lines)

panic.txt: 'host chose qid=705 -> MMIO offset 0x2000 ... Fatal trap 12 ... fault virtual address = 0xffffffff82605000 ... Stopped at vmxrep_sysctl+0x62f: movl $0x1,(%rbx)'. Probe (run 37811): pages at BAR0+0x1000/+0x2000 unmapped. First-run variant in session log: 'OOB target is mapped FOREIGN MMIO'. fixrun.log: 'vmxrep902(fixed rxhead): derived qid 704 >= nrxqueues 1 - MMIO write suppressed, guest alive'.

PoC changes

No seed PoC; built replica module modelling BAR0 as 1-page pmap_mapdev (what bus_alloc_resource_any does for a 4KB BAR) with the identical offset math + write; added target-page probe (37811) after the first run landed on mapped foreign MMIO.

Verified recommended fix

Range-check the derived queue index against nrxqueues before VMXNET3_BAR0_RXH2 and suppress the write otherwise (see fix.diff)

Verdict

The update_rxhead block of vmxnet3_rxq_eof() (if_vmx.c:2315-2327) computes a BAR0 MMIO offset from the raw host-supplied 10-bit rxcd->qid after only subtracting nrxqueues, with no range check: offsets up to 0xA00+1022*8=0x29F0 are written on a 4KB BAR0 (register file ends at 0xA78). Proven on the guest kernel with the replica: hostile qid=705 -> write at BAR0+0x2000 -> fatal trap 12 supervisor write at unmapped 0xffffffff82605000 (movl $0x1,(%rbx)). The cross-device variant was also observed: an earlier mapdev placement had the OOB target resolve to MAPPED FOREIGN MMIO, i.e. an out-of-BAR write into another device's registers. Host-chosen offset, host-influenced value. Panic DoS guaranteed; MMIO corruption ceiling platform-dependent.