Merge pull request #4782 from lambdageek/dev-mono_string_new-excise
[mono.git] / mono / metadata / mono-security-windows.c
1 /**
2  * \file
3  * Windows security support.
4  *
5  * Copyright 2016 Microsoft
6  * Licensed under the MIT license. See LICENSE file in the project root for full license information.
7  */
8 #include <config.h>
9 #include <glib.h>
10
11 #if defined(HOST_WIN32)
12 #include <winsock2.h>
13 #include <windows.h>
14 #include "mono/metadata/mono-security-windows-internals.h"
15
16 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
17 #include <aclapi.h>
18 #include <accctrl.h>
19 #endif
20
21 #ifndef PROTECTED_DACL_SECURITY_INFORMATION
22 #define PROTECTED_DACL_SECURITY_INFORMATION     0x80000000L
23 #endif
24
25 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
26 static gunichar2*
27 GetSidName (gunichar2 *server, PSID sid, gint32 *size)
28 {
29         gunichar2 *uniname = NULL;
30         DWORD cchName = 0;
31         DWORD cchDomain = 0;
32         SID_NAME_USE peUse; /* out */
33
34         LookupAccountSid (server, sid, NULL, &cchName, NULL,
35                 &cchDomain, &peUse);
36
37         if ((cchName > 0) && (cchDomain > 0)) {
38                 gunichar2 *user = g_malloc0 ((cchName + 1) * 2);
39                 gunichar2 *domain = g_malloc0 ((cchDomain + 1) * 2);
40
41                 LookupAccountSid (server, sid, user, &cchName, domain,
42                         &cchDomain, &peUse);
43
44                 if (cchName > 0) {
45                         if (cchDomain > 0) {
46                                 /* domain/machine name included (+ sepearator) */
47                                 *size = cchName + cchDomain + 1;
48                                 uniname = g_malloc0 ((*size + 1) * 2);
49                                 memcpy (uniname, domain, cchDomain * 2);
50                                 *(uniname + cchDomain) = '\\';
51                                 memcpy (uniname + cchDomain + 1, user, cchName * 2);
52                                 g_free (user);
53                         }
54                         else {
55                                 /* no domain / machine */
56                                 *size = cchName;
57                                 uniname = user;
58                         }
59                 }
60                 else {
61                         /* nothing -> return NULL */
62                         g_free (user);
63                 }
64
65                 g_free (domain);
66         }
67
68         return uniname;
69 }
70
71 gpointer
72 ves_icall_System_Security_Principal_WindowsIdentity_GetCurrentToken (void)
73 {
74         gpointer token = NULL;
75
76         /* Note: This isn't a copy of the Token - we must not close it!!!
77          * http://www.develop.com/kbrown/book/html/whatis_windowsprincipal.html
78          */
79
80         /* thread may be impersonating somebody */
81         if (OpenThreadToken (GetCurrentThread (), MAXIMUM_ALLOWED, 1, &token) == 0) {
82                 /* if not take the process identity */
83                 OpenProcessToken (GetCurrentProcess (), MAXIMUM_ALLOWED, &token);
84         }
85
86         return token;
87 }
88
89 gint32
90 mono_security_win_get_token_name (gpointer token, gunichar2 ** uniname)
91 {
92         gint32 size = 0;
93
94         GetTokenInformation (token, TokenUser, NULL, size, (PDWORD)&size);
95         if (size > 0) {
96                 TOKEN_USER *tu = g_malloc0 (size);
97                 if (GetTokenInformation (token, TokenUser, tu, size, (PDWORD)&size)) {
98                         *uniname = GetSidName (NULL, tu->User.Sid, &size);
99                 }
100                 g_free (tu);
101         }
102
103         return size;
104 }
105 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
106
107 MonoString*
108 ves_icall_System_Security_Principal_WindowsIdentity_GetTokenName (gpointer token)
109 {
110         MonoError error;
111         MonoString *result = NULL;
112         gunichar2 *uniname = NULL;
113         gint32 size = 0;
114
115         error_init (&error);
116
117         size = mono_security_win_get_token_name (token, &uniname);
118
119         if (size > 0) {
120                 result = mono_string_new_utf16_checked (mono_domain_get (), uniname, size, &error);
121         }
122         else
123                 result = mono_string_new_checked (mono_domain_get (), "", &error);
124
125         if (uniname)
126                 g_free (uniname);
127
128         mono_error_set_pending_exception (&error);
129         return result;
130 }
131
132 gpointer
133 ves_icall_System_Security_Principal_WindowsIdentity_GetUserToken (MonoString *username)
134 {
135         gpointer token = NULL;
136
137         /* TODO: MS has something like this working in Windows 2003 (client and
138          * server) but works only for domain accounts (so it's quite limiting).
139          * http://www.develop.com/kbrown/book/html/howto_logonuser.html
140          */
141         g_warning ("Unsupported on Win32 (anyway requires W2K3 minimum)");
142         return token;
143 }
144
145 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
146 MonoArray*
147 ves_icall_System_Security_Principal_WindowsIdentity_GetRoles (gpointer token)
148 {
149         MonoError error;
150         MonoArray *array = NULL;
151         MonoDomain *domain = mono_domain_get ();
152
153         gint32 size = 0;
154
155         GetTokenInformation (token, TokenGroups, NULL, size, (PDWORD)&size);
156         if (size > 0) {
157                 TOKEN_GROUPS *tg = g_malloc0 (size);
158                 if (GetTokenInformation (token, TokenGroups, tg, size, (PDWORD)&size)) {
159                         int i=0;
160                         int num = tg->GroupCount;
161
162                         array = mono_array_new_checked (domain, mono_get_string_class (), num, &error);
163                         if (mono_error_set_pending_exception (&error)) {
164                                 g_free (tg);
165                                 return NULL;
166                         }
167
168                         for (i=0; i < num; i++) {
169                                 gint32 size = 0;
170                                 gunichar2 *uniname = GetSidName (NULL, tg->Groups [i].Sid, &size);
171
172                                 if (uniname) {
173                                         MonoString *str = mono_string_new_utf16_checked (domain, uniname, size, &error);
174                                         if (!is_ok (&error)) {
175                                                 g_free (uniname);
176                                                 g_free (tg);
177                                                 mono_error_set_pending_exception (&error);
178                                                 return NULL;
179                                         }
180                                         mono_array_setref (array, i, str);
181                                         g_free (uniname);
182                                 }
183                         }
184                 }
185                 g_free (tg);
186         }
187
188         if (!array) {
189                 /* return empty array of string, i.e. string [0] */
190                 array = mono_array_new_checked (domain, mono_get_string_class (), 0, &error);
191                 mono_error_set_pending_exception (&error);
192         }
193         return array;
194 }
195 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
196
197 gboolean
198 ves_icall_System_Security_Principal_WindowsImpersonationContext_CloseToken (gpointer token)
199 {
200         gboolean result = TRUE;
201         result = (CloseHandle (token) != 0);
202         return result;
203 }
204
205 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
206 gpointer
207 ves_icall_System_Security_Principal_WindowsImpersonationContext_DuplicateToken (gpointer token)
208 {
209         gpointer dupe = NULL;
210
211         if (DuplicateToken (token, SecurityImpersonation, &dupe) == 0) {
212                 dupe = NULL;
213         }
214         return dupe;
215 }
216 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
217
218 gboolean
219 ves_icall_System_Security_Principal_WindowsPrincipal_IsMemberOfGroupId (gpointer user, gpointer group)
220 {
221         gboolean result = FALSE;
222
223         /* The convertion from an ID to a string is done in managed code for Windows */
224         g_warning ("IsMemberOfGroupId should never be called on Win32");
225         return result;
226 }
227
228 gboolean
229 ves_icall_System_Security_Principal_WindowsPrincipal_IsMemberOfGroupName (gpointer user, MonoString *group)
230 {
231         gboolean result = FALSE;
232
233         /* Windows version use a cache built using WindowsIdentity._GetRoles */
234         g_warning ("IsMemberOfGroupName should never be called on Win32");
235         return result;
236 }
237
238 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
239 static PSID
240 GetAdministratorsSid (void)
241 {
242         SID_IDENTIFIER_AUTHORITY admins = { SECURITY_NT_AUTHORITY };
243         PSID pSid = NULL;
244         if (!AllocateAndInitializeSid (&admins, 2, SECURITY_BUILTIN_DOMAIN_RID,
245                 DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pSid))
246                 return NULL;
247         /* Note: this SID must be freed with FreeSid () */
248         return pSid;
249 }
250
251 static PSID
252 GetEveryoneSid (void)
253 {
254         SID_IDENTIFIER_AUTHORITY everyone = { SECURITY_WORLD_SID_AUTHORITY };
255         PSID pSid = NULL;
256         if (!AllocateAndInitializeSid (&everyone, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &pSid))
257                 return NULL;
258         /* Note: this SID must be freed with FreeSid () */
259         return pSid;
260 }
261
262 static PSID
263 GetCurrentUserSid (void)
264 {
265         PSID sid = NULL;
266         guint32 size = 0;
267         gpointer token = ves_icall_System_Security_Principal_WindowsIdentity_GetCurrentToken ();
268
269         GetTokenInformation (token, TokenUser, NULL, size, (PDWORD)&size);
270         if (size > 0) {
271                 TOKEN_USER *tu = g_malloc0 (size);
272                 if (GetTokenInformation (token, TokenUser, tu, size, (PDWORD)&size)) {
273                         DWORD length = GetLengthSid (tu->User.Sid);
274                         sid = (PSID) g_malloc0 (length);
275                         if (!CopySid (length, sid, tu->User.Sid)) {
276                                 g_free (sid);
277                                 sid = NULL;
278                         }
279                 }
280                 g_free (tu);
281         }
282         /* Note: this SID must be freed with g_free () */
283         return sid;
284 }
285
286 static ACCESS_MASK
287 GetRightsFromSid (PSID sid, PACL acl)
288 {
289         ACCESS_MASK rights = 0;
290         TRUSTEE trustee;
291
292         BuildTrusteeWithSidW (&trustee, sid);
293         if (GetEffectiveRightsFromAcl (acl, &trustee, &rights) != ERROR_SUCCESS)
294                 return 0;
295
296         return rights;
297 }
298
299 gboolean
300 mono_security_win_is_machine_protected (gunichar2 *path)
301 {
302         gboolean success = FALSE;
303         PACL pDACL = NULL;
304         PSECURITY_DESCRIPTOR pSD = NULL;
305         PSID pEveryoneSid = NULL;
306
307         DWORD dwRes = GetNamedSecurityInfoW (path, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &pDACL, NULL, &pSD);
308         if (dwRes != ERROR_SUCCESS)
309                 return FALSE;
310
311         /* We check that Everyone is still limited to READ-ONLY -
312         but not if new entries have been added by an Administrator */
313
314         pEveryoneSid = GetEveryoneSid ();
315         if (pEveryoneSid) {
316                 ACCESS_MASK rights = GetRightsFromSid (pEveryoneSid, pDACL);
317                 /* http://msdn.microsoft.com/library/en-us/security/security/generic_access_rights.asp?frame=true */
318                 success = (rights == (READ_CONTROL | SYNCHRONIZE | FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES));
319                 FreeSid (pEveryoneSid);
320         }
321         /* Note: we don't need to check our own access -
322         we'll know soon enough when reading the file */
323
324         if (pSD)
325                 LocalFree (pSD);
326
327         return success;
328 }
329
330 gboolean
331 mono_security_win_is_user_protected (gunichar2 *path)
332 {
333         gboolean success = FALSE;
334         PACL pDACL = NULL;
335         PSID pEveryoneSid = NULL;
336         PSECURITY_DESCRIPTOR pSecurityDescriptor = NULL;
337
338         DWORD dwRes = GetNamedSecurityInfoW (path, SE_FILE_OBJECT,
339                 DACL_SECURITY_INFORMATION, NULL, NULL, &pDACL, NULL, &pSecurityDescriptor);
340         if (dwRes != ERROR_SUCCESS)
341                 return FALSE;
342
343         /* We check that our original entries in the ACL are in place -
344         but not if new entries have been added by the user */
345
346         /* Everyone should be denied */
347         pEveryoneSid = GetEveryoneSid ();
348         if (pEveryoneSid) {
349                 ACCESS_MASK rights = GetRightsFromSid (pEveryoneSid, pDACL);
350                 success = (rights == 0);
351                 FreeSid (pEveryoneSid);
352         }
353         /* Note: we don't need to check our own access -
354         we'll know soon enough when reading the file */
355
356         if (pSecurityDescriptor)
357                 LocalFree (pSecurityDescriptor);
358
359         return success;
360 }
361
362 gboolean
363 mono_security_win_protect_machine (gunichar2 *path)
364 {
365         PSID pEveryoneSid = GetEveryoneSid ();
366         PSID pAdminsSid = GetAdministratorsSid ();
367         DWORD retval = -1;
368
369         if (pEveryoneSid && pAdminsSid) {
370                 PACL pDACL = NULL;
371                 EXPLICIT_ACCESS ea [2];
372                 ZeroMemory (&ea, 2 * sizeof (EXPLICIT_ACCESS));
373
374                 /* grant all access to the BUILTIN\Administrators group */
375                 BuildTrusteeWithSidW (&ea [0].Trustee, pAdminsSid);
376                 ea [0].grfAccessPermissions = GENERIC_ALL;
377                 ea [0].grfAccessMode = SET_ACCESS;
378                 ea [0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
379                 ea [0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
380                 ea [0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
381
382                 /* read-only access everyone */
383                 BuildTrusteeWithSidW (&ea [1].Trustee, pEveryoneSid);
384                 ea [1].grfAccessPermissions = GENERIC_READ;
385                 ea [1].grfAccessMode = SET_ACCESS;
386                 ea [1].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
387                 ea [1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
388                 ea [1].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
389
390                 retval = SetEntriesInAcl (2, ea, NULL, &pDACL);
391                 if (retval == ERROR_SUCCESS) {
392                         /* with PROTECTED_DACL_SECURITY_INFORMATION we */
393                         /* remove any existing ACL (like inherited ones) */
394                         retval = SetNamedSecurityInfo (path, SE_FILE_OBJECT,
395                                 DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
396                                 NULL, NULL, pDACL, NULL);
397                 }
398                 if (pDACL)
399                         LocalFree (pDACL);
400         }
401
402         if (pEveryoneSid)
403                 FreeSid (pEveryoneSid);
404         if (pAdminsSid)
405                 FreeSid (pAdminsSid);
406         return (retval == ERROR_SUCCESS);
407 }
408
409 gboolean
410 mono_security_win_protect_user (gunichar2 *path)
411 {
412         DWORD retval = -1;
413
414         PSID pCurrentSid = GetCurrentUserSid ();
415         if (pCurrentSid) {
416                 PACL pDACL = NULL;
417                 EXPLICIT_ACCESS ea;
418                 ZeroMemory (&ea, sizeof (EXPLICIT_ACCESS));
419
420                 /* grant exclusive access to the current user */
421                 BuildTrusteeWithSidW (&ea.Trustee, pCurrentSid);
422                 ea.grfAccessPermissions = GENERIC_ALL;
423                 ea.grfAccessMode = SET_ACCESS;
424                 ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
425                 ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
426                 ea.Trustee.TrusteeType = TRUSTEE_IS_USER;
427
428                 retval = SetEntriesInAcl (1, &ea, NULL, &pDACL);
429                 if (retval == ERROR_SUCCESS) {
430                         /* with PROTECTED_DACL_SECURITY_INFORMATION we
431                            remove any existing ACL (like inherited ones) */
432                         retval = SetNamedSecurityInfo (path, SE_FILE_OBJECT,
433                                 DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
434                                 NULL, NULL, pDACL, NULL);
435                 }
436
437                 if (pDACL)
438                         LocalFree (pDACL);
439                 g_free (pCurrentSid); /* g_malloc0 */
440         }
441
442         return (retval == ERROR_SUCCESS);
443 }
444 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
445
446 MonoBoolean
447 ves_icall_Mono_Security_Cryptography_KeyPairPersistence_CanSecure (MonoString *root)
448 {
449         gint32 flags;
450
451         /* ACL are nice... unless you have FAT or other uncivilized filesystem */
452         if (!GetVolumeInformation (mono_string_chars (root), NULL, 0, NULL, NULL, (LPDWORD)&flags, NULL, 0))
453                 return FALSE;
454         return ((flags & FS_PERSISTENT_ACLS) == FS_PERSISTENT_ACLS);
455 }
456
457 MonoBoolean
458 ves_icall_Mono_Security_Cryptography_KeyPairPersistence_IsMachineProtected (MonoString *path)
459 {
460         gboolean ret = FALSE;
461
462         /* no one, but the owner, should have write access to the directory */
463         ret = mono_security_win_is_machine_protected (mono_string_chars (path));
464         return (MonoBoolean)ret;
465 }
466
467 MonoBoolean
468 ves_icall_Mono_Security_Cryptography_KeyPairPersistence_IsUserProtected (MonoString *path)
469 {
470         gboolean ret = FALSE;
471
472         /* no one, but the user, should have access to the directory */
473         ret = mono_security_win_is_user_protected (mono_string_chars (path));
474         return (MonoBoolean)ret;
475 }
476
477 MonoBoolean
478 ves_icall_Mono_Security_Cryptography_KeyPairPersistence_ProtectMachine (MonoString *path)
479 {
480         gboolean ret = FALSE;
481
482         /* read/write to owner, read to everyone else */
483         ret = mono_security_win_protect_machine (mono_string_chars (path));
484         return (MonoBoolean)ret;
485 }
486
487 MonoBoolean
488 ves_icall_Mono_Security_Cryptography_KeyPairPersistence_ProtectUser (MonoString *path)
489 {
490         gboolean ret = FALSE;
491
492         /* read/write to user, no access to everyone else */
493         ret = mono_security_win_protect_user (mono_string_chars (path));
494         return (MonoBoolean)ret;
495 }
496 #endif /* HOST_WIN32 */