Monthly Archives: July 2011

Oracle VirtualBox Integer Overflow Vulnerabilities

In VirtualBox 4.0.10 and the Critical Patch Update for July 2011, Oracle addressed two vulnerabilities that could be leveraged by an attacker to gain elevated privileges in a Windows guest (CVE-2011-2300) or execute arbitrary code on the host (CVE-2011-2305). The former affected the VirtualBox XPDM display driver, installed on Windows guests as part of the VirtualBox Guest Additions, while the latter was a vulnerability in the VirtualBox 3D graphics stack, resulting in host memory corruption. Before we look closer at the details of these vulnerabilities, we’ll briefly review the Windows 2000/XP display driver architecture.

Windows 2000/XP Display Driver Model

In the XPDM architecture, every graphics adapter is associated with a display driver and a corresponding miniport driver. The display driver is essentially a DLL whose primary responsibility is rendering. It is written for any number of adapters that share a common drawing interface and hooks drawing operations offered by GDI (win32k) to enhance performance. The display driver also has direct access to video hardware registers for time-critical operations.

XP Display Driver Model Architecture

The video miniport driver generally interacts with other NT kernel components as is written specifically for one adapter (or family of adapters). It manages all the resources shared between the video miniport driver and the display driver, and handles hardware initialization, mode sets, and physical device memory mapping. Notably, the video miniport driver can only make calls exported by videoprt.sys. In order to request support from the video miniport driver, the display driver calls EngDeviceIoControl with a control code specific to the desired operation.

A display driver may implement a number of different graphics DDI functions depending on the drawing operations to accelerate or features to support. In general, DDI functions are grouped into three categories: functions required by every display driver, functions required under certain conditions, and functions that are optional. The functions implemented and supported by a display driver are defined by DrvEnableDriver (DriverEntry), whose prototype is shown below.

BOOL DrvEnableDriver(
    ULONG iEngineVersion,
    ULONG cj,
    __in  DRVENABLEDATA *pded
);

Upon loading the driver, the OS sets the iEngineVersion according to the version of GDI that is currently running (e.g. Windows 2000 or XP). The display driver then fills the DRVENABLEDATA structure accordingly with the supported interfaces. Subsequently, upon making GDI and DirectX calls, win32k.sys and dxg.sys call the corresponding function in the display driver.

DrvEnableDriver Example

Because the XPDM display driver model requires vendors to marshal great amounts of data in kernel-mode in handling large and complex Direct3D/DirectDraw structures, there is a significant probability of security vulnerabilities being present. It should be noted that the newer Windows Vista Display Driver Model (WDDM) tries to address this by moving such processing into a user-mode driver. In the event that GDI does not offer a suitable interface for say some specific graphics adapter capability, XPDM allows developers to use generic Escape calls. This enables user-mode applications to instruct the display driver to exercise specific functionality, much like the DeviceIoControl interface does in regular kernel-mode drivers. It is no secret that such interfaces should be carefully audited as they directly operate on user provided data and thus become a prime candidate for privilege escalation vulnerabilities.

XPDM Display Driver Integer Overflow Vulnerability

The XPDM display driver in VirtualBox implements the DrvEscape interface to provide information about the mode of operation, but more importantly the ability to set the visible region of a guest’s display. This is done by making a VBOXESC_SETVISIBLEREGION escape call (e.g. via ExtEscape) with a RGNDATA formatted buffer holding the array of rectangles that compose the region. The display driver copies this information to a buffer of its own before sending the information to the host.

