A KLOCK_QUEUE_HANDLE cannot be shared across multiple threads

Folks like to write wrappers. One very popular wrapper is for a KSPIN_LOCK.  An example wrapper would be

typedef struct _MY_LOCK {
    KSPIN_LOCK Lock;
    KIRQL LockIrql;

VOID FORCEINLINE AcquireMyLock(PMY_LOCK Lock) { KeAcquireSpinLock(&Lock->Lock, &Lock->LockIrql); }
VOID FORCEINLINE ReleaseMyLock(PMY_LOCK Lock) { KeReleaseSpinLock(&Lock->Lock, Lock->LockIrql); }

This works because KeAcquireSpinLock treats the PKIRQL parameter as a OUT parameter, it is written to after the lock has been acquired and is not used in the lock acquisition itself. I personally am paranoid about such undocumented implementation details and I assume that the PKIRQL can be both used as scratch space during the acquisition of the lock and would rewrite AcquireMyLock to use a local to get the previous irql and then assign it into the structure.

    KIRQL irql;

    KeAcquireSpinLock(&Lock->Lock, &irql);
    Lock->LockIrql = irql;

Now one might look at the DDK, see KeAcquireInStackQueuedSpinLock, replace KIRQL LockIrql in MY_LOCK with a KLOCK_QUEUE_HANDLE LockHandle, update AcquireMyLock and ReleaseMyLock to use the new InStack calls, recompile the code and expect everything to work.  But it won't.  It will not work because the KLOCK_QUEUE_HANDLE pointer (or just lock handle from now on) passed to KeAcquireInStackQueuedSpinLock is used for the acquisiion of the spinlock.  If the lock is held, the lock handle is placed into a list of waiters and let's assume that the lock handle is placed at the end of the waiter list.  Since the lock handle is an opaque structure, let's make an assumption that it contains a Next field which points to the next waiter.

Now imagine if 2 threads were to use the same lock handle to acquire the same spin lock and that spin lock as already held by a 3rd thread (so our first 2 threads will be inserted into the waiter list).  Thread #1 would use the lock handle to insert itself into the waiter list and be inserted at the end of the list.  Now thread #2 uses the lock handle to insert itself into the waiter list, also updating the Next field modified by thread #1. You have now corrupted the list of waiters (the same way you would corrupt a doubley linked list if you inserted the same LIST_ENTRY twice into the list) and will hang or bugcheck.

In conclusion, each thread which acquires an in stack queued spinlock must have its own lock handle.  99.99% of the time, the lock handle is declared on the stack, hence the name in stack queued spinlock.

Comments (0)

Skip to main content