【翻译】Oracle VirtualBox虚拟机逃逸漏洞分析

本文介绍了两个近期公布的Virtual Box 虚拟机逃逸漏洞,问题存在于 Oracle VirtualBox 5.1.30和5.2-rc1中。漏洞的发现归功于独立安全研究人员Niklas Baumstark。目前漏洞已被提交至 Beyond Security 的 SecuriTeam。

厂商回应

Oracle已针对漏洞发布补丁。更多细节详见:http://www.oracle.com/technetwork/security-advisory/cpujan2018-3236628.html

CVE 编号:CVE: CVE-2018-2698

漏洞详情

漏洞存在于 VirtualBox 的核心图形框架(VBVA子组件)中,影响所有主机操作系统,在用户级VirtualBox主机进程中提供了任意读/写原语。

VirtualBox 模拟的VGA设备与一定数量的VRAM相关联,VRAM 在host(宿主机)上的 VM 进程中,以及 guest(客户机)内核内存中连续映射。VRAM 的一部分被用作通用的共享内存,用于 host 和 guest 之间的通信(host – guest shared memory interface,HGSMI)。借助这种共享内存机制,guest 可以向 host 发出某些命令,例如实现鼠标自动捕获和无缝窗口功能。guest 还可以通过一个名为 VDMA 的子系统,通知 host 代表其在VRAM内部复制数据。

1. vboxVDMACmdExecBpbTransfer 中存在的越界读写漏洞

VBOXVDMACMD_DMA_BPB_TRANSFER 结构体的定义如下((详见 virtualbox 源码 \include\VBox\Graphics\VBoxVideo.h):

  typedef struct VBOXVDMACMD_DMA_BPB_TRANSFER
    {
        uint32_t cbTransferSize;
        uint32_t fFlags;
        union
        {
            uint64_t phBuf;
            VBOXVIDEOOFFSET offVramBuf;
        } Src;
        union
        {
            uint64_t phBuf;
            VBOXVIDEOOFFSET offVramBuf;
        } Dst;
    } VBOXVDMACMD_DMA_BPB_TRANSFER, *PVBOXVDMACMD_DMA_BPB_TRANSFER;

当发送一个类型为 VBOXVDMACMD_TYPE_DMA_BPB_TRANSFER 的 VDMA 命令时,这种类型的请求对象驻留在 HGSMI 堆中,并完全由 guest 控制。

在 host 上,一个指向该对象的指针最终被传递给\src\VBox\Devices\Graphics\DevVGA_VDMA.cpp 中的如下函数:

 static int vboxVDMACmdExecBpbTransfer(PVBOXVDMAHOST pVdma, const PVBOXVDMACMD_DMA_BPB_TRANSFER pTransfer, uint32_t cbBuffer)
    {
        // ...
        uint32_t cbTransfer = pTransfer->cbTransferSize;
        uint32_t cbTransfered = 0;
        // ...
        do
        {
            uint32_t cbSubTransfer = cbTransfer;
            if (pTransfer->fFlags & VBOXVDMACMD_DMA_BPB_TRANSFER_F_SRC_VRAMOFFSET)
            {
                // [[ Note 1 ]]
                pvSrc  = pvRam + pTransfer->Src.offVramBuf + cbTransfered;
            }
            else
            {
                // ...
            }

            if (pTransfer->fFlags & VBOXVDMACMD_DMA_BPB_TRANSFER_F_DST_VRAMOFFSET)
            {
                // [[ Note 2 ]]
                pvDst  = pvRam + pTransfer->Dst.offVramBuf + cbTransfered;
            }
            else
            {
                // ...
            }

            if (RT_SUCCESS(rc))
            {
                memcpy(pvDst, pvSrc, cbSubTransfer);
                cbTransfer -= cbSubTransfer;
                cbTransfered += cbSubTransfer;
            }
            else
            {
                cbTransfer = 0; /* to break */
            }
            // ...
        } while (cbTransfer);

        if (RT_SUCCESS(rc))
            return sizeof (*pTransfer);
        return rc;
    }

以上代码中标注的 Note 1 和 Note 2 处, guest 控制的偏移量 pTransfer-> Src.offVramBuf 和 pTransfer-> Dst.offVramBuf ,与 VRAM 地址相加,没有进行任何验证或边界检查。在之后的 memcpy 中,size 来自一个 guest 可控的变量,pTransfer->cbTransferSize。

以上,获得了  memcpy(VRAM + X, VRAM + Y, Z) ,其中 X,Y,Z 可被 guest 控制。

2. vboxVDMACmdExecBpbTransfer 中存在的越界读写漏洞

VBOXVDMACMD_DMA_PRESENT_BLT 结构体定义如下:

typedef uint64_t VBOXVIDEOOFFSET;
    /* [...] */
    typedef struct VBOXVDMACMD_DMA_PRESENT_BLT
    {
        VBOXVIDEOOFFSET offSrc;
        VBOXVIDEOOFFSET offDst;
        VBOXVDMA_SURF_DESC srcDesc;
        VBOXVDMA_SURF_DESC dstDesc;
        VBOXVDMA_RECTL srcRectl;
        VBOXVDMA_RECTL dstRectl;
        uint32_t u32Reserved;
        uint32_t cDstSubRects;
        VBOXVDMA_RECTL aDstSubRects[1];
    } VBOXVDMACMD_DMA_PRESENT_BLT, *PVBOXVDMACMD_DMA_PRESENT_BLT;

当发送一个 VBOXVDMACMD_TYPE_DMA_PRESENT_BLT 类型的 VDMA 命令时,这种类型的请求对象会驻留在 HGSMI 堆中,并完全由 guest 控制。

在 host 上,一个指向该对象的指针最终被传递给  \src\VBox\Devices\Graphics\DevVGA_VDMA.cpp中的如下函数:

 static int vboxVDMACmdExecBlt(PVBOXVDMAHOST pVdma, const PVBOXVDMACMD_DMA_PRESENT_BLT pBlt, uint32_t cbBuffer)
    {
        const uint32_t cbBlt = VBOXVDMACMD_BODY_FIELD_OFFSET(uint32_t, VBOXVDMACMD_DMA_PRESENT_BLT, aDstSubRects[pBlt->cDstSubRects]);
        Assert(cbBlt <= cbBuffer);
        if (cbBuffer < cbBlt) return VERR_INVALID_FUNCTION; /* we do not support stretching for now */ Assert(pBlt->srcRectl.width == pBlt->dstRectl.width);
        Assert(pBlt->srcRectl.height == pBlt->dstRectl.height);
        if (pBlt->srcRectl.width != pBlt->dstRectl.width)
            return VERR_INVALID_FUNCTION;
        if (pBlt->srcRectl.height != pBlt->dstRectl.height)
            return VERR_INVALID_FUNCTION;
        Assert(pBlt->cDstSubRects);  /* [[ Note 2 ]] */

        uint8_t * pvRam = pVdma->pVGAState->vram_ptrR3;
        VBOXVDMA_RECTL updateRectl = {0, 0, 0, 0};

        if (pBlt->cDstSubRects)
        {
            /* [...] */
        }
        else
        {
            /* [[ Note 1 ]] */
            int rc = vboxVDMACmdExecBltPerform(pVdma, pvRam + pBlt->offDst, pvRam + pBlt->offSrc,
                    &pBlt->dstDesc, &pBlt->srcDesc,
                    &pBlt->dstRectl,
                    &pBlt->srcRectl);
            AssertRC(rc);
            if (!RT_SUCCESS(rc))
                return rc;

            vboxVDMARectlUnite(&updateRectl, &pBlt->dstRectl);
        }

        return cbBlt;
    }

以上代码中标注的 Note 1 处, guest 可控的偏移 pBlt->offDst 和 pBlt->offSrc 与 VRAM 地址相加,没有进行任何验证或边界检查。

请注意,Note 2 中的 Assert 在生产版本中不可用,所以代码可以到达 else 分支。之后调用的 vboxVDMACmdExecBltPerform 在计算的地址之间执行一个memcpy:

 static int vboxVDMACmdExecBltPerform(PVBOXVDMAHOST pVdma, uint8_t *pvDstSurf, const uint8_t *pvSrcSurf,
                                        const PVBOXVDMA_SURF_DESC pDstDesc, const PVBOXVDMA_SURF_DESC pSrcDesc,
                                        const VBOXVDMA_RECTL * pDstRectl, const VBOXVDMA_RECTL * pSrcRectl)
    {
        /* [...] /*
        if (pDstDesc->width == pDstRectl->width
                && pSrcDesc->width == pSrcRectl->width
                && pSrcDesc->width == pDstDesc->width)
        {
            Assert(!pDstRectl->left);
            Assert(!pSrcRectl->left);
            uint32_t cbOff = pDstDesc->pitch * pDstRectl->top;
            uint32_t cbSize = pDstDesc->pitch * pDstRectl->height;
            memcpy(pvDstSurf + cbOff, pvSrcSurf + cbOff, cbSize);
        }
        else
        {
            /* [...] /*
        }
        return VINF_SUCCESS;
    }

通过设置 pDstDesc-> pitch = 1,pDstRectl-> top = 0,可以得到 cbOff = 0 和 cbSize = pDstRectl-> height(这里同样是通过 guest 控制)。最后调用 memcpy(VRAM + X,VRAM + Y,Z),其中 X,Y,Z 可被 guest 控制。

Spread the word. Share this post!

Meet The Author

Leave Comment