/*++ Copyright (c) 2001 Microsoft Corporation Module Name: keyedevent.c Abstract: This module houses routines that do keyed event processing. Author: Neill Clift (NeillC) 25-Apr-2001 Revision History: --*/ #include "exp.h" #pragma hdrstop #ifdef ALLOC_PRAGMA #pragma alloc_text(INIT, ExpKeyedEventInitialization) #pragma alloc_text(PAGE, NtCreateKeyedEvent) #pragma alloc_text(PAGE, NtOpenKeyedEvent) #pragma alloc_text(PAGE, NtReleaseKeyedEvent) #pragma alloc_text(PAGE, NtWaitForKeyedEvent) #endif // // Define the keyed event object type // typedef struct _KEYED_EVENT_OBJECT { EX_PUSH_LOCK Lock; LIST_ENTRY WaitQueue; } KEYED_EVENT_OBJECT, *PKEYED_EVENT_OBJECT; POBJECT_TYPE ExpKeyedEventObjectType; // // The low bit of the keyvalue signifies that we are a release thread waiting // for the wait thread to enter the keyed event code. // #define KEYVALUE_RELEASE 1 #define LOCK_KEYED_EVENT_EXCLUSIVE(xxxKeyedEventObject,xxxCurrentThread) { \ KeEnterCriticalRegionThread (&(xxxCurrentThread)->Tcb); \ ExAcquirePushLockExclusive (&(xxxKeyedEventObject)->Lock); \ } #define UNLOCK_KEYED_EVENT_EXCLUSIVE(xxxKeyedEventObject,xxxCurrentThread) { \ ExReleasePushLockExclusive (&(xxxKeyedEventObject)->Lock); \ KeLeaveCriticalRegionThread (&(xxxCurrentThread)->Tcb); \ } #define UNLOCK_KEYED_EVENT_EXCLUSIVE_UNSAFE(xxxKeyedEventObject) { \ ExReleasePushLockExclusive (&(xxxKeyedEventObject)->Lock); \ } NTSTATUS ExpKeyedEventInitialization ( VOID ) /*++ Routine Description: Initialize the keyed event objects and globals. Arguments: None. Return Value: NTSTATUS - Status of call --*/ { NTSTATUS Status; UNICODE_STRING Name; OBJECT_TYPE_INITIALIZER oti = {0}; OBJECT_ATTRIBUTES oa; SECURITY_DESCRIPTOR SecurityDescriptor; PACL Dacl; ULONG DaclLength; HANDLE KeyedEventHandle; GENERIC_MAPPING GenericMapping = {STANDARD_RIGHTS_READ | KEYEDEVENT_WAIT, STANDARD_RIGHTS_WRITE | KEYEDEVENT_WAKE, STANDARD_RIGHTS_EXECUTE, KEYEDEVENT_ALL_ACCESS}; PAGED_CODE (); RtlInitUnicodeString (&Name, L"KeyedEvent"); oti.Length = sizeof (oti); oti.InvalidAttributes = 0; oti.PoolType = PagedPool; oti.ValidAccessMask = KEYEDEVENT_ALL_ACCESS; oti.GenericMapping = GenericMapping; oti.DefaultPagedPoolCharge = 0; oti.DefaultNonPagedPoolCharge = 0; oti.UseDefaultObject = TRUE; Status = ObCreateObjectType (&Name, &oti, NULL, &ExpKeyedEventObjectType); if (!NT_SUCCESS (Status)) { return Status; } // // Create a global object for processes that are out of memory // Status = RtlCreateSecurityDescriptor (&SecurityDescriptor, SECURITY_DESCRIPTOR_REVISION); if (!NT_SUCCESS (Status)) { return Status; } DaclLength = sizeof (ACL) + sizeof (ACCESS_ALLOWED_ACE) * 3 + RtlLengthSid (SeLocalSystemSid) + RtlLengthSid (SeAliasAdminsSid) + RtlLengthSid (SeWorldSid); Dacl = ExAllocatePoolWithTag (PagedPool, DaclLength, 'lcaD'); if (Dacl == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } Status = RtlCreateAcl (Dacl, DaclLength, ACL_REVISION); if (!NT_SUCCESS (Status)) { ExFreePool (Dacl); return Status; } Status = RtlAddAccessAllowedAce (Dacl, ACL_REVISION, KEYEDEVENT_ALL_ACCESS, SeAliasAdminsSid); if (!NT_SUCCESS (Status)) { ExFreePool (Dacl); return Status; } Status = RtlAddAccessAllowedAce (Dacl, ACL_REVISION, KEYEDEVENT_ALL_ACCESS, SeLocalSystemSid); if (!NT_SUCCESS (Status)) { ExFreePool (Dacl); return Status; } Status = RtlAddAccessAllowedAce (Dacl, ACL_REVISION, KEYEDEVENT_WAIT|KEYEDEVENT_WAKE|READ_CONTROL, SeWorldSid); if (!NT_SUCCESS (Status)) { ExFreePool (Dacl); return Status; } Status = RtlSetDaclSecurityDescriptor (&SecurityDescriptor, TRUE, Dacl, FALSE); if (!NT_SUCCESS (Status)) { ExFreePool (Dacl); return Status; } RtlInitUnicodeString (&Name, L"\\KernelObjects\\CritSecOutOfMemoryEvent"); InitializeObjectAttributes (&oa, &Name, OBJ_PERMANENT, NULL, &SecurityDescriptor); Status = ZwCreateKeyedEvent (&KeyedEventHandle, KEYEDEVENT_ALL_ACCESS, &oa, 0); ExFreePool (Dacl); if (NT_SUCCESS (Status)) { Status = ZwClose (KeyedEventHandle); } return Status; } NTSTATUS NtCreateKeyedEvent ( OUT PHANDLE KeyedEventHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN ULONG Flags ) /*++ Routine Description: Create a keyed event object and return its handle Arguments: KeyedEventHandle - Address to store returned handle in DesiredAccess - Access required to keyed event ObjectAttributes - Object attributes block to describe parent handle and name of event Return Value: NTSTATUS - Status of call --*/ { NTSTATUS Status; PKEYED_EVENT_OBJECT KeyedEventObject; HANDLE Handle; KPROCESSOR_MODE PreviousMode; // // Get previous processor mode and probe output arguments if necessary. // Zero the handle for error paths. // PreviousMode = KeGetPreviousMode(); try { if (PreviousMode != KernelMode) { ProbeForReadSmallStructure (KeyedEventHandle, sizeof (*KeyedEventHandle), sizeof (*KeyedEventHandle)); } *KeyedEventHandle = NULL; } except (ExSystemExceptionFilter ()) { return GetExceptionCode (); } if (Flags != 0) { return STATUS_INVALID_PARAMETER_4; } // // Create a new keyed event object and initialize it. // Status = ObCreateObject (PreviousMode, ExpKeyedEventObjectType, ObjectAttributes, PreviousMode, NULL, sizeof (KEYED_EVENT_OBJECT), 0, 0, &KeyedEventObject); if (!NT_SUCCESS (Status)) { return Status; } // // Initialize the lock and wait queue // ExInitializePushLock (&KeyedEventObject->Lock); InitializeListHead (&KeyedEventObject->WaitQueue); // // Insert the object into the handle table // Status = ObInsertObject (KeyedEventObject, NULL, DesiredAccess, 0, NULL, &Handle); if (!NT_SUCCESS (Status)) { return Status; } try { *KeyedEventHandle = Handle; } except (ExSystemExceptionFilter ()) { // // The caller changed the page protection or deleted the momory for the handle. // No point closing the handle as process rundown will do that and we don't // know its still the same handle // Status = GetExceptionCode (); } return Status; } NTSTATUS NtOpenKeyedEvent ( OUT PHANDLE KeyedEventHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes ) /*++ Routine Description: Open a keyed event object and return its handle Arguments: KeyedEventHandle - Address to store returned handle in DesiredAccess - Access required to keyed event ObjectAttributes - Object attributes block to describe parent handle and name of event Return Value: NTSTATUS - Status of call --*/ { HANDLE Handle; KPROCESSOR_MODE PreviousMode; NTSTATUS Status; // // Get previous processor mode and probe output handle address // if necessary. // PreviousMode = KeGetPreviousMode(); try { if (PreviousMode != KernelMode) { ProbeForReadSmallStructure (KeyedEventHandle, sizeof (*KeyedEventHandle), sizeof (*KeyedEventHandle)); } *KeyedEventHandle = NULL; } except (ExSystemExceptionFilter ()) { return GetExceptionCode (); } // // Open handle to the keyed event object with the specified desired access. // Status = ObOpenObjectByName (ObjectAttributes, ExpKeyedEventObjectType, PreviousMode, NULL, DesiredAccess, NULL, &Handle); if (NT_SUCCESS (Status)) { try { *KeyedEventHandle = Handle; } except (ExSystemExceptionFilter ()) { Status = GetExceptionCode (); } } return Status; } NTSTATUS NtReleaseKeyedEvent ( IN HANDLE KeyedEventHandle, IN PVOID KeyValue, IN BOOLEAN Alertable, IN PLARGE_INTEGER Timeout OPTIONAL ) /*++ Routine Description: Release a previous or soon to be waiter with a matching key Arguments: KeyedEventHandle - Handle to a keyed event KeyValue - Value to be used to match the waiter against Alertable - Should the wait be alertable, we rarely should have to wait Timeout - Timout value for the wait, waits should be rare Return Value: NTSTATUS - Status of call --*/ { NTSTATUS Status; KPROCESSOR_MODE PreviousMode; PKEYED_EVENT_OBJECT KeyedEventObject; PETHREAD CurrentThread, TargetThread; PEPROCESS CurrentProcess; PLIST_ENTRY ListHead, ListEntry; LARGE_INTEGER TimeoutValue; PVOID OldKeyValue = NULL; if ((((ULONG_PTR)KeyValue) & KEYVALUE_RELEASE) != 0) { return STATUS_INVALID_PARAMETER_1; } CurrentThread = PsGetCurrentThread (); PreviousMode = KeGetPreviousModeByThread (&CurrentThread->Tcb); if (Timeout != NULL) { try { if (PreviousMode != KernelMode) { ProbeForRead (Timeout, sizeof (*Timeout), sizeof (UCHAR)); } TimeoutValue = *Timeout; Timeout = &TimeoutValue; } except(ExSystemExceptionFilter ()) { return GetExceptionCode (); } } Status = ObReferenceObjectByHandle (KeyedEventHandle, KEYEDEVENT_WAKE, ExpKeyedEventObjectType, PreviousMode, &KeyedEventObject, NULL); if (!NT_SUCCESS (Status)) { return Status; } CurrentProcess = PsGetCurrentProcessByThread (CurrentThread); ListHead = &KeyedEventObject->WaitQueue; LOCK_KEYED_EVENT_EXCLUSIVE (KeyedEventObject, CurrentThread); ListEntry = ListHead->Flink; while (1) { if (ListEntry == ListHead) { // // We could not find a key matching ours in the list. // Either somebody called us with wrong values or the waiter // has not managed to get queued yet. We wait ourselves // to be released by the waiter. // OldKeyValue = CurrentThread->KeyedWaitValue; CurrentThread->KeyedWaitValue = (PVOID) (((ULONG_PTR)KeyValue)|KEYVALUE_RELEASE); // // Insert the thread at the head of the list. We establish an invariant // were release waiters are always at the front of the queue to improve // the wait code since it only has to search as far as the first non-release // waiter. // InsertHeadList (ListHead, &CurrentThread->KeyedWaitChain); TargetThread = NULL; break; } else { TargetThread = CONTAINING_RECORD (ListEntry, ETHREAD, KeyedWaitChain); if (TargetThread->KeyedWaitValue == KeyValue && THREAD_TO_PROCESS (TargetThread) == CurrentProcess) { RemoveEntryList (ListEntry); InitializeListHead (ListEntry); break; } } ListEntry = ListEntry->Flink; } // // Release the lock but leave APC's disabled. // This prevents us from being suspended and holding up the target. // UNLOCK_KEYED_EVENT_EXCLUSIVE_UNSAFE (KeyedEventObject); if (TargetThread != NULL) { KeReleaseSemaphore (&TargetThread->KeyedWaitSemaphore, SEMAPHORE_INCREMENT, 1, FALSE); KeLeaveCriticalRegionThread (&CurrentThread->Tcb); } else { KeLeaveCriticalRegionThread (&CurrentThread->Tcb); Status = KeWaitForSingleObject (&CurrentThread->KeyedWaitSemaphore, Executive, PreviousMode, Alertable, Timeout); // // If we were woken by termination then we must manualy remove // ourselves from the queue // if (Status != STATUS_SUCCESS) { BOOLEAN Wait = TRUE; LOCK_KEYED_EVENT_EXCLUSIVE (KeyedEventObject, CurrentThread); if (!IsListEmpty (&CurrentThread->KeyedWaitChain)) { RemoveEntryList (&CurrentThread->KeyedWaitChain); InitializeListHead (&CurrentThread->KeyedWaitChain); Wait = FALSE; } UNLOCK_KEYED_EVENT_EXCLUSIVE (KeyedEventObject, CurrentThread); // // If this thread was no longer in the queue then another thread // must be about to wake us up. Wait for that wake. // if (Wait) { KeWaitForSingleObject (&CurrentThread->KeyedWaitSemaphore, Executive, KernelMode, FALSE, NULL); } } CurrentThread->KeyedWaitValue = OldKeyValue; } ObDereferenceObject (KeyedEventObject); return Status; } NTSTATUS NtWaitForKeyedEvent ( IN HANDLE KeyedEventHandle, IN PVOID KeyValue, IN BOOLEAN Alertable, IN PLARGE_INTEGER Timeout OPTIONAL ) /*++ Routine Description: Wait on the keyed event for a specific release Arguments: KeyedEventHandle - Handle to a keyed event KeyValue - Value to be used to match the release thread against Alertable - Makes the wait alertable or not Timeout - Timeout value for wait Return Value: NTSTATUS - Status of call --*/ { NTSTATUS Status; KPROCESSOR_MODE PreviousMode; PKEYED_EVENT_OBJECT KeyedEventObject; PETHREAD CurrentThread, TargetThread; PEPROCESS CurrentProcess; PLIST_ENTRY ListHead, ListEntry; LARGE_INTEGER TimeoutValue; PVOID OldKeyValue=NULL; if ((((ULONG_PTR)KeyValue) & KEYVALUE_RELEASE) != 0) { return STATUS_INVALID_PARAMETER_1; } CurrentThread = PsGetCurrentThread (); PreviousMode = KeGetPreviousModeByThread (&CurrentThread->Tcb); if (Timeout != NULL) { try { if (PreviousMode != KernelMode) { ProbeForRead (Timeout, sizeof (*Timeout), sizeof (UCHAR)); } TimeoutValue = *Timeout; Timeout = &TimeoutValue; } except(ExSystemExceptionFilter ()) { return GetExceptionCode (); } } Status = ObReferenceObjectByHandle (KeyedEventHandle, KEYEDEVENT_WAIT, ExpKeyedEventObjectType, PreviousMode, &KeyedEventObject, NULL); if (!NT_SUCCESS (Status)) { return Status; } CurrentProcess = PsGetCurrentProcessByThread (CurrentThread); ListHead = &KeyedEventObject->WaitQueue; LOCK_KEYED_EVENT_EXCLUSIVE (KeyedEventObject, CurrentThread); ListEntry = ListHead->Flink; while (1) { TargetThread = CONTAINING_RECORD (ListEntry, ETHREAD, KeyedWaitChain); if (ListEntry == ListHead || (((ULONG_PTR)(TargetThread->KeyedWaitValue))&KEYVALUE_RELEASE) == 0) { // // We could not find a key matching ours in the list so we must wait // OldKeyValue = CurrentThread->KeyedWaitValue; CurrentThread->KeyedWaitValue = KeyValue; // // Insert the thread at the tail of the list. We establish an invariant // were waiters are always at the back of the queue behind releasers to improve // the wait code since it only has to search as far as the first non-release // waiter. // InsertTailList (ListHead, &CurrentThread->KeyedWaitChain); TargetThread = NULL; break; } else { if (TargetThread->KeyedWaitValue == (PVOID)(((ULONG_PTR)KeyValue)|KEYVALUE_RELEASE) && THREAD_TO_PROCESS (TargetThread) == CurrentProcess) { RemoveEntryList (ListEntry); InitializeListHead (ListEntry); break; } } ListEntry = ListEntry->Flink; } // // Release the lock but leave APC's disabled. // This prevents us from being suspended and holding up the target. // UNLOCK_KEYED_EVENT_EXCLUSIVE_UNSAFE (KeyedEventObject); if (TargetThread == NULL) { KeLeaveCriticalRegionThread (&CurrentThread->Tcb); Status = KeWaitForSingleObject (&CurrentThread->KeyedWaitSemaphore, Executive, PreviousMode, Alertable, Timeout); // // If we were woken by termination then we must manualy remove // ourselves from the queue // if (Status != STATUS_SUCCESS) { BOOLEAN Wait = TRUE; LOCK_KEYED_EVENT_EXCLUSIVE (KeyedEventObject, CurrentThread); if (!IsListEmpty (&CurrentThread->KeyedWaitChain)) { RemoveEntryList (&CurrentThread->KeyedWaitChain); InitializeListHead (&CurrentThread->KeyedWaitChain); Wait = FALSE; } UNLOCK_KEYED_EVENT_EXCLUSIVE (KeyedEventObject, CurrentThread); // // If this thread was no longer in the queue then another thread // must be about to wake us up. Wait for that wake. // if (Wait) { KeWaitForSingleObject (&CurrentThread->KeyedWaitSemaphore, Executive, KernelMode, FALSE, NULL); } } CurrentThread->KeyedWaitValue = OldKeyValue; } else { KeReleaseSemaphore (&TargetThread->KeyedWaitSemaphore, SEMAPHORE_INCREMENT, 1, FALSE); KeLeaveCriticalRegionThread (&CurrentThread->Tcb); } ObDereferenceObject (KeyedEventObject); return Status; }