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
,
68 FILE_OPEN_FOR_BACKUP_INTENT
69 | pc
.is_known_reparse_point ()
70 ? FILE_OPEN_REPARSE_POINT
: 0);
71 if (!NT_SUCCESS (status
))
74 __seterrno_from_nt_status (status
);
77 status
= NtQuerySecurityObject (fh
, ALL_SECURITY_INFORMATION
,
80 if (!NT_SUCCESS (status
))
83 __seterrno_from_nt_status (status
);
87 /* We have a security descriptor now. Unfortunately, if you want to know
88 if an ACE is inherited from the parent object, this isn't sufficient.
90 In the simple case, the SDs control word contains one of the
91 SE_DACL_AUTO_INHERITED or SE_DACL_PROTECTED flags, or at least one of
92 the ACEs has the INHERITED_ACE flag set. In all of these cases we
93 know the DACL has been inherited.
95 If none of these flags is set in the SD, the information whether
96 or not an ACE has been inherited is not available in the DACL of the
97 object. In this case GetSecurityInfo fetches the SD from the parent
98 directory and tests if the object's SD contains inherited ACEs from the
101 Note that we're not testing the SE_DACL_AUTO_INHERITED and
102 SE_DACL_PROTECTED flags here because we know the state the file's SD
103 is in. Since we're creating all files with a NULL descriptor, the DACL
104 is either inherited from the parent, or it's the default DACL. In
105 neither case, one of these flags is set.
107 For speed, we're not calling RtlConvertToAutoInheritSecurityObject
108 anymore (but keep the code here for reference). Rather we just test
109 if one of the parent's ACEs is inheritable. If so, we know we inherited
110 it and set the SE_DACL_AUTO_INHERITED flag. If not, we may assume our
111 object's DACL is the default DACL.
113 This functionality is slow and the extra information is only required
114 when the file has been created and the permissions are about to be set
115 to POSIX permissions. Therefore we only use it in case the file just
121 ACCESS_ALLOWED_ACE
*ace
;
122 UNICODE_STRING dirname
;
123 PSECURITY_DESCRIPTOR psd
;
126 /* Open the parent directory with READ_CONTROL... */
127 RtlSplitUnicodePath (pc
.get_nt_native_path (), &dirname
, NULL
);
128 InitializeObjectAttributes (&attr
, &dirname
, pc
.objcaseinsensitive (),
130 status
= NtOpenFile (&fh
, READ_CONTROL
, &attr
, &io
,
131 FILE_SHARE_VALID_FLAGS
,
132 FILE_OPEN_FOR_BACKUP_INTENT
133 | FILE_OPEN_REPARSE_POINT
);
134 if (!NT_SUCCESS (status
))
136 debug_printf ("NtOpenFile (%S), status %y", &dirname
, status
);
139 /* ... fetch the parent's security descriptor ... */
140 psd
= (PSECURITY_DESCRIPTOR
) tp
.w_get ();
141 status
= NtQuerySecurityObject (fh
, ALL_SECURITY_INFORMATION
,
144 if (!NT_SUCCESS (status
))
146 debug_printf ("NtQuerySecurityObject (%S), status %y",
151 /* ... and create a new security descriptor in which all inherited ACEs
152 are marked with the INHERITED_ACE flag. For a description of the
153 undocumented RtlConvertToAutoInheritSecurityObject function from
154 ntdll.dll see the MSDN man page for the advapi32 function
155 ConvertToAutoInheritPrivateObjectSecurity. Fortunately the latter
157 PSECURITY_DESCRIPTOR nsd
;
158 status
= RtlConvertToAutoInheritSecurityObject (psd
, sd
, &nsd
, NULL
,
161 if (!NT_SUCCESS (status
))
163 debug_printf ("RtlConvertToAutoInheritSecurityObject (%S), status %y",
167 /* Eventually copy the new security descriptor into sd and delete the
168 original one created by RtlConvertToAutoInheritSecurityObject from
170 len
= RtlLengthSecurityDescriptor (nsd
);
171 memcpy ((PSECURITY_DESCRIPTOR
) sd
, nsd
, len
);
172 RtlDeleteSecurityObject (&nsd
);
174 /* ... and check the parent descriptor for inheritable ACEs matching
175 our current object type (file/dir). The simple truth in our case
176 is, either the parent dir had inheritable ACEs and all our ACEs are
177 inherited, or the parent dir didn't have inheritable ACEs and all
178 our ACEs are taken from the default DACL. */
179 bool inherited
= false;
180 BYTE search_flags
= pc
.isdir () ? SUB_CONTAINERS_AND_OBJECTS_INHERIT
181 : SUB_OBJECTS_ONLY_INHERIT
;
182 if (NT_SUCCESS (RtlGetDaclSecurityDescriptor (psd
, &exists
, &dacl
, &def
))
184 for (ULONG idx
= 0; idx
< dacl
->AceCount
; ++idx
)
185 if (NT_SUCCESS (RtlGetAce (dacl
, idx
, (PVOID
*) &ace
))
186 && (ace
->Header
.AceFlags
& search_flags
))
191 /* Then, if the parent descriptor contained inheritable ACEs, we mark
192 the SD as SE_DACL_AUTO_INHERITED. Note that this requires the
193 matching check in get_posix_access. If we ever revert to
194 RtlConvertToAutoInheritSecurityObject, the check in get_posix_access
195 has to test every single ACE for the INHERITED_ACE flag again. */
197 && NT_SUCCESS (RtlGetDaclSecurityDescriptor (sd
, &exists
, &dacl
,
200 RtlSetControlSecurityDescriptor (sd
, SE_DACL_AUTO_INHERITED
,
201 SE_DACL_AUTO_INHERITED
);
208 set_file_sd (HANDLE fh
, path_conv
&pc
, security_descriptor
&sd
, bool is_chown
)
210 NTSTATUS status
= STATUS_SUCCESS
;
214 for (; retry
< 2; ++retry
)
218 status
= NtSetSecurityObject (fh
,
219 is_chown
? ALL_SECURITY_INFORMATION
220 : DACL_SECURITY_INFORMATION
,
222 if (NT_SUCCESS (status
))
230 OBJECT_ATTRIBUTES attr
;
232 status
= NtOpenFile (&fh
, (is_chown
? WRITE_OWNER
: 0) | WRITE_DAC
,
233 fh
? pc
.init_reopen_attr (attr
, fh
)
234 : pc
.get_object_attr (attr
, sec_none_nih
),
236 FILE_SHARE_VALID_FLAGS
,
237 FILE_OPEN_FOR_BACKUP_INTENT
238 | pc
.is_known_reparse_point ()
239 ? FILE_OPEN_REPARSE_POINT
: 0);
240 if (!NT_SUCCESS (status
))
249 if (!NT_SUCCESS (status
))
250 __seterrno_from_nt_status (status
);
255 get_reg_sd (HANDLE handle
, security_descriptor
&sd_ret
)
260 ret
= RegGetKeySecurity ((HKEY
) handle
, ALL_SECURITY_INFORMATION
,
262 if (ret
== ERROR_INSUFFICIENT_BUFFER
)
264 if (!sd_ret
.malloc (len
))
267 ret
= RegGetKeySecurity ((HKEY
) handle
, ALL_SECURITY_INFORMATION
,
270 if (ret
!= ERROR_SUCCESS
)
279 get_reg_attribute (HKEY hkey
, mode_t
&attribute
, uid_t
*uidret
,
282 security_descriptor sd
;
284 if (!get_reg_sd (hkey
, sd
))
286 get_posix_access (sd
, attribute
, uidret
, gidret
, NULL
, 0);
289 /* The entries are already set to default values */
294 get_file_attribute (HANDLE handle
, path_conv
&pc
,
295 mode_t
&attribute
, uid_t
*uidret
, gid_t
*gidret
)
299 security_descriptor sd
;
301 if (!get_file_sd (handle
, pc
, sd
, false))
303 get_posix_access (sd
, attribute
, uidret
, gidret
, NULL
, 0);
306 /* ENOSYS is returned by get_file_sd if fetching the DACL from a remote
307 share returns STATUS_INVALID_NETWORK_RESPONSE, which in turn is
308 converted to ERROR_BAD_NET_RESP. This potentially occurs when trying
309 to fetch DACLs from a NT4 machine which is not part of the domain of
310 the requesting machine. */
311 else if (get_errno () != ENOSYS
)
314 *uidret
= ILLEGAL_UID
;
316 *gidret
= ILLEGAL_GID
;
323 *uidret
= myself
->uid
;
325 *gidret
= myself
->gid
;
331 add_access_allowed_ace (PACL acl
, DWORD attributes
, PSID sid
, size_t &len_add
,
334 NTSTATUS status
= RtlAddAccessAllowedAceEx (acl
, ACL_REVISION
, inherit
,
336 if (!NT_SUCCESS (status
))
338 __seterrno_from_nt_status (status
);
341 len_add
+= sizeof (ACCESS_ALLOWED_ACE
) - sizeof (DWORD
) + RtlLengthSid (sid
);
346 add_access_denied_ace (PACL acl
, DWORD attributes
, PSID sid
, size_t &len_add
,
349 NTSTATUS status
= RtlAddAccessDeniedAceEx (acl
, ACL_REVISION
, inherit
,
351 if (!NT_SUCCESS (status
))
353 __seterrno_from_nt_status (status
);
356 len_add
+= sizeof (ACCESS_DENIED_ACE
) - sizeof (DWORD
) + RtlLengthSid (sid
);
361 set_security_attribute (path_conv
&pc
, int attribute
, PSECURITY_ATTRIBUTES psa
,
362 security_descriptor
&sd
)
364 psa
->lpSecurityDescriptor
= sd
.malloc (SECURITY_DESCRIPTOR_MIN_LENGTH
);
365 RtlCreateSecurityDescriptor ((PSECURITY_DESCRIPTOR
) psa
->lpSecurityDescriptor
,
366 SECURITY_DESCRIPTOR_REVISION
);
367 psa
->lpSecurityDescriptor
= set_posix_access (attribute
, geteuid (),
373 get_object_sd (HANDLE handle
, security_descriptor
&sd
)
378 status
= NtQuerySecurityObject (handle
, ALL_SECURITY_INFORMATION
,
380 if (status
!= STATUS_BUFFER_TOO_SMALL
)
382 __seterrno_from_nt_status (status
);
385 if (!sd
.malloc (len
))
390 status
= NtQuerySecurityObject (handle
, ALL_SECURITY_INFORMATION
,
392 if (!NT_SUCCESS (status
))
394 __seterrno_from_nt_status (status
);
401 get_object_attribute (HANDLE handle
, uid_t
*uidret
, gid_t
*gidret
,
404 security_descriptor sd
;
406 if (get_object_sd (handle
, sd
))
408 return get_posix_access (sd
, attribute
, uidret
, gidret
, NULL
, 0)
413 create_object_sd_from_attribute (uid_t uid
, gid_t gid
, mode_t attribute
,
414 security_descriptor
&sd
)
416 return set_posix_access (attribute
, uid
, gid
, NULL
, 0, sd
, false)
421 set_object_sd (HANDLE handle
, security_descriptor
&sd
, bool chown
)
424 status
= NtSetSecurityObject (handle
, chown
? ALL_SECURITY_INFORMATION
425 : DACL_SECURITY_INFORMATION
, sd
);
426 if (!NT_SUCCESS (status
))
428 __seterrno_from_nt_status (status
);
435 set_object_attribute (HANDLE handle
, uid_t uid
, gid_t gid
, mode_t attribute
)
437 security_descriptor sd
;
439 if (create_object_sd_from_attribute (uid
, gid
, attribute
, sd
)
440 || set_object_sd (handle
, sd
, uid
!= ILLEGAL_UID
|| gid
!= ILLEGAL_GID
))
446 set_created_file_access (HANDLE handle
, path_conv
&pc
, mode_t attr
)
449 security_descriptor sd
, sd_ret
;
458 if (!get_file_sd (handle
, pc
, sd
, true))
460 attr
|= S_JUSTCREATED
;
464 aclp
= (aclent_t
*) tp
.c_get ();
465 if ((nentries
= get_posix_access (sd
, attr_rd
, &uid
, &gid
, aclp
,
466 MAX_ACL_ENTRIES
, &std_acl
)) >= 0)
470 /* Symlinks always get the request POSIX perms. */
471 aclp
[0].a_perm
= (attr
>> 6) & S_IRWXO
;
472 if ((idx
= searchace (aclp
, nentries
, GROUP_OBJ
)) >= 0)
473 aclp
[idx
].a_perm
= (attr
>> 3) & S_IRWXO
;
474 if ((idx
= searchace (aclp
, nentries
, CLASS_OBJ
)) >= 0)
475 aclp
[idx
].a_perm
= (attr
>> 3) & S_IRWXO
;
476 if ((idx
= searchace (aclp
, nentries
, OTHER_OBJ
)) >= 0)
477 aclp
[idx
].a_perm
= attr
& S_IRWXO
;
481 /* Overwrite ACL permissions as required by POSIX 1003.1e
483 aclp
[0].a_perm
&= (attr
>> 6) & S_IRWXO
;
484 if ((idx
= searchace (aclp
, nentries
, CLASS_OBJ
)) >= 0)
485 aclp
[idx
].a_perm
&= (attr
>> 3) & S_IRWXO
;
487 && (idx
= searchace (aclp
, nentries
, GROUP_OBJ
)) >= 0)
488 aclp
[idx
].a_perm
&= (attr
>> 3) & S_IRWXO
;
489 if ((idx
= searchace (aclp
, nentries
, OTHER_OBJ
)) >= 0)
490 aclp
[idx
].a_perm
&= attr
& S_IRWXO
;
492 /* Construct appropriate inherit attribute for new directories.
493 Basically we do this only for the sake of non-Cygwin applications.
494 Cygwin applications don't need these. Additionally, if the
495 S_ISGID bit is set, propagate it. */
498 mode_t def_attr
= attr
& ~cygheap
->umask
;
500 if (searchace (aclp
, nentries
, DEF_USER_OBJ
) < 0)
502 aclp
[nentries
].a_type
= DEF_USER_OBJ
;
503 aclp
[nentries
].a_id
= ILLEGAL_UID
;
504 aclp
[nentries
++].a_perm
= (def_attr
>> 6) & S_IRWXO
;
506 if (searchace (aclp
, nentries
, DEF_GROUP_OBJ
) < 0)
508 aclp
[nentries
].a_type
= DEF_GROUP_OBJ
;
509 aclp
[nentries
].a_id
= ILLEGAL_GID
;
510 aclp
[nentries
++].a_perm
= (def_attr
>> 3) & S_IRWXO
;
512 if (searchace (aclp
, nentries
, DEF_OTHER_OBJ
) < 0)
514 aclp
[nentries
].a_type
= DEF_OTHER_OBJ
;
515 aclp
[nentries
].a_id
= ILLEGAL_UID
;
516 aclp
[nentries
++].a_perm
= def_attr
& S_IRWXO
;
518 if (attr_rd
& S_ISGID
)
521 if (set_posix_access (attr
, uid
, gid
, aclp
, nentries
, sd_ret
,
523 ret
= set_file_sd (handle
, pc
, sd_ret
, attr_rd
& S_ISGID
);
530 check_access (security_descriptor
&sd
, GENERIC_MAPPING
&mapping
,
531 ACCESS_MASK desired
, int flags
, bool effective
)
534 NTSTATUS status
, allow
;
536 DWORD plen
= sizeof (PRIVILEGE_SET
) + 3 * sizeof (LUID_AND_ATTRIBUTES
);
537 PPRIVILEGE_SET pset
= (PPRIVILEGE_SET
) alloca (plen
);
538 HANDLE tok
= ((effective
&& cygheap
->user
.issetuid ())
539 ? cygheap
->user
.imp_token ()
544 if (!DuplicateTokenEx (hProcToken
, MAXIMUM_ALLOWED
, NULL
,
545 SecurityImpersonation
, TokenImpersonation
,
554 status
= NtAccessCheck (sd
, tok
, desired
, &mapping
, pset
, &plen
, &granted
,
556 if (!NT_SUCCESS (status
))
558 else if (!NT_SUCCESS (allow
))
560 /* CV, 2006-10-16: Now, that's really weird. Imagine a user who has no
561 standard access to a file, but who has backup and restore privileges
562 and these privileges are enabled in the access token. One would
563 expect that the AccessCheck function takes this into consideration
564 when returning the access status. Otherwise, why bother with the
565 pset parameter, right?
566 But not so. AccessCheck actually returns a status of "false" here,
567 even though opening a file with backup resp. restore intent
568 naturally succeeds for this user. This definitely spoils the results
569 of access(2) for administrative users or the SYSTEM account. So, in
570 case the access check fails, another check against the user's
571 backup/restore privileges has to be made. Sigh. */
572 int granted_flags
= 0;
577 pset
->PrivilegeCount
= 1;
579 pset
->Privilege
[0].Luid
.HighPart
= 0L;
580 pset
->Privilege
[0].Luid
.LowPart
= SE_BACKUP_PRIVILEGE
;
581 pset
->Privilege
[0].Attributes
= 0;
582 status
= NtPrivilegeCheck (tok
, pset
, &has_priv
);
583 if (NT_SUCCESS (status
) && has_priv
)
584 granted_flags
|= R_OK
;
588 pset
->PrivilegeCount
= 1;
590 pset
->Privilege
[0].Luid
.HighPart
= 0L;
591 pset
->Privilege
[0].Luid
.LowPart
= SE_RESTORE_PRIVILEGE
;
592 pset
->Privilege
[0].Attributes
= 0;
593 status
= NtPrivilegeCheck (tok
, pset
, &has_priv
);
594 if (NT_SUCCESS (status
) && has_priv
)
595 granted_flags
|= W_OK
;
597 if (granted_flags
== flags
)
607 /* Samba override. Check security descriptor for Samba UNIX user and group
608 accounts and check if we have an RFC 2307 mapping to a Windows account.
609 Create a new security descriptor with all of the UNIX accounts with
610 valid mapping replaced with their Windows counterpart. */
612 convert_samba_sd (security_descriptor
&sd_ret
)
619 SECURITY_DESCRIPTOR sd
;
624 PACCESS_ALLOWED_ACE ace
;
626 if (!NT_SUCCESS (RtlGetOwnerSecurityDescriptor (sd_ret
, &sid
, &dummy
)))
629 if (!NT_SUCCESS (RtlGetGroupSecurityDescriptor (sd_ret
, &sid
, &dummy
)))
633 if (sid_id_auth (owner
) == 22)
636 uid_t uid
= owner
.get_uid (&cldap
);
637 if (uid
< UNIX_POSIX_OFFSET
&& (pwd
= internal_getpwuid (uid
)))
638 owner
.getfrompw (pwd
);
640 if (sid_id_auth (group
) == 22)
643 gid_t gid
= group
.get_gid (&cldap
);
644 if (gid
< UNIX_POSIX_OFFSET
&& (grp
= internal_getgrgid (gid
)))
645 group
.getfromgr (grp
);
648 if (!NT_SUCCESS (RtlGetDaclSecurityDescriptor (sd_ret
, &dummy
,
651 acl
= (PACL
) tp
.w_get ();
652 RtlCreateAcl (acl
, ACL_MAXIMUM_SIZE
, ACL_REVISION
);
653 acl_len
= sizeof (ACL
);
655 for (DWORD i
= 0; i
< oacl
->AceCount
; ++i
)
656 if (NT_SUCCESS (RtlGetAce (oacl
, i
, (PVOID
*) &ace
)))
658 cygsid
ace_sid ((PSID
) &ace
->SidStart
);
659 if (sid_id_auth (ace_sid
) == 22)
661 if (sid_sub_auth (ace_sid
, 0) == 1) /* user */
664 uid_t uid
= ace_sid
.get_uid (&cldap
);
665 if (uid
< UNIX_POSIX_OFFSET
&& (pwd
= internal_getpwuid (uid
)))
666 ace_sid
.getfrompw (pwd
);
668 else if (sid_sub_auth (ace_sid
, 0) == 2) /* group */
671 gid_t gid
= ace_sid
.get_gid (&cldap
);
672 if (gid
< UNIX_POSIX_OFFSET
&& (grp
= internal_getgrgid (gid
)))
673 ace_sid
.getfromgr (grp
);
676 if (!add_access_allowed_ace (acl
, ace
->Mask
, ace_sid
, acl_len
,
677 ace
->Header
.AceFlags
))
680 acl
->AclSize
= acl_len
;
682 RtlCreateSecurityDescriptor (&sd
, SECURITY_DESCRIPTOR_REVISION
);
683 RtlSetControlSecurityDescriptor (&sd
, SE_DACL_PROTECTED
, SE_DACL_PROTECTED
);
684 RtlSetOwnerSecurityDescriptor (&sd
, owner
, FALSE
);
685 RtlSetGroupSecurityDescriptor (&sd
, group
, FALSE
);
687 status
= RtlSetDaclSecurityDescriptor (&sd
, TRUE
, acl
, FALSE
);
688 if (!NT_SUCCESS (status
))
691 status
= RtlAbsoluteToSelfRelativeSD (&sd
, sd_ret
, &sd_size
);
692 if (sd_size
> 0 && sd_ret
.malloc (sd_size
))
693 RtlAbsoluteToSelfRelativeSD (&sd
, sd_ret
, &sd_size
);
697 check_file_access (path_conv
&pc
, int flags
, bool effective
)
699 security_descriptor sd
;
701 ACCESS_MASK desired
= 0;
703 desired
|= FILE_READ_DATA
;
705 desired
|= FILE_WRITE_DATA
;
707 desired
|= FILE_EXECUTE
;
708 if (!get_file_sd (pc
.handle (), pc
, sd
, false))
710 /* Tweak Samba security descriptor as necessary. */
711 if (pc
.fs_is_samba ())
712 convert_samba_sd (sd
);
713 ret
= check_access (sd
, file_mapping
, desired
, flags
, effective
);
715 debug_printf ("flags %y, ret %d", flags
, ret
);
720 check_registry_access (HANDLE hdl
, int flags
, bool effective
)
722 security_descriptor sd
;
724 static GENERIC_MAPPING NO_COPY_RO reg_mapping
= { KEY_READ
,
728 ACCESS_MASK desired
= 0;
730 desired
|= KEY_ENUMERATE_SUB_KEYS
;
732 desired
|= KEY_SET_VALUE
;
734 desired
|= KEY_QUERY_VALUE
;
736 if ((HKEY
) hdl
== HKEY_PERFORMANCE_DATA
)
737 /* RegGetKeySecurity() always fails with ERROR_INVALID_HANDLE. */
739 else if (!get_reg_sd (hdl
, sd
))
740 ret
= check_access (sd
, reg_mapping
, desired
, flags
, effective
);
742 /* As long as we can't write the registry... */
748 debug_printf ("flags %y, ret %d", flags
, ret
);