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