//+--------------------------------------------------------------------------- // // 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 ; LIST_ENTRY PageTouchList ; RTL_CRITICAL_SECTION NotifyLock ; RTL_CRITICAL_SECTION ScavLock ; RTL_CRITICAL_SECTION PageTouchLock ; HKEY LsaRegistryKey ; HANDLE LsaRegistryWatchEvent ; #define SCAV_TABLE 8 PVOID DeadScavItems[ SCAV_TABLE ]; ULONG DeadScavIndex ; #define SCAVENGER_WAIT_INTERVAL 60000L // // Internal flags: // #define SCAVFLAG_IN_PROGRESS 0x40000000 // Active #define SCAVFLAG_ABOUT_TO_DIE 0x20000000 // About to be removed #define SCAVFLAG_IMMEDIATE 0x08000000 // Immediate Execute #define SCAVFLAG_STATE_CHANGE 0x04000000 // State Change #define SCAVFLAG_TRIGGER_FREE 0x02000000 // Trigger will free #define SCAVFLAG_NOTIFY_EVENT 0x01000000 #define SCAVFLAG_EXECUTE_INLINE 0x00800000 // Execute stub directly #define SCAVFLAG_ASYNC_TIMER_DELETE 0x00400000 #define NOTIFYFLAG_CHILD_SYNC 0x80000000 // All sub funcs are synchronous #define NOTIFYFLAG_BLOCK_CALLS 0x40000000 // Block calls #define NOTIFY_FLAG_SYNCHRONOUS 0x00000001 #if DBG #define SCAVFLAG_ITEM_BREAK 0x10000000 #endif // // Define indices for well known events. Shutdown is triggered when // the console handler starts a shutdown. config is when someone adds // another notifier. state is when the state of the machine has changed. // #define SCAVENGER_SHUTDOWN_EVENT 0 #define SCAVENGER_CONFIG_EVENT 1 #define SCAVENGER_NOTIFY_EVENT 2 #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 LsapInternalBreak( VOID ) { PLIST_ENTRY Scan ; PLSAP_PAGE_TOUCH Touch ; ULONG Sum = 0 ; PULONG Address ; SIZE_T i ; if ( !LsapDebuggerOk ) { return; } RtlEnterCriticalSection( &PageTouchLock ); Scan = PageTouchList.Flink ; while ( Scan != &PageTouchList ) { Touch = CONTAINING_RECORD( Scan, LSAP_PAGE_TOUCH, List ); Address = (PULONG) Touch->Address ; for ( i = 0 ; i < Touch->Range / sizeof( ULONG ) ; i++ ) { Sum += *Address++ ; } Scan = Scan->Flink ; } DbgBreakPoint(); RtlLeaveCriticalSection( &PageTouchLock ); } VOID LsaIAddTouchAddress( PVOID Address, SIZE_T Range ) { PLSAP_PAGE_TOUCH Touch ; PLIST_ENTRY Scan ; PLSAP_PAGE_TOUCH Touch2 = NULL ; Touch = (PLSAP_PAGE_TOUCH) LsapAllocatePrivateHeap( sizeof( LSAP_PAGE_TOUCH ) ); if ( !Touch ) { return; } Touch->Address = Address ; Touch->Range = Range ; RtlEnterCriticalSection( &PageTouchLock ); Scan = PageTouchList.Flink ; while ( Scan != &PageTouchList ) { Touch2 = CONTAINING_RECORD( Scan, LSAP_PAGE_TOUCH, List ); if ( Touch2->Address == Touch->Address ) { break; } Scan = Scan->Flink ; Touch2 = NULL ; } if ( !Touch2 ) { InsertTailList( &PageTouchList, &Touch->List ); } else { LsapFreePrivateHeap( Touch ); } RtlLeaveCriticalSection( &PageTouchLock ); } VOID LsaIRemoveTouchAddress( PVOID Address ) { PLSAP_PAGE_TOUCH Touch = NULL ; PLIST_ENTRY Scan ; RtlEnterCriticalSection( &PageTouchLock ); Scan = PageTouchList.Flink ; while ( Scan != &PageTouchList ) { Touch = CONTAINING_RECORD( Scan, LSAP_PAGE_TOUCH, List ); if ( Touch->Address == Address ) { break; } Scan = Scan->Flink ; Touch = NULL ; } if ( Touch ) { RemoveEntryList( &Touch->List ); } RtlLeaveCriticalSection( &PageTouchLock ); } 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 ) { 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->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 ) { 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 ; LsapFreePrivateHeap( 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 ; 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: 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 ); break; case NOTIFIER_TYPE_HANDLE_WAIT: Item->TimerHandle = RegisterWaitForSingleObjectEx( hEvent, LsapTimerCallback, Item, INFINITE, (fItem & NOTIFIER_FLAG_NEW_THREAD ? 0 : WT_EXECUTEINWAITTHREAD ) ); Success = (Item->TimerHandle != 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 ; break; } if ( !Success ) { LsapFreePrivateHeap( Item ); return NULL ; } // // 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 )); if ( (Item->Type != NOTIFIER_TYPE_NOTIFY_EVENT) && (Item->Type != NOTIFIER_TYPE_IMMEDIATE ) ) { 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(); } 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 ); InitializeListHead( &PageTouchList ); RtlInitializeCriticalSection( &ScavLock ); RtlInitializeCriticalSection( &NotifyLock ); RtlInitializeCriticalSection( &PageTouchLock ); // // 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 ; } BOOLEAN NTAPI LsaIEventNotify( ULONG Class, ULONG Flags, ULONG EventSize, PVOID EventData) { return LsapEventNotify( Class, Flags, EventSize, EventData ); }