/* * Process Hacker - * service provider * * Copyright (C) 2009-2015 wj32 * * This file is part of Process Hacker. * * Process Hacker is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Process Hacker is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Process Hacker. If not, see . */ #include #include #include typedef DWORD (WINAPI *_NotifyServiceStatusChangeW)( _In_ SC_HANDLE hService, _In_ DWORD dwNotifyMask, _In_ PSERVICE_NOTIFYW pNotifyBuffer ); typedef struct _PHP_SERVICE_NAME_ENTRY { PH_HASH_ENTRY HashEntry; PH_STRINGREF Name; ENUM_SERVICE_STATUS_PROCESS *ServiceEntry; } PHP_SERVICE_NAME_ENTRY, *PPHP_SERVICE_NAME_ENTRY; typedef enum _PHP_SERVICE_NOTIFY_STATE { SnNone, SnAdding, SnRemoving, SnNotify } PHP_SERVICE_NOTIFY_STATE; typedef struct _PHP_SERVICE_NOTIFY_CONTEXT { LIST_ENTRY ListEntry; SC_HANDLE ServiceHandle; PPH_STRING ServiceName; // Valid only when adding BOOLEAN IsServiceManager; PHP_SERVICE_NOTIFY_STATE State; SERVICE_NOTIFY Buffer; } PHP_SERVICE_NOTIFY_CONTEXT, *PPHP_SERVICE_NOTIFY_CONTEXT; VOID NTAPI PhpServiceItemDeleteProcedure( _In_ PVOID Object, _In_ ULONG Flags ); BOOLEAN NTAPI PhpServiceHashtableCompareFunction( _In_ PVOID Entry1, _In_ PVOID Entry2 ); ULONG NTAPI PhpServiceHashtableHashFunction( _In_ PVOID Entry ); VOID PhpAddProcessItemService( _In_ PPH_PROCESS_ITEM ProcessItem, _In_ PPH_SERVICE_ITEM ServiceItem ); VOID PhpRemoveProcessItemService( _In_ PPH_PROCESS_ITEM ProcessItem, _In_ PPH_SERVICE_ITEM ServiceItem ); VOID PhpInitializeServiceNonPoll( VOID ); PPH_OBJECT_TYPE PhServiceItemType; PPH_HASHTABLE PhServiceHashtable; PH_QUEUED_LOCK PhServiceHashtableLock = PH_QUEUED_LOCK_INIT; PHAPPAPI PH_CALLBACK_DECLARE(PhServiceAddedEvent); PHAPPAPI PH_CALLBACK_DECLARE(PhServiceModifiedEvent); PHAPPAPI PH_CALLBACK_DECLARE(PhServiceRemovedEvent); PHAPPAPI PH_CALLBACK_DECLARE(PhServicesUpdatedEvent); BOOLEAN PhEnableServiceNonPoll = FALSE; static BOOLEAN PhpNonPollInitialized = FALSE; static BOOLEAN PhpNonPollActive = FALSE; static HANDLE PhpNonPollThreadHandle; static ULONG PhpNonPollGate; static _NotifyServiceStatusChangeW NotifyServiceStatusChangeW_I; static HANDLE PhpNonPollEventHandle; static PH_QUEUED_LOCK PhpNonPollServiceListLock = PH_QUEUED_LOCK_INIT; static LIST_ENTRY PhpNonPollServiceListHead; static LIST_ENTRY PhpNonPollServicePendingListHead; BOOLEAN PhServiceProviderInitialization( VOID ) { PhServiceItemType = PhCreateObjectType(L"ServiceItem", 0, PhpServiceItemDeleteProcedure); PhServiceHashtable = PhCreateHashtable( sizeof(PPH_SERVICE_ITEM), PhpServiceHashtableCompareFunction, PhpServiceHashtableHashFunction, 40 ); return TRUE; } PPH_SERVICE_ITEM PhCreateServiceItem( _In_opt_ LPENUM_SERVICE_STATUS_PROCESS Information ) { PPH_SERVICE_ITEM serviceItem; serviceItem = PhCreateObject( PhEmGetObjectSize(EmServiceItemType, sizeof(PH_SERVICE_ITEM)), PhServiceItemType ); memset(serviceItem, 0, sizeof(PH_SERVICE_ITEM)); if (Information) { serviceItem->Name = PhCreateString(Information->lpServiceName); serviceItem->Key = serviceItem->Name->sr; serviceItem->DisplayName = PhCreateString(Information->lpDisplayName); serviceItem->Type = Information->ServiceStatusProcess.dwServiceType; serviceItem->State = Information->ServiceStatusProcess.dwCurrentState; serviceItem->ControlsAccepted = Information->ServiceStatusProcess.dwControlsAccepted; serviceItem->Flags = Information->ServiceStatusProcess.dwServiceFlags; serviceItem->ProcessId = UlongToHandle(Information->ServiceStatusProcess.dwProcessId); if (serviceItem->ProcessId) PhPrintUInt32(serviceItem->ProcessIdString, HandleToUlong(serviceItem->ProcessId)); } PhEmCallObjectOperation(EmServiceItemType, serviceItem, EmObjectCreate); return serviceItem; } VOID PhpServiceItemDeleteProcedure( _In_ PVOID Object, _In_ ULONG Flags ) { PPH_SERVICE_ITEM serviceItem = (PPH_SERVICE_ITEM)Object; PhEmCallObjectOperation(EmServiceItemType, serviceItem, EmObjectDelete); if (serviceItem->Name) PhDereferenceObject(serviceItem->Name); if (serviceItem->DisplayName) PhDereferenceObject(serviceItem->DisplayName); } BOOLEAN PhpServiceHashtableCompareFunction( _In_ PVOID Entry1, _In_ PVOID Entry2 ) { PPH_SERVICE_ITEM serviceItem1 = *(PPH_SERVICE_ITEM *)Entry1; PPH_SERVICE_ITEM serviceItem2 = *(PPH_SERVICE_ITEM *)Entry2; return PhEqualStringRef(&serviceItem1->Key, &serviceItem2->Key, TRUE); } ULONG PhpServiceHashtableHashFunction( _In_ PVOID Entry ) { PPH_SERVICE_ITEM serviceItem = *(PPH_SERVICE_ITEM *)Entry; return PhHashStringRef(&serviceItem->Key, TRUE); } PPH_SERVICE_ITEM PhpLookupServiceItem( _In_ PPH_STRINGREF Name ) { PH_SERVICE_ITEM lookupServiceItem; PPH_SERVICE_ITEM lookupServiceItemPtr = &lookupServiceItem; PPH_SERVICE_ITEM *serviceItem; lookupServiceItem.Key = *Name; serviceItem = (PPH_SERVICE_ITEM *)PhFindEntryHashtable( PhServiceHashtable, &lookupServiceItemPtr ); if (serviceItem) return *serviceItem; else return NULL; } PPH_SERVICE_ITEM PhReferenceServiceItem( _In_ PWSTR Name ) { PPH_SERVICE_ITEM serviceItem; PH_STRINGREF key; // Construct a temporary service item for the lookup. PhInitializeStringRef(&key, Name); PhAcquireQueuedLockShared(&PhServiceHashtableLock); serviceItem = PhpLookupServiceItem(&key); if (serviceItem) PhReferenceObject(serviceItem); PhReleaseQueuedLockShared(&PhServiceHashtableLock); return serviceItem; } VOID PhMarkNeedsConfigUpdateServiceItem( _In_ PPH_SERVICE_ITEM ServiceItem ) { ServiceItem->NeedsConfigUpdate = TRUE; } VOID PhpRemoveServiceItem( _In_ PPH_SERVICE_ITEM ServiceItem ) { PhRemoveEntryHashtable(PhServiceHashtable, &ServiceItem); PhDereferenceObject(ServiceItem); } PH_SERVICE_CHANGE PhGetServiceChange( _In_ PPH_SERVICE_MODIFIED_DATA Data ) { if ( ( Data->OldService.State == SERVICE_STOPPED || Data->OldService.State == SERVICE_START_PENDING ) && Data->Service->State == SERVICE_RUNNING ) { return ServiceStarted; } if ( ( Data->OldService.State == SERVICE_PAUSED || Data->OldService.State == SERVICE_CONTINUE_PENDING ) && Data->Service->State == SERVICE_RUNNING ) { return ServiceContinued; } if ( ( Data->OldService.State == SERVICE_RUNNING || Data->OldService.State == SERVICE_PAUSE_PENDING ) && Data->Service->State == SERVICE_PAUSED ) { return ServicePaused; } if ( ( Data->OldService.State == SERVICE_RUNNING || Data->OldService.State == SERVICE_STOP_PENDING ) && Data->Service->State == SERVICE_STOPPED ) { return ServiceStopped; } return -1; } VOID PhUpdateProcessItemServices( _In_ PPH_PROCESS_ITEM ProcessItem ) { PH_HASHTABLE_ENUM_CONTEXT enumContext; PPH_SERVICE_ITEM *serviceItem; // We don't need to lock as long as the service provider // never runs concurrently with the process provider. This // is currently true. PhBeginEnumHashtable(PhServiceHashtable, &enumContext); while (serviceItem = PhNextEnumHashtable(&enumContext)) { if ( (*serviceItem)->PendingProcess && (*serviceItem)->ProcessId == ProcessItem->ProcessId ) { PhpAddProcessItemService(ProcessItem, *serviceItem); } } } VOID PhpAddProcessItemService( _In_ PPH_PROCESS_ITEM ProcessItem, _In_ PPH_SERVICE_ITEM ServiceItem ) { PhAcquireQueuedLockExclusive(&ProcessItem->ServiceListLock); if (!ProcessItem->ServiceList) ProcessItem->ServiceList = PhCreatePointerList(2); if (!PhFindItemPointerList(ProcessItem->ServiceList, ServiceItem)) { PhReferenceObject(ServiceItem); PhAddItemPointerList(ProcessItem->ServiceList, ServiceItem); } PhReleaseQueuedLockExclusive(&ProcessItem->ServiceListLock); ServiceItem->PendingProcess = FALSE; ProcessItem->JustProcessed = 1; } VOID PhpRemoveProcessItemService( _In_ PPH_PROCESS_ITEM ProcessItem, _In_ PPH_SERVICE_ITEM ServiceItem ) { HANDLE pointerHandle; if (!ProcessItem->ServiceList) return; PhAcquireQueuedLockExclusive(&ProcessItem->ServiceListLock); if (pointerHandle = PhFindItemPointerList(ProcessItem->ServiceList, ServiceItem)) { PhRemoveItemPointerList(ProcessItem->ServiceList, pointerHandle); PhDereferenceObject(ServiceItem); } PhReleaseQueuedLockExclusive(&ProcessItem->ServiceListLock); ProcessItem->JustProcessed = 1; } VOID PhpUpdateServiceItemConfig( _In_ SC_HANDLE ScManagerHandle, _In_ PPH_SERVICE_ITEM ServiceItem ) { SC_HANDLE serviceHandle; serviceHandle = OpenService(ScManagerHandle, ServiceItem->Name->Buffer, SERVICE_QUERY_CONFIG); if (serviceHandle) { LPQUERY_SERVICE_CONFIG config; SERVICE_DELAYED_AUTO_START_INFO delayedAutoStartInfo; ULONG returnLength; PSERVICE_TRIGGER_INFO triggerInfo; config = PhGetServiceConfig(serviceHandle); if (config) { ServiceItem->StartType = config->dwStartType; ServiceItem->ErrorControl = config->dwErrorControl; PhFree(config); } if (QueryServiceConfig2( serviceHandle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, (BYTE *)&delayedAutoStartInfo, sizeof(SERVICE_DELAYED_AUTO_START_INFO), &returnLength )) { ServiceItem->DelayedStart = delayedAutoStartInfo.fDelayedAutostart; } else { ServiceItem->DelayedStart = FALSE; } if (triggerInfo = PhQueryServiceVariableSize(serviceHandle, SERVICE_CONFIG_TRIGGER_INFO)) { ServiceItem->HasTriggers = triggerInfo->cTriggers != 0; PhFree(triggerInfo); } else { ServiceItem->HasTriggers = FALSE; } CloseServiceHandle(serviceHandle); } } static BOOLEAN PhpCompareServiceNameEntry( _In_ PPHP_SERVICE_NAME_ENTRY Value1, _In_ PPHP_SERVICE_NAME_ENTRY Value2 ) { return PhEqualStringRef(&Value1->Name, &Value2->Name, TRUE); } static ULONG PhpHashServiceNameEntry( _In_ PPHP_SERVICE_NAME_ENTRY Value ) { return PhHashStringRef(&Value->Name, TRUE); } VOID PhServiceProviderUpdate( _In_ PVOID Object ) { static SC_HANDLE scManagerHandle = NULL; static ULONG runCount = 0; static PPH_HASH_ENTRY nameHashSet[256]; static PPHP_SERVICE_NAME_ENTRY nameEntries = NULL; static ULONG nameEntriesCount; static ULONG nameEntriesAllocated = 0; LPENUM_SERVICE_STATUS_PROCESS services; ULONG numberOfServices; ULONG i; PPH_HASH_ENTRY hashEntry; // We always execute the first run, and we only initialize non-polling after the first run. if (PhEnableServiceNonPoll && runCount != 0) { if (!PhpNonPollInitialized) { if (WindowsVersion >= WINDOWS_VISTA) { PhpInitializeServiceNonPoll(); } PhpNonPollInitialized = TRUE; } if (PhpNonPollActive) { if (InterlockedExchange(&PhpNonPollGate, 0) == 0) { // Non-poll gate is closed; skip all processing. goto UpdateEnd; } } } if (!scManagerHandle) { scManagerHandle = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE); if (!scManagerHandle) return; } services = PhEnumServices(scManagerHandle, 0, 0, &numberOfServices); if (!services) return; // Build a hash set containing the service names. // This has caused a massive decrease in background CPU usage, and // is certainly much better than the quadratic-time string comparisons // we were doing before (in the "Look for dead services" section). nameEntriesCount = 0; if (nameEntriesAllocated < numberOfServices) { nameEntriesAllocated = numberOfServices + 32; if (nameEntries) PhFree(nameEntries); nameEntries = PhAllocate(sizeof(PHP_SERVICE_NAME_ENTRY) * nameEntriesAllocated); } PhInitializeHashSet(nameHashSet, PH_HASH_SET_SIZE(nameHashSet)); for (i = 0; i < numberOfServices; i++) { PPHP_SERVICE_NAME_ENTRY entry; entry = &nameEntries[nameEntriesCount++]; PhInitializeStringRefLongHint(&entry->Name, services[i].lpServiceName); entry->ServiceEntry = &services[i]; PhAddEntryHashSet( nameHashSet, PH_HASH_SET_SIZE(nameHashSet), &entry->HashEntry, PhpHashServiceNameEntry(entry) ); } // Look for dead services. { PPH_LIST servicesToRemove = NULL; PH_HASHTABLE_ENUM_CONTEXT enumContext; PPH_SERVICE_ITEM *serviceItem; PhBeginEnumHashtable(PhServiceHashtable, &enumContext); while (serviceItem = PhNextEnumHashtable(&enumContext)) { BOOLEAN found = FALSE; PHP_SERVICE_NAME_ENTRY lookupNameEntry; // Check if the service still exists. lookupNameEntry.Name = (*serviceItem)->Name->sr; hashEntry = PhFindEntryHashSet( nameHashSet, PH_HASH_SET_SIZE(nameHashSet), PhpHashServiceNameEntry(&lookupNameEntry) ); for (; hashEntry; hashEntry = hashEntry->Next) { PPHP_SERVICE_NAME_ENTRY nameEntry; nameEntry = CONTAINING_RECORD(hashEntry, PHP_SERVICE_NAME_ENTRY, HashEntry); if (PhpCompareServiceNameEntry(&lookupNameEntry, nameEntry)) { found = TRUE; break; } } if (!found) { // Remove the service from its process. if ((*serviceItem)->ProcessId) { PPH_PROCESS_ITEM processItem; processItem = PhReferenceProcessItem((HANDLE)(*serviceItem)->ProcessId); if (processItem) { PhpRemoveProcessItemService(processItem, *serviceItem); PhDereferenceObject(processItem); } } // Raise the service removed event. PhInvokeCallback(&PhServiceRemovedEvent, *serviceItem); if (!servicesToRemove) servicesToRemove = PhCreateList(2); PhAddItemList(servicesToRemove, *serviceItem); } } if (servicesToRemove) { PhAcquireQueuedLockExclusive(&PhServiceHashtableLock); for (i = 0; i < servicesToRemove->Count; i++) { PhpRemoveServiceItem((PPH_SERVICE_ITEM)servicesToRemove->Items[i]); } PhReleaseQueuedLockExclusive(&PhServiceHashtableLock); PhDereferenceObject(servicesToRemove); } } // Look for new services and update existing ones. for (i = 0; i < PH_HASH_SET_SIZE(nameHashSet); i++) { for (hashEntry = nameHashSet[i]; hashEntry; hashEntry = hashEntry->Next) { PPH_SERVICE_ITEM serviceItem; PPHP_SERVICE_NAME_ENTRY nameEntry; ENUM_SERVICE_STATUS_PROCESS *serviceEntry; nameEntry = CONTAINING_RECORD(hashEntry, PHP_SERVICE_NAME_ENTRY, HashEntry); serviceEntry = nameEntry->ServiceEntry; serviceItem = PhpLookupServiceItem(&nameEntry->Name); if (!serviceItem) { // Create the service item and fill in basic information. serviceItem = PhCreateServiceItem(serviceEntry); PhpUpdateServiceItemConfig(scManagerHandle, serviceItem); // Add the service to its process, if appropriate. if ( ( serviceItem->State == SERVICE_RUNNING || serviceItem->State == SERVICE_PAUSED ) && serviceItem->ProcessId ) { PPH_PROCESS_ITEM processItem; if (processItem = PhReferenceProcessItem(serviceItem->ProcessId)) { PhpAddProcessItemService(processItem, serviceItem); PhDereferenceObject(processItem); } else { // The process doesn't exist yet (to us). Set the pending // flag and when the process is added this will be // fixed. serviceItem->PendingProcess = TRUE; } } // Add the service item to the hashtable. PhAcquireQueuedLockExclusive(&PhServiceHashtableLock); PhAddEntryHashtable(PhServiceHashtable, &serviceItem); PhReleaseQueuedLockExclusive(&PhServiceHashtableLock); // Raise the service added event. PhInvokeCallback(&PhServiceAddedEvent, serviceItem); } else { if ( serviceItem->Type != serviceEntry->ServiceStatusProcess.dwServiceType || serviceItem->State != serviceEntry->ServiceStatusProcess.dwCurrentState || serviceItem->ControlsAccepted != serviceEntry->ServiceStatusProcess.dwControlsAccepted || serviceItem->ProcessId != UlongToHandle(serviceEntry->ServiceStatusProcess.dwProcessId) || serviceItem->NeedsConfigUpdate ) { PH_SERVICE_MODIFIED_DATA serviceModifiedData; PH_SERVICE_CHANGE serviceChange; // The service has been "modified". serviceModifiedData.Service = serviceItem; memset(&serviceModifiedData.OldService, 0, sizeof(PH_SERVICE_ITEM)); serviceModifiedData.OldService.Type = serviceItem->Type; serviceModifiedData.OldService.State = serviceItem->State; serviceModifiedData.OldService.ControlsAccepted = serviceItem->ControlsAccepted; serviceModifiedData.OldService.ProcessId = serviceItem->ProcessId; // Update the service item. serviceItem->Type = serviceEntry->ServiceStatusProcess.dwServiceType; serviceItem->State = serviceEntry->ServiceStatusProcess.dwCurrentState; serviceItem->ControlsAccepted = serviceEntry->ServiceStatusProcess.dwControlsAccepted; serviceItem->ProcessId = UlongToHandle(serviceEntry->ServiceStatusProcess.dwProcessId); if (serviceItem->ProcessId) PhPrintUInt32(serviceItem->ProcessIdString, HandleToUlong(serviceItem->ProcessId)); else serviceItem->ProcessIdString[0] = 0; // Add/remove the service from its process. serviceChange = PhGetServiceChange(&serviceModifiedData); if ( (serviceChange == ServiceStarted && serviceItem->ProcessId) || (serviceChange == ServiceStopped && serviceModifiedData.OldService.ProcessId) ) { PPH_PROCESS_ITEM processItem; if (serviceChange == ServiceStarted) processItem = PhReferenceProcessItem(serviceItem->ProcessId); else processItem = PhReferenceProcessItem(serviceModifiedData.OldService.ProcessId); if (processItem) { if (serviceChange == ServiceStarted) PhpAddProcessItemService(processItem, serviceItem); else PhpRemoveProcessItemService(processItem, serviceItem); PhDereferenceObject(processItem); } else { if (serviceChange == ServiceStarted) serviceItem->PendingProcess = TRUE; else serviceItem->PendingProcess = FALSE; } } else if ( serviceItem->State == SERVICE_RUNNING && serviceItem->ProcessId != serviceModifiedData.OldService.ProcessId && serviceItem->ProcessId ) { PPH_PROCESS_ITEM processItem; // The service stopped and started, and the only change we have detected // is in the process ID. if (processItem = PhReferenceProcessItem(serviceModifiedData.OldService.ProcessId)) { PhpRemoveProcessItemService(processItem, serviceItem); PhDereferenceObject(processItem); } if (processItem = PhReferenceProcessItem(serviceItem->ProcessId)) { PhpAddProcessItemService(processItem, serviceItem); PhDereferenceObject(processItem); } else { serviceItem->PendingProcess = TRUE; } } // Do a config update if necessary. if (serviceItem->NeedsConfigUpdate) { PhpUpdateServiceItemConfig(scManagerHandle, serviceItem); serviceItem->NeedsConfigUpdate = FALSE; } // Raise the service modified event. PhInvokeCallback(&PhServiceModifiedEvent, &serviceModifiedData); } } } } PhFree(services); UpdateEnd: PhInvokeCallback(&PhServicesUpdatedEvent, NULL); runCount++; } VOID CALLBACK PhpServiceNonPollScNotifyCallback( _In_ PVOID pParameter ) { PSERVICE_NOTIFYW notifyBuffer = pParameter; PPHP_SERVICE_NOTIFY_CONTEXT notifyContext = notifyBuffer->pContext; if (notifyBuffer->dwNotificationStatus == ERROR_SUCCESS) { if ((notifyBuffer->dwNotificationTriggered & (SERVICE_NOTIFY_CREATED | SERVICE_NOTIFY_DELETED)) && notifyBuffer->pszServiceNames) { PWSTR name; SIZE_T nameLength; name = notifyBuffer->pszServiceNames; while (TRUE) { nameLength = PhCountStringZ(name); if (nameLength == 0) break; if (name[0] == '/') { PPHP_SERVICE_NOTIFY_CONTEXT newNotifyContext; // Service creation newNotifyContext = PhAllocate(sizeof(PHP_SERVICE_NOTIFY_CONTEXT)); memset(newNotifyContext, 0, sizeof(PHP_SERVICE_NOTIFY_CONTEXT)); newNotifyContext->State = SnAdding; newNotifyContext->ServiceName = PhCreateString(name + 1); InsertTailList(&PhpNonPollServicePendingListHead, &newNotifyContext->ListEntry); } name += nameLength + 1; } LocalFree(notifyBuffer->pszServiceNames); } notifyContext->State = SnNotify; RemoveEntryList(¬ifyContext->ListEntry); InsertTailList(&PhpNonPollServicePendingListHead, ¬ifyContext->ListEntry); } else if (notifyBuffer->dwNotificationStatus == ERROR_SERVICE_MARKED_FOR_DELETE) { if (!notifyContext->IsServiceManager) { notifyContext->State = SnRemoving; RemoveEntryList(¬ifyContext->ListEntry); InsertTailList(&PhpNonPollServicePendingListHead, ¬ifyContext->ListEntry); } } else { notifyContext->State = SnNotify; RemoveEntryList(¬ifyContext->ListEntry); InsertTailList(&PhpNonPollServicePendingListHead, ¬ifyContext->ListEntry); } PhpNonPollGate = 1; NtSetEvent(PhpNonPollEventHandle, NULL); } VOID PhpDestroyServiceNotifyContext( _In_ PPHP_SERVICE_NOTIFY_CONTEXT NotifyContext ) { if (NotifyContext->Buffer.pszServiceNames) LocalFree(NotifyContext->Buffer.pszServiceNames); CloseServiceHandle(NotifyContext->ServiceHandle); PhClearReference(&NotifyContext->ServiceName); PhFree(NotifyContext); } NTSTATUS PhpServiceNonPollThreadStart( _In_ PVOID Parameter ) { ULONG result; SC_HANDLE scManagerHandle; LPENUM_SERVICE_STATUS_PROCESS services; ULONG numberOfServices; ULONG i; PLIST_ENTRY listEntry; PPHP_SERVICE_NOTIFY_CONTEXT notifyContext; if (!NT_SUCCESS(NtCreateEvent(&PhpNonPollEventHandle, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE))) { PhpNonPollActive = FALSE; PhpNonPollGate = 1; return STATUS_UNSUCCESSFUL; } while (TRUE) { scManagerHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE); if (!scManagerHandle) goto ErrorExit; InitializeListHead(&PhpNonPollServiceListHead); InitializeListHead(&PhpNonPollServicePendingListHead); if (!(services = PhEnumServices(scManagerHandle, 0, 0, &numberOfServices))) goto ErrorExit; for (i = 0; i < numberOfServices; i++) { SC_HANDLE serviceHandle; if (serviceHandle = OpenService(scManagerHandle, services[i].lpServiceName, SERVICE_QUERY_STATUS)) { notifyContext = PhAllocate(sizeof(PHP_SERVICE_NOTIFY_CONTEXT)); memset(notifyContext, 0, sizeof(PHP_SERVICE_NOTIFY_CONTEXT)); notifyContext->ServiceHandle = serviceHandle; notifyContext->State = SnNotify; InsertTailList(&PhpNonPollServicePendingListHead, ¬ifyContext->ListEntry); } } PhFree(services); notifyContext = PhAllocate(sizeof(PHP_SERVICE_NOTIFY_CONTEXT)); memset(notifyContext, 0, sizeof(PHP_SERVICE_NOTIFY_CONTEXT)); notifyContext->ServiceHandle = scManagerHandle; notifyContext->IsServiceManager = TRUE; notifyContext->State = SnNotify; InsertTailList(&PhpNonPollServicePendingListHead, ¬ifyContext->ListEntry); while (TRUE) { BOOLEAN lagging = FALSE; listEntry = PhpNonPollServicePendingListHead.Flink; while (listEntry != &PhpNonPollServicePendingListHead) { notifyContext = CONTAINING_RECORD(listEntry, PHP_SERVICE_NOTIFY_CONTEXT, ListEntry); listEntry = listEntry->Flink; switch (notifyContext->State) { case SnNone: break; case SnAdding: notifyContext->ServiceHandle = OpenService(scManagerHandle, notifyContext->ServiceName->Buffer, SERVICE_QUERY_STATUS); if (!notifyContext->ServiceHandle) { RemoveEntryList(¬ifyContext->ListEntry); PhpDestroyServiceNotifyContext(notifyContext); continue; } PhClearReference(¬ifyContext->ServiceName); notifyContext->State = SnNotify; goto NotifyCase; case SnRemoving: RemoveEntryList(¬ifyContext->ListEntry); PhpDestroyServiceNotifyContext(notifyContext); break; case SnNotify: NotifyCase: memset(¬ifyContext->Buffer, 0, sizeof(SERVICE_NOTIFY)); notifyContext->Buffer.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE; notifyContext->Buffer.pfnNotifyCallback = PhpServiceNonPollScNotifyCallback; notifyContext->Buffer.pContext = notifyContext; result = NotifyServiceStatusChangeW_I( notifyContext->ServiceHandle, notifyContext->IsServiceManager ? (SERVICE_NOTIFY_CREATED | SERVICE_NOTIFY_DELETED) : (SERVICE_NOTIFY_STOPPED | SERVICE_NOTIFY_START_PENDING | SERVICE_NOTIFY_STOP_PENDING | SERVICE_NOTIFY_RUNNING | SERVICE_NOTIFY_CONTINUE_PENDING | SERVICE_NOTIFY_PAUSE_PENDING | SERVICE_NOTIFY_PAUSED | SERVICE_NOTIFY_DELETE_PENDING), ¬ifyContext->Buffer ); switch (result) { case ERROR_SUCCESS: notifyContext->State = SnNone; RemoveEntryList(¬ifyContext->ListEntry); InsertTailList(&PhpNonPollServiceListHead, ¬ifyContext->ListEntry); break; case ERROR_SERVICE_NOTIFY_CLIENT_LAGGING: // We are lagging behind. Re-open the handle to the SCM as soon as possible. lagging = TRUE; break; case ERROR_SERVICE_MARKED_FOR_DELETE: default: RemoveEntryList(¬ifyContext->ListEntry); PhpDestroyServiceNotifyContext(notifyContext); break; } break; } } while (NtWaitForSingleObject(PhpNonPollEventHandle, TRUE, NULL) != STATUS_WAIT_0) NOTHING; if (lagging) break; } // Execute all pending callbacks. NtTestAlert(); listEntry = PhpNonPollServiceListHead.Flink; while (listEntry != &PhpNonPollServiceListHead) { notifyContext = CONTAINING_RECORD(listEntry, PHP_SERVICE_NOTIFY_CONTEXT, ListEntry); listEntry = listEntry->Flink; PhpDestroyServiceNotifyContext(notifyContext); } listEntry = PhpNonPollServicePendingListHead.Flink; while (listEntry != &PhpNonPollServicePendingListHead) { notifyContext = CONTAINING_RECORD(listEntry, PHP_SERVICE_NOTIFY_CONTEXT, ListEntry); listEntry = listEntry->Flink; PhpDestroyServiceNotifyContext(notifyContext); } CloseServiceHandle(scManagerHandle); } NtClose(PhpNonPollEventHandle); return STATUS_SUCCESS; ErrorExit: PhpNonPollActive = FALSE; PhpNonPollGate = 1; NtClose(PhpNonPollEventHandle); return STATUS_UNSUCCESSFUL; } VOID PhpInitializeServiceNonPoll( VOID ) { // Dynamically import the required functions. NotifyServiceStatusChangeW_I = PhGetModuleProcAddress(L"advapi32.dll", "NotifyServiceStatusChangeW"); if (!NotifyServiceStatusChangeW_I) return; PhpNonPollActive = TRUE; PhpNonPollGate = 1; // initially the gate should be open since we only just initialized everything PhpNonPollThreadHandle = PhCreateThread(0, PhpServiceNonPollThreadStart, NULL); if (!PhpNonPollThreadHandle) { PhpNonPollActive = FALSE; return; } }