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*
GetSidName(gunichar2 * server,PSID sid,gint32 * size)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
mono_security_principal_windows_identity_get_current_token(void)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
ves_icall_System_Security_Principal_WindowsIdentity_GetCurrentToken(MonoError * error)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
mono_security_win_get_token_name(gpointer token,gunichar2 ** uniname)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
ves_icall_System_Security_Principal_WindowsIdentity_GetTokenName(gpointer token,MonoError * error)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
ves_icall_System_Security_Principal_WindowsIdentity_GetUserToken(MonoStringHandle username,MonoError * error)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*
ves_icall_System_Security_Principal_WindowsIdentity_GetRoles(gpointer token)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
ves_icall_System_Security_Principal_WindowsImpersonationContext_CloseToken(gpointer token)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
ves_icall_System_Security_Principal_WindowsImpersonationContext_DuplicateToken(gpointer token)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
ves_icall_System_Security_Principal_WindowsPrincipal_IsMemberOfGroupId(gpointer user,gpointer group)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
ves_icall_System_Security_Principal_WindowsPrincipal_IsMemberOfGroupName(gpointer user,MonoString * group)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
GetAdministratorsSid(void)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
GetEveryoneSid(void)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
GetCurrentUserSid(void)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
GetRightsFromSid(PSID sid,PACL acl)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
mono_security_win_is_machine_protected(gunichar2 * path)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
mono_security_win_is_user_protected(gunichar2 * path)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
mono_security_win_protect_machine(gunichar2 * path)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
mono_security_win_protect_user(gunichar2 * path)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
ves_icall_Mono_Security_Cryptography_KeyPairPersistence_CanSecure(MonoString * root)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
ves_icall_Mono_Security_Cryptography_KeyPairPersistence_IsMachineProtected(MonoString * path)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
ves_icall_Mono_Security_Cryptography_KeyPairPersistence_IsUserProtected(MonoString * path)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
ves_icall_Mono_Security_Cryptography_KeyPairPersistence_ProtectMachine(MonoString * path)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
ves_icall_Mono_Security_Cryptography_KeyPairPersistence_ProtectUser(MonoString * path)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 */
503