xref: /reactos/dll/appcompat/apphelp/layer.c (revision 53221834)
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