1 /*
2  * PROJECT:         ReactOS API Tests
3  * LICENSE:         LGPL - See COPYING.LIB in the top level directory
4  * PURPOSE:         Tests for SHIM engine caching.
5  * PROGRAMMER:      Mark Jansen
6  */
7 
8 #include "precomp.h"
9 
10 #include <winsvc.h>
11 
12 enum ServiceCommands
13 {
14     RegisterShimCacheWithHandle = 128,
15     RegisterShimCacheWithoutHandle = 129,
16 };
17 
18 static NTSTATUS (NTAPI *pNtApphelpCacheControl)(APPHELPCACHESERVICECLASS, PAPPHELP_CACHE_SERVICE_LOOKUP);
19 
20 NTSTATUS CallCacheControl(UNICODE_STRING* PathName, BOOLEAN WithMapping, APPHELPCACHESERVICECLASS Service)
21 {
22     APPHELP_CACHE_SERVICE_LOOKUP CacheEntry = { {0} };
23     NTSTATUS Status;
24     CacheEntry.ImageName = *PathName;
25     if (WithMapping)
26     {
27         OBJECT_ATTRIBUTES LocalObjectAttributes;
28         IO_STATUS_BLOCK IoStatusBlock;
29         InitializeObjectAttributes(&LocalObjectAttributes, PathName,
30             OBJ_CASE_INSENSITIVE, NULL, NULL);
31         Status = NtOpenFile(&CacheEntry.ImageHandle,
32                     SYNCHRONIZE | FILE_READ_ATTRIBUTES | FILE_READ_DATA | FILE_EXECUTE,
33                     &LocalObjectAttributes, &IoStatusBlock,
34                     FILE_SHARE_READ | FILE_SHARE_DELETE,
35                     FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE);
36         ok_ntstatus(Status, STATUS_SUCCESS);
37     }
38     else
39     {
40         CacheEntry.ImageHandle = INVALID_HANDLE_VALUE;
41     }
42     Status = pNtApphelpCacheControl(Service, &CacheEntry);
43     if (CacheEntry.ImageHandle != INVALID_HANDLE_VALUE)
44         NtClose(CacheEntry.ImageHandle);
45     return Status;
46 }
47 
48 int InitEnv(UNICODE_STRING* PathName)
49 {
50     NTSTATUS Status = CallCacheControl(PathName, FALSE, ApphelpCacheServiceRemove);
51     if (Status == STATUS_INVALID_PARAMETER)
52     {
53         /* Windows Vista+ has a different layout for APPHELP_CACHE_SERVICE_LOOKUP */
54         return 0;
55     }
56     ok(Status == STATUS_SUCCESS || Status == STATUS_NOT_FOUND,
57         "Wrong value for Status, expected: SUCCESS or NOT_FOUND, got: 0x%lx\n",
58         Status);
59     return 1;
60 }
61 
62 void CheckValidation(UNICODE_STRING* PathName)
63 {
64     APPHELP_CACHE_SERVICE_LOOKUP CacheEntry = { {0} };
65     NTSTATUS Status;
66 
67     /* Validate the handling of a NULL pointer */
68     Status = pNtApphelpCacheControl(ApphelpCacheServiceRemove, NULL);
69     ok_ntstatus(Status, STATUS_INVALID_PARAMETER);
70     Status = pNtApphelpCacheControl(ApphelpCacheServiceLookup, NULL);
71     ok_ntstatus(Status, STATUS_INVALID_PARAMETER);
72 
73     /* Validate the handling of a NULL pointer inside the struct */
74     Status = pNtApphelpCacheControl(ApphelpCacheServiceRemove, &CacheEntry);
75     ok_ntstatus(Status, STATUS_INVALID_PARAMETER);
76     Status = pNtApphelpCacheControl(ApphelpCacheServiceLookup, &CacheEntry);
77     ok_ntstatus(Status, STATUS_INVALID_PARAMETER);
78 
79     /* Just call the dump function */
80     Status = pNtApphelpCacheControl(ApphelpCacheServiceDump, NULL);
81     ok_ntstatus(Status, STATUS_SUCCESS);
82 
83     /* Validate the handling of an invalid handle inside the struct */
84     CacheEntry.ImageName = *PathName;
85     CacheEntry.ImageHandle = (HANDLE)2;
86     Status = pNtApphelpCacheControl(ApphelpCacheServiceLookup, &CacheEntry);
87     ok_ntstatus(Status, STATUS_NOT_FOUND);
88 
89     /* Validate the handling of an invalid service number */
90     Status = pNtApphelpCacheControl(999, NULL);
91     ok_ntstatus(Status, STATUS_INVALID_PARAMETER);
92     Status = pNtApphelpCacheControl(999, &CacheEntry);
93     ok_ntstatus(Status, STATUS_INVALID_PARAMETER);
94 }
95 
96 static BOOLEAN RequestAddition(SC_HANDLE service_handle, BOOLEAN WithMapping)
97 {
98     SERVICE_STATUS Status;
99     ControlService(service_handle, WithMapping ? RegisterShimCacheWithHandle :
100                     RegisterShimCacheWithoutHandle, &Status);
101     /* TODO: how to get a return code from the service? */
102     return TRUE;
103 }
104 
105 static void RunApphelpCacheControlTests(SC_HANDLE service_handle)
106 {
107     WCHAR szPath[MAX_PATH];
108     UNICODE_STRING ntPath;
109     BOOLEAN Result;
110     NTSTATUS Status;
111     APPHELP_CACHE_SERVICE_LOOKUP CacheEntry;
112 
113     GetModuleFileNameW(NULL, szPath, sizeof(szPath) / sizeof(szPath[0]));
114     Result = RtlDosPathNameToNtPathName_U(szPath, &ntPath, NULL, NULL);
115     ok(Result == TRUE, "RtlDosPathNameToNtPathName_U\n");
116     if (!InitEnv(&ntPath))
117     {
118         skip("NtApphelpCacheControl expects a different structure layout\n");
119         return;
120     }
121     /* At this point we have made sure that our binary is not present in the cache,
122         and that the NtApphelpCacheControl function expects the struct layout we use. */
123     CheckValidation(&ntPath);
124 
125     /* We expect not to find it */
126     Status = CallCacheControl(&ntPath, TRUE, ApphelpCacheServiceLookup);
127     ok_ntstatus(Status, STATUS_NOT_FOUND);
128     Status = CallCacheControl(&ntPath, FALSE, ApphelpCacheServiceLookup);
129     ok_ntstatus(Status, STATUS_NOT_FOUND);
130 
131     /* First we add our process without a file handle (so it will be registered without file info) */
132     RequestAddition(service_handle, FALSE);
133 
134     /* now we try to find it without validating file info */
135     Status = CallCacheControl(&ntPath, FALSE, ApphelpCacheServiceLookup);
136     ok_ntstatus(Status, STATUS_SUCCESS);
137     /* when validating file info the cache notices the file is wrong, so it is dropped from the cache */
138     Status = CallCacheControl(&ntPath, TRUE, ApphelpCacheServiceLookup);
139     ok_ntstatus(Status, STATUS_NOT_FOUND);
140     /* making the second check without info also fail. */
141     Status = CallCacheControl(&ntPath, FALSE, ApphelpCacheServiceLookup);
142     ok_ntstatus(Status, STATUS_NOT_FOUND);
143 
144 
145     /* Now we add the file with file info */
146     RequestAddition(service_handle, TRUE);
147 
148     /* so both checks should succeed */
149     Status = CallCacheControl(&ntPath, TRUE, ApphelpCacheServiceLookup);
150     ok_ntstatus(Status, STATUS_SUCCESS);
151     Status = CallCacheControl(&ntPath, FALSE, ApphelpCacheServiceLookup);
152     ok_ntstatus(Status, STATUS_SUCCESS);
153 
154     /* We know the file is in the cache now (assuming previous tests succeeded,
155         let's test invalid handle behavior */
156     CacheEntry.ImageName = ntPath;
157     CacheEntry.ImageHandle = 0;
158     Status = pNtApphelpCacheControl(ApphelpCacheServiceLookup, &CacheEntry);
159     ok_ntstatus(Status, STATUS_NOT_FOUND);
160 
161     /* re-add it for the next test */
162     RequestAddition(service_handle, TRUE);
163     Status = CallCacheControl(&ntPath, TRUE, ApphelpCacheServiceLookup);
164     ok_ntstatus(Status, STATUS_SUCCESS);
165     CacheEntry.ImageHandle = (HANDLE)1;
166     Status = pNtApphelpCacheControl(ApphelpCacheServiceLookup, &CacheEntry);
167     ok_ntstatus(Status, STATUS_NOT_FOUND);
168 
169     /* and again */
170     RequestAddition(service_handle, TRUE);
171     Status = CallCacheControl(&ntPath, TRUE, ApphelpCacheServiceLookup);
172     ok_ntstatus(Status, STATUS_SUCCESS);
173 #ifdef _WIN64
174     CacheEntry.ImageHandle = (HANDLE)0x8000000000000000ULL;
175 #else
176     CacheEntry.ImageHandle = (HANDLE)0x80000000;
177 #endif
178     Status = pNtApphelpCacheControl(ApphelpCacheServiceLookup, &CacheEntry);
179     ok_ntstatus(Status, STATUS_NOT_FOUND);
180 
181     RtlFreeHeap(RtlGetProcessHeap(), 0, ntPath.Buffer);
182 }
183 
184 
185 /* Most service related code was taken from services_winetest:service and modified for usage here
186     The rest came from MSDN */
187 
188 static SERVICE_STATUS_HANDLE (WINAPI *pRegisterServiceCtrlHandlerExA)(LPCSTR,LPHANDLER_FUNCTION_EX,LPVOID);
189 static char service_name[100] = "apphelp_test_service";
190 static HANDLE service_stop_event;
191 static SERVICE_STATUS_HANDLE service_status;
192 
193 static BOOLEAN RegisterInShimCache(BOOLEAN WithMapping)
194 {
195     WCHAR szPath[MAX_PATH];
196     UNICODE_STRING ntPath;
197     BOOLEAN Result;
198     NTSTATUS Status;
199     GetModuleFileNameW(NULL, szPath, sizeof(szPath) / sizeof(szPath[0]));
200     Result = RtlDosPathNameToNtPathName_U(szPath, &ntPath, NULL, NULL);
201     if (!Result)
202     {
203         DbgPrint("RegisterInShimCache: RtlDosPathNameToNtPathName_U failed\n");
204         return FALSE;
205     }
206 
207     Status = CallCacheControl(&ntPath, WithMapping, ApphelpCacheServiceUpdate);
208     if (!NT_SUCCESS(Status))
209     {
210         DbgPrint("RegisterInShimCache: CallCacheControl failed\n");
211         RtlFreeHeap(RtlGetProcessHeap(), 0, ntPath.Buffer);
212         return FALSE;
213     }
214     RtlFreeHeap(RtlGetProcessHeap(), 0, ntPath.Buffer);
215     return TRUE;
216 }
217 
218 
219 static DWORD WINAPI service_handler(DWORD ctrl, DWORD event_type, void *event_data, void *context)
220 {
221     SERVICE_STATUS status = {0};
222     status.dwServiceType = SERVICE_WIN32;
223     status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
224 
225     switch(ctrl)
226     {
227     case SERVICE_CONTROL_STOP:
228     case SERVICE_CONTROL_SHUTDOWN:
229         status.dwCurrentState = SERVICE_STOP_PENDING;
230         status.dwControlsAccepted = 0;
231         SetServiceStatus(service_status, &status);
232         SetEvent(service_stop_event);
233         return NO_ERROR;
234     case RegisterShimCacheWithHandle:
235         if (!RegisterInShimCache(TRUE))
236         {
237             /* TODO: how should we communicate a failure? */
238         }
239         break;
240     case RegisterShimCacheWithoutHandle:
241         if (!RegisterInShimCache(FALSE))
242         {
243             /* TODO: how should we communicate a failure? */
244         }
245         break;
246     default:
247         DbgPrint("Unhandled: %d\n", ctrl);
248         break;
249     }
250     status.dwCurrentState = SERVICE_RUNNING;
251     SetServiceStatus(service_status, &status);
252     return NO_ERROR;
253 }
254 
255 static void WINAPI service_main(DWORD argc, char **argv)
256 {
257     SERVICE_STATUS status = {0};
258     service_status = pRegisterServiceCtrlHandlerExA(service_name, service_handler, NULL);
259     if(!service_status)
260         return;
261 
262     status.dwServiceType = SERVICE_WIN32;
263     status.dwCurrentState = SERVICE_RUNNING;
264     status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
265     SetServiceStatus(service_status, &status);
266 
267     WaitForSingleObject(service_stop_event, INFINITE);
268 
269     status.dwCurrentState = SERVICE_STOPPED;
270     status.dwControlsAccepted = 0;
271     SetServiceStatus(service_status, &status);
272 }
273 
274 static SC_HANDLE InstallService(SC_HANDLE scm_handle)
275 {
276     char service_cmd[MAX_PATH+150], *ptr;
277     SC_HANDLE service;
278 
279     ptr = service_cmd + GetModuleFileNameA(NULL, service_cmd, MAX_PATH);
280     strcpy(ptr, " NtApphelpCacheControl service");
281     ptr += strlen(ptr);
282 
283     service = CreateServiceA(scm_handle, service_name, service_name, GENERIC_ALL,
284                              SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE,
285                              service_cmd, NULL, NULL, NULL, NULL, NULL);
286     if (!service)
287     {
288         skip("Could not create helper service\n");
289         return NULL;
290     }
291     return service;
292 }
293 
294 static void WaitService(SC_HANDLE service_handle, DWORD Status, SERVICE_STATUS_PROCESS* ssp)
295 {
296     DWORD dwBytesNeeded;
297     DWORD dwStartTime = GetTickCount();
298     while (ssp->dwCurrentState != Status)
299     {
300         Sleep(40);
301         if (!QueryServiceStatusEx(service_handle, SC_STATUS_PROCESS_INFO,
302             (LPBYTE)ssp, sizeof(SERVICE_STATUS_PROCESS), &dwBytesNeeded ))
303         {
304             ok(0, "QueryServiceStatusEx failed waiting for %lu\n", Status);
305             break;
306         }
307         if ((GetTickCount() - dwStartTime) > 1000)
308         {
309             ok(0, "Timeout waiting for (%lu) from service, is: %lu.\n",
310                 Status, ssp->dwCurrentState);
311             break;
312         }
313     }
314 }
315 
316 static void RunTest()
317 {
318     SC_HANDLE scm_handle = OpenSCManagerA(NULL, NULL, SC_MANAGER_ALL_ACCESS);
319     SC_HANDLE service_handle = InstallService(scm_handle);
320     if (service_handle)
321     {
322         SERVICE_STATUS_PROCESS ssp = {0};
323         BOOL res = StartServiceA(service_handle, 0, NULL);
324         if (res)
325         {
326             WaitService(service_handle, SERVICE_RUNNING, &ssp);
327             RunApphelpCacheControlTests(service_handle);
328             ControlService(service_handle, SERVICE_CONTROL_STOP, (LPSERVICE_STATUS)&ssp);
329             WaitService(service_handle, SERVICE_STOPPED, &ssp);
330         }
331         else
332         {
333             skip("Could not start helper service\n");
334         }
335         DeleteService(service_handle);
336     }
337     CloseServiceHandle(scm_handle);
338 }
339 
340 START_TEST(NtApphelpCacheControl)
341 {
342     char **argv;
343     int argc;
344 
345     pRegisterServiceCtrlHandlerExA = (void*)GetProcAddress(GetModuleHandleA("advapi32.dll"), "RegisterServiceCtrlHandlerExA");
346     if (!pRegisterServiceCtrlHandlerExA)
347     {
348         win_skip("RegisterServiceCtrlHandlerExA not available, skipping tests\n");
349         return;
350     }
351 
352     pNtApphelpCacheControl = (void*)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtApphelpCacheControl");
353     if (!pNtApphelpCacheControl)
354     {
355         win_skip("NtApphelpCacheControl not available, skipping tests\n");
356         return;
357     }
358 
359     argc = winetest_get_mainargs(&argv);
360     if(argc < 3)
361     {
362         RunTest();
363     }
364     else
365     {
366         SERVICE_TABLE_ENTRYA servtbl[] = {
367             {service_name, service_main},
368             {NULL, NULL}
369         };
370         service_stop_event = CreateEventA(NULL, TRUE, FALSE, NULL);
371         StartServiceCtrlDispatcherA(servtbl);
372         Sleep(50);
373         CloseHandle(service_stop_event);
374     }
375 }
376 
377 
378