September 2026 · ZDI-26-647 / CVE-2026-59346 (CVSSv3 9.3 vendor / 7.5 ZDI) · Fixed in VMware Workstation/Fusion 26H1u1 (VMSA-2026-0007)
Summary
A 32-bit integer overflow in the VMXNET3 TSO segmentation code in vmware-vmx. The function computes a buffer allocation as seg_count * per_seg_size using a 32-bit imul. When the product exceeds 232, Mem_Malloc receives the truncated value, but the segmentation loop writes the full amount into the undersized buffer. The result is a heap overflow with attacker-controlled content in the host VMX process.
This is a bypass of CVE-2025-41236. That patch added bounds checks on individual descriptor fields, but never validated their product, which is the value that actually wraps. The root cause survived the fix.
Background
VMXNET3 is VMware’s paravirtualized network adapter and the default NIC type for modern VMware guests. When a guest sends a TCP Segmentation Offload packet, the host-side vmware-vmx process splits it into MSS-sized segments: read the descriptor fields from the guest TX ring (header length, MSS, payload length), compute the segment count, allocate a buffer for all segments, then copy packet headers into each slot.
The attack surface is the guest TX ring. A guest with administrative privileges can craft raw TX descriptors, bypassing the in-guest kernel driver’s own TSO logic, and ring the VMXNET3 doorbell register at BAR0+0x600 to trigger host-side processing. This is the entry point for both CVE-2025-41236 and this bypass.
The CVE-2025-41236 patch and its gap
The CVE-2025-41236 fix added four bounds checks to the TSO function. They run after the host reads the guest’s TX descriptor fields and before the allocation. In pseudocode:
if (MSS > 9216) goto reject;
if (per_seg_size > 9216) goto reject;
if (MSS + per_seg_size > 9216) goto reject;
If any check fails, the function logs NetPkt: TxOffload: expectations not satisfied and returns NULL. In disassembly (offsets from Workstation 25.0.1 build 25219725; will differ in other builds):
; Check 1: MSS > 9216?
cmp $0x2400, %r12d ; r12d = MSS
seta %r8b
; Check 2: per_seg_size > 9216?
cmp $0x2400, %r13d ; r13d = per_seg_size
ja reject
; Check 3: deferred MSS check
test %r8b, %r8b
jne reject
; Check 4: MSS + per_seg_size > 9216?
lea (%r12,%r13,1), %edx
cmp $0x2400, %edx
ja reject
All four checks validate individual fields or their sum against a fixed ceiling. None of them checks seg_count. None of them checks the product seg_count * per_seg_size. These are the values that determine the allocation size, and they are the values that overflow.
The gap is conceptual: bounding multiplicands individually does not bound their product. A small MSS maximises seg_count (computed as ceil(payload / MSS)), and if per_seg_size stays under 9216, every check passes while the product wraps 32 bits. The CVE-2025-41236 patch prevented the specific trigger that was originally reported but did not address the root cause.
The overflow
After the bounds checks pass, the function finalises per_seg_size with 8-byte alignment and multiplies it by seg_count:
add $0x11, %edx ; header_total + 17
and $0xfffffff8, %edx ; 8-byte align → per_seg_size
mov 0x24(%rsp), %edi ; edi = seg_count
imul %edx, %edi ; edi = seg_count * per_seg_size (32-BIT)
call Mem_Malloc ; allocates the truncated value
The imul operates on 32-bit registers (%edi, %edx), discarding the upper 32 bits of the result. When the true product exceeds 232, Mem_Malloc receives only the low 32 bits. The segmentation loop then iterates over all seg_count segments and writes per_seg_size bytes of packet headers per segment into the undersized buffer.
A worked example. Set MSS to 1 and payload length high enough to yield a seg_count of 688,297. After alignment, per_seg_size is 6,240 (under the 9,216 cap, so all four checks pass). The true product is 688,297 × 6,240 = 4,294,973,280. In 32 bits that wraps to 5,984. Mem_Malloc allocates roughly 6 KB. The segmentation loop writes 6,222 bytes of packet headers (Ethernet + IPv6 + extension headers + TCP) per segment for all 688,297 iterations. Total write: approximately 4.3 GB into a 6 KB buffer.
The overflow content is attacker-controlled: it comes from the guest’s packet headers, which are fully specified in the TX descriptor chain. The per-segment write layout:
Bytes 0-9: internal per-segment header (10 bytes)
Bytes 10-23: Ethernet header (14 bytes)
Bytes 24-63: IPv6 header (40 bytes)
Bytes 64-6207: 3× IPv6 Destination Options (3 × 2,048 = 6,144 bytes)
Bytes 6208-6231: TCP header (24 bytes)
Total per segment: 6,222 bytes written, per_seg_size: 6,240
The overflow size is tunable. The modular arithmetic solver selects input parameters that produce a specific wrap residue, and therefore a specific overflow length. With the maximum-overflow parameters, writes continue through adjacent heap chunks and eventually reach unmapped memory, crashing vmware-vmx with SIGSEGV. That is the blunt version. The interesting case is the controlled one.
Overflow geometry and heap metadata corruption
Choose parameters where (seg_count × per_seg_size) mod 232 yields a value just 256 bytes below per_seg_size. With a wrap residue of 5,984, Mem_Malloc(5984) produces a glibc chunk of 6,000 bytes (0x1770). The buffer starts at chunk_addr + 16, giving 5,984 bytes of usable space. The first segment’s SgCopy write puts 6,222 bytes into that 5,984-byte buffer, overflowing by exactly 238 bytes into the adjacent heap.
What sits at those offsets is the N+1 glibc chunk’s metadata:
Buffer + 5984: N+1 chunk prev_size (8 bytes)
Buffer + 5992: N+1 chunk size (8 bytes)
Both fields land within the IPv6 Destination Options padding region. Destination Options headers carry a variable-length padding field whose content is entirely guest-specified. The attacker writes arbitrary 8-byte values to both prev_size and size.
This is silent corruption. By writing valid-looking metadata (a size value with PREV_INUSE set, an aligned prev_size), free() processes the corrupted chunk without triggering glibc’s integrity checks. No crash, no abort, no signal. The corrupted metadata sits dormant until the adjacent chunk is later freed or reallocated, at which point it is consumed by glibc’s allocator. From there the standard glibc heap exploitation primitives apply: unsafe unlink for an arbitrary write, tcache poisoning for allocation to an arbitrary address, or FSOP via corrupted FILE stream structures in the vmware-vmx process.
The content of the overflow (which bytes land on the metadata fields) and the size of the overflow (how far past the buffer it writes) are both derived from the guest’s packet construction. The attacker controls what is written and where, relative to the allocation, by choosing the Destination Options padding content and the modular arithmetic parameters independently.
Impact
Denial of service is the immediate outcome with untuned parameters: the host vmware-vmx process crashes (SIGSEGV when writes reach unmapped memory). The crash log shows the fault in the VMXNET3 TX path on the vcpu-0 thread, with a glibc realloc/abort backtrace confirming heap corruption.
With tuned parameters, the vulnerability provides deterministic, silent corruption of glibc heap metadata with fully attacker-controlled content and offsets. This is a guest-to-host code execution primitive against the vmware-vmx process, not merely a denial-of-service bug.
Prerequisites: administrative privileges in the guest VM, and a VMXNET3 virtual network adapter (the default for modern VMware guests). Affected products: Workstation, Fusion, Player, and ESXi. All share the same VMXNET3 TSO segmentation code in vmware-vmx. On ESXi the VMX process runs inside a sandbox, which limits the post-exploitation surface but does not prevent the overflow itself.
The fix
The root cause is the 32-bit truncation in the imul. The fix is straightforward: either perform the multiplication in 64-bit arithmetic so the product is never silently truncated, or validate that the product does not exceed a reasonable allocation limit before calling Mem_Malloc. Bounding individual descriptor fields (the CVE-2025-41236 approach) is necessary for sanity-checking inputs but is not sufficient to prevent the overflow, because the dangerous value is the product of two fields, not either field alone.
Fixed in VMware Workstation and Fusion 26H1u1, released September 3, 2026 (VMSA-2026-0007).
Timeline
2026-04-26 — Reported to Trend Micro ZDI
2026-08-26 — ZDI notifies vendor (Broadcom)
2026-09-03 — Broadcom ships 26H1u1 (VMSA-2026-0007)
2026-09-09 — ZDI advisory published (ZDI-26-647)
