1 /* sec/base.cc: NT file access control functions
3 Originaly written by Gunther Ebert, gunther.ebert@ixos-leipzig.de
4 Completely rewritten by Corinna Vinschen <corinna@vinschen.de>
6 This file is part of Cygwin.
8 This software is a copyrighted work licensed under the terms of the
9 Cygwin license. Please consult the file "CYGWIN_LICENSE" for
15 #include <cygwin/acl.h>
27 #define ALL_SECURITY_INFORMATION (DACL_SECURITY_INFORMATION \
28 | GROUP_SECURITY_INFORMATION \
29 | OWNER_SECURITY_INFORMATION)
31 static GENERIC_MAPPING NO_COPY_RO file_mapping
= { FILE_GENERIC_READ
,
36 get_file_sd (HANDLE fh
, path_conv
&pc
, security_descriptor
&sd
,
39 NTSTATUS status
= STATUS_SUCCESS
;
40 OBJECT_ATTRIBUTES attr
;
42 ULONG len
= SD_MAXIMUM_SIZE
, rlen
;
44 /* Allocate space for the security descriptor. */
50 /* Try to fetch the security descriptor if the handle is valid. */
53 status
= NtQuerySecurityObject (fh
, ALL_SECURITY_INFORMATION
,
55 if (!NT_SUCCESS (status
))
56 debug_printf ("NtQuerySecurityObject (%S), status %y",
57 pc
.get_nt_native_path (), status
);
59 /* If the handle was NULL, or fetching with the original handle didn't work,
60 try to reopen the file with READ_CONTROL and fetch the security descriptor
62 if (!fh
|| !NT_SUCCESS (status
))
64 status
= NtOpenFile (&fh
, READ_CONTROL
,
65 fh
? pc
.init_reopen_attr (attr
, fh
)
66 : pc
.get_object_attr (attr
, sec_none_nih
),
67 &io
, FILE_SHARE_VALID_FLAGS
,
69 | FILE_OPEN_FOR_BACKUP_INTENT
70 | pc
.is_known_reparse_point ()
71 ? FILE_OPEN_REPARSE_POINT
: 0);
72 if (!NT_SUCCESS (status
))
75 __seterrno_from_nt_status (status
);
78 status
= NtQuerySecurityObject (fh
, ALL_SECURITY_INFORMATION
,
81 if (!NT_SUCCESS (status
))
84 __seterrno_from_nt_status (status
);
88 /* We have a security descriptor now. Unfortunately, if you want to know
89 if an ACE is inherited from the parent object, this isn't sufficient.
91 In the simple case, the SDs control word contains one of the
92 SE_DACL_AUTO_INHERITED or SE_DACL_PROTECTED flags, or at least one of
93 the ACEs has the INHERITED_ACE flag set. In all of these cases we
94 know the DACL has been inherited.
96 If none of these flags is set in the SD, the information whether
97 or not an ACE has been inherited is not available in the DACL of the
98 object. In this case GetSecurityInfo fetches the SD from the parent
99 directory and tests if the object's SD contains inherited ACEs from the
102 Note that we're not testing the SE_DACL_AUTO_INHERITED and
103 SE_DACL_PROTECTED flags here because we know the state the file's SD
104 is in. Since we're creating all files with a NULL descriptor, the DACL
105 is either inherited from the parent, or it's the default DACL. In
106 neither case, one of these flags is set.
108 For speed, we're not calling RtlConvertToAutoInheritSecurityObject
109 anymore (but keep the code here for reference). Rather we just test
110 if one of the parent's ACEs is inheritable. If so, we know we inherited
111 it and set the SE_DACL_AUTO_INHERITED flag. If not, we may assume our
112 object's DACL is the default DACL.
114 This functionality is slow and the extra information is only required
115 when the file has been created and the permissions are about to be set
116 to POSIX permissions. Therefore we only use it in case the file just
122 ACCESS_ALLOWED_ACE
*ace
;
123 UNICODE_STRING dirname
;
124 PSECURITY_DESCRIPTOR psd
;
127 /* Open the parent directory with READ_CONTROL... */
128 RtlSplitUnicodePath (pc
.get_nt_native_path (), &dirname
, NULL
);
129 InitializeObjectAttributes (&attr
, &dirname
, pc
.objcaseinsensitive (),
131 status
= NtOpenFile (&fh
, READ_CONTROL
, &attr
, &io
,
132 FILE_SHARE_VALID_FLAGS
,
134 | FILE_OPEN_FOR_BACKUP_INTENT
135 | FILE_OPEN_REPARSE_POINT
);
136 if (!NT_SUCCESS (status
))
138 debug_printf ("NtOpenFile (%S), status %y", &dirname
, status
);
141 /* ... fetch the parent's security descriptor ... */
142 psd
= (PSECURITY_DESCRIPTOR
) tp
.w_get ();
143 status
= NtQuerySecurityObject (fh
, ALL_SECURITY_INFORMATION
,
146 if (!NT_SUCCESS (status
))
148 debug_printf ("NtQuerySecurityObject (%S), status %y",
153 /* ... and create a new security descriptor in which all inherited ACEs
154 are marked with the INHERITED_ACE flag. For a description of the
155 undocumented RtlConvertToAutoInheritSecurityObject function from
156 ntdll.dll see the MSDN man page for the advapi32 function
157 ConvertToAutoInheritPrivateObjectSecurity. Fortunately the latter
159 PSECURITY_DESCRIPTOR nsd
;
160 status
= RtlConvertToAutoInheritSecurityObject (psd
, sd
, &nsd
, NULL
,
163 if (!NT_SUCCESS (status
))
165 debug_printf ("RtlConvertToAutoInheritSecurityObject (%S), status %y",
169 /* Eventually copy the new security descriptor into sd and delete the
170 original one created by RtlConvertToAutoInheritSecurityObject from
172 len
= RtlLengthSecurityDescriptor (nsd
);
173 memcpy ((PSECURITY_DESCRIPTOR
) sd
, nsd
, len
);
174 RtlDeleteSecurityObject (&nsd
);
176 /* ... and check the parent descriptor for inheritable ACEs matching
177 our current object type (file/dir). The simple truth in our case
178 is, either the parent dir had inheritable ACEs and all our ACEs are
179 inherited, or the parent dir didn't have inheritable ACEs and all
180 our ACEs are taken from the default DACL. */
181 bool inherited
= false;
182 BYTE search_flags
= pc
.isdir () ? SUB_CONTAINERS_AND_OBJECTS_INHERIT
183 : SUB_OBJECTS_ONLY_INHERIT
;
184 if (NT_SUCCESS (RtlGetDaclSecurityDescriptor (psd
, &exists
, &dacl
, &def
))
186 for (ULONG idx
= 0; idx
< dacl
->AceCount
; ++idx
)
187 if (NT_SUCCESS (RtlGetAce (dacl
, idx
, (PVOID
*) &ace
))
188 && (ace
->Header
.AceFlags
& search_flags
))
193 /* Then, if the parent descriptor contained inheritable ACEs, we mark
194 the SD as SE_DACL_AUTO_INHERITED. Note that this requires the
195 matching check in get_posix_access. If we ever revert to
196 RtlConvertToAutoInheritSecurityObject, the check in get_posix_access
197 has to test every single ACE for the INHERITED_ACE flag again. */
199 && NT_SUCCESS (RtlGetDaclSecurityDescriptor (sd
, &exists
, &dacl
,
202 RtlSetControlSecurityDescriptor (sd
, SE_DACL_AUTO_INHERITED
,
203 SE_DACL_AUTO_INHERITED
);
210 set_file_sd (HANDLE fh
, path_conv
&pc
, security_descriptor
&sd
, bool is_chown
)
212 NTSTATUS status
= STATUS_SUCCESS
;
216 for (; retry
< 2; ++retry
)
220 status
= NtSetSecurityObject (fh
,
221 is_chown
? ALL_SECURITY_INFORMATION
222 : DACL_SECURITY_INFORMATION
,
224 if (NT_SUCCESS (status
))
232 OBJECT_ATTRIBUTES attr
;
234 status
= NtOpenFile (&fh
, (is_chown
? WRITE_OWNER
: 0) | WRITE_DAC
,
235 fh
? pc
.init_reopen_attr (attr
, fh
)
236 : pc
.get_object_attr (attr
, sec_none_nih
),
238 FILE_SHARE_VALID_FLAGS
,
240 | FILE_OPEN_FOR_BACKUP_INTENT
241 | pc
.is_known_reparse_point ()
242 ? FILE_OPEN_REPARSE_POINT
: 0);
243 if (!NT_SUCCESS (status
))
252 if (!NT_SUCCESS (status
))
253 __seterrno_from_nt_status (status
);
258 get_reg_sd (HANDLE handle
, security_descriptor
&sd_ret
)
263 ret
= RegGetKeySecurity ((HKEY
) handle
, ALL_SECURITY_INFORMATION
,
265 if (ret
== ERROR_INSUFFICIENT_BUFFER
)
267 if (!sd_ret
.malloc (len
))
270 ret
= RegGetKeySecurity ((HKEY
) handle
, ALL_SECURITY_INFORMATION
,
273 if (ret
!= ERROR_SUCCESS
)
282 get_reg_attribute (HKEY hkey
, mode_t
&attribute
, uid_t
*uidret
,
285 security_descriptor sd
;
287 if (!get_reg_sd (hkey
, sd
))
289 get_posix_access (sd
, attribute
, uidret
, gidret
, NULL
, 0);
292 /* The entries are already set to default values */
297 get_file_attribute (HANDLE handle
, path_conv
&pc
,
298 mode_t
&attribute
, uid_t
*uidret
, gid_t
*gidret
)
302 security_descriptor sd
;
304 if (!get_file_sd (handle
, pc
, sd
, false))
306 get_posix_access (sd
, attribute
, uidret
, gidret
, NULL
, 0);
309 /* ENOSYS is returned by get_file_sd if fetching the DACL from a remote
310 share returns STATUS_INVALID_NETWORK_RESPONSE, which in turn is
311 converted to ERROR_BAD_NET_RESP. This potentially occurs when trying
312 to fetch DACLs from a NT4 machine which is not part of the domain of
313 the requesting machine.
314 FIXME: We dropped NT4 support, but are there other scenarios? */
315 else if (get_errno () != ENOSYS
)
318 *uidret
= ILLEGAL_UID
;
320 *gidret
= ILLEGAL_GID
;
327 *uidret
= myself
->uid
;
329 *gidret
= myself
->gid
;
335 add_access_allowed_ace (PACL acl
, DWORD attributes
, PSID sid
, size_t &len_add
,
338 NTSTATUS status
= RtlAddAccessAllowedAceEx (acl
, ACL_REVISION
, inherit
,
340 if (!NT_SUCCESS (status
))
342 __seterrno_from_nt_status (status
);
345 len_add
+= sizeof (ACCESS_ALLOWED_ACE
) - sizeof (DWORD
) + RtlLengthSid (sid
);
350 add_access_denied_ace (PACL acl
, DWORD attributes
, PSID sid
, size_t &len_add
,
353 NTSTATUS status
= RtlAddAccessDeniedAceEx (acl
, ACL_REVISION
, inherit
,
355 if (!NT_SUCCESS (status
))
357 __seterrno_from_nt_status (status
);
360 len_add
+= sizeof (ACCESS_DENIED_ACE
) - sizeof (DWORD
) + RtlLengthSid (sid
);
365 set_security_attribute (path_conv
&pc
, int attribute
, PSECURITY_ATTRIBUTES psa
,
366 security_descriptor
&sd
)
368 psa
->lpSecurityDescriptor
= sd
.malloc (SECURITY_DESCRIPTOR_MIN_LENGTH
);
369 RtlCreateSecurityDescriptor ((PSECURITY_DESCRIPTOR
) psa
->lpSecurityDescriptor
,
370 SECURITY_DESCRIPTOR_REVISION
);
371 psa
->lpSecurityDescriptor
= set_posix_access (attribute
, geteuid (),
377 get_object_sd (HANDLE handle
, security_descriptor
&sd
)
382 status
= NtQuerySecurityObject (handle
, ALL_SECURITY_INFORMATION
,
384 if (status
!= STATUS_BUFFER_TOO_SMALL
)
386 __seterrno_from_nt_status (status
);
389 if (!sd
.malloc (len
))
394 status
= NtQuerySecurityObject (handle
, ALL_SECURITY_INFORMATION
,
396 if (!NT_SUCCESS (status
))
398 __seterrno_from_nt_status (status
);
405 get_object_attribute (HANDLE handle
, uid_t
*uidret
, gid_t
*gidret
,
408 security_descriptor sd
;
410 if (get_object_sd (handle
, sd
))
412 return get_posix_access (sd
, attribute
, uidret
, gidret
, NULL
, 0)
417 create_object_sd_from_attribute (uid_t uid
, gid_t gid
, mode_t attribute
,
418 security_descriptor
&sd
)
420 return set_posix_access (attribute
, uid
, gid
, NULL
, 0, sd
, false)
425 set_object_sd (HANDLE handle
, security_descriptor
&sd
, bool chown
)
428 status
= NtSetSecurityObject (handle
, chown
? ALL_SECURITY_INFORMATION
429 : DACL_SECURITY_INFORMATION
, sd
);
430 if (!NT_SUCCESS (status
))
432 __seterrno_from_nt_status (status
);
439 set_object_attribute (HANDLE handle
, uid_t uid
, gid_t gid
, mode_t attribute
)
441 security_descriptor sd
;
443 if (create_object_sd_from_attribute (uid
, gid
, attribute
, sd
)
444 || set_object_sd (handle
, sd
, uid
!= ILLEGAL_UID
|| gid
!= ILLEGAL_GID
))
450 set_created_file_access (HANDLE handle
, path_conv
&pc
, mode_t attr
)
453 security_descriptor sd
, sd_ret
;
462 if (!get_file_sd (handle
, pc
, sd
, true))
464 attr
|= S_JUSTCREATED
;
468 aclp
= (aclent_t
*) tp
.c_get ();
469 if ((nentries
= get_posix_access (sd
, attr_rd
, &uid
, &gid
, aclp
,
470 MAX_ACL_ENTRIES
, &std_acl
)) >= 0)
474 /* Symlinks always get the request POSIX perms. */
475 aclp
[0].a_perm
= (attr
>> 6) & S_IRWXO
;
476 if ((idx
= searchace (aclp
, nentries
, GROUP_OBJ
)) >= 0)
477 aclp
[idx
].a_perm
= (attr
>> 3) & S_IRWXO
;
478 if ((idx
= searchace (aclp
, nentries
, CLASS_OBJ
)) >= 0)
479 aclp
[idx
].a_perm
= (attr
>> 3) & S_IRWXO
;
480 if ((idx
= searchace (aclp
, nentries
, OTHER_OBJ
)) >= 0)
481 aclp
[idx
].a_perm
= attr
& S_IRWXO
;
485 /* Overwrite ACL permissions as required by POSIX 1003.1e
487 aclp
[0].a_perm
&= (attr
>> 6) & S_IRWXO
;
488 if ((idx
= searchace (aclp
, nentries
, CLASS_OBJ
)) >= 0)
489 aclp
[idx
].a_perm
&= (attr
>> 3) & S_IRWXO
;
491 && (idx
= searchace (aclp
, nentries
, GROUP_OBJ
)) >= 0)
492 aclp
[idx
].a_perm
&= (attr
>> 3) & S_IRWXO
;
493 if ((idx
= searchace (aclp
, nentries
, OTHER_OBJ
)) >= 0)
494 aclp
[idx
].a_perm
&= attr
& S_IRWXO
;
496 /* Construct appropriate inherit attribute for new directories.
497 Basically we do this only for the sake of non-Cygwin applications.
498 Cygwin applications don't need these. Additionally, if the
499 S_ISGID bit is set, propagate it. */
502 mode_t def_attr
= attr
& ~cygheap
->umask
;
504 if (searchace (aclp
, nentries
, DEF_USER_OBJ
) < 0)
506 aclp
[nentries
].a_type
= DEF_USER_OBJ
;
507 aclp
[nentries
].a_id
= ILLEGAL_UID
;
508 aclp
[nentries
++].a_perm
= (def_attr
>> 6) & S_IRWXO
;
510 if (searchace (aclp
, nentries
, DEF_GROUP_OBJ
) < 0)
512 aclp
[nentries
].a_type
= DEF_GROUP_OBJ
;
513 aclp
[nentries
].a_id
= ILLEGAL_GID
;
514 aclp
[nentries
++].a_perm
= (def_attr
>> 3) & S_IRWXO
;
516 if (searchace (aclp
, nentries
, DEF_OTHER_OBJ
) < 0)
518 aclp
[nentries
].a_type
= DEF_OTHER_OBJ
;
519 aclp
[nentries
].a_id
= ILLEGAL_UID
;
520 aclp
[nentries
++].a_perm
= def_attr
& S_IRWXO
;
522 if (attr_rd
& S_ISGID
)
525 if (set_posix_access (attr
, uid
, gid
, aclp
, nentries
, sd_ret
,
527 ret
= set_file_sd (handle
, pc
, sd_ret
, attr_rd
& S_ISGID
);
534 check_access (security_descriptor
&sd
, GENERIC_MAPPING
&mapping
,
535 ACCESS_MASK desired
, int flags
, bool effective
)
538 NTSTATUS status
, allow
;
540 DWORD plen
= sizeof (PRIVILEGE_SET
) + 3 * sizeof (LUID_AND_ATTRIBUTES
);
541 PPRIVILEGE_SET pset
= (PPRIVILEGE_SET
) alloca (plen
);
542 HANDLE tok
= ((effective
&& cygheap
->user
.issetuid ())
543 ? cygheap
->user
.imp_token ()
548 if (!DuplicateTokenEx (hProcToken
, MAXIMUM_ALLOWED
, NULL
,
549 SecurityImpersonation
, TokenImpersonation
,
558 status
= NtAccessCheck (sd
, tok
, desired
, &mapping
, pset
, &plen
, &granted
,
560 if (!NT_SUCCESS (status
))
562 else if (!NT_SUCCESS (allow
))
564 /* CV, 2006-10-16: Now, that's really weird. Imagine a user who has no
565 standard access to a file, but who has backup and restore privileges
566 and these privileges are enabled in the access token. One would
567 expect that the AccessCheck function takes this into consideration
568 when returning the access status. Otherwise, why bother with the
569 pset parameter, right?
570 But not so. AccessCheck actually returns a status of "false" here,
571 even though opening a file with backup resp. restore intent
572 naturally succeeds for this user. This definitely spoils the results
573 of access(2) for administrative users or the SYSTEM account. So, in
574 case the access check fails, another check against the user's
575 backup/restore privileges has to be made. Sigh. */
576 int granted_flags
= 0;
581 pset
->PrivilegeCount
= 1;
583 pset
->Privilege
[0].Luid
.HighPart
= 0L;
584 pset
->Privilege
[0].Luid
.LowPart
= SE_BACKUP_PRIVILEGE
;
585 pset
->Privilege
[0].Attributes
= 0;
586 status
= NtPrivilegeCheck (tok
, pset
, &has_priv
);
587 if (NT_SUCCESS (status
) && has_priv
)
588 granted_flags
|= R_OK
;
592 pset
->PrivilegeCount
= 1;
594 pset
->Privilege
[0].Luid
.HighPart
= 0L;
595 pset
->Privilege
[0].Luid
.LowPart
= SE_RESTORE_PRIVILEGE
;
596 pset
->Privilege
[0].Attributes
= 0;
597 status
= NtPrivilegeCheck (tok
, pset
, &has_priv
);
598 if (NT_SUCCESS (status
) && has_priv
)
599 granted_flags
|= W_OK
;
601 if (granted_flags
== flags
)
611 /* Samba override. Check security descriptor for Samba UNIX user and group
612 accounts and check if we have an RFC 2307 mapping to a Windows account.
613 Create a new security descriptor with all of the UNIX accounts with
614 valid mapping replaced with their Windows counterpart. */
616 convert_samba_sd (security_descriptor
&sd_ret
)
623 SECURITY_DESCRIPTOR sd
;
628 PACCESS_ALLOWED_ACE ace
;
630 if (!NT_SUCCESS (RtlGetOwnerSecurityDescriptor (sd_ret
, &sid
, &dummy
)))
633 if (!NT_SUCCESS (RtlGetGroupSecurityDescriptor (sd_ret
, &sid
, &dummy
)))
637 if (sid_id_auth (owner
) == 22)
640 uid_t uid
= owner
.get_uid (&cldap
);
641 if (uid
< UNIX_POSIX_OFFSET
&& (pwd
= internal_getpwuid (uid
)))
642 owner
.getfrompw (pwd
);
644 if (sid_id_auth (group
) == 22)
647 gid_t gid
= group
.get_gid (&cldap
);
648 if (gid
< UNIX_POSIX_OFFSET
&& (grp
= internal_getgrgid (gid
)))
649 group
.getfromgr (grp
);
652 if (!NT_SUCCESS (RtlGetDaclSecurityDescriptor (sd_ret
, &dummy
,
655 acl
= (PACL
) tp
.w_get ();
656 RtlCreateAcl (acl
, ACL_MAXIMUM_SIZE
, ACL_REVISION
);
657 acl_len
= sizeof (ACL
);
659 for (DWORD i
= 0; i
< oacl
->AceCount
; ++i
)
660 if (NT_SUCCESS (RtlGetAce (oacl
, i
, (PVOID
*) &ace
)))
662 cygsid
ace_sid ((PSID
) &ace
->SidStart
);
663 if (sid_id_auth (ace_sid
) == 22)
665 if (sid_sub_auth (ace_sid
, 0) == 1) /* user */
668 uid_t uid
= ace_sid
.get_uid (&cldap
);
669 if (uid
< UNIX_POSIX_OFFSET
&& (pwd
= internal_getpwuid (uid
)))
670 ace_sid
.getfrompw (pwd
);
672 else if (sid_sub_auth (ace_sid
, 0) == 2) /* group */
675 gid_t gid
= ace_sid
.get_gid (&cldap
);
676 if (gid
< UNIX_POSIX_OFFSET
&& (grp
= internal_getgrgid (gid
)))
677 ace_sid
.getfromgr (grp
);
680 if (!add_access_allowed_ace (acl
, ace
->Mask
, ace_sid
, acl_len
,
681 ace
->Header
.AceFlags
))
684 acl
->AclSize
= acl_len
;
686 RtlCreateSecurityDescriptor (&sd
, SECURITY_DESCRIPTOR_REVISION
);
687 RtlSetControlSecurityDescriptor (&sd
, SE_DACL_PROTECTED
, SE_DACL_PROTECTED
);
688 RtlSetOwnerSecurityDescriptor (&sd
, owner
, FALSE
);
689 RtlSetGroupSecurityDescriptor (&sd
, group
, FALSE
);
691 status
= RtlSetDaclSecurityDescriptor (&sd
, TRUE
, acl
, FALSE
);
692 if (!NT_SUCCESS (status
))
695 status
= RtlAbsoluteToSelfRelativeSD (&sd
, sd_ret
, &sd_size
);
696 if (sd_size
> 0 && sd_ret
.malloc (sd_size
))
697 RtlAbsoluteToSelfRelativeSD (&sd
, sd_ret
, &sd_size
);
701 check_file_access (path_conv
&pc
, int flags
, bool effective
)
703 security_descriptor sd
;
705 ACCESS_MASK desired
= 0;
707 desired
|= FILE_READ_DATA
;
709 desired
|= FILE_WRITE_DATA
;
711 desired
|= FILE_EXECUTE
;
712 if (!get_file_sd (pc
.handle (), pc
, sd
, false))
714 /* Tweak Samba security descriptor as necessary. */
715 if (pc
.fs_is_samba ())
716 convert_samba_sd (sd
);
717 ret
= check_access (sd
, file_mapping
, desired
, flags
, effective
);
719 debug_printf ("flags %y, ret %d", flags
, ret
);
724 check_registry_access (HANDLE hdl
, int flags
, bool effective
)
726 security_descriptor sd
;
728 static GENERIC_MAPPING NO_COPY_RO reg_mapping
= { KEY_READ
,
732 ACCESS_MASK desired
= 0;
734 desired
|= KEY_ENUMERATE_SUB_KEYS
;
736 desired
|= KEY_SET_VALUE
;
738 desired
|= KEY_QUERY_VALUE
;
740 if ((HKEY
) hdl
== HKEY_PERFORMANCE_DATA
)
741 /* RegGetKeySecurity() always fails with ERROR_INVALID_HANDLE. */
743 else if (!get_reg_sd (hdl
, sd
))
744 ret
= check_access (sd
, reg_mapping
, desired
, flags
, effective
);
746 /* As long as we can't write the registry... */
752 debug_printf ("flags %y, ret %d", flags
, ret
);