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
SdbpInitTempStr(PSDB_TMP_STR String)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
SdbpFreeTempStr(PSDB_TMP_STR String)56 void SdbpFreeTempStr(PSDB_TMP_STR String)
57 {
58 if (String->Str.Buffer != String->FixedBuffer)
59 {
60 SdbFree(String->Str.Buffer);
61 }
62 }
63
SdbpResizeTempStr(PSDB_TMP_STR String,WORD newLength)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
SdbpGetLongPathName(PCWSTR wszPath,PSDB_TMP_STR Result)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
SdbpIsPathOnRemovableMedia(PCWSTR Path)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' */
SdbpBuildSignMediaId(PSDB_TMP_STR LongPath)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 */
SdbpResolvePath(PSDB_TMP_STR LongPath,PCWSTR wszPath)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;
Wow64QueryFlag(void)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
SdbpOpenKey(PUNICODE_STRING FullPath,BOOL bMachine,ACCESS_MASK Access,PHANDLE KeyHandle)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
SdbpGetPermLayersInternal(PUNICODE_STRING FullPath,PWSTR pwszLayers,PDWORD pdwBytes,BOOL bMachine)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
SdbDeletePermLayerKeys(PCWSTR wszPath,BOOL bMachine)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
SdbpMatchLayer(PCWSTR start,PCWSTR end,PCWSTR compare)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
SdbpAppendLayer(PWSTR target,DWORD len,PCWSTR layer,PCWSTR end)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 */
AllowPermLayer(PCWSTR Path)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 */
SdbGetPermLayerKeys(PCWSTR wszPath,PWSTR pwszLayers,PDWORD pdwBytes,DWORD dwFlags)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 */
SdbSetPermLayerKeys(PCWSTR wszPath,PCWSTR wszLayers,BOOL bMachine)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 */
SetPermLayerState(PCWSTR wszPath,PCWSTR wszLayer,DWORD dwFlags,BOOL bMachine,BOOL bEnable)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