diff --git a/winsup/cygwin/ChangeLog b/winsup/cygwin/ChangeLog index 9877cafe6..3a280edb5 100644 --- a/winsup/cygwin/ChangeLog +++ b/winsup/cygwin/ChangeLog @@ -1,3 +1,155 @@ +2015-11-18 Corinna Vinschen + + Reapply POSIX ACL changes. + + * sec_acl.cc (get_posix_access): Check for Cygwin "standard" ACL. + Apply umask, if so. Align comments. + * security.cc (set_created_file_access): Fix permission masking by + incoming requested file mode. + + * sec_acl.cc (set_posix_access): Apply mask only in terms of execute bit + for SYSTEM and Admins group. + + * sec_acl.cc (set_posix_access): Don't create DENY ACEs for USER and + GROUP entries if they are the same as USER_OBJ or GROUP_OBJ. + + * fhandler.h (fhandler_pty_slave::facl): Add prototype. + * fhandler_tty.cc (fhandler_pty_slave::facl): New method. + (fhandler_pty_slave::fchown): Fix uid/gid handling. + * sec_acl.cc (set_posix_access): Drop superfluous class_idx variable. + Simplify and move around code in a few places. To improve ACL + readability, add r/w permissions to Admins ACE appended to pty ACL. + Add comment to explain Windows ACE Mask filtering being in the way of + creating a real CLASS_OBJ. + (get_posix_access): Fake CLASS_OBJ for ptys. Explain why. + * security.cc (get_object_attribute): Add S_IFCHR flag to attributes + when calling get_posix_access. + + * sec_acl.cc (set_posix_access): Move merging group perms into owner + perms in case of owner == group after mask has been computed. Take + mask into account when doing so to avoid unnecessary ACCESS_DENIED_ACE. + + * sec_acl.cc (get_posix_access): Only set saw_group_obj flag if we saw + the ACCESS_ALLOWED_ACE. + + * fhandler_disk_file.cc (fhandler_disk_file::fchmod): Deliberatly + set GROUP_OBJ and CLASS_OBJ perms to new group perms. Add comment + to explain why. + * security.cc (set_created_file_access): Ditto. + + * sec_acl.cc (set_posix_access): Replace previous patch. Return + EINVAL if uid and/or guid is invalid and not backed by an actual + Windows account. + + * sec_acl.cc (set_posix_access): Workaround owner/group SIDs being NULL. + + * sec_acl.cc (set_posix_access): Handle files with owner == group. + Rephrase switch statement checking against unfiltered a_type value. + (get_posix_access): Handle files with owner == group. + + * sec_acl.cc (get_posix_access): Don't use GROUP_OBJ access to fix up + CLASS_OBJ mask on old-style ACLs. Fix a comment. + + * sec_acl.cc (set_posix_access): Always make sure Admins have + WRITE_DAC and WRITE_OWNER permissions. + * security.h (create_object_sd_from_attribute): Drop handle parameter + from prototype. + * security.cc (create_object_sd_from_attribute): Drop handle parameter. + Just create the standard POSIXy security descriptor. + (set_object_attribute): Accommodate dropped paramter in call to + create_object_sd_from_attribute. + * fhandler_tty.cc: Ditto, throughout. + + * fhandler_disk_file.cc (fhandler_disk_file::fchmod): Fix typo in + mask computation. + + * fhandler.cc (fhandler_base::open_with_arch): Call open with mode + not umasked. + (fhandler_base::open): Explicitely umask mode on NFS here. Call new + set_created_file_access rather than set_file_attribute. + * fhandler_disk_file.cc (fhandler_disk_file::fchmod): Reimplement + setting permissions on filesystems supporting ACLs using the new + set_posix_access call. + (fhandler_disk_file::fchown): Ditto. + (fhandler_disk_file::mkdir): Call new set_created_file_access rather + than set_file_attribute. + * fhandler_socket.cc (fhandler_socket::bind): Don't umask here. Add + WRITE_OWNER access to allow writing group in case of SGID bit set. + Call new set_created_file_access rather than set_file_attribute. + * path.cc (symlink_worker): Call new set_created_file_access rather + than set_file_attribute. + * sec_acl.cc (searchace): Un-staticize. + (set_posix_access): New, complementary functionality to + get_posix_access. + (setacl): Implement in terms of get_posix_access/set_posix_access. + (get_posix_access): Add handling for just created files requiring + their first Cygwin ACL. Fix new_style recognition. Handle SGID + bit. For old-style ACLs, ignore SYSTEM and Administrators when + computing the {DEF_}CLASS_OBJ perms. + * security.cc (get_file_sd): Revamp comment. Change and (hopefully) + speed up inheritance processing for just created files. + (alloc_sd): Remove. + (set_security_attribute): Call set_posix_access instead of alloc_sd. + (get_object_attribute): Fix return value. + (create_object_sd_from_attribute): Call set_posix_access instead of + alloc_sd. + (set_file_attribute): Remove. + (set_created_file_access): New function implemented in terms of + get_posix_access/set_posix_access. + * security.h (set_file_attribute): Remove prototype. + (set_created_file_access): Add prototype. + (searchace): Ditto. + (set_posix_access): Ditto. + * syscalls.cc (open): Call open_with_arch with mode not umasked. + + * sec_acl.cc: Change preceeding comment explaining new-style ACLs. + Describe how to generate deny ACEs in more detail. Accommodate the + fact that a NULL deny ACE is used for {DEF_}CLASS_OBJ, rather than + a special Cygwin ACE. Improve further comments. + (CYG_ACE_NEW_STYLE): Define. + (get_posix_access): Change from Cygwin ACE to NULL deny ACE. Fix + CLASS_OBJ handling to generate CLASS_OBJ and DEF_CLASS_OBJ from a single + NULL deny ACE if the inheritance flags say so. + * sec_helper.cc (well_known_cygwin_sid): Remove. + * security.h (well_known_cygwin_sid): Drop declaration. + + * sec_acl.cc (CYG_ACE_ISBITS_TO_WIN): Fix typo. + (get_posix_access): Rename index variable from i to idx. Define only + once at top level. + + * security.cc (add_access_allowed_ace): Drop unused parameter "offset". + Accommodate throughout. + (add_access_denied_ace): Ditto. + * sec_acl.cc: Accommodate above change throughout. + * security.h (add_access_allowed_ace): Adjust prototype to above change. + (add_access_denied_ace): Ditto. + + * sec_acl.cc (get_posix_access): Handle multiple ACEs for the + owner and primary group of the file. Handle the default primary + group ACE as DEF_GROUP_OBJ entry if the directory has the S_ISGID bit + set. Add comments. Minor code rearrangements. + + 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-11-18 Corinna Vinschen * include/cygwin/version.h (CYGWIN_VERSION_DLL_MAJOR): Bump to 2004. diff --git a/winsup/cygwin/fhandler.cc b/winsup/cygwin/fhandler.cc index 4343cdf09..7e4d996ce 100644 --- a/winsup/cygwin/fhandler.cc +++ b/winsup/cygwin/fhandler.cc @@ -463,7 +463,7 @@ fhandler_base::open_with_arch (int flags, mode_t mode) { int res; if (!(res = (archetype && archetype->io_handle) - || open (flags, (mode & 07777) & ~cygheap->umask))) + || open (flags, mode & 07777))) { if (archetype) delete archetype; @@ -662,9 +662,10 @@ fhandler_base::open (int flags, mode_t mode) + p->EaNameLength + 1); memset (nfs_attr, 0, sizeof (fattr3)); nfs_attr->type = NF3REG; - nfs_attr->mode = mode; + nfs_attr->mode = (mode & 07777) & ~cygheap->umask; } - else if (!has_acls () && !(mode & (S_IWUSR | S_IWGRP | S_IWOTH))) + else if (!has_acls () + && !(mode & ~cygheap->umask & (S_IWUSR | S_IWGRP | S_IWOTH))) /* If mode has no write bits set, and ACLs are not used, we set the DOS R/O attribute. */ file_attributes |= FILE_ATTRIBUTE_READONLY; @@ -716,7 +717,7 @@ fhandler_base::open (int flags, mode_t mode) This is the result of a discussion on the samba-technical list, starting at http://lists.samba.org/archive/samba-technical/2008-July/060247.html */ if (io.Information == FILE_CREATED && has_acls ()) - set_file_attribute (fh, pc, ILLEGAL_UID, ILLEGAL_GID, S_JUSTCREATED | mode); + set_created_file_access (fh, pc, mode); /* If you O_TRUNC a file on Linux, the data is truncated, but the EAs are preserved. If you open a file on Windows with FILE_OVERWRITE{_IF} or diff --git a/winsup/cygwin/fhandler.h b/winsup/cygwin/fhandler.h index 6e964aaec..43e56efa6 100644 --- a/winsup/cygwin/fhandler.h +++ b/winsup/cygwin/fhandler.h @@ -1557,6 +1557,7 @@ class fhandler_pty_slave: public fhandler_pty_common select_record *select_read (select_stuff *); virtual char const *ttyname () { return pc.dev.name; } int __reg2 fstat (struct stat *buf); + int __reg3 facl (int, int, struct acl *); int __reg1 fchmod (mode_t mode); int __reg2 fchown (uid_t uid, gid_t gid); diff --git a/winsup/cygwin/fhandler_disk_file.cc b/winsup/cygwin/fhandler_disk_file.cc index c38ba0243..9b54d29be 100644 --- a/winsup/cygwin/fhandler_disk_file.cc +++ b/winsup/cygwin/fhandler_disk_file.cc @@ -846,7 +846,7 @@ int __reg1 fhandler_disk_file::fchmod (mode_t mode) { extern int chmod_device (path_conv& pc, mode_t mode); - int res = -1; + int ret = -1; int oret = 0; NTSTATUS status; IO_STATUS_BLOCK io; @@ -893,17 +893,45 @@ fhandler_disk_file::fchmod (mode_t mode) if (!NT_SUCCESS (status)) __seterrno_from_nt_status (status); else - res = 0; + ret = 0; goto out; } if (pc.has_acls ()) { - if (pc.isdir ()) - mode |= S_IFDIR; - if (!set_file_attribute (get_handle (), pc, - ILLEGAL_UID, ILLEGAL_GID, mode)) - res = 0; + security_descriptor sd, sd_ret; + uid_t uid; + gid_t gid; + tmp_pathbuf tp; + aclent_t *aclp; + int nentries, idx; + + if (!get_file_sd (get_handle (), pc, sd, false)) + { + aclp = (aclent_t *) tp.c_get (); + if ((nentries = get_posix_access (sd, NULL, &uid, &gid, + aclp, MAX_ACL_ENTRIES)) >= 0) + { + /* Overwrite ACL permissions as required by POSIX 1003.1e + draft 17. */ + aclp[0].a_perm = (mode >> 6) & S_IRWXO; + /* Deliberate deviation from POSIX 1003.1e here. We're not + writing CLASS_OBJ *or* GROUP_OBJ, but both. Otherwise we're + going to be in constant trouble with user expectations. */ + if ((idx = searchace (aclp, nentries, GROUP_OBJ)) >= 0) + aclp[idx].a_perm = (mode >> 3) & S_IRWXO; + if (nentries > MIN_ACL_ENTRIES + && (idx = searchace (aclp, nentries, CLASS_OBJ)) >= 0) + aclp[idx].a_perm = (mode >> 3) & S_IRWXO; + if ((idx = searchace (aclp, nentries, OTHER_OBJ)) >= 0) + aclp[idx].a_perm = mode & S_IRWXO; + if (pc.isdir ()) + mode |= S_IFDIR; + if (set_posix_access (mode, uid, gid, aclp, nentries, sd_ret, + pc.fs_is_samba ())) + ret = set_file_sd (get_handle (), pc, sd_ret, false); + } + } } /* If the mode has any write bits set, the DOS R/O flag is in the way. */ @@ -940,20 +968,28 @@ fhandler_disk_file::fchmod (mode_t mode) if (!NT_SUCCESS (status)) __seterrno_from_nt_status (status); else - res = 0; + ret = 0; } out: if (oret) close_fs (); - return res; + return ret; } int __reg2 fhandler_disk_file::fchown (uid_t uid, gid_t gid) { int oret = 0; + int ret = -1; + security_descriptor sd, sd_ret; + mode_t attr = pc.isdir () ? S_IFDIR : 0; + uid_t old_uid; + gid_t old_gid; + tmp_pathbuf tp; + aclent_t *aclp; + int nentries; if (!pc.has_acls ()) { @@ -970,52 +1006,71 @@ fhandler_disk_file::fchown (uid_t uid, gid_t gid) return -1; } - mode_t attrib = 0; - if (pc.isdir ()) - attrib |= S_IFDIR; - uid_t old_uid; - int res = get_file_attribute (get_handle (), pc, &attrib, &old_uid, NULL); - if (!res) - { - /* Typical Windows default ACLs can contain permissions for one - group, while being owned by another user/group. The permission - bits returned above are pretty much useless then. Creating a - new ACL with these useless permissions results in a potentially - broken symlink. So what we do here is to set the underlying - permissions of symlinks to a sensible value which allows the - world to read the symlink and only the new owner to change it. */ - if (pc.issymlink ()) - attrib = S_IFLNK | STD_RBITS | STD_WBITS; - res = set_file_attribute (get_handle (), pc, uid, gid, attrib); - /* If you're running a Samba server which has no winbind running, the - uid<->SID mapping is disfunctional. Even trying to chown to your - own account fails since the account used on the server is the UNIX - account which gets used for the standard user mapping. This is a - default mechanism which doesn't know your real Windows SID. - There are two possible error codes in different Samba releases for - this situation, one of them is unfortunately the not very significant - STATUS_ACCESS_DENIED. Instead of relying on the error codes, we're - using the below very simple heuristic. If set_file_attribute failed, - and the original user account was either already unknown, or one of - the standard UNIX accounts, we're faking success. */ - if (res == -1 && pc.fs_is_samba ()) - { - PSID sid; + if (get_file_sd (get_handle (), pc, sd, false)) + goto out; - if (old_uid == ILLEGAL_UID - || ((sid = sidfromuid (old_uid, NULL)) != NO_SID - && RtlEqualPrefixSid (sid, - well_known_samba_unix_user_fake_sid))) - { - debug_printf ("Faking chown worked on standalone Samba"); - res = 0; - } + aclp = (aclent_t *) tp.c_get (); + if ((nentries = get_posix_access (sd, &attr, &old_uid, &old_gid, + aclp, MAX_ACL_ENTRIES)) < 0) + goto out; + + if (uid == ILLEGAL_UID) + uid = old_uid; + if (gid == ILLEGAL_GID) + gid = old_gid; + if (uid == old_uid && gid == old_gid) + { + ret = 0; + goto out; + } + + /* Windows ACLs can contain permissions for one group, while being owned by + another user/group. The permission bits returned above are pretty much + useless then. Creating a new ACL with these useless permissions results + in a potentially broken symlink. So what we do here is to set the + underlying permissions of symlinks to a sensible value which allows the + world to read the symlink and only the new owner to change it. */ + if (pc.issymlink ()) + for (int idx = 0; idx < nentries; ++idx) + { + aclp[idx].a_perm |= S_IROTH; + if (aclp[idx].a_type & USER_OBJ) + aclp[idx].a_perm |= S_IWOTH; + } + + if (set_posix_access (attr, uid, gid, aclp, nentries, sd_ret, + pc.fs_is_samba ())) + ret = set_file_sd (get_handle (), pc, sd_ret, true); + + /* If you're running a Samba server with no winbind, the uid<->SID mapping + is disfunctional. Even trying to chown to your own account fails since + the account used on the server is the UNIX account which gets used for + the standard user mapping. This is a default mechanism which doesn't + know your real Windows SID. There are two possible error codes in + different Samba releases for this situation, one of them unfortunately + the not very significant STATUS_ACCESS_DENIED. Instead of relying on + the error codes, we're using the below very simple heuristic. + If set_file_sd failed, and the original user account was either already + unknown, or one of the standard UNIX accounts, we're faking success. */ + if (ret == -1 && pc.fs_is_samba ()) + { + PSID sid; + + if (uid == old_uid + || ((sid = sidfromuid (old_uid, NULL)) != NO_SID + && RtlEqualPrefixSid (sid, + well_known_samba_unix_user_fake_sid))) + { + debug_printf ("Faking chown worked on standalone Samba"); + ret = 0; } } + +out: if (oret) close_fs (); - return res; + return ret; } int __reg3 @@ -1774,10 +1829,11 @@ fhandler_disk_file::mkdir (mode_t mode) p, plen); if (NT_SUCCESS (status)) { + /* Set the "directory attribute" so that pc.isdir() returns correct + value in subsequent function calls. */ + pc.file_attributes (FILE_ATTRIBUTE_DIRECTORY); if (has_acls ()) - set_file_attribute (dir, pc, ILLEGAL_UID, ILLEGAL_GID, - S_JUSTCREATED | S_IFDIR - | ((mode & 07777) & ~cygheap->umask)); + set_created_file_access (dir, pc, mode & 07777); NtClose (dir); res = 0; } diff --git a/winsup/cygwin/fhandler_socket.cc b/winsup/cygwin/fhandler_socket.cc index 658dca74a..bc3c610e0 100644 --- a/winsup/cygwin/fhandler_socket.cc +++ b/winsup/cygwin/fhandler_socket.cc @@ -1065,10 +1065,10 @@ fhandler_socket::bind (const struct sockaddr *name, int namelen) sin.sin_port = ntohs (sin.sin_port); debug_printf ("AF_LOCAL: socket bound to port %u", sin.sin_port); - mode_t mode = adjust_socket_file_mode ((S_IRWXU | S_IRWXG | S_IRWXO) - & ~cygheap->umask); + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; DWORD fattr = FILE_ATTRIBUTE_SYSTEM; - if (!(mode & (S_IWUSR | S_IWGRP | S_IWOTH)) && !pc.has_acls ()) + if (!pc.has_acls () + && !(mode & ~cygheap->umask & (S_IWUSR | S_IWGRP | S_IWOTH))) fattr |= FILE_ATTRIBUTE_READONLY; SECURITY_ATTRIBUTES sa = sec_none_nih; NTSTATUS status; @@ -1086,7 +1086,7 @@ fhandler_socket::bind (const struct sockaddr *name, int namelen) I don't know what setting that is or how to recognize such a share, so for now we don't request WRITE_DAC on remote drives. */ if (pc.has_acls () && !pc.isremote ()) - access |= READ_CONTROL | WRITE_DAC; + access |= READ_CONTROL | WRITE_DAC | WRITE_OWNER; status = NtCreateFile (&fh, access, pc.get_object_attr (attr, sa), &io, NULL, fattr, 0, FILE_CREATE, @@ -1104,8 +1104,7 @@ fhandler_socket::bind (const struct sockaddr *name, int namelen) else { if (pc.has_acls ()) - set_file_attribute (fh, pc, ILLEGAL_UID, ILLEGAL_GID, - S_JUSTCREATED | mode); + set_created_file_access (fh, pc, mode); char buf[sizeof (SOCKET_COOKIE) + 80]; __small_sprintf (buf, "%s%u %c ", SOCKET_COOKIE, sin.sin_port, get_socket_type () == SOCK_STREAM ? 's' diff --git a/winsup/cygwin/fhandler_tty.cc b/winsup/cygwin/fhandler_tty.cc index f22998f9f..93fb22b77 100644 --- a/winsup/cygwin/fhandler_tty.cc +++ b/winsup/cygwin/fhandler_tty.cc @@ -12,6 +12,7 @@ details. */ #include "winsup.h" #include #include +#include #include #include "cygerrno.h" #include "security.h" @@ -388,9 +389,8 @@ fhandler_pty_slave::open (int flags, mode_t) sd.malloc (sizeof (SECURITY_DESCRIPTOR)); RtlCreateSecurityDescriptor (sd, SECURITY_DESCRIPTOR_REVISION); SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE }; - if (!create_object_sd_from_attribute (NULL, myself->uid, myself->gid, - S_IFCHR | S_IRUSR | S_IWUSR | S_IWGRP, - sd)) + if (!create_object_sd_from_attribute (myself->uid, myself->gid, + S_IRUSR | S_IWUSR | S_IWGRP, sd)) sa.lpSecurityDescriptor = (PSECURITY_DESCRIPTOR) sd; acquire_output_mutex (INFINITE); inuse = get_ttyp ()->create_inuse (&sa); @@ -1049,6 +1049,62 @@ fhandler_pty_slave::fstat (struct stat *st) return 0; } +int __reg3 +fhandler_pty_slave::facl (int cmd, int nentries, aclent_t *aclbufp) +{ + int res = -1; + bool to_close = false; + security_descriptor sd; + mode_t attr = S_IFCHR; + + switch (cmd) + { + case SETACL: + if (!aclsort32 (nentries, 0, aclbufp)) + set_errno (ENOTSUP); + break; + case GETACL: + if (!aclbufp) + { + set_errno (EFAULT); + break; + } + /*FALLTHRU*/ + case GETACLCNT: + if (!input_available_event) + { + char buf[MAX_PATH]; + shared_name (buf, INPUT_AVAILABLE_EVENT, get_minor ()); + input_available_event = OpenEvent (READ_CONTROL, TRUE, buf); + if (input_available_event) + to_close = true; + } + if (!input_available_event + || get_object_sd (input_available_event, sd)) + { + res = get_posix_access (NULL, &attr, NULL, NULL, aclbufp, nentries); + if (aclbufp && res == MIN_ACL_ENTRIES) + { + aclbufp[0].a_perm = S_IROTH | S_IWOTH; + aclbufp[0].a_id = 18; + aclbufp[1].a_id = 544; + } + break; + } + if (cmd == GETACL) + res = get_posix_access (sd, &attr, NULL, NULL, aclbufp, nentries); + else + res = get_posix_access (sd, &attr, NULL, NULL, NULL, 0); + break; + default: + set_errno (EINVAL); + break; + } + if (to_close) + CloseHandle (input_available_event); + return res; +} + /* Helper function for fchmod and fchown, which just opens all handles and signals success via bool return. */ bool @@ -1121,7 +1177,7 @@ fhandler_pty_slave::fchmod (mode_t mode) sd.malloc (sizeof (SECURITY_DESCRIPTOR)); RtlCreateSecurityDescriptor (sd, SECURITY_DESCRIPTOR_REVISION); if (!get_object_attribute (input_available_event, &uid, &gid, NULL) - && !create_object_sd_from_attribute (NULL, uid, gid, S_IFCHR | mode, sd)) + && !create_object_sd_from_attribute (uid, gid, mode, sd)) ret = fch_set_sd (sd, false); errout: if (to_close) @@ -1151,11 +1207,13 @@ fhandler_pty_slave::fchown (uid_t uid, gid_t gid) RtlCreateSecurityDescriptor (sd, SECURITY_DESCRIPTOR_REVISION); if (!get_object_attribute (input_available_event, &o_uid, &o_gid, &mode)) { - if ((uid == ILLEGAL_UID || uid == o_uid) - && (gid == ILLEGAL_GID || gid == o_gid)) + if (uid == ILLEGAL_UID) + uid = o_uid; + if (gid == ILLEGAL_GID) + gid = o_gid; + if (uid == o_uid && gid == o_gid) ret = 0; - else if (!create_object_sd_from_attribute (input_available_event, - uid, gid, S_IFCHR | mode, sd)) + else if (!create_object_sd_from_attribute (uid, gid, mode, sd)) ret = fch_set_sd (sd, true); } errout: @@ -1695,9 +1753,8 @@ fhandler_pty_master::setup () /* Create security attribute. Default permissions are 0620. */ sd.malloc (sizeof (SECURITY_DESCRIPTOR)); RtlCreateSecurityDescriptor (sd, SECURITY_DESCRIPTOR_REVISION); - if (!create_object_sd_from_attribute (NULL, myself->uid, myself->gid, - S_IFCHR | S_IRUSR | S_IWUSR | S_IWGRP, - sd)) + if (!create_object_sd_from_attribute (myself->uid, myself->gid, + S_IRUSR | S_IWUSR | S_IWGRP, sd)) sa.lpSecurityDescriptor = (PSECURITY_DESCRIPTOR) sd; /* Carefully check that the input_available_event didn't already exist. diff --git a/winsup/cygwin/include/cygwin/acl.h b/winsup/cygwin/include/cygwin/acl.h index 34f1c0c44..b7cbf03f3 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/path.cc b/winsup/cygwin/path.cc index 88080e176..0c0a21187 100644 --- a/winsup/cygwin/path.cc +++ b/winsup/cygwin/path.cc @@ -2061,10 +2061,9 @@ symlink_worker (const char *oldpath, const char *newpath, bool isdevice) __seterrno_from_nt_status (status); __leave; } - if (win32_newpath.has_acls ()) - set_file_attribute (fh, win32_newpath, ILLEGAL_UID, ILLEGAL_GID, - (io.Information == FILE_CREATED ? S_JUSTCREATED : 0) - | S_IFLNK | STD_RBITS | STD_WBITS); + if (io.Information == FILE_CREATED && win32_newpath.has_acls ()) + set_created_file_access (fh, win32_newpath, + S_IFLNK | STD_RBITS | STD_WBITS); status = NtWriteFile (fh, NULL, NULL, NULL, &io, buf, cp - buf, NULL, NULL); if (NT_SUCCESS (status) && io.Information == (ULONG) (cp - buf)) 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; + + /* 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; + } } - /* Fill access control list. */ + /* 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 (); - size_t acl_len = sizeof (ACL); - int ace_off = 0; - - cygsid sid; - struct passwd *pw; - struct group *gr; - int pos; - cyg_ldap cldap; - 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) - { - 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; - 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) - { - 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) - && !(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) + DWORD inherit = def ? SUB_CONTAINERS_AND_OBJECTS_INHERIT | INHERIT_ONLY : 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) + + /* No default ACEs on files. */ + if (def && !S_ISDIR (attr)) { - inheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE; - /* invalidate the corresponding default entry. */ - invalid[i + 1 + pos] = true; + /* 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; } - switch (aclbufp[i].a_type) + + /* 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) { - 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)) - { - set_errno (EINVAL); - return -1; - } - 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)) - { - set_errno (EINVAL); - return -1; - } - 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; + class_obj = aclbufp[tmp_idx].a_perm; + access |= CYG_ACE_MASK_TO_WIN (class_obj); } + else + { + /* Setting class_obj to group_obj allows to write below code without + additional checks for existence of a CLASS_OBJ. */ + class_obj = group_obj; + } + /* 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) + { + /* Create deny ACEs for users, then groups. */ + for (start_idx = idx; + idx < nentries && aclbufp[idx].a_type & check_types; + ++idx) + { + /* 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; + } + /* Create allow ACEs for users, then groups. */ + for (idx = start_idx; + idx < nentries && aclbufp[idx].a_type & check_types; + ++idx) + { + /* 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; + } + } + /* 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,6 +523,507 @@ 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 +get_posix_access (PSECURITY_DESCRIPTOR psd, + mode_t *attr_ret, uid_t *uid_ret, gid_t *gid_ret, + aclent_t *aclbufp, int nentries) +{ + tmp_pathbuf tp; + NTSTATUS status; + 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; + 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; + } + 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; + + /* 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; + lacl[1].a_id = gid; + lacl[2].a_type = OTHER_OBJ; + lacl[2].a_id = ILLEGAL_GID; + + /* No ACEs? Everybody has full access. */ + if (!acl_exists || !acl || acl->AceCount == 0) + { + for (pos = 0; pos < MIN_ACL_ENTRIES; ++pos) + lacl[pos].a_perm = S_IROTH | S_IWOTH | S_IXOTH; + goto out; + } + + /* 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) + { + if (!NT_SUCCESS (RtlGetAce (acl, idx, (PVOID *) &ace))) + continue; + + ace_sid = (PSID) &ace->SidStart; + + 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) + { + /* 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; + } + } + } + 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) + { + /* 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 (type == GROUP_OBJ) + { + /* Same for the primary group. */ + if (saw_group_obj) + type = GROUP; + if (ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE) + saw_group_obj = true; + } + 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) + { + /* Fix up CLASS_OBJ value. */ + if (type & (USER | GROUP)) + { + has_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) + 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 & 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 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; + 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) + { + /* Fix up DEF_CLASS_OBJ value. */ + 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. */ + if (type == DEF_GROUP_OBJ) + def_pgrp_pos = pos; + } + } + } + } + /* 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 = 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++; + } + } + /* 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 (pos > nentries) + { + set_errno (ENOSPC); + return -1; + } + memcpy (aclbufp, lacl, pos * sizeof (aclent_t)); + 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) { @@ -377,220 +1031,7 @@ getacl (HANDLE handle, path_conv &pc, int nentries, aclent_t *aclbufp) if (get_file_sd (handle, pc, sd, false)) return -1; - - cygpsid owner_sid; - cygpsid group_sid; - NTSTATUS status; - BOOLEAN dummy; - uid_t uid; - gid_t gid; - cyg_ldap cldap; - - status = RtlGetOwnerSecurityDescriptor (sd, (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); - if (!NT_SUCCESS (status)) - { - __seterrno_from_nt_status (status); - return -1; - } - gid = group_sid.get_gid (&cldap); - - aclent_t lacl[MAX_ACL_ENTRIES]; - 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; - lacl[1].a_id = gid; - 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)) - { - __seterrno_from_nt_status (status); - return -1; - } - - 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 (i = 0; i < acl->AceCount; ++i) - { - 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) - { - /* 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) - { - 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); - - if (!type) - continue; - if (!(ace->Header.AceFlags & INHERIT_ONLY_ACE || type & ACL_DEFAULT)) - { - if ((pos = searchace (lacl, MAX_ACL_ENTRIES, type, id)) >= 0) - { - getace (lacl[pos], type, id, ace->Mask, ace->Header.AceType); - /* Fix up CLASS_OBJ value. */ - if (type == USER || type == GROUP) - { - has_class_perm = true; - class_perm |= lacl[pos].a_perm; - } - } - } - if ((ace->Header.AceFlags - & (CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE)) - && pc.isdir ()) - { - 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); - /* Fix up DEF_CLASS_OBJ value. */ - if (type == DEF_USER || type == DEF_GROUP) - { - has_def_class_perm = true; - def_class_perm |= lacl[pos].a_perm; - } - /* And note the position of the DEF_GROUP_OBJ entry. */ - else 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) - { - lacl[pos].a_type = CLASS_OBJ; - lacl[pos].a_id = ILLEGAL_GID; - lacl[pos].a_perm = class_perm | lacl[pgrp_pos].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 = 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++; - } - } - /* 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) - { - 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; - } - } - if ((pos = searchace (lacl, MAX_ACL_ENTRIES, 0)) < 0) - pos = MAX_ACL_ENTRIES; - 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); - aclsort32 (pos, 0, aclbufp); - } + 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 8067385d0..ab86aa7a4 100644 --- a/winsup/cygwin/sec_helper.cc +++ b/winsup/cygwin/sec_helper.cc @@ -619,22 +619,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 9a94c53d1..ac25d71c4 100644 --- a/winsup/cygwin/security.cc +++ b/winsup/cygwin/security.cc @@ -15,6 +15,7 @@ details. */ #include "winsup.h" #include #include +#include #include "cygerrno.h" #include "security.h" #include "path.h" @@ -34,7 +35,6 @@ static GENERIC_MAPPING NO_COPY_RO file_mapping = { FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE, FILE_ALL_ACCESS }; - LONG get_file_sd (HANDLE fh, path_conv &pc, security_descriptor &sd, bool justcreated) @@ -85,62 +85,46 @@ get_file_sd (HANDLE fh, path_conv &pc, security_descriptor &sd, return -1; } } - /* Ok, so we have a security descriptor now. Unfortunately, if you want - to know if an ACE is inherited from the parent object, you can't just - call NtQuerySecurityObject once. The problem is this: + /* We have a security descriptor now. Unfortunately, if you want to know + if an ACE is inherited from the parent object, this isn't sufficient. In the simple case, the SDs control word contains one of the SE_DACL_AUTO_INHERITED or SE_DACL_PROTECTED flags, or at least one of - the ACEs has the INHERITED_ACE flag set. In all of these cases the - GetSecurityInfo function calls NtQuerySecurityObject only once, too, - apparently because it figures that the DACL is self-sufficient, which - it usually is. Windows Explorer, for instance, takes great care to - set these flags in a security descriptor if you change the ACL in the - GUI property dialog. + the ACEs has the INHERITED_ACE flag set. In all of these cases we + know the DACL has been inherited. - The tricky case is if none of these flags is set in the SD. That means - the information whether or not an ACE has been inherited is not available - in the DACL of the object. In this case GetSecurityInfo also fetches the - SD from the parent directory and tests if the object's SD contains - inherited ACEs from the parent. The below code is closly emulating the - behaviour of GetSecurityInfo so we can get rid of this advapi32 dependency. + If none of these flags is set in the SD, the information whether + or not an ACE has been inherited is not available in the DACL of the + object. In this case GetSecurityInfo fetches the SD from the parent + directory and tests if the object's SD contains inherited ACEs from the + parent. - However, this functionality is slow, and the extra information is only - required when the file has been created and the permissions are about - to be set to POSIX permissions. Therefore we only use it in case the - file just got created. + Note that we're not testing the SE_DACL_AUTO_INHERITED and + SE_DACL_PROTECTED flags here because we know the state the file's SD + is in. Since we're creating all files with a NULL descriptor, the DACL + is either inherited from the parent, or it's the default DACL. In + neither case, one of these flags is set. - Note that GetSecurityInfo has a problem on 5.1 and 5.2 kernels. Sometimes - it returns ERROR_INVALID_ADDRESS if a former request for the parent - directories' SD used NtQuerySecurityObject, rather than GetSecurityInfo - as well. See http://cygwin.com/ml/cygwin-developers/2011-03/msg00027.html - for the solution. This problem does not occur with the below code, so - the workaround has been removed. */ + For speed, we're not calling RtlConvertToAutoInheritSecurityObject + anymore (but keep the code here for reference). Rather we just test + if one of the parent's ACEs is inheritable. If so, we know we inherited + it and set the SE_DACL_AUTO_INHERITED flag. If not, we may assume our + object's DACL is the default DACL. + + This functionality is slow and the extra information is only required + when the file has been created and the permissions are about to be set + to POSIX permissions. Therefore we only use it in case the file just + got created. */ if (justcreated) { - SECURITY_DESCRIPTOR_CONTROL ctrl; - ULONG dummy; PACL dacl; BOOLEAN exists, def; ACCESS_ALLOWED_ACE *ace; UNICODE_STRING dirname; - PSECURITY_DESCRIPTOR psd, nsd; + PSECURITY_DESCRIPTOR psd; tmp_pathbuf tp; - /* Check SDs control flags. If SE_DACL_AUTO_INHERITED or - SE_DACL_PROTECTED is set we're done. */ - RtlGetControlSecurityDescriptor (sd, &ctrl, &dummy); - if (ctrl & (SE_DACL_AUTO_INHERITED | SE_DACL_PROTECTED)) - return 0; - /* Otherwise iterate over the ACEs and see if any one of them has the - INHERITED_ACE flag set. If so, we're done. */ - if (NT_SUCCESS (RtlGetDaclSecurityDescriptor (sd, &exists, &dacl, &def)) - && exists && dacl) - for (ULONG idx = 0; idx < dacl->AceCount; ++idx) - if (NT_SUCCESS (RtlGetAce (dacl, idx, (PVOID *) &ace)) - && (ace->Header.AceFlags & INHERITED_ACE)) - return 0; - /* Otherwise, open the parent directory with READ_CONTROL... */ + /* Open the parent directory with READ_CONTROL... */ RtlSplitUnicodePath (pc.get_nt_native_path (), &dirname, NULL); InitializeObjectAttributes (&attr, &dirname, pc.objcaseinsensitive (), NULL, NULL); @@ -164,12 +148,14 @@ get_file_sd (HANDLE fh, path_conv &pc, security_descriptor &sd, &dirname, status); return 0; } +#if 0 /* ... and create a new security descriptor in which all inherited ACEs are marked with the INHERITED_ACE flag. For a description of the undocumented RtlConvertToAutoInheritSecurityObject function from ntdll.dll see the MSDN man page for the advapi32 function ConvertToAutoInheritPrivateObjectSecurity. Fortunately the latter is just a shim. */ + PSECURITY_DESCRIPTOR nsd; status = RtlConvertToAutoInheritSecurityObject (psd, sd, &nsd, NULL, pc.isdir (), &file_mapping); @@ -185,6 +171,36 @@ get_file_sd (HANDLE fh, path_conv &pc, security_descriptor &sd, len = RtlLengthSecurityDescriptor (nsd); memcpy ((PSECURITY_DESCRIPTOR) sd, nsd, len); RtlDeleteSecurityObject (&nsd); +#else + /* ... and check the parent descriptor for inheritable ACEs matching + our current object type (file/dir). The simple truth in our case + is, either the parent dir had inheritable ACEs and all our ACEs are + inherited, or the parent dir didn't have inheritable ACEs and all + our ACEs are taken from the default DACL. */ + bool inherited = false; + BYTE search_flags = pc.isdir () ? SUB_CONTAINERS_AND_OBJECTS_INHERIT + : SUB_OBJECTS_ONLY_INHERIT; + if (NT_SUCCESS (RtlGetDaclSecurityDescriptor (psd, &exists, &dacl, &def)) + && exists && dacl) + for (ULONG idx = 0; idx < dacl->AceCount; ++idx) + if (NT_SUCCESS (RtlGetAce (dacl, idx, (PVOID *) &ace)) + && (ace->Header.AceFlags & search_flags)) + { + inherited = true; + break; + } + /* Then, if the parent descriptor contained inheritable ACEs, we mark + the SD as SE_DACL_AUTO_INHERITED. Note that this requires the + matching check in get_posix_access. If we ever revert to + RtlConvertToAutoInheritSecurityObject, the check in get_posix_access + has to test every single ACE for the INHERITED_ACE flag again. */ + if (inherited + && NT_SUCCESS (RtlGetDaclSecurityDescriptor (sd, &exists, &dacl, + &def)) + && exists && dacl) + RtlSetControlSecurityDescriptor (sd, SE_DACL_AUTO_INHERITED, + SE_DACL_AUTO_INHERITED); +#endif } return 0; } @@ -234,213 +250,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); - bool userisowner = RtlEqualSid (owner_sid, cygheap->user.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; - /* If the current user is the owner of the file, check if the - additional SIDs are in the user's token. Note that this is - some ugly hack, but a full-fledged solution requires to - create tokens or perhaps using AUTHZ. */ - BOOL ret; - if (userisowner - && CheckTokenMembership (cygheap->user.issetuid () - ? cygheap->user.imp_token () : NULL, - ace_sid, &ret) - && ret) - { - 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; - } - } - } - *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) { @@ -473,7 +282,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 */ @@ -490,7 +299,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 @@ -518,8 +327,8 @@ get_file_attribute (HANDLE handle, path_conv &pc, } bool -add_access_allowed_ace (PACL acl, int offset, DWORD attributes, - PSID sid, size_t &len_add, DWORD inherit) +add_access_allowed_ace (PACL acl, DWORD attributes, PSID sid, size_t &len_add, + DWORD inherit) { NTSTATUS status = RtlAddAccessAllowedAceEx (acl, ACL_REVISION, inherit, attributes, sid); @@ -533,8 +342,8 @@ add_access_allowed_ace (PACL acl, int offset, DWORD attributes, } bool -add_access_denied_ace (PACL acl, int offset, DWORD attributes, - PSID sid, size_t &len_add, DWORD inherit) +add_access_denied_ace (PACL acl, DWORD attributes, PSID sid, size_t &len_add, + DWORD inherit) { NTSTATUS status = RtlAddAccessDeniedAceEx (acl, ACL_REVISION, inherit, attributes, sid); @@ -547,367 +356,6 @@ add_access_denied_ace (PACL acl, int offset, DWORD attributes, return true; } -static PSECURITY_DESCRIPTOR -alloc_sd (path_conv &pc, uid_t uid, gid_t gid, int attribute, - security_descriptor &sd_ret) -{ - NTSTATUS status; - BOOLEAN dummy; - tmp_pathbuf tp; - - /* NOTE: If the high bit of attribute is set, we have just created - a file or directory. See below for an explanation. */ - - debug_printf("uid %u, gid %u, attribute 0%o", uid, gid, attribute); - - /* Get owner and group from current security descriptor. */ - PSID cur_owner_sid = NULL; - PSID cur_group_sid = NULL; - status = RtlGetOwnerSecurityDescriptor (sd_ret, &cur_owner_sid, &dummy); - if (!NT_SUCCESS (status)) - debug_printf ("RtlGetOwnerSecurityDescriptor: %y", status); - status = RtlGetGroupSecurityDescriptor (sd_ret, &cur_group_sid, &dummy); - if (!NT_SUCCESS (status)) - debug_printf ("RtlGetGroupSecurityDescriptor: %y", status); - - /* Get SID of owner. */ - cygsid owner_sid; - /* Check for current user first */ - if (uid == myself->uid) - owner_sid = cygheap->user.sid (); - else if (uid == ILLEGAL_UID) - owner_sid = cur_owner_sid; - else if (!owner_sid.getfrompw (internal_getpwuid (uid))) - { - set_errno (EINVAL); - return NULL; - } - owner_sid.debug_print ("alloc_sd: owner SID ="); - - /* Get SID of new group. */ - cygsid group_sid; - /* Check for current user first */ - if (gid == myself->gid) - group_sid = cygheap->user.groups.pgsid; - else if (gid == ILLEGAL_GID) - group_sid = cur_group_sid; - else if (!group_sid.getfromgr (internal_getgrgid (gid))) - { - set_errno (EINVAL); - return NULL; - } - group_sid.debug_print ("alloc_sd: group SID ="); - - /* Initialize local security descriptor. */ - SECURITY_DESCRIPTOR sd; - RtlCreateSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION); - - /* We set the SE_DACL_PROTECTED flag here to prevent the DACL from being - modified by inheritable ACEs. */ - RtlSetControlSecurityDescriptor (&sd, SE_DACL_PROTECTED, SE_DACL_PROTECTED); - - /* Create owner for local security descriptor. */ - status = RtlSetOwnerSecurityDescriptor (&sd, owner_sid, FALSE); - if (!NT_SUCCESS (status)) - { - __seterrno_from_nt_status (status); - return NULL; - } - - /* Create group for local security descriptor. */ - status = RtlSetGroupSecurityDescriptor (&sd, group_sid, FALSE); - if (!NT_SUCCESS (status)) - { - __seterrno_from_nt_status (status); - return NULL; - } - - /* Initialize local access control list. */ - PACL acl = (PACL) tp.w_get (); - RtlCreateAcl (acl, ACL_MAXIMUM_SIZE, ACL_REVISION); - - /* From here fill ACL. */ - size_t acl_len = sizeof (ACL); - int ace_off = 0; - /* Only used for sync objects (for ttys). The admins group should - always have the right to manipulate the ACL, so we have to make sure - that the ACL gives the admins group STANDARD_RIGHTS_ALL access. */ - bool saw_admins = false; - - /* Construct allow attribute for owner. - Don't set FILE_READ/WRITE_ATTRIBUTES unconditionally on Samba, otherwise - it enforces read permissions. Same for other's below. */ - DWORD owner_allow = STANDARD_RIGHTS_ALL - | (pc.fs_is_samba () - ? 0 : (FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES)); - if (attribute & S_IRUSR) - owner_allow |= FILE_GENERIC_READ; - if (attribute & S_IWUSR) - owner_allow |= FILE_GENERIC_WRITE; - if (attribute & S_IXUSR) - owner_allow |= FILE_GENERIC_EXECUTE & ~FILE_READ_ATTRIBUTES; - if (S_ISDIR (attribute) - && (attribute & (S_IWUSR | S_IXUSR)) == (S_IWUSR | S_IXUSR)) - owner_allow |= FILE_DELETE_CHILD; - /* For sync objects note that the owner is admin. */ - if (S_ISCHR (attribute) && owner_sid == well_known_admins_sid) - saw_admins = true; - - /* Construct allow attribute for group. */ - DWORD group_allow = STANDARD_RIGHTS_READ | SYNCHRONIZE - | (pc.fs_is_samba () ? 0 : FILE_READ_ATTRIBUTES); - if (attribute & S_IRGRP) - group_allow |= FILE_GENERIC_READ; - if (attribute & S_IWGRP) - group_allow |= FILE_GENERIC_WRITE; - if (attribute & S_IXGRP) - group_allow |= FILE_GENERIC_EXECUTE & ~FILE_READ_ATTRIBUTES; - if (S_ISDIR (attribute) - && (attribute & (S_IWGRP | S_IXGRP)) == (S_IWGRP | S_IXGRP) - && !(attribute & S_ISVTX)) - group_allow |= FILE_DELETE_CHILD; - /* For sync objects, add STANDARD_RIGHTS_ALL for admins group. */ - if (S_ISCHR (attribute) && group_sid == well_known_admins_sid) - { - group_allow |= STANDARD_RIGHTS_ALL; - saw_admins = true; - } - - /* Construct allow attribute for everyone. */ - DWORD other_allow = STANDARD_RIGHTS_READ | SYNCHRONIZE - | (pc.fs_is_samba () ? 0 : FILE_READ_ATTRIBUTES); - if (attribute & S_IROTH) - other_allow |= FILE_GENERIC_READ; - if (attribute & S_IWOTH) - other_allow |= FILE_GENERIC_WRITE; - if (attribute & S_IXOTH) - other_allow |= FILE_GENERIC_EXECUTE & ~FILE_READ_ATTRIBUTES; - if (S_ISDIR (attribute) - && (attribute & (S_IWOTH | S_IXOTH)) == (S_IWOTH | S_IXOTH) - && !(attribute & S_ISVTX)) - other_allow |= FILE_DELETE_CHILD; - - /* Construct SUID, SGID and VTX bits in NULL ACE. */ - DWORD null_allow = 0L; - if (attribute & (S_ISUID | S_ISGID | S_ISVTX)) - { - if (attribute & S_ISUID) - null_allow |= FILE_APPEND_DATA; - if (attribute & S_ISGID) - null_allow |= FILE_WRITE_DATA; - if (attribute & S_ISVTX) - null_allow |= FILE_READ_DATA; - } - - /* Add owner and group permissions if SIDs are equal - and construct deny attributes for group and owner. */ - bool isownergroup; - if ((isownergroup = (owner_sid == group_sid))) - owner_allow |= group_allow; - - 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_sid, acl_len, NO_INHERITANCE)) - return NULL; - /* 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_sid, acl_len, NO_INHERITANCE)) - return NULL; - /* Set allow ACE for owner. */ - if (!add_access_allowed_ace (acl, ace_off++, owner_allow, - owner_sid, acl_len, NO_INHERITANCE)) - return NULL; - /* Set deny ACE for group, if still needed. */ - if ((group_deny & owner_allow) && !isownergroup - && !add_access_denied_ace (acl, ace_off++, group_deny, - group_sid, acl_len, NO_INHERITANCE)) - return NULL; - /* Set allow ACE for group. */ - if (!isownergroup - && !add_access_allowed_ace (acl, ace_off++, group_allow, - group_sid, acl_len, NO_INHERITANCE)) - return NULL; - - /* For sync objects, if we didn't see the admins group so far, add entry - with STANDARD_RIGHTS_ALL access. */ - if (S_ISCHR (attribute) && !saw_admins) - { - if (!add_access_allowed_ace (acl, ace_off++, STANDARD_RIGHTS_ALL, - well_known_admins_sid, acl_len, - NO_INHERITANCE)) - return NULL; - saw_admins = true; - } - - /* Set allow ACE for everyone. */ - if (!add_access_allowed_ace (acl, ace_off++, other_allow, - well_known_world_sid, acl_len, NO_INHERITANCE)) - return NULL; - /* Set null ACE for special bits. */ - if (null_allow - && !add_access_allowed_ace (acl, ace_off++, null_allow, - well_known_null_sid, acl_len, NO_INHERITANCE)) - return NULL; - - /* Fill ACL with unrelated ACEs from current security descriptor. */ - PACL oacl; - BOOLEAN acl_exists = FALSE; - ACCESS_ALLOWED_ACE *ace; - - status = RtlGetDaclSecurityDescriptor (sd_ret, &acl_exists, &oacl, &dummy); - if (NT_SUCCESS (status) && acl_exists && oacl) - for (DWORD i = 0; i < oacl->AceCount; ++i) - if (NT_SUCCESS (RtlGetAce (oacl, i, (PVOID *) &ace))) - { - cygpsid ace_sid ((PSID) &ace->SidStart); - - /* Always skip NULL SID as well as admins SID on virtual device files - in /proc/sys. */ - if (ace_sid == well_known_null_sid - || (S_ISCHR (attribute) && ace_sid == well_known_admins_sid)) - continue; - /* Check for ACEs which are always created in the preceding code - and check for the default inheritence ACEs which will be created - for just created directories. Skip them for just created - directories or if they are not inherited. If they are inherited, - make sure they are *only* inherited, so they don't collide with - the permissions set in this function. */ - if ((ace_sid == cur_owner_sid) - || (ace_sid == owner_sid) - || (ace_sid == cur_group_sid) - || (ace_sid == group_sid) - || (ace_sid == well_known_creator_owner_sid) - || (ace_sid == well_known_creator_group_sid) - || (ace_sid == well_known_world_sid)) - { - if ((S_ISDIR (attribute) && (attribute & S_JUSTCREATED)) - || (ace->Header.AceFlags - & (CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE)) == 0) - continue; - else - ace->Header.AceFlags |= INHERIT_ONLY_ACE; - } - if (attribute & S_JUSTCREATED) - { - /* Since files and dirs are created with a NULL descriptor, - inheritence rules kick in. If no inheritable entries exist - in the parent object, Windows will create entries from the - user token's default DACL in the file DACL. These entries - are not desired and we drop them silently. */ - if (!(ace->Header.AceFlags & INHERITED_ACE)) - continue; - /* Remove the INHERITED_ACE flag since on POSIX systems - inheritance is settled when the file has been created. - This also avoids error messages in Windows Explorer when - opening a file's security tab. Explorer complains if - inheritable ACEs are preceding non-inheritable ACEs. */ - ace->Header.AceFlags &= ~INHERITED_ACE; - /* However, if the newly created object is a directory, - 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; - } - else if (uid == ILLEGAL_UID && gid == ILLEGAL_UID - && ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE - && ace_sid != well_known_creator_group_sid - && ace_sid != well_known_creator_owner_sid - && ace_sid != well_known_world_sid) - { - /* FIXME: Temporary workaround for the problem that chmod does - not affect the group permissions if other users and groups - in the ACL have more permissions than the primary group due - to the CLASS_OBJ emulation. The temporary workaround is to - disallow any secondary ACE in the ACL more permissions than - the primary group when writing a new ACL via chmod. */ - ace->Mask &= group_allow; - } - /* Add unrelated ACCESS_DENIED_ACE to the beginning but behind - the owner_deny, ACCESS_ALLOWED_ACE to the end. FIXME: this - would break the order of the inherit-only ACEs. */ - status = RtlAddAce (acl, ACL_REVISION, - ace->Header.AceType == ACCESS_DENIED_ACE_TYPE - ? (owner_deny ? 1 : 0) : MAXDWORD, - (LPVOID) ace, ace->Header.AceSize); - if (!NT_SUCCESS (status)) - { - __seterrno_from_nt_status (status); - return NULL; - } - ace_off++; - acl_len += ace->Header.AceSize; - } - - /* Construct appropriate inherit attribute for new directories. Keep in - mind that we do this only for the sake of non-Cygwin applications. - 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; - /* Set allow ACE for owner. */ - if (!add_access_allowed_ace (acl, ace_off++, owner_allow, - well_known_creator_owner_sid, acl_len, - inherit)) - return NULL; - /* Set allow ACE for group. */ - if (!add_access_allowed_ace (acl, ace_off++, group_allow, - well_known_creator_group_sid, acl_len, - inherit)) - return NULL; - /* Set allow ACE for everyone. */ - if (!add_access_allowed_ace (acl, ace_off++, other_allow, - well_known_world_sid, acl_len, inherit)) - return NULL; - } - - /* Set AclSize to computed value. */ - acl->AclSize = acl_len; - debug_printf ("ACL-Size: %d", acl_len); - - /* Create DACL for local security descriptor. */ - status = RtlSetDaclSecurityDescriptor (&sd, TRUE, acl, FALSE); - if (!NT_SUCCESS (status)) - { - __seterrno_from_nt_status (status); - return NULL; - } - - /* Make self relative security descriptor. */ - DWORD sd_size = 0; - RtlAbsoluteToSelfRelativeSD (&sd, sd_ret, &sd_size); - if (sd_size <= 0) - { - __seterrno (); - return NULL; - } - if (!sd_ret.malloc (sd_size)) - { - set_errno (ENOMEM); - return NULL; - } - status = RtlAbsoluteToSelfRelativeSD (&sd, sd_ret, &sd_size); - if (!NT_SUCCESS (status)) - { - __seterrno_from_nt_status (status); - return NULL; - } - debug_printf ("Created SD-Size: %u", sd_ret.size ()); - - return sd_ret; -} - void set_security_attribute (path_conv &pc, int attribute, PSECURITY_ATTRIBUTES psa, security_descriptor &sd) @@ -915,8 +363,9 @@ set_security_attribute (path_conv &pc, int attribute, PSECURITY_ATTRIBUTES psa, psa->lpSecurityDescriptor = sd.malloc (SECURITY_DESCRIPTOR_MIN_LENGTH); RtlCreateSecurityDescriptor ((PSECURITY_DESCRIPTOR) psa->lpSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION); - psa->lpSecurityDescriptor = alloc_sd (pc, geteuid32 (), getegid32 (), - attribute, sd); + psa->lpSecurityDescriptor = set_posix_access (attribute, geteuid32 (), + getegid32 (), NULL, 0, + sd, false); } int @@ -952,22 +401,24 @@ get_object_attribute (HANDLE handle, uid_t *uidret, gid_t *gidret, mode_t *attribute) { security_descriptor sd; + mode_t attr = S_IFCHR; if (get_object_sd (handle, sd)) return -1; - get_info_from_sd (sd, attribute, uidret, gidret); - return 0; + if (attribute) + *attribute |= S_IFCHR; + else + attribute = &attr; + return get_posix_access (sd, attribute, uidret, gidret, NULL, 0) + >= 0 ? 0 : -1; } int -create_object_sd_from_attribute (HANDLE handle, uid_t uid, gid_t gid, - mode_t attribute, security_descriptor &sd) +create_object_sd_from_attribute (uid_t uid, gid_t gid, mode_t attribute, + security_descriptor &sd) { - path_conv pc; - if ((handle && get_object_sd (handle, sd)) - || !alloc_sd (pc, uid, gid, attribute, sd)) - return -1; - return 0; + return set_posix_access (S_IFCHR | attribute, uid, gid, NULL, 0, sd, false) + ? 0 : -1; } int @@ -985,36 +436,98 @@ set_object_sd (HANDLE handle, security_descriptor &sd, bool chown) } int -set_object_attribute (HANDLE handle, uid_t uid, gid_t gid, - mode_t attribute) +set_object_attribute (HANDLE handle, uid_t uid, gid_t gid, mode_t attribute) { security_descriptor sd; - if (create_object_sd_from_attribute (handle, uid, gid, attribute, sd) + if (create_object_sd_from_attribute (uid, gid, attribute, sd) || set_object_sd (handle, sd, uid != ILLEGAL_UID || gid != ILLEGAL_GID)) return -1; return 0; } int -set_file_attribute (HANDLE handle, path_conv &pc, - uid_t uid, gid_t gid, mode_t attribute) +set_created_file_access (HANDLE handle, path_conv &pc, mode_t attr) { int ret = -1; + security_descriptor sd, sd_ret; + mode_t attr_rd; + uid_t uid; + gid_t gid; + tmp_pathbuf tp; + aclent_t *aclp; + int nentries, idx; - if (pc.has_acls ()) + if (!get_file_sd (handle, pc, sd, true)) { - security_descriptor sd; - - if (!get_file_sd (handle, pc, sd, (bool)(attribute & S_JUSTCREATED)) - && alloc_sd (pc, uid, gid, attribute, sd)) - ret = set_file_sd (handle, pc, sd, - uid != ILLEGAL_UID || gid != ILLEGAL_GID); + attr |= S_JUSTCREATED; + if (pc.isdir ()) + attr |= S_IFDIR; + attr_rd = attr; + aclp = (aclent_t *) tp.c_get (); + if ((nentries = get_posix_access (sd, &attr_rd, &uid, &gid, + aclp, MAX_ACL_ENTRIES)) >= 0) + { + if (S_ISLNK (attr)) + { + /* Symlinks always get the request POSIX perms. */ + aclp[0].a_perm = (attr >> 6) & S_IRWXO; + if ((idx = searchace (aclp, nentries, GROUP_OBJ)) >= 0) + aclp[idx].a_perm = (attr >> 3) & S_IRWXO; + if (nentries > MIN_ACL_ENTRIES + && (idx = searchace (aclp, nentries, CLASS_OBJ)) >= 0) + aclp[idx].a_perm = (attr >> 3) & S_IRWXO; + if ((idx = searchace (aclp, nentries, OTHER_OBJ)) >= 0) + aclp[idx].a_perm = attr & S_IRWXO; + } + else + { + /* Overwrite ACL permissions as required by POSIX 1003.1e + draft 17. */ + aclp[0].a_perm &= (attr >> 6) & S_IRWXO; + /* Deliberate deviation from POSIX 1003.1e here. We're not + writing CLASS_OBJ *or* GROUP_OBJ, but both. Otherwise we're + going to be in constant trouble with user expectations. */ + if ((idx = searchace (aclp, nentries, GROUP_OBJ)) >= 0) + aclp[idx].a_perm &= (attr >> 3) & S_IRWXO; + if (nentries > MIN_ACL_ENTRIES + && (idx = searchace (aclp, nentries, CLASS_OBJ)) >= 0) + aclp[idx].a_perm &= (attr >> 3) & S_IRWXO; + if ((idx = searchace (aclp, nentries, OTHER_OBJ)) >= 0) + aclp[idx].a_perm &= attr & S_IRWXO; + } + /* Construct appropriate inherit attribute for new directories. + Basically we do this only for the sake of non-Cygwin applications. + Cygwin applications don't need these. Additionally, if the + S_ISGID bit is set, propagate it. */ + if (S_ISDIR (attr)) + { + if (searchace (aclp, nentries, DEF_USER_OBJ) < 0) + { + aclp[nentries].a_type = DEF_USER_OBJ; + aclp[nentries].a_id = ILLEGAL_UID; + aclp[nentries++].a_perm = (attr >> 6) & S_IRWXO; + } + if (searchace (aclp, nentries, DEF_GROUP_OBJ) < 0) + { + aclp[nentries].a_type = DEF_GROUP_OBJ; + aclp[nentries].a_id = ILLEGAL_GID; + aclp[nentries++].a_perm = (attr >> 3) & S_IRWXO; + } + if (searchace (aclp, nentries, DEF_OTHER_OBJ) < 0) + { + aclp[nentries].a_type = DEF_OTHER_OBJ; + aclp[nentries].a_id = ILLEGAL_UID; + aclp[nentries++].a_perm = attr & S_IRWXO; + } + if (attr_rd & S_ISGID) + attr |= S_ISGID; + } + if (set_posix_access (attr, uid, gid, aclp, nentries, sd_ret, + pc.fs_is_samba ())) + ret = set_file_sd (handle, pc, sd_ret, attr_rd & S_ISGID); + } } - else - ret = 0; - syscall_printf ("%d = set_file_attribute(%S, %d, %d, 0%o)", - ret, pc.get_nt_native_path (), uid, gid, attribute); return ret; } @@ -1165,7 +678,7 @@ convert_samba_sd (security_descriptor &sd_ret) ace_sid.getfromgr (grp); } } - if (!add_access_allowed_ace (acl, i, ace->Mask, ace_sid, acl_len, + if (!add_access_allowed_ace (acl, ace->Mask, ace_sid, acl_len, ace->Header.AceFlags)) return; } diff --git a/winsup/cygwin/security.h b/winsup/cygwin/security.h index 867345774..5378b2a45 100644 --- a/winsup/cygwin/security.h +++ b/winsup/cygwin/security.h @@ -436,20 +436,19 @@ class path_conv; /* File manipulation */ int __reg3 get_file_attribute (HANDLE, path_conv &, mode_t *, uid_t *, gid_t *); -int __reg3 set_file_attribute (HANDLE, path_conv &, - uid_t, gid_t, mode_t); +int __reg3 set_created_file_access (HANDLE, path_conv &, mode_t); int __reg2 get_object_sd (HANDLE, security_descriptor &); int __reg3 get_object_attribute (HANDLE, uid_t *, gid_t *, mode_t *); int __reg3 set_object_attribute (HANDLE, uid_t, gid_t, mode_t); -int __reg3 create_object_sd_from_attribute (HANDLE, uid_t, gid_t, - mode_t, security_descriptor &); +int __reg3 create_object_sd_from_attribute (uid_t, gid_t, mode_t, + security_descriptor &); int __reg3 set_object_sd (HANDLE, security_descriptor &, bool); int __reg3 get_reg_attribute (HKEY hkey, mode_t *, uid_t *, gid_t *); LONG __reg3 get_file_sd (HANDLE fh, path_conv &, security_descriptor &, bool); LONG __reg3 set_file_sd (HANDLE fh, path_conv &, security_descriptor &, bool); -bool __reg3 add_access_allowed_ace (PACL, int, DWORD, PSID, size_t &, DWORD); -bool __reg3 add_access_denied_ace (PACL, int, DWORD, PSID, size_t &, DWORD); +bool __reg3 add_access_allowed_ace (PACL, DWORD, PSID, size_t &, DWORD); +bool __reg3 add_access_denied_ace (PACL, DWORD, PSID, size_t &, DWORD); int __reg3 check_file_access (path_conv &, int, bool); int __reg3 check_registry_access (HANDLE, int, bool); @@ -463,6 +462,11 @@ 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 searchace (struct acl *, int, int, uid_t id = ILLEGAL_UID); +PSECURITY_DESCRIPTOR set_posix_access (mode_t, uid_t, gid_t, struct acl *, int, + security_descriptor &, bool); +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 &); diff --git a/winsup/cygwin/syscalls.cc b/winsup/cygwin/syscalls.cc index c08d12fa9..86faa31e4 100644 --- a/winsup/cygwin/syscalls.cc +++ b/winsup/cygwin/syscalls.cc @@ -1434,8 +1434,7 @@ open (const char *unix_path, int flags, ...) } else if ((fh->is_fs_special () && fh->device_access_denied (flags)) - || !fh->open_with_arch (flags, (mode & 07777) - & ~cygheap->umask)) + || !fh->open_with_arch (flags, mode & 07777)) delete fh; else { diff --git a/winsup/doc/ChangeLog b/winsup/doc/ChangeLog index df8c64114..88287fed5 100644 --- a/winsup/doc/ChangeLog +++ b/winsup/doc/ChangeLog @@ -1,3 +1,8 @@ +2015-11-18 Corinna Vinschen + + * utils.xml (setfacl): Show new option output. + (getfacl): Show new option output. + 2015-11-17 Yaakov Selkowitz * new-features.xml (ov-new2.4): New section. Document rpmatch. diff --git a/winsup/doc/utils.xml b/winsup/doc/utils.xml index 5d3df69b1..7c2740dea 100644 --- a/winsup/doc/utils.xml +++ b/winsup/doc/utils.xml @@ -590,18 +590,21 @@ Other options: -getfacl [-adn] FILE [FILE2...] +getfacl [-adceEn] FILE [FILE2...] Options - -a, --access display the file access control list - -d, --default display the default access control list - -h, --help print help explaining the command line options - -n, --noname display user and group IDs instead of names - -V, --version output version information and exit + -a, --access display the file access control list only + -d, --default display the default access control list only + -c, --omit-header do not display the comment header + -e, --all-effective print all effective rights + -E, --no-effective print no effective rights + -n, --numeric print numeric user/group identifiers + -V, --version print version and exit + -h, --help this help text When multiple files are specified on the command line, a blank line separates the ACLs for each file. @@ -1965,29 +1968,27 @@ Example: regtool get '\user\software\Microsoft\Clock\iFormat' -setfacl [-r] {-f ACL_FILE | -s acl_entries} FILE... -setfacl [-r] {-b|[-d acl_entries] [-m acl_entries]} FILE... +setfacl [-n] {-f ACL_FILE | -s acl_entries} FILE... +setfacl [-n] {-b|-k|[-x acl_entries] [-m acl_entries]} FILE... Options - -b, --remove-all remove all extended ACL entries - -d, --delete delete one or more specified ACL entries - -f, --file set ACL entries for FILE to ACL entries read - from a ACL_FILE - -k, --remove-default - remove all default ACL entries - -m, --modify modify one or more specified ACL entries - -r, --replace replace mask entry with maximum permissions - needed for the file group class - -s, --substitute substitute specified ACL entries for the - ACL of FILE - -h, --help output usage information and exit - -V, --version output version information and exit + -b, --remove-all remove all extended ACL entries\n" + -x, --delete delete one or more specified ACL entries\n" + -f, --file set ACL entries for FILE to ACL entries read\n" + from ACL_FILE\n" + -k, --remove-default remove all default ACL entries\n" + -m, --modify modify one or more specified ACL entries\n" + -n, --no-mask don't recalculate the effective rights mask\n" + --mask do recalculate the effective rights mask\n" + -s, --substitute substitute specified ACL entries on FILE\n" + -V, --version print version and exit\n" + -h, --help this help text\n" -At least one of (-b, -d, -f, -k, -m, -s) must be specified +At least one of (-b, -x, -f, -k, -m, -s) must be specified\n" @@ -1996,8 +1997,8 @@ At least one of (-b, -d, -f, -k, -m, -s) must be specified For each file given as parameter, setfacl will either replace its complete ACL (-s, -f), or it will add, modify, or delete ACL entries. - For more information on Cygwin and Windows ACLs, see see in the Cygwin User's Guide. + For more information on Cygwin and Windows ACLs, see + in the Cygwin User's Guide. Acl_entries are one or more comma-separated ACL entries from the following list: @@ -2024,13 +2025,14 @@ At least one of (-b, -d, -f, -k, -m, -s) must be specified The following options are supported: - -b Remove all extended ACL entries. The base - ACL entries of the owner, group and others are retained. + -b,--remove-all Remove all + extended ACL entries. The base ACL entries of the owner, group and + others are retained. - -d Delete one or more specified entries from the - file's ACL. The owner, group and others entries must not be deleted. - Acl_entries to be deleted should be specified without permissions, as in - the following list: + -x,--delete Delete one or + more specified entries from the file's ACL. The owner, group and others + entries must not be deleted. Acl_entries to be deleted should be + specified without permissions, as in the following list: u[ser]:uid[:] g[roup]:gid[:] @@ -2041,11 +2043,12 @@ At least one of (-b, -d, -f, -k, -m, -s) must be specified d[efault]:o[ther][:] - -f Take the Acl_entries from ACL_FILE one per - line. Whitespace characters are ignored, and the character "#" may be - used to start a comment. The special filename "-" indicates reading from - stdin. Note that you can use this with getfacl and - setfacl to copy ACLs from one file to another: + -f,--file Take the Acl_entries + from ACL_FILE one per line. Whitespace characters are ignored, and the + character "#" may be used to start a comment. The special filename "-" + indicates reading from stdin. Note that you can use this with + getfacl and setfacl to copy ACLs + from one file to another: $ getfacl source_file | setfacl -f - target_file @@ -2061,22 +2064,31 @@ $ getfacl source_file | setfacl -f - target_file file, one default group entry for the group of the file, one default mask entry for the file group class, and one default other entry. - -k Remove all default ACL entries. If no default - ACL entries exist, no warnings are issued. + -k,--remove-default Remove all + default ACL entries. If no default ACL entries exist, no warnings are + issued. - -m Add or modify one or more specified ACL - entries. Acl_entries is a comma-separated list of entries from the same - list as above. + -m,--modify Add or modify one + or more specified ACL entries. Acl_entries is a comma-separated list of + entries from the same list as above. - -r Causes the permissions specified in the mask - entry to be ignored and replaced by the maximum permissions needed for - the file group class. + -n,--no-mask Valid in + conjunction with -m. Do not recalculate the effective rights mask. + The default behavior of setfacl is to recalculate the ACL mask entry, + unless a mask entry was explicitly given. The mask entry is set to + the union of all permissions of the owning group, and all named user + and group entries. (These are exactly the entries affected by the + mask entry). - -s Like -f, but substitute the - file's ACL with Acl_entries specified in a comma-separated list on the - command line. + --mask Valid in conjunction with -m. Do + recalculate the effective rights mask, even if an ACL mask entry was + explicitly given. (See the -n option.) - While the -d and -m options + -s,--substitute Like + -f, but substitute the file's ACL with Acl_entries + specified in a comma-separated list on the command line. + + While the -x and -m options may be used in the same command, the -f and -s options may be used only exclusively. diff --git a/winsup/utils/ChangeLog b/winsup/utils/ChangeLog index ac6eb98eb..104aae244 100644 --- a/winsup/utils/ChangeLog +++ b/winsup/utils/ChangeLog @@ -1,3 +1,31 @@ +2015-11-18 Corinna Vinschen + + Reapply POSIX ACL changes. + + * getfacl.c (main): Special-case SYSTEM and Admins group. Add comments. + + * setfacl.c: Align more to Linux tool. + (delacl): New function to delete acl entries only. + (modacl): Drop delete functionality. Add handling of recomputing the + mask and default mask values. + (delallacl): Rename from delacl. + (setfacl): Call delacl in Delete case. Call delallacl in DeleteAll + and DeleteDef case. + (usage): Accommodate new options. Rearrange and rephrase slightly. + (longopts): Emit 'x' in --delete case. Add --no-mask and --mask + options. + (opts): Add -x and -n options. + (main): Handle -d and -x the same. Handle -n and --mask options. + Drop handling for -r option. + + * getfacl.c (usage): Align more closely to Linux version. Add new + options -c, -e, -E. Change formatting to accommodate longer options. + (longopts): Rename --noname to --numeric. Keep --noname for backward + compatibility. Add --omit-header, --all-effective and --no-effective + options. + (opts): Add -c, -e and -E option. + (main): Handle new -c, -e, and -E options. + 2015-10-22 Yaakov Selkowitz * getconf.c (conf_table): Add LEVEL*_CACHE_* variables. diff --git a/winsup/utils/getfacl.c b/winsup/utils/getfacl.c index 0bc484877..45e5e2090 100644 --- a/winsup/utils/getfacl.c +++ b/winsup/utils/getfacl.c @@ -66,61 +66,68 @@ static void usage (FILE * stream) { fprintf (stream, "Usage: %s [-adn] FILE [FILE2...]\n" - "\n" - "Display file and directory access control lists (ACLs).\n" - "\n" - " -a, --access display the file access control list\n" - " -d, --default display the default access control list\n" - " -h, --help print help explaining the command line options\n" - " -n, --noname display user and group IDs instead of names\n" - " -V, --version output version information and exit\n" - "\n" - "When multiple files are specified on the command line, a blank\n" - "line separates the ACLs for each file.\n", prog_name); + "\n" + "Display file and directory access control lists (ACLs).\n" + "\n" + " -a, --access display the file access control list only\n" + " -d, --default display the default access control list only\n" + " -c, --omit-header do not display the comment header\n" + " -e, --all-effective print all effective rights\n" + " -E, --no-effective print no effective rights\n" + " -n, --numeric print numeric user/group identifiers\n" + " -V, --version print version and exit\n" + " -h, --help this help text\n" + "\n" + "When multiple files are specified on the command line, a blank\n" + "line separates the ACLs for each file.\n", prog_name); if (stream == stdout) { fprintf (stream, "" - "For each argument that is a regular file, special file or\n" - "directory, getfacl displays the owner, the group, and the ACL.\n" - "For directories getfacl displays additionally the default ACL.\n" - "\n" - "With no options specified, getfacl displays the filename, the\n" - "owner, the group, the setuid (s), setgid (s), and sticky (t)\n" - "bits if available, and both the ACL and the default ACL, if it\n" - "exists.\n" - "\n" - "The format for ACL output is as follows:\n" - " # file: filename\n" - " # owner: name or uid\n" - " # group: name or uid\n" - " # flags: sst\n" - " user::perm\n" - " user:name or uid:perm\n" - " group::perm\n" - " group:name or gid:perm\n" - " mask:perm\n" - " other:perm\n" - " default:user::perm\n" - " default:user:name or uid:perm\n" - " default:group::perm\n" - " default:group:name or gid:perm\n" - " default:mask:perm\n" - " default:other:perm\n" - "\n"); + "For each argument that is a regular file, special file or\n" + "directory, getfacl displays the owner, the group, and the ACL.\n" + "For directories getfacl displays additionally the default ACL.\n" + "\n" + "With no options specified, getfacl displays the filename, the\n" + "owner, the group, the setuid (s), setgid (s), and sticky (t)\n" + "bits if available, and both the ACL and the default ACL, if it\n" + "exists.\n" + "\n" + "The format for ACL output is as follows:\n" + " # file: filename\n" + " # owner: name or uid\n" + " # group: name or uid\n" + " # flags: sst\n" + " user::perm\n" + " user:name or uid:perm\n" + " group::perm\n" + " group:name or gid:perm\n" + " mask:perm\n" + " other:perm\n" + " default:user::perm\n" + " default:user:name or uid:perm\n" + " default:group::perm\n" + " default:group:name or gid:perm\n" + " default:mask:perm\n" + " default:other:perm\n" + "\n"); } } struct option longopts[] = { {"access", no_argument, NULL, 'a'}, {"all", no_argument, NULL, 'a'}, + {"omit-header", no_argument, NULL, 'c'}, + {"all-effective", no_argument, NULL, 'e'}, + {"no-effective", no_argument, NULL, 'E'}, {"default", no_argument, NULL, 'd'}, {"dir", no_argument, NULL, 'd'}, {"help", no_argument, NULL, 'h'}, - {"noname", no_argument, NULL, 'n'}, + {"noname", no_argument, NULL, 'n'}, /* Backward compat */ + {"numeric", no_argument, NULL, 'n'}, {"version", no_argument, NULL, 'V'}, {0, no_argument, NULL, 0} }; -const char *opts = "adhnV"; +const char *opts = "acdeEhnV"; static void print_version () @@ -142,8 +149,11 @@ main (int argc, char **argv) int c; int ret = 0; int aopt = 0; + int copt = 0; + int eopt = 0; int dopt = 0; int nopt = 0; + int istty = isatty (fileno (stdout)); struct stat st; aclent_t acls[MAX_ACL_ENTRIES]; @@ -155,9 +165,18 @@ main (int argc, char **argv) case 'a': aopt = 1; break; + case 'c': + copt = 1; + break; case 'd': dopt = 1; break; + case 'e': + eopt = 1; + break; + case 'E': + eopt = -1; + break; case 'h': usage (stdout); return 0; @@ -179,6 +198,8 @@ main (int argc, char **argv) for (; optind < argc; ++optind) { int i, num_acls; + mode_t mask = S_IRWXO, def_mask = S_IRWXO; + if (stat (argv[optind], &st) || (num_acls = acl (argv[optind], GETACL, MAX_ACL_ENTRIES, acls)) < 0) { @@ -187,28 +208,42 @@ main (int argc, char **argv) ret = 2; continue; } - printf ("# file: %s\n", argv[optind]); - if (nopt) + if (!copt) { - printf ("# owner: %lu\n", (unsigned long)st.st_uid); - printf ("# group: %lu\n", (unsigned long)st.st_gid); + printf ("# file: %s\n", argv[optind]); + if (nopt) + { + printf ("# owner: %lu\n", (unsigned long)st.st_uid); + printf ("# group: %lu\n", (unsigned long)st.st_gid); + } + else + { + printf ("# owner: %s\n", username (st.st_uid)); + printf ("# group: %s\n", groupname (st.st_gid)); + } + if (st.st_mode & (S_ISUID | S_ISGID | S_ISVTX)) + printf ("# flags: %c%c%c\n", (st.st_mode & S_ISUID) ? 's' : '-', + (st.st_mode & S_ISGID) ? 's' : '-', + (st.st_mode & S_ISVTX) ? 't' : '-'); } - else - { - printf ("# owner: %s\n", username (st.st_uid)); - printf ("# group: %s\n", groupname (st.st_gid)); - } - if (st.st_mode & (S_ISUID | S_ISGID | S_ISVTX)) - printf ("# flags: %c%c%c\n", (st.st_mode & S_ISUID) ? 's' : '-', - (st.st_mode & S_ISGID) ? 's' : '-', - (st.st_mode & S_ISVTX) ? 't' : '-'); for (i = 0; i < num_acls; ++i) { + if (acls[i].a_type == CLASS_OBJ) + mask = acls[i].a_perm; + else if (acls[i].a_type == DEF_CLASS_OBJ) + def_mask = acls[i].a_perm; + } + for (i = 0; i < num_acls; ++i) + { + int n = 0; + int print_effective = 0; + mode_t effective = acls[i].a_perm; + if (acls[i].a_type & ACL_DEFAULT) { if (aopt) continue; - printf ("default:"); + n += printf ("default:"); } else if (dopt) continue; @@ -219,18 +254,18 @@ main (int argc, char **argv) break; case USER: if (nopt) - printf ("user:%lu:", (unsigned long)acls[i].a_id); + n += printf ("user:%lu:", (unsigned long)acls[i].a_id); else - printf ("user:%s:", username (acls[i].a_id)); + n += printf ("user:%s:", username (acls[i].a_id)); break; case GROUP_OBJ: - printf ("group::"); + n += printf ("group::"); break; case GROUP: if (nopt) - printf ("group:%lu:", (unsigned long)acls[i].a_id); + n += printf ("group:%lu:", (unsigned long)acls[i].a_id); else - printf ("group:%s:", groupname (acls[i].a_id)); + n += printf ("group:%s:", groupname (acls[i].a_id)); break; case CLASS_OBJ: printf ("mask:"); @@ -239,7 +274,53 @@ main (int argc, char **argv) printf ("other:"); break; } - printf ("%s\n", permstr (acls[i].a_perm)); + n += printf ("%s", permstr (acls[i].a_perm)); + switch (acls[i].a_type) + { + case USER: + case GROUP_OBJ: + effective = acls[i].a_perm & mask; + print_effective = 1; + break; + case GROUP: + /* Special case SYSTEM and Admins group: The mask only + applies to them as far as the execute bit is concerned. */ + if (acls[i].a_id == 18 || acls[i].a_id == 544) + effective = acls[i].a_perm & (mask | S_IROTH | S_IWOTH); + else + effective = acls[i].a_perm & mask; + print_effective = 1; + break; + case DEF_USER: + case DEF_GROUP_OBJ: + effective = acls[i].a_perm & def_mask; + print_effective = 1; + break; + case DEF_GROUP: + /* Special case SYSTEM and Admins group: The mask only + applies to them as far as the execute bit is concerned. */ + if (acls[i].a_id == 18 || acls[i].a_id == 544) + effective = acls[i].a_perm & (def_mask | S_IROTH | S_IWOTH); + else + effective = acls[i].a_perm & def_mask; + print_effective = 1; + break; + } + if (print_effective && eopt >= 0 + && (eopt > 0 || effective != acls[i].a_perm)) + { + if (istty) + { + n = 40 - n; + if (n <= 0) + n = 1; + printf ("%*s", n, " "); + } + else + putchar ('\t'); + printf ("#effective:%s", permstr (effective)); + } + putchar ('\n'); } putchar ('\n'); } diff --git a/winsup/utils/setfacl.c b/winsup/utils/setfacl.c index ba674d009..20a42f33c 100644 --- a/winsup/utils/setfacl.c +++ b/winsup/utils/setfacl.c @@ -1,7 +1,7 @@ /* setfacl.c - Copyright 2000, 2001, 2002, 2003, 2006, 2008, 2009, 2010, 2011, 2014 - Red Hat Inc. + Copyright 2000, 2001, 2002, 2003, 2006, 2008, 2009, 2010, 2011, 2014, + 2015 Red Hat Inc. Written by Corinna Vinschen @@ -53,6 +53,8 @@ typedef enum { SetFromFile } action_t; +int mask_opt = 0; + mode_t getperm (char *in) { if (isdigit ((unsigned char) *in) && !in[1]) @@ -239,7 +241,7 @@ searchace (aclent_t *aclp, int nentries, int type, int id) } int -modacl (aclent_t *tgt, int tcnt, aclent_t *src, int scnt) +delacl (aclent_t *tgt, int tcnt, aclent_t *src, int scnt) { int t, s, i; @@ -249,25 +251,115 @@ modacl (aclent_t *tgt, int tcnt, aclent_t *src, int scnt) (src[s].a_type & (USER | GROUP)) ? src[s].a_id : -1); if (t < 0) return -1; - if (src[s].a_perm == ILLEGAL_MODE) + if (t < tcnt) { - if (t < tcnt) - { - for (i = t + 1; i < tcnt; ++i) - tgt[i - 1] = tgt[i]; - --tcnt; - } - } - else - { - tgt[t] = src[s]; - if (t >= tcnt) - ++tcnt; + for (i = t + 1; i < tcnt; ++i) + tgt[i - 1] = tgt[i]; + --tcnt; } } return tcnt; } +int +modacl (aclent_t *tgt, int tcnt, aclent_t *src, int scnt) +{ + int t, s; + int recompute_mask = 0, recompute_def_mask = 0; + int need_mask = 0, need_def_mask = 0; + int has_mask = 0, has_def_mask = 0; + int mask_idx = -1, def_mask_idx = -1; + mode_t mask = 0, def_mask = 0; + + /* Replace or add given acl entries. */ + for (s = 0; s < scnt; ++s) + { + t = searchace (tgt, MAX_ACL_ENTRIES, src[s].a_type, + (src[s].a_type & (USER | GROUP)) ? src[s].a_id : -1); + if (t < 0) + return -1; + tgt[t] = src[s]; + if (t >= tcnt) + ++tcnt; + /* Note if CLASS_OBJ and/or DEF_CLASS_OBJ are present in input. */ + if (src[s].a_type == CLASS_OBJ) + has_mask = 1; + else if (src[s].a_type == DEF_CLASS_OBJ) + has_def_mask = 1; + else if (src[s].a_type & ACL_DEFAULT) + recompute_def_mask = 1; + else + recompute_mask = 1; + } + /* Now recompute mask, if requested (default) */ + for (t = 0; t < tcnt; ++t) + { + switch (tgt[t].a_type) + { + case USER: + case GROUP: + /* Do we need a CLASS_OBJ at all? */ + need_mask = 1; + /*FALLTHRU*/ + case GROUP_OBJ: + /* Compute resulting maximum mask. */ + mask |= tgt[t].a_perm; + break; + case CLASS_OBJ: + /* Do we already have a CLASS_OBJ? */ + mask_idx = t; + break; + case DEF_USER: + case DEF_GROUP: + /* Do we need a DEF_CLASS_OBJ at all? */ + need_def_mask = 1; + /*FALLTHRU*/ + case DEF_GROUP_OBJ: + /* Compute resulting maximum default mask. */ + def_mask |= tgt[t].a_perm; + break; + case DEF_CLASS_OBJ: + /* Do we already have a DEF_CLASS_OBJ? */ + def_mask_idx = t; + break; + } + } + /* Recompute mask, if requested */ + if (recompute_mask && need_mask && mask_opt >= 0 + && (mask_opt > 0 || !has_mask)) + { + if (mask_idx >= 0) + t = mask_idx; + else + t = searchace (tgt, MAX_ACL_ENTRIES, CLASS_OBJ, -1); + if (t < 0) + return -1; + if (t >= tcnt) + ++tcnt; + tgt[t].a_type = CLASS_OBJ; + tgt[t].a_id = -1; + tgt[t].a_perm = mask; + } + /* Recompute default mask, if requested */ + if (recompute_def_mask && need_def_mask && mask_opt >= 0 + && (mask_opt > 0 || !has_def_mask)) + { + if (def_mask_idx >= 0) + t = def_mask_idx; + else + t = searchace (tgt, MAX_ACL_ENTRIES, DEF_CLASS_OBJ, -1); + if (t < 0) + return -1; + if (t >= tcnt) + ++tcnt; + tgt[t].a_type = DEF_CLASS_OBJ; + tgt[t].a_id = -1; + tgt[t].a_perm = def_mask; + } + + return tcnt; +} + int addmissing (aclent_t *tgt, int tcnt) { @@ -333,7 +425,7 @@ addmissing (aclent_t *tgt, int tcnt) } int -delacl (aclent_t *tgt, int tcnt, action_t action) +delallacl (aclent_t *tgt, int tcnt, action_t action) { int t; @@ -369,7 +461,7 @@ setfacl (action_t action, const char *path, aclent_t *acls, int cnt) break; case Delete: if ((lcnt = acl (path, GETACL, MAX_ACL_ENTRIES, lacl)) < 0 - || (lcnt = modacl (lacl, lcnt, acls, cnt)) < 0 + || (lcnt = delacl (lacl, lcnt, acls, cnt)) < 0 || (lcnt = acl (path, SETACL, lcnt, lacl)) < 0) { perror (prog_name); @@ -379,7 +471,7 @@ setfacl (action_t action, const char *path, aclent_t *acls, int cnt) case DeleteAll: case DeleteDef: if ((lcnt = acl (path, GETACL, MAX_ACL_ENTRIES, lacl)) < 0 - || (lcnt = delacl (lacl, lcnt, action)) < 0 + || (lcnt = delallacl (lacl, lcnt, action)) < 0 || (lcnt = acl (path, SETACL, lcnt, lacl)) < 0) { perror (prog_name); @@ -404,114 +496,126 @@ static void usage (FILE *stream) { fprintf (stream, "" - "Usage: %s [-r] {-f ACL_FILE | -s acl_entries} FILE...\n" - " %s [-r] {-b|[-d acl_entries] [-m acl_entries]} FILE...\n" - "\n" - "Modify file and directory access control lists (ACLs)\n" - "\n" - " -b, --remove-all remove all extended ACL entries\n" - " -d, --delete delete one or more specified ACL entries\n" - " -f, --file set ACL entries for FILE to ACL entries read\n" - " from a ACL_FILE\n" - " -k, --remove-default\n" - " remove all default ACL entries\n" - " -m, --modify modify one or more specified ACL entries\n" - " -r, --replace replace mask entry with maximum permissions\n" - " needed for the file group class\n" - " -s, --substitute substitute specified ACL entries for the\n" - " ACL of FILE\n" - " -h, --help output usage information and exit\n" - " -V, --version output version information and exit\n" - "\n" - "At least one of (-b, -d, -f, -k, -m, -s) must be specified\n" - "\n", prog_name, prog_name); - if (stream == stdout) - { - printf("" - " Acl_entries are one or more comma-separated ACL entries \n" - " from the following list:\n" - "\n" - " u[ser]::perm\n" - " u[ser]:uid:perm\n" - " g[roup]::perm\n" - " g[roup]:gid:perm\n" - " m[ask]:perm\n" - " o[ther]:perm\n" - "\n" - " Default entries are like the above with the additional\n" - " default identifier. For example: \n" - "\n" - " d[efault]:u[ser]:uid:perm\n" - "\n" - " 'perm' is either a 3-char permissions string in the form\n" - " \"rwx\" with the character - for no permission\n" - " or it is the octal representation of the permissions, a\n" - " value from 0 (equivalent to \"---\") to 7 (\"rwx\").\n" - " 'uid' is a user name or a numerical uid.\n" - " 'gid' is a group name or a numerical gid.\n" - "\n" - "\n" - "For each file given as parameter, %s will either replace its\n" - "complete ACL (-s, -f), or it will add, modify, or delete ACL\n" - "entries.\n" - "\n" - "The following options are supported:\n" - "\n" - "-b Remove all extended ACL entries. The base ACL entries of the\n" - " owner, group and others are retained.\n" - "\n" - "-d Delete one or more specified entries from the file's ACL.\n" - " The owner, group and others entries must not be deleted.\n" - " Acl_entries to be deleted should be specified without\n" - " permissions, as in the following list:\n" - "\n" - " u[ser]:uid[:]\n" - " g[roup]:gid[:]\n" - " m[ask][:]\n" - " d[efault]:u[ser][:uid]\n" - " d[efault]:g[roup][:gid]\n" - " d[efault]:m[ask][:]\n" - " d[efault]:o[ther][:]\n" - "\n" - "-f Take the Acl_entries from ACL_FILE one per line. Whitespace\n" - " characters are ignored, and the character \"#\" may be used\n" - " to start a comment. The special filename \"-\" indicates\n" - " reading from stdin.\n" - " Required entries are\n" - " - One user entry for the owner of the file.\n" - " - One group entry for the group of the file.\n" - " - One other entry.\n" - " If additional user and group entries are given:\n" - " - A mask entry for the file group class of the file.\n" - " - No duplicate user or group entries with the same uid/gid.\n" - " If it is a directory:\n" - " - One default user entry for the owner of the file.\n" - " - One default group entry for the group of the file.\n" - " - One default mask entry for the file group class.\n" - " - One default other entry.\n" - "\n" - "-k Remove all default ACL entries. If no default ACL entries\n" - " exist, no warnings are issued.\n" - "\n" - "-m Add or modify one or more specified ACL entries.\n" - " Acl_entries is a comma-separated list of entries from the \n" - " same list as above.\n" - "\n" - "-r Causes the permissions specified in the mask entry to be\n" - " ignored and replaced by the maximum permissions needed for\n" - " the file group class.\n" - "\n" - "-s Like -f, but substitute the file's ACL with Acl_entries\n" - " specified in a comma-separated list on the command line.\n" - "\n" - "While the -d and -m options may be used in the same command, the\n" - "-f and -s options may be used only exclusively.\n" - "\n" - "Directories may contain default ACL entries. Files created\n" - "in a directory that contains default ACL entries will have\n" - "permissions according to the combination of the current umask,\n" - "the explicit permissions requested and the default ACL entries\n" - "\n", prog_name); + "Usage: %s {-f ACL_FILE | -s acl_entries} FILE...\n" + " %s {-b|[-x acl_entries] [-m acl_entries]} FILE...\n" + "\n" + "Modify file and directory access control lists (ACLs)\n" + "\n" + " -b, --remove-all remove all extended ACL entries\n" + " -x, --delete delete one or more specified ACL entries\n" + " -f, --file set ACL entries for FILE to ACL entries read\n" + " from ACL_FILE\n" + " -k, --remove-default remove all default ACL entries\n" + " -m, --modify modify one or more specified ACL entries\n" + " -n, --no-mask don't recalculate the effective rights mask\n" + " --mask do recalculate the effective rights mask\n" + " -s, --substitute substitute specified ACL entries on FILE\n" + " -V, --version print version and exit\n" + " -h, --help this help text\n" + "\n" + "At least one of (-b, -x, -f, -k, -m, -s) must be specified\n" + "\n", prog_name, prog_name); + if (stream == stdout) + { + printf("" + " Acl_entries are one or more comma-separated ACL entries \n" + " from the following list:\n" + "\n" + " u[ser]::perm\n" + " u[ser]:uid:perm\n" + " g[roup]::perm\n" + " g[roup]:gid:perm\n" + " m[ask]:perm\n" + " o[ther]:perm\n" + "\n" + " Default entries are like the above with the additional\n" + " default identifier. For example: \n" + "\n" + " d[efault]:u[ser]:uid:perm\n" + "\n" + " 'perm' is either a 3-char permissions string in the form\n" + " \"rwx\" with the character - for no permission\n" + " or it is the octal representation of the permissions, a\n" + " value from 0 (equivalent to \"---\") to 7 (\"rwx\").\n" + " 'uid' is a user name or a numerical uid.\n" + " 'gid' is a group name or a numerical gid.\n" + "\n" + "\n" + "For each file given as parameter, %s will either replace its\n" + "complete ACL (-s, -f), or it will add, modify, or delete ACL\n" + "entries.\n" + "\n" + "The following options are supported:\n" + "\n" + "-b, --remove-all\n" + " Remove all extended ACL entries. The base ACL entries of the\n" + " owner, group and others are retained.\n" + "\n" + "-x, --delete\n" + " Delete one or more specified entries from the file's ACL.\n" + " The owner, group and others entries must not be deleted.\n" + " Acl_entries to be deleted should be specified without\n" + " permissions, as in the following list:\n" + "\n" + " u[ser]:uid[:]\n" + " g[roup]:gid[:]\n" + " m[ask][:]\n" + " d[efault]:u[ser][:uid]\n" + " d[efault]:g[roup][:gid]\n" + " d[efault]:m[ask][:]\n" + " d[efault]:o[ther][:]\n" + "\n" + "-f, --file\n" + " Take the Acl_entries from ACL_FILE one per line. Whitespace\n" + " characters are ignored, and the character \"#\" may be used\n" + " to start a comment. The special filename \"-\" indicates\n" + " reading from stdin.\n" + " Required entries are\n" + " - One user entry for the owner of the file.\n" + " - One group entry for the group of the file.\n" + " - One other entry.\n" + " If additional user and group entries are given:\n" + " - A mask entry for the file group class of the file.\n" + " - No duplicate user or group entries with the same uid/gid.\n" + " If it is a directory:\n" + " - One default user entry for the owner of the file.\n" + " - One default group entry for the group of the file.\n" + " - One default mask entry for the file group class.\n" + " - One default other entry.\n" + "\n" + "-k, --remove-default\n" + " Remove all default ACL entries. If no default ACL entries exist,\n" + " no warnings are issued.\n" + "\n" + "-m, --modify\n" + " Add or modify one or more specified ACL entries. Acl_entries is\n" + " a comma-separated list of entries from the same list as above.\n" + "\n" + "-n, --no-mask\n" + " Valid in conjunction with -m. Do not recalculate the effective\n" + " rights mask. The default behavior of setfacl is to recalculate the\n" + " ACL mask entry, unless a mask entry was explicitly given. The\n" + " mask entry is set to the union of all permissions of the owning\n" + " group, and all named user and group entries. (These are exactly\n" + " the entries affected by the mask entry).\n" + "\n" + "--mask\n" + " Valid in conjunction with -m. Do recalculate the effective rights\n" + " mask, even if an ACL mask entry was explicitly given. (See the\n" + " -n option.)\n" + "\n" + "-s, --substitute\n" + " Like -f, but substitute the file's ACL with ACL entries\n" + " specified in a comma-separated list on the command line.\n" + "\n" + "While the -x and -m options may be used in the same command, the\n" + "-f and -s options may be used only exclusively.\n" + "\n" + "Directories may contain default ACL entries. Files created\n" + "in a directory that contains default ACL entries will have\n" + "permissions according to the combination of the current umask,\n" + "the explicit permissions requested and the default ACL entries\n" + "\n", prog_name); } else fprintf(stream, "Try '%s --help' for more information.\n", prog_name); @@ -519,17 +623,19 @@ usage (FILE *stream) struct option longopts[] = { {"remove-all", no_argument, NULL, 'b'}, - {"delete", required_argument, NULL, 'd'}, + {"delete", required_argument, NULL, 'x'}, {"file", required_argument, NULL, 'f'}, {"remove-default", no_argument, NULL, 'k'}, {"modify", required_argument, NULL, 'm'}, + {"no-mask", required_argument, NULL, 'n'}, + {"mask", required_argument, NULL, '\n'}, {"replace", no_argument, NULL, 'r'}, {"substitute", required_argument, NULL, 's'}, {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'V'}, {0, no_argument, NULL, 0} }; -const char *opts = "bd:f:hkm:rs:V"; +const char *opts = "bd:f:hkm:nrs:Vx"; static void print_version () @@ -550,7 +656,6 @@ main (int argc, char **argv) { int c; action_t action = NoAction; - int ropt = 0; aclent_t acls[MAX_ACL_ENTRIES]; int aclidx = 0; int ret = 0; @@ -570,7 +675,8 @@ main (int argc, char **argv) return 1; } break; - case 'd': + case 'd': /* Backward compat */ + case 'x': if (action == NoAction) action = Delete; else if (action == Modify) @@ -628,14 +734,13 @@ main (int argc, char **argv) return 2; } break; + case 'n': + mask_opt = -1; + break; + case '\n': + mask_opt = 1; + break; case 'r': - if (!ropt) - ropt = 1; - else - { - usage (stderr); - return 1; - } break; case 's': if (action == NoAction)