CVE: CVE-2020-2758

Tested Versions:

  • Oracle VirtualBox 6.1.2 r135662

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.

A flaw lies exists in the code responsible for handling the VirtualBox Video Acceleration (VBVA) feature. The bug specifically is a use-after-free of a VBoxVHWASurfaceBase object, and could result in code execution on the host machine.

This bug lies within in the code responsible for handling the VirtualBox Video Acceleration (VBVA) feature. This feature is accessed through the VirtualBox Host-Guest Shared Memory Interface (HGSMI), making use of the VRAM buffer for MMIO and IO port 0x3d0 (VGA_PORT_HGSMI_GUEST). The bug specifically is a use-after-free of a VBoxVHWASurfaceBase object.

When VBVA commands are sent from the guest, they are handled by the VBoxQGLOverlay::vboxDoVHWACmdExec function when hardware acceleration is enabled.

// src/VBox/Frontends/VirtualBox/src/VBoxFBOverlay.cpp:4669
void VBoxQGLOverlay::vboxDoVHWACmdExec(void RT_UNTRUSTED_VOLATILE_GUEST *pvCmd, int /*VBOXVHWACMD_TYPE*/ enmCmdInt, bool fGuestCmd)
{
    struct VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *pCmd = (struct VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *)pvCmd;
    VBOXVHWACMD_TYPE enmCmd = (VBOXVHWACMD_TYPE)enmCmdInt;

    switch (enmCmd)
    {
...
        case VBOXVHWACMD_TYPE_SURF_CREATE:
        {
            VBOXVHWACMD_SURF_CREATE RT_UNTRUSTED_VOLATILE_GUEST *pBody = VBOXVHWACMD_BODY(pCmd, VBOXVHWACMD_SURF_CREATE);
            Assert(!mGlOn == !mOverlayImage.hasSurfaces());
            initGl();
            makeCurrent();
            vboxSetGlOn(true);
            pCmd->rc = mOverlayImage.vhwaSurfaceCreate(pBody);
...
        case VBOXVHWACMD_TYPE_SURF_OVERLAY_UPDATE:
        {
            VBOXVHWACMD_SURF_OVERLAY_UPDATE RT_UNTRUSTED_VOLATILE_GUEST *pBody = VBOXVHWACMD_BODY(pCmd, VBOXVHWACMD_SURF_OVERLAY_UPDATE);
            Assert(!mGlOn == !mOverlayImage.hasSurfaces());
            initGl();
            makeCurrent();
            pCmd->rc = mOverlayImage.vhwaSurfaceOverlayUpdate(pBody);
...

When provided with a VBOXVHWACMD_TYPE_SURF_CREATE command, VBoxVHWAImage::vhwaSurfaceCreate will be called, which can create a new VBoxVHWASurfaceBase object. A pointer to that VBoxVHWASurfaceBase object will be stored in the mSurfHandleTable member of the calling object, which is simply an array of pointers indexed by a handle.

// src/VBox/Frontends/VirtualBox/src/VBoxFBOverlay.cpp:2287
int VBoxVHWAImage::vhwaSurfaceCreate(struct VBOXVHWACMD_SURF_CREATE RT_UNTRUSTED_VOLATILE_GUEST *pCmd)
{
...
    VBoxVHWASurfaceBase *surf = NULL;
...
        if (format.isValid())
        {
            surf = new VBoxVHWASurfaceBase(this,
                                           surfSize,
                                           primaryRect,
                                           QRect(0, 0, surfSize.width(), surfSize.height()),
                                           mViewport,
                                           format,
                                           pSrcBltCKey, pDstBltCKey, pSrcOverlayCKey, pDstOverlayCKey,
#ifdef VBOXVHWA_USE_TEXGROUP
                                           0,
#endif
                                           fFlags);
        }
...
        handle = mSurfHandleTable.put(surf);
        pCmd->SurfInfo.hSurf = (VBOXVHWA_SURFHANDLE)handle;

However, when certain command flags are enabled, a new VBoxVHWASurfaceBase is not created and instead surf is set to an existing VBoxVHWASurfaceBase object.

// src/VBox/Frontends/VirtualBox/src/VBoxFBOverlay.cpp:2287
int VBoxVHWAImage::vhwaSurfaceCreate(struct VBOXVHWACMD_SURF_CREATE RT_UNTRUSTED_VOLATILE_GUEST *pCmd)
{
...
    VBoxVHWASurfaceBase *surf = NULL;
...
    if (pCmd->SurfInfo.surfCaps & VBOXVHWA_SCAPS_PRIMARYSURFACE)
    {
        bNoPBO = true;
        bPrimary = true;
        VBoxVHWASurfaceBase *pVga = vgaSurface(); /* == mDisplay.getVGA() == mDisplay.mSurfVGA */
...
                        surf = pVga;

When this code path is followed, our mSurfHandleTable will hold a reference to the mSurfVGA of the mDisplay object. However, this mDisplay object may be replaced by other functionalities, for example, the resize functionality. Following a screen resize, which can be triggered by the guest, the following code will be executed.

// src/VBox/Frontends/VirtualBox/src/VBoxFBOverlay.cpp:3752
void VBoxVHWAImage::resize(const VBoxFBSizeInfo &size)
{
...
    VBoxVHWASurfaceBase *pDisplay = mDisplay.setVGA(NULL);
    if (pDisplay)
        delete pDisplay;

    VBoxVHWAColorFormat format(bitsPerPixel, r,g,b);
    QSize dispSize(displayWidth, displayHeight);
    QRect dispRect(0, 0, displayWidth, displayHeight);
    pDisplay = new VBoxVHWASurfaceBase(this,
                                       dispSize,
                                       dispRect,
                                       dispRect,
                                       dispRect, /* we do not know viewport at the stage of precise, set as a
                                                    disp rect, it will be updated on repaint */
                                       format,
                                       NULL, NULL, NULL, NULL,
#ifdef VBOXVHWA_USE_TEXGROUP
                                       0,
#endif
                                       0 /* VBOXVHWAIMG_TYPE fFlags */);

Evidently, while the mDisplay member’s mSurfVGA has been free’d and updated with a new allocation, the mSurfHandleTable will still hold a pointer to the old free’d VBoxVHWASurfaceBase object. This creates a use-after-free scenario as other functionalities like VBOXVHWACMD_TYPE_SURF_UNLOCK can still access this free’d pointer through its handle, for various operations.

// src/VBox/Frontends/VirtualBox/src/VBoxFBOverlay.cpp:2823
int VBoxVHWAImage::vhwaSurfaceOverlayUpdate(struct VBOXVHWACMD_SURF_OVERLAY_UPDATE RT_UNTRUSTED_VOLATILE_GUEST *pCmd)
{
    VBoxVHWASurfaceBase *pSrcSurf = handle2Surface(pCmd->u.in.hSrcSurf);
    AssertReturn(pSrcSurf, VERR_INVALID_PARAMETER);
    VBoxVHWASurfList *pList = pSrcSurf->getComplexList();
    vboxCheckUpdateAddress (pSrcSurf, pCmd->u.in.offSrcSurface);
    VBOXQGLLOG(("OverlayUpdate: pSrcSurf (0x%x)\n",pSrcSurf));
    VBoxVHWASurfaceBase *pDstSurf = NULL;
...

Additionally, this bug is exploitable, providing enough primitives for the guest code to escape the virtual machine and achieve code execution on the host VirtualBox process.

Timeline:

  • 2020-03-03 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.