2 * Copyright 2015 Mark Jansen
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"
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 BOOL
SdbpBuildSignMediaId(PSDB_TMP_STR LongPath
)
139 SdbpInitTempStr(&Scratch
);
140 SdbpResizeTempStr(&Scratch
, LongPath
->Str
.Length
/ sizeof(WCHAR
) + 30);
141 StringCbCopyNW(Scratch
.Str
.Buffer
, Scratch
.Str
.MaximumLength
, LongPath
->Str
.Buffer
, LongPath
->Str
.Length
);
142 Ptr
= wcsrchr(LongPath
->Str
.Buffer
, '\\');
146 WIN32_FIND_DATAW FindData
;
149 FindHandle
= FindFirstFileW(LongPath
->Str
.Buffer
, &FindData
);
150 if (FindHandle
!= INVALID_HANDLE_VALUE
)
155 if (!(FindData
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) && FindData
.nFileSizeLow
)
156 SignMedia
= SignMedia
<< 1 ^ FindData
.nFileSizeLow
;
157 } while (FindNextFileW(FindHandle
, &FindData
));
159 FindClose(FindHandle
);
160 SdbpResizeTempStr(LongPath
, (LongPath
->Str
.Length
>> 1) + 20);
161 StringCbPrintfW(LongPath
->Str
.Buffer
, LongPath
->Str
.MaximumLength
, SIGN_MEDIA_FMT
, SignMedia
, Scratch
.Str
.Buffer
+ 3);
162 LongPath
->Str
.Length
= wcslen(LongPath
->Str
.Buffer
) * sizeof(WCHAR
);
163 SdbpFreeTempStr(&Scratch
);
167 SdbpFreeTempStr(&Scratch
);
168 SdbpFreeTempStr(LongPath
);
172 BOOL
SdbpResolvePath(PSDB_TMP_STR LongPath
, PCWSTR wszPath
)
174 SdbpInitTempStr(LongPath
);
175 if (!SdbpGetLongPathName(wszPath
, LongPath
))
177 SdbpFreeTempStr(LongPath
);
180 if (SdbpIsPathOnRemovableMedia(LongPath
->Str
.Buffer
))
182 return SdbpBuildSignMediaId(LongPath
);
187 static ACCESS_MASK g_QueryFlag
= 0xffffffff;
188 ACCESS_MASK
QueryFlag(void)
190 if (g_QueryFlag
== 0xffffffff)
192 ULONG_PTR wow64_ptr
= 0;
193 NTSTATUS Status
= NtQueryInformationProcess(NtCurrentProcess(), ProcessWow64Information
, &wow64_ptr
, sizeof(wow64_ptr
), NULL
);
194 g_QueryFlag
= (NT_SUCCESS(Status
) && wow64_ptr
!= 0) ? KEY_WOW64_64KEY
: 0;
199 NTSTATUS
SdbpOpenKey(PUNICODE_STRING FullPath
, BOOL bMachine
, ACCESS_MASK Access
, PHANDLE KeyHandle
)
201 UNICODE_STRING BasePath
;
202 const WCHAR
* LayersKey
= APPCOMPAT_LAYER_KEY
;
203 OBJECT_ATTRIBUTES ObjectLayer
= RTL_INIT_OBJECT_ATTRIBUTES(FullPath
, OBJ_CASE_INSENSITIVE
);
205 FullPath
->Buffer
= NULL
;
206 FullPath
->Length
= FullPath
->MaximumLength
= 0;
209 RtlInitUnicodeString(&BasePath
, REGISTRY_MACHINE
);
213 Status
= RtlFormatCurrentUserKeyPath(&BasePath
);
214 if (!NT_SUCCESS(Status
))
216 SHIM_ERR("Unable to acquire user registry key, Error: 0x%lx\n", Status
);
220 FullPath
->MaximumLength
= BasePath
.Length
+ (wcslen(LayersKey
) + 1) * sizeof(WCHAR
);
221 FullPath
->Buffer
= SdbAlloc(FullPath
->MaximumLength
);
222 FullPath
->Length
= 0;
223 RtlAppendUnicodeStringToString(FullPath
, &BasePath
);
225 RtlFreeUnicodeString(&BasePath
);
226 RtlAppendUnicodeToString(FullPath
, LayersKey
);
228 Status
= NtOpenKey(KeyHandle
, Access
| QueryFlag(), &ObjectLayer
);
229 if (!NT_SUCCESS(Status
))
231 SHIM_ERR("Unable to open Key \"%wZ\" Status 0x%lx\n", FullPath
, Status
);
232 SdbFree(FullPath
->Buffer
);
233 FullPath
->Buffer
= NULL
;
239 BOOL
SdbpGetPermLayersInternal(PUNICODE_STRING FullPath
, PWSTR pwszLayers
, PDWORD pdwBytes
, BOOL bMachine
)
241 UNICODE_STRING FullKey
;
242 ULONG ValueBuffer
[(MAX_LAYER_LENGTH
* sizeof(WCHAR
) + sizeof(KEY_VALUE_PARTIAL_INFORMATION
) + sizeof(ULONG
) - 1) / sizeof(ULONG
)];
243 PKEY_VALUE_PARTIAL_INFORMATION PartialInfo
= (PVOID
)ValueBuffer
;
248 Status
= SdbpOpenKey(&FullKey
, bMachine
, KEY_QUERY_VALUE
, &KeyHandle
);
249 if (NT_SUCCESS(Status
))
251 Status
= NtQueryValueKey(KeyHandle
, FullPath
, KeyValuePartialInformation
, PartialInfo
, sizeof(ValueBuffer
), &Length
);
252 if (NT_SUCCESS(Status
))
254 StringCbCopyNW(pwszLayers
, *pdwBytes
, (PCWSTR
)PartialInfo
->Data
, PartialInfo
->DataLength
);
255 *pdwBytes
= PartialInfo
->DataLength
;
259 SHIM_INFO("Failed to read value info from Key \"%wZ\" Status 0x%lx\n", &FullKey
, Status
);
262 SdbFree(FullKey
.Buffer
);
264 return NT_SUCCESS(Status
);
267 BOOL
SdbDeletePermLayerKeys(PCWSTR wszPath
, BOOL bMachine
)
269 UNICODE_STRING FullKey
;
270 SDB_TMP_STR LongPath
;
274 if (!SdbpResolvePath(&LongPath
, wszPath
))
277 Status
= SdbpOpenKey(&FullKey
, bMachine
, KEY_SET_VALUE
, &KeyHandle
);
278 if (NT_SUCCESS(Status
))
280 Status
= NtDeleteValueKey(KeyHandle
, &LongPath
.Str
);
281 if (!NT_SUCCESS(Status
))
283 SHIM_INFO("Failed to delete value from Key \"%wZ\" Status 0x%lx\n", &FullKey
, Status
);
284 /* This is what we want, so if the key didnt exist, we should not fail :) */
285 if (Status
== STATUS_OBJECT_NAME_NOT_FOUND
)
286 Status
= STATUS_SUCCESS
;
289 SdbFree(FullKey
.Buffer
);
291 SdbpFreeTempStr(&LongPath
);
292 return NT_SUCCESS(Status
);
295 BOOL
SdbpMatchLayer(PCWSTR start
, PCWSTR end
, PCWSTR compare
)
299 return !wcsicmp(start
, compare
);
301 return wcslen(compare
) == len
&& !_wcsnicmp(start
, compare
, len
);
304 BOOL
SdbpAppendLayer(PWSTR target
, DWORD len
, PCWSTR layer
, PCWSTR end
)
306 NTSTATUS Status
= STATUS_SUCCESS
;
308 Status
= StringCbCatW(target
, len
, SPACE_ONLY
);
310 if (NT_SUCCESS(Status
))
313 Status
= StringCbCatNW(target
, len
, layer
, (end
- layer
) * sizeof(WCHAR
));
315 Status
= StringCbCatW(target
, len
, layer
);
318 return NT_SUCCESS(Status
);
323 * Determine if we allow permission layers to apply on this file.
325 * @param [in] Path Full pathname of the file, only the drive part is used.
327 * @return TRUE if we allow permission layer, FALSE if not.
329 BOOL WINAPI
AllowPermLayer(PCWSTR Path
)
331 WCHAR tmp
[] = { 'A',':','\\', 0 };
335 SHIM_ERR("Invalid argument\n");
343 SHIM_INFO("\"%S\" is a network path.\n", Path
);
346 SHIM_INFO("\"%S\" not a full path we can operate on.\n", Path
);
350 type
= GetDriveTypeW(tmp
);
351 if (type
== DRIVE_REMOTE
)
353 /* The logging here indicates that it does not like a CDROM or removable media, but it only
354 seems to bail out on a media that reports it is remote...
355 I have included correct logging, I doubt anyone would parse the logging, so this shouldnt break anything. */
356 SHIM_INFO("\"%S\" is on a remote drive.\n", Path
);
363 * Read the layers specified for the application.
365 * @param [in] wszPath Full pathname of the file.
366 * @param [out] pwszLayers On return, the layers set on the file.
367 * @param pdwBytes The size of the pwszLayers buffer in bytes, and on return the size of
368 * the data written (in bytes)
369 * @param [in] dwFlags The flags, [GPLK_USER | GPLK_MACHINE].
371 * @return TRUE if it succeeds, FALSE if it fails.
373 BOOL WINAPI
SdbGetPermLayerKeys(PCWSTR wszPath
, PWSTR pwszLayers
, PDWORD pdwBytes
, DWORD dwFlags
)
376 SDB_TMP_STR LongPath
;
377 DWORD dwBytes
, dwTotal
= 0;
378 if (!wszPath
|| !pdwBytes
)
380 SHIM_ERR("NULL parameter passed for wszPath or pdwBytes.\n");
384 if (!SdbpResolvePath(&LongPath
, wszPath
))
387 if (dwFlags
& GPLK_MACHINE
)
389 if (SdbpGetPermLayersInternal(&LongPath
.Str
, pwszLayers
, &dwBytes
, TRUE
))
392 dwTotal
= dwBytes
- sizeof(WCHAR
); /* Compensate for the nullterm. */
393 pwszLayers
+= dwTotal
/ sizeof(WCHAR
);
394 dwBytes
= *pdwBytes
- dwBytes
;
395 if (dwFlags
& GPLK_USER
)
397 *(pwszLayers
++) = L
' ';
399 dwBytes
-= sizeof(WCHAR
);
400 dwTotal
+= sizeof(WCHAR
);
404 if (dwFlags
& GPLK_USER
)
406 if (SdbpGetPermLayersInternal(&LongPath
.Str
, pwszLayers
, &dwBytes
, FALSE
))
409 dwTotal
+= dwBytes
- sizeof(WCHAR
); /* Compensate for the nullterm. */
411 else if (dwTotal
> 0 && pwszLayers
[-1] == L
' ')
413 pwszLayers
[-1] = '\0';
414 dwTotal
-= sizeof(WCHAR
);
418 dwTotal
+= sizeof(WCHAR
);
420 SdbpFreeTempStr(&LongPath
);
425 * Set or clear the Layer key.
427 * @param [in] wszPath Full pathname of the file.
428 * @param [in] wszLayers The layers to add (space separated), or an empty string / NULL to
430 * @param [in] bMachine TRUE to machine.
432 * @return TRUE if it succeeds, FALSE if it fails.
434 BOOL WINAPI
SdbSetPermLayerKeys(PCWSTR wszPath
, PCWSTR wszLayers
, BOOL bMachine
)
436 UNICODE_STRING FullKey
;
437 SDB_TMP_STR LongPath
;
441 if (!wszLayers
|| *wszLayers
== '\0')
442 return SdbDeletePermLayerKeys(wszPath
, bMachine
);
444 if (!SdbpResolvePath(&LongPath
, wszPath
))
447 Status
= SdbpOpenKey(&FullKey
, bMachine
, KEY_SET_VALUE
, &KeyHandle
);
448 if (NT_SUCCESS(Status
))
450 Status
= NtSetValueKey(KeyHandle
, &LongPath
.Str
, 0, REG_SZ
, (PVOID
)wszLayers
, (wcslen(wszLayers
)+1) * sizeof(WCHAR
));
451 if (!NT_SUCCESS(Status
))
453 SHIM_INFO("Failed to write a value to Key \"%wZ\" Status 0x%lx\n", &FullKey
, Status
);
454 if (Status
== STATUS_OBJECT_NAME_NOT_FOUND
)
455 Status
= STATUS_SUCCESS
;
458 SdbFree(FullKey
.Buffer
);
460 SdbpFreeTempStr(&LongPath
);
461 return NT_SUCCESS(Status
);
465 * Adds or removes a single layer entry.
467 * @param [in] wszPath Full pathname of the file.
468 * @param [in] wszLayer The layer to add or remove.
469 * @param [in] dwFlags Additional flags to add / remove [LAYER_APPLY_TO_SYSTEM_EXES | ???].
470 * @param [in] bMachine When TRUE, the setting applies to all users, when FALSE only applies
471 * to the current user.
472 * @param [in] bEnable TRUE to enable, FALSE to disable a layer / flag specified.
474 * @return TRUE if it succeeds, FALSE if it fails.
476 BOOL WINAPI
SetPermLayerState(PCWSTR wszPath
, PCWSTR wszLayer
, DWORD dwFlags
, BOOL bMachine
, BOOL bEnable
)
478 WCHAR fullLayer
[MAX_LAYER_LENGTH
] = { 0 };
479 WCHAR newLayer
[MAX_LAYER_LENGTH
] = { 0 };
480 DWORD dwBytes
= sizeof(fullLayer
), dwWriteFlags
= 0;
485 SHIM_ERR("Invalid argument\n");
488 if (dwFlags
& ~(LAYER_APPLY_TO_SYSTEM_EXES
| LAYER_UNK_FLAG2
))
490 SHIM_ERR("Invalid flags\n");
493 p
= wcspbrk(wszLayer
, DISALLOWED_LAYER_CHARS
);
499 SHIM_ERR("Only one layer can be passed in at a time.\n");
503 SHIM_ERR("Flags cannot be passed in with the layer name.\n");
507 if (!SdbGetPermLayerKeys(wszPath
, fullLayer
, &dwBytes
, bMachine
? GPLK_MACHINE
: GPLK_USER
))
510 dwBytes
= sizeof(fullLayer
);
514 while (*start
== '!' || *start
== '#' || *start
== ' ' || *start
== '\t')
517 dwWriteFlags
|= LAYER_APPLY_TO_SYSTEM_EXES
;
518 else if (*start
== '!')
519 dwWriteFlags
|= LAYER_UNK_FLAG2
;
523 dwWriteFlags
|= dwFlags
;
525 dwWriteFlags
&= ~dwFlags
;
528 if (dwWriteFlags
& LAYER_UNK_FLAG2
)
530 if (dwWriteFlags
& LAYER_APPLY_TO_SYSTEM_EXES
)
535 while (*start
== ' ' || *start
== '\t')
540 p
= wcspbrk(start
, LAYER_SEPARATORS
);
541 if (!SdbpMatchLayer(start
, p
, wszLayer
))
543 SdbpAppendLayer(newLayer
, sizeof(newLayer
), start
, p
);
548 if (bEnable
&& wszLayer
[0])
550 SdbpAppendLayer(newLayer
, sizeof(newLayer
), wszLayer
, NULL
);
553 return SdbSetPermLayerKeys(wszPath
, newLayer
, bMachine
);