CVE: CVE-2019-3031

Tested Versions:

  • Oracle VirtualBox 6.0.4 revision r128413

Product URL(s): https://virtualbox.org

VirtualBox is a x86 and AMD64/Intel64 virtualization product for enterprise as well as home use. It is a solution commercially supported by Oracle, in addition to being made available as open source software. It runs on various host platforms like Windows, Linux, Mac and Solaris and also supports a large number of guest operating systems.

Vulnerability

Besides the default VirtualBox Video Adapter, VirtualBox also emulates VMware virtual SVGA device. It is not enabled by default. The vulnerability occurs while processing a SVGA command which the guest send to the host.

VirtualBox also emulates the VMware virtual SVGA device, but it is not enabled by default. The graphics device on a VM can be switched by the following host command:

VBoxManage modifyvm <VMNAME> --graphicscontroller vmsvga

The vulnerability occurs while processing a SVGA command which the guest send to the host.

The SVGA FIFO

The SVGA FIFO is a memory buffer which is shared between host and guest, both sides can read and write to this buffer. When the guest wants to send an SVGA command to the host, it forms a command inside the FIFO buffer and notifies the host that the FIFO buffer now contains data to be processed, then the host uses some SVGA registers to locate the command and processes it until the FIFO is empty.

vmsvgaFIFOLoop()

The vmsvgaFIFOLoop function runs a loop to wait for the guest to write the command to the FIFO buffer and process them. When the guest has written the command data, we can enable the following flags to start the process:

static DECLCALLBACK(int) vmsvgaFIFOLoop(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
{
    ...    
    
    while (pThread->enmState == PDMTHREADSTATE_RUNNING)
    {
        ...        
        
        if (   !pThis->svga.fEnabled
            || !pThis->svga.fConfigured)
        {
            vmsvgaFifoSetNotBusy(pThis, pSVGAState, pFIFO[SVGA_FIFO_MIN]);
            fBadOrDisabledFifo = true;
            continue;
        }
    ...        

When svga.fEnabled and svga.fConfigured are enabled, the host locates the command in FIFO buffer by the following code snippet:

uint32_t const offFifoMin    = pFIFO[SVGA_FIFO_MIN];
uint32_t const offFifoMax    = pFIFO[SVGA_FIFO_MAX];
uint32_t       offCurrentCmd = pFIFO[SVGA_FIFO_STOP];

RT_UNTRUSTED_NONVOLATILE_COPY_FENCE();
if (RT_UNLIKELY(   !VMSVGA_IS_VALID_FIFO_REG(SVGA_FIFO_STOP, offFifoMin)
                || offFifoMax <= offFifoMin
                || offFifoMax > pThis->svga.cbFIFO
                || (offFifoMax & 3) != 0
                || (offFifoMin & 3) != 0
                || offCurrentCmd < offFifoMin
                || offCurrentCmd > offFifoMax))
{
     ...
}
RT_UNTRUSTED_VALIDATED_FENCE();
if (RT_UNLIKELY(offCurrentCmd & 3))
{
    STAM_REL_COUNTER_INC(&pSVGAState->StatFifoErrors);
    LogRelMax(8, ("vmsvgaFIFOLoop: Misaligned offCurrentCmd=%#x?\n", offCurrentCmd));
    offCurrentCmd = ~UINT32_C(3);
}

If the command id is SVGA_3D_CMD_SETLIGHTENABLED:

case SVGA_3D_CMD_SETLIGHTENABLED:
{
    SVGA3dCmdSetLightEnabled *pCmd = (SVGA3dCmdSetLightEnabled *)(pHdr + 1);
    VMSVGAFIFO_CHECK_3D_CMD_MIN_SIZE_BREAK(sizeof(*pCmd));
    STAM_REL_COUNTER_INC(&pSVGAState->StatR3Cmd3dSetLightEnable);

    rc = vmsvga3dSetLightEnabled(pThis, pCmd->cid, pCmd->index, pCmd->enabled);
    break;
}

pHdr points to a buffer inside the FIFO buffer, so it can be fully controlled by the guest and we can pass any pCmd->index value to vmsvga3dSetLightEnabled function:

int vmsvga3dSetLightEnabled(PVGASTATE pThis, uint32_t cid, uint32_t index, uint32_t enabled)
{
    HRESULT               hr;
    PVMSVGA3DCONTEXT      pContext;
    PVMSVGA3DSTATE        pState = pThis->svga.p3dState;
    AssertReturn(pState, VERR_NO_MEMORY);

    Log(("vmsvga3dSetLightEnabled %x %d -> %d\n", cid, index, enabled));

    int rc = vmsvga3dContextFromCid(pState, cid, &pContext);
    AssertRCReturn(rc, rc);

    /* Store for vm state save/restore */
    if (index < SVGA3D_MAX_LIGHTS)
        pContext->state.aLightData[index].fEnabled = !!enabled;
    else
        AssertFailed();

    hr = pContext->pDevice->LightEnable(index, (BOOL)enabled);
    AssertMsgReturn(hr == D3D_OK, ("LightEnable failed with %x\n", hr), VERR_INTERNAL_ERROR);

    return VINF_SUCCESS;
}

If index is larger than SVGA3D_MAX_LIGHTS, AssertFailed() will be called, but this assertion is stripped out in the official release build so we can bypass the check. Then, the pContext->pDevice->LightEnable() will be called with an invalid index and cause the crash while trying to read at an invalid memory address.

To trigger the bug:

  • The attacker must first obtain the ability to execute high-privileged code on the target guest system.
  • The guest VM setting must use the vmsvga graphics controller.
  • The guest VM setting must enable the 3D acceleration.

Timeline

  • 2019-09-19 Reported to vendor
  • 2019-10-20 Vendor patched

Vendor Response

The vendor has acknowledged the issue and released an update to address it.

The vendor advisory can be found here: https://www.oracle.com/security-alerts/cpuoct2019.html.