//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1992 - 1993. // // File: scavenge.c // // Contents: Home of the LSA scavenger thread // // Classes: // // Functions: // // History: 6-08-93 RichardW Created // //---------------------------------------------------------------------------- #include #include "scavenge.hxx" #define SCAV_INTERNAL_NO_TRACE 0x10000000 ULONG ScavNotifyCount; LIST_ENTRY NotifyList ; LIST_ENTRY NotifyEvents ; LIST_ENTRY ScavList ; RTL_CRITICAL_SECTION NotifyLock ; RTL_CRITICAL_SECTION ScavLock ; HKEY LsaRegistryKey ; HANDLE LsaRegistryWatchEvent ; #define SCAV_TABLE 8 PVOID DeadScavItems[ SCAV_TABLE ]; ULONG DeadScavIndex ; #define SCAVENGER_WAIT_INTERVAL 60000L // // Internal flags: // #define SCAVFLAG_NOTIFY_EVENT 0x01000000 #define SCAVFLAG_ASYNC_TIMER_DELETE 0x00400000 #define NOTIFY_FLAG_SYNCHRONOUS 0x00000001 #define LockScavenger() RtlEnterCriticalSection( &ScavLock ) #define UnlockScavenger() RtlLeaveCriticalSection( &ScavLock ) #define LockNotify() RtlEnterCriticalSection( &NotifyLock ) #define UnlockNotify() RtlLeaveCriticalSection( &NotifyLock ) // // Define locking macros for the scav list // #define LsapRefScavItem( Item ) \ { \ RtlEnterCriticalSection( &ScavLock ); \ ((PLSAP_SCAVENGER_ITEM) Item)->RefCount++ ; \ RtlLeaveCriticalSection( &ScavLock ); \ } #define LsapRefScavItemUnsafe( Item ) \ { \ ((PLSAP_SCAVENGER_ITEM) Item)->RefCount++ ; \ } DWORD WINAPI LsapScavengerThread( PVOID Ignored ); BOOLEAN LsapBreakEveryMinute = FALSE; BOOLEAN LsapDebuggerOk = FALSE ; VOID FreeScavengerItem( IN PLSAP_SCAVENGER_ITEM Item ) { ASSERT( Item->List.Flink == NULL ); ASSERT( Item->PackageList.Flink == NULL ); LsapFreePrivateHeap( Item ); } VOID LsapInternalBreak( VOID ) { if ( !LsapDebuggerOk ) { return; } DbgBreakPoint(); } ULONG NTAPI LsapScavengerBreak( PVOID Param ) { if (LsapBreakEveryMinute) { LsapInternalBreak(); } HANDLE hToken; DWORD ReturnLength; TOKEN_STATISTICS TokenStats; NTSTATUS Status; Status = NtOpenProcessToken( NtCurrentProcess(), TOKEN_QUERY, &hToken ); if (NT_SUCCESS( Status )) { Status = NtQueryInformationToken ( hToken, TokenStatistics, &TokenStats, sizeof( TOKEN_STATISTICS ), &ReturnLength ); if (NT_SUCCESS( Status )) { if (TokenStats.ExpirationTime.QuadPart == 0i64) { LsapInternalBreak(); } } NtClose( hToken ); } return(0); } //+--------------------------------------------------------------------------- // // Function: LsapRegistryWatch // // Synopsis: Callback that handles registry changes in the LSA key // // Arguments: [Ignored] // // History: 05-10-00 RichardW // // Notes: // //---------------------------------------------------------------------------- DWORD LsapRegistryWatch( PVOID Ignored ) { // // refresh dynamically changable parameters. // LoadParameters( TRUE ); RegNotifyChangeKeyValue( LsaRegistryKey, TRUE, REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET, LsaRegistryWatchEvent, TRUE ); LsapEventNotify( NOTIFY_CLASS_REGISTRY_CHANGE, 0, 0, NULL ); return 0 ; } //+--------------------------------------------------------------------------- // // Function: LsapDerefScavItem // // Synopsis: Dereference, optionally freeing a scavenger item // // Arguments: [Item] -- // // History: 6-03-97 RichardW Created // // Notes: // //---------------------------------------------------------------------------- VOID LsapDerefScavItem( PLSAP_SCAVENGER_ITEM Item ) { HANDLE TimerDeleteHandle ; LockScavenger(); Item->RefCount-- ; if ( Item->RefCount == 0 ) { DebugLog(( DEB_TRACE_SCAV, "Removing item %x\n", Item )); if ( Item->List.Flink ) { RemoveEntryList( &Item->List ); Item->List.Flink = NULL ; } if ( Item->PackageList.Flink ) { RemoveEntryList( &Item->PackageList ); Item->PackageList.Flink = NULL ; } // // Because the rtl thread pool is asynchronous to this one, // we need to keep track of recently deceased scavenger items // to prevent them from being used in an RTL thread. The add // function will scan the table to remove any potential duplicates. // DeadScavItems[ DeadScavIndex ] = Item ; DeadScavIndex ++ ; DeadScavIndex &= (SCAV_TABLE - 1); UnlockScavenger(); if ( Item->Type == NOTIFIER_TYPE_INTERVAL ) { // // Kill the timerq handle: // if ( Item->TimerHandle != INVALID_HANDLE_VALUE && Item->TimerHandle != NULL ) { if ( (Item->Flags & SCAVFLAG_ASYNC_TIMER_DELETE) != 0 ) { TimerDeleteHandle = 0 ; } else { TimerDeleteHandle = INVALID_HANDLE_VALUE ; } DeleteTimerQueueTimer( NULL, Item->TimerHandle, TimerDeleteHandle ); } } else if ( Item->Type == NOTIFIER_TYPE_HANDLE_WAIT ) { if ( Item->TimerHandle != INVALID_HANDLE_VALUE && Item->TimerHandle != NULL ) { UnregisterWaitEx( Item->TimerHandle, INVALID_HANDLE_VALUE ); } } if ( ( Item->Type != NOTIFIER_TYPE_NOTIFY_EVENT ) && ( Item->Type != NOTIFIER_TYPE_IMMEDIATE ) ) { // // Yield to let the other thread remove the item // Sleep( 100 ); } Item->ScavCheck = SCAVMAGIC_FREE ; FreeScavengerItem( Item ); } else { UnlockScavenger(); } } //+--------------------------------------------------------------------------- // // Function: LsapScavengerTrigger // // Synopsis: Actual Trigger // // Arguments: [Parameter] -- Item to call // // History: 5-24-97 RichardW Created // // Notes: // //---------------------------------------------------------------------------- ULONG LsapScavengerTrigger( PVOID Parameter ) { PLSAP_SCAVENGER_ITEM Item ; #ifdef LSAP_VERIFY_PACKAGE_ID ULONG_PTR dwPackageID, dwCurId; #endif if ( ShutdownBegun ) { return 0; } SetCurrentSession( pDefaultSession ); Item = (PLSAP_SCAVENGER_ITEM) Parameter ; DsysAssert( Item->ScavCheck == SCAVMAGIC_ACTIVE ); __try { #ifdef LSAP_VERIFY_PACKAGE_ID dwPackageID = Item->PackageId; dwCurId = GetCurrentPackageId(); if ((dwCurId != SPMGR_ID) || (dwPackageID != SPMGR_ID)) #endif { SetCurrentPackageId( Item->PackageId ); } (VOID) Item->Function( Item->Parameter ); #ifdef LSAP_VERIFY_PACKAGE_ID if (dwPackageID != SPMGR_ID) #endif { SetCurrentPackageId( SPMGR_ID ); } } __except( SP_EXCEPTION ) { SPException( GetExceptionCode(), Item->PackageId ); } LsapDerefScavItem( Item ); return 0 ; } //+--------------------------------------------------------------------------- // // Function: LsapTimerCallback // // Synopsis: Callback from thread pool for scavenger items // // Arguments: [Context] -- // [Timeout] -- // // History: 7-01-98 RichardW Created // // Notes: // //---------------------------------------------------------------------------- VOID LsapTimerCallback( PVOID Context, BOOLEAN Timeout ) { PLSAP_SCAVENGER_ITEM Item ; ULONG i ; BOOL OneShot ; SetCurrentSession( pDefaultSession ); Item = (PLSAP_SCAVENGER_ITEM) Context ; // // We only scan for handle and timer events. Things executed // by QueueUserWorkItem don't end up in this list, but that's // okay, since those items go directly to LsapScavengerTrigger // LockScavenger(); for ( i = 0 ; i < SCAV_TABLE ; i++ ) { if ( DeadScavItems[ i ] == Item ) { break; } } if ( i != SCAV_TABLE ) { // // uh oh, a dead one that was still in the queue in this // rtl worker thread. Ignore it. // UnlockScavenger(); return ; } if ( Item->Flags & SCAVFLAG_ASYNC_TIMER_DELETE ) { // // This is a bad condition. An item that should have // been fired once has shown up again. Ignore it. // UnlockScavenger(); return; } LsapRefScavItemUnsafe( Item ); OneShot = ( Item->Flags & NOTIFIER_FLAG_ONE_SHOT ) != 0 ; if ( OneShot ) { // // This flag has the side effect of preventing further // callbacks. That lets us delete is asynchronously later. // Item->Flags |= SCAVFLAG_ASYNC_TIMER_DELETE ; } UnlockScavenger(); if ( (Item->Flags & SCAV_INTERNAL_NO_TRACE ) == 0 ) { DebugLog(( DEB_TRACE_SCAV, "Triggering item %x, type %d\n", Item, Item->Type )); } LsapScavengerTrigger( Item ); // // If this is a one-shot item that's in the list, then it was // a delayed or otherwise "real" one-shot. Deref it again to // kill it. // if ( OneShot ) { LsapDerefScavItem( Item ); } } //+--------------------------------------------------------------------------- // // Function: LsapScavengerHandleNotify // // Synopsis: Called whenever a notification event goes off. // // Arguments: [Ignored] -- // // History: 5-23-97 RichardW Created // // Notes: // //---------------------------------------------------------------------------- DWORD WINAPI LsapScavengerHandleNotify( PVOID Ignored ) { PLIST_ENTRY NotifyScan ; PLIST_ENTRY EventScan ; PLSAP_NOTIFY_EVENT Event ; PLSAP_SCAVENGER_ITEM Item ; do { LockNotify(); if ( !IsListEmpty( &NotifyEvents ) ) { EventScan = RemoveHeadList( &NotifyEvents ); } else { EventScan = NULL ; } UnlockNotify(); if ( EventScan ) { Event = CONTAINING_RECORD( EventScan, LSAP_NOTIFY_EVENT, List ); LockScavenger(); NotifyScan = NotifyList.Flink ; while ( NotifyScan != &NotifyList ) { Item = CONTAINING_RECORD( NotifyScan, LSAP_SCAVENGER_ITEM, List ); if ( Item->Class == Event->Notify.EventClass ) { Event->Notify.PackageParameter = Item->Parameter ; Item->Function( &Event->Notify ); } NotifyScan = NotifyScan->Flink ; } UnlockScavenger(); if ( Event->Flags & NOTIFY_FLAG_SYNCHRONOUS ) { SetEvent( Event->hSync ); } LsapFreeLsaHeap( Event ); } } while ( EventScan ); return 0 ; } //+--------------------------------------------------------------------------- // // Function: LsaIRegisterNotification // // Synopsis: Registers a callback, to be called on either a handle signalled, // or an time based interval, or special async events // // Arguments: [pFunction] -- Callback function // [pvParameter] -- Parameter to pass // [Type] -- Type of callback // [Class] -- Event class // [fItem] -- Flags // [Interval] -- Interval to call // [hEvent] -- Handle to wait on // // History: 6-03-97 RichardW Created // // Notes: // //---------------------------------------------------------------------------- PVOID NTAPI LsaIRegisterNotification( IN LPTHREAD_START_ROUTINE pFunction, IN PVOID pvParameter, IN ULONG Type, IN ULONG Class, IN ULONG fItem, IN ULONG Interval, IN HANDLE hEvent) { PLSAP_SCAVENGER_ITEM Item ; PLIST_ENTRY List = NULL ; BOOL Success ; DWORD DueTime ; ULONG i; Item = (PLSAP_SCAVENGER_ITEM) LsapAllocatePrivateHeap( sizeof( LSAP_SCAVENGER_ITEM ) ); if ( !Item ) { return NULL ; } Item->List.Flink = NULL ; Item->PackageList.Flink = NULL ; Item->Type = Type ; Item->Function = pFunction ; Item->Parameter = pvParameter ; Item->RefCount = 1 ; Item->PackageId = GetCurrentPackageId(); Item->ScavCheck = SCAVMAGIC_ACTIVE; Item->Flags = fItem ; Item->TimerHandle = INVALID_HANDLE_VALUE; // // Okay, we have set up the item, more or less. Now, insert it // into the list we have selected for it. // DebugLog(( DEB_TRACE_SCAV, "Created scavenger item %x, type %d\n", Item, Item->Type )); switch ( Type ) { case NOTIFIER_TYPE_IMMEDIATE: Item->Flags |= NOTIFIER_FLAG_ONE_SHOT ; Success = QueueUserWorkItem( LsapScavengerTrigger, Item, FALSE ); // // And that's all. the item may in fact be freed by now, since the // worker thread could have completed. So, return success now, // return (Item); break; case NOTIFIER_TYPE_INTERVAL: case NOTIFIER_TYPE_HANDLE_WAIT: LockScavenger(); InsertTailList( &ScavList, &Item->List ); // // Make sure this pointer doesn't show up in the list of dead ones. // this can happen due to heap reuse. // for ( i = 0 ; i < SCAV_TABLE ; i++ ) { if ( DeadScavItems[ i ] == Item ) { DeadScavItems[ i ] = NULL ; } } UnlockScavenger(); if ( Type == NOTIFIER_TYPE_INTERVAL ) { if ( fItem & NOTIFIER_FLAG_SECONDS ) { Interval *= 60 ; } Interval *= 1000 ; Success = CreateTimerQueueTimer( &Item->TimerHandle, NULL, LsapTimerCallback, Item, Interval, (fItem & NOTIFIER_FLAG_ONE_SHOT ? 0 : Interval ), 0 ); } else { ASSERT( Type == NOTIFIER_TYPE_HANDLE_WAIT ); Item->TimerHandle = RegisterWaitForSingleObjectEx( hEvent, LsapTimerCallback, Item, INFINITE, (fItem & NOTIFIER_FLAG_NEW_THREAD ? 0 : WT_EXECUTEINWAITTHREAD ) ); Success = (Item->TimerHandle != NULL); } if ( !Success ) { LsapDerefScavItem( Item ); return NULL ; } break; case NOTIFIER_TYPE_NOTIFY_EVENT: Item->Class = Class ; Item->Flags |= SCAVFLAG_NOTIFY_EVENT ; LockScavenger(); InsertTailList( &NotifyList, &Item->List ); UnlockScavenger(); Success = TRUE ; break; default: Success = FALSE ; FreeScavengerItem( Item ); break; } return Item ; } //+--------------------------------------------------------------------------- // // Function: LsaICancelNotification // // Arguments: [pvScavHandle] -- // // History: 5-26-97 RichardW Created // // Notes: // //---------------------------------------------------------------------------- NTSTATUS NTAPI LsaICancelNotification( PVOID pvScavHandle ) { PLSAP_SCAVENGER_ITEM Item ; Item = (PLSAP_SCAVENGER_ITEM) pvScavHandle ; if ( Item->ScavCheck != SCAVMAGIC_ACTIVE ) { return STATUS_INVALID_PARAMETER ; } LsapDerefScavItem( Item ); return STATUS_SUCCESS ; } //+--------------------------------------------------------------------------- // // Function: LsapEventNotify // // Synopsis: Notify waiters of a security package event // // Arguments: [Class] -- Event Class // [Flags] -- Flags // [EventSize] -- Size of event data // [EventData] -- ptr to event data // // History: 5-26-97 RichardW Created // // Notes: // //---------------------------------------------------------------------------- BOOLEAN NTAPI LsapEventNotify( ULONG Class, ULONG Flags, ULONG EventSize, PVOID EventData) { PLSAP_NOTIFY_EVENT Event ; HANDLE hEvent = NULL; Event = (PLSAP_NOTIFY_EVENT) LsapAllocateLsaHeap( sizeof( LSAP_NOTIFY_EVENT ) + EventSize ); if (Event) { Event->Notify.EventClass = Class; Event->Notify.EventDataSize = EventSize; Event->Notify.EventData = (PUCHAR) ( Event + 1 ); RtlCopyMemory( Event->Notify.EventData, EventData, EventSize ); Event->Flags = Flags; if (Flags & NOTIFY_FLAG_SYNCHRONOUS) { hEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); Event->hSync = hEvent ; if (!Event->hSync) { LsapFreeLsaHeap( Event ); return(FALSE); } } else { Event->hSync = NULL ; } // // Insert event into list // LockNotify(); InsertTailList( &NotifyEvents, &Event->List ); UnlockNotify(); DebugLog((DEB_TRACE_SCAV, "EventNotify( %d ) - Data at %x\n", Class, Event->Notify.EventData )); // // Wake up the scavenger thread // SetEvent( hStateChangeEvent ); // // If told to wait, block until scav thread signals the event // if (Flags & NOTIFY_FLAG_SYNCHRONOUS) { WaitForSingleObjectEx( hEvent, INFINITE, FALSE ); CloseHandle( hEvent ); } return( TRUE ); } return( FALSE ); } //+--------------------------------------------------------------------------- // // Function: LsapInitializeScavenger // // Synopsis: Initialize Scavenger, // // Arguments: (none) // // History: 5-26-97 RichardW Created // // Notes: // //---------------------------------------------------------------------------- BOOL LsapInitializeScavenger( VOID ) { ULONG i ; PVOID hNotify ; HANDLE hThread ; DWORD tid ; DWORD Debugger ; DWORD dwSize ; DWORD dwType ; SYSTEM_KERNEL_DEBUGGER_INFORMATION KdInfo ; // // Initialize the lists // InitializeListHead( &NotifyList ); InitializeListHead( &NotifyEvents ); InitializeListHead( &ScavList ); RtlInitializeCriticalSection( &ScavLock ); RtlInitializeCriticalSection( &NotifyLock ); // // Event set whenever there is a notification. // hStateChangeEvent = CreateEvent(NULL, FALSE, FALSE, NULL); // // Create basic entries // hNotify = LsaIRegisterNotification( LsapScavengerHandleNotify, 0, NOTIFIER_TYPE_HANDLE_WAIT, 0, NOTIFIER_FLAG_NEW_THREAD, 0, hStateChangeEvent ); if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, TEXT("System\\CurrentControlSet\\Control\\Lsa"), 0, KEY_READ, &LsaRegistryKey ) == 0 ) { LsaRegistryWatchEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if ( LsaRegistryWatchEvent ) { hNotify = LsaIRegisterNotification( LsapRegistryWatch, NULL, NOTIFIER_TYPE_HANDLE_WAIT, 0, NOTIFIER_FLAG_NEW_THREAD, 0, LsaRegistryWatchEvent ); if ( hNotify ) { // // Call it once to start the registry watch // LsapRegistryWatch( NULL ); } else { CloseHandle( LsaRegistryWatchEvent ); } } else { RegCloseKey( LsaRegistryKey ); } } // // If we are under a debugger, or a kernel debugger is attached, or the // flag is in the registry, turn on the watch thread // Debugger = 0 ; dwSize = sizeof( Debugger ); RegQueryValueEx( LsaRegistryKey, TEXT("EnableDebugCheck"), 0, &dwType, (PBYTE) &Debugger, &dwSize ); NtQuerySystemInformation( SystemKernelDebuggerInformation, &KdInfo, sizeof( KdInfo ), NULL ); if ( (KdInfo.KernelDebuggerEnabled) || (NtCurrentPeb()->BeingDebugged) || (Debugger != 0 ) ) { LsapDebuggerOk = TRUE ; (void) LsaIRegisterNotification( LsapScavengerBreak, NULL, NOTIFIER_TYPE_INTERVAL, 0, // no class SCAV_INTERNAL_NO_TRACE, // no flags 60, // every minute NULL // no handle ); } return TRUE ; }