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
CallCacheControl(UNICODE_STRING * PathName,BOOLEAN WithMapping,APPHELPCACHESERVICECLASS Service)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
InitEnv(UNICODE_STRING * PathName)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
CheckValidation(UNICODE_STRING * PathName)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
RequestAddition(SC_HANDLE service_handle,BOOLEAN WithMapping)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
RunApphelpCacheControlTests(SC_HANDLE service_handle)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
RegisterInShimCache(BOOLEAN WithMapping)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
service_handler(DWORD ctrl,DWORD event_type,void * event_data,void * context)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
service_main(DWORD argc,char ** argv)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
InstallService(SC_HANDLE scm_handle)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
WaitService(SC_HANDLE service_handle,DWORD Status,SERVICE_STATUS_PROCESS * ssp)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
RunTest()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
START_TEST(NtApphelpCacheControl)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