2 * Copyright 2015-2017 Mark Jansen (mark.jansen@reactos.org)
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 #define WIN32_NO_STATUS
27 #define GPLK_MACHINE 2
28 #define MAX_LAYER_LENGTH 256
29 #define LAYER_APPLY_TO_SYSTEM_EXES 1
30 #define LAYER_UNK_FLAG2 2
37 #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}
38 #define REGISTRY_MACHINE (const WCHAR[]){'\\','R','e','g','i','s','t','r','y','\\','M','a','c','h','i','n','e',0}
39 #define SPACE_ONLY (const WCHAR[]){' ',0}
40 #define DISALLOWED_LAYER_CHARS (const WCHAR[]){' ','#','!',0}
41 #define LAYER_SEPARATORS (const WCHAR[]){' ','\t',0}
42 #define SIGN_MEDIA_FMT (const WCHAR[]){'S','I','G','N','.','M','E','D','I','A','=','%','X',' ','%','s',0}
45 #define APPCOMPAT_LAYER_KEY L"\\Software\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers"
46 #define REGISTRY_MACHINE L"\\Registry\\Machine"
47 #define SPACE_ONLY L" "
48 #define DISALLOWED_LAYER_CHARS L" #!"
49 #define LAYER_SEPARATORS L" \t"
50 #define SIGN_MEDIA_FMT L"SIGN.MEDIA=%X %s"
53 /* Fixme: use RTL_UNICODE_STRING_BUFFER */
54 typedef struct SDB_TMP_STR
57 WCHAR FixedBuffer
[MAX_PATH
];
58 } SDB_TMP_STR
, *PSDB_TMP_STR
;
60 void SdbpInitTempStr(PSDB_TMP_STR String
)
62 String
->Str
.Buffer
= String
->FixedBuffer
;
63 String
->Str
.Length
= 0;
64 String
->Str
.MaximumLength
= sizeof(String
->FixedBuffer
);
67 void SdbpFreeTempStr(PSDB_TMP_STR String
)
69 if (String
->Str
.Buffer
!= String
->FixedBuffer
)
71 SdbFree(String
->Str
.Buffer
);
75 void SdbpResizeTempStr(PSDB_TMP_STR String
, WORD newLength
)
77 if (newLength
> String
->Str
.MaximumLength
)
79 SdbpFreeTempStr(String
);
80 String
->Str
.MaximumLength
= newLength
* sizeof(WCHAR
);
81 String
->Str
.Buffer
= SdbAlloc(String
->Str
.MaximumLength
);
82 String
->Str
.Length
= 0;
86 BOOL
SdbpGetLongPathName(PCWSTR wszPath
, PSDB_TMP_STR Result
)
88 DWORD max
= Result
->Str
.MaximumLength
/ 2;
89 DWORD ret
= GetLongPathNameW(wszPath
, Result
->Str
.Buffer
, max
);
94 SdbpResizeTempStr(Result
, ret
);
95 max
= Result
->Str
.MaximumLength
/ 2;
96 ret
= GetLongPathNameW(wszPath
, Result
->Str
.Buffer
, max
);
100 Result
->Str
.Length
= ret
* 2;
104 SHIM_ERR("Failed to convert short path to long path error 0x%lx\n", GetLastError());
108 BOOL
SdbpIsPathOnRemovableMedia(PCWSTR Path
)
110 WCHAR tmp
[] = { 'A',':','\\',0 };
112 if (!Path
|| Path
[0] == UNICODE_NULL
)
114 SHIM_ERR("Invalid argument\n");
122 SHIM_INFO("\"%S\" is a network path.\n", Path
);
125 SHIM_INFO("\"%S\" not a full path we can operate on.\n", Path
);
129 type
= GetDriveTypeW(tmp
);
131 return type
== DRIVE_REMOVABLE
|| type
== DRIVE_CDROM
;
134 /* Convert a path on removable media to 'SIGN.MEDIA=%X filename' */
135 BOOL
SdbpBuildSignMediaId(PSDB_TMP_STR LongPath
)
140 SdbpInitTempStr(&Scratch
);
141 SdbpResizeTempStr(&Scratch
, LongPath
->Str
.Length
/ sizeof(WCHAR
) + 30);
142 StringCbCopyNW(Scratch
.Str
.Buffer
, Scratch
.Str
.MaximumLength
, LongPath
->Str
.Buffer
, LongPath
->Str
.Length
);
143 Ptr
= wcsrchr(LongPath
->Str
.Buffer
, '\\');
147 WIN32_FIND_DATAW FindData
;
150 FindHandle
= FindFirstFileW(LongPath
->Str
.Buffer
, &FindData
);
151 if (FindHandle
!= INVALID_HANDLE_VALUE
)
156 if (!(FindData
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) && FindData
.nFileSizeLow
)
157 SignMedia
= SignMedia
<< 1 ^ FindData
.nFileSizeLow
;
158 } while (FindNextFileW(FindHandle
, &FindData
));
160 FindClose(FindHandle
);
161 SdbpResizeTempStr(LongPath
, (LongPath
->Str
.Length
>> 1) + 20);
162 StringCbPrintfW(LongPath
->Str
.Buffer
, LongPath
->Str
.MaximumLength
, SIGN_MEDIA_FMT
, SignMedia
, Scratch
.Str
.Buffer
+ 3);
163 LongPath
->Str
.Length
= (USHORT
)SdbpStrlen(LongPath
->Str
.Buffer
) * sizeof(WCHAR
);
164 SdbpFreeTempStr(&Scratch
);
168 SdbpFreeTempStr(&Scratch
);
169 SdbpFreeTempStr(LongPath
);
173 /* Convert a given path to a long or media path */
174 BOOL
SdbpResolvePath(PSDB_TMP_STR LongPath
, PCWSTR wszPath
)
176 SdbpInitTempStr(LongPath
);
177 if (!SdbpGetLongPathName(wszPath
, LongPath
))
179 SdbpFreeTempStr(LongPath
);
182 if (SdbpIsPathOnRemovableMedia(LongPath
->Str
.Buffer
))
184 return SdbpBuildSignMediaId(LongPath
);
189 static ACCESS_MASK g_QueryFlag
= 0xffffffff;
190 ACCESS_MASK
QueryFlag(void)
192 if (g_QueryFlag
== 0xffffffff)
194 ULONG_PTR wow64_ptr
= 0;
195 NTSTATUS Status
= NtQueryInformationProcess(NtCurrentProcess(), ProcessWow64Information
, &wow64_ptr
, sizeof(wow64_ptr
), NULL
);
196 g_QueryFlag
= (NT_SUCCESS(Status
) && wow64_ptr
!= 0) ? KEY_WOW64_64KEY
: 0;
201 NTSTATUS
SdbpOpenKey(PUNICODE_STRING FullPath
, BOOL bMachine
, ACCESS_MASK Access
, PHANDLE KeyHandle
)
203 UNICODE_STRING BasePath
;
204 const WCHAR
* LayersKey
= APPCOMPAT_LAYER_KEY
;
205 OBJECT_ATTRIBUTES ObjectLayer
= RTL_INIT_OBJECT_ATTRIBUTES(FullPath
, OBJ_CASE_INSENSITIVE
);
207 FullPath
->Buffer
= NULL
;
208 FullPath
->Length
= FullPath
->MaximumLength
= 0;
211 RtlInitUnicodeString(&BasePath
, REGISTRY_MACHINE
);
215 Status
= RtlFormatCurrentUserKeyPath(&BasePath
);
216 if (!NT_SUCCESS(Status
))
218 SHIM_ERR("Unable to acquire user registry key, Error: 0x%lx\n", Status
);
222 FullPath
->MaximumLength
= (USHORT
)(BasePath
.Length
+ SdbpStrsize(LayersKey
));
223 FullPath
->Buffer
= SdbAlloc(FullPath
->MaximumLength
);
224 FullPath
->Length
= 0;
225 RtlAppendUnicodeStringToString(FullPath
, &BasePath
);
227 RtlFreeUnicodeString(&BasePath
);
228 RtlAppendUnicodeToString(FullPath
, LayersKey
);
230 Status
= NtOpenKey(KeyHandle
, Access
| QueryFlag(), &ObjectLayer
);
231 if (!NT_SUCCESS(Status
))
233 SHIM_ERR("Unable to open Key \"%wZ\" Status 0x%lx\n", FullPath
, Status
);
234 SdbFree(FullPath
->Buffer
);
235 FullPath
->Buffer
= NULL
;
241 BOOL
SdbpGetPermLayersInternal(PUNICODE_STRING FullPath
, PWSTR pwszLayers
, PDWORD pdwBytes
, BOOL bMachine
)
243 UNICODE_STRING FullKey
;
244 ULONG ValueBuffer
[(MAX_LAYER_LENGTH
* sizeof(WCHAR
) + sizeof(KEY_VALUE_PARTIAL_INFORMATION
) + sizeof(ULONG
) - 1) / sizeof(ULONG
)];
245 PKEY_VALUE_PARTIAL_INFORMATION PartialInfo
= (PVOID
)ValueBuffer
;
250 Status
= SdbpOpenKey(&FullKey
, bMachine
, KEY_QUERY_VALUE
, &KeyHandle
);
251 if (NT_SUCCESS(Status
))
253 Status
= NtQueryValueKey(KeyHandle
, FullPath
, KeyValuePartialInformation
, PartialInfo
, sizeof(ValueBuffer
), &Length
);
254 if (NT_SUCCESS(Status
))
256 StringCbCopyNW(pwszLayers
, *pdwBytes
, (PCWSTR
)PartialInfo
->Data
, PartialInfo
->DataLength
);
257 *pdwBytes
= PartialInfo
->DataLength
;
261 SHIM_INFO("Failed to read value info from Key \"%wZ\" Status 0x%lx\n", &FullKey
, Status
);
264 SdbFree(FullKey
.Buffer
);
266 return NT_SUCCESS(Status
);
269 BOOL
SdbDeletePermLayerKeys(PCWSTR wszPath
, BOOL bMachine
)
271 UNICODE_STRING FullKey
;
272 SDB_TMP_STR LongPath
;
276 if (!SdbpResolvePath(&LongPath
, wszPath
))
279 Status
= SdbpOpenKey(&FullKey
, bMachine
, KEY_SET_VALUE
, &KeyHandle
);
280 if (NT_SUCCESS(Status
))
282 Status
= NtDeleteValueKey(KeyHandle
, &LongPath
.Str
);
283 if (!NT_SUCCESS(Status
))
285 SHIM_INFO("Failed to delete value from Key \"%wZ\" Status 0x%lx\n", &FullKey
, Status
);
286 /* This is what we want, so if the key didnt exist, we should not fail :) */
287 if (Status
== STATUS_OBJECT_NAME_NOT_FOUND
)
288 Status
= STATUS_SUCCESS
;
291 SdbFree(FullKey
.Buffer
);
293 SdbpFreeTempStr(&LongPath
);
294 return NT_SUCCESS(Status
);
297 BOOL
SdbpMatchLayer(PCWSTR start
, PCWSTR end
, PCWSTR compare
)
301 return !wcsicmp(start
, compare
);
303 return wcslen(compare
) == len
&& !_wcsnicmp(start
, compare
, len
);
306 BOOL
SdbpAppendLayer(PWSTR target
, DWORD len
, PCWSTR layer
, PCWSTR end
)
308 NTSTATUS Status
= STATUS_SUCCESS
;
310 Status
= StringCbCatW(target
, len
, SPACE_ONLY
);
312 if (NT_SUCCESS(Status
))
315 Status
= StringCbCatNW(target
, len
, layer
, (end
- layer
) * sizeof(WCHAR
));
317 Status
= StringCbCatW(target
, len
, layer
);
320 return NT_SUCCESS(Status
);
325 * Determine if we allow permission layers to apply on this file.
327 * @param [in] Path Full pathname of the file, only the drive part is used.
329 * @return TRUE if we allow permission layer, FALSE if not.
331 BOOL WINAPI
AllowPermLayer(PCWSTR Path
)
333 WCHAR tmp
[] = { 'A',':','\\', 0 };
337 SHIM_ERR("Invalid argument\n");
345 SHIM_INFO("\"%S\" is a network path.\n", Path
);
348 SHIM_INFO("\"%S\" not a full path we can operate on.\n", Path
);
352 type
= GetDriveTypeW(tmp
);
353 if (type
== DRIVE_REMOTE
)
355 /* The logging here indicates that it does not like a CDROM or removable media, but it only
356 seems to bail out on a media that reports it is remote...
357 I have included correct logging, I doubt anyone would parse the logging, so this shouldnt break anything. */
358 SHIM_INFO("\"%S\" is on a remote drive.\n", Path
);
365 * Read the layers specified for the application.
367 * @param [in] wszPath Full pathname of the file.
368 * @param [out] pwszLayers On return, the layers set on the file.
369 * @param pdwBytes The size of the pwszLayers buffer in bytes, and on return the size of
370 * the data written (in bytes)
371 * @param [in] dwFlags The flags, [GPLK_USER | GPLK_MACHINE].
373 * @return TRUE if it succeeds, FALSE if it fails.
375 BOOL WINAPI
SdbGetPermLayerKeys(PCWSTR wszPath
, PWSTR pwszLayers
, PDWORD pdwBytes
, DWORD dwFlags
)
378 SDB_TMP_STR LongPath
;
379 DWORD dwBytes
, dwTotal
= 0;
380 if (!wszPath
|| !pdwBytes
)
382 SHIM_ERR("NULL parameter passed for wszPath or pdwBytes.\n");
386 if (!SdbpResolvePath(&LongPath
, wszPath
))
389 if (dwFlags
& GPLK_MACHINE
)
391 if (SdbpGetPermLayersInternal(&LongPath
.Str
, pwszLayers
, &dwBytes
, TRUE
))
394 dwTotal
= dwBytes
- sizeof(WCHAR
); /* Compensate for the nullterm. */
395 pwszLayers
+= dwTotal
/ sizeof(WCHAR
);
396 dwBytes
= *pdwBytes
- dwBytes
;
397 if (dwFlags
& GPLK_USER
)
399 *(pwszLayers
++) = L
' ';
401 dwBytes
-= sizeof(WCHAR
);
402 dwTotal
+= sizeof(WCHAR
);
406 if (dwFlags
& GPLK_USER
)
408 if (SdbpGetPermLayersInternal(&LongPath
.Str
, pwszLayers
, &dwBytes
, FALSE
))
411 dwTotal
+= dwBytes
- sizeof(WCHAR
); /* Compensate for the nullterm. */
413 else if (dwTotal
> 0 && pwszLayers
[-1] == L
' ')
415 pwszLayers
[-1] = '\0';
416 dwTotal
-= sizeof(WCHAR
);
420 dwTotal
+= sizeof(WCHAR
);
422 SdbpFreeTempStr(&LongPath
);
427 * Set or clear the Layer key.
429 * @param [in] wszPath Full pathname of the file.
430 * @param [in] wszLayers The layers to add (space separated), or an empty string / NULL to
432 * @param [in] bMachine TRUE to machine.
434 * @return TRUE if it succeeds, FALSE if it fails.
436 BOOL WINAPI
SdbSetPermLayerKeys(PCWSTR wszPath
, PCWSTR wszLayers
, BOOL bMachine
)
438 UNICODE_STRING FullKey
;
439 SDB_TMP_STR LongPath
;
443 if (!wszLayers
|| *wszLayers
== '\0')
444 return SdbDeletePermLayerKeys(wszPath
, bMachine
);
446 if (!SdbpResolvePath(&LongPath
, wszPath
))
449 Status
= SdbpOpenKey(&FullKey
, bMachine
, KEY_SET_VALUE
, &KeyHandle
);
450 if (NT_SUCCESS(Status
))
452 Status
= NtSetValueKey(KeyHandle
, &LongPath
.Str
, 0, REG_SZ
, (PVOID
)wszLayers
, SdbpStrsize(wszLayers
));
453 if (!NT_SUCCESS(Status
))
455 SHIM_INFO("Failed to write a value to Key \"%wZ\" Status 0x%lx\n", &FullKey
, Status
);
456 if (Status
== STATUS_OBJECT_NAME_NOT_FOUND
)
457 Status
= STATUS_SUCCESS
;
460 SdbFree(FullKey
.Buffer
);
462 SdbpFreeTempStr(&LongPath
);
463 return NT_SUCCESS(Status
);
467 * Adds or removes a single layer entry.
469 * @param [in] wszPath Full pathname of the file.
470 * @param [in] wszLayer The layer to add or remove.
471 * @param [in] dwFlags Additional flags to add / remove [LAYER_APPLY_TO_SYSTEM_EXES | ???].
472 * @param [in] bMachine When TRUE, the setting applies to all users, when FALSE only applies
473 * to the current user.
474 * @param [in] bEnable TRUE to enable, FALSE to disable a layer / flag specified.
476 * @return TRUE if it succeeds, FALSE if it fails.
478 BOOL WINAPI
SetPermLayerState(PCWSTR wszPath
, PCWSTR wszLayer
, DWORD dwFlags
, BOOL bMachine
, BOOL bEnable
)
480 WCHAR fullLayer
[MAX_LAYER_LENGTH
] = { 0 };
481 WCHAR newLayer
[MAX_LAYER_LENGTH
] = { 0 };
482 DWORD dwBytes
= sizeof(fullLayer
), dwWriteFlags
= 0;
487 SHIM_ERR("Invalid argument\n");
490 if (dwFlags
& ~(LAYER_APPLY_TO_SYSTEM_EXES
| LAYER_UNK_FLAG2
))
492 SHIM_ERR("Invalid flags\n");
495 p
= wcspbrk(wszLayer
, DISALLOWED_LAYER_CHARS
);
501 SHIM_ERR("Only one layer can be passed in at a time.\n");
505 SHIM_ERR("Flags cannot be passed in with the layer name.\n");
509 if (!SdbGetPermLayerKeys(wszPath
, fullLayer
, &dwBytes
, bMachine
? GPLK_MACHINE
: GPLK_USER
))
512 dwBytes
= sizeof(fullLayer
);
516 while (*start
== '!' || *start
== '#' || *start
== ' ' || *start
== '\t')
519 dwWriteFlags
|= LAYER_APPLY_TO_SYSTEM_EXES
;
520 else if (*start
== '!')
521 dwWriteFlags
|= LAYER_UNK_FLAG2
;
525 dwWriteFlags
|= dwFlags
;
527 dwWriteFlags
&= ~dwFlags
;
530 if (dwWriteFlags
& LAYER_UNK_FLAG2
)
532 if (dwWriteFlags
& LAYER_APPLY_TO_SYSTEM_EXES
)
537 while (*start
== ' ' || *start
== '\t')
542 p
= wcspbrk(start
, LAYER_SEPARATORS
);
543 if (!SdbpMatchLayer(start
, p
, wszLayer
))
545 SdbpAppendLayer(newLayer
, sizeof(newLayer
), start
, p
);
550 if (bEnable
&& wszLayer
[0])
552 SdbpAppendLayer(newLayer
, sizeof(newLayer
), wszLayer
, NULL
);
555 return SdbSetPermLayerKeys(wszPath
, newLayer
, bMachine
);