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