From 52f01a0ff8f06ec1f15ab576867da3abf577e891 Mon Sep 17 00:00:00 2001 From: Corinna Vinschen Date: Wed, 18 Mar 2015 17:49:12 +0100 Subject: [PATCH] Preliminary read side implementation of new permission handling. * acl.h (MAX_ACL_ENTRIES): Raise to 2730. Add comment to explain. * sec_acl.cc: Add leading comment to explain new ACL style. Add definitions and macros to use for bits in new Cygwin ACL. (DENY_RWX): New mask value for all temporary deny bits. (getace): Add bool parameter to decide when leaving all bits intact, rather than filtering them per the already set bits. (get_posix_access): New function, taking over functionality to read POSIX ACL from SECURITY_DESCRIPTOR. (getacl): Just call get_posix_access. * sec_helper.cc (well_known_cygwin_sid): Define. * security.cc (get_attribute_from_acl): Remove. (get_info_from_sd): Remove. (get_reg_sd): Call get_posix_access instead of get_info_from_sd. (get_file_attribute): Ditto. (get_object_attribute): Ditto. * security.h (well_known_cygwin_sid): Declare. (get_posix_access): Add prototype. * Throughout, use simpler ACE macros from Windows' accctrl.h. Signed-off-by: Corinna Vinschen --- winsup/cygwin/ChangeLog | 23 ++ winsup/cygwin/include/cygwin/acl.h | 12 +- winsup/cygwin/sec_acl.cc | 506 ++++++++++++++++++++--------- winsup/cygwin/sec_helper.cc | 11 +- winsup/cygwin/security.cc | 204 +----------- winsup/cygwin/security.h | 3 + 6 files changed, 399 insertions(+), 360 deletions(-) diff --git a/winsup/cygwin/ChangeLog b/winsup/cygwin/ChangeLog index bd9e71ffa..5d45cdceb 100644 --- a/winsup/cygwin/ChangeLog +++ b/winsup/cygwin/ChangeLog @@ -1,3 +1,26 @@ +2015-03-18 Corinna Vinschen + + Preliminary read side implementation of new permission handling. + * acl.h (MAX_ACL_ENTRIES): Raise to 2730. Add comment to explain. + * sec_acl.cc: Add leading comment to explain new ACL style. + Add definitions and macros to use for bits in new Cygwin ACL. + (DENY_RWX): New mask value for all temporary deny bits. + (getace): Add bool parameter to decide when leaving all bits intact, + rather than filtering them per the already set bits. + (get_posix_access): New function, taking over functionality to read + POSIX ACL from SECURITY_DESCRIPTOR. + (getacl): Just call get_posix_access. + * sec_helper.cc (well_known_cygwin_sid): Define. + * security.cc (get_attribute_from_acl): Remove. + (get_info_from_sd): Remove. + (get_reg_sd): Call get_posix_access instead of get_info_from_sd. + (get_file_attribute): Ditto. + (get_object_attribute): Ditto. + * security.h (well_known_cygwin_sid): Declare. + (get_posix_access): Add prototype. + + * Throughout, use simpler ACE macros from Windows' accctrl.h. + 2015-03-18 Corinna Vinschen * grp.cc (pwdgrp::parse_group): Call cygsid::getfromgr_passwd. diff --git a/winsup/cygwin/include/cygwin/acl.h b/winsup/cygwin/include/cygwin/acl.h index 8fa5a65a5..dcdfc0076 100644 --- a/winsup/cygwin/include/cygwin/acl.h +++ b/winsup/cygwin/include/cygwin/acl.h @@ -1,6 +1,6 @@ /* cygwin/acl.h header file for Cygwin. - Copyright 1999, 2000, 2001, 2002, 2010, 2014 Red Hat, Inc. + Copyright 1999, 2000, 2001, 2002, 2010, 2014, 2015 Red Hat, Inc. Written by C. Vinschen. This file is part of Cygwin. @@ -25,8 +25,16 @@ extern "C" { #define GETACL (0x1) #define GETACLCNT (0x2) +/* Windows ACLs have a maximum size of 64K. Counting the most pessimistic way, + the maximum number of ACEs is 3276. Technet claims "approximately 1820", + which uses the length of normal user and group SIDs for the computation. + We're now going with 2730, the number of aclent_t entries matching a 32K + buffer. + On one hand, there are only a limited number of SIDs shorter than the normal + user/group SIDs, on the other hand there are no deny aclent_t entries, so we + should be fine with 32K aclbuf_t buffers provided by the caller. */ #define MIN_ACL_ENTRIES (3) // minimal acl entries from GETACLCNT -#define MAX_ACL_ENTRIES (256) // max entries of each type +#define MAX_ACL_ENTRIES (2730) // max entries of each type // Return values of aclcheck(3) in case of error */ #define GRP_ERROR (0x1) diff --git a/winsup/cygwin/sec_acl.cc b/winsup/cygwin/sec_acl.cc index e5019cb35..a93e191ff 100644 --- a/winsup/cygwin/sec_acl.cc +++ b/winsup/cygwin/sec_acl.cc @@ -24,6 +24,64 @@ details. */ #include "ntdll.h" #include "tls_pbuf.h" +/* How does a correctly constructed new-style Windows ACL claiming to be a + POSIX ACL look like? + + - Cygwin ACE (special bits, CLASS_OBJ). + + - If a USER entry has more permissions than any group, Everyone, or if it + has more permissions than allowed by the CLASS_OBJ entry: + + USER deny ACEs == POSIX allow + & ^(mask | all group allows | Everyone allow) + + - USER_OBJ allow ACE + - USER allow ACEs + + The POSIX permissions returned for a USER entry are the allow bits alone! + + - If a GROUP entry has more permissions than Everyone, or if it has more + permissions than allowed by the CLASS_OBJ entry: + + GROUP deny ACEs == POSIX allow & ^(mask | Everyone allow) + + - GROUP_OBJ allow ACE + - GROUP allow ACEs + + The POSIX permissions returned for a GROUP entry are the allow bits alone! + + - OTHER_OBJ allow ACE + + Rinse and repeat for default ACEs with INHERIT flags set. + + - Default Cygwin ACE (S_ISGID, CLASS_OBJ). */ + + /* POSIX <-> Win32 */ + +/* Historically, these bits are stored in a NULL SID ACE. To distinguish + the new ACL style from the old one, we're now using an invented SID, the + Cygwin SID (S-1-0-1132029815). The new ACEs can exist twice in an ACL, + the "normal one" containg CLASS_OBJ and special bits, and the one with + INHERIT bit set to pass the DEF_CLASS_OBJ bits and the S_ISGID bit on. */ +#define CYG_ACE_ISVTX 0x001 /* 0x200 <-> 0x001 */ +#define CYG_ACE_ISGID 0x002 /* 0x400 <-> 0x002 */ +#define CYG_ACE_ISUID 0x004 /* 0x800 <-> 0x004 */ +#define CYG_ACE_ISBITS_TO_POSIX(val) \ + (((val) & 0x007) << 9) +#define CYG_ACE_ISBITS_TO_WIN(val) \ + (((val) & (S_ISVTX | S_ISUID | S_IS_GID)) >> 9) + +#define CYG_ACE_MASK_X 0x008 /* 0x001 <-> 0x008 */ +#define CYG_ACE_MASK_W 0x010 /* 0x002 <-> 0x010 */ +#define CYG_ACE_MASK_R 0x020 /* 0x004 <-> 0x020 */ +#define CYG_ACE_MASK_RWX 0x038 +#define CYG_ACE_MASK_VALID 0x040 /* has mask if set */ +#define CYG_ACE_MASK_TO_POSIX(val) \ + (((val) & CYG_ACE_MASK_RWX) >> 3) +#define CYG_ACE_MASK_TO_WIN(val) \ + ((((val) & S_IRWXO) << 3) \ + | CYG_ACE_MASK_VALID) + static int searchace (aclent_t *aclp, int nentries, int type, uid_t id = ILLEGAL_UID) { @@ -235,8 +293,7 @@ setacl (HANDLE handle, path_conv &pc, int nentries, aclent_t *aclbufp, allow |= FILE_DELETE_CHILD; /* Set inherit property. */ DWORD inheritance = (aclbufp[i].a_type & ACL_DEFAULT) - ? (CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE - | INHERIT_ONLY_ACE) + ? (SUB_CONTAINERS_AND_OBJECTS_INHERIT | INHERIT_ONLY) : NO_INHERITANCE; /* * If a specific acl contains a corresponding default entry with @@ -251,7 +308,7 @@ setacl (HANDLE handle, path_conv &pc, int nentries, aclent_t *aclbufp, ? aclbufp[i].a_id : ILLEGAL_UID)) >= 0 && aclbufp[i].a_perm == aclbufp[i + 1 + pos].a_perm) { - inheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE; + inheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; /* invalidate the corresponding default entry. */ invalid[i + 1 + pos] = true; } @@ -337,15 +394,19 @@ setacl (HANDLE handle, path_conv &pc, int nentries, aclent_t *aclbufp, #define DENY_R 040000 #define DENY_W 020000 #define DENY_X 010000 +#define DENY_RWX (DENY_R | DENY_W | DENY_X) +/* New style ACL means, just read the bits and store them away. Don't + create masked values on your own. */ static void getace (aclent_t &acl, int type, int id, DWORD win_ace_mask, - DWORD win_ace_type) + DWORD win_ace_type, bool new_style) { acl.a_type = type; acl.a_id = id; - if ((win_ace_mask & FILE_READ_BITS) && !(acl.a_perm & (S_IROTH | DENY_R))) + if ((win_ace_mask & FILE_READ_BITS) + && (new_style || !(acl.a_perm & (S_IROTH | DENY_R)))) { if (win_ace_type == ACCESS_ALLOWED_ACE_TYPE) acl.a_perm |= S_IROTH; @@ -353,7 +414,8 @@ getace (aclent_t &acl, int type, int id, DWORD win_ace_mask, acl.a_perm |= DENY_R; } - if ((win_ace_mask & FILE_WRITE_BITS) && !(acl.a_perm & (S_IWOTH | DENY_W))) + if ((win_ace_mask & FILE_WRITE_BITS) + && (new_style || !(acl.a_perm & (S_IWOTH | DENY_W)))) { if (win_ace_type == ACCESS_ALLOWED_ACE_TYPE) acl.a_perm |= S_IWOTH; @@ -361,7 +423,8 @@ getace (aclent_t &acl, int type, int id, DWORD win_ace_mask, acl.a_perm |= DENY_W; } - if ((win_ace_mask & FILE_EXEC_BITS) && !(acl.a_perm & (S_IXOTH | DENY_X))) + if ((win_ace_mask & FILE_EXEC_BITS) + && (new_style || !(acl.a_perm & (S_IXOTH | DENY_X)))) { if (win_ace_type == ACCESS_ALLOWED_ACE_TYPE) acl.a_perm |= S_IXOTH; @@ -370,40 +433,92 @@ getace (aclent_t &acl, int type, int id, DWORD win_ace_mask, } } +/* From the SECURITY_DESCRIPTOR given in psd, compute user, owner, posix + attributes, as well as the POSIX acl. The function returns the number + of entries returned in aclbufp, or -1 in case of error. */ int -getacl (HANDLE handle, path_conv &pc, int nentries, aclent_t *aclbufp) +get_posix_access (PSECURITY_DESCRIPTOR psd, + mode_t *attr_ret, uid_t *uid_ret, gid_t *gid_ret, + aclent_t *aclbufp, int nentries) { - security_descriptor sd; - - if (get_file_sd (handle, pc, sd, false)) - return -1; - - cygpsid owner_sid; - cygpsid group_sid; + tmp_pathbuf tp; NTSTATUS status; - BOOLEAN dummy; + BOOLEAN dummy, acl_exists; + PACL acl; + PACCESS_ALLOWED_ACE ace; + cygpsid owner_sid, group_sid; + cyg_ldap cldap; uid_t uid; gid_t gid; - cyg_ldap cldap; + mode_t attr = 0; + aclent_t *lacl = NULL; + cygpsid ace_sid; + int pos, type, id; - status = RtlGetOwnerSecurityDescriptor (sd, (PSID *) &owner_sid, &dummy); + bool new_style = false; + int types_def = 0; + int def_pgrp_pos = -1; + bool has_class_perm = false, has_def_class_perm = false; + mode_t class_perm = 0, def_class_perm = 0; + + if (aclbufp && nentries < MIN_ACL_ENTRIES) + { + set_errno (EINVAL); + return -1; + } + /* If reading the security descriptor failed, treat the object as + unreadable. */ + if (!psd) + { + if (attr_ret) + *attr_ret &= S_IFMT; + if (uid_ret) + *uid_ret = ILLEGAL_UID; + if (gid_ret) + *gid_ret = ILLEGAL_GID; + if (aclbufp) + { + aclbufp[0].a_type = USER_OBJ; + aclbufp[0].a_id = ILLEGAL_UID; + aclbufp[0].a_perm = 0; + aclbufp[1].a_type = GROUP_OBJ; + aclbufp[1].a_id = ILLEGAL_GID; + aclbufp[1].a_perm = 0; + aclbufp[2].a_type = OTHER_OBJ; + aclbufp[2].a_id = ILLEGAL_GID; + aclbufp[2].a_perm = 0; + return MIN_ACL_ENTRIES; + } + return 0; + } + /* Fetch owner, group, and ACL from security descriptor. */ + status = RtlGetOwnerSecurityDescriptor (psd, (PSID *) &owner_sid, &dummy); if (!NT_SUCCESS (status)) { __seterrno_from_nt_status (status); return -1; } + status = RtlGetGroupSecurityDescriptor (psd, (PSID *) &group_sid, &dummy); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + status = RtlGetDaclSecurityDescriptor (psd, &acl_exists, &acl, &dummy); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + /* Set uidret, gidret, and initalize attributes. */ uid = owner_sid.get_uid (&cldap); - - status = RtlGetGroupSecurityDescriptor (sd, (PSID *) &group_sid, &dummy); - if (!NT_SUCCESS (status)) - { - __seterrno_from_nt_status (status); - return -1; - } gid = group_sid.get_gid (&cldap); + if (attr_ret) + attr |= (*attr_ret & S_IFMT); - aclent_t lacl[MAX_ACL_ENTRIES]; - memset (&lacl, 0, MAX_ACL_ENTRIES * sizeof (aclent_t)); + /* Create and initialize local aclent_t array. */ + lacl = (aclent_t *) tp.c_get (); + memset (lacl, 0, MAX_ACL_ENTRIES * sizeof (aclent_t *)); lacl[0].a_type = USER_OBJ; lacl[0].a_id = uid; lacl[1].a_type = GROUP_OBJ; @@ -411,102 +526,127 @@ getacl (HANDLE handle, path_conv &pc, int nentries, aclent_t *aclbufp) lacl[2].a_type = OTHER_OBJ; lacl[2].a_id = ILLEGAL_GID; - PACL acl; - BOOLEAN acl_exists; - - status = RtlGetDaclSecurityDescriptor (sd, &acl_exists, &acl, &dummy); - if (!NT_SUCCESS (status)) + /* No ACEs? Everybody has full access. */ + if (!acl_exists || !acl || acl->AceCount == 0) { - __seterrno_from_nt_status (status); - return -1; + for (pos = 0; pos < MIN_ACL_ENTRIES; ++pos) + lacl[pos].a_perm = S_IROTH | S_IWOTH | S_IXOTH; + goto out; } - int pos, i, types_def = 0; - int pgrp_pos = 1, def_pgrp_pos = -1; - bool has_class_perm = false, has_def_class_perm = false; - mode_t class_perm = 0, def_class_perm = 0; - - if (!acl_exists || !acl) - for (pos = 0; pos < 3; ++pos) - lacl[pos].a_perm = S_IROTH | S_IWOTH | S_IXOTH; - else + for (int i = 0; i < acl->AceCount; ++i) { - for (i = 0; i < acl->AceCount; ++i) + if (!NT_SUCCESS (RtlGetAce (acl, i, (PVOID *) &ace))) + continue; + + ace_sid = (PSID) &ace->SidStart; + + if (ace_sid == well_known_null_sid) { - ACCESS_ALLOWED_ACE *ace; - - if (!NT_SUCCESS (RtlGetAce (acl, i, (PVOID *) &ace))) - continue; - - cygpsid ace_sid ((PSID) &ace->SidStart); - int id; - int type = 0; - - if (ace_sid == well_known_null_sid) + /* Old-style or non-Cygwin ACL. Fetch only the special bits. */ + attr |= CYG_ACE_ISBITS_TO_POSIX (ace->Mask); + continue; + } + if (ace_sid == well_known_cygwin_sid) + { + /* New-style ACL. Note the fact that a mask value is present since + that changes how getace fetches the information. That's fine, + because the Cygwin SID ACE is supposed to precede all USER, GROUP + and GROUP_OBJ entries. Any ACL not created that way has been + rearranged by the Windows functionality to create the brain-dead + "canonical" ACL order and is broken anyway. */ + attr |= CYG_ACE_ISBITS_TO_POSIX (ace->Mask); + if (ace->Mask & CYG_ACE_MASK_VALID) { - /* Simply ignore. */ - continue; + new_style = true; + type = (ace->Header.AceFlags & SUB_CONTAINERS_AND_OBJECTS_INHERIT) + ? DEF_CLASS_OBJ : CLASS_OBJ; + if ((pos = searchace (lacl, MAX_ACL_ENTRIES, type, ILLEGAL_GID)) + >= 0) + { + lacl[pos].a_type = type; + lacl[pos].a_id = ILLEGAL_GID; + lacl[pos].a_perm = CYG_ACE_MASK_TO_POSIX (ace->Mask); + } + if (type == CLASS_OBJ) /* Needed for POSIX permissions. */ + { + has_class_perm = true; + class_perm = lacl[pos].a_perm; + } + else + { + has_def_class_perm = true; + def_class_perm = lacl[pos].a_perm; + } } - if (ace_sid == well_known_world_sid) - { - type = OTHER_OBJ; - id = ILLEGAL_GID; - } - else if (ace_sid == owner_sid) - { - type = USER_OBJ; - id = uid; - } - else if (ace_sid == group_sid) - { - type = GROUP_OBJ; - id = gid; - } - else if (ace_sid == well_known_creator_group_sid) - { - type = DEF_GROUP_OBJ; - types_def |= type; - id = ILLEGAL_GID; - } - else if (ace_sid == well_known_creator_owner_sid) - { - type = DEF_USER_OBJ; - types_def |= type; - id = ILLEGAL_GID; - } - else - id = ace_sid.get_id (TRUE, &type, &cldap); - + continue; + } + else if (ace_sid == owner_sid) + { + type = USER_OBJ; + id = uid; + } + else if (ace_sid == group_sid) + { + type = GROUP_OBJ; + id = gid; + } + else if (ace_sid == well_known_world_sid) + { + type = OTHER_OBJ; + id = ILLEGAL_GID; + } + else if (ace_sid == well_known_creator_owner_sid) + { + type = DEF_USER_OBJ; + types_def |= type; + id = ILLEGAL_GID; + } + else if (ace_sid == well_known_creator_group_sid) + { + type = DEF_GROUP_OBJ; + types_def |= type; + id = ILLEGAL_GID; + } + else + { + id = ace_sid.get_id (TRUE, &type, &cldap); if (!type) continue; - if (!(ace->Header.AceFlags & INHERIT_ONLY_ACE || type & ACL_DEFAULT)) + } + if (!(ace->Header.AceFlags & INHERIT_ONLY || type & ACL_DEFAULT)) + { + if ((pos = searchace (lacl, MAX_ACL_ENTRIES, type, id)) >= 0) { - if ((pos = searchace (lacl, MAX_ACL_ENTRIES, type, id)) >= 0) + getace (lacl[pos], type, id, ace->Mask, ace->Header.AceType, + new_style && type & (USER | GROUP)); + if (!new_style) { - getace (lacl[pos], type, id, ace->Mask, ace->Header.AceType); /* Fix up CLASS_OBJ value. */ - if (type == USER || type == GROUP) + if (type & (USER | GROUP)) { has_class_perm = true; class_perm |= lacl[pos].a_perm; } } } - if ((ace->Header.AceFlags - & (CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE)) - && pc.isdir ()) + } + if ((ace->Header.AceFlags & SUB_CONTAINERS_AND_OBJECTS_INHERIT)) + { + if (type == USER_OBJ) + type = USER; + else if (type == GROUP_OBJ) + type = GROUP; + type |= ACL_DEFAULT; + types_def |= type; + if ((pos = searchace (lacl, MAX_ACL_ENTRIES, type, id)) >= 0) { - if (type == USER_OBJ) - type = USER; - else if (type == GROUP_OBJ) - type = GROUP; - type |= ACL_DEFAULT; - types_def |= type; - if ((pos = searchace (lacl, MAX_ACL_ENTRIES, type, id)) >= 0) + getace (lacl[pos], type, id, ace->Mask, ace->Header.AceType, + new_style && type & (USER | GROUP)); + if (!new_style) { - getace (lacl[pos], type, id, ace->Mask, ace->Header.AceType); /* Fix up DEF_CLASS_OBJ value. */ - if (type == DEF_USER || type == DEF_GROUP) + if (type & (USER | GROUP)) { has_def_class_perm = true; def_class_perm |= lacl[pos].a_perm; @@ -517,80 +657,134 @@ getacl (HANDLE handle, path_conv &pc, int nentries, aclent_t *aclbufp) } } } - /* If secondary user and group entries exist in the ACL, fake a matching - CLASS_OBJ entry. The CLASS_OBJ permissions are the or'ed permissions - of the primary group permissions and all secondary user and group - permissions. */ - if (has_class_perm && (pos = searchace (lacl, MAX_ACL_ENTRIES, 0)) >= 0) + } + /* If this is an old-style or non-Cygwin ACL, and secondary user and group + entries exist in the ACL, fake a matching CLASS_OBJ entry. The CLASS_OBJ + permissions are the or'ed permissions of the primary group permissions + and all secondary user and group permissions. */ + if (!new_style && has_class_perm + && (pos = searchace (lacl, MAX_ACL_ENTRIES, 0)) >= 0) + { + lacl[pos].a_type = CLASS_OBJ; + lacl[pos].a_id = ILLEGAL_GID; + lacl[pos].a_perm = class_perm | lacl[1].a_perm; + } + /* Ensure that the default acl contains at least + DEF_(USER|GROUP|OTHER)_OBJ entries. */ + if (types_def && (pos = searchace (lacl, MAX_ACL_ENTRIES, 0)) >= 0) + { + if (!(types_def & USER_OBJ)) { - lacl[pos].a_type = CLASS_OBJ; - lacl[pos].a_id = ILLEGAL_GID; - lacl[pos].a_perm = class_perm | lacl[pgrp_pos].a_perm; + lacl[pos].a_type = DEF_USER_OBJ; + lacl[pos].a_id = uid; + lacl[pos].a_perm = lacl[0].a_perm; + pos++; } - /* Ensure that the default acl contains at least - DEF_(USER|GROUP|OTHER)_OBJ entries. */ - if (types_def && (pos = searchace (lacl, MAX_ACL_ENTRIES, 0)) >= 0) + if (!(types_def & GROUP_OBJ) && pos < MAX_ACL_ENTRIES) { - if (!(types_def & USER_OBJ)) - { - lacl[pos].a_type = DEF_USER_OBJ; - lacl[pos].a_id = uid; - lacl[pos].a_perm = lacl[0].a_perm; - pos++; - } - if (!(types_def & GROUP_OBJ) && pos < MAX_ACL_ENTRIES) - { - lacl[pos].a_type = DEF_GROUP_OBJ; - lacl[pos].a_id = gid; - lacl[pos].a_perm = lacl[1].a_perm; - /* Note the position of the DEF_GROUP_OBJ entry. */ - def_pgrp_pos = pos; - pos++; - } - if (!(types_def & OTHER_OBJ) && pos < MAX_ACL_ENTRIES) - { - lacl[pos].a_type = DEF_OTHER_OBJ; - lacl[pos].a_id = ILLEGAL_GID; - lacl[pos].a_perm = lacl[2].a_perm; - pos++; - } + lacl[pos].a_type = DEF_GROUP_OBJ; + lacl[pos].a_id = gid; + lacl[pos].a_perm = lacl[1].a_perm; + /* Note the position of the DEF_GROUP_OBJ entry. */ + def_pgrp_pos = pos; + pos++; } - /* If secondary user default and group default entries exist in the ACL, - fake a matching DEF_CLASS_OBJ entry. The DEF_CLASS_OBJ permissions are - the or'ed permissions of the primary group default permissions and all - secondary user and group default permissions. */ - if (has_def_class_perm - && (pos = searchace (lacl, MAX_ACL_ENTRIES, 0)) >= 0) + if (!(types_def & OTHER_OBJ) && pos < MAX_ACL_ENTRIES) { - lacl[pos].a_type = DEF_CLASS_OBJ; + lacl[pos].a_type = DEF_OTHER_OBJ; lacl[pos].a_id = ILLEGAL_GID; - lacl[pos].a_perm = def_class_perm; - if (def_pgrp_pos >= 0) - lacl[pos].a_perm |= lacl[def_pgrp_pos].a_perm; + lacl[pos].a_perm = lacl[2].a_perm; + pos++; } } + /* If this is an old-style or non-Cygwin ACL, and secondary user default + and group default entries exist in the ACL, fake a matching DEF_CLASS_OBJ + entry. The DEF_CLASS_OBJ permissions are the or'ed permissions of the + primary group default permissions and all secondary user and group def. + permissions. */ + if (!new_style && has_def_class_perm + && (pos = searchace (lacl, MAX_ACL_ENTRIES, 0)) >= 0) + { + lacl[pos].a_type = DEF_CLASS_OBJ; + lacl[pos].a_id = ILLEGAL_GID; + lacl[pos].a_perm = def_class_perm; + if (def_pgrp_pos >= 0) + lacl[pos].a_perm |= lacl[def_pgrp_pos].a_perm; + } + + /* Make sure `pos' contains the number of used entries in lacl. */ if ((pos = searchace (lacl, MAX_ACL_ENTRIES, 0)) < 0) pos = MAX_ACL_ENTRIES; + + /* For old-style or non-Cygwin ACLs, check for merging permissions. */ + if (!new_style) + for (int i = 0; i < pos; ++i) + { + /* Current user? If the user entry has a deny ACE, don't check. */ + if (lacl[i].a_id == myself->uid + && lacl[i].a_type & (USER_OBJ | USER) + && !(lacl[i].a_type & ACL_DEFAULT) + && !(lacl[i].a_perm & DENY_RWX)) + { + int gpos; + gid_t grps[NGROUPS_MAX]; + cyg_ldap cldap; + + /* Sum up all permissions of groups the user is member of, plus + everyone perms, and merge them to user perms. */ + mode_t grp_perm = lacl[2].a_perm & S_IRWXO; + int gnum = internal_getgroups (NGROUPS_MAX, grps, &cldap); + for (int g = 0; g < gnum && grp_perm != S_IRWXO; ++g) + if ((gpos = 1, grps[g] == lacl[gpos].a_id) + || (gpos = searchace (lacl, MAX_ACL_ENTRIES, GROUP, grps[g])) + >= 0) + grp_perm |= lacl[gpos].a_perm & S_IRWXO; + lacl[i].a_perm |= grp_perm; + } + /* For all groups, if everyone has more permissions, add everyone + perms to group perms. Skip groups with deny ACE. */ + else if (lacl[i].a_id & (GROUP_OBJ | GROUP) + && !(lacl[i].a_type & ACL_DEFAULT) + && !(lacl[i].a_perm & DENY_RWX)) + lacl[i].a_perm |= lacl[2].a_perm & S_IRWXO; + } + /* Construct POSIX permission bits. Fortunately we know exactly where + to fetch the affecting bits from, at least as long as the array + hasn't been sorted. */ + attr |= (lacl[0].a_perm & S_IRWXO) << 6; + attr |= (has_class_perm ? class_perm : (lacl[1].a_perm & S_IRWXO)) << 3; + attr |= (lacl[2].a_perm & S_IRWXO); + +out: + if (uid_ret) + *uid_ret = uid; + if (gid_ret) + *gid_ret = gid; + if (attr_ret) + *attr_ret = attr; if (aclbufp) { -#if 0 - /* Disable owner/group permissions equivalence if owner SID == group SID. - It's technically not quite correct, but it helps in case a security - conscious application checks if a file has too open permissions. In - fact, since owner == group, there's no security issue here. */ - if (owner_sid == group_sid) - lacl[1].a_perm = lacl[0].a_perm; -#endif if (pos > nentries) { set_errno (ENOSPC); return -1; } memcpy (aclbufp, lacl, pos * sizeof (aclent_t)); - for (i = 0; i < pos; ++i) - aclbufp[i].a_perm &= ~(DENY_R | DENY_W | DENY_X); + for (int i = 0; i < pos; ++i) + aclbufp[i].a_perm &= S_IRWXO; aclsort32 (pos, 0, aclbufp); } + return pos; +} + +int +getacl (HANDLE handle, path_conv &pc, int nentries, aclent_t *aclbufp) +{ + security_descriptor sd; + + if (get_file_sd (handle, pc, sd, false)) + return -1; + int pos = get_posix_access (sd, NULL, NULL, NULL, aclbufp, nentries); syscall_printf ("%R = getacl(%S)", pos, pc.get_nt_native_path ()); return pos; } diff --git a/winsup/cygwin/sec_helper.cc b/winsup/cygwin/sec_helper.cc index 679f3a858..1ea69c372 100644 --- a/winsup/cygwin/sec_helper.cc +++ b/winsup/cygwin/sec_helper.cc @@ -40,6 +40,8 @@ SECURITY_ATTRIBUTES NO_COPY_RO sec_all_nih = MKSID (well_known_null_sid, "S-1-0-0", SECURITY_NULL_SID_AUTHORITY, 1, SECURITY_NULL_RID); +MKSID (well_known_cygwin_sid, "S-1-0-1132029815", + SECURITY_NULL_SID_AUTHORITY, 1, 0x43796777); /* "Cygw" */ MKSID (well_known_world_sid, "S-1-1-0", SECURITY_WORLD_SID_AUTHORITY, 1, SECURITY_WORLD_RID); MKSID (well_known_local_sid, "S-1-2-0", @@ -616,22 +618,21 @@ _recycler_sd (void *buf, bool users, bool dir) pre-Vista permissions the same way as on Vista and later. */ RtlCreateAcl (dacl, MAX_DACL_LEN (3), ACL_REVISION); RtlAddAccessAllowedAceEx (dacl, ACL_REVISION, - dir ? CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE + dir ? SUB_CONTAINERS_AND_OBJECTS_INHERIT : NO_INHERITANCE, FILE_ALL_ACCESS, well_known_admins_sid); RtlAddAccessAllowedAceEx (dacl, ACL_REVISION, - dir ? CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE + dir ? SUB_CONTAINERS_AND_OBJECTS_INHERIT : NO_INHERITANCE, FILE_ALL_ACCESS, well_known_system_sid); if (users) - RtlAddAccessAllowedAceEx (dacl, ACL_REVISION, NO_PROPAGATE_INHERIT_ACE, + RtlAddAccessAllowedAceEx (dacl, ACL_REVISION, INHERIT_NO_PROPAGATE, FILE_GENERIC_READ | FILE_GENERIC_EXECUTE | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES, well_known_users_sid); else RtlAddAccessAllowedAceEx (dacl, ACL_REVISION, - dir ? CONTAINER_INHERIT_ACE - | OBJECT_INHERIT_ACE + dir ? SUB_CONTAINERS_AND_OBJECTS_INHERIT : NO_INHERITANCE, FILE_ALL_ACCESS, cygheap->user.sid ()); LPVOID ace; diff --git a/winsup/cygwin/security.cc b/winsup/cygwin/security.cc index 1bc0c5047..e6cbd4cd5 100644 --- a/winsup/cygwin/security.cc +++ b/winsup/cygwin/security.cc @@ -234,194 +234,6 @@ set_file_sd (HANDLE fh, path_conv &pc, security_descriptor &sd, bool is_chown) return res; } -static void -get_attribute_from_acl (mode_t *attribute, PACL acl, PSID owner_sid, - PSID group_sid, bool grp_member) -{ - ACCESS_ALLOWED_ACE *ace; - mode_t allow = 0; - mode_t deny = 0; - mode_t *flags, *anti; - bool isownergroup = RtlEqualSid (owner_sid, group_sid); - - for (DWORD i = 0; i < acl->AceCount; ++i) - { - if (!NT_SUCCESS (RtlGetAce (acl, i, (PVOID *) &ace))) - continue; - if (ace->Header.AceFlags & INHERIT_ONLY_ACE) - continue; - switch (ace->Header.AceType) - { - case ACCESS_ALLOWED_ACE_TYPE: - flags = &allow; - anti = &deny; - break; - case ACCESS_DENIED_ACE_TYPE: - flags = &deny; - anti = &allow; - break; - default: - continue; - } - - cygpsid ace_sid ((PSID) &ace->SidStart); - if (ace_sid == well_known_world_sid) - { - if (ace->Mask & FILE_READ_BITS) - *flags |= ((!(*anti & S_IROTH)) ? S_IROTH : 0) - | ((!isownergroup && !(*anti & S_IRGRP)) ? S_IRGRP : 0) - | ((!(*anti & S_IRUSR)) ? S_IRUSR : 0); - if (ace->Mask & FILE_WRITE_BITS) - *flags |= ((!(*anti & S_IWOTH)) ? S_IWOTH : 0) - | ((!isownergroup && !(*anti & S_IWGRP)) ? S_IWGRP : 0) - | ((!(*anti & S_IWUSR)) ? S_IWUSR : 0); - if (ace->Mask & FILE_EXEC_BITS) - *flags |= ((!(*anti & S_IXOTH)) ? S_IXOTH : 0) - | ((!isownergroup && !(*anti & S_IXGRP)) ? S_IXGRP : 0) - | ((!(*anti & S_IXUSR)) ? S_IXUSR : 0); - if ((S_ISDIR (*attribute)) && - (ace->Mask & (FILE_WRITE_DATA | FILE_EXECUTE | FILE_DELETE_CHILD)) - == (FILE_WRITE_DATA | FILE_EXECUTE)) - *flags |= S_ISVTX; - } - else if (ace_sid == well_known_null_sid) - { - /* Read SUID, SGID and VTX bits from NULL ACE. */ - if (ace->Mask & FILE_READ_DATA) - *flags |= S_ISVTX; - if (ace->Mask & FILE_WRITE_DATA) - *flags |= S_ISGID; - if (ace->Mask & FILE_APPEND_DATA) - *flags |= S_ISUID; - } - else if (ace_sid == owner_sid) - { - if (ace->Mask & FILE_READ_BITS) - *flags |= ((!(*anti & S_IRUSR)) ? S_IRUSR : 0); - if (ace->Mask & FILE_WRITE_BITS) - *flags |= ((!(*anti & S_IWUSR)) ? S_IWUSR : 0); - if (ace->Mask & FILE_EXEC_BITS) - *flags |= ((!(*anti & S_IXUSR)) ? S_IXUSR : 0); - /* Apply deny mask to group if group SID == owner SID. */ - if (group_sid && isownergroup - && ace->Header.AceType == ACCESS_DENIED_ACE_TYPE) - { - if (ace->Mask & FILE_READ_BITS) - *flags |= ((!(*anti & S_IRUSR)) ? S_IRGRP : 0); - if (ace->Mask & FILE_WRITE_BITS) - *flags |= ((!(*anti & S_IWUSR)) ? S_IWGRP : 0); - if (ace->Mask & FILE_EXEC_BITS) - *flags |= ((!(*anti & S_IXUSR)) ? S_IXGRP : 0); - } - } - else if (ace_sid == group_sid) - { - if (ace->Mask & FILE_READ_BITS) - *flags |= ((!(*anti & S_IRGRP)) ? S_IRGRP : 0) - | ((grp_member && !(*anti & S_IRUSR)) ? S_IRUSR : 0); - if (ace->Mask & FILE_WRITE_BITS) - *flags |= ((!(*anti & S_IWGRP)) ? S_IWGRP : 0) - | ((grp_member && !(*anti & S_IWUSR)) ? S_IWUSR : 0); - if (ace->Mask & FILE_EXEC_BITS) - *flags |= ((!(*anti & S_IXGRP)) ? S_IXGRP : 0) - | ((grp_member && !(*anti & S_IXUSR)) ? S_IXUSR : 0); - } - else if (flags == &allow) - { - /* Simplified computation of additional group permissions based on - the CLASS_OBJ value. CLASS_OBJ represents the or'ed value of - the primary group permissions and all secondary user and group - permissions. FIXME: This only takes ACCESS_ALLOWED_ACEs into - account. The computation with additional ACCESS_DENIED_ACE - handling is much more complicated. */ - if (ace->Mask & FILE_READ_BITS) - *flags |= S_IRGRP; - if (ace->Mask & FILE_WRITE_BITS) - *flags |= S_IWGRP; - if (ace->Mask & FILE_EXEC_BITS) - *flags |= S_IXGRP; - } - } - *attribute &= ~(S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX | S_ISGID | S_ISUID); -#if 0 - /* Disable owner/group permissions equivalence if owner SID == group SID. - It's technically not quite correct, but it helps in case a security - conscious application checks if a file has too open permissions. In - fact, since owner == group, there's no security issue here. */ - if (owner_sid && group_sid && RtlEqualSid (owner_sid, group_sid) - /* FIXME: temporary exception for /var/empty */ - && well_known_system_sid != group_sid) - { - allow &= ~(S_IRGRP | S_IWGRP | S_IXGRP); - allow |= (((allow & S_IRUSR) ? S_IRGRP : 0) - | ((allow & S_IWUSR) ? S_IWGRP : 0) - | ((allow & S_IXUSR) ? S_IXGRP : 0)); - } -#endif - *attribute |= allow; -} - -static void -get_info_from_sd (PSECURITY_DESCRIPTOR psd, mode_t *attribute, - uid_t *uidret, gid_t *gidret) -{ - if (!psd) - { - /* If reading the security descriptor failed, treat the object - as unreadable. */ - if (attribute) - *attribute &= ~(S_IRWXU | S_IRWXG | S_IRWXO); - if (uidret) - *uidret = ILLEGAL_UID; - if (gidret) - *gidret = ILLEGAL_GID; - return; - } - - cygpsid owner_sid; - cygpsid group_sid; - NTSTATUS status; - BOOLEAN dummy; - - status = RtlGetOwnerSecurityDescriptor (psd, (PSID *) &owner_sid, &dummy); - if (!NT_SUCCESS (status)) - debug_printf ("RtlGetOwnerSecurityDescriptor: %y", status); - status = RtlGetGroupSecurityDescriptor (psd, (PSID *) &group_sid, &dummy); - if (!NT_SUCCESS (status)) - debug_printf ("RtlGetGroupSecurityDescriptor: %y", status); - - uid_t uid; - gid_t gid; - bool grp_member = get_sids_info (owner_sid, group_sid, &uid, &gid); - if (uidret) - *uidret = uid; - if (gidret) - *gidret = gid; - - if (!attribute) - { - syscall_printf ("uid %u, gid %u", uid, gid); - return; - } - - PACL acl; - BOOLEAN acl_exists; - - status = RtlGetDaclSecurityDescriptor (psd, &acl_exists, &acl, &dummy); - if (!NT_SUCCESS (status)) - { - __seterrno_from_nt_status (status); - *attribute &= ~(S_IRWXU | S_IRWXG | S_IRWXO); - } - else if (!acl_exists || !acl) - *attribute |= S_IRWXU | S_IRWXG | S_IRWXO; - else - get_attribute_from_acl (attribute, acl, owner_sid, group_sid, grp_member); - - syscall_printf ("%sACL %y, uid %u, gid %u", - (!acl_exists || !acl)?"NO ":"", *attribute, uid, gid); -} - static int get_reg_sd (HANDLE handle, security_descriptor &sd_ret) { @@ -454,7 +266,7 @@ get_reg_attribute (HKEY hkey, mode_t *attribute, uid_t *uidret, if (!get_reg_sd (hkey, sd)) { - get_info_from_sd (sd, attribute, uidret, gidret); + get_posix_access (sd, attribute, uidret, gidret, NULL, 0); return 0; } /* The entries are already set to default values */ @@ -471,7 +283,7 @@ get_file_attribute (HANDLE handle, path_conv &pc, if (!get_file_sd (handle, pc, sd, false)) { - get_info_from_sd (sd, attribute, uidret, gidret); + get_posix_access (sd, attribute, uidret, gidret, NULL, 0); return 0; } /* ENOSYS is returned by get_file_sd if fetching the DACL from a remote @@ -773,10 +585,10 @@ alloc_sd (path_conv &pc, uid_t uid, gid_t gid, int attribute, { if ((S_ISDIR (attribute) && (attribute & S_JUSTCREATED)) || (ace->Header.AceFlags - & (CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE)) == 0) + & (SUB_CONTAINERS_AND_OBJECTS_INHERIT)) == 0) continue; else - ace->Header.AceFlags |= INHERIT_ONLY_ACE; + ace->Header.AceFlags |= INHERIT_ONLY; } if (attribute & S_JUSTCREATED) { @@ -797,8 +609,7 @@ alloc_sd (path_conv &pc, uid_t uid, gid_t gid, int attribute, it inherits the default ACL from its parent, so mark all unrelated, inherited ACEs inheritable. */ if (S_ISDIR (attribute)) - ace->Header.AceFlags |= CONTAINER_INHERIT_ACE - | OBJECT_INHERIT_ACE; + ace->Header.AceFlags |= SUB_CONTAINERS_AND_OBJECTS_INHERIT; } else if (uid == ILLEGAL_UID && gid == ILLEGAL_UID && ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE @@ -835,8 +646,7 @@ alloc_sd (path_conv &pc, uid_t uid, gid_t gid, int attribute, Cygwin applications don't need this. */ if (S_ISDIR (attribute) && (attribute & S_JUSTCREATED)) { - const DWORD inherit = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE - | INHERIT_ONLY_ACE; + const DWORD inherit = SUB_CONTAINERS_AND_OBJECTS_INHERIT | INHERIT_ONLY; /* Set allow ACE for owner. */ if (!add_access_allowed_ace (acl, ace_off++, owner_allow, well_known_creator_owner_sid, acl_len, @@ -936,7 +746,7 @@ get_object_attribute (HANDLE handle, uid_t *uidret, gid_t *gidret, if (get_object_sd (handle, sd)) return -1; - get_info_from_sd (sd, attribute, uidret, gidret); + get_posix_access (sd, attribute, uidret, gidret, NULL, 0); return 0; } diff --git a/winsup/cygwin/security.h b/winsup/cygwin/security.h index 867345774..9457eba6b 100644 --- a/winsup/cygwin/security.h +++ b/winsup/cygwin/security.h @@ -393,6 +393,7 @@ public: }; extern cygpsid well_known_null_sid; +extern cygpsid well_known_cygwin_sid; extern cygpsid well_known_world_sid; extern cygpsid well_known_local_sid; extern cygpsid well_known_console_logon_sid; @@ -463,6 +464,8 @@ bool get_sids_info (cygpsid, cygpsid, uid_t * , gid_t *); struct acl; extern "C" int aclsort32 (int, int, struct acl *); extern "C" int acl32 (const char *, int, int, struct acl *); +int get_posix_access (PSECURITY_DESCRIPTOR, mode_t *, uid_t *, gid_t *, + struct acl *, int); int getacl (HANDLE, path_conv &, int, struct acl *); int setacl (HANDLE, path_conv &, int, struct acl *, bool &);