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-2017 Mark Jansen (mark.jansen@reactos.org)
8 #define WIN32_NO_STATUS
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
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}
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"
42 /* Fixme: use RTL_UNICODE_STRING_BUFFER */
43 typedef struct SDB_TMP_STR
46 WCHAR FixedBuffer
[MAX_PATH
];
47 } SDB_TMP_STR
, *PSDB_TMP_STR
;
49 void SdbpInitTempStr(PSDB_TMP_STR String
)
51 String
->Str
.Buffer
= String
->FixedBuffer
;
52 String
->Str
.Length
= 0;
53 String
->Str
.MaximumLength
= sizeof(String
->FixedBuffer
);
56 void SdbpFreeTempStr(PSDB_TMP_STR String
)
58 if (String
->Str
.Buffer
!= String
->FixedBuffer
)
60 SdbFree(String
->Str
.Buffer
);
64 void SdbpResizeTempStr(PSDB_TMP_STR String
, WORD newLength
)
66 if (newLength
> String
->Str
.MaximumLength
)
68 SdbpFreeTempStr(String
);
69 String
->Str
.MaximumLength
= newLength
* sizeof(WCHAR
);
70 String
->Str
.Buffer
= SdbAlloc(String
->Str
.MaximumLength
);
71 String
->Str
.Length
= 0;
75 BOOL
SdbpGetLongPathName(PCWSTR wszPath
, PSDB_TMP_STR Result
)
77 DWORD max
= Result
->Str
.MaximumLength
/ 2;
78 DWORD ret
= GetLongPathNameW(wszPath
, Result
->Str
.Buffer
, max
);
83 SdbpResizeTempStr(Result
, ret
);
84 max
= Result
->Str
.MaximumLength
/ 2;
85 ret
= GetLongPathNameW(wszPath
, Result
->Str
.Buffer
, max
);
89 Result
->Str
.Length
= ret
* 2;
93 SHIM_ERR("Failed to convert short path to long path error 0x%lx\n", GetLastError());
97 BOOL
SdbpIsPathOnRemovableMedia(PCWSTR Path
)
99 WCHAR tmp
[] = { 'A',':','\\',0 };
101 if (!Path
|| Path
[0] == UNICODE_NULL
)
103 SHIM_ERR("Invalid argument\n");
111 SHIM_INFO("\"%S\" is a network path.\n", Path
);
114 SHIM_INFO("\"%S\" not a full path we can operate on.\n", Path
);
118 type
= GetDriveTypeW(tmp
);
120 return type
== DRIVE_REMOVABLE
|| type
== DRIVE_CDROM
;
123 /* Convert a path on removable media to 'SIGN.MEDIA=%X filename' */
124 BOOL
SdbpBuildSignMediaId(PSDB_TMP_STR LongPath
)
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
, '\\');
136 WIN32_FIND_DATAW FindData
;
139 FindHandle
= FindFirstFileW(LongPath
->Str
.Buffer
, &FindData
);
140 if (FindHandle
!= INVALID_HANDLE_VALUE
)
145 if (!(FindData
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) && FindData
.nFileSizeLow
)
146 SignMedia
= SignMedia
<< 1 ^ FindData
.nFileSizeLow
;
147 } while (FindNextFileW(FindHandle
, &FindData
));
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
);
157 SdbpFreeTempStr(&Scratch
);
158 SdbpFreeTempStr(LongPath
);
162 /* Convert a given path to a long or media path */
163 BOOL
SdbpResolvePath(PSDB_TMP_STR LongPath
, PCWSTR wszPath
)
165 SdbpInitTempStr(LongPath
);
166 if (!SdbpGetLongPathName(wszPath
, LongPath
))
168 SdbpFreeTempStr(LongPath
);
171 if (SdbpIsPathOnRemovableMedia(LongPath
->Str
.Buffer
))
173 return SdbpBuildSignMediaId(LongPath
);
178 static ACCESS_MASK g_QueryFlag
= 0xffffffff;
179 ACCESS_MASK
QueryFlag(void)
181 if (g_QueryFlag
== 0xffffffff)
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;
190 NTSTATUS
SdbpOpenKey(PUNICODE_STRING FullPath
, BOOL bMachine
, ACCESS_MASK Access
, PHANDLE KeyHandle
)
192 UNICODE_STRING BasePath
;
193 const WCHAR
* LayersKey
= APPCOMPAT_LAYER_KEY
;
194 OBJECT_ATTRIBUTES ObjectLayer
= RTL_INIT_OBJECT_ATTRIBUTES(FullPath
, OBJ_CASE_INSENSITIVE
);
196 FullPath
->Buffer
= NULL
;
197 FullPath
->Length
= FullPath
->MaximumLength
= 0;
200 RtlInitUnicodeString(&BasePath
, REGISTRY_MACHINE
);
204 Status
= RtlFormatCurrentUserKeyPath(&BasePath
);
205 if (!NT_SUCCESS(Status
))
207 SHIM_ERR("Unable to acquire user registry key, Error: 0x%lx\n", Status
);
211 FullPath
->MaximumLength
= (USHORT
)(BasePath
.Length
+ SdbpStrsize(LayersKey
));
212 FullPath
->Buffer
= SdbAlloc(FullPath
->MaximumLength
);
213 FullPath
->Length
= 0;
214 RtlAppendUnicodeStringToString(FullPath
, &BasePath
);
216 RtlFreeUnicodeString(&BasePath
);
217 RtlAppendUnicodeToString(FullPath
, LayersKey
);
219 Status
= NtOpenKey(KeyHandle
, Access
| QueryFlag(), &ObjectLayer
);
220 if (!NT_SUCCESS(Status
))
222 SHIM_ERR("Unable to open Key \"%wZ\" Status 0x%lx\n", FullPath
, Status
);
223 SdbFree(FullPath
->Buffer
);
224 FullPath
->Buffer
= NULL
;
230 BOOL
SdbpGetPermLayersInternal(PUNICODE_STRING FullPath
, PWSTR pwszLayers
, PDWORD pdwBytes
, BOOL bMachine
)
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
;
239 Status
= SdbpOpenKey(&FullKey
, bMachine
, KEY_QUERY_VALUE
, &KeyHandle
);
240 if (NT_SUCCESS(Status
))
242 Status
= NtQueryValueKey(KeyHandle
, FullPath
, KeyValuePartialInformation
, PartialInfo
, sizeof(ValueBuffer
), &Length
);
243 if (NT_SUCCESS(Status
))
245 StringCbCopyNW(pwszLayers
, *pdwBytes
, (PCWSTR
)PartialInfo
->Data
, PartialInfo
->DataLength
);
246 *pdwBytes
= PartialInfo
->DataLength
;
250 SHIM_INFO("Failed to read value info from Key \"%wZ\" Status 0x%lx\n", &FullKey
, Status
);
253 SdbFree(FullKey
.Buffer
);
255 return NT_SUCCESS(Status
);
258 BOOL
SdbDeletePermLayerKeys(PCWSTR wszPath
, BOOL bMachine
)
260 UNICODE_STRING FullKey
;
261 SDB_TMP_STR LongPath
;
265 if (!SdbpResolvePath(&LongPath
, wszPath
))
268 Status
= SdbpOpenKey(&FullKey
, bMachine
, KEY_SET_VALUE
, &KeyHandle
);
269 if (NT_SUCCESS(Status
))
271 Status
= NtDeleteValueKey(KeyHandle
, &LongPath
.Str
);
272 if (!NT_SUCCESS(Status
))
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
;
280 SdbFree(FullKey
.Buffer
);
282 SdbpFreeTempStr(&LongPath
);
283 return NT_SUCCESS(Status
);
286 BOOL
SdbpMatchLayer(PCWSTR start
, PCWSTR end
, PCWSTR compare
)
290 return !wcsicmp(start
, compare
);
292 return wcslen(compare
) == len
&& !_wcsnicmp(start
, compare
, len
);
295 BOOL
SdbpAppendLayer(PWSTR target
, DWORD len
, PCWSTR layer
, PCWSTR end
)
297 NTSTATUS Status
= STATUS_SUCCESS
;
299 Status
= StringCbCatW(target
, len
, SPACE_ONLY
);
301 if (NT_SUCCESS(Status
))
304 Status
= StringCbCatNW(target
, len
, layer
, (end
- layer
) * sizeof(WCHAR
));
306 Status
= StringCbCatW(target
, len
, layer
);
309 return NT_SUCCESS(Status
);
314 * Determine if we allow permission layers to apply on this file.
316 * @param [in] Path Full pathname of the file, only the drive part is used.
318 * @return TRUE if we allow permission layer, FALSE if not.
320 BOOL WINAPI
AllowPermLayer(PCWSTR Path
)
322 WCHAR tmp
[] = { 'A',':','\\', 0 };
326 SHIM_ERR("Invalid argument\n");
334 SHIM_INFO("\"%S\" is a network path.\n", Path
);
337 SHIM_INFO("\"%S\" not a full path we can operate on.\n", Path
);
341 type
= GetDriveTypeW(tmp
);
342 if (type
== DRIVE_REMOTE
)
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
);
354 * Read the layers specified for the application.
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].
362 * @return TRUE if it succeeds, FALSE if it fails.
364 BOOL WINAPI
SdbGetPermLayerKeys(PCWSTR wszPath
, PWSTR pwszLayers
, PDWORD pdwBytes
, DWORD dwFlags
)
367 SDB_TMP_STR LongPath
;
368 DWORD dwBytes
, dwTotal
= 0;
369 if (!wszPath
|| !pdwBytes
)
371 SHIM_ERR("NULL parameter passed for wszPath or pdwBytes.\n");
375 if (!SdbpResolvePath(&LongPath
, wszPath
))
378 if (dwFlags
& GPLK_MACHINE
)
380 if (SdbpGetPermLayersInternal(&LongPath
.Str
, pwszLayers
, &dwBytes
, TRUE
))
383 dwTotal
= dwBytes
- sizeof(WCHAR
); /* Compensate for the nullterm. */
384 pwszLayers
+= dwTotal
/ sizeof(WCHAR
);
385 dwBytes
= *pdwBytes
- dwBytes
;
386 if (dwFlags
& GPLK_USER
)
388 *(pwszLayers
++) = L
' ';
390 dwBytes
-= sizeof(WCHAR
);
391 dwTotal
+= sizeof(WCHAR
);
395 if (dwFlags
& GPLK_USER
)
397 if (SdbpGetPermLayersInternal(&LongPath
.Str
, pwszLayers
, &dwBytes
, FALSE
))
400 dwTotal
+= dwBytes
- sizeof(WCHAR
); /* Compensate for the nullterm. */
402 else if (dwTotal
> 0 && pwszLayers
[-1] == L
' ')
404 pwszLayers
[-1] = '\0';
405 dwTotal
-= sizeof(WCHAR
);
409 dwTotal
+= sizeof(WCHAR
);
411 SdbpFreeTempStr(&LongPath
);
416 * Set or clear the Layer key.
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
421 * @param [in] bMachine TRUE to machine.
423 * @return TRUE if it succeeds, FALSE if it fails.
425 BOOL WINAPI
SdbSetPermLayerKeys(PCWSTR wszPath
, PCWSTR wszLayers
, BOOL bMachine
)
427 UNICODE_STRING FullKey
;
428 SDB_TMP_STR LongPath
;
432 if (!wszLayers
|| *wszLayers
== '\0')
433 return SdbDeletePermLayerKeys(wszPath
, bMachine
);
435 if (!SdbpResolvePath(&LongPath
, wszPath
))
438 Status
= SdbpOpenKey(&FullKey
, bMachine
, KEY_SET_VALUE
, &KeyHandle
);
439 if (NT_SUCCESS(Status
))
441 Status
= NtSetValueKey(KeyHandle
, &LongPath
.Str
, 0, REG_SZ
, (PVOID
)wszLayers
, SdbpStrsize(wszLayers
));
442 if (!NT_SUCCESS(Status
))
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
;
449 SdbFree(FullKey
.Buffer
);
451 SdbpFreeTempStr(&LongPath
);
452 return NT_SUCCESS(Status
);
456 * Adds or removes a single layer entry.
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.
465 * @return TRUE if it succeeds, FALSE if it fails.
467 BOOL WINAPI
SetPermLayerState(PCWSTR wszPath
, PCWSTR wszLayer
, DWORD dwFlags
, BOOL bMachine
, BOOL bEnable
)
469 WCHAR fullLayer
[MAX_LAYER_LENGTH
] = { 0 };
470 WCHAR newLayer
[MAX_LAYER_LENGTH
] = { 0 };
471 DWORD dwBytes
= sizeof(fullLayer
), dwWriteFlags
= 0;
476 SHIM_ERR("Invalid argument\n");
479 if (dwFlags
& ~(LAYER_APPLY_TO_SYSTEM_EXES
| LAYER_UNK_FLAG2
))
481 SHIM_ERR("Invalid flags\n");
484 p
= wcspbrk(wszLayer
, DISALLOWED_LAYER_CHARS
);
490 SHIM_ERR("Only one layer can be passed in at a time.\n");
494 SHIM_ERR("Flags cannot be passed in with the layer name.\n");
498 if (!SdbGetPermLayerKeys(wszPath
, fullLayer
, &dwBytes
, bMachine
? GPLK_MACHINE
: GPLK_USER
))
501 dwBytes
= sizeof(fullLayer
);
505 while (*start
== '!' || *start
== '#' || *start
== ' ' || *start
== '\t')
508 dwWriteFlags
|= LAYER_APPLY_TO_SYSTEM_EXES
;
509 else if (*start
== '!')
510 dwWriteFlags
|= LAYER_UNK_FLAG2
;
514 dwWriteFlags
|= dwFlags
;
516 dwWriteFlags
&= ~dwFlags
;
519 if (dwWriteFlags
& LAYER_UNK_FLAG2
)
521 if (dwWriteFlags
& LAYER_APPLY_TO_SYSTEM_EXES
)
526 while (*start
== ' ' || *start
== '\t')
531 p
= wcspbrk(start
, LAYER_SEPARATORS
);
532 if (!SdbpMatchLayer(start
, p
, wszLayer
))
534 SdbpAppendLayer(newLayer
, sizeof(newLayer
), start
, p
);
539 if (bEnable
&& wszLayer
[0])
541 SdbpAppendLayer(newLayer
, sizeof(newLayer
), wszLayer
, NULL
);
544 return SdbSetPermLayerKeys(wszPath
, newLayer
, bMachine
);