CVE: CVE-2020-2575

Tested Versions:

  • Oracle VirtualBox 6.1.0 revision r135406

Product URL(s):

Description of the vulnerability

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.

The emulated OHCI controller that handles requests contains a vulnerability due to the lack of proper initialization.

This vulnerability was disclosed via the Pwn2Own programme by ZDI.

vusbMsgSetup() Function

If we send a Setup Message URB Request to the OHCI device, vusbMsgSetup() function will handle that request:

// VirtualBox-6.1.0\src\VBox\Devices\USB\VUSBUrb.cpp:664
static bool vusbMsgSetup(PVUSBPIPE pPipe, const void *pvBuf, uint32_t cbBuf)
{
    PVUSBCTRLEXTRA  pExtra = pPipe->pCtrl;
    const VUSBSETUP *pSetupIn = (PVUSBSETUP)pvBuf;

...    

    /*
     * Check that we've got sufficient space in the message URB.
     */
    if (pExtra->cbMax < cbBuf + pSetupIn->wLength + sizeof(VUSBURBVUSBINT))
    {
        uint32_t cbReq = RT_ALIGN_32(cbBuf + pSetupIn->wLength + sizeof(VUSBURBVUSBINT), 1024);
        PVUSBCTRLEXTRA pNew = (PVUSBCTRLEXTRA)RTMemRealloc(pExtra, RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[cbReq]));
        if (!pNew)
        {
            Log(("vusbMsgSetup: out of memory!!! cbReq=%u %zu\n",
                 cbReq, RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[cbReq])));
            return false;
        }
        if (pExtra != pNew)
        {
            pNew->pMsg = (PVUSBSETUP)pNew->Urb.abData;
            pExtra = pNew;
            pPipe->pCtrl = pExtra;
        }
        pExtra->Urb.pVUsb = (PVUSBURBVUSB)&pExtra->Urb.abData[cbBuf + pSetupIn->wLength];
        pExtra->Urb.pVUsb->pUrb = &pExtra->Urb;
        pExtra->cbMax = cbReq;
    }

...    
    
    return true;
}

pSetupIn->wLength is the size of the new control pipe extra state data, if pSetupIn->wLength is bigger than the current one, the control pipe extra state data object will be reallocated with the new size:

uint32_t cbReq = RT_ALIGN_32(cbBuf + pSetupIn->wLength + sizeof(VUSBURBVUSBINT), 1024);
PVUSBCTRLEXTRA pNew = (PVUSBCTRLEXTRA)RTMemRealloc(pExtra, RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[cbReq]));

After that, pExtra->Urb.pVUsb object will be reside in the reallocated buffer but with a new offset pSetupIn->wLength and it only initialize the pUrb field:

pExtra->Urb.pVUsb = (PVUSBURBVUSB)&pExtra->Urb.abData[cbBuf + pSetupIn->wLength];
pExtra->Urb.pVUsb->pUrb = &pExtra->Urb;

In vusbUrbQueueAsyncRh() function, pExtra->Urb.pVUsb is used to get the pDev pointer:

// VirtualBox-6.1.0\src\VBox\Devices\USB\VUSBUrb.cpp:439
int vusbUrbQueueAsyncRh(PVUSBURB pUrb)
{
#ifdef LOG_ENABLED
    vusbUrbTrace(pUrb, "vusbUrbQueueAsyncRh", false);
#endif

    /* Immediately return in case of error.
     * XXX There is still a race: The Rh might vanish after this point! */
    PVUSBDEV pDev = pUrb->pVUsb->pDev;
    PVUSBROOTHUB pRh = vusbDevGetRh(pDev);
    if (!pRh)
    {
        Log(("vusbUrbQueueAsyncRh returning VERR_OBJECT_DESTROYED\n"));
        return VERR_OBJECT_DESTROYED;
    }

    RTCritSectEnter(&pDev->CritSectAsyncUrbs);
    int rc = pDev->pUsbIns->pReg->pfnUrbQueue(pDev->pUsbIns, pUrb);

...    
    
}

The pUrb->pVUsb->pDev still stay uninitialized after the reallocation, so we can spray the heap before the RTMemRealloc() call then control the pUrb->pVUsb->pDev pointer.

Timeline:

  • 2020-04-30 Vendor disclosure via ZDI
  • 2020-04-30 Coordinated public disclosure

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/cpuapr2020.html.