1 /* 2 * PROJECT: ReactOS Application compatibility module 3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) 4 * PURPOSE: Registry layer manipulation functions 5 * COPYRIGHT: Copyright 2015-2019 Mark Jansen (mark.jansen@reactos.org) 6 */ 7 8 #define WIN32_NO_STATUS 9 #include "windef.h" 10 #include "winbase.h" 11 #include "strsafe.h" 12 #include <ntndk.h> 13 #include "apphelp.h" 14 15 #define GPLK_USER 1 16 #define GPLK_MACHINE 2 17 #define MAX_LAYER_LENGTH 256 18 #define LAYER_APPLY_TO_SYSTEM_EXES 1 19 #define LAYER_UNK_FLAG2 2 20 21 #ifndef REG_SZ 22 #define REG_SZ 1 23 #endif 24 25 #if defined(__GNUC__) 26 #define APPCOMPAT_LAYER_KEY (const WCHAR[]){'\\','S','o','f','t','w','a','r','e','\\','M','i','c','r','o','s','o','f','t','\\','W','i','n','d','o','w','s',' ','N','T','\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\','A','p','p','C','o','m','p','a','t','F','l','a','g','s','\\','L','a','y','e','r','s',0} 27 #define REGISTRY_MACHINE (const WCHAR[]){'\\','R','e','g','i','s','t','r','y','\\','M','a','c','h','i','n','e',0} 28 #define SPACE_ONLY (const WCHAR[]){' ',0} 29 #define DISALLOWED_LAYER_CHARS (const WCHAR[]){' ','#','!',0} 30 #define LAYER_SEPARATORS (const WCHAR[]){' ','\t',0} 31 #define SIGN_MEDIA_FMT (const WCHAR[]){'S','I','G','N','.','M','E','D','I','A','=','%','X',' ','%','s',0} 32 33 #else 34 #define APPCOMPAT_LAYER_KEY L"\\Software\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers" 35 #define REGISTRY_MACHINE L"\\Registry\\Machine" 36 #define SPACE_ONLY L" " 37 #define DISALLOWED_LAYER_CHARS L" #!" 38 #define LAYER_SEPARATORS L" \t" 39 #define SIGN_MEDIA_FMT L"SIGN.MEDIA=%X %s" 40 #endif 41 42 /* Fixme: use RTL_UNICODE_STRING_BUFFER */ 43 typedef struct SDB_TMP_STR 44 { 45 UNICODE_STRING Str; 46 WCHAR FixedBuffer[MAX_PATH]; 47 } SDB_TMP_STR, *PSDB_TMP_STR; 48 49 void SdbpInitTempStr(PSDB_TMP_STR String) 50 { 51 String->Str.Buffer = String->FixedBuffer; 52 String->Str.Length = 0; 53 String->Str.MaximumLength = sizeof(String->FixedBuffer); 54 } 55 56 void SdbpFreeTempStr(PSDB_TMP_STR String) 57 { 58 if (String->Str.Buffer != String->FixedBuffer) 59 { 60 SdbFree(String->Str.Buffer); 61 } 62 } 63 64 void SdbpResizeTempStr(PSDB_TMP_STR String, WORD newLength) 65 { 66 if (newLength > String->Str.MaximumLength) 67 { 68 SdbpFreeTempStr(String); 69 String->Str.MaximumLength = newLength * sizeof(WCHAR); 70 String->Str.Buffer = SdbAlloc(String->Str.MaximumLength); 71 String->Str.Length = 0; 72 } 73 } 74 75 BOOL SdbpGetLongPathName(PCWSTR wszPath, PSDB_TMP_STR Result) 76 { 77 DWORD max = Result->Str.MaximumLength / 2; 78 DWORD ret = GetLongPathNameW(wszPath, Result->Str.Buffer, max); 79 if (ret) 80 { 81 if (ret >= max) 82 { 83 SdbpResizeTempStr(Result, ret); 84 max = Result->Str.MaximumLength / 2; 85 ret = GetLongPathNameW(wszPath, Result->Str.Buffer, max); 86 } 87 if (ret && ret < max) 88 { 89 Result->Str.Length = ret * 2; 90 return TRUE; 91 } 92 } 93 SHIM_ERR("Failed to convert short path to long path error 0x%lx\n", GetLastError()); 94 return FALSE; 95 } 96 97 BOOL SdbpIsPathOnRemovableMedia(PCWSTR Path) 98 { 99 WCHAR tmp[] = { 'A',':','\\',0 }; 100 ULONG type; 101 if (!Path || Path[0] == UNICODE_NULL) 102 { 103 SHIM_ERR("Invalid argument\n"); 104 return FALSE; 105 } 106 switch (Path[1]) 107 { 108 case L':': 109 break; 110 case L'\\': 111 SHIM_INFO("\"%S\" is a network path.\n", Path); 112 return FALSE; 113 default: 114 SHIM_INFO("\"%S\" not a full path we can operate on.\n", Path); 115 return FALSE; 116 } 117 tmp[0] = Path[0]; 118 type = GetDriveTypeW(tmp); 119 120 return type == DRIVE_REMOVABLE || type == DRIVE_CDROM; 121 } 122 123 /* Convert a path on removable media to 'SIGN.MEDIA=%X filename' */ 124 BOOL SdbpBuildSignMediaId(PSDB_TMP_STR LongPath) 125 { 126 SDB_TMP_STR Scratch; 127 PWCHAR Ptr; 128 129 SdbpInitTempStr(&Scratch); 130 SdbpResizeTempStr(&Scratch, LongPath->Str.Length / sizeof(WCHAR) + 30); 131 StringCbCopyNW(Scratch.Str.Buffer, Scratch.Str.MaximumLength, LongPath->Str.Buffer, LongPath->Str.Length); 132 Ptr = wcsrchr(LongPath->Str.Buffer, '\\'); 133 if (Ptr) 134 { 135 HANDLE FindHandle; 136 WIN32_FIND_DATAW FindData; 137 Ptr[1] = '*'; 138 Ptr[2] = '\0'; 139 FindHandle = FindFirstFileW(LongPath->Str.Buffer, &FindData); 140 if (FindHandle != INVALID_HANDLE_VALUE) 141 { 142 DWORD SignMedia = 0; 143 do 144 { 145 if (!(FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && FindData.nFileSizeLow) 146 SignMedia = SignMedia << 1 ^ FindData.nFileSizeLow; 147 } while (FindNextFileW(FindHandle, &FindData)); 148 149 FindClose(FindHandle); 150 SdbpResizeTempStr(LongPath, (LongPath->Str.Length >> 1) + 20); 151 StringCbPrintfW(LongPath->Str.Buffer, LongPath->Str.MaximumLength, SIGN_MEDIA_FMT, SignMedia, Scratch.Str.Buffer + 3); 152 LongPath->Str.Length = (USHORT)SdbpStrlen(LongPath->Str.Buffer) * sizeof(WCHAR); 153 SdbpFreeTempStr(&Scratch); 154 return TRUE; 155 } 156 } 157 SdbpFreeTempStr(&Scratch); 158 SdbpFreeTempStr(LongPath); 159 return FALSE; 160 } 161 162 /* Convert a given path to a long or media path */ 163 BOOL SdbpResolvePath(PSDB_TMP_STR LongPath, PCWSTR wszPath) 164 { 165 SdbpInitTempStr(LongPath); 166 if (!SdbpGetLongPathName(wszPath, LongPath)) 167 { 168 SdbpFreeTempStr(LongPath); 169 return FALSE; 170 } 171 if (SdbpIsPathOnRemovableMedia(LongPath->Str.Buffer)) 172 { 173 return SdbpBuildSignMediaId(LongPath); 174 } 175 return TRUE; 176 } 177 178 static ACCESS_MASK g_QueryFlag = 0xffffffff; 179 ACCESS_MASK Wow64QueryFlag(void) 180 { 181 if (g_QueryFlag == 0xffffffff) 182 { 183 ULONG_PTR wow64_ptr = 0; 184 NTSTATUS Status = NtQueryInformationProcess(NtCurrentProcess(), ProcessWow64Information, &wow64_ptr, sizeof(wow64_ptr), NULL); 185 g_QueryFlag = (NT_SUCCESS(Status) && wow64_ptr != 0) ? KEY_WOW64_64KEY : 0; 186 } 187 return g_QueryFlag; 188 } 189 190 NTSTATUS SdbpOpenKey(PUNICODE_STRING FullPath, BOOL bMachine, ACCESS_MASK Access, PHANDLE KeyHandle) 191 { 192 UNICODE_STRING BasePath; 193 const WCHAR* LayersKey = APPCOMPAT_LAYER_KEY; 194 OBJECT_ATTRIBUTES ObjectLayer = RTL_INIT_OBJECT_ATTRIBUTES(FullPath, OBJ_CASE_INSENSITIVE); 195 NTSTATUS Status; 196 FullPath->Buffer = NULL; 197 FullPath->Length = FullPath->MaximumLength = 0; 198 if (bMachine) 199 { 200 RtlInitUnicodeString(&BasePath, REGISTRY_MACHINE); 201 } 202 else 203 { 204 Status = RtlFormatCurrentUserKeyPath(&BasePath); 205 if (!NT_SUCCESS(Status)) 206 { 207 SHIM_ERR("Unable to acquire user registry key, Error: 0x%lx\n", Status); 208 return Status; 209 } 210 } 211 FullPath->MaximumLength = (USHORT)(BasePath.Length + SdbpStrsize(LayersKey)); 212 FullPath->Buffer = SdbAlloc(FullPath->MaximumLength); 213 FullPath->Length = 0; 214 RtlAppendUnicodeStringToString(FullPath, &BasePath); 215 if (!bMachine) 216 RtlFreeUnicodeString(&BasePath); 217 RtlAppendUnicodeToString(FullPath, LayersKey); 218 219 Status = NtOpenKey(KeyHandle, Access | Wow64QueryFlag(), &ObjectLayer); 220 if (!NT_SUCCESS(Status)) 221 { 222 SHIM_ERR("Unable to open Key \"%wZ\" Status 0x%lx\n", FullPath, Status); 223 SdbFree(FullPath->Buffer); 224 FullPath->Buffer = NULL; 225 } 226 return Status; 227 } 228 229 230 BOOL SdbpGetPermLayersInternal(PUNICODE_STRING FullPath, PWSTR pwszLayers, PDWORD pdwBytes, BOOL bMachine) 231 { 232 UNICODE_STRING FullKey; 233 ULONG ValueBuffer[(MAX_LAYER_LENGTH * sizeof(WCHAR) + sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(ULONG) - 1) / sizeof(ULONG)]; 234 PKEY_VALUE_PARTIAL_INFORMATION PartialInfo = (PVOID)ValueBuffer; 235 ULONG Length = 0; 236 HANDLE KeyHandle; 237 NTSTATUS Status; 238 239 Status = SdbpOpenKey(&FullKey, bMachine, KEY_QUERY_VALUE, &KeyHandle); 240 if (NT_SUCCESS(Status)) 241 { 242 Status = NtQueryValueKey(KeyHandle, FullPath, KeyValuePartialInformation, PartialInfo, sizeof(ValueBuffer), &Length); 243 if (NT_SUCCESS(Status)) 244 { 245 StringCbCopyNW(pwszLayers, *pdwBytes, (PCWSTR)PartialInfo->Data, PartialInfo->DataLength); 246 *pdwBytes = PartialInfo->DataLength; 247 } 248 else 249 { 250 SHIM_INFO("Failed to read value info from Key \"%wZ\" Status 0x%lx\n", &FullKey, Status); 251 } 252 NtClose(KeyHandle); 253 SdbFree(FullKey.Buffer); 254 } 255 return NT_SUCCESS(Status); 256 } 257 258 BOOL SdbDeletePermLayerKeys(PCWSTR wszPath, BOOL bMachine) 259 { 260 UNICODE_STRING FullKey; 261 SDB_TMP_STR LongPath; 262 HANDLE KeyHandle; 263 NTSTATUS Status; 264 265 if (!SdbpResolvePath(&LongPath, wszPath)) 266 return FALSE; 267 268 Status = SdbpOpenKey(&FullKey, bMachine, KEY_SET_VALUE, &KeyHandle); 269 if (NT_SUCCESS(Status)) 270 { 271 Status = NtDeleteValueKey(KeyHandle, &LongPath.Str); 272 if (!NT_SUCCESS(Status)) 273 { 274 SHIM_INFO("Failed to delete value from Key \"%wZ\" Status 0x%lx\n", &FullKey, Status); 275 /* This is what we want, so if the key didnt exist, we should not fail :) */ 276 if (Status == STATUS_OBJECT_NAME_NOT_FOUND) 277 Status = STATUS_SUCCESS; 278 } 279 NtClose(KeyHandle); 280 SdbFree(FullKey.Buffer); 281 } 282 SdbpFreeTempStr(&LongPath); 283 return NT_SUCCESS(Status); 284 } 285 286 BOOL SdbpMatchLayer(PCWSTR start, PCWSTR end, PCWSTR compare) 287 { 288 size_t len; 289 if (!end) 290 return !wcsicmp(start, compare); 291 len = end - start; 292 return wcslen(compare) == len && !_wcsnicmp(start, compare, len); 293 } 294 295 BOOL SdbpAppendLayer(PWSTR target, DWORD len, PCWSTR layer, PCWSTR end) 296 { 297 NTSTATUS Status = STATUS_SUCCESS; 298 if (target[0]) 299 Status = StringCbCatW(target, len, SPACE_ONLY); 300 301 if (NT_SUCCESS(Status)) 302 { 303 if (end) 304 Status = StringCbCatNW(target, len, layer, (end - layer) * sizeof(WCHAR)); 305 else 306 Status = StringCbCatW(target, len, layer); 307 } 308 309 return NT_SUCCESS(Status); 310 } 311 312 313 /** 314 * Determine if we allow permission layers to apply on this file. 315 * 316 * @param [in] Path Full pathname of the file, only the drive part is used. 317 * 318 * @return TRUE if we allow permission layer, FALSE if not. 319 */ 320 BOOL WINAPI AllowPermLayer(PCWSTR Path) 321 { 322 WCHAR tmp[] = { 'A',':','\\', 0 }; 323 ULONG type; 324 if (!Path) 325 { 326 SHIM_ERR("Invalid argument\n"); 327 return FALSE; 328 } 329 switch (Path[1]) 330 { 331 case L':': 332 break; 333 case L'\\': 334 SHIM_INFO("\"%S\" is a network path.\n", Path); 335 return FALSE; 336 default: 337 SHIM_INFO("\"%S\" not a full path we can operate on.\n", Path); 338 return FALSE; 339 } 340 tmp[0] = Path[0]; 341 type = GetDriveTypeW(tmp); 342 if (type == DRIVE_REMOTE) 343 { 344 /* The logging here indicates that it does not like a CDROM or removable media, but it only 345 seems to bail out on a media that reports it is remote... 346 I have included correct logging, I doubt anyone would parse the logging, so this shouldnt break anything. */ 347 SHIM_INFO("\"%S\" is on a remote drive.\n", Path); 348 return FALSE; 349 } 350 return TRUE; 351 } 352 353 /** 354 * Read the layers specified for the application. 355 * 356 * @param [in] wszPath Full pathname of the file. 357 * @param [out] pwszLayers On return, the layers set on the file. 358 * @param pdwBytes The size of the pwszLayers buffer in bytes, and on return the size of 359 * the data written (in bytes) 360 * @param [in] dwFlags The flags, [GPLK_USER | GPLK_MACHINE]. 361 * 362 * @return TRUE if it succeeds, FALSE if it fails. 363 */ 364 BOOL WINAPI SdbGetPermLayerKeys(PCWSTR wszPath, PWSTR pwszLayers, PDWORD pdwBytes, DWORD dwFlags) 365 { 366 BOOL Result = FALSE; 367 SDB_TMP_STR LongPath; 368 DWORD dwBytes, dwTotal = 0; 369 if (!wszPath || !pdwBytes) 370 { 371 SHIM_ERR("NULL parameter passed for wszPath or pdwBytes.\n"); 372 return FALSE; 373 } 374 375 if (!SdbpResolvePath(&LongPath, wszPath)) 376 return FALSE; 377 dwBytes = *pdwBytes; 378 if (dwFlags & GPLK_MACHINE) 379 { 380 if (SdbpGetPermLayersInternal(&LongPath.Str, pwszLayers, &dwBytes, TRUE)) 381 { 382 Result = TRUE; 383 dwTotal = dwBytes - sizeof(WCHAR); /* Compensate for the nullterm. */ 384 pwszLayers += dwTotal / sizeof(WCHAR); 385 dwBytes = *pdwBytes - dwBytes; 386 if (dwFlags & GPLK_USER) 387 { 388 *(pwszLayers++) = L' '; 389 *pwszLayers = L'\0'; 390 dwBytes -= sizeof(WCHAR); 391 dwTotal += sizeof(WCHAR); 392 } 393 } 394 } 395 if (dwFlags & GPLK_USER) 396 { 397 if (SdbpGetPermLayersInternal(&LongPath.Str, pwszLayers, &dwBytes, FALSE)) 398 { 399 Result = TRUE; 400 dwTotal += dwBytes - sizeof(WCHAR); /* Compensate for the nullterm. */ 401 } 402 else if (dwTotal > 0 && pwszLayers[-1] == L' ') 403 { 404 pwszLayers[-1] = '\0'; 405 dwTotal -= sizeof(WCHAR); 406 } 407 } 408 if (dwTotal) 409 dwTotal += sizeof(WCHAR); 410 *pdwBytes = dwTotal; 411 SdbpFreeTempStr(&LongPath); 412 return Result; 413 } 414 415 /** 416 * Set or clear the Layer key. 417 * 418 * @param [in] wszPath Full pathname of the file. 419 * @param [in] wszLayers The layers to add (space separated), or an empty string / NULL to 420 * remove all layers. 421 * @param [in] bMachine TRUE to machine. 422 * 423 * @return TRUE if it succeeds, FALSE if it fails. 424 */ 425 BOOL WINAPI SdbSetPermLayerKeys(PCWSTR wszPath, PCWSTR wszLayers, BOOL bMachine) 426 { 427 UNICODE_STRING FullKey; 428 SDB_TMP_STR LongPath; 429 HANDLE KeyHandle; 430 NTSTATUS Status; 431 432 if (!wszLayers || *wszLayers == '\0') 433 return SdbDeletePermLayerKeys(wszPath, bMachine); 434 435 if (!SdbpResolvePath(&LongPath, wszPath)) 436 return FALSE; 437 438 Status = SdbpOpenKey(&FullKey, bMachine, KEY_SET_VALUE, &KeyHandle); 439 if (NT_SUCCESS(Status)) 440 { 441 Status = NtSetValueKey(KeyHandle, &LongPath.Str, 0, REG_SZ, (PVOID)wszLayers, SdbpStrsize(wszLayers)); 442 if (!NT_SUCCESS(Status)) 443 { 444 SHIM_INFO("Failed to write a value to Key \"%wZ\" Status 0x%lx\n", &FullKey, Status); 445 if (Status == STATUS_OBJECT_NAME_NOT_FOUND) 446 Status = STATUS_SUCCESS; 447 } 448 NtClose(KeyHandle); 449 SdbFree(FullKey.Buffer); 450 } 451 SdbpFreeTempStr(&LongPath); 452 return NT_SUCCESS(Status); 453 } 454 455 /** 456 * Adds or removes a single layer entry. 457 * 458 * @param [in] wszPath Full pathname of the file. 459 * @param [in] wszLayer The layer to add or remove. 460 * @param [in] dwFlags Additional flags to add / remove [LAYER_APPLY_TO_SYSTEM_EXES | ???]. 461 * @param [in] bMachine When TRUE, the setting applies to all users, when FALSE only applies 462 * to the current user. 463 * @param [in] bEnable TRUE to enable, FALSE to disable a layer / flag specified. 464 * 465 * @return TRUE if it succeeds, FALSE if it fails. 466 */ 467 BOOL WINAPI SetPermLayerState(PCWSTR wszPath, PCWSTR wszLayer, DWORD dwFlags, BOOL bMachine, BOOL bEnable) 468 { 469 WCHAR fullLayer[MAX_LAYER_LENGTH] = { 0 }; 470 WCHAR newLayer[MAX_LAYER_LENGTH] = { 0 }; 471 DWORD dwBytes = sizeof(fullLayer), dwWriteFlags = 0; 472 PWSTR start, p; 473 474 if (!wszLayer) 475 { 476 SHIM_ERR("Invalid argument\n"); 477 return FALSE; 478 } 479 if (dwFlags & ~(LAYER_APPLY_TO_SYSTEM_EXES | LAYER_UNK_FLAG2)) 480 { 481 SHIM_ERR("Invalid flags\n"); 482 return FALSE; 483 } 484 p = wcspbrk(wszLayer, DISALLOWED_LAYER_CHARS); 485 if (p) 486 { 487 switch (*p) 488 { 489 case ' ': 490 SHIM_ERR("Only one layer can be passed in at a time.\n"); 491 return FALSE; 492 case '#': 493 case '!': 494 SHIM_ERR("Flags cannot be passed in with the layer name.\n"); 495 return FALSE; 496 } 497 } 498 if (!SdbGetPermLayerKeys(wszPath, fullLayer, &dwBytes, bMachine ? GPLK_MACHINE : GPLK_USER)) 499 { 500 fullLayer[0] = '\0'; 501 dwBytes = sizeof(fullLayer); 502 } 503 504 start = fullLayer; 505 while (*start == '!' || *start == '#' || *start == ' ' || *start == '\t') 506 { 507 if (*start == '#') 508 dwWriteFlags |= LAYER_APPLY_TO_SYSTEM_EXES; 509 else if (*start == '!') 510 dwWriteFlags |= LAYER_UNK_FLAG2; 511 start++; 512 } 513 if (bEnable) 514 dwWriteFlags |= dwFlags; 515 else 516 dwWriteFlags &= ~dwFlags; 517 518 p = newLayer; 519 if (dwWriteFlags & LAYER_UNK_FLAG2) 520 *(p++) = '!'; 521 if (dwWriteFlags & LAYER_APPLY_TO_SYSTEM_EXES) 522 *(p++) = '#'; 523 524 do 525 { 526 while (*start == ' ' || *start == '\t') 527 ++start; 528 529 if (*start == '\0') 530 break; 531 p = wcspbrk(start, LAYER_SEPARATORS); 532 if (!SdbpMatchLayer(start, p, wszLayer)) 533 { 534 SdbpAppendLayer(newLayer, sizeof(newLayer), start, p); 535 } 536 start = p + 1; 537 } while (p); 538 539 if (bEnable && wszLayer[0]) 540 { 541 SdbpAppendLayer(newLayer, sizeof(newLayer), wszLayer, NULL); 542 } 543 544 return SdbSetPermLayerKeys(wszPath, newLayer, bMachine); 545 } 546