CVE: CVE-2019-2984
Tested Versions: Oracle VirtualBox 5.2.18 revision r123745
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.
There are several interfaces for the guest to communicate with the host in VirtualBox, one of them is Host-Guest Shared Memory Interface (HGSMI) services. These vulnerabilities occur in the VirtualBox Video Acceleration (VBVA) channel, which works on top of HGSMI.
Vulnerability
When the guest submits a vbva command buffer (hit VGA_PORT_HGSMI_GUEST
), this buffer will be passed to a function based on the command info. The pvBuffer
buffer and its size cbBuffer
are guest controlled:
/*
* @return VBox status code.
* @param pvHandler The VBVA channel context.
* @param u16ChannelInfo Command code.
* @param pvBuffer HGSMI buffer with command data. Considered volatile!
* @param cbBuffer Size of command data.
*/
static DECLCALLBACK(int) vbvaChannelHandler(void *pvHandler, uint16_t u16ChannelInfo,
void RT_UNTRUSTED_VOLATILE_GUEST *pvBuffer, HGSMISIZE cbBuffer)
{
int rc = VINF_SUCCESS;
LogFlowFunc(("pvHandler %p, u16ChannelInfo %d, pvBuffer %p, cbBuffer %u\n",
pvHandler, u16ChannelInfo, pvBuffer, cbBuffer));
PVGASTATE pVGAState = (PVGASTATE)pvHandler;
PHGSMIINSTANCE pIns = pVGAState->pHGSMI;
VBVACONTEXT *pCtx = (VBVACONTEXT *)HGSMIContext(pIns);
switch (u16ChannelInfo)
{
...
#ifdef VBOX_WITH_VIDEOHWACCEL
case VBVA_VHWA_CMD:
if (cbBuffer >= VBOXVHWACMD_HEADSIZE())
{
vbvaVHWAHandleCommand(pVGAState, (VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *)pvBuffer);
rc = VINF_SUCCESS;
}
else
rc = VERR_INVALID_PARAMETER;
break;
#endif
...
In case VBOX_WITH_VIDEOHWACCEL
, function vbvaVHWAHandleCommand
will process the buffer, through several wrapper functions, the buffer come to the function vbvaVHWACommandSubmitInner
static void vbvaVHWAHandleCommand(PVGASTATE pVGAState, VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *pCmd)
{
if (vbvaVHWACheckPendingCommands(pVGAState))
{
if (vbvaVHWACommandSubmit(pVGAState, pCmd, false))
return;
}
vbvaVHWACommandPend(pVGAState, pCmd);
}
static bool vbvaVHWACommandSubmit(PVGASTATE pVGAState, VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *pCommand,
bool fAsyncCommand)
{
bool fPending = false;
bool fRet = vbvaVHWACommandSubmitInner(pVGAState, pCommand, &fPending);
if (!fPending)
vbvaVHWACommandComplete(pVGAState, pCommand, fAsyncCommand);
return fRet;
}
static bool vbvaVHWACommandSubmitInner(PVGASTATE pVGAState, VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *pCommand,
bool *pfPending)
{
*pfPending = false;
...
ASSERT_GUEST_STMT_RETURN(pVGAState->pDrv->pfnVHWACommandProcess, pCommand->rc = VERR_INVALID_STATE, true);
RT_UNTRUSTED_VALIDATED_FENCE();
/*
* Call the driver to process the command.
*/
Log(("VGA Command >>> %#p, %d\n", pCommand, enmCmd));
int rc = pVGAState->pDrv->pfnVHWACommandProcess(pVGAState->pDrv, enmCmd, fGuestCmd, pCommand);
if (rc == VINF_CALLBACK_RETURN)
{
Log(("VGA Command --- Going Async %#p, %d\n", pCommand, enmCmd));
*pfPending = true;
return true; /* Command will be completed asynchronously by the driver and need not be put in the pending list. */
}
...
return true;
}
One thing must be noted is the vtable pVGAState->pDrv
only be initialized when the 2D Video Acceleration
is enabled in guest display setting.
The vtable was initialized in Display::i_drvConstruct
function, then we follow the command buffer:
DECLCALLBACK(int) Display::i_drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
{
...
#ifdef VBOX_WITH_VIDEOHWACCEL
pThis->IConnector.pfnVHWACommandProcess = Display::i_displayVHWACommandProcess;
#endif
...
DECLCALLBACK(int) Display::i_displayVHWACommandProcess(PPDMIDISPLAYCONNECTOR pInterface, int enmCmd, bool fGuestCmd,
VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *pCommand)
{
PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
return pDrv->pDisplay->i_handleVHWACommandProcess(enmCmd, fGuestCmd, pCommand);
}
int Display::i_handleVHWACommandProcess(int enmCmd, bool fGuestCmd, VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *pCommand)
{
...
HRESULT hr = pFramebuffer->ProcessVHWACommand((BYTE *)pCommand, enmCmd, fGuestCmd);
...
}
class VBoxOverlayFrameBuffer : public UIFrameBufferPrivate
{
public:
STDMETHOD(ProcessVHWACommand)(BYTE *pCommand, LONG enmCmd, BOOL fGuestCmd)
{
...
int rc = mOverlay.onVHWACommand((struct VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *)pCommand, enmCmd, fGuestCmd != FALSE);
UIFrameBufferPrivate::unlock();
...
}
...
int VBoxQGLOverlay::onVHWACommand(struct VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *pCmd,
int /*VBOXVHWACMD_TYPE*/ enmCmdInt, bool fGuestCmd)
{
...
mCmdPipe.postCmd(VBOXVHWA_PIPECMD_VHWA, (void *)pCmd, enmCmd, fGuestCmd);
return VINF_CALLBACK_RETURN;
}
void VBoxVHWACommandElementProcessor::postCmd(VBOXVHWA_PIPECMD_TYPE aType, void *pvData,
int /*VBOXVHWACMD_TYPE*/ enmCmd, bool fGuestCmd)
{
...
pCmd->setData(aType, pvData, enmCmd, fGuestCmd);
...
RTListAppend(&mCommandList, &pCmd->ListNode);
RTCritSectLeave(&mCritSect);
if (pNotifyObject)
{
VBoxVHWACommandProcessEvent *pCurrentEvent = new VBoxVHWACommandProcessEvent();
QApplication::postEvent(pNotifyObject, pCurrentEvent);
m_NotifyObjectRefs.dec();
}
}
void VBoxQGLOverlay::onVHWACommandEvent(QEvent *pEvent)
{
...
VBoxVHWACommandElement *pCmd = mCmdPipe.getCmd();
if (pCmd)
{
processCmd(pCmd);
mCmdPipe.doneCmd();
}
...
}
void VBoxQGLOverlay::processCmd(VBoxVHWACommandElement * pCmd)
{
...
case VBOXVHWA_PIPECMD_VHWA:
vboxDoVHWACmd(pCmd->vhwaCmdPtr(), pCmd->vhwaCmdType(), pCmd->vhwaIsGuestCmd());
break;
...
}
void VBoxQGLOverlay::vboxDoVHWACmd(void RT_UNTRUSTED_VOLATILE_GUEST *pvCmd, int /*VBOXVHWACMD_TYPE*/ enmCmd, bool fGuestCmd)
{
vboxDoVHWACmdExec(pvCmd, enmCmd, fGuestCmd);
...
}
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_COLORKEY_SET:
{
VBOXVHWACMD_SURF_COLORKEY_SET RT_UNTRUSTED_VOLATILE_GUEST *pBody = VBOXVHWACMD_BODY(pCmd, VBOXVHWACMD_SURF_COLORKEY_SET);
Assert(!mGlOn == !mOverlayImage.hasSurfaces());
initGl();
makeCurrent();
pCmd->rc = mOverlayImage.vhwaSurfaceColorkeySet(pBody);
/* this is here to ensure we have color key changes picked up */
vboxDoCheckUpdateViewport();
mNeedOverlayRepaint = true;
Assert(!mGlOn == !mOverlayImage.hasSurfaces());
break;
}
...
In case VBOXVHWACMD_TYPE_SURF_COLORKEY_SET
, the pBody
is still guest controlled buffer and will be passed to mOverlayImage.vhwaSurfaceColorkeySet()
function:
int VBoxVHWAImage::vhwaSurfaceColorkeySet(struct VBOXVHWACMD_SURF_COLORKEY_SET RT_UNTRUSTED_VOLATILE_GUEST *pCmd)
{
VBoxVHWASurfaceBase *pSurf = handle2Surface(pCmd->u.in.hSurf);
VBOXQGLLOG_ENTER(("pSurf (0x%x)\n", pSurf));
vboxCheckUpdateAddress (pSurf, pCmd->u.in.offSurface);
VBoxVHWASurfaceBase *pSurf = handle2Surface(pCmd->u.in.hSurf);
VBoxVHWASurfList *pList = pSurf->getComplexList();
Assert(pSurf->handle() != VBOXVHWA_SURFHANDLE_INVALID);
...
}
int VBoxVHWAImage::vhwaSurfaceLock(struct VBOXVHWACMD_SURF_LOCK RT_UNTRUSTED_VOLATILE_GUEST *pCmd)
{
VBoxVHWASurfaceBase *pSurf = handle2Surface(pCmd->u.in.hSurf);
VBOXQGLLOG_ENTER(("pSurf (0x%x)\n",pSurf));
vboxCheckUpdateAddress (pSurf, pCmd->u.in.offSurface);
...
}
int VBoxVHWAImage::vhwaSurfaceUnlock(struct VBOXVHWACMD_SURF_UNLOCK RT_UNTRUSTED_VOLATILE_GUEST *pCmd)
{
VBoxVHWASurfaceBase *pSurf = handle2Surface(pCmd->u.in.hSurf);
VBOXQGLLOG_ENTER(("pSurf (0x%x)\n",pSurf));
if (pCmd->u.in.xUpdatedMemValid)
{
QRect r = VBOXVHWA_CONSTRUCT_QRECT_FROM_RECTL_WH(&pCmd->u.in.xUpdatedMemRect);
pSurf->updatedMem(&r);
}
return pSurf->unlock();
}
int VBoxVHWAImage::vhwaSurfaceFlip(struct VBOXVHWACMD_SURF_FLIP RT_UNTRUSTED_VOLATILE_GUEST *pCmd)
{
VBoxVHWASurfaceBase *pTargSurf = handle2Surface(pCmd->u.in.hTargSurf);
VBoxVHWASurfaceBase *pCurrSurf = handle2Surface(pCmd->u.in.hCurrSurf);
VBOXQGLLOG_ENTER(("pTargSurf (0x%x), pCurrSurf (0x%x)\n",pTargSurf,pCurrSurf));
vboxCheckUpdateAddress (pCurrSurf, pCmd->u.in.offCurrSurface);
vboxCheckUpdateAddress (pTargSurf, pCmd->u.in.offTargSurface);
...
}
int VBoxVHWAImage::vhwaSurfaceOverlayUpdate(struct VBOXVHWACMD_SURF_OVERLAY_UPDATE RT_UNTRUSTED_VOLATILE_GUEST *pCmd)
{
VBoxVHWASurfaceBase *pSrcSurf = handle2Surface(pCmd->u.in.hSrcSurf);
VBoxVHWASurfList *pList = pSrcSurf->getComplexList();
vboxCheckUpdateAddress (pSrcSurf, pCmd->u.in.offSrcSurface);
...
}
int VBoxVHWAImage::vhwaSurfaceOverlaySetPosition(struct VBOXVHWACMD_SURF_OVERLAY_SETPOSITION RT_UNTRUSTED_VOLATILE_GUEST *pCmd)
{
VBoxVHWASurfaceBase *pDstSurf = handle2Surface(pCmd->u.in.hDstSurf);
VBoxVHWASurfaceBase *pSrcSurf = handle2Surface(pCmd->u.in.hSrcSurf);
VBOXQGLLOG_ENTER(("pDstSurf (0x%x), pSrcSurf (0x%x)\n",pDstSurf,pSrcSurf));
vboxCheckUpdateAddress (pSrcSurf, pCmd->u.in.offSrcSurface);
vboxCheckUpdateAddress (pDstSurf, pCmd->u.in.offDstSurface);
...
}
There is another NULL pointer dereference in VBoxQGLOverlay::vhwaSurfaceUnlock
:
VBoxVHWASurfaceBase * getVGA() const { return mSurfVGA; }
VBoxVHWASurfaceBase * vgaSurface() { return mDisplay.getVGA(); }
int VBoxQGLOverlay::vhwaSurfaceUnlock(struct VBOXVHWACMD_SURF_UNLOCK RT_UNTRUSTED_VOLATILE_GUEST *pCmd)
{
int rc = mOverlayImage.vhwaSurfaceUnlock(pCmd);
VBoxVHWASurfaceBase *pVGA = mOverlayImage.vgaSurface();
const VBoxVHWADirtyRect &rect = pVGA->getDirtyRect();
mNeedOverlayRepaint = true;
if (!rect.isClear())
mMainDirtyRect.add(rect);
return rc;
}
The mSurfVGA
object needs to be initialized but if we send a VBOXVHWACMD_TYPE_SURF_UNLOCK
request before initializing it, the pointer pVGA
will be NULL and is used right after that, causing the VM to crash.
To trigger the bug,
- Attacker must first obtain the ability to execute high-privileged code on the target guest system
- 2D acceleration on the guest VM setting must be enabled
Sending a request with any of following types without first creating a surface will result in a VM crash:
VBOXVHWACMD_TYPE_SURF_DESTROY
VBOXVHWACMD_TYPE_SURF_LOCK
VBOXVHWACMD_TYPE_SURF_UNLOCK
VBOXVHWACMD_TYPE_SURF_FLIP
VBOXVHWACMD_TYPE_SURF_OVERLAY_UPDATE
VBOXVHWACMD_TYPE_SURF_OVERLAY_SETPOSITION
VBOXVHWACMD_TYPE_SURF_COLORKEY_SET
Timeline
- 2019-09-11 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.