CVE: CVE-2020-2682
Tested Versions:
- Oracle VirtualBox 5.2.18 revision r123745
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.
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. The vulnerability occurs in the VirtualBox Video Acceleration (VBVA) channel which works on top of HGSMI.
vbvaChannelHandler() function
The guest submitted a vbva command
buffer (hit VGA_PORT_HGSMI_GUEST
), this buffer will be passed to a correlative function base 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
is 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
{
Q_OBJECT;
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));
...
}
The handle2Surface
function:
VBoxVHWASurfaceBase* handle2Surface(uint32_t h)
{
VBoxVHWASurfaceBase* pSurf = (VBoxVHWASurfaceBase*)mSurfHandleTable.get(h);
Assert(pSurf);
return pSurf;
}
The method get
of mSurfHandleTable
is declared as:
void* VBoxVHWAHandleTable::get(uint32_t h)
{
Assert(h < mcSize);
Assert(h > 0);
return mTable[h];
}
Since the index h
is guest controlled and the Assert
macro will be disabled in the release build, this obviously an Out-Of-Bound access vulnerability.
Later in 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);
mRepaintNeeded = true;
if (pCmd->u.in.flags & VBOXVHWA_CKEY_DESTBLT)
{
VBoxVHWAColorKey ckey(pCmd->u.in.CKey.high, pCmd->u.in.CKey.low);
pSurf->setDstBltCKey(&ckey);
}
...
return VINF_SUCCESS;
}
pCmd
is guest controlled, so the value of ckey
is controlled, after that we can control the value which will be written to the mSrcBltCKey
field of VBoxVHWASurfaceBase
class object pSurf
:
class VBoxVHWASurfaceBase
{
public:
VBoxVHWASurfaceBase (class VBoxVHWAImage *pImage,
const QSize & aSize,
const QRect & aTargRect,
const QRect & aSrcRect,
const QRect & aVisTargRect,
VBoxVHWAColorFormat & aColorFormat,
VBoxVHWAColorKey * pSrcBltCKey, VBoxVHWAColorKey * pDstBltCKey,
VBoxVHWAColorKey * pSrcOverlayCKey, VBoxVHWAColorKey * pDstOverlayCKey,
VBOXVHWAIMG_TYPE aImgFlags);
...
void setSrcBltCKey (const VBoxVHWAColorKey * ckey)
{
if(ckey)
{
mSrcBltCKey = *ckey;
mpSrcBltCKey = &mSrcBltCKey;
}
else
{
mpSrcBltCKey = NULL;
}
}
...
Timeline:
- 2019-09-09 Disclosed to ZDI
- 2019-10-31 Verified and reported to vendor
- 2020-01-15 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/cpujan2020.html.