CVE-2010-3941: Windows VDM Task Initialization Vulnerability

In MS10-098, Microsoft patched multiple vulnerabilities reported in win32k.sys that could be leveraged by a non-privileged user to gain elevated rights on a vulnerable system. One of the vulnerabilities affected the win32k component of the WOW32 subsystem, which plays an integral role in managing and scheduling tasks (i.e. Win16 applications) in the Virtual DOS Machine (VDM). As VDM bugs historically have been of interest [1][2] to the security community, I wanted to spend some time explaining the details of this particular vulnerability.

WOW Subsystem

MS-DOS and 16-bit Windows applications are not natively supported by their own environment subsystems running in separate user-mode processes. Instead, they are supported by Virtual DOS Machines (VDM) that provide an execution environment that resembles native MS-DOS. The lowest 16Mb of the VDM uses 16-bit segmented addressing to access memory, and contains MS-DOS emulation code as well as MS-DOS applications. The upper areas of the VDM use 32-bit addressing, and are used to provide Win32 and Windows NT executive services normally provided by MS-DOS itself.

The VDM used to run Win16 applications contain an extra layer of software called the Win16 on Win32 (WOW) layer. When an application calls a Win16 function, the WOW layer intercepts the call and passes control to the equivalent Win32 function. A single VDM is shared by all Win16 applications to emulate the native Windows 3.1 environment in which all applications share the same address space.  This also allows the WOW layer to mimic the non-preemptive multitasking environment expected by Win16 applications. Although each Win16 application is granted its own thread, the WOW layer ensures that only one Win16 application’s thread is active at any given time. [3]

WOW Structures

In order to properly emulate the Windows 3.1 execution environment, threads and processes define their own dedicated WOW kernel structures. When starting a new Win16 application, CSRSS invokes the NtUserNotifyProcessCreate system call to create a WOW per thread information structure (win32k!tagWOWTHREADINFO) for the new task.

typedef struct _tagWOWTHREADINFO        // 5 elements, 0x14 bytes (sizeof)
/*0x000*/     struct _tagWOWTHREADINFO* pwtiNext;
/*0x004*/     ULONG32      idTask;
/*0x008*/     ULONG32      idWaitObject;
/*0x00C*/     ULONG32      idParentProcess;
/*0x010*/     struct _KEVENT* pIdleEvent;

Notably, the WOWTHREADINFO structure defines a pointer to the idle event object (pIdleEvent), used in WOW task synchronization, as well as a unique id (idTask) specific to each task in a VDM. The task id is also stored at offset 0x18 in an undocumented thread data structure pointed to by NtCurrentTeb()->WOW32Reserved. As the WOWTHREADINFO structure is created upon process creation, it is actually initialized before the thread object itself is set up.

When a VDM first starts up and is initialized in WOW32!W32Init, it additionally calls WOW32!WK32InitializeHungAppSupport to allocate and initialize a per process WOW information structure (win32k!tagWOWPROCESSINFO).

typedef struct _tagWOWPROCESSINFO          // 10 elements, 0x28 bytes (sizeof)
/*0x000*/     struct _tagWOWPROCESSINFO* pwpiNext;
/*0x004*/     struct _tagTHREADINFO* ptiScheduled;
/*0x008*/     struct _tagTDB* ptdbHead;
/*0x00C*/     VOID*        lpfnWowExitTask;
/*0x010*/     struct _KEVENT* pEventWowExec;
/*0x014*/     VOID*        hEventWowExecClient;
/*0x018*/     ULONG32      nSendLock;
/*0x01C*/     ULONG32      nRecvLock;
/*0x020*/     struct _tagTHREADINFO* CSOwningThread;
/*0x024*/     LONG32       CSLockCount;

The WOWPROCESSINFO structure is global to all tasks running in a VDM in the same way that a PROCESSINFO structure is global to all GUI threads running in a process. It holds information such as the currently scheduled task (ptiScheduled) and the list of WOW tasks (ptdbHead), sorted by priority. Each entry in the task list is represented by a task data block structure (win32k!tagTDB), shown below.

typedef struct _tagTDB              // 7 elements, 0x18 bytes (sizeof)
/*0x000*/     struct _tagTDB* ptdbNext;
/*0x004*/     INT32        nEvents;
/*0x008*/     INT32        nPriority;
/*0x00C*/     struct _tagTHREADINFO* pti;
/*0x010*/     struct _tagWOWTHREADINFO* pwti;
/*0x014*/     UINT16       hTaskWow;
/*0x016*/     UINT16       TDB_Flags;
} tagTDB, *PtagTDB;

The TDB is created in NtUserInitTask when a VDM calls WOW32!WK32WOWInitTask to initialize a new task. It sets the task priority (nPriority) and links the task to the WOW per-thread information structure (pwti), as well as the regular per-thread information structure (pti). The task data block structure also keeps track of pending events (nEvents), used when scheduling tasks within a VDM.

Relationships between WOW kernel structures

Task Initialization Vulnerability

In initializing a new task via NtUserInitTask within the context of a shared VDM, zzzInitTask attempts to associate the already created WOWTHREADINFO structure with the task data block structure of the new task. However, the function fails to check if the specified task id (provided in the eighth argument to NtUserInitTask) has already been initialized. Thus, in initializing a task with an id of an already initialized task, the WOW per-thread information structure is assigned to a second task. Consequently, destroying either task referencing this particular structure would cause the other task to reference freed memory. When in turn the other task is destroyed, a double free occurs. As an attacker could easily reallocate the freed memory after having destroyed the first task, the vulnerability could be leveraged to escalate privileges (e.g. by freeing an in-use object or by exploiting the kernel pool).

In short, the following steps can be taken to reproduce the vulnerability.

  1. Start a 16-bit Windows application
  2. Enumerate the task ids of NTVDM’s threads by inspecting TEB->WOW32Reserved
  3. Call NtUserInitTask from the NTVDM process and specify the task id of an existing task/thread
  4. Terminate the process

A bugcheck such as the one below should be triggered.

*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *

The current thread is making a bad pool request.  Typically this is at a bad IRQL level or double freeing the same allocation, etc.
Arg1: 00000007, Attempt to free pool which was already freed
Arg2: 00001097, (reserved)
Arg3: 5a040063, Memory contents of the pool block
Arg4: ffa9e320, Address of the block of pool being deallocated

Debugging Details:

POOL_ADDRESS:  ffa9e320 Paged session pool


BUGCHECK_STR:  0xc2_7_Uswt


PROCESS_NAME:  ntvdm.exe


LAST_CONTROL_TRANSFER:  from 828e9e71 to 82878394

96877734 828e9e71 00000003 73526f7a 00000065 nt!RtlpBreakWithStatusInstruction
96877784 828ea96d 00000003 ffa9e318 000001ff nt!KiBugCheckDebugBreak+0x1c
96877b48 8292c1b6 000000c2 00000007 00001097 nt!KeBugCheck2+0x68b
96877bc4 92e7db18 ffa9e320 00000000 00000000 nt!ExFreePoolWithTag+0x1b1
96877be0 92defd09 ffa98a08 fe59fdd8 04751a78 win32k!DestroyTask+0xa1
96877c30 92decf87 895e6158 895e6158 00000000 win32k!xxxDestroyThreadInfo+0x5b5
96877c44 92deeaa6 895e6158 00000001 895e6158 win32k!UserThreadCallout+0x77
96877c60 82a55af4 895e6158 00000001 73526422 win32k!W32pThreadCallout+0x3a
96877cdc 82a8350e 00000000 00000000 895e6158 nt!PspExitThread+0x455
96877cfc 82a81958 895e6158 00000000 00000001 nt!PspTerminateThreadByPointer+0x61
96877d24 8285042a 00000000 00000000 0287f2ac nt!NtTerminateThread+0x74
96877d24 77a564f4 00000000 00000000 0287f2ac nt!KiFastCallEntry+0x12a
0287f290 77a55d2c 77a40892 00000000 00000000 ntdll!KiFastSystemCallRet
0287f294 77a40892 00000000 00000000 0287f364 ntdll!NtTerminateThread+0xc
0287f2ac 0e08d2d8 00000000 0287f308 6f1bb442 ntdll!RtlExitUserThread+0x39
0287f2b8 6f1bb442 00000000 6f1a718d e68f4f42 ntvdm!host_ExitThread+0x13
0287f2c0 6f1a718d e68f4f42 00000200 010ec288 WOW32!WK32WOWKillTask+0x28
0287f308 0e0a23ad 0e0a32ce 80000000 00002bf4 WOW32!W32Dispatch+0xb4
0287f30c 0e0a32ce 80000000 00002bf4 00000000 ntvdm!EventVdmBop+0x29
0287f324 6f1bb3d7 16c729e8 6f1badc6 010e0f10 ntvdm!cpu_simulate+0x186
0287fb74 0e08d29d 16c729e8 e53c55e4 00000000 WOW32!W32Thread+0x611
0287fbb4 77211174 010e0f10 0287fc00 77a6b3f5 ntvdm!ThreadStartupRoutine+0x2c
0287fbc0 77a6b3f5 010e0f10 7528fc69 00000000 kernel32!BaseThreadInitThunk+0xe
0287fc00 77a6b3c8 0e08d271 010e0f10 00000000 ntdll!__RtlUserThreadStart+0x70
0287fc18 00000000 0e08d271 010e0f10 00000000 ntdll!_RtlUserThreadStart+0x1b


92e7db18 a1fcc1f392      mov     eax,dword ptr [win32k!gpsi (92f3c1fc)]


SYMBOL_NAME:  win32k!DestroyTask+a1

FOLLOWUP_NAME:  MachineOwner


IMAGE_NAME:  win32k.sys


FAILURE_BUCKET_ID:  0xc2_7_Uswt_win32k!DestroyTask+a1

BUCKET_ID:  0xc2_7_Uswt_win32k!DestroyTask+a1

Followup: MachineOwner


  1. Microsoft Windows NT #GP Trap Handler Allows Users to Switch Kernel Stack
  2. Windows VDM Zero Page Race Condition Privilege Escalation

Comments are closed.