Add ProgramData to the env var whitelist for running scripts
[cygwin-setup.git] / win32.cc
blob55072a90466260b769415e3cc40b668e2831679c
1 /*
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
10 * http://www.gnu.org/
12 * Written by Brian Dessent <brian@dessent.net>
16 #include "win32.h"
17 #include <memory>
18 #include <malloc.h>
19 #include "LogFile.h"
20 #include "resource.h"
21 #include "ini.h"
22 #include <sys/stat.h>
23 #include "String++.h"
25 NTSecurity nt_sec;
27 #define ALL_INHERIT_ACE (CONTAINER_INHERIT_ACE \
28 | OBJECT_INHERIT_ACE \
29 | INHERIT_ONLY_ACE)
30 #define NO_INHERIT_ACE (0)
32 PSECURITY_DESCRIPTOR
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;
37 DWORD offset = 0;
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;
51 /* USER */
52 /* Default user to current user. */
53 if (!owner_sid)
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;
64 else
65 offset++;
66 /* GROUP */
67 /* Default group to current primary group. */
68 if (!group_sid)
69 group_sid = groupSID;
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;
83 else
84 offset++;
85 /* OTHER */
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;
99 else
100 offset++;
101 if (mode & 07000) /* At least one of S_ISUID, S_ISGID, S_ISVTX */
103 DWORD attribute = 0;
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;
114 else
115 offset++;
117 /* For directories, we also add inherit-only ACEs for CREATOR OWNER,
118 CREATOR GROUP, and EVERYONE (aka OTHER). */
119 if (mode & S_IFDIR)
121 if (mode & 01000) // S_ISVTX
123 /* Don't allow default write permissions for group and other
124 in a S_ISVTX dir. */
125 /* GROUP */
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;
131 /* OTHER */
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;
143 else
144 offset++;
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;
150 else
151 offset++;
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;
157 else
158 offset++;
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;
165 return &out_sd;
168 void
169 NTSecurity::NoteFailedAPI (const std::string &api)
171 Log (LOG_TIMESTAMP) << api << "() failed: " << GetLastError () << endLog;
174 void
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 ()))
181 return;
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 ()))
186 return;
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 ()))
192 return;
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 ()))
197 return;
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 ()))
202 return;
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 ()))
206 return;
207 wellKnownSIDsinitialized (true);
210 void
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
217 objects.
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");
237 return;
240 /* Create the ACE which grants full access to "Everyone" and store it
241 in dacl. */
242 if (!AddAccessAllowedAceEx (dacl, ACL_REVISION, NO_INHERIT_ACE,
243 GENERIC_ALL, everyOneSID.theSID ()))
245 NoteFailedAPI ("AddAccessAllowedAceEx");
246 return;
249 /* Set the default DACL to the above computed ACL. */
250 if (!SetTokenInformation (token.theHANDLE(), TokenDefaultDacl, &dacl,
251 bufferSize))
252 NoteFailedAPI ("SetTokenInformation");
255 void
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");
263 else
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,
275 0, NULL, NULL))
276 NoteFailedAPI ("AdjustTokenPrivileges");
277 else if (GetLastError () == ERROR_NOT_ALL_ASSIGNED)
278 Log (LOG_TIMESTAMP) << "User has NO backup/restore rights" << endLog;
279 else
280 Log (LOG_TIMESTAMP) << "User has backup/restore rights" << endLog;
284 void
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");
296 void
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,
304 &tpg, sizeof tpg))
305 NoteFailedAPI ("SetTokenInformation");
306 else
307 groupSID = administratorsSID.theSID ();
310 void
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");
319 return;
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;
328 else
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 ())
333 return;
335 /* Set the default DACL to all permissions for everyone as a fallback. */
336 setDefaultDACL ();
338 /* Get the user */
339 if (!GetTokenInformation (token.theHANDLE (), TokenUser, &ownerSID,
340 sizeof ownerSID, &size))
342 NoteFailedAPI ("GetTokenInformation(user)");
343 return;
345 /* Make it the owner */
346 TOKEN_OWNER owner = { ownerSID.user.User.Sid };
347 if (!SetTokenInformation (token.theHANDLE (), TokenOwner, &owner,
348 sizeof owner))
350 NoteFailedAPI ("SetTokenInformation(owner)");
351 return;
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. */
368 if (isAdmin)
369 setAdminGroup ();
372 bool
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);
381 bool
382 NTSecurity::hasSymlinkCreationRights ()
384 LUID symlink;
385 if (!LookupPrivilegeValue (NULL, SE_CREATE_SYMBOLIC_LINK_NAME, &symlink))
387 NoteFailedAPI ("LookupPrivilegeValue");
388 return FALSE;
391 DWORD size;
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,
397 size, &size))
399 NoteFailedAPI ("GetTokenInformation(privileges)");
400 return FALSE;
403 unsigned int i;
404 for (i = 0; i < privileges->PrivilegeCount; i++)
406 if (memcmp(&privileges->Privileges[i].Luid, &symlink, sizeof(LUID)) == 0)
408 return TRUE;
412 return FALSE;
415 VersionInfo::VersionInfo ()
417 v.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
418 if (GetVersionEx (&v) == 0)
420 Log (LOG_PLAIN) << "GetVersionEx () failed: " << GetLastError ()
421 << endLog;
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
430 order problems. */
431 VersionInfo& GetVer ()
433 static VersionInfo *vi = new VersionInfo ();
434 return *vi;
437 /* Identify native machine arch if we are running under WoW */
438 USHORT
439 WowNativeMachine ()
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) {
452 #ifdef _X86_
453 BOOL bIsWow64 = FALSE;
454 if (pfnIsWow64Process(GetCurrentProcess(), &bIsWow64))
455 return bIsWow64 ? IMAGE_FILE_MACHINE_AMD64 : IMAGE_FILE_MACHINE_I386;
456 #endif
459 #ifdef __x86_64__
460 return IMAGE_FILE_MACHINE_AMD64;
461 #else
462 return IMAGE_FILE_MACHINE_I386;
463 #endif
466 const std::wstring
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);
474 if (hRes)
476 HGLOBAL h = ::LoadResource(hInstance, hRes);
477 if (h)
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;
488 int len = *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.
495 return L"";
498 const std::wstring
499 LoadStringW(unsigned int uID)
501 wchar_t *buf;
503 int len = ::LoadStringW(GetModuleHandle(NULL), uID, (LPWSTR)&buf, 0);
504 if (len > 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));
511 const std::string
512 LoadStringUtf8(unsigned int uID)
514 return wstring_to_string(LoadStringW(uID));
517 bool
518 is_developer_mode(void)
520 HKEY hKey;
521 LSTATUS err = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock", 0, KEY_READ, &hKey);
522 if (err != ERROR_SUCCESS)
523 return false;
525 DWORD value;
526 DWORD size = sizeof(DWORD);
527 err = RegQueryValueExW(hKey, L"AllowDevelopmentWithoutDevLicense", NULL, NULL, reinterpret_cast<LPBYTE>(&value), &size);
528 RegCloseKey(hKey);
529 if (err != ERROR_SUCCESS)
530 return false;
532 return value != 0;