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.