Detailing Two VMware Workstation TOCTOU Vulnerabilities

October 22, 2020 | Reno Robert

On October 20, VMware released a security patch addressing six vulnerabilities in VMware ESXi, Workstation, Fusion, and NSX-T. Two of those bugs fall into the category of Time-of-check Time-of-use (TOCTOU) race conditions. Now that the patch is out, I wanted to detail these TOCTOU bugs and their impact on VMware systems.

The Vulnerability

VMware Workstation uses a modified PhoenixBIOS 4.0 Release 6 for its legacy BIOS emulation. One of the modifications observed during the analysis of the BIOS.440.ROM image is the usage of a VMware backdoor. “Backdoor” in this context does not have any malign implication. Rather, “backdoor” refers to a legitimate channel by which the virtual guest can communicate with the hypervisor. In this case, the backdoor is implemented via an emulated I/O port. The guest sends a message via the backdoor by executing the following instructions:

By cross-referencing the backdoor call along with definitions from open-vm-tools, the following set of commands were identified as being used in the ROM image:

Each one of these commands refers to a backdoor function, implemented on the host, that can be invoked from the guest by passing appropriate values to the emulated port shown above.

The BDOOR_CMD_PATCH_ACPI_TABLES command turned out to be the most interesting to analyze since it parses ACPI tables from the guest memory. The following analysis is based on VMware Workstation for Linux version 15.5.6.

The backdoor function implementing command BDOOR_CMD_PATCH_ACPI_TABLES starts by checking the VMware Tools version installed in the guest and the current privilege level (CPL) of the guest processor making the invocation.

The Get_VMTools_Version function returns the VMware Tools version as an encoded integer, which is defined in open-vm-tools as follows:

     #define TOOLS_VERSION_UINT(MJR, MNR, BASE) (((MJR) << 10) + ((MNR) << 5) + (BASE))

The BDOOR_CMD_PATCH_ACPI_TABLES command is usable only if the guest reports a VMware Tools version less than 6.0.0. Also, there is a check to make sure the command is invoked from CPL 0, also known as ring 0, which is the highest privilege level within the guest. This is probably intended to restrict the usage of this backdoor command to guest boot code. Once the validations are done, the guest BIOS high memory area from 0xE0000 to 0xFFFFF is scanned for the signature “RSD PTR ” on a 16-byte boundary to locate the Root System Description Pointer (RSDP) structure.

After this, the rest of the ACPI data structures are also parsed to locate the Differentiated System Description Table (DSDT).

Once the DSDT is found, the backdoor function patches out AML code related to the S1 sleep state by finding and replacing _S1 with FOO.

Testing

In order to test this, we need to dump the DSDT table and reboot the guest after reporting a VMware Tools version less than 6.0.0 to the host. Going through open-vm-tools, it is understood that “tools.set.version” is the GuestRPC command to set this info.

After the reboot, we dump the DSDT table again and diff the ASL code. 

The S1 sleep state has been patched out and the table checksum has been updated correspondingly.

Vulnerabilities

I discovered two distinct Time-of-check Time-of-use (TOCTOU) vulnerabilities during my analysis. One is a constrained Out-Of-Bound (OOB) write, and the other is an OOB read that can lead to information disclosure.

The DSDT table contains an ACPI header followed by AML byte code. The ACPI header is as follows:

The most interesting fields of the header for this writeup are length and checksum. The table length and checksum are first validated in the ValidateAndGetACPITable function called at 0x01D9C6A:

In summary, ValidateAndGetACPITable first reads the ACPI table size from guest memory. Then, using that size value, it maps the entire table from guest physical memory into host memory. It then sums the bytes in the mapped memory to calculate the ACPI checksum to validate the table.

CVE-2020-3982/ZDI-20-1268

After validating the table, the code reads the ACPI table length once more from guest memory (this time via the mapping), and performs a search for _S1 in DSDT AML code.

A guest OS can change the table size value between the two fetches, leading to a constrained OOB write primitive: finding _S1 and replacing it with FOO.

CVE-2020-3981/ZDI-20-1267

Once the S1 sleep object is patched, the checksum in the header needs to be updated. In preparation for calculating the new checksum, the code retrieves the table length yet again from guest memory:

If the guest has increased the value of the length field before this read occurs, the result will be an out-of-bounds read during checksum computation.

Proof of Concept

Though this backdoor function is meant to be invoked only once during the execution of trusted BIOS code, it is not disabled post-boot and continues to be accessible even for the guest OS. Since the BIOS memory area is writable, a fake RSDP structure can be inserted by the guest at the address 0xE0000 before invoking the backdoor. Since the RSDT physical address is set up in a fake RSDP structure, the entire ACPI parsing can be hijacked:

Figure 1 - Hijacking the ACPI parsing

An attacker would need to set up the DSDT table at the end of Guest RAM such that any OOB access will end up in the host memory adjacent to the mapped guest memory area. While the OOB write is highly constrained, the OOB read during ACPI checksum calculation can be used to leak arbitrary host heap memory contents beyond the guest memory area.

The ACPI table checksum is a value such that the sum of all bytes of the table is 0 (mod 256). Considering this, the info leak strategy should be to leak one byte at a time. An attacker could set up the DSDT ACPI table header such that length and checksum fields are accessible from the guest. The AML code occupies the end of guest memory area bordering the host heap area, which is not accessible from the guest. They could then trigger a 1-byte OOB read using a race condition and check if the checksum value has changed. If yes, based on the previous checksum value and updated checksum value, they could calculate the leaked byte. If no change in the checksum is observed after a certain amount of tries, then the leaked byte is assumed to be 0. The attacker could then trigger a 2-byte OOB read to leak the subsequent byte, and so forth. Below is the proof-of-concept to demonstrate the same:

Here is the host heap state of a vmware-vmx process with 2GB of guest memory:

Conclusion

VMware has fixed the issue in Workstation 16.0 by removing the legacy backdoor call meant to disable ACPI S1 sleep state. Although VMware classified the OOB Write as Moderate, it is theoretically possible to leverage CVE-2020-3982 leverage to escalate privileges and execute code in the context of the hypervisor. However, due to the OOB Write being highly constrained, a more likely outcome would be a crash of the virtual machine's vmx process or corruption of the hypervisor's memory heap. The VMSA-2020-0023 patch also fixes a remotely exploitable bug in ESXi reported by my colleague Lucas Leong. He’ll provide the details of that bug in a future blog.

Until then, you can find me on Twitter @RenoRobertr, and follow the team for the latest in exploit techniques and security patches.