Skip to content

Oracle VM VirtualBox: Integer Overflow Leading To Out-Of-Bounds Read in virtioNetR3CtrlMac

High
rcorrea35 published GHSA-gj33-x76j-grh8 Nov 16, 2023

Package

VirtualBox (Oracle)

Affected versions

< https://www.virtualbox.org/changeset/101400/vbox

Patched versions

https://www.virtualbox.org/changeset/101400/vbox

Description

Summary

A guest inside a VirtualBox VM using the virtio-net network adapter can trigger an integer overflow leading to out-of-bounds read in src/VBox/Devices/Network/DevVirtioNet.cpp to cause a denial-of-service or leak information of the hypervisor.

Severity

High - An attacker with high privileges in the guest can cause a denial-of-service or leak information of the hypervisor.

Proof of Concept

The following function handles a VIRTIONET_CTRL_MAC control command which fetches multiple 32bit cMacs values from the guest:

static uint8_t virtioNetR3CtrlMac(PVIRTIONET pThis, PVIRTIONET_CTRL_HDR_T pCtrlPktHdr, PVIRTQBUF pVirtqBuf)
{
    LogFunc(("[%s] Processing CTRL MAC command\n", pThis->szInst));


    AssertMsgReturn(pVirtqBuf->cbPhysSend >= sizeof(*pCtrlPktHdr),
                   ("insufficient descriptor space for ctrl pkt hdr"),
                   VIRTIONET_ERROR);

    size_t cbRemaining = pVirtqBuf->cbPhysSend;
    switch(pCtrlPktHdr->uCmd)
    {
        // ...
        case VIRTIONET_CTRL_MAC_TABLE_SET:
        {
            VIRTIONET_CTRL_MAC_TABLE_LEN cMacs;

            /* Load unicast MAC filter table */
            AssertMsgReturn(cbRemaining >= sizeof(cMacs),
                           ("DESC chain too small to process CTRL_MAC_TABLE_SET cmd\n"), VIRTIONET_ERROR);

            /* Fetch count of unicast filter MACs from guest buffer */
            virtioCoreR3VirtqBufDrain(&pThis->Virtio, pVirtqBuf, &cMacs, sizeof(cMacs));
            cbRemaining -= sizeof(cMacs);

            Log7Func(("[%s] Guest provided %d unicast MAC Table entries\n", pThis->szInst, cMacs));

            if (cMacs)
            {
                uint32_t cbMacs = cMacs * sizeof(RTMAC);

                AssertMsgReturn(cbMacs <= sizeof(pThis->aMacUnicastFilter)  / sizeof(RTMAC),
                                ("Guest provided Unicast MAC filter table exceeds hardcoded table size"), VIRTIONET_ERROR);

                AssertMsgReturn(cbRemaining >= cbMacs,
                                ("Virtq buffer too small to process CTRL_MAC_TABLE_SET cmd\n"), VIRTIONET_ERROR);


                /* Fetch unicast table contents from guest buffer */
                virtioCoreR3VirtqBufDrain(&pThis->Virtio, pVirtqBuf, &pThis->aMacUnicastFilter, cbMacs);
                cbRemaining -= cbMacs;
            }
            pThis->cUnicastFilterMacs = cMacs;

            /* Load multicast MAC filter table */
            AssertMsgReturn(cbRemaining >= sizeof(cMacs),
                            ("Virtq buffer too small to process CTRL_MAC_TABLE_SET cmd\n"), VIRTIONET_ERROR);

            /* Fetch count of multicast filter MACs from guest buffer */
            virtioCoreR3VirtqBufDrain(&pThis->Virtio, pVirtqBuf, &cMacs, sizeof(cMacs));
            cbRemaining -= sizeof(cMacs);

            Log10Func(("[%s] Guest provided %d multicast MAC Table entries\n", pThis->szInst, cMacs));

            if (cMacs)
            {
                uint32_t cbMacs = cMacs * sizeof(RTMAC);

                AssertMsgReturn(cbMacs <= sizeof(pThis->aMacMulticastFilter)  / sizeof(RTMAC),
                                ("Guest provided Unicast MAC filter table exceeds hardcoded table size"), VIRTIONET_ERROR);

                AssertMsgReturn(cbRemaining >= cbMacs,
                                ("Virtq buffer too small to process CTRL_MAC_TABLE_SET cmd\n"), VIRTIONET_ERROR);

                /* Fetch multicast table contents from guest buffer */
                virtioCoreR3VirtqBufDrain(&pThis->Virtio, pVirtqBuf, &pThis->aMacMulticastFilter, cbMacs);
                cbRemaining -= cbMacs;
            }
            pThis->cMulticastFilterMacs = cMacs;
            // ...
            break;
        }
        default:
            LogRelFunc(("Unrecognized MAC subcommand in CTRL pkt from guest\n"));
            return VIRTIONET_ERROR;
    }
    return VIRTIONET_OK;
}

There are only checks on cbMacs, but not on cMacs. Hence, it is possible to provide a large value, e.g. 0x2AAAAAAB to cause an integer overflow. The resulting cbMacs will wrap around and hence bypass the checks. As a result a big cMacs can be used for pThis->cUnicastFilterMacs and pThis->cMulticastFilterMacs. In the function virtioNetR3AddressFilter(), both values are truncated to 16bit and used as limits for the arrays pThis->aMacMulticastFilter and pThis->aMacUnicastFilter:

static bool virtioNetR3AddressFilter(PVIRTIONET pThis, const void *pvBuf, size_t cb)
{
    // ...
    for (uint16_t i = 0; i < pThis->cMulticastFilterMacs; i++)
    {
        if (!memcmp(&pThis->aMacMulticastFilter[i], pvBuf, sizeof(RTMAC)))
        {
            // ...
            return true;
        }
    }

    for (uint16_t i = 0; i < pThis->cUnicastFilterMacs; i++)
        if (!memcmp(&pThis->aMacUnicastFilter[i], pvBuf, sizeof(RTMAC)))
        {
            Log11(("acpt (ucast whitelist)\n"));
            return true;
        }
    // ...
    return false;
}

Using a timing attack, it might be possible to exfiltrate the values read out-of-bounds.

Timeline

Date reported: 08/15/2023
Date fixed: 10/17/2023
Date disclosed: 11/16/2023

Severity

High

CVE ID

CVE-2023-22100

Weaknesses

No CWEs

Credits