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