[trunk/src/VBox/Additions/WINNT/Graphics/Video/disp/xpdm/VBoxDispDriver.cpp]

        case VBOXESC_SETVISIBLEREGION:
        {
            LOGF(("VBOXESC_SETVISIBLEREGION"));
            LPRGNDATA lpRgnData = (LPRGNDATA)pvIn;

            if (cjIn >= sizeof(RGNDATAHEADER)
                &&  pvIn
                &&  lpRgnData->rdh.dwSize == sizeof(RGNDATAHEADER)
                &&  lpRgnData->rdh.iType  == RDH_RECTANGLES
                &&  cjIn == lpRgnData->rdh.nCount * sizeof(RECT) + sizeof(RGNDATAHEADER))
            {
                DWORD   i;
                PRTRECT pRTRect;
                int     rc;
                RECT   *pRect = (RECT *)&lpRgnData->Buffer;

                pRTRect = (PRTRECT) EngAllocMem(0, lpRgnData->rdh.nCount*sizeof(RTRECT), MEM_ALLOC_TAG);
                if (!pRTRect)
                {
                    WARN(("failed to allocate %d bytes", lpRgnData->rdh.nCount*sizeof(RTRECT)));
                    break;
                }

                for (i=0; i<lpRgnData->rdh.nCount; ++i)
                {
                    LOG(("New visible rectangle (%d,%d) (%d,%d)",
                         pRect[i].left, pRect[i].bottom, pRect[i].right, pRect[i].top));
                    pRTRect[i].xLeft   = pRect[i].left;
                    pRTRect[i].yBottom = pRect[i].bottom;
                    pRTRect[i].xRight  = pRect[i].right;
                    pRTRect[i].yTop    = pRect[i].top;
                }

The vulnerability addressed in VirtualBox 4.0.10 was an integer overflow in multiplying the number of rectangles (cRects) with the size of a RTRECT structure (equivalent to RECT), subsequently used to determine the size of the driver allocated buffer. This in turn resulted in a kernel pool overflow as the driver later used cRects in copying the rectangles. The latest maintenance release ensures that the multiplication does not wrap by casting sizeof(RECT) to an uint64_t and that the rectangle count does not exceed 1 million.

            if (    cjIn >= sizeof(RGNDATAHEADER)
                &&  pvIn
                &&  lpRgnData->rdh.dwSize == sizeof(RGNDATAHEADER)
                &&  lpRgnData->rdh.iType  == RDH_RECTANGLES
                &&  (cRects = lpRgnData->rdh.nCount) <= _1M
                &&  cjIn == cRects * (uint64_t)sizeof(RECT) + sizeof(RGNDATAHEADER))
            {

Although there’s a theoretical possibility of exploiting this vulnerability, the large copy makes it somewhat difficult. On MP systems, an attacker may be able to corrupt a kernel object or structure in the non-paged pool and trigger use of it before the copy reaches unmapped memory or triggers some other fault.

VBoxSharedOpenGL Host Service Integer Overflow Vulnerability

As current graphics adapters do not offer virtualization of resources, providers of virtualization solutions need to abstract the graphics rendering in guests in order to offer the same 2D/3D experience you get on native systems. In VMware Workstation and Player, 3D rendering is performed by buffering commands in a shared memory between the guest and the host which the host processes and translates to OpenGL or Direct3D calls. VirtualBox takes a similar approach and uses Chromium to forward graphics calls from the guest system to the host where they are executed with hardware acceleration.

In the Host-Guest Communication Manager (HGCM), VirtualBox provides the VBoxSharedOpenGL service to give guest applications more direct control of commands passed to the host. Guest applications connect to the VBoxSharedOpenGL service by opening the VBoxGuest device and using the VBOXGUEST_IOCTL_HGCM_CONNECT I/O control code. Subsequent commands are then passed by issuing HGCM calls (VBOXGUEST_IOCTL_HGCM_CALL) and specifying the service command to execute. One of the service commands (SHCRGL_GUEST_FN_WRITE_BUFFER) processed by the host service was found vulnerable to an integer overflow. The affected code is shown below.

[trunk/src/VBox/HostServices/SharedOpenGL/crserver/crservice.cpp]

        case SHCRGL_GUEST_FN_WRITE_BUFFER:
        {
            Log(("svcCall: SHCRGL_GUEST_FN_WRITE_BUFFER\n"));
            /* Verify parameter count and types. */
            if (cParms != SHCRGL_CPARMS_WRITE_BUFFER)
            {
                rc = VERR_INVALID_PARAMETER;
            }
            else
            if (   paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /*iBufferID*/
                || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /*cbBufferSize*/
                || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /*ui32Offset*/
                || paParms[3].type != VBOX_HGCM_SVC_PARM_PTR   /*pBuffer*/
               )
            {
                rc = VERR_INVALID_PARAMETER;
            }
            else
            {
                /* Fetch parameters. */
                uint32_t iBuffer      = paParms[0].u.uint32;
                uint32_t cbBufferSize = paParms[1].u.uint32;
                uint32_t ui32Offset   = paParms[2].u.uint32;
                uint8_t *pBuffer      = (uint8_t *)paParms[3].u.pointer.addr;
                uint32_t cbBuffer     = paParms[3].u.pointer.size;

                /* Execute the function. */
                CRVBOXSVCBUFFER_t *pSvcBuffer = svcGetBuffer(iBuffer, cbBufferSize);
                if (!pSvcBuffer || ui32Offset+cbBuffer>cbBufferSize)
                {
                    rc = VERR_INVALID_PARAMETER;
                }
                else
                {
                    memcpy((void*)((uintptr_t)pSvcBuffer->pData+ui32Offset), pBuffer, cbBuffer);

                    /* Return the buffer id */
                    paParms[0].u.uint32 = pSvcBuffer->uiId;
                }
            }

            break;
        }

In the above code, the VBoxSharedOpenGL service does not sufficiently validate the ui32Offset parameter used in specifying the offset into a host managed buffer. If ui32Offset+cbBuffer results in a wrapped integer, the function may attempt to copy the contents of the guest supplied data at a negative offset from the allocated buffer on x86 hosts (and thus corrupting the preceding memory), or at a positive unvalidated buffer offset (+ui32Offset) on x64 systems. In order to fix this, ui32Offset was cast to uint64_t.

Naturally, exploitability in this case depends on the memory allocator of the host operating system. Writing at a negative offset allows the attacker to corrupt the memory of any preceding memory allocation and may allow for arbitrary code execution if the attacker can sufficiently influence the heap state of the host process. On 64-bit hosts, the attacker may pass a very large pBuffer (cbBuffer) and thus require a small ui32Offset to pass the overflow check. This would allow the attacker to corrupt subsequently positioned memory blocks in the host process.

In attempting to exploit vulnerabilities in virtualized devices such as in a guest-to-host escape scenario, an attacker is typically required to install a driver (to do raw I/O operations or manipulate device memory directly) or perform other tasks that require administrative privileges. In this particular case, no special privileges are required as the vulnerability is triggered through the VirtualBox guest device which permits unprivileged access. Although Oracle admits that the Chromium stack (not enabled by default) may open VirtualBox to security vulnerabilities, the affected component is not directly related to Chromium.

Mitigating Null Pointer Exploitation on Windows

As part of a small research project, I recently looked into how exploitation of null pointer vulnerabilities could be mitigated on Windows. The problem with many of the recent vulnerabilities affecting Windows kernel components is that a large number of these issues can be exploited provided that the attacker maps and controls the contents of the null page. As many of you probably know, Windows allows non-privileged users to map the null page through functions such as NtAllocateVirtualMemory or NtMapViewOfFile.

Although there are multiple ways to approach the problem, the solution proposed relies on manipulation of virtual address descriptors (VADs) using a kernel-mode driver. As VADs are used to implement the PAGE_NOACCESS protection in Windows and contain special properties to secure address ranges in process memory, they can be used to deny null page access in both user and kernel space. The following paper details the proposed mitigation and suggests a possible implementation.

Locking Down the Windows Kernel: Mitigating Null Pointer Exploitation [PDF]

Abstract. One of the most prevalent bug classes affecting Windows kernel components today is undeniably NULL pointer dereferences. Unlike other platforms such as Linux, Windows (in staying true to backwards compatibility) allows non-privileged users to map the null page within the context of a user process. As kernel and user-mode components share the same virtual address space, an attacker may potentially be able to exploit kernel null dereference vulnerabilities by controlling the dereferenced data. In this paper, we propose a way to generically mitigate NULL pointer exploitation on Windows by restricting access to the lower portion of process memory using VAD manipulation. Importantly, as the proposed method employs features already present in the memory manager and does not introduce any offending hooks, it can be introduced on a wide range of Windows platforms. Additionally, because the mitigation only introduces minor changes at process creation-time, the performance cost is minimal.