Welcome to mirror list, hosted at ThFree Co, Russian Federation.

cygwin.com/git/newlib-cygwin.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'winsup/cygwin/sec_acl.cc')
-rw-r--r--winsup/cygwin/sec_acl.cc1195
1 files changed, 818 insertions, 377 deletions
diff --git a/winsup/cygwin/sec_acl.cc b/winsup/cygwin/sec_acl.cc
index 5a2048caf..7d97fca02 100644
--- a/winsup/cygwin/sec_acl.cc
+++ b/winsup/cygwin/sec_acl.cc
@@ -24,8 +24,75 @@ details. */
#include "ntdll.h"
#include "tls_pbuf.h"
-static int
-searchace (aclent_t *aclp, int nentries, int type, uid_t id = ILLEGAL_UID)
+/* How does a correctly constructed new-style Windows ACL claiming to be a
+ POSIX ACL look like?
+
+ - NULL ACE (special bits, CLASS_OBJ).
+
+ - USER_OBJ deny. If the user has less permissions than the sum of CLASS_OBJ
+ (or GROUP_OBJ if CLASS_OBJ doesn't exist) and OTHER_OBJ, deny the excess
+ permissions so that group and other perms don't spill into the owner perms.
+
+ USER_OBJ deny ACE == ~USER_OBJ & (CLASS_OBJ | OTHER_OBJ)
+ or
+ USER_OBJ deny ACE == ~USER_OBJ & (GROUP_OBJ | OTHER_OBJ)
+
+ - USER deny. If a user has different permissions from CLASS_OBJ, or if the
+ user has less permissions than OTHER_OBJ, deny the excess permissions.
+
+ USER deny ACE == (USER ^ CLASS_OBJ) | (~USER & OTHER_OBJ)
+
+ - USER_OBJ allow ACE
+ - USER allow ACEs
+
+ The POSIX permissions returned for a USER entry are the allow bits alone!
+
+ - GROUP{_OBJ} deny. If a group has more permissions than CLASS_OBJ,
+ or less permissions than OTHER_OBJ, deny the excess permissions.
+
+ GROUP{_OBJ} deny ACEs == (GROUP & ~CLASS_OBJ) | (~GROUP & OTHER_OBJ)
+
+ - 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 NULL 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 using an access denied ACE, plus
+ setting an as yet unused bit in the access mask. The new ACEs can exist
+ twice in an ACL, the "normal one" containing 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_ISGID)) >> 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)
+#define CYG_ACE_NEW_STYLE READ_CONTROL /* New style if set. */
+
+int
+searchace (aclent_t *aclp, int nentries, int type, uid_t id)
{
int i;
@@ -36,270 +103,325 @@ searchace (aclent_t *aclp, int nentries, int type, uid_t id = ILLEGAL_UID)
return -1;
}
-/* This function *requires* an acl list sorted with aclsort{32}. */
-int
-setacl (HANDLE handle, path_conv &pc, int nentries, aclent_t *aclbufp,
- bool &writable)
+/* Define own bit masks rather than using the GENERIC masks. The latter
+ also contain standard rights, which we don't need here. */
+#define FILE_ALLOW_READ (FILE_READ_DATA | FILE_READ_ATTRIBUTES | \
+ FILE_READ_EA)
+#define FILE_DENY_READ (FILE_READ_DATA | FILE_READ_EA)
+#define FILE_ALLOW_WRITE (FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | \
+ FILE_WRITE_EA | FILE_APPEND_DATA)
+#define FILE_DENY_WRITE FILE_ALLOW_WRITE | FILE_DELETE_CHILD
+#define FILE_DENY_WRITE_OWNER (FILE_WRITE_DATA | FILE_WRITE_EA | \
+ FILE_APPEND_DATA | FILE_DELETE_CHILD)
+#define FILE_ALLOW_EXEC (FILE_EXECUTE)
+#define FILE_DENY_EXEC FILE_ALLOW_EXEC
+
+#define STD_RIGHTS_OTHER (STANDARD_RIGHTS_READ | SYNCHRONIZE)
+#define STD_RIGHTS_OWNER (STANDARD_RIGHTS_ALL | SYNCHRONIZE)
+
+/* From the attributes and the POSIX ACL list, compute a new-style Cygwin
+ security descriptor. The function returns a pointer to the
+ SECURITY_DESCRIPTOR in sd_ret, or NULL if the function fails.
+
+ This function *requires* a verified and sorted acl list! */
+PSECURITY_DESCRIPTOR
+set_posix_access (mode_t attr, uid_t uid, gid_t gid,
+ aclent_t *aclbufp, int nentries,
+ security_descriptor &sd_ret,
+ bool is_samba)
{
- security_descriptor sd_ret;
- tmp_pathbuf tp;
-
- if (get_file_sd (handle, pc, sd_ret, false))
- return -1;
-
+ SECURITY_DESCRIPTOR sd;
+ cyg_ldap cldap;
+ PSID owner, group;
NTSTATUS status;
+ tmp_pathbuf tp;
+ cygpsid *aclsid;
PACL acl;
- BOOLEAN acl_exists, dummy;
-
- /* Get owner SID. */
- PSID owner_sid;
- status = RtlGetOwnerSecurityDescriptor (sd_ret, &owner_sid, &dummy);
- if (!NT_SUCCESS (status))
- {
- __seterrno_from_nt_status (status);
- return -1;
- }
- cygsid owner (owner_sid);
-
- /* Get group SID. */
- PSID group_sid;
- status = RtlGetGroupSecurityDescriptor (sd_ret, &group_sid, &dummy);
- if (!NT_SUCCESS (status))
- {
- __seterrno_from_nt_status (status);
- return -1;
- }
- cygsid group (group_sid);
-
- /* Search for NULL ACE and store state of SUID, SGID and VTX bits. */
- DWORD null_mask = 0;
- if (NT_SUCCESS (RtlGetDaclSecurityDescriptor (sd_ret, &acl_exists, &acl,
- &dummy)))
- for (USHORT i = 0; i < acl->AceCount; ++i)
- {
- ACCESS_ALLOWED_ACE *ace;
- if (NT_SUCCESS (RtlGetAce (acl, i, (PVOID *) &ace)))
- {
- cygpsid ace_sid ((PSID) &ace->SidStart);
- if (ace_sid == well_known_null_sid)
- {
- null_mask = ace->Mask;
- break;
- }
- }
- }
+ size_t acl_len = sizeof (ACL);
+ mode_t class_obj = 0, other_obj, group_obj, deny;
+ DWORD access;
+ int idx, start_idx, tmp_idx;
+ bool owner_eq_group = false;
+ bool dev_has_admins = false;
/* Initialize local security descriptor. */
- SECURITY_DESCRIPTOR sd;
RtlCreateSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION);
/* As in alloc_sd, set SE_DACL_PROTECTED to prevent the DACL from being
modified by inheritable ACEs. */
RtlSetControlSecurityDescriptor (&sd, SE_DACL_PROTECTED, SE_DACL_PROTECTED);
+ /* Fetch owner and group and set in security descriptor. */
+ owner = sidfromuid (uid, &cldap);
+ group = sidfromgid (gid, &cldap);
+ if (!owner || !group)
+ {
+ set_errno (EINVAL);
+ return NULL;
+ }
status = RtlSetOwnerSecurityDescriptor (&sd, owner, FALSE);
if (!NT_SUCCESS (status))
{
__seterrno_from_nt_status (status);
- return -1;
+ return NULL;
}
status = RtlSetGroupSecurityDescriptor (&sd, group, FALSE);
if (!NT_SUCCESS (status))
{
__seterrno_from_nt_status (status);
- return -1;
+ return NULL;
}
+ owner_eq_group = RtlEqualSid (owner, group);
+ if (S_ISCHR (attr))
+ dev_has_admins = well_known_admins_sid == owner
+ || well_known_admins_sid == group;
- /* Fill access control list. */
- acl = (PACL) tp.w_get ();
- size_t acl_len = sizeof (ACL);
- int ace_off = 0;
+ /* No POSIX ACL? Use attr to generate one from scratch. */
+ if (!aclbufp)
+ {
+ aclbufp = (aclent_t *) tp.c_get ();
+ aclbufp[0].a_type = USER_OBJ;
+ aclbufp[0].a_id = ILLEGAL_UID;
+ aclbufp[0].a_perm = (attr >> 6) & S_IRWXO;
+ aclbufp[1].a_type = GROUP_OBJ;
+ aclbufp[1].a_id = ILLEGAL_GID;
+ aclbufp[1].a_perm = (attr >> 3) & S_IRWXO;
+ aclbufp[2].a_type = OTHER_OBJ;
+ aclbufp[2].a_id = ILLEGAL_GID;
+ aclbufp[2].a_perm = attr & S_IRWXO;
+ nentries = MIN_ACL_ENTRIES;
+ if (S_ISDIR (attr))
+ {
+ aclbufp[3].a_type = DEF_USER_OBJ;
+ aclbufp[3].a_id = ILLEGAL_UID;
+ aclbufp[3].a_perm = (attr >> 6) & S_IRWXO;
+ aclbufp[4].a_type = GROUP_OBJ;
+ aclbufp[4].a_id = ILLEGAL_GID;
+ aclbufp[4].a_perm = (attr >> 3) & S_IRWXO;
+ aclbufp[5].a_type = OTHER_OBJ;
+ aclbufp[5].a_id = ILLEGAL_GID;
+ aclbufp[5].a_perm = attr & S_IRWXO;
+ nentries += MIN_ACL_ENTRIES;
+ }
+ }
- cygsid sid;
- struct passwd *pw;
- struct group *gr;
- int pos;
- cyg_ldap cldap;
+ /* Collect SIDs of all entries in aclbufp. */
+ aclsid = (cygpsid *) tp.w_get ();
+ for (idx = 0; idx < nentries; ++idx)
+ switch (aclbufp[idx].a_type)
+ {
+ case USER_OBJ:
+ aclsid[idx] = owner;
+ break;
+ case DEF_USER_OBJ:
+ aclsid[idx] = well_known_creator_owner_sid;
+ break;
+ case USER:
+ case DEF_USER:
+ aclsid[idx] = sidfromuid (aclbufp[idx].a_id, &cldap);
+ break;
+ case GROUP_OBJ:
+ aclsid[idx] = group;
+ break;
+ case DEF_GROUP_OBJ:
+ aclsid[idx] = !(attr & S_ISGID) ? (PSID) well_known_creator_group_sid
+ : group;
+ break;
+ case GROUP:
+ case DEF_GROUP:
+ aclsid[idx] = sidfromgid (aclbufp[idx].a_id, &cldap);
+ break;
+ case CLASS_OBJ:
+ case DEF_CLASS_OBJ:
+ aclsid[idx] = well_known_null_sid;
+ break;
+ case OTHER_OBJ:
+ case DEF_OTHER_OBJ:
+ aclsid[idx] = well_known_world_sid;
+ break;
+ }
+ /* Initialize ACL. */
+ acl = (PACL) tp.w_get ();
RtlCreateAcl (acl, ACL_MAXIMUM_SIZE, ACL_REVISION);
- writable = false;
-
- bool *invalid = (bool *) tp.c_get ();
- memset (invalid, 0, nentries * sizeof *invalid);
-
- /* Pre-compute owner, group, and other permissions to allow creating
- matching deny ACEs as in alloc_sd. */
- DWORD owner_allow = 0, group_allow = 0, other_allow = 0;
- PDWORD allow;
- for (int i = 0; i < nentries; ++i)
+ /* This loop has two runs, the first handling the actual permission,
+ the second handling the default permissions. */
+ idx = 0;
+ for (int def = 0; def <= ACL_DEFAULT; def += ACL_DEFAULT)
{
- switch (aclbufp[i].a_type)
+ DWORD inherit = def ? SUB_CONTAINERS_AND_OBJECTS_INHERIT | INHERIT_ONLY
+ : NO_INHERITANCE;
+
+ /* No default ACEs on files. */
+ if (def && !S_ISDIR (attr))
{
- case USER_OBJ:
- allow = &owner_allow;
- *allow = STANDARD_RIGHTS_ALL
- | (pc.fs_is_samba () ? 0 : FILE_WRITE_ATTRIBUTES);
- break;
- case GROUP_OBJ:
- allow = &group_allow;
- break;
- case OTHER_OBJ:
- allow = &other_allow;
+ /* Trying to set default ACEs on a non-directory is an error.
+ The underlying functions on Linux return EACCES. */
+ if (idx < nentries && aclbufp[idx].a_type & ACL_DEFAULT)
+ {
+ set_errno (EACCES);
+ return NULL;
+ }
break;
- default:
- continue;
- }
- *allow |= STANDARD_RIGHTS_READ | SYNCHRONIZE
- | (pc.fs_is_samba () ? 0 : FILE_READ_ATTRIBUTES);
- if (aclbufp[i].a_perm & S_IROTH)
- *allow |= FILE_GENERIC_READ;
- if (aclbufp[i].a_perm & S_IWOTH)
- {
- *allow |= FILE_GENERIC_WRITE;
- writable = true;
}
- if (aclbufp[i].a_perm & S_IXOTH)
- *allow |= FILE_GENERIC_EXECUTE & ~FILE_READ_ATTRIBUTES;
- /* Keep S_ISVTX rule in sync with alloc_sd. */
- if (pc.isdir ()
- && (aclbufp[i].a_perm & (S_IWOTH | S_IXOTH)) == (S_IWOTH | S_IXOTH)
- && (aclbufp[i].a_type == USER_OBJ
- || !(null_mask & FILE_READ_DATA)))
- *allow |= FILE_DELETE_CHILD;
- invalid[i] = true;
- }
- bool isownergroup = (owner == group);
- DWORD owner_deny = ~owner_allow & (group_allow | other_allow);
- owner_deny &= ~(STANDARD_RIGHTS_READ
- | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES);
- DWORD group_deny = ~group_allow & other_allow;
- group_deny &= ~(STANDARD_RIGHTS_READ | FILE_READ_ATTRIBUTES);
-
- /* Set deny ACE for owner. */
- if (owner_deny
- && !add_access_denied_ace (acl, ace_off++, owner_deny,
- owner, acl_len, NO_INHERITANCE))
- return -1;
- /* Set deny ACE for group here to respect the canonical order,
- if this does not impact owner */
- if (group_deny && !(group_deny & owner_allow) && !isownergroup
- && !add_access_denied_ace (acl, ace_off++, group_deny,
- group, acl_len, NO_INHERITANCE))
- return -1;
- /* Set allow ACE for owner. */
- if (!add_access_allowed_ace (acl, ace_off++, owner_allow,
- owner, acl_len, NO_INHERITANCE))
- return -1;
- /* Set deny ACE for group, if still needed. */
- if (group_deny & owner_allow && !isownergroup
- && !add_access_denied_ace (acl, ace_off++, group_deny,
- group, acl_len, NO_INHERITANCE))
- return -1;
- /* Set allow ACE for group. */
- if (!isownergroup
- && !add_access_allowed_ace (acl, ace_off++, group_allow,
- group, acl_len, NO_INHERITANCE))
- return -1;
- /* Set allow ACE for everyone. */
- if (!add_access_allowed_ace (acl, ace_off++, other_allow,
- well_known_world_sid, acl_len, NO_INHERITANCE))
- return -1;
- /* If a NULL ACE exists, copy it verbatim. */
- if (null_mask)
- if (!add_access_allowed_ace (acl, ace_off++, null_mask, well_known_null_sid,
- acl_len, NO_INHERITANCE))
- return -1;
- for (int i = 0; i < nentries; ++i)
- {
- DWORD allow;
- /* Skip invalidated entries. */
- if (invalid[i])
- continue;
- allow = STANDARD_RIGHTS_READ
- | (pc.fs_is_samba () ? 0 : FILE_READ_ATTRIBUTES);
- if (aclbufp[i].a_perm & S_IROTH)
- allow |= FILE_GENERIC_READ;
- if (aclbufp[i].a_perm & S_IWOTH)
+ /* To compute deny access masks, we need group_obj, other_obj and... */
+ tmp_idx = searchace (aclbufp, nentries, def | GROUP_OBJ);
+ /* No default entries present? */
+ if (tmp_idx < 0)
+ break;
+ group_obj = aclbufp[tmp_idx].a_perm;
+ tmp_idx = searchace (aclbufp, nentries, def | OTHER_OBJ);
+ other_obj = aclbufp[tmp_idx].a_perm;
+
+ /* ... class_obj. Create Cygwin ACE. Only the S_ISGID attribute gets
+ inherited. */
+ access = CYG_ACE_ISBITS_TO_WIN (def ? attr & S_ISGID : attr)
+ | CYG_ACE_NEW_STYLE;
+ tmp_idx = searchace (aclbufp, nentries, def | CLASS_OBJ);
+ if (tmp_idx >= 0)
{
- allow |= FILE_GENERIC_WRITE;
- writable = true;
+ class_obj = aclbufp[tmp_idx].a_perm;
+ access |= CYG_ACE_MASK_TO_WIN (class_obj);
}
- if (aclbufp[i].a_perm & S_IXOTH)
- allow |= FILE_GENERIC_EXECUTE & ~FILE_READ_ATTRIBUTES;
- /* Keep S_ISVTX rule in sync with alloc_sd. */
- if (pc.isdir ()
- && (aclbufp[i].a_perm & (S_IWOTH | S_IXOTH)) == (S_IWOTH | S_IXOTH)
- && !(null_mask & FILE_READ_DATA))
- allow |= FILE_DELETE_CHILD;
- /* Set inherit property. */
- DWORD inheritance = (aclbufp[i].a_type & ACL_DEFAULT)
- ? (CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE
- | INHERIT_ONLY_ACE)
- : NO_INHERITANCE;
- /*
- * If a specific acl contains a corresponding default entry with
- * identical permissions, only one Windows ACE with proper
- * inheritance bits is created.
- */
- if (!(aclbufp[i].a_type & ACL_DEFAULT)
- && aclbufp[i].a_type & (USER|GROUP)
- && (pos = searchace (aclbufp + i + 1, nentries - i - 1,
- aclbufp[i].a_type | ACL_DEFAULT,
- (aclbufp[i].a_type & (USER|GROUP))
- ? aclbufp[i].a_id : ILLEGAL_UID)) >= 0
- && aclbufp[i].a_perm == aclbufp[i + 1 + pos].a_perm)
+ else
{
- inheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE;
- /* invalidate the corresponding default entry. */
- invalid[i + 1 + pos] = true;
+ /* Setting class_obj to group_obj allows to write below code without
+ additional checks for existence of a CLASS_OBJ. */
+ class_obj = group_obj;
}
- switch (aclbufp[i].a_type)
+ /* Note that Windows filters the ACE Mask value so it only reflects
+ the bit values supported by the object type. The result is that
+ we can't set a CLASS_OBJ value for ptys. The get_posix_access
+ function has to workaround that. */
+ if (!add_access_denied_ace (acl, access, well_known_null_sid, acl_len,
+ inherit))
+ return NULL;
+
+ /* Do we potentially chmod a file with owner SID == group SID? If so,
+ make sure the owner perms are always >= group perms. */
+ if (!def && owner_eq_group)
+ aclbufp[0].a_perm |= group_obj & class_obj;
+
+ /* This loop has two runs, the first w/ check_types == (USER_OBJ | USER),
+ the second w/ check_types == (GROUP_OBJ | GROUP). Each run creates
+ first the deny, then the allow ACEs for the current types. */
+ for (int check_types = USER_OBJ | USER;
+ check_types < CLASS_OBJ;
+ check_types <<= 2)
{
- case DEF_USER_OBJ:
- allow |= STANDARD_RIGHTS_ALL
- | (pc.fs_is_samba () ? 0 : FILE_WRITE_ATTRIBUTES);
- if (!add_access_allowed_ace (acl, ace_off++, allow,
- well_known_creator_owner_sid, acl_len, inheritance))
- return -1;
- break;
- case USER:
- case DEF_USER:
- if (!(pw = internal_getpwuid (aclbufp[i].a_id, &cldap))
- || !sid.getfrompw (pw))
+ /* Create deny ACEs for users, then groups. */
+ for (start_idx = idx;
+ idx < nentries && aclbufp[idx].a_type & check_types;
+ ++idx)
{
- set_errno (EINVAL);
- return -1;
+ /* Avoid creating DENY ACEs for the second occurrence of
+ accounts which show up twice, as USER_OBJ and USER, or
+ GROUP_OBJ and GROUP. */
+ if ((aclbufp[idx].a_type & USER && aclsid[idx] == owner)
+ || (aclbufp[idx].a_type & GROUP && aclsid[idx] == group))
+ continue;
+ /* For the rules how to construct the deny access mask, see the
+ comment right at the start of this file. */
+ if (aclbufp[idx].a_type & USER_OBJ)
+ deny = ~aclbufp[idx].a_perm & (class_obj | other_obj);
+ else if (aclbufp[idx].a_type & USER)
+ deny = (aclbufp[idx].a_perm ^ class_obj)
+ | (~aclbufp[idx].a_perm & other_obj);
+ /* Accommodate Windows: Only generate deny masks for SYSTEM
+ and the Administrators group in terms of the execute bit,
+ if they are not the primary group. */
+ else if (aclbufp[idx].a_type & GROUP
+ && (aclsid[idx] == well_known_system_sid
+ || aclsid[idx] == well_known_admins_sid))
+ deny = aclbufp[idx].a_perm & ~(class_obj | S_IROTH | S_IWOTH);
+ else
+ deny = (aclbufp[idx].a_perm & ~class_obj)
+ | (~aclbufp[idx].a_perm & other_obj);
+ if (!deny)
+ continue;
+ access = 0;
+ if (deny & S_IROTH)
+ access |= FILE_DENY_READ;
+ if (deny & S_IWOTH)
+ access |= (aclbufp[idx].a_type & USER_OBJ)
+ ? FILE_DENY_WRITE_OWNER : FILE_DENY_WRITE;
+ if (deny & S_IXOTH)
+ access |= FILE_DENY_EXEC;
+ if (!add_access_denied_ace (acl, access, aclsid[idx], acl_len,
+ inherit))
+ return NULL;
}
- if (!add_access_allowed_ace (acl, ace_off++, allow,
- sid, acl_len, inheritance))
- return -1;
- break;
- case DEF_GROUP_OBJ:
- if (!add_access_allowed_ace (acl, ace_off++, allow,
- well_known_creator_group_sid, acl_len, inheritance))
- return -1;
- break;
- case GROUP:
- case DEF_GROUP:
- if (!(gr = internal_getgrgid (aclbufp[i].a_id, &cldap))
- || !sid.getfromgr (gr))
+ /* Create allow ACEs for users, then groups. */
+ for (idx = start_idx;
+ idx < nentries && aclbufp[idx].a_type & check_types;
+ ++idx)
{
- set_errno (EINVAL);
- return -1;
+ /* Don't set FILE_READ/WRITE_ATTRIBUTES unconditionally on Samba,
+ otherwise it enforces read permissions. */
+ access = STD_RIGHTS_OTHER | (is_samba ? 0 : FILE_READ_ATTRIBUTES);
+ if (aclbufp[idx].a_type & USER_OBJ)
+ {
+ access |= STD_RIGHTS_OWNER;
+ if (!is_samba)
+ access |= FILE_WRITE_ATTRIBUTES;
+ /* Set FILE_DELETE_CHILD on files with "rwx" perms for the
+ owner so that the owner gets "full control" (Duh). */
+ if (aclbufp[idx].a_perm == S_IRWXO)
+ access |= FILE_DELETE_CHILD;
+ }
+ if (aclbufp[idx].a_perm & S_IROTH)
+ access |= FILE_ALLOW_READ;
+ if (aclbufp[idx].a_perm & S_IWOTH)
+ access |= FILE_ALLOW_WRITE;
+ if (aclbufp[idx].a_perm & S_IXOTH)
+ access |= FILE_ALLOW_EXEC;
+ /* Handle S_ISVTX. */
+ if (S_ISDIR (attr)
+ && (aclbufp[idx].a_perm & (S_IWOTH | S_IXOTH))
+ == (S_IWOTH | S_IXOTH)
+ && (!(attr & S_ISVTX) || aclbufp[idx].a_type & USER_OBJ))
+ access |= FILE_DELETE_CHILD;
+ /* For ptys, make sure the Administrators group has WRITE_DAC
+ and WRITE_OWNER perms. */
+ if (dev_has_admins && aclsid[idx] == well_known_admins_sid)
+ access |= STD_RIGHTS_OWNER;
+ if (!add_access_allowed_ace (acl, access, aclsid[idx], acl_len,
+ inherit))
+ return NULL;
}
- if (!add_access_allowed_ace (acl, ace_off++, allow,
- sid, acl_len, inheritance))
- return -1;
- break;
- case DEF_OTHER_OBJ:
- if (!add_access_allowed_ace (acl, ace_off++, allow,
- well_known_world_sid,
- acl_len, inheritance))
- return -1;
}
+ /* For ptys if the admins group isn't in the ACL, add an ACE to make
+ sure the group has WRITE_DAC and WRITE_OWNER perms. */
+ if (S_ISCHR (attr) && !dev_has_admins
+ && !add_access_allowed_ace (acl,
+ STD_RIGHTS_OWNER | FILE_ALLOW_READ
+ | FILE_ALLOW_WRITE,
+ well_known_admins_sid, acl_len,
+ NO_INHERITANCE))
+ return NULL;
+ /* Create allow ACE for other. It's preceeded by class_obj if it exists.
+ If so, skip it. */
+ if (aclbufp[idx].a_type & CLASS_OBJ)
+ ++idx;
+ access = STD_RIGHTS_OTHER | (is_samba ? 0 : FILE_READ_ATTRIBUTES);
+ if (aclbufp[idx].a_perm & S_IROTH)
+ access |= FILE_ALLOW_READ;
+ if (aclbufp[idx].a_perm & S_IWOTH)
+ access |= FILE_ALLOW_WRITE;
+ if (aclbufp[idx].a_perm & S_IXOTH)
+ access |= FILE_ALLOW_EXEC;
+ /* Handle S_ISVTX. */
+ if (S_ISDIR (attr)
+ && (aclbufp[idx].a_perm & (S_IWOTH | S_IXOTH)) == (S_IWOTH | S_IXOTH)
+ && !(attr & S_ISVTX))
+ access |= FILE_DELETE_CHILD;
+ if (!add_access_allowed_ace (acl, access, aclsid[idx++], acl_len,
+ inherit))
+ return NULL;
}
+
/* Set AclSize to computed value. */
acl->AclSize = acl_len;
debug_printf ("ACL-Size: %u", acl_len);
@@ -308,7 +430,7 @@ setacl (HANDLE handle, path_conv &pc, int nentries, aclent_t *aclbufp,
if (!NT_SUCCESS (status))
{
__seterrno_from_nt_status (status);
- return -1;
+ return NULL;
}
/* Make self relative security descriptor in sd_ret. */
DWORD sd_size = 0;
@@ -316,36 +438,65 @@ setacl (HANDLE handle, path_conv &pc, int nentries, aclent_t *aclbufp,
if (sd_size <= 0)
{
__seterrno ();
- return -1;
+ return NULL;
}
if (!sd_ret.realloc (sd_size))
{
set_errno (ENOMEM);
- return -1;
+ return NULL;
}
status = RtlAbsoluteToSelfRelativeSD (&sd, sd_ret, &sd_size);
if (!NT_SUCCESS (status))
{
__seterrno_from_nt_status (status);
- return -1;
+ return NULL;
}
debug_printf ("Created SD-Size: %u", sd_ret.size ());
+ return sd_ret;
+}
+
+/* This function *requires* a verified and sorted acl list! */
+int
+setacl (HANDLE handle, path_conv &pc, int nentries, aclent_t *aclbufp,
+ bool &writable)
+{
+ security_descriptor sd, sd_ret;
+ mode_t attr = pc.isdir () ? S_IFDIR : 0;
+ uid_t uid;
+ gid_t gid;
+
+ if (get_file_sd (handle, pc, sd, false))
+ return -1;
+ if (get_posix_access (sd, &attr, &uid, &gid, NULL, 0) < 0)
+ return -1;
+ if (!set_posix_access (attr, uid, gid, aclbufp, nentries,
+ sd_ret, pc.fs_is_samba ()))
+ return -1;
+ /* FIXME? Caller needs to know if any write perms are set to allow removing
+ the DOS R/O bit. */
+ writable = true;
return set_file_sd (handle, pc, sd_ret, false);
}
-/* Temporary access denied bits */
+/* Temporary access denied bits used by getace and get_posix_access during
+ Windows ACL processing. These bits get removed before the created POSIX
+ ACL gets published. */
#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 +504,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 +513,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 +523,110 @@ 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;
+ SECURITY_DESCRIPTOR_CONTROL ctrl;
+ ULONG rev;
+ PACL acl;
+ PACCESS_ALLOWED_ACE ace;
+ cygpsid owner_sid, group_sid;
+ cyg_ldap cldap;
uid_t uid;
gid_t gid;
- cyg_ldap cldap;
-
- status = RtlGetOwnerSecurityDescriptor (sd, (PSID *) &owner_sid, &dummy);
+ mode_t attr = 0;
+ aclent_t *lacl = NULL;
+ cygpsid ace_sid;
+ int pos, type, id, idx;
+
+ bool owner_eq_group;
+ bool just_created = false;
+ bool standard_ACEs_only = true;
+ bool new_style = false;
+ bool saw_user_obj = false;
+ bool saw_group_obj = false;
+ bool saw_other_obj = false;
+ bool saw_def_user_obj = false;
+ bool saw_def_group_obj = false;
+ bool has_class_perm = false;
+ bool has_def_class_perm = false;
+
+ mode_t class_perm = 0;
+ mode_t def_class_perm = 0;
+ int types_def = 0;
+ int def_pgrp_pos = -1;
+
+ 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;
}
- uid = owner_sid.get_uid (&cldap);
-
- status = RtlGetGroupSecurityDescriptor (sd, (PSID *) &group_sid, &dummy);
+ 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);
gid = group_sid.get_gid (&cldap);
+ if (attr_ret)
+ {
+ attr = *attr_ret & S_IFMT;
+ just_created = *attr_ret & S_JUSTCREATED;
+ }
+ /* Remember the fact that owner and group are the same account. */
+ owner_eq_group = owner_sid == group_sid;
- 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,186 +634,404 @@ 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
+ /* Files and dirs are created with a NULL descriptor, so inheritence
+ rules kick in. If no inheritable entries exist in the parent object,
+ Windows will create entries according to the user token's default DACL.
+ These entries are not desired and we ignore them at creation time.
+ We're just checking the SE_DACL_AUTO_INHERITED flag here, since that's
+ what we set in get_file_sd. Read the longish comment there before
+ changing this test! */
+ if (just_created
+ && NT_SUCCESS (RtlGetControlSecurityDescriptor (psd, &ctrl, &rev))
+ && !(ctrl & SE_DACL_AUTO_INHERITED))
+ ;
+ else for (idx = 0; idx < acl->AceCount; ++idx)
{
- for (i = 0; i < acl->AceCount; ++i)
- {
- ACCESS_ALLOWED_ACE *ace;
-
- if (!NT_SUCCESS (RtlGetAce (acl, i, (PVOID *) &ace)))
- continue;
+ if (!NT_SUCCESS (RtlGetAce (acl, idx, (PVOID *) &ace)))
+ continue;
- cygpsid ace_sid ((PSID) &ace->SidStart);
- int id;
- int type = 0;
+ ace_sid = (PSID) &ace->SidStart;
- if (ace_sid == well_known_null_sid)
- {
- /* Simply ignore. */
- continue;
- }
- 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)
+ if (ace_sid == well_known_null_sid)
+ {
+ /* Fetch special bits. */
+ attr |= CYG_ACE_ISBITS_TO_POSIX (ace->Mask);
+ if (ace->Header.AceType == ACCESS_DENIED_ACE_TYPE
+ && ace->Mask & CYG_ACE_NEW_STYLE)
{
- type = GROUP_OBJ;
- id = gid;
+ /* 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. */
+ new_style = true;
+ attr |= CYG_ACE_ISBITS_TO_POSIX (ace->Mask);
+ if (ace->Mask & CYG_ACE_MASK_VALID)
+ {
+ if (!(ace->Header.AceFlags & INHERIT_ONLY))
+ {
+ if ((pos = searchace (lacl, MAX_ACL_ENTRIES, CLASS_OBJ))
+ >= 0)
+ {
+ lacl[pos].a_type = CLASS_OBJ;
+ lacl[pos].a_id = ILLEGAL_GID;
+ lacl[pos].a_perm = CYG_ACE_MASK_TO_POSIX (ace->Mask);
+ }
+ has_class_perm = true;
+ class_perm = lacl[pos].a_perm;
+ }
+ if (ace->Header.AceFlags & SUB_CONTAINERS_AND_OBJECTS_INHERIT)
+ {
+ if ((pos = searchace (lacl, MAX_ACL_ENTRIES,
+ DEF_CLASS_OBJ)) >= 0)
+ {
+ lacl[pos].a_type = DEF_CLASS_OBJ;
+ lacl[pos].a_id = ILLEGAL_GID;
+ lacl[pos].a_perm = CYG_ACE_MASK_TO_POSIX (ace->Mask);
+ }
+ has_def_class_perm = true;
+ def_class_perm = lacl[pos].a_perm;
+ }
+ }
}
- else if (ace_sid == well_known_creator_group_sid)
+ continue;
+ }
+ 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;
+ if (ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE
+ && !(ace->Header.AceFlags & INHERIT_ONLY))
+ saw_other_obj = true;
+ }
+ else if (ace_sid == well_known_creator_owner_sid)
+ {
+ type = DEF_USER_OBJ;
+ types_def |= type;
+ id = ILLEGAL_GID;
+ saw_def_user_obj = true;
+ }
+ else if (ace_sid == well_known_creator_group_sid)
+ {
+ type = DEF_GROUP_OBJ;
+ types_def |= type;
+ id = ILLEGAL_GID;
+ saw_def_group_obj = true;
+ }
+ else
+ {
+ id = ace_sid.get_id (TRUE, &type, &cldap);
+ if (!type)
+ continue;
+ }
+ /* If the SGID attribute is set on a just created file or dir, the
+ first group in the ACL is the desired primary group of the new
+ object. Alternatively, the first repetition of the owner SID is
+ the desired primary group, and we mark the object as owner_eq_group
+ object. */
+ if (just_created && attr & S_ISGID && !saw_group_obj
+ && (type == GROUP || (type == USER_OBJ && saw_user_obj)))
+ {
+ type = GROUP_OBJ;
+ lacl[1].a_id = gid = id;
+ owner_eq_group = true;
+ }
+ if (!(ace->Header.AceFlags & INHERIT_ONLY || type & ACL_DEFAULT))
+ {
+ if (type == USER_OBJ)
{
- type = DEF_GROUP_OBJ;
- types_def |= type;
- id = ILLEGAL_GID;
+ /* If we get a second entry for the owner SID, it's either a
+ GROUP_OBJ entry for the same SID, if owner SID == group SID,
+ or it's an additional USER entry. The latter can happen
+ when chown'ing a file. */
+ if (saw_user_obj)
+ {
+ if (owner_eq_group && !saw_group_obj)
+ {
+ type = GROUP_OBJ;
+ if (ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE)
+ saw_group_obj = true;
+ }
+ else
+ type = USER;
+ }
+ else if (ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE)
+ saw_user_obj = true;
}
- else if (ace_sid == well_known_creator_owner_sid)
+ else if (type == GROUP_OBJ)
{
- type = DEF_USER_OBJ;
- types_def |= type;
- id = ILLEGAL_GID;
+ /* Same for the primary group. */
+ if (saw_group_obj)
+ type = GROUP;
+ if (ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE)
+ saw_group_obj = true;
}
- else
- id = ace_sid.get_id (TRUE, &type, &cldap);
-
- if (!type)
- continue;
- if (!(ace->Header.AceFlags & INHERIT_ONLY_ACE || 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_OBJ | 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;
+ /* Accommodate Windows: Never add SYSTEM and Admins to
+ CLASS_OBJ. Unless (implicitly) if they are the
+ GROUP_OBJ entry. */
+ if (ace_sid != well_known_system_sid
+ && ace_sid != well_known_admins_sid)
+ class_perm |= lacl[pos].a_perm;
}
}
+ /* For a newly created file, we'd like to know if we're running
+ with a standard ACL, one only consisting of POSIX perms, plus
+ SYSTEM and Admins as maximum non-POSIX perms entries. If it's
+ a standard ACL, we apply umask. That's not entirely correct,
+ but it's probably the best we can do. */
+ else if (type & (USER | GROUP)
+ && just_created
+ && standard_ACEs_only
+ && ace_sid != well_known_system_sid
+ && ace_sid != well_known_admins_sid)
+ standard_ACEs_only = false;
}
- 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)
+ {
+ /* As above: If we get a second entry for the owner SID, it's
+ a GROUP_OBJ entry for the same SID if owner SID == group SID,
+ but this time only if the S_ISGID bit is set. Otherwise it's
+ an additional USER entry. */
+ if (saw_def_user_obj)
+ {
+ if (owner_eq_group && !saw_def_group_obj && attr & S_ISGID)
+ type = GROUP_OBJ; /* This needs post-processing in the
+ following GROUP_OBJ handling... */
+ else
+ type = USER;
+ }
+ else if (ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE)
+ saw_def_user_obj = true;
+ }
+ if (type == GROUP_OBJ)
{
- if (type == USER_OBJ)
- type = USER;
- else if (type == GROUP_OBJ)
+ /* If the SGID bit is set, the inheritable entry for the
+ primary group is, in fact, the DEF_GROUP_OBJ entry,
+ so don't change the type to GROUP in this case. */
+ if (!new_style || saw_def_group_obj || !(attr & S_ISGID))
type = GROUP;
- type |= ACL_DEFAULT;
- types_def |= type;
- if ((pos = searchace (lacl, MAX_ACL_ENTRIES, type, id)) >= 0)
+ else if (ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE)
+ saw_def_group_obj = true;
+ }
+ 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_OBJ | 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;
+ /* Accommodate Windows: Never add SYSTEM and Admins to
+ CLASS_OBJ. Unless (implicitly) if they are the
+ GROUP_OBJ entry. */
+ if (ace_sid != well_known_system_sid
+ && ace_sid != well_known_admins_sid)
def_class_perm |= lacl[pos].a_perm;
}
/* And note the position of the DEF_GROUP_OBJ entry. */
- else if (type == DEF_GROUP_OBJ)
+ if (type == DEF_GROUP_OBJ)
def_pgrp_pos = pos;
}
}
}
- /* 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;
+ }
+ /* For ptys, fake a mask if the admins group is neither owner nor group.
+ In that case we have an extra ACE for the admins group, and we need a
+ CLASS_OBJ to get a valid POSIX ACL. However, Windows filters the ACE
+ Mask value so it only reflects the bit values supported by the object
+ type. The result is that we can't set an explicit CLASS_OBJ value for
+ ptys in the NULL SID ACE. */
+ else if (S_ISCHR (attr) && owner_sid != well_known_admins_sid
+ && group_sid != well_known_admins_sid
+ && (pos = searchace (lacl, MAX_ACL_ENTRIES, CLASS_OBJ)) >= 0)
+ {
+ lacl[pos].a_type = CLASS_OBJ;
+ lacl[pos].a_id = ILLEGAL_GID;
+ lacl[pos].a_perm = lacl[1].a_perm; /* == group perms */
+ }
+ /* If this is a just created file, and this is an ACL with only standard
+ entries, or if standard POSIX permissions are missing (probably no
+ inherited ACEs so created from a default DACL), assign the permissions
+ specified by the file creation mask. The values get masked by the
+ actually requested permissions by the caller per POSIX 1003.1e draft 17. */
+ if (just_created)
+ {
+ mode_t perms = (S_IRWXU | S_IRWXG | S_IRWXO) & ~cygheap->umask;
+ if (standard_ACEs_only || !saw_user_obj)
+ lacl[0].a_perm = (perms >> 6) & S_IRWXO;
+ if (standard_ACEs_only || !saw_group_obj)
+ lacl[1].a_perm = (perms >> 3) & S_IRWXO;
+ if (standard_ACEs_only || !saw_other_obj)
+ lacl[2].a_perm = perms & S_IRWXO;
+ }
+ /* 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 (idx = 0; idx < pos; ++idx)
+ {
+ /* Current user? If the user entry has a deny ACE, don't check. */
+ if (lacl[idx].a_id == myself->uid
+ && lacl[idx].a_type & (USER_OBJ | USER)
+ && !(lacl[idx].a_type & ACL_DEFAULT)
+ && !(lacl[idx].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[idx].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[idx].a_id & (GROUP_OBJ | GROUP)
+ && !(lacl[idx].a_type & ACL_DEFAULT)
+ && !(lacl[idx].a_perm & DENY_RWX))
+ lacl[idx].a_perm |= lacl[2].a_perm & S_IRWXO;
+ }
+ /* If owner SID == group SID (Microsoft Accounts) merge group perms into
+ user perms but leave group perms intact. That's a fake, but it allows
+ to keep track of the POSIX group perms without much effort. */
+ if (owner_eq_group)
+ lacl[0].a_perm |= lacl[1].a_perm;
+ /* 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 (idx = 0; idx < pos; ++idx)
+ aclbufp[idx].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;
}