xref: /reactos/base/services/svchost/svchost.c (revision 3d3a5aa0)
1 /*
2  * PROJECT:     ReactOS Service Host
3  * LICENSE:     BSD - See COPYING.ARM in the top level directory
4  * FILE:        base/services/svchost/svchost.c
5  * PURPOSE:     Main Service Host Implementation Routines
6  * PROGRAMMERS: ReactOS Portable Systems Group
7  */
8 
9 /* INCLUDES ******************************************************************/
10 
11 #include "svchost.h"
12 
13 #include <objidl.h>
14 
15 /* GLOBALS *******************************************************************/
16 
17 LIST_ENTRY DllList;
18 CRITICAL_SECTION ListLock;
19 DWORD ServiceCount;
20 LPCWSTR ServiceNames;
21 PSVCHOST_SERVICE ServiceArray;
22 
23 /* FUNCTIONS *****************************************************************/
24 
25 __callback
26 LONG
27 WINAPI
SvchostUnhandledExceptionFilter(_In_ struct _EXCEPTION_POINTERS * ExceptionInfo)28 SvchostUnhandledExceptionFilter (
29     _In_ struct _EXCEPTION_POINTERS * ExceptionInfo
30     )
31 {
32     /* Call the default OS handler */
33     return RtlUnhandledExceptionFilter(ExceptionInfo);
34 }
35 
36 VOID
37 WINAPI
DummySvchostCtrlHandler(_In_ DWORD dwControl)38 DummySvchostCtrlHandler (
39     _In_ DWORD dwControl
40     )
41 {
42     /* This is just a stub while we abort loading */
43     return;
44 }
45 
46 PSVCHOST_OPTIONS
47 WINAPI
BuildCommandOptions(_In_ LPWSTR pszCmdLine)48 BuildCommandOptions (
49     _In_ LPWSTR pszCmdLine
50     )
51 {
52     ULONG cbCmdLine;
53     PSVCHOST_OPTIONS pOptions;
54     LPWSTR pch, pGroupNameStart, lpServiceGroup;
55     LPWSTR *pGroupName = NULL;
56 
57     /* Nothing to do without a command-line */
58     if (pszCmdLine == NULL) return ERROR_SUCCESS;
59 
60     /* Get the length if the command line*/
61     cbCmdLine = sizeof(WCHAR) * lstrlenW(pszCmdLine) + sizeof(UNICODE_NULL);
62 
63     /* Allocate a buffer for the parsed options */
64     pOptions = MemAlloc(HEAP_ZERO_MEMORY, cbCmdLine + sizeof(*pOptions));
65     if (pOptions == NULL) return ERROR_SUCCESS;
66 
67     /* Copy the buffer into the parsed options block */
68     pOptions->CmdLineBuffer = (PWCHAR)(pOptions + 1);
69     memcpy(pOptions->CmdLineBuffer, pszCmdLine, cbCmdLine);
70 
71     /* Set the command-line as being inside the block itself */
72     pch = pOptions->CmdLineBuffer;
73     ASSERT(pch);
74     pOptions->CmdLine = pch;
75 
76     /* Now scan it */
77     while (*pch != UNICODE_NULL)
78     {
79         /* And look for the first space or TAB */
80         if ((*pch == ' ') || (*pch == '\t'))
81         {
82             /* Terminate the command-line there */
83             *pch++ = UNICODE_NULL;
84             break;
85         }
86 
87         /* Keep searching */
88         pch++;
89     }
90 
91     /* Lowercase the entire command line, but not the options */
92     SvchostCharLowerW(pOptions->CmdLine);
93 
94     /* Loop over the command-line */
95     while (*pch != UNICODE_NULL)
96     {
97         /* Do an inner loop within it */
98         while (*pch != UNICODE_NULL)
99         {
100             /* And keep going until we find a space or TAB */
101             if ((*pch != ' ') && (*pch != '\t')) break;
102             pch++;
103         }
104 
105         /* Did we reach the end? If so, bail out */
106         if (*pch == UNICODE_NULL) break;
107 
108         /* We found the space -- is it followed by a dash or slash? */
109         if ((*pch == '-' || *pch == '/') && (*(pch + 1) != UNICODE_NULL))
110         {
111             /* Yep, and there's at least one character. Is it k or K? */
112             pch++;
113             if (*pch == 'k' || *pch == 'K')
114             {
115                 /* Yep, we have a proper command line with a group! */
116                 pOptions->HasServiceGroup = TRUE;
117                 pGroupName = &pOptions->ServiceGroupName;
118             }
119 
120             /* Keep going -- the name follows between spaces or quotes */
121             pch++;
122         }
123         else
124         {
125             /* Nope, so this might be the group being entered */
126             pGroupNameStart = pch;
127 
128             /* Check if there's a quote */
129             if ((*pch == '"') && (*(pch + 1) != UNICODE_NULL))
130             {
131                 /* There is, keep going until the quote is over with */
132                 pGroupNameStart = ++pch;
133                 while (*pch) if (*pch++ == '"') break;
134             }
135             else
136             {
137                 /* No quote, so keep going until the next space or TAB */
138                 while ((*pch) && ((*pch != ' ') && (*pch != '\t'))) pch++;
139             }
140 
141             /* Now we have a space or quote delimited name, terminate it */
142             if (*pch) *pch++ = UNICODE_NULL;
143 
144             /* Ok, so we have a string -- was it preceded by a -K or /K? */
145             if (pGroupName)
146             {
147                 /* Yes it was, remember this, and don't overwrite it later */
148                 *pGroupName = pGroupNameStart;
149                 pGroupName = NULL;
150             }
151         }
152     }
153 
154     /* Print out the service group for the debugger, and the command line */
155     DBG_TRACE("Command line     : %ws\n", pszCmdLine);
156     lpServiceGroup = (pOptions->HasServiceGroup) ?
157                       pOptions->ServiceGroupName : L"No";
158     DBG_TRACE("Service Group    : %ws\n", lpServiceGroup);
159 
160     /* Was a group specified? */
161     if (pOptions->HasServiceGroup == FALSE)
162     {
163         /* This isn't valid. Print out help on the debugger and fail */
164         DBG_TRACE("Generic Service Host\n\n"
165                   "%ws [-k <key>] | [-r] | <service>\n\n"
166                   "   -k <key>   Host all services whose ImagePath matches\n"
167                   "              %ws -k <key>.\n\n",
168                   lpServiceGroup);
169         MemFree(pOptions);
170         pOptions = NULL;
171     }
172 
173     /* Return the allocated option block, if valid*/
174     return pOptions;
175 }
176 
177 DWORD
178 WINAPI
ReadPerInstanceRegistryParameters(_In_ HKEY hKey,_In_ PSVCHOST_OPTIONS pOptions)179 ReadPerInstanceRegistryParameters (
180     _In_ HKEY hKey,
181     _In_ PSVCHOST_OPTIONS pOptions
182     )
183 {
184     DWORD dwError, dwData;
185     HKEY hSubKey;
186 
187     /* We should have a name for this service group... */
188     ASSERT(pOptions->ServiceGroupName);
189 
190     /* Query the group to see what services are part of it */
191     dwError = RegQueryString(hKey,
192                              pOptions->ServiceGroupName,
193                              REG_MULTI_SZ,
194                              (PBYTE*)&ServiceNames);
195     if ((dwError == ERROR_SUCCESS) &&
196         ((ServiceNames == NULL) || (*ServiceNames == UNICODE_NULL)))
197     {
198         /* If the key exists but there's no data, fail */
199         return ERROR_INVALID_DATA;
200     }
201 
202     /* Open the key for this group */
203     if (RegOpenKeyExW(hKey,
204                       pOptions->ServiceGroupName,
205                       0,
206                       KEY_READ,
207                       &hSubKey) != ERROR_SUCCESS)
208     {
209         /* If we couldn't, bail out */
210         return dwError;
211     }
212 
213     /* Check if we should initialize COM */
214     if (RegQueryDword(hSubKey,
215                       L"CoInitializeSecurityParam",
216                       &dwData) == ERROR_SUCCESS)
217     {
218         /* Yes, remember the parameter to be sent when we do so */
219         pOptions->CoInitializeSecurityParam = dwData;
220     }
221 
222     /* Also, if COM is requested, we must read a bunch more data... */
223     if (pOptions->CoInitializeSecurityParam)
224     {
225         /* First, read the authentication level, use a default if none is set */
226         if (RegQueryDword(hSubKey, L"AuthenticationLevel", &dwData))
227         {
228             pOptions->AuthenticationLevel = RPC_C_AUTHN_LEVEL_PKT;
229         }
230         else
231         {
232             pOptions->AuthenticationLevel = dwData;
233         }
234 
235         /* Do the same for the impersonation level */
236         if (RegQueryDword(hSubKey, L"ImpersonationLevel", &dwData))
237         {
238             pOptions->ImpersonationLevel = RPC_C_IMP_LEVEL_IDENTIFY;
239         }
240         else
241         {
242             pOptions->ImpersonationLevel = dwData;
243         }
244 
245         /* Do the same for the authentication capabilities */
246         if (RegQueryDword(hSubKey, L"AuthenticationCapabilities", &dwData))
247         {
248             pOptions->AuthenticationCapabilities = EOAC_NO_CUSTOM_MARSHAL |
249                                                    EOAC_DISABLE_AAA;
250         }
251         else
252         {
253             pOptions->AuthenticationCapabilities = dwData;
254         }
255     }
256 
257     /* Check if we need a specific RPC stack size, if not, we'll set it later */
258     if (!RegQueryDword(hSubKey, L"DefaultRpcStackSize", &dwData))
259     {
260         pOptions->DefaultRpcStackSize = dwData;
261     }
262 
263     /* Finally, check if the services should be marked critical later on */
264     if (!RegQueryDword(hSubKey, L"SystemCritical", &dwData))
265     {
266         pOptions->SystemCritical = dwData;
267     }
268 
269     /* All done reading the settings */
270     RegCloseKey(hSubKey);
271     return dwError;
272 }
273 
274 VOID
275 WINAPI
BuildServiceArray(_In_ PSVCHOST_OPTIONS lpOptions)276 BuildServiceArray (
277     _In_ PSVCHOST_OPTIONS lpOptions
278     )
279 {
280     DWORD dwError;
281     PSVCHOST_SERVICE pService;
282     LPCWSTR pszServiceName;
283     HKEY hKey;
284 
285     /* Open the service host key */
286     dwError = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
287                             L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Svchost",
288                             0,
289                             KEY_READ,
290                             &hKey);
291     if (dwError != ERROR_SUCCESS) return;
292 
293     /* Read all the parameters for this service group */
294     dwError = ReadPerInstanceRegistryParameters(hKey, lpOptions);
295     RegCloseKey(hKey);
296     if (dwError != ERROR_SUCCESS) return;
297 
298     /* Acquire the database lock while we create the service group */
299     EnterCriticalSection(&ListLock);
300 
301     /* Loop the array of services for this group */
302     pszServiceName = ServiceNames;
303     while (*pszServiceName != UNICODE_NULL)
304     {
305         /* For each one, increment the amount of services we'llhost */
306         ServiceCount++;
307         pszServiceName += lstrlenW(pszServiceName) + 1;
308     }
309 
310     /* We should host at least one... */
311     ASSERT(ServiceCount);
312 
313     /* Now allocate an array to hold all their pointers */
314     ServiceArray = MemAlloc(HEAP_ZERO_MEMORY, sizeof(*pService) * ServiceCount);
315     if (ServiceArray)
316     {
317         /* Start at the beginning of the array */
318         pService = ServiceArray;
319 
320         /* Loop through all the services */
321         pszServiceName = ServiceNames;
322         while (*pszServiceName != UNICODE_NULL)
323         {
324             /* Save the service's name and move to the next entry */
325             pService++->pszServiceName = pszServiceName;
326             pszServiceName += lstrlenW(pszServiceName) + 1;
327         }
328 
329         /* We should have gotten the same count as earlier! */
330         ASSERT(pService == ServiceArray + ServiceCount);
331     }
332 
333     /* We're done, drop the lock */
334     LeaveCriticalSection(&ListLock);
335 }
336 
337 BOOL
338 WINAPI
FDebugBreakForService(_In_ LPCWSTR ServiceName)339 FDebugBreakForService (
340     _In_ LPCWSTR ServiceName
341     )
342 {
343     DWORD Data;
344     BOOL DebugBreakOn = FALSE;
345     HKEY hKey, hSubKey;
346 
347     /* Open the key for Svchost itself */
348     if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
349                       L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Svchost",
350                       0,
351                       KEY_READ,
352                       &hKey) == ERROR_SUCCESS)
353     {
354         /* That worked, now open the key for this service, if it exists */
355         if (RegOpenKeyExW(hKey, ServiceName, 0, KEY_READ, &hSubKey) == ERROR_SUCCESS)
356         {
357             /* It does -- is there a DebugBreak value set? */
358             if (RegQueryDword(hSubKey, L"DebugBreak", &Data) == ERROR_SUCCESS)
359             {
360                 /* There is! Check if it's 0 or 1 */
361                 DebugBreakOn = Data != 0;
362             }
363 
364             /* Done, close the service key */
365             RegCloseKey(hSubKey);
366         }
367 
368         /* Done, close the svchost key */
369         RegCloseKey(hKey);
370     }
371 
372     /* Return if the value was set or not */
373     return DebugBreakOn;
374 }
375 
376 DWORD
377 WINAPI
OpenServiceParametersKey(_In_ LPCWSTR lpSubKey,_Out_ PHKEY phKey)378 OpenServiceParametersKey (
379     _In_ LPCWSTR lpSubKey,
380     _Out_ PHKEY phKey
381     )
382 {
383     DWORD dwError;
384     HKEY hSubKey = NULL, hKey = NULL;
385     ASSERT(phKey);
386 
387     /* Open the services key */
388     dwError = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
389                             L"System\\CurrentControlSet\\Services",
390                             0,
391                             KEY_READ,
392                             &hKey);
393     if (dwError == ERROR_SUCCESS)
394     {
395         /* Now open the service specific key, returning its handle */
396         dwError = RegOpenKeyExW(hKey, lpSubKey, 0, KEY_READ, &hSubKey);
397         if (dwError == ERROR_SUCCESS)
398         {
399             /* And if that worked, return a handle to the parameters key */
400             dwError = RegOpenKeyExW(hSubKey,
401                                     L"Parameters",
402                                     0,
403                                     KEY_READ,
404                                     phKey);
405         }
406     }
407 
408     /* Clean up any leftovers*/
409     if (hKey != NULL) RegCloseKey(hKey);
410     if (hSubKey != NULL) RegCloseKey(hSubKey);
411     return dwError;
412 }
413 
414 VOID
415 WINAPI
UnloadServiceDll(_In_ PSVCHOST_SERVICE pService)416 UnloadServiceDll (
417     _In_ PSVCHOST_SERVICE pService
418     )
419 {
420     DWORD cbData;
421     DWORD Data;
422     DWORD dwType;
423     HKEY hKey = NULL;
424 
425     cbData = 4;
426     Data = 0;
427 
428     /* Grab the lock while we touch the thread count and potentially unload */
429     EnterCriticalSection(&ListLock);
430 
431     /* There is one less active callout into the DLL, it may be unloadabl now */
432     ASSERT(pService->cServiceActiveThreadCount > 0);
433     pService->cServiceActiveThreadCount--;
434 
435     /* Try to open the service registry key */
436     if (OpenServiceParametersKey(pService->pszServiceName, &hKey) == ERROR_SUCCESS)
437     {
438         /* It worked -- check if we should unload the service when stopped */
439         if (RegQueryValueExW(hKey,
440                              L"ServiceDllUnloadOnStop",
441                              0,
442                              &dwType,
443                              (LPBYTE)&Data,
444                              &cbData) == ERROR_SUCCESS)
445         {
446             /* Make sure the data is correctly formatted */
447             if ((dwType == REG_DWORD) && (Data == 1))
448             {
449                 /* Does the service still have active threads? */
450                 if (pService->cServiceActiveThreadCount != 0)
451                 {
452                     /* Yep, so we can't kill it just yet */
453                     DBG_TRACE("Skip Unload dll %ws for service %ws, "
454                               "active threads %d\n",
455                               pService->pDll->pszDllPath,
456                               pService->pszServiceName,
457                               pService->cServiceActiveThreadCount);
458                 }
459                 else
460                 {
461                     /* Nope, we're good to go */
462                     DBG_TRACE("Unload dll %ws for service %ws\n",
463                               pService->pDll->pszDllPath,
464                               pService->pszServiceName);
465 
466                     /* Free the library and clear the module handle */
467                     FreeLibrary(pService->pDll->hModule);
468                     pService->pDll->hModule = NULL;
469                 }
470             }
471         }
472     }
473 
474     /* Drop the lock, unload is complete */
475     LeaveCriticalSection(&ListLock);
476 
477     /* Close the parameters key if we had it */
478     if (hKey) RegCloseKey(hKey);
479 }
480 
481 VOID
482 WINAPI
SvchostStopCallback(_In_ PVOID Context,_In_ BOOLEAN TimerOrWaitFired)483 SvchostStopCallback (
484     _In_ PVOID Context,
485     _In_ BOOLEAN TimerOrWaitFired
486     )
487 {
488     PSVCHOST_CALLBACK_CONTEXT pSvcsStopCbContext = Context;
489     PSVCHOST_STOP_CALLBACK pfnStopCallback;
490 
491     /* Hold the lock while we grab the callback */
492     EnterCriticalSection(&ListLock);
493 
494     /* Grab the callback, then clear it */
495     ASSERT(pSvcsStopCbContext->pService != NULL);
496     pfnStopCallback = pSvcsStopCbContext->pService->pfnStopCallback;
497     ASSERT(pfnStopCallback != NULL);
498     pSvcsStopCbContext->pService->pfnStopCallback = NULL;
499 
500     /* Now release the lock, making sure the above was atomic */
501     LeaveCriticalSection(&ListLock);
502 
503     /* Now make the callout */
504     DBG_TRACE("Call stop callback for service %ws, active threads %d\n",
505               pSvcsStopCbContext->pService->pszServiceName,
506               pSvcsStopCbContext->pService->cServiceActiveThreadCount);
507     pfnStopCallback(pSvcsStopCbContext->pContext, TimerOrWaitFired);
508 
509     /* Decrement the active threads -- maybe the DLL can unload now */
510     UnloadServiceDll(pSvcsStopCbContext->pService);
511 
512     /* We no longer need the context */
513     MemFree(pSvcsStopCbContext);
514 }
515 
516 DWORD
517 WINAPI
SvcRegisterStopCallback(_Out_ PHANDLE phNewWaitObject,_In_ PCWSTR ServiceName,_In_ HANDLE hObject,_In_ PSVCHOST_STOP_CALLBACK pfnStopCallback,_In_ PVOID pContext,_In_ ULONG dwFlags)518 SvcRegisterStopCallback (
519     _Out_ PHANDLE phNewWaitObject,
520     _In_ PCWSTR ServiceName,
521     _In_ HANDLE hObject,
522     _In_ PSVCHOST_STOP_CALLBACK pfnStopCallback,
523     _In_ PVOID pContext,
524     _In_ ULONG dwFlags
525     )
526 {
527     ULONG i;
528     PSVCHOST_CALLBACK_CONTEXT pSvcsStopCbContext = NULL;
529     PSVCHOST_SERVICE pService = NULL;
530     DWORD dwError = ERROR_SUCCESS;
531 
532     /* All parameters must be present */
533     if ((phNewWaitObject == NULL) ||
534         (ServiceName == NULL) ||
535         (hObject == NULL) ||
536         (pfnStopCallback == NULL))
537     {
538         /* Fail otherwise */
539         return ERROR_INVALID_PARAMETER;
540     }
541 
542     /* Don't allow new DLLs while we send notifications */
543     EnterCriticalSection(&ListLock);
544 
545     /* Scan all registered services */
546     for (i = 0; i < ServiceCount; i++)
547     {
548         /* Search for the service entry matching this service name */
549         if (lstrcmpiW(ServiceName, ServiceArray[i].pszServiceName) == 0)
550         {
551             /* Found it */
552             pService = &ServiceArray[i];
553             break;
554         }
555     }
556 
557     /* Do we have a service? Does it already have a callback? */
558     if ((pService == NULL) || (pService->pfnStopCallback != NULL))
559     {
560         /* Affirmative, nothing for us to do */
561         dwError = ERROR_INVALID_DATA;
562         DBG_ERR("Service %ws missing or already registered for callback, "
563                 "status %d\n",
564                 ServiceName,
565                 dwError);
566         goto Quickie;
567     }
568 
569     /* Allocate our internal context */
570     pSvcsStopCbContext = MemAlloc(HEAP_ZERO_MEMORY, sizeof(*pSvcsStopCbContext));
571     if (pSvcsStopCbContext == NULL)
572     {
573         /* We failed, bail out */
574         dwError = ERROR_NOT_ENOUGH_MEMORY;
575         DBG_ERR("MemAlloc for context block for service %ws failed, status "
576                 "%d\n",
577                 ServiceName,
578                 dwError);
579         goto Quickie;
580     }
581 
582     /* Setup the context and register for the wait */
583     pSvcsStopCbContext->pContext = pContext;
584     pSvcsStopCbContext->pService = pService;
585     if (RegisterWaitForSingleObject(phNewWaitObject,
586                                     hObject,
587                                     SvchostStopCallback,
588                                     pSvcsStopCbContext,
589                                     INFINITE,
590                                     dwFlags))
591     {
592         /* We now have an active thread associated with this wait */
593         pService->cServiceActiveThreadCount++;
594         pService->pfnStopCallback = pfnStopCallback;
595         DBG_TRACE("Registered stop callback for service %ws, active threads %d\n",
596                   ServiceName,
597                   pService->cServiceActiveThreadCount);
598     }
599     else
600     {
601         /* Registration failed, bail out */
602         dwError = GetLastError();
603         DBG_ERR("Registration for stop callback for service %ws failed, "
604                 "status %d\n",
605                 ServiceName,
606                 dwError);
607     }
608 
609 Quickie:
610     /* Drop the lock, and free the context if we failed */
611     LeaveCriticalSection(&ListLock);
612     if (dwError != ERROR_SUCCESS) MemFree(pSvcsStopCbContext);
613     return dwError;
614 }
615 
616 VOID
617 WINAPI
AbortSvchostService(_In_ LPCWSTR lpServiceName,_In_ DWORD dwExitCode)618 AbortSvchostService (
619     _In_ LPCWSTR lpServiceName,
620     _In_ DWORD dwExitCode
621     )
622 {
623     SERVICE_STATUS_HANDLE scHandle;
624     SERVICE_STATUS ServiceStatus;
625 
626     /* Make us stopped and accept only query commands */
627     ServiceStatus.dwCheckPoint = 0;
628     ServiceStatus.dwWaitHint = 0;
629     ServiceStatus.dwServiceSpecificExitCode = 0;
630     ServiceStatus.dwCurrentState = SERVICE_STOPPED;
631     ServiceStatus.dwControlsAccepted = SERVICE_QUERY_CONFIG;
632     ServiceStatus.dwServiceType = SERVICE_WIN32;
633     ServiceStatus.dwWin32ExitCode = dwExitCode;
634 
635     /* Register a handler that will do nothing while we are being stopped */
636     scHandle = RegisterServiceCtrlHandlerW(lpServiceName,
637                                            DummySvchostCtrlHandler);
638     if (scHandle)
639     {
640         /* Stop us */
641         if (!SetServiceStatus(scHandle, &ServiceStatus))
642         {
643             /* Tell the debugger if this didn't work */
644             DBG_ERR("AbortSvchostService: SetServiceStatus error %ld\n",
645                     GetLastError());
646         }
647     }
648     else
649     {
650         /* Tell the debugger if we couldn't register the handler */
651         DBG_ERR("AbortSvchostService: RegisterServiceCtrlHandler failed %d\n",
652                 GetLastError());
653     }
654 }
655 
656 PVOID
657 WINAPI
GetServiceDllFunction(_In_ PSVCHOST_DLL pDll,_In_ LPCSTR lpProcName,_Out_ PDWORD lpdwError)658 GetServiceDllFunction (
659     _In_ PSVCHOST_DLL pDll,
660     _In_ LPCSTR lpProcName,
661     _Out_ PDWORD lpdwError
662     )
663 {
664     HMODULE hModule;
665     PVOID lpAddress = NULL;
666     ULONG_PTR ulCookie = 0;
667 
668     /* Activate the context */
669     if (ActivateActCtx(pDll->hActCtx, &ulCookie) == FALSE)
670     {
671         /* We couldn't, bail out */
672         if (lpdwError) *lpdwError = GetLastError();
673         DBG_ERR("ActivateActCtx for %ws failed.  Error %d.\n",
674                 pDll->pszDllPath,
675                 GetLastError());
676         return lpAddress;
677     }
678 
679     /* Check if we already have a loaded module for this service */
680     hModule = pDll->hModule;
681     if (!hModule)
682     {
683         /* We don't -- load it */
684         hModule = LoadLibraryExW(pDll->pszDllPath,
685                                     NULL,
686                                     LOAD_WITH_ALTERED_SEARCH_PATH);
687         if (!hModule)
688         {
689             /* We failed to load it, bail out */
690             if (lpdwError) *lpdwError = GetLastError();
691             DBG_ERR("LoadLibrary (%ws) failed.  Error %d.\n",
692                     pDll->pszDllPath,
693                     GetLastError());
694             DeactivateActCtx(0, ulCookie);
695             return lpAddress;
696         }
697 
698         /* Loaded! */
699         pDll->hModule = hModule;
700     }
701 
702     /* Next, get the address being looked up*/
703     lpAddress = GetProcAddress(hModule, lpProcName);
704     if (!lpAddress && lpdwError)
705     {
706         /* We couldn't find it, write the error code and a debug statement */
707         *lpdwError = GetLastError();
708         DBG_ERR("GetProcAddress (%s) failed on DLL %ws.  Error %d.\n",
709                 lpProcName,
710                 pDll->pszDllPath,
711                 GetLastError());
712     }
713 
714     /* All done, get rid of the activation context */
715     DeactivateActCtx(0, ulCookie);
716     return lpAddress;
717 }
718 
719 PSVCHOST_DLL
720 WINAPI
FindDll(_In_ LPCWSTR pszManifestPath,_In_ LPCWSTR pszDllPath,_In_ PSVCHOST_SERVICE pService)721 FindDll (
722     _In_ LPCWSTR pszManifestPath,
723     _In_ LPCWSTR pszDllPath,
724     _In_ PSVCHOST_SERVICE pService
725     )
726 {
727     PSVCHOST_DLL pDll, pFoundDll = NULL;
728     PLIST_ENTRY pNextEntry;
729     ASSERT(pszDllPath);
730 
731     /* Lock the DLL database */
732     EnterCriticalSection(&ListLock);
733 
734     /* Scan through the database */
735     pNextEntry = DllList.Flink;
736     while (pNextEntry != &DllList)
737     {
738         /* Search for an existing DLL with the same parameters */
739         pDll = CONTAINING_RECORD(pNextEntry, SVCHOST_DLL, DllList);
740         if ((lstrcmpW(pDll->pszDllPath, pszDllPath) == 0) &&
741             (lstrcmpW(pDll->pszManifestPath, pszManifestPath) == 0) &&
742             (lstrcmpW(pDll->pService->pszServiceName, pService->pszServiceName) == 0))
743         {
744             /* Found it! */
745             pFoundDll = pDll;
746             break;
747         }
748 
749         /* Keep searching */
750         pNextEntry = pNextEntry->Flink;
751     }
752 
753     /* All done, release the lock and return what we found, if anything */
754     LeaveCriticalSection(&ListLock);
755     return pFoundDll;
756 }
757 
758 PSVCHOST_DLL
759 WINAPI
AddDll(_In_ LPCWSTR pszManifestPath,_In_ LPCWSTR pszDllPath,_In_ PSVCHOST_SERVICE pService,_Out_ PDWORD lpdwError)760 AddDll (
761     _In_ LPCWSTR pszManifestPath,
762     _In_ LPCWSTR pszDllPath,
763     _In_ PSVCHOST_SERVICE pService,
764     _Out_ PDWORD lpdwError
765     )
766 {
767     PSVCHOST_DLL pDll;
768     ULONG nDllPathLength, nManifestPathLength;
769     ASSERT(pszDllPath);
770     ASSERT(*pszDllPath);
771 
772     /* Compute the length of the two paths */
773     nDllPathLength = lstrlenW(pszDllPath);
774     nManifestPathLength = lstrlenW(pszManifestPath);
775 
776     /* Allocate the DLL entry for this service */
777     pDll =  MemAlloc(HEAP_ZERO_MEMORY,
778                      sizeof(*pDll) +
779                      (nDllPathLength + nManifestPathLength) * sizeof(WCHAR) +
780                      2 * sizeof(UNICODE_NULL));
781     if (pDll == NULL)
782     {
783         /* Bail out with failure */
784         *lpdwError = ERROR_NOT_ENOUGH_MEMORY;
785         return pDll;
786     }
787 
788     /* Put the DLL path right after the DLL entry */
789     pDll->pszDllPath = (LPCWSTR)(pDll + 1);
790 
791     /* Put the manifest path right past the DLL path (note the pointer math) */
792     pDll->pszManifestPath = pDll->pszDllPath + nDllPathLength + 1;
793 
794     /* Now copy both paths */
795     CopyMemory((PVOID)pDll->pszDllPath,
796                pszDllPath,
797                sizeof(WCHAR) * nDllPathLength);
798     CopyMemory((PVOID)pDll->pszManifestPath,
799                pszManifestPath,
800                sizeof(WCHAR) * nManifestPathLength);
801 
802     /* Now set the service, and make sure both paths are NULL-terminated */
803     pDll->pService = pService;
804     ASSERT(pDll->hActCtx == NULL);
805     ASSERT(pDll->pszDllPath[nDllPathLength] == UNICODE_NULL);
806     ASSERT(pDll->pszManifestPath[nManifestPathLength] == UNICODE_NULL);
807 
808     /* Finally, add the entry to the DLL database, while holding the lock */
809     EnterCriticalSection(&ListLock);
810     InsertTailList(&DllList, &pDll->DllList);
811     LeaveCriticalSection(&ListLock);
812 
813     /* And return it */
814     return pDll;
815 }
816 
817 VOID
818 WINAPI
GetServiceMainFunctions(_In_ PSVCHOST_SERVICE pService,_Out_ PVOID * pServiceMain,_Out_ PVOID * pPushServiceGlobals,_Out_ PDWORD lpdwError)819 GetServiceMainFunctions (
820     _In_ PSVCHOST_SERVICE pService,
821     _Out_ PVOID *pServiceMain,
822     _Out_ PVOID *pPushServiceGlobals,
823     _Out_ PDWORD lpdwError
824     )
825 {
826     DWORD dwError, cbDllLength, cbData, dwType;
827     PSVCHOST_DLL pDll;
828     ACTCTXW actCtx;
829     LPCWSTR pszDllPath;
830     HKEY hKey;
831     HANDLE hActCtx;
832     LPWSTR lpData;
833     WCHAR szDllBuffer[MAX_PATH + 2], szManifestBuffer[MAX_PATH + 2];
834 
835     /* Initialize the activation context we might need later */
836     RtlZeroMemory(&actCtx, sizeof(actCtx));
837     actCtx.cbSize = sizeof(actCtx);
838 
839     /* We clean these up in our failure path so initialize them to NULL here */
840     hActCtx = NULL;
841     hKey = NULL;
842     lpData = NULL;
843     *lpdwError = ERROR_SUCCESS;
844 
845     /* Null terminate the string buffers */
846     szDllBuffer[0] = UNICODE_NULL;
847     szManifestBuffer[0] = UNICODE_NULL;
848 
849     /* Do we already have a DLL ready to go for this service? */
850     pDll = pService->pDll;
851     if (pDll != NULL) goto HaveDll;
852 
853     /* Nope, we're starting from scratch. Open a handle to parameters key */
854     dwError = OpenServiceParametersKey(pService->pszServiceName, &hKey);
855     if (dwError)
856     {
857         *lpdwError = dwError;
858         ASSERT(*lpdwError != NO_ERROR);
859         goto Quickie;
860     }
861 
862     /* Allocate enough space to hold a unicode path (NULL-terminated) */
863     cbData = MAX_PATH * sizeof(WCHAR) + sizeof(UNICODE_NULL);
864     lpData = MemAlloc(0, cbData);
865     if (lpData == NULL)
866     {
867         /* No memory, bail out */
868         *lpdwError = ERROR_NOT_ENOUGH_MEMORY;
869         goto Quickie;
870     }
871 
872     /* Query the DLL path */
873     lpData[0] = UNICODE_NULL;
874     dwError = RegQueryValueExW(hKey,
875                                L"ServiceDll",
876                                0,
877                                &dwType,
878                                (LPBYTE)lpData,
879                                &cbData);
880     if (dwError != ERROR_SUCCESS)
881     {
882         *lpdwError = dwError;
883         DBG_ERR("RegQueryValueEx for the ServiceDll parameter of the %ws "
884                 "service returned %u\n",
885                 pService->pszServiceName,
886                 dwError);
887         goto Quickie;
888     }
889 
890     /* Is the registry data valid and present? */
891     if ((dwType != REG_EXPAND_SZ) || (lpData[0] == UNICODE_NULL))
892     {
893         /* Nope, bail out */
894         *lpdwError = ERROR_FILE_NOT_FOUND;
895         DBG_ERR("The ServiceDll parameter for the %ws service is not of type "
896                 "REG_EXPAND_SZ\n",
897                 pService->pszServiceName);
898         goto Quickie;
899     }
900 
901     /* Convert the expandable path into an absolute path */
902     ExpandEnvironmentStringsW(lpData, szDllBuffer, MAX_PATH);
903     SvchostCharLowerW(szDllBuffer);
904 
905     /* Check if the service has a manifest file associated with it */
906     cbData = MAX_PATH * sizeof(WCHAR) + sizeof(UNICODE_NULL);
907     dwError = RegQueryValueExW(hKey,
908                                L"ServiceManifest",
909                                NULL,
910                                &dwType,
911                                (LPBYTE)lpData,
912                                &cbData);
913     if (dwError != ERROR_SUCCESS)
914     {
915         /* Did we fail because one wasn't set? */
916         if ((dwError != ERROR_PATH_NOT_FOUND) &&
917             (dwError != ERROR_FILE_NOT_FOUND))
918         {
919             /* We failed for some other reason, bail out */
920             *lpdwError = dwError;
921             DBG_ERR("RegQueryValueEx for the ServiceManifest parameter of the "
922                     "%ws service returned %u\n",
923                     pService->pszServiceName,
924                     dwError);
925             goto Quickie;
926         }
927 
928         /* We have no manifest, make sure the buffer is empty */
929         szManifestBuffer[0] = UNICODE_NULL;
930 
931         /* We're done with this buffer */
932         MemFree(lpData);
933         lpData = NULL;
934 
935         /* Use the whole DLL path, since we don't have a manifest */
936         pszDllPath = szDllBuffer;
937     }
938     else
939     {
940         /* Do we have invalid registry data? */
941         if ((dwType != REG_EXPAND_SZ) || (*lpData == UNICODE_NULL))
942         {
943             /* Yes, pretend there's no manifest and bail out */
944             *lpdwError = ERROR_FILE_NOT_FOUND;
945             DBG_ERR("The ServiceManifest parameter for the %ws service is not "
946                     "of type REG_EXPAND_SZ\n",
947                     pService->pszServiceName);
948             goto Quickie;
949         }
950 
951         /* Expand the string into our stack buffer */
952         ExpandEnvironmentStringsW(lpData, szManifestBuffer, MAX_PATH);
953 
954         /* We no longer need the heap buffer*/
955         MemFree(lpData);
956         lpData = NULL;
957 
958         /* Lowercase the manifest path */
959         SvchostCharLowerW(szManifestBuffer);
960 
961         /* Now loop over the DLL path */
962         cbDllLength = lstrlenW(szDllBuffer);
963         while (cbDllLength)
964         {
965             /* From the end, until we find the first path separator */
966             if (szDllBuffer[cbDllLength] == '\\' || szDllBuffer[cbDllLength] == '/')
967             {
968                 /* Use just a short name (cut out the rest of the path) */
969                 pszDllPath = &szDllBuffer[cbDllLength + 1];
970                 break;
971             }
972 
973             /* Try the next character */
974             cbDllLength--;
975         }
976     }
977 
978     /* See if we already have a matching DLL and manifest for this service */
979     pDll = FindDll(szManifestBuffer, pszDllPath, pService);
980     if (pDll == NULL)
981     {
982         /* We don't have it yet -- does this DLL have a manifest? */
983         if (szManifestBuffer[0] != UNICODE_NULL)
984         {
985             /* Create an activation context for it */
986             actCtx.lpSource = szManifestBuffer;
987             hActCtx = CreateActCtxW(&actCtx);
988             if (hActCtx == INVALID_HANDLE_VALUE)
989             {
990                 /* We've failed to create one, bail out */
991                 *lpdwError = GetLastError();
992                 DBG_ERR("CreateActCtxW(%ws) for the %ws service returned %u\n",
993                         szManifestBuffer,
994                         pService->pszServiceName,
995                         *lpdwError);
996                 goto Quickie;
997             }
998         }
999 
1000         /* Add this new DLL into the database */
1001         pDll = AddDll(szManifestBuffer, pszDllPath, pService, lpdwError);
1002         if (pDll)
1003         {
1004             /* Save the activation context and zero it so we don't release later */
1005             pDll->hActCtx = hActCtx;
1006             hActCtx = NULL;
1007         }
1008     }
1009 
1010     /* We either found, added, or failed to add, the DLL for this service */
1011     ASSERT(!pService->pDll);
1012     pService->pDll = pDll;
1013 
1014     /* In all cases, we will query the ServiceMain function, however*/
1015     RegQueryStringA(hKey,
1016                     L"ServiceMain",
1017                     REG_SZ,
1018                     &pService->pszServiceMain);
1019 
1020     /* And now we'll check if we were able to create it earlier (or find it) */
1021     if (!pService->pDll)
1022     {
1023         /* We were not, so there's no point in going on */
1024         ASSERT(*lpdwError != NO_ERROR);
1025         goto Quickie;
1026     }
1027 
1028     /* We do have a valid DLL, so now get the service main routine out of it */
1029 HaveDll:
1030     *pServiceMain = GetServiceDllFunction(pDll,
1031                                           pService->pszServiceMain ?
1032                                           pService->pszServiceMain :
1033                                           "ServiceMain",
1034                                           lpdwError);
1035 
1036     /* And now get the globals routine out of it (this one is optional) */
1037     *pPushServiceGlobals = GetServiceDllFunction(pDll,
1038                                                  "SvchostPushServiceGlobals",
1039                                                  NULL);
1040 
1041 Quickie:
1042     /* We're done, cleanup any resources we left behind */
1043     if (hKey != NULL) RegCloseKey(hKey);
1044     if (lpData != NULL) MemFree(lpData);
1045     if ((hActCtx) && (hActCtx != INVALID_HANDLE_VALUE)) ReleaseActCtx(hActCtx);
1046 }
1047 
1048 VOID
1049 WINAPI
ServiceStarter(_In_ DWORD dwNumServicesArgs,_In_ LPWSTR * lpServiceArgVectors)1050 ServiceStarter (
1051     _In_ DWORD dwNumServicesArgs,
1052     _In_ LPWSTR *lpServiceArgVectors
1053     )
1054 {
1055     DWORD i, dwError = ERROR_FILE_NOT_FOUND;
1056     PSVCHOST_SERVICE ServiceEntry;
1057     LPCWSTR lpServiceName;
1058     LPSERVICE_MAIN_FUNCTIONW ServiceMain = NULL;
1059     PSVCHOST_INIT_GLOBALS ServiceInitGlobals = NULL;
1060 
1061     /* Hold the lock while we start a service */
1062     EnterCriticalSection(&ListLock);
1063 
1064     /* Get this service's name, and loop the database */
1065     lpServiceName = *lpServiceArgVectors;
1066     for (i = 0; i < ServiceCount; i++)
1067     {
1068         /* Try to find a match by name */
1069         if (!lstrcmpiW(lpServiceName, ServiceArray[i].pszServiceName)) break;
1070     }
1071 
1072     /* Check if we didn't find it */
1073     if (i == ServiceCount)
1074     {
1075         /* This looks like a bogus attempt, bail out */
1076         LeaveCriticalSection(&ListLock);
1077         return;
1078     }
1079 
1080     /* We have it */
1081     ServiceEntry = &ServiceArray[i];
1082 
1083     /* Should we breakpoint before loading this service? */
1084     if (FDebugBreakForService(lpServiceName) != FALSE)
1085     {
1086         /* Yep -- do it */
1087         DBG_TRACE("Attaching debugger before getting ServiceMain for %ws...\n",
1088                   lpServiceName);
1089         DebugBreak();
1090     }
1091 
1092     /* Get the callbacks for this service */
1093     GetServiceMainFunctions(&ServiceArray[i],
1094                             (PVOID*)&ServiceMain,
1095                             (PVOID*)&ServiceInitGlobals,
1096                             &dwError);
1097 
1098     /* If this service is valid and needs globals, but we don't have them... */
1099     if ((ServiceMain != NULL) &&
1100         (ServiceInitGlobals != NULL) &&
1101         (g_pSvchostSharedGlobals == NULL))
1102     {
1103         /* Go and build them for the first time! */
1104         SvchostBuildSharedGlobals();
1105     }
1106 
1107     /* We call a service if it has a main, or if wants globals and we have them */
1108     if (((ServiceInitGlobals != NULL) && (g_pSvchostSharedGlobals != NULL)) ||
1109         ((ServiceMain != NULL) && (ServiceInitGlobals == NULL)))
1110     {
1111         /* Therefore, make sure it won't be unloaded as we drop the lock */
1112         ServiceEntry->cServiceActiveThreadCount++;
1113     }
1114 
1115     /* Drop the lock while we call into the service DLL */
1116     LeaveCriticalSection(&ListLock);
1117 
1118     /* First: does this service want globals? */
1119     if (ServiceInitGlobals != NULL)
1120     {
1121         /* Do we have any to give? */
1122         if (g_pSvchostSharedGlobals != NULL)
1123         {
1124             /* Yes, push them to the service */
1125             ServiceInitGlobals(g_pSvchostSharedGlobals);
1126 
1127             /* Does the service NOT have an entrypoint? */
1128             if (ServiceMain == NULL)
1129             {
1130                 /* We're going to abort loading, it serves no use */
1131                 if (lpServiceName != NULL)
1132                 {
1133                     AbortSvchostService(lpServiceName, dwError);
1134                 }
1135 
1136                 /* And drop a reference count since it's not active anymore */
1137                 UnloadServiceDll(ServiceEntry);
1138             }
1139         }
1140         else if (lpServiceName != NULL)
1141         {
1142             /* We have no globals to give, so don't even bother calling it */
1143             AbortSvchostService(lpServiceName, dwError);
1144         }
1145     }
1146 
1147     /* Next up, does it have an entrypoint? */
1148     if (ServiceMain != NULL)
1149     {
1150         /* It does, so call it */
1151         DBG_TRACE("Calling ServiceMain for %ws...\n", lpServiceName);
1152         ServiceMain(dwNumServicesArgs, lpServiceArgVectors);
1153 
1154         /* We're out of the service now, so we can drop a reference */
1155         UnloadServiceDll(ServiceEntry);
1156     }
1157     else if (lpServiceName != NULL)
1158     {
1159         /* It has no entrypoint, so abort its launch */
1160         AbortSvchostService(lpServiceName, dwError);
1161     }
1162 }
1163 
1164 SERVICE_TABLE_ENTRYW*
1165 WINAPI
BuildServiceTable(VOID)1166 BuildServiceTable (
1167     VOID
1168     )
1169 {
1170     SERVICE_TABLE_ENTRYW *pServiceTable;
1171     ULONG i;
1172 
1173     /* Acquire the database lock while we go over the services */
1174     EnterCriticalSection(&ListLock);
1175 
1176     /* Allocate space for a NULL entry at the end as well, Windows needs this */
1177     pServiceTable = MemAlloc(HEAP_ZERO_MEMORY,
1178                              (ServiceCount + 1) * sizeof(*pServiceTable));
1179     if (pServiceTable)
1180     {
1181         /* Go over all our registered services */
1182         for (i = 0; i < ServiceCount; i++)
1183         {
1184             /* And set their parameters in the service table */
1185             pServiceTable[i].lpServiceName = (LPWSTR)ServiceArray[i].pszServiceName;
1186             pServiceTable[i].lpServiceProc = ServiceStarter;
1187             DBG_TRACE("Added service table entry for %ws\n",
1188                       pServiceTable[i].lpServiceName);
1189         }
1190     }
1191 
1192     /* All done, can release the lock now */
1193     LeaveCriticalSection(&ListLock);
1194     return pServiceTable;
1195 }
1196 
1197 BOOLEAN
1198 WINAPI
CallPerInstanceInitFunctions(_In_ PSVCHOST_OPTIONS pOptions)1199 CallPerInstanceInitFunctions (
1200     _In_ PSVCHOST_OPTIONS pOptions
1201     )
1202 {
1203     PIMAGE_NT_HEADERS pNtHeaders;
1204     BOOLEAN bResult;
1205 
1206     /* Is COM required for this host? */
1207     if (pOptions->CoInitializeSecurityParam != 0)
1208     {
1209         /* Yes, initialize COM security and parameters */
1210         bResult = InitializeSecurity(pOptions->CoInitializeSecurityParam,
1211                                      pOptions->AuthenticationLevel,
1212                                      pOptions->ImpersonationLevel,
1213                                      pOptions->AuthenticationCapabilities);
1214         if (bResult == FALSE) return FALSE;
1215     }
1216 
1217     /* Do we have a custom RPC stack size? */
1218     if (pOptions->DefaultRpcStackSize != 0)
1219     {
1220         /* Yes, go set it */
1221         RpcMgmtSetServerStackSize(pOptions->DefaultRpcStackSize << 10);
1222     }
1223     else
1224     {
1225         /* Nope, get the NT headers from the image */
1226         pNtHeaders = RtlImageNtHeader(NtCurrentPeb()->ImageBaseAddress);
1227         if (pNtHeaders)
1228         {
1229             /* And just use whatever the default thread stack is */
1230             RpcMgmtSetServerStackSize(pNtHeaders->OptionalHeader.SizeOfStackCommit);
1231         }
1232     }
1233 
1234     /* Is this host holding critical services? */
1235     if (pOptions->SystemCritical != FALSE)
1236     {
1237         /* Mark the process as critical if so */
1238         RtlSetProcessIsCritical(TRUE, NULL, TRUE);
1239     }
1240 
1241     /* All done */
1242     return TRUE;
1243 }
1244 
1245 VOID
1246 __cdecl
wmainCRTStartup(VOID)1247 wmainCRTStartup (
1248     VOID
1249     )
1250 {
1251     PSVCHOST_OPTIONS lpOptions;
1252     SERVICE_TABLE_ENTRYW *pServiceTable;
1253     LPWSTR pszCmdLine;
1254 
1255     /* Set a generic SEH filter and hard fail all critical errors */
1256     SetUnhandledExceptionFilter(SvchostUnhandledExceptionFilter);
1257     SetErrorMode(SEM_FAILCRITICALERRORS);
1258 
1259     /* Initialize the heap allocator */
1260     MemInit(GetProcessHeap());
1261 
1262     /* Initialize the DLL database and lock */
1263     InitializeListHead(&DllList);
1264     InitializeCriticalSection(&ListLock);
1265 
1266     /* Get the command-line and parse it to get the service group */
1267     pszCmdLine = GetCommandLineW();
1268     lpOptions = BuildCommandOptions(pszCmdLine);
1269     if (lpOptions == NULL)
1270     {
1271         /* Without a valid command-line, there's nothing for us to do */
1272         DBG_TRACE("Calling ExitProcess for %ws\n", pszCmdLine);
1273         ExitProcess(0);
1274     }
1275 
1276     /* Now use the service group information to lookup all the services */
1277     BuildServiceArray(lpOptions);
1278 
1279     /* Convert the list of services in this group to the SCM format */
1280     pServiceTable = BuildServiceTable();
1281     if (pServiceTable == NULL)
1282     {
1283         /* This is critical, bail out without it */
1284         MemFree(lpOptions);
1285         return;
1286     }
1287 
1288     /* Initialize COM and RPC as needed for this service group */
1289     if (CallPerInstanceInitFunctions(lpOptions) == FALSE)
1290     {
1291         /* Exit with a special code indicating COM/RPC setup has failed */
1292         DBG_ERR("%s", "CallPerInstanceInitFunctions failed -- exiting!\n");
1293         ExitProcess(1);
1294     }
1295 
1296     /* We're ready to go -- free the options buffer */
1297     MemFree(lpOptions);
1298 
1299     /* And call into ADVAPI32 to get our services going */
1300     StartServiceCtrlDispatcherW(pServiceTable);
1301 }
1302