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