2 * Copyright (c) 2007 Brian Dessent
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * A copy of the GNU General Public License can be found at
12 * Written by Brian Dessent <brian@dessent.net>
27 #define ALL_INHERIT_ACE (CONTAINER_INHERIT_ACE \
28 | OBJECT_INHERIT_ACE \
30 #define NO_INHERIT_ACE (0)
33 NTSecurity::GetPosixPerms (const char *fname
, PSID owner_sid
, PSID group_sid
,
34 mode_t mode
, SECURITY_DESCRIPTOR
&out_sd
, acl_t
&acl
)
36 DWORD u_attribute
, g_attribute
, o_attribute
;
39 /* Initialize out SD */
40 if (!InitializeSecurityDescriptor (&out_sd
, SECURITY_DESCRIPTOR_REVISION
))
41 Log (LOG_TIMESTAMP
) << "InitializeSecurityDescriptor(" << fname
42 << ") failed: " << GetLastError () << endLog
;
43 out_sd
.Control
|= SE_DACL_PROTECTED
;
45 /* Initialize ACL and fill with almost POSIX-like permissions.
46 Note that the current user always requires write permissions, otherwise
47 creating files in directories with restricted permissions fails. */
48 if (!InitializeAcl (&acl
.acl
, sizeof acl
, ACL_REVISION
))
49 Log (LOG_TIMESTAMP
) << "InitializeAcl(" << fname
<< ") failed: "
50 << GetLastError () << endLog
;
52 /* Default user to current user. */
54 owner_sid
= ownerSID
.user
.User
.Sid
;
55 u_attribute
= STANDARD_RIGHTS_ALL
| FILE_GENERIC_READ
| FILE_GENERIC_WRITE
;
56 if (mode
& 0100) // S_IXUSR
57 u_attribute
|= FILE_GENERIC_EXECUTE
;
58 if ((mode
& 0300) == 0300) // S_IWUSR | S_IXUSR
59 u_attribute
|= FILE_DELETE_CHILD
;
60 if (!AddAccessAllowedAceEx (&acl
.acl
, ACL_REVISION
, NO_INHERIT_ACE
,
61 u_attribute
, owner_sid
))
62 Log (LOG_TIMESTAMP
) << "AddAccessAllowedAceEx(" << fname
63 << ", owner) failed: " << GetLastError () << endLog
;
67 /* Default group to current primary group. */
70 g_attribute
= STANDARD_RIGHTS_READ
| FILE_READ_ATTRIBUTES
;
71 if (mode
& 0040) // S_IRGRP
72 g_attribute
|= FILE_GENERIC_READ
;
73 if (mode
& 0020) // S_IWGRP
74 g_attribute
|= FILE_GENERIC_WRITE
;
75 if (mode
& 0010) // S_IXGRP
76 g_attribute
|= FILE_GENERIC_EXECUTE
;
77 if ((mode
& 01030) == 00030) // S_IWGRP | S_IXGRP, !S_ISVTX
78 g_attribute
|= FILE_DELETE_CHILD
;
79 if (!AddAccessAllowedAceEx (&acl
.acl
, ACL_REVISION
, NO_INHERIT_ACE
,
80 g_attribute
, group_sid
))
81 Log (LOG_TIMESTAMP
) << "AddAccessAllowedAceEx(" << fname
82 << ", group) failed: " << GetLastError () << endLog
;
86 o_attribute
= STANDARD_RIGHTS_READ
| FILE_READ_ATTRIBUTES
;
87 if (mode
& 0004) // S_IROTH
88 o_attribute
|= FILE_GENERIC_READ
;
89 if (mode
& 0002) // S_IWOTH
90 o_attribute
|= FILE_GENERIC_WRITE
;
91 if (mode
& 0001) // S_IXOTH
92 o_attribute
|= FILE_GENERIC_EXECUTE
;
93 if ((mode
& 01003) == 00003) // S_IWOTH | S_IXOTH, !S_ISVTX
94 o_attribute
|= FILE_DELETE_CHILD
;
95 if (!AddAccessAllowedAceEx (&acl
.acl
, ACL_REVISION
, NO_INHERIT_ACE
,
96 o_attribute
, everyOneSID
.theSID ()))
97 Log (LOG_TIMESTAMP
) << "AddAccessAllowedAceEx(" << fname
98 << ", everyone) failed: " << GetLastError () << endLog
;
101 if (mode
& 07000) /* At least one of S_ISUID, S_ISGID, S_ISVTX */
104 if (mode
& 04000) // S_ISUID
105 attribute
|= FILE_APPEND_DATA
;
106 if (mode
& 02000) // S_ISGID
107 attribute
|= FILE_WRITE_DATA
;
108 if (mode
& 01000) // S_ISVTX
109 attribute
|= FILE_READ_DATA
;
110 if (!AddAccessAllowedAceEx (&acl
.acl
, ACL_REVISION
, NO_INHERIT_ACE
,
111 attribute
, nullSID
.theSID ()))
112 Log (LOG_TIMESTAMP
) << "AddAccessAllowedAceEx(" << fname
113 << ", null) failed: " << GetLastError () << endLog
;
117 /* For directories, we also add inherit-only ACEs for CREATOR OWNER,
118 CREATOR GROUP, and EVERYONE (aka OTHER). */
121 if (mode
& 01000) // S_ISVTX
123 /* Don't allow default write permissions for group and other
126 g_attribute
= STANDARD_RIGHTS_READ
| FILE_READ_ATTRIBUTES
;
127 if (mode
& 0040) // S_IRGRP
128 g_attribute
|= FILE_GENERIC_READ
;
129 if (mode
& 0010) // S_IXGRP
130 g_attribute
|= FILE_GENERIC_EXECUTE
;
132 o_attribute
= STANDARD_RIGHTS_READ
| FILE_READ_ATTRIBUTES
;
133 if (mode
& 0004) // S_IROTH
134 o_attribute
|= FILE_GENERIC_READ
;
135 if (mode
& 0001) // S_IXOTH
136 o_attribute
|= FILE_GENERIC_EXECUTE
;
138 if (!AddAccessAllowedAceEx (&acl
.acl
, ACL_REVISION
, ALL_INHERIT_ACE
,
139 u_attribute
, cr_ownerSID
.theSID ()))
140 Log (LOG_TIMESTAMP
) << "AddAccessAllowedAceEx(" << fname
141 << ", creator owner) failed: "
142 << GetLastError () << endLog
;
145 if (!AddAccessAllowedAceEx (&acl
.acl
, ACL_REVISION
, ALL_INHERIT_ACE
,
146 g_attribute
, cr_groupSID
.theSID ()))
147 Log (LOG_TIMESTAMP
) << "AddAccessAllowedAceEx(" << fname
148 << ", creator group) failed: "
149 << GetLastError () << endLog
;
152 if (!AddAccessAllowedAceEx (&acl
.acl
, ACL_REVISION
, ALL_INHERIT_ACE
,
153 o_attribute
, everyOneSID
.theSID ()))
154 Log (LOG_TIMESTAMP
) << "AddAccessAllowedAceEx(" << fname
155 << ", everyone inherit) failed: "
156 << GetLastError () << endLog
;
161 /* Set SD's DACL to just created ACL. */
162 if (!SetSecurityDescriptorDacl (&out_sd
, TRUE
, &acl
.acl
, FALSE
))
163 Log (LOG_TIMESTAMP
) << "SetSecurityDescriptorDacl(" << fname
164 << ") failed: " << GetLastError () << endLog
;
169 NTSecurity::NoteFailedAPI (const std::string
&api
)
171 Log (LOG_TIMESTAMP
) << api
<< "() failed: " << GetLastError () << endLog
;
175 NTSecurity::initialiseWellKnownSIDs ()
177 SID_IDENTIFIER_AUTHORITY n_sid_auth
= { SECURITY_NULL_SID_AUTHORITY
};
178 /* Get the SID for "NULL" S-1-0-0 */
179 if (!AllocateAndInitializeSid (&n_sid_auth
, 1, SECURITY_NULL_RID
,
180 0, 0, 0, 0, 0, 0, 0, &nullSID
.theSID ()))
182 SID_IDENTIFIER_AUTHORITY e_sid_auth
= { SECURITY_WORLD_SID_AUTHORITY
};
183 /* Get the SID for "Everyone" S-1-1-0 */
184 if (!AllocateAndInitializeSid (&e_sid_auth
, 1, SECURITY_WORLD_RID
,
185 0, 0, 0, 0, 0, 0, 0, &everyOneSID
.theSID ()))
187 SID_IDENTIFIER_AUTHORITY nt_sid_auth
= { SECURITY_NT_AUTHORITY
};
188 /* Get the SID for "Administrators" S-1-5-32-544 */
189 if (!AllocateAndInitializeSid (&nt_sid_auth
, 2, SECURITY_BUILTIN_DOMAIN_RID
,
190 DOMAIN_ALIAS_RID_ADMINS
, 0, 0, 0, 0, 0, 0,
191 &administratorsSID
.theSID ()))
193 /* Get the SID for "Users" S-1-5-32-545 */
194 if (!AllocateAndInitializeSid (&nt_sid_auth
, 2, SECURITY_BUILTIN_DOMAIN_RID
,
195 DOMAIN_ALIAS_RID_USERS
, 0, 0, 0, 0, 0, 0,
196 &usersSID
.theSID ()))
198 SID_IDENTIFIER_AUTHORITY c_sid_auth
= { SECURITY_CREATOR_SID_AUTHORITY
};
199 /* Get the SID for "CREATOR OWNER" S-1-3-0 */
200 if (!AllocateAndInitializeSid (&c_sid_auth
, 1, SECURITY_CREATOR_OWNER_RID
,
201 0, 0, 0, 0, 0, 0, 0, &cr_ownerSID
.theSID ()))
203 /* Get the SID for "CREATOR GROUP" S-1-3-1 */
204 if (!AllocateAndInitializeSid (&c_sid_auth
, 1, SECURITY_CREATOR_GROUP_RID
,
205 0, 0, 0, 0, 0, 0, 0, &cr_groupSID
.theSID ()))
207 wellKnownSIDsinitialized (true);
211 NTSecurity::setDefaultDACL ()
213 /* To assure that the created files have a useful ACL, the
214 default DACL in the process token is set to full access to
215 everyone. This applies to files and subdirectories created
216 in directories which don't propagate permissions to child
218 To assure that the files group is meaningful, a token primary
219 group of None is changed to Users or Administrators.
220 This is the fallback if real POSIX permissions don't
221 work for some reason. */
223 /* Create a buffer which has enough room to contain the TOKEN_DEFAULT_DACL
224 structure plus an ACL with one ACE. */
225 size_t bufferSize
= sizeof (ACL
) + sizeof (ACCESS_ALLOWED_ACE
)
226 + GetLengthSid (everyOneSID
.theSID ()) - sizeof (DWORD
);
228 std::unique_ptr
<char[]> buf (new char[bufferSize
]);
230 /* First initialize the TOKEN_DEFAULT_DACL structure. */
231 PACL dacl
= (PACL
) buf
.get ();
233 /* Initialize the ACL for containing one ACE. */
234 if (!InitializeAcl (dacl
, bufferSize
, ACL_REVISION
))
236 NoteFailedAPI ("InitializeAcl");
240 /* Create the ACE which grants full access to "Everyone" and store it
242 if (!AddAccessAllowedAceEx (dacl
, ACL_REVISION
, NO_INHERIT_ACE
,
243 GENERIC_ALL
, everyOneSID
.theSID ()))
245 NoteFailedAPI ("AddAccessAllowedAceEx");
249 /* Set the default DACL to the above computed ACL. */
250 if (!SetTokenInformation (token
.theHANDLE(), TokenDefaultDacl
, &dacl
,
252 NoteFailedAPI ("SetTokenInformation");
256 NTSecurity::setBackupPrivileges ()
258 LUID backup
, restore
;
259 if (!LookupPrivilegeValue (NULL
, SE_BACKUP_NAME
, &backup
))
260 NoteFailedAPI ("LookupPrivilegeValue");
261 else if (!LookupPrivilegeValue (NULL
, SE_RESTORE_NAME
, &restore
))
262 NoteFailedAPI ("LookupPrivilegeValue");
265 PTOKEN_PRIVILEGES new_privs
;
267 new_privs
= (PTOKEN_PRIVILEGES
) alloca (sizeof (TOKEN_PRIVILEGES
)
268 + sizeof (LUID_AND_ATTRIBUTES
));
269 new_privs
->PrivilegeCount
= 2;
270 new_privs
->Privileges
[0].Luid
= backup
;
271 new_privs
->Privileges
[0].Attributes
= SE_PRIVILEGE_ENABLED
;
272 new_privs
->Privileges
[1].Luid
= restore
;
273 new_privs
->Privileges
[1].Attributes
= SE_PRIVILEGE_ENABLED
;
274 if (!AdjustTokenPrivileges (token
.theHANDLE (), FALSE
, new_privs
,
276 NoteFailedAPI ("AdjustTokenPrivileges");
277 else if (GetLastError () == ERROR_NOT_ALL_ASSIGNED
)
278 Log (LOG_TIMESTAMP
) << "User has NO backup/restore rights" << endLog
;
280 Log (LOG_TIMESTAMP
) << "User has backup/restore rights" << endLog
;
285 NTSecurity::resetPrimaryGroup ()
287 if (primaryGroupSID
.pgrp
.PrimaryGroup
)
289 Log (LOG_TIMESTAMP
) << "Changing gid back to original" << endLog
;
290 if (!SetTokenInformation (token
.theHANDLE (), TokenPrimaryGroup
,
291 &primaryGroupSID
, sizeof primaryGroupSID
))
292 NoteFailedAPI ("SetTokenInformation");
297 NTSecurity::setAdminGroup ()
299 TOKEN_PRIMARY_GROUP tpg
;
301 tpg
.PrimaryGroup
= administratorsSID
.theSID ();
302 Log (LOG_TIMESTAMP
) << "Changing gid to Administrators" << endLog
;
303 if (!SetTokenInformation (token
.theHANDLE (), TokenPrimaryGroup
,
305 NoteFailedAPI ("SetTokenInformation");
307 groupSID
= administratorsSID
.theSID ();
311 NTSecurity::setDefaultSecurity (bool isAdmin
)
313 /* Get the processes access token. */
314 if (!OpenProcessToken (GetCurrentProcess (),
315 TOKEN_READ
| TOKEN_ADJUST_DEFAULT
316 | TOKEN_ADJUST_PRIVILEGES
, &token
.theHANDLE ()))
318 NoteFailedAPI ("OpenProcessToken");
322 /* Set backup and restore privileges if available. */
323 setBackupPrivileges ();
325 /* Log if symlink creation privilege is available. */
326 if (!hasSymlinkCreationRights())
327 Log (LOG_TIMESTAMP
) << "User has NO symlink creation right" << endLog
;
329 Log (LOG_TIMESTAMP
) << "User has symlink creation right" << endLog
;
331 /* If initializing the well-known SIDs didn't work, we're finished here. */
332 if (!wellKnownSIDsinitialized ())
335 /* Set the default DACL to all permissions for everyone as a fallback. */
339 if (!GetTokenInformation (token
.theHANDLE (), TokenUser
, &ownerSID
,
340 sizeof ownerSID
, &size
))
342 NoteFailedAPI ("GetTokenInformation(user)");
345 /* Make it the owner */
346 TOKEN_OWNER owner
= { ownerSID
.user
.User
.Sid
};
347 if (!SetTokenInformation (token
.theHANDLE (), TokenOwner
, &owner
,
350 NoteFailedAPI ("SetTokenInformation(owner)");
353 /* Get original primary group. The token's primary group will be reset
354 to the original group right before we call the postinstall scripts.
355 This is necessary, otherwise, if the installing user is a domain user,
356 the group information created by the postinstall calls to `mkpasswd -c,
357 mkgroup -c' will be plain wrong. */
358 if (!GetTokenInformation (token
.theHANDLE (), TokenPrimaryGroup
,
359 &primaryGroupSID
, sizeof primaryGroupSID
, &size
))
361 NoteFailedAPI("GetTokenInformation(pgrp)");
362 primaryGroupSID
.pgrp
.PrimaryGroup
= (PSID
) NULL
;
364 groupSID
= primaryGroupSID
.pgrp
.PrimaryGroup
;
365 /* Try to set the primary group to the Administrators group, but only if
366 "Install for all users" has been chosen. If it doesn't work, we're
367 no admin and that's all there's to say about it. */
373 NTSecurity::isRunAsAdmin ()
375 BOOL is_run_as_admin
= FALSE
;
376 if (!CheckTokenMembership(NULL
, administratorsSID
.theSID (), &is_run_as_admin
))
377 NoteFailedAPI("CheckTokenMembership(administratorsSID)");
378 return (is_run_as_admin
== TRUE
);
382 NTSecurity::hasSymlinkCreationRights ()
385 if (!LookupPrivilegeValue (NULL
, SE_CREATE_SYMBOLIC_LINK_NAME
, &symlink
))
387 NoteFailedAPI ("LookupPrivilegeValue");
392 GetTokenInformation (token
.theHANDLE (), TokenPrivileges
, NULL
, 0, &size
);
393 /* Will fail ERROR_INSUFFICIENT_BUFFER, but updates size */
395 TOKEN_PRIVILEGES
*privileges
= (TOKEN_PRIVILEGES
*)alloca(size
);
396 if (!GetTokenInformation (token
.theHANDLE (), TokenPrivileges
, privileges
,
399 NoteFailedAPI ("GetTokenInformation(privileges)");
404 for (i
= 0; i
< privileges
->PrivilegeCount
; i
++)
406 if (memcmp(&privileges
->Privileges
[i
].Luid
, &symlink
, sizeof(LUID
)) == 0)
415 VersionInfo::VersionInfo ()
417 v
.dwOSVersionInfoSize
= sizeof (OSVERSIONINFO
);
418 if (GetVersionEx (&v
) == 0)
420 Log (LOG_PLAIN
) << "GetVersionEx () failed: " << GetLastError ()
423 /* If GetVersionEx fails we really should bail with an error of some kind,
424 but for now just assume we're on NT and continue. */
425 v
.dwPlatformId
= VER_PLATFORM_WIN32_NT
;
429 /* This is the Construct on First Use idiom to avoid static initialization
431 VersionInfo
& GetVer ()
433 static VersionInfo
*vi
= new VersionInfo ();
437 /* Identify native machine arch if we are running under WoW */
441 typedef BOOL (WINAPI
*PFNISWOW64PROCESS2
)(HANDLE
, USHORT
*, USHORT
*);
442 PFNISWOW64PROCESS2 pfnIsWow64Process2
= (PFNISWOW64PROCESS2
)GetProcAddress(GetModuleHandle("kernel32"), "IsWow64Process2");
444 typedef BOOL (WINAPI
*PFNISWOW64PROCESS
)(HANDLE
, PBOOL
);
445 PFNISWOW64PROCESS pfnIsWow64Process
= (PFNISWOW64PROCESS
)GetProcAddress(GetModuleHandle("kernel32"), "IsWow64Process");
447 USHORT processMachine
, nativeMachine
;
448 if ((pfnIsWow64Process2
) &&
449 (pfnIsWow64Process2(GetCurrentProcess(), &processMachine
, &nativeMachine
)))
450 return nativeMachine
;
451 else if (pfnIsWow64Process
) {
453 BOOL bIsWow64
= FALSE
;
454 if (pfnIsWow64Process(GetCurrentProcess(), &bIsWow64
))
455 return bIsWow64
? IMAGE_FILE_MACHINE_AMD64
: IMAGE_FILE_MACHINE_I386
;
460 return IMAGE_FILE_MACHINE_AMD64
;
462 return IMAGE_FILE_MACHINE_I386
;
467 LoadStringWEx(UINT uID
, UINT langId
)
469 HINSTANCE hInstance
= GetModuleHandle(NULL
);
471 // Convert the string ID into a bundle number
472 LPCSTR bundle
= MAKEINTRESOURCE(uID
/ 16 + 1);
473 HRSRC hRes
= ::FindResourceEx(hInstance
, RT_STRING
, bundle
, langId
);
476 HGLOBAL h
= ::LoadResource(hInstance
, hRes
);
479 HGLOBAL hGlob
= ::LockResource(h
);
481 // walk string bundle
482 wchar_t *buf
= (wchar_t *)hGlob
;
483 for (unsigned int i
= 0; i
< (uID
& 15); i
++)
485 buf
+= 1 + (UINT
)*buf
;
489 return std::wstring(buf
+ 1, len
);
492 // N.B.: Due to the way string bundles are encoded, there's no difference
493 // between an absent string resource whose bundle is present, and a string
494 // resource containing the null string.
499 LoadStringW(unsigned int uID
)
503 int len
= ::LoadStringW(GetModuleHandle(NULL
), uID
, (LPWSTR
)&buf
, 0);
505 return std::wstring(buf
, len
);
507 // if empty or absent, fallback to the untranslated string
508 return LoadStringWEx(uID
, MAKELANGID(LANG_ENGLISH
, SUBLANG_ENGLISH_US
));
512 LoadStringUtf8(unsigned int uID
)
514 return wstring_to_string(LoadStringW(uID
));
518 is_developer_mode(void)
521 LSTATUS err
= RegOpenKeyExW(HKEY_LOCAL_MACHINE
, L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock", 0, KEY_READ
, &hKey
);
522 if (err
!= ERROR_SUCCESS
)
526 DWORD size
= sizeof(DWORD
);
527 err
= RegQueryValueExW(hKey
, L
"AllowDevelopmentWithoutDevLicense", NULL
, NULL
, reinterpret_cast<LPBYTE
>(&value
), &size
);
529 if (err
!= ERROR_SUCCESS
)