2 * security.c: Security internal calls
5 * Sebastien Pouliot <sebastien@ximian.com>
7 * (C) 2004 Novell (http://www.novell.com)
14 #include <mono/metadata/appdomain.h>
15 #include <mono/metadata/image.h>
16 #include <mono/metadata/exception.h>
17 #include <mono/metadata/object-internals.h>
18 #include <mono/metadata/metadata-internals.h>
19 #include <mono/metadata/security.h>
20 #include <mono/io-layer/io-layer.h>
21 #include <mono/utils/strenc.h>
28 #ifndef PROTECTED_DACL_SECURITY_INFORMATION
29 #define PROTECTED_DACL_SECURITY_INFORMATION 0x80000000L
39 #include <sys/types.h>
46 #ifndef HAVE_GETGRGID_R
47 #warning Non-thread safe getgrgid being used!
49 #ifndef HAVE_GETGRNAM_R
50 #warning Non-thread safe getgrnam being used!
52 #ifndef HAVE_GETPWNAM_R
53 #warning Non-thread safe getpwnam being used!
55 #ifndef HAVE_GETPWUID_R
56 #warning Non-thread safe getpwuid being used!
59 #endif /* defined(__GNUC__) */
61 #endif /* not PLATFORM_WIN32 */
64 /* internal functions - reuse driven */
68 /* ask a server to translate a SID into a textual representation */
70 GetSidName (gunichar2
*server
, PSID sid
, gint32
*size
)
72 gunichar2
*uniname
= NULL
;
75 SID_NAME_USE peUse
; /* out */
77 LookupAccountSid (server
, sid
, NULL
, &cchName
, NULL
,
80 if ((cchName
> 0) && (cchDomain
> 0)) {
81 gunichar2
*user
= g_malloc0 ((cchName
+ 1) * 2);
82 gunichar2
*domain
= g_malloc0 ((cchDomain
+ 1) * 2);
84 LookupAccountSid (server
, sid
, user
, &cchName
, domain
,
89 /* domain/machine name included (+ sepearator) */
90 *size
= cchName
+ cchDomain
+ 1;
91 uniname
= g_malloc0 ((*size
+ 1) * 2);
92 memcpy (uniname
, domain
, cchDomain
* 2);
93 *(uniname
+ cchDomain
) = '\\';
94 memcpy (uniname
+ cchDomain
+ 1, user
, cchName
* 2);
98 /* no domain / machine */
104 /* nothing -> return NULL */
115 #else /* not PLATFORM_WIN32 */
119 GetTokenName (uid_t uid
)
123 #ifdef HAVE_GETPWUID_R
129 struct passwd
*p
= NULL
;
132 #ifdef HAVE_GETPWUID_R
133 #ifdef _SC_GETPW_R_SIZE_MAX
134 fbufsize
= (size_t) sysconf (_SC_GETPW_R_SIZE_MAX
);
136 fbufsize
= (size_t) 1024;
138 fbuf
= g_malloc0 (fbufsize
);
139 retval
= getpwuid_r (uid
, &pwd
, fbuf
, fbufsize
, &p
);
140 result
= ((retval
== 0) && (p
== &pwd
));
142 /* default to non thread-safe but posix compliant function */
144 result
= (p
!= NULL
);
148 uname
= g_strdup (p
->pw_name
);
151 #ifdef HAVE_GETPWUID_R
160 IsMemberInList (uid_t user
, struct group
*g
)
162 gboolean result
= FALSE
;
163 gchar
*utf8_username
= GetTokenName (user
);
169 gchar
**users
= g
->gr_mem
;
173 if (strcmp (utf8_username
, u
) == 0) {
181 g_free (utf8_username
);
187 IsDefaultGroup (uid_t user
, gid_t group
)
189 #ifdef HAVE_GETPWUID_R
195 struct passwd
*p
= NULL
;
198 #ifdef HAVE_GETPWUID_R
199 #ifdef _SC_GETPW_R_SIZE_MAX
200 fbufsize
= (size_t) sysconf (_SC_GETPW_R_SIZE_MAX
);
202 fbufsize
= (size_t) 1024;
205 fbuf
= g_malloc0 (fbufsize
);
206 retval
= getpwuid_r (user
, &pwd
, fbuf
, fbufsize
, &p
);
207 result
= ((retval
== 0) && (p
== &pwd
));
209 /* default to non thread-safe but posix compliant function */
211 result
= (p
!= NULL
);
215 result
= (p
->pw_gid
== group
);
218 #ifdef HAVE_GETPWUID_R
227 IsMemberOf (gid_t user
, struct group
*g
)
232 /* is it the user default group */
233 if (IsDefaultGroup (user
, g
->gr_gid
))
236 /* is the user in the group list */
237 return IsMemberInList (user
, g
);
246 /* System.Environment */
250 ves_icall_System_Environment_get_UserName (void)
254 /* using glib is more portable */
255 return mono_string_new (mono_domain_get (), g_get_user_name ());
259 /* System.Security.Principal.WindowsIdentity */
263 ves_icall_System_Security_Principal_WindowsIdentity_GetCurrentToken (void)
265 gpointer token
= NULL
;
269 #ifdef PLATFORM_WIN32
270 /* Note: This isn't a copy of the Token - we must not close it!!!
271 * http://www.develop.com/kbrown/book/html/whatis_windowsprincipal.html
274 /* thread may be impersonating somebody */
275 if (OpenThreadToken (GetCurrentThread (), TOKEN_QUERY
, 1, &token
) == 0) {
276 /* if not take the process identity */
277 OpenProcessToken (GetCurrentProcess (), TOKEN_QUERY
, &token
);
280 token
= (gpointer
) geteuid ();
287 ves_icall_System_Security_Principal_WindowsIdentity_GetTokenName (gpointer token
)
289 MonoString
*result
= NULL
;
290 gunichar2
*uniname
= NULL
;
293 #ifdef PLATFORM_WIN32
296 GetTokenInformation (token
, TokenUser
, NULL
, size
, (PDWORD
)&size
);
298 TOKEN_USER
*tu
= g_malloc0 (size
);
299 if (GetTokenInformation (token
, TokenUser
, tu
, size
, (PDWORD
)&size
)) {
300 uniname
= GetSidName (NULL
, tu
->User
.Sid
, &size
);
305 gchar
*uname
= GetTokenName ((uid_t
) token
);
310 size
= strlen (uname
);
311 uniname
= g_utf8_to_utf16 (uname
, size
, NULL
, NULL
, NULL
);
314 #endif /* PLATFORM_WIN32 */
317 result
= mono_string_new_utf16 (mono_domain_get (), uniname
, size
);
320 result
= mono_string_new (mono_domain_get (), "");
330 ves_icall_System_Security_Principal_WindowsIdentity_GetUserToken (MonoString
*username
)
332 #ifdef PLATFORM_WIN32
333 gpointer token
= NULL
;
337 /* TODO: MS has something like this working in Windows 2003 (client and
338 * server) but works only for domain accounts (so it's quite limiting).
339 * http://www.develop.com/kbrown/book/html/howto_logonuser.html
341 g_warning ("Unsupported on Win32 (anyway requires W2K3 minimum)");
343 #else /* PLATFORM_WIN32*/
345 #ifdef HAVE_GETPWNAM_R
351 gpointer token
= (gpointer
) -2;
358 utf8_name
= mono_unicode_to_external (mono_string_chars (username
));
360 #ifdef HAVE_GETPWNAM_R
361 #ifdef _SC_GETPW_R_SIZE_MAX
362 fbufsize
= (size_t) sysconf (_SC_GETPW_R_SIZE_MAX
);
364 fbufsize
= (size_t) 1024;
367 fbuf
= g_malloc0 (fbufsize
);
368 retval
= getpwnam_r (utf8_name
, &pwd
, fbuf
, fbufsize
, &p
);
369 result
= ((retval
== 0) && (p
== &pwd
));
371 /* default to non thread-safe but posix compliant function */
372 p
= getpwnam (utf8_name
);
373 result
= (p
!= NULL
);
377 token
= (gpointer
) p
->pw_uid
;
380 #ifdef HAVE_GETPWNAM_R
389 /* http://www.dotnet247.com/247reference/msgs/39/195403.aspx
390 // internal static string[] WindowsIdentity._GetRoles (IntPtr token)
393 ves_icall_System_Security_Principal_WindowsIdentity_GetRoles (gpointer token
)
395 MonoArray
*array
= NULL
;
396 MonoDomain
*domain
= mono_domain_get ();
397 #ifdef PLATFORM_WIN32
402 GetTokenInformation (token
, TokenGroups
, NULL
, size
, (PDWORD
)&size
);
404 TOKEN_GROUPS
*tg
= g_malloc0 (size
);
405 if (GetTokenInformation (token
, TokenGroups
, tg
, size
, (PDWORD
)&size
)) {
407 int num
= tg
->GroupCount
;
409 array
= mono_array_new (domain
, mono_get_string_class (), num
);
411 for (i
=0; i
< num
; i
++) {
413 gunichar2
*uniname
= GetSidName (NULL
, tg
->Groups
[i
].Sid
, &size
);
416 MonoString
*str
= mono_string_new_utf16 (domain
, uniname
, size
);
417 mono_array_set (array
, MonoString
*, i
, str
);
425 /* POSIX-compliant systems should use IsMemberOfGroupId or IsMemberOfGroupName */
426 g_warning ("WindowsIdentity._GetRoles should never be called on POSIX");
429 /* return empty array of string, i.e. string [0] */
430 array
= mono_array_new (domain
, mono_get_string_class (), 0);
436 /* System.Security.Principal.WindowsImpersonationContext */
440 ves_icall_System_Security_Principal_WindowsImpersonationContext_CloseToken (gpointer token
)
442 gboolean result
= TRUE
;
446 #ifdef PLATFORM_WIN32
447 result
= (CloseHandle (token
) != 0);
454 ves_icall_System_Security_Principal_WindowsImpersonationContext_DuplicateToken (gpointer token
)
456 gpointer dupe
= NULL
;
460 #ifdef PLATFORM_WIN32
461 if (DuplicateToken (token
, SecurityImpersonation
, &dupe
) == 0) {
472 ves_icall_System_Security_Principal_WindowsImpersonationContext_SetCurrentToken (gpointer token
)
476 /* Posix version implemented in /mono/mono/io-layer/security.c */
477 return (ImpersonateLoggedOnUser (token
) != 0);
482 ves_icall_System_Security_Principal_WindowsImpersonationContext_RevertToSelf (void)
486 /* Posix version implemented in /mono/mono/io-layer/security.c */
487 return (RevertToSelf () != 0);
491 /* System.Security.Principal.WindowsPrincipal */
494 ves_icall_System_Security_Principal_WindowsPrincipal_IsMemberOfGroupId (gpointer user
, gpointer group
)
496 gboolean result
= FALSE
;
498 #ifdef PLATFORM_WIN32
501 /* The convertion from an ID to a string is done in managed code for Windows */
502 g_warning ("IsMemberOfGroupId should never be called on Win32");
504 #else /* PLATFORM_WIN32 */
506 #ifdef HAVE_GETGRGID_R
512 struct group
*g
= NULL
;
516 #ifdef HAVE_GETGRGID_R
517 #ifdef _SC_GETGR_R_SIZE_MAX
518 fbufsize
= (size_t) sysconf (_SC_GETGR_R_SIZE_MAX
);
520 fbufsize
= (size_t) 1024;
522 fbuf
= g_malloc0 (fbufsize
);
523 retval
= getgrgid_r ((gid_t
) group
, &grp
, fbuf
, fbufsize
, &g
);
524 result
= ((retval
== 0) && (g
== &grp
));
526 /* default to non thread-safe but posix compliant function */
527 g
= getgrgid ((gid_t
) group
);
528 result
= (g
!= NULL
);
532 result
= IsMemberOf ((uid_t
) user
, g
);
535 #ifdef HAVE_GETGRGID_R
539 #endif /* PLATFORM_WIN32 */
546 ves_icall_System_Security_Principal_WindowsPrincipal_IsMemberOfGroupName (gpointer user
, MonoString
*group
)
548 gboolean result
= FALSE
;
550 #ifdef PLATFORM_WIN32
554 /* Windows version use a cache built using WindowsIdentity._GetRoles */
555 g_warning ("IsMemberOfGroupName should never be called on Win32");
557 #else /* PLATFORM_WIN32 */
558 gchar
*utf8_groupname
;
562 utf8_groupname
= mono_unicode_to_external (mono_string_chars (group
));
563 if (utf8_groupname
) {
564 struct group
*g
= NULL
;
565 #ifdef HAVE_GETGRNAM_R
569 #ifdef _SC_GETGR_R_SIZE_MAX
570 size_t fbufsize
= (size_t) sysconf (_SC_GETGR_R_SIZE_MAX
);
572 size_t fbufsize
= (size_t) 1024;
574 fbuf
= g_malloc0 (fbufsize
);
575 retval
= getgrnam_r (utf8_groupname
, &grp
, fbuf
, fbufsize
, &g
);
576 result
= ((retval
== 0) && (g
== &grp
));
578 /* default to non thread-safe but posix compliant function */
579 g
= getgrnam (utf8_groupname
);
580 result
= (g
!= NULL
);
584 result
= IsMemberOf ((uid_t
) user
, g
);
587 #ifdef HAVE_GETGRNAM_R
590 g_free (utf8_groupname
);
592 #endif /* PLATFORM_WIN32 */
598 /* Mono.Security.Cryptography IO related internal calls */
600 #ifdef PLATFORM_WIN32
603 GetAdministratorsSid (void)
605 SID_IDENTIFIER_AUTHORITY admins
= SECURITY_NT_AUTHORITY
;
607 if (!AllocateAndInitializeSid (&admins
, 2, SECURITY_BUILTIN_DOMAIN_RID
,
608 DOMAIN_ALIAS_RID_ADMINS
, 0, 0, 0, 0, 0, 0, &pSid
))
610 /* Note: this SID must be freed with FreeSid () */
616 GetEveryoneSid (void)
618 SID_IDENTIFIER_AUTHORITY everyone
= SECURITY_WORLD_SID_AUTHORITY
;
620 if (!AllocateAndInitializeSid (&everyone
, 1, SECURITY_WORLD_RID
, 0, 0, 0, 0, 0, 0, 0, &pSid
))
622 /* Note: this SID must be freed with FreeSid () */
628 GetCurrentUserSid (void)
632 gpointer token
= ves_icall_System_Security_Principal_WindowsIdentity_GetCurrentToken ();
634 GetTokenInformation (token
, TokenUser
, NULL
, size
, (PDWORD
)&size
);
636 TOKEN_USER
*tu
= g_malloc0 (size
);
637 if (GetTokenInformation (token
, TokenUser
, tu
, size
, (PDWORD
)&size
)) {
638 DWORD length
= GetLengthSid (tu
->User
.Sid
);
639 sid
= (PSID
) g_malloc0 (length
);
640 if (!CopySid (length
, sid
, tu
->User
.Sid
)) {
647 /* Note: this SID must be freed with g_free () */
653 GetRightsFromSid (PSID sid
, PACL acl
)
655 ACCESS_MASK rights
= 0;
658 BuildTrusteeWithSidW (&trustee
, sid
);
659 if (GetEffectiveRightsFromAcl (acl
, &trustee
, &rights
) != ERROR_SUCCESS
)
667 IsMachineProtected (gunichar2
*path
)
669 gboolean success
= FALSE
;
671 PSID pEveryoneSid
= NULL
;
673 DWORD dwRes
= GetNamedSecurityInfoW (path
, SE_FILE_OBJECT
, DACL_SECURITY_INFORMATION
, NULL
, NULL
, &pDACL
, NULL
, NULL
);
674 if (dwRes
!= ERROR_SUCCESS
)
677 /* We check that Everyone is still limited to READ-ONLY -
678 but not if new entries have been added by an Administrator */
680 pEveryoneSid
= GetEveryoneSid ();
682 ACCESS_MASK rights
= GetRightsFromSid (pEveryoneSid
, pDACL
);
683 /* http://msdn.microsoft.com/library/en-us/security/security/generic_access_rights.asp?frame=true */
684 success
= (rights
== (READ_CONTROL
| SYNCHRONIZE
| FILE_READ_DATA
| FILE_READ_EA
| FILE_READ_ATTRIBUTES
));
685 FreeSid (pEveryoneSid
);
687 /* Note: we don't need to check our own access -
688 we'll know soon enough when reading the file */
698 IsUserProtected (gunichar2
*path
)
700 gboolean success
= FALSE
;
702 PSID pEveryoneSid
= NULL
;
704 DWORD dwRes
= GetNamedSecurityInfoW (path
, SE_FILE_OBJECT
,
705 DACL_SECURITY_INFORMATION
, NULL
, NULL
, &pDACL
, NULL
, NULL
);
706 if (dwRes
!= ERROR_SUCCESS
)
709 /* We check that our original entries in the ACL are in place -
710 but not if new entries have been added by the user */
712 /* Everyone should be denied */
713 pEveryoneSid
= GetEveryoneSid ();
715 ACCESS_MASK rights
= GetRightsFromSid (pEveryoneSid
, pDACL
);
716 success
= (rights
== 0);
717 FreeSid (pEveryoneSid
);
719 /* Note: we don't need to check our own access -
720 we'll know soon enough when reading the file */
730 ProtectMachine (gunichar2
*path
)
732 PSID pEveryoneSid
= GetEveryoneSid ();
733 PSID pAdminsSid
= GetAdministratorsSid ();
736 if (pEveryoneSid
&& pAdminsSid
) {
738 EXPLICIT_ACCESS ea
[2];
739 ZeroMemory (&ea
, 2 * sizeof (EXPLICIT_ACCESS
));
741 /* grant all access to the BUILTIN\Administrators group */
742 BuildTrusteeWithSidW (&ea
[0].Trustee
, pAdminsSid
);
743 ea
[0].grfAccessPermissions
= GENERIC_ALL
;
744 ea
[0].grfAccessMode
= SET_ACCESS
;
745 ea
[0].grfInheritance
= SUB_CONTAINERS_AND_OBJECTS_INHERIT
;
746 ea
[0].Trustee
.TrusteeForm
= TRUSTEE_IS_SID
;
747 ea
[0].Trustee
.TrusteeType
= TRUSTEE_IS_WELL_KNOWN_GROUP
;
749 /* read-only access everyone */
750 BuildTrusteeWithSidW (&ea
[1].Trustee
, pEveryoneSid
);
751 ea
[1].grfAccessPermissions
= GENERIC_READ
;
752 ea
[1].grfAccessMode
= SET_ACCESS
;
753 ea
[1].grfInheritance
= SUB_CONTAINERS_AND_OBJECTS_INHERIT
;
754 ea
[1].Trustee
.TrusteeForm
= TRUSTEE_IS_SID
;
755 ea
[1].Trustee
.TrusteeType
= TRUSTEE_IS_WELL_KNOWN_GROUP
;
757 retval
= SetEntriesInAcl (2, ea
, NULL
, &pDACL
);
758 if (retval
== ERROR_SUCCESS
) {
759 /* with PROTECTED_DACL_SECURITY_INFORMATION we */
760 /* remove any existing ACL (like inherited ones) */
761 retval
= SetNamedSecurityInfo (path
, SE_FILE_OBJECT
,
762 DACL_SECURITY_INFORMATION
| PROTECTED_DACL_SECURITY_INFORMATION
,
763 NULL
, NULL
, pDACL
, NULL
);
770 FreeSid (pEveryoneSid
);
772 FreeSid (pAdminsSid
);
773 return (retval
== ERROR_SUCCESS
);
778 ProtectUser (gunichar2
*path
)
782 PSID pCurrentSid
= GetCurrentUserSid ();
786 ZeroMemory (&ea
, sizeof (EXPLICIT_ACCESS
));
788 /* grant exclusive access to the current user */
789 BuildTrusteeWithSidW (&ea
.Trustee
, pCurrentSid
);
790 ea
.grfAccessPermissions
= GENERIC_ALL
;
791 ea
.grfAccessMode
= SET_ACCESS
;
792 ea
.grfInheritance
= SUB_CONTAINERS_AND_OBJECTS_INHERIT
;
793 ea
.Trustee
.TrusteeForm
= TRUSTEE_IS_SID
;
794 ea
.Trustee
.TrusteeType
= TRUSTEE_IS_USER
;
796 retval
= SetEntriesInAcl (1, &ea
, NULL
, &pDACL
);
797 if (retval
== ERROR_SUCCESS
) {
798 /* with PROTECTED_DACL_SECURITY_INFORMATION we
799 remove any existing ACL (like inherited ones) */
800 retval
= SetNamedSecurityInfo (path
, SE_FILE_OBJECT
,
801 DACL_SECURITY_INFORMATION
| PROTECTED_DACL_SECURITY_INFORMATION
,
802 NULL
, NULL
, pDACL
, NULL
);
807 g_free (pCurrentSid
); /* g_malloc0 */
810 return (retval
== ERROR_SUCCESS
);
816 IsProtected (MonoString
*path
, gint32 protection
)
818 gboolean result
= FALSE
;
819 gchar
*utf8_name
= mono_unicode_to_external (mono_string_chars (path
));
822 if (stat (utf8_name
, &st
) == 0) {
823 result
= (((st
.st_mode
& 0777) & protection
) == 0);
832 Protect (MonoString
*path
, gint32 file_mode
, gint32 add_dir_mode
)
834 gboolean result
= FALSE
;
835 gchar
*utf8_name
= mono_unicode_to_external (mono_string_chars (path
));
838 if (stat (utf8_name
, &st
) == 0) {
839 int mode
= file_mode
;
840 if (st
.st_mode
& S_IFDIR
)
841 mode
|= add_dir_mode
;
842 result
= (chmod (utf8_name
, mode
) == 0);
849 #endif /* not PLATFORM_WIN32 */
853 ves_icall_Mono_Security_Cryptography_KeyPairPersistence_CanSecure (MonoString
*root
)
860 /* ACL are nice... unless you have FAT or other uncivilized filesystem */
861 if (!GetVolumeInformation (mono_string_chars (root
), NULL
, 0, NULL
, NULL
, (LPDWORD
)&flags
, NULL
, 0))
863 return ((flags
& FS_PERSISTENT_ACLS
) == FS_PERSISTENT_ACLS
);
866 /* we assume some kind of security is applicable outside Windows */
873 ves_icall_Mono_Security_Cryptography_KeyPairPersistence_IsMachineProtected (MonoString
*path
)
875 gboolean ret
= FALSE
;
879 /* no one, but the owner, should have write access to the directory */
880 #ifdef PLATFORM_WIN32
881 ret
= IsMachineProtected (mono_string_chars (path
));
883 ret
= IsProtected (path
, (S_IWGRP
| S_IWOTH
));
890 ves_icall_Mono_Security_Cryptography_KeyPairPersistence_IsUserProtected (MonoString
*path
)
892 gboolean ret
= FALSE
;
896 /* no one, but the user, should have access to the directory */
897 #ifdef PLATFORM_WIN32
898 ret
= IsUserProtected (mono_string_chars (path
));
900 ret
= IsProtected (path
, (S_IRGRP
| S_IWGRP
| S_IXGRP
| S_IROTH
| S_IWOTH
| S_IXOTH
));
907 ves_icall_Mono_Security_Cryptography_KeyPairPersistence_ProtectMachine (MonoString
*path
)
909 gboolean ret
= FALSE
;
913 /* read/write to owner, read to everyone else */
914 #ifdef PLATFORM_WIN32
915 ret
= ProtectMachine (mono_string_chars (path
));
917 ret
= Protect (path
, (S_IRUSR
| S_IWUSR
| S_IRGRP
| S_IROTH
), (S_IXUSR
| S_IXGRP
| S_IXOTH
));
924 ves_icall_Mono_Security_Cryptography_KeyPairPersistence_ProtectUser (MonoString
*path
)
926 gboolean ret
= FALSE
;
930 /* read/write to user, no access to everyone else */
931 #ifdef PLATFORM_WIN32
932 ret
= ProtectUser (mono_string_chars (path
));
934 ret
= Protect (path
, (S_IRUSR
| S_IWUSR
), S_IXUSR
);
941 * Returns TRUE if there is "something" where the Authenticode signature is
942 * normally located. Returns FALSE is data directory is empty.
944 * Note: Neither the structure nor the signature is verified by this function.
947 ves_icall_System_Security_Policy_Evidence_IsAuthenticodePresent (MonoReflectionAssembly
*refass
)
949 if (refass
&& refass
->assembly
&& refass
->assembly
->image
) {
950 return mono_image_has_authenticode_entry (refass
->assembly
->image
);