1 /*
2  * PROJECT:     ReactOS Local Spooler API Tests
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Functions needed to run our code as a service. This is needed to run in SYSTEM security context.
5  * COPYRIGHT:   Copyright 2015 Colin Finck (colin@reactos.org)
6  */
7 
8 #include <apitest.h>
9 
10 #define WIN32_NO_STATUS
11 #include <windef.h>
12 #include <winbase.h>
13 #include <wingdi.h>
14 #include <winreg.h>
15 #include <winsvc.h>
16 #include <winspool.h>
17 #include <winsplp.h>
18 #include <tlhelp32.h>
19 
20 #include "localspl_apitest.h"
21 
22 //#define NDEBUG
23 #include <debug.h>
24 
25 
26 static void
27 _DoDLLInjection()
28 {
29     DWORD cbDLLPath;
30     HANDLE hProcess;
31     HANDLE hSnapshot;
32     HANDLE hThread;
33     PROCESSENTRY32W pe;
34     PVOID pLoadLibraryAddress;
35     PVOID pLoadLibraryArgument;
36     PWSTR p;
37     WCHAR wszFilePath[MAX_PATH];
38 
39     // Get the full path to our EXE file.
40     if (!GetModuleFileNameW(NULL, wszFilePath, _countof(wszFilePath)))
41     {
42         DPRINT("GetModuleFileNameW failed with error %lu!\n", GetLastError());
43         return;
44     }
45 
46     // Replace the extension.
47     p = wcsrchr(wszFilePath, L'.');
48     if (!p)
49     {
50         DPRINT("File path has no file extension: %S\n", wszFilePath);
51         return;
52     }
53 
54     wcscpy(p, L".dll");
55     cbDLLPath = (lstrlenW(wszFilePath) + 1) * sizeof(WCHAR);
56 
57     // Create a snapshot of the currently running processes.
58     hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
59     if (hSnapshot == INVALID_HANDLE_VALUE)
60     {
61         DPRINT("CreateToolhelp32Snapshot failed with error %lu!\n", GetLastError());
62         return;
63     }
64 
65     // Enumerate through all running processes.
66     pe.dwSize = sizeof(pe);
67     if (!Process32FirstW(hSnapshot, &pe))
68     {
69         DPRINT("Process32FirstW failed with error %lu!\n", GetLastError());
70         return;
71     }
72 
73     do
74     {
75         // Check if this is the spooler server process.
76         if (_wcsicmp(pe.szExeFile, L"spoolsv.exe") != 0)
77             continue;
78 
79         // Open a handle to the process.
80         hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID);
81         if (!hProcess)
82         {
83             DPRINT("OpenProcess failed with error %lu!\n", GetLastError());
84             return;
85         }
86 
87         // Get the address of LoadLibraryW.
88         pLoadLibraryAddress = (PVOID)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW");
89         if (!pLoadLibraryAddress)
90         {
91             DPRINT("GetProcAddress failed with error %lu!\n", GetLastError());
92             return;
93         }
94 
95         // Allocate memory for the DLL path in the spooler process.
96         pLoadLibraryArgument = VirtualAllocEx(hProcess, NULL, cbDLLPath, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
97         if (!pLoadLibraryArgument)
98         {
99             DPRINT("VirtualAllocEx failed with error %lu!\n", GetLastError());
100             return;
101         }
102 
103         // Write the DLL path to the process memory.
104         if (!WriteProcessMemory(hProcess, pLoadLibraryArgument, wszFilePath, cbDLLPath, NULL))
105         {
106             DPRINT("WriteProcessMemory failed with error %lu!\n", GetLastError());
107             return;
108         }
109 
110         // Create a new thread in the spooler process that calls LoadLibraryW as the start routine with our DLL as the argument.
111         // This effectively injects our DLL into the spooler process and we can inspect localspl.dll there just like the spooler.
112         hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadLibraryAddress, pLoadLibraryArgument, 0, NULL);
113         if (!hThread)
114         {
115             DPRINT("CreateRemoteThread failed with error %lu!\n", GetLastError());
116             return;
117         }
118 
119         CloseHandle(hThread);
120         break;
121     }
122     while (Process32NextW(hSnapshot, &pe));
123 }
124 
125 static DWORD WINAPI
126 _ServiceControlHandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext)
127 {
128     return NO_ERROR;
129 }
130 
131 static void WINAPI
132 _ServiceMain(DWORD dwArgc, LPWSTR* lpszArgv)
133 {
134     SERVICE_STATUS_HANDLE hServiceStatus;
135     SERVICE_STATUS ServiceStatus;
136 
137     UNREFERENCED_PARAMETER(dwArgc);
138     UNREFERENCED_PARAMETER(lpszArgv);
139 
140     // Register our service for control.
141     hServiceStatus = RegisterServiceCtrlHandlerExW(SERVICE_NAME, _ServiceControlHandlerEx, NULL);
142 
143     // Report SERVICE_RUNNING status.
144     ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
145     ServiceStatus.dwServiceSpecificExitCode = 0;
146     ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
147     ServiceStatus.dwWaitHint = 4000;
148     ServiceStatus.dwWin32ExitCode = NO_ERROR;
149     ServiceStatus.dwCurrentState = SERVICE_RUNNING;
150     SetServiceStatus(hServiceStatus, &ServiceStatus);
151 
152     // Do our funky crazy stuff.
153     _DoDLLInjection();
154 
155     // Our work is done.
156     ServiceStatus.dwCurrentState = SERVICE_STOPPED;
157     SetServiceStatus(hServiceStatus, &ServiceStatus);
158 }
159 
160 START_TEST(service)
161 {
162     int argc;
163     char** argv;
164 
165 #if defined(_M_AMD64)
166     if (!winetest_interactive)
167     {
168         skip("ROSTESTS-366: Skipping localspl_apitest:service because it hangs on Windows Server 2003 x64-Testbot. Set winetest_interactive to run it anyway.\n");
169         return;
170     }
171 #endif
172 
173     SERVICE_TABLE_ENTRYW ServiceTable[] =
174     {
175         { SERVICE_NAME, _ServiceMain },
176         { NULL, NULL }
177     };
178 
179     // This is no real test, but an easy way to integrate the service handler routines into the API-Test executable.
180     // Therefore, bail out if someone tries to run "service" as a usual test.
181     argc = winetest_get_mainargs(&argv);
182     if (argc != 3)
183         return;
184 
185     // If we have exactly 3 arguments, we're run as a service, so initialize the corresponding service handler functions.
186     StartServiceCtrlDispatcherW(ServiceTable);
187 
188     // Prevent the testing framework from outputting a "0 tests executed" line here.
189     ExitProcess(0);
190 }
191