As already discussed in a number of reports in this tracker (#285, #286, #287, #288, #289, #292), VMware Workstation (current version 12.1.1 build-3770994) ships with a feature called "Virtual Printers", which enables the virtualized operating systems to access printers installed on the Host. Inside the VM, the communication takes place through a COM1 device, and the incoming data is handled by a dedicated "vprintproxy.exe" process on the Host, as launched by the "vmware-vmx.exe" service. Administrative privileges are not required to access COM1 in the guest, at least on Windows.
The vprintproxy.exe is a significant attack surface for potential VM escapes. Due to its nature, the application implements support for a variety of complex protocols and file formats, such as the printing protocol, EMFSPOOL format, and further embedded EMFs, fonts, images etc. This report addresses a bug in the handling of EMF files in the TPView.DLL library extensively used by vprintproxy.exe.
The version of the TPView.DLL file referenced in this report is 9.4.1045.1 (md5sum b6211e8b5c2883fa16231b0a6bf014f3).
At address 0x10038AF0, there is a function called CEMF::EnhMetaFileProc, which is used as a custom callback to the EnumEnhMetaFile API. It is responsible for the special handling of several chosen EMF records, such as EMR_SMALLTEXTOUT, EMR_EXTCREATEFONTINDIRECTW, EMR_EXTTEXTOUTA or EMR_EXTTEXTOUTW. The double-free bug resides in a nested function processing EMR_SMALLTEXTOUT records, located at address 0x100391B0. The epilogue of the function, as generated by Hex-Rays, is as follows:
--- cut ---
if ( v12 & 0x100 && !(v12 & 4) || *(a3 + 44) )
{
*a4 = v8;
v8 = 0;
v14 = 1;
}
else
{
v17 = *(v8 + 4);
v16 = v8 + 52;
v13 = *(v8 + 2);
if ( v12 & 0x200 )
{
ExtTextOutA(hdc, v13, *(v8 + 3), a6 & v12, 0, v16, v17, 0);
free(v8); <========== Free #1
v14 = 0;
}
else
{
ExtTextOutW(hdc, v13, *(v8 + 3), a6 & v12, 0, v16, v17, 0);
free(v8); <========== Free #1
v14 = 0;
}
}
free(v8); <========== Free #2
return v14;
}
--- cut ---
On the above listing, it is clearly visible that if a conditional branch is taken to either of the ExtTextOut API calls, the buffer under "v8" is freed twice -- once directly after the calls, and then at the end of the function. The behavior is also easily confirmed in the assembly view:
--- cut ---
.text:100392BA push eax ; options
.text:100392BB mov eax, [edi+0Ch]
.text:100392BE push eax ; y
.text:100392BF push ecx ; x
.text:100392C0 push edx ; hdc
.text:100392C1 call ds:ExtTextOutA
.text:100392C7 push edi ; void *
.text:100392C8 call _free <========== Free #1
.text:100392CD add esp, 4
.text:100392D0 xor esi, esi
.text:100392D2 jmp short loc_10039349
.text:100392D4 ; ---------------------------------------------------------------------------
.text:100392D4
.text:100392D4 loc_100392D4: ; CODE XREF: Handle_EMR_SMALLTEXTOUT+105j
.text:100392D4 and eax, [ebp+arg_C]
.text:100392D7 push eax ; options
.text:100392D8 mov eax, [edi+0Ch]
.text:100392DB push eax ; y
.text:100392DC push ecx ; x
.text:100392DD push edx ; hdc
.text:100392DE call ds:ExtTextOutW
.text:100392E4 push edi ; void *
.text:100392E5 call _free <========== Free #1
.text:100392EA add esp, 4
.text:100392ED xor esi, esi
.text:100392EF jmp short loc_10039349
...
.text:10039349 loc_10039349: ; CODE XREF: Handle_EMR_SMALLTEXTOUT+122j
.text:10039349 ; Handle_EMR_SMALLTEXTOUT+13Fj ...
.text:10039349 push edi ; void *
.text:1003934A call _free <========== Free #2
--- cut ---
Unfortunately, in order to reach the vulnerable condition, the "v12 & 0x100 && !(v12 & 4) || *(a3 + 44)" condition must evaluate to false. During some brief experimentation with Kostya's exploit from bug #287 , I was unable to set the value at *(a3 + 44) to zero, thus failing to direct code execution to the vulnerable code area (performing the first free()). However, since this is such an obvious bug, and may be triggerable under the right configuration or with the right interaction with the Printer Proxy, I am still reporting it without a working proof of concept to demonstrate a crash.
This bug is subject to a 90 day disclosure deadline. If 90 days elapse without a broadly available patch, then the bug report will automatically become visible to the public.
Status: Fixed