KeWaitForMultipleObjects, pero no mucho!

February 27th, 2008 - Fernando Roberto

A while ago, my friend Slug published a post about his adventures in Kernel looking for the reason for a blue screen. The problem was caused by an incorrect call to the KeWaitForMultipleObjects() function in an attempt to wait for 4 objects. Yeah Wanderley, that’s a result of inheriting a code from everyone. Just because the function has the word “multiple” in its name, it  does not mean we can wait for that absurd amount of four objects. In this post I am giving you a simple example of how to use this feature.

Where does the limit live?

The KeWaitForMultipleObjects() function uses an array of elements of KWAIT_BLOCK type which contains the data referring to the multiple waiting objects being performed. In many cases, this amount of multiple waiting objects is no more than three. In order to avoid ever having to build an array of KWAIT_BLOCK structures to perform a multiple waiting, the structure that represents a thread in kernel mode, the KTHREAD, contains an array of four elements of this structure embedded in it, as it is shown below.

kd> dt nt!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 MutantListHead   : _LIST_ENTRY
   +0x018 InitialStack     : Ptr32 Void
   +0x01c StackLimit       : Ptr32 Void
   +0x020 Teb              : Ptr32 Void
   +0x024 TlsArray         : Ptr32 Void
   +0x028 KernelStack      : Ptr32 Void
   +0x02c DebugActive      : UChar
   +0x02d State            : UChar
   +0x02e Alerted          : [2] UChar
   +0x030 Iopl             : UChar
   +0x031 NpxState         : UChar
   +0x032 Saturation       : Char
   +0x033 Priority         : Char
   +0x034 ApcState         : _KAPC_STATE
   +0x04c ContextSwitches  : Uint4B
   +0x050 IdleSwapBlock    : UChar
   +0x051 Spare0           : [3] UChar
   +0x054 WaitStatus       : Int4B
   +0x058 WaitIrql         : UChar
   +0x059 WaitMode         : Char
   +0x05a WaitNext         : UChar
   +0x05b WaitReason       : UChar
   +0x05c WaitBlockList    : Ptr32 _KWAIT_BLOCK
   +0x060 WaitListEntry    : _LIST_ENTRY
   +0x060 SwapListEntry    : _SINGLE_LIST_ENTRY
   +0x068 WaitTime         : Uint4B
   +0x06c BasePriority     : Char
   +0x06d DecrementCount   : UChar
   +0x06e PriorityDecrement : Char
   +0x06f Quantum          : Char
   +0x070 WaitBlock        : [4] _KWAIT_BLOCK
   +0x0d0 LegoData         : Ptr32 Void
   +0x0d4 KernelApcDisable : Uint4B
   +0x0d8 UserAffinity     : Uint4B
   +0x0dc SystemAffinityActive : UChar
   +0x0dd PowerState       : UChar
   +0x0de NpxIrql          : UChar
   +0x0df InitialNode      : UChar
   +0x0e0 ServiceTable     : Ptr32 Void
   +0x0e4 Queue            : Ptr32 _KQUEUE
   +0x0e8 ApcQueueLock     : Uint4B
   +0x0f0 Timer            : _KTIMER
   +0x118 QueueListEntry   : _LIST_ENTRY
   +0x120 SoftAffinity     : Uint4B
   +0x124 Affinity         : Uint4B
   +0x128 Preempted        : UChar
   +0x129 ProcessReadyQueue : UChar
   +0x12a KernelStackResident : UChar
   +0x12b NextProcessor    : UChar
   +0x12c CallbackStack    : Ptr32 Void
   +0x130 Win32Thread      : Ptr32 Void
   +0x134 TrapFrame        : Ptr32 _KTRAP_FRAME
   +0x138 ApcStatePointer  : [2] Ptr32 _KAPC_STATE
   +0x140 PreviousMode     : Char
   +0x141 EnableStackSwap  : UChar
   +0x142 LargeStack       : UChar
   +0x143 ResourceIndex    : UChar
   +0x144 KernelTime       : Uint4B
   +0x148 UserTime         : Uint4B
   +0x14c SavedApcState    : _KAPC_STATE
   +0x164 Alertable        : UChar
   +0x165 ApcStateIndex    : UChar
   +0x166 ApcQueueable     : UChar
   +0x167 AutoAlignment    : UChar
   +0x168 StackBase        : Ptr32 Void
   +0x16c SuspendApc       : _KAPC
   +0x19c SuspendSemaphore : _KSEMAPHORE
   +0x1b0 ThreadListEntry  : _LIST_ENTRY
   +0x1b8 FreezeCount      : Char
   +0x1b9 SuspendCount     : Char
   +0x1ba IdealProcessor   : UChar
   +0x1bb DisableBoost     : UChar

From these four elements, three of them are intended to be  used by KeWaitForMultipleObjects(), while the fourth one is reserved to be used by the system to implement the waiting with a timeout.

To wait for up to three objects, the code would be as it is shown below:

    //-f--> PVOID array
    PVOID       pObjects[3];
 
    //-f--> We have to initialize a pointer array for 
    //      the objects which the waiting is based in.
    pObjects[0] = &kEvent1;
    pObjects[1] = &kEvent2;
    pObjects[2] = &kEvent3;
 
    //-f--> That's a simple call for a multiple waiting objects
    //      of 3 ones.
    nts = KeWaitForMultipleObjects(3,
                                   pObjects,
                                   WaitAll,
                                   Executive,
                                   KernelMode,
                                   FALSE,
                                   NULL,
                                   NULL);

Does it mean we cannot wait for more than three objects each time? Well, let me explain it. When it is necessary to wait for more than three objects, we must allocate a large buffer, enough to hold an N-element array of the KWAIT_BLOCK structure, where N is less than or equal to 64. If there is more than that, we are going to have a nice MAXIMUM_WAIT_OBJECTS_EXCEEDED.

“If a buffer is supplied, the Count parameter may not exceed MAXIMUM_WAIT_OBJECTS. If no buffer is supplied, the Count parameter may not exceed THREAD_WAIT_OBJECTS.”

//-f--> From wdm.h
 
#define THREAD_WAIT_OBJECTS 3       // Builtin usable wait blocks
 
#define MAXIMUM_WAIT_OBJECTS 64     // Maximum number of wait objects

In that case, to be able to do a multiple 10-objects waiting, we’d have the code below:

   //-f--> PVOID array
    PVOID           pObjects[10];
 
    //-f--> An additional array of KWAIT_BLOCKs
    PKWAIT_BLOCK    pWaitBlocks = NULL;
 
    //-f--> We have to initialize a pointer array for
    //      the objects which the waiting is based in.
    pObjects[0] = &kEvent1;
    pObjects[1] = &kEvent2;
    pObjects[2] = &kEvent3;
    pObjects[3] = &kEvent4;
    pObjects[4] = &kEvent5;
 
    //-f--> You may not believe it but I do know how to use loops and arrays.
    //      I need to show you that the objects don't necessarily
    //      need to be in an array; however, the pointers related
    //      to them do.
    pObjects[5] = &kEvent6;
    pObjects[6] = &kEvent7;
    pObjects[7] = &kEvent8;
    pObjects[8] = &kEvent9;
    pObjects[9] = &kEvent10;
 
    //-f--> We cannot initialize the KWAIT_BLOCKS array structures  
    //      embedded into the KTHREAD structure; we must allocate our own array.
    pWaitBlocks = (PKWAIT_BLOCK) ExAllocatePool(NonPagedPool,
                                                sizeof(KWAIT_BLOCK) * 10);
 
    //-f--> Doing another test doesn't hurt anybody; however, try not
    //      to test a driver to see how many people die.
    ASSERT(pWaitBlocks != NULL);
 
    //-f--> A simple call to a multiple waiting for
    //      10 objects without a bugcheck.
    nts = KeWaitForMultipleObjects(10,
                                   pObjects,
                                   WaitAll,
                                   Executive,
                                   KernelMode,
                                   FALSE,
                                   NULL,
                                   pWaitBlocks);
 
    //-f--> Having memory leak is not polite.
    ExFreePool(pWaitBlocks);

In summary, that’s all.
Good bye!

Leave a Reply