Example of how the Annotations in WDM.H and OACR catch a bug on the developer desktop (PFD/SDV "Clean" 2 of 6)

Suppose you have code that acquires a spinlock and then attempts to wait before releasing that spinlock without specifying a timeout value:

NTSTATUS

MyDPCRoutineRunningAtDispatchLevel()

{

  // A whole bunch of code

  KeAcquireSpinLock(&myLock, &oldIrql);

  // More code

  KeWaitForSingleObject(&myTimer, Executive, KernelMode, FALSE, NULL);

  // Even more code

}

Because NULL is an acceptable value for the last parameter to KeWaitForSingleObject, the compiler won’t complain about it. Shortly after the code is done and compiled, however, OACR pops up to warn me of potential issues, including an instance where I’m calling KeWaitForSingleObject at an illegal IRQL. Once I click on the OACR pop-up and bring up the Warning viewer, I see this:

 

PFD Warning 28121

 

Reading the warning shows that I created a demo driver that is a extremely simplified version of a mistake I’ve seen more than once. Most driver developers know that waiting indefinitely at DISPATCH_LEVEL is a potentially fatal mistake, so it is unlikely that these two calls would appear so close to one another. However, if “//more code” on line 59 was actually a long series of conditionals that resulted in the lock not being freed before the wait call, it may be difficult to see in a visual code review. Another important point: in my example code, I added no annotations, only making those calls to KeAcquireSpinLock and KeWaitForSingleObject at those various points, and OACR/PFD alerted me to this extremely painful bug well before the code left my desktop.

The reason that PFD knew I was violating a basic rule of KeWaitForSingleObject is because of the annotations that are now present on those two functions in wdm.h:

__drv_maxIRQL(DISPATCH_LEVEL)

__drv_savesIRQL

__drv_setsIRQL(DISPATCH_LEVEL)

_DECL_HAL_KE_IMPORT

KIRQL

FASTCALL

KfAcquireSpinLock (

    __inout __deref __drv_acquiresExclusiveResource(KeSpinLockType)

    PKSPIN_LOCK SpinLock

    );

__drv_minIRQL(PASSIVE_LEVEL)

__drv_when((Timeout==NULL || *Timeout!=0), __drv_maxIRQL(APC_LEVEL))

__drv_when((Timeout!=NULL && *Timeout==0), __drv_maxIRQL(DISPATCH_LEVEL))

NTKERNELAPI

NTSTATUS

KeWaitForSingleObject (

    __in __deref __drv_notPointer PVOID Object,

    __in __drv_strictTypeMatch(__drv_typeCond) KWAIT_REASON WaitReason,

    __in __drv_strictType(KPROCESSOR_MODE/enum _MODE,__drv_typeConst)

    KPROCESSOR_MODE WaitMode,

    __in BOOLEAN Alertable,

    __in_opt PLARGE_INTEGER Timeout

    );

The bolded portions of each API declaration are important to the example above:

1. __drv_setsIRQL tells PFD that when this KeAcquireSpinLock (actually Kf…, due to #defines in wdm.h) is called, it sets the IRQL value to DISPATCH_LEVEL.

2. When KeWaitForSingleObject is called and the timeout is either null or valid and not equal to zero (zero is return immediately), then KeWaitForSingleObject shouldn’t be called at dispatch level.

The annotations in WDM enabled the developer to see this coding error in very short order, at least as compared to a manual review that would require him or her to a) walk through whatever logic “//more code” represented and b) remember that calling KeWait… at raised IRQL is bad, which also assumes he or she remembered that the spinlock acquisition also raised the IRQL in the first place. Even if the developer is completely knowledgeable about all points in b), the savings on walking the code to find this issue cannot be ignored.