阅读:
968
本文介绍了两个近期公布的Virtual Box 虚拟机逃逸漏洞,问题存在于 Oracle VirtualBox 5.1.30和5.2-rc1中。漏洞的发现归功于独立安全研究人员Niklas Baumstark。目前漏洞已被提交至 Beyond Security 的 SecuriTeam。
厂商回应
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 控制。