Wednesday, February 26, 2014

Outswapped kernel stack

You definitely know that kernel stack can be outswapped if some conditions are met. One such condition is waiting with WaitMode set to UserMode.

 If an event is allocated on a kernel stack it can be swapped out when a driver does something like this

GetOperationCompletionStatus( ... )
{
    KEVENT    Event;

    KeInitializeEvent( &Event. SynchronizationEvent, FALSE );

    KeAcquireSpinlock( &Lock, &OldIrql );
    {
       if( FALSE == Opeartion->Completed  ){
          Opeartion->CompletionEvent = &Event;
          Wait = TRUE;
       }
    }
    KeReleaseSpinLock( &Lock, OldIrql );

    while( Wait ){

       // allow a user to wake up the thread when terminating the
       // process, but note that the stack might be outswapped
       // when the thread is blocked waiting for the event
       WaitStatus = KeWaitForSingleObject( &Event,
                                           Executive,
                                           UserMode,
                                           FALSE,
                                           NULL );

       if( STATUS_SUCCESS != WaitStatus )
       {
          KeAcquireSpinlock( &Lock, &OldIrql );
          {
             // if NULL then go back to waiting as
             // there is a ongoing completion
             if( Opeartion->CompletionEvent ){
                Opeartion->CompletionEvent = NULL;
                Wait = FALSE;
             }
          }
          KeReleaseSpinLock( &Lock, OldIrql );

       }
}


NotifyOfCompletion()
{
    KeAcquireSpinlock( &Lock, &OldIrql );
    {
        Opeartion->Completed = TRUE;
        if( Opeartion->CompletionEvent  ){

           // the following call sometimes crashes the system
           // when tries to access an outswapped page
           KeSetEvent( Opeartion->CompletionEvent,                                      IO_NO_INCREMENT,
                       FALSE );
           Opeartion->CompletionEvent = NULL;
       }
    }
    KeReleaseSpinLock( &Lock, OldIrql );
}

 the reason to do this is when you want to be slightly more gentle and allow a user to terminate a waiting thread, this is a common scenario for distributed file systems where response time might be up to minutes.

    The problem with the above code is that a kernel stack can be swapped out while waiting in  KeWaitForSingleObject whith waiting mode set to UserMode . The call to KeSetEvent tries to access the event on the outswapped stack when the IRQL is DISPATCH_LEVEL and it has nothing to do with a call to KeAcquireSpinlock, the same will be even if you try to call KeSetEvent without raising IRQL as KeSetEvent elevates IRQL when working with the event.

   If you check the event address on an outswapped stack with WinDBG you see 

1: kd> !pte 0xaf792cac
                    VA af792cac
PDE at C0602BD8            PTE at C057BC90
contains 000000005402F863  contains 00000000A5129BE2
pfn 5402f     ---DA--KWEV  not valid
                            Transition: a5129

                            Protect: 1f - Outswapped kernel stack

The solution to the above example is to allocate the event from the NonPaged pool.

No comments:

Post a Comment