/** * acls.c - General function to process NTFS ACLs * * This module is part of ntfs-3g library, but may also be * integrated in tools running over Linux or Windows * * Copyright (c) 2007-2010 Jean-Pierre Andre * * This program/include file is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program/include file is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (in the main directory of the NTFS-3G * distribution in the file COPYING); if not, write to the Free Software * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H /* * integration into ntfs-3g */ #include "config.h" #ifdef HAVE_STDIO_H #include #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_SYSLOG_H #include #endif #include #include #include #include "types.h" #include "layout.h" #include "security.h" #include "acls.h" #include "misc.h" #else /* * integration into secaudit, check whether Win32, * may have to be adapted to compiler or something else */ #ifndef WIN32 #if defined(__WIN32) | defined(__WIN32__) | defined(WNSC) #define WIN32 1 #endif #endif #include #include #include #include #include #include #include /* * integration into secaudit/Win32 */ #ifdef WIN32 #include #include #define __LITTLE_ENDIAN 1234 #define __BYTE_ORDER __LITTLE_ENDIAN #else /* * integration into secaudit/STSC */ #ifdef STSC #include #undef __BYTE_ORDER #define __BYTE_ORDER __BIG_ENDIAN #else /* * integration into secaudit/Linux */ #include #include #include #include #endif /* STSC */ #endif /* WIN32 */ #include "secaudit.h" #endif /* HAVE_CONFIG_H */ /* * A few useful constants */ /* * null SID (S-1-0-0) */ static const char nullsidbytes[] = { 1, /* revision */ 1, /* auth count */ 0, 0, 0, 0, 0, 0, /* base */ 0, 0, 0, 0 /* 1st level */ }; static const SID *nullsid = (const SID*)nullsidbytes; /* * SID for world (S-1-1-0) */ static const char worldsidbytes[] = { 1, /* revision */ 1, /* auth count */ 0, 0, 0, 0, 0, 1, /* base */ 0, 0, 0, 0 /* 1st level */ } ; const SID *worldsid = (const SID*)worldsidbytes; /* * SID for administrator */ static const char adminsidbytes[] = { 1, /* revision */ 2, /* auth count */ 0, 0, 0, 0, 0, 5, /* base */ 32, 0, 0, 0, /* 1st level */ 32, 2, 0, 0 /* 2nd level */ }; const SID *adminsid = (const SID*)adminsidbytes; /* * SID for system */ static const char systemsidbytes[] = { 1, /* revision */ 1, /* auth count */ 0, 0, 0, 0, 0, 5, /* base */ 18, 0, 0, 0 /* 1st level */ }; static const SID *systemsid = (const SID*)systemsidbytes; /* * SID for generic creator-owner * S-1-3-0 */ static const char ownersidbytes[] = { 1, /* revision */ 1, /* auth count */ 0, 0, 0, 0, 0, 3, /* base */ 0, 0, 0, 0 /* 1st level */ } ; static const SID *ownersid = (const SID*)ownersidbytes; /* * SID for generic creator-group * S-1-3-1 */ static const char groupsidbytes[] = { 1, /* revision */ 1, /* auth count */ 0, 0, 0, 0, 0, 3, /* base */ 1, 0, 0, 0 /* 1st level */ } ; static const SID *groupsid = (const SID*)groupsidbytes; /* * Determine the size of a SID */ int ntfs_sid_size(const SID * sid) { return (sid->sub_authority_count * 4 + 8); } /* * Test whether two SID are equal */ BOOL ntfs_same_sid(const SID *first, const SID *second) { int size; size = ntfs_sid_size(first); return ((ntfs_sid_size(second) == size) && !memcmp(first, second, size)); } /* * Test whether a SID means "world user" * Local users group also recognized as world */ static int is_world_sid(const SID * usid) { return ( /* check whether S-1-1-0 : world */ ((usid->sub_authority_count == 1) && (usid->identifier_authority.high_part == const_cpu_to_be16(0)) && (usid->identifier_authority.low_part == const_cpu_to_be32(1)) && (usid->sub_authority[0] == const_cpu_to_le32(0))) /* check whether S-1-5-32-545 : local user */ || ((usid->sub_authority_count == 2) && (usid->identifier_authority.high_part == const_cpu_to_be16(0)) && (usid->identifier_authority.low_part == const_cpu_to_be32(5)) && (usid->sub_authority[0] == const_cpu_to_le32(32)) && (usid->sub_authority[1] == const_cpu_to_le32(545))) ); } /* * Test whether a SID means "some user (or group)" * Currently we only check for S-1-5-21... but we should * probably test for other configurations */ BOOL ntfs_is_user_sid(const SID *usid) { return ((usid->sub_authority_count == 5) && (usid->identifier_authority.high_part == const_cpu_to_be16(0)) && (usid->identifier_authority.low_part == const_cpu_to_be32(5)) && (usid->sub_authority[0] == const_cpu_to_le32(21))); } /* * Determine the size of a security attribute * whatever the order of fields */ unsigned int ntfs_attr_size(const char *attr) { const SECURITY_DESCRIPTOR_RELATIVE *phead; const ACL *pdacl; const ACL *psacl; const SID *psid; unsigned int offdacl; unsigned int offsacl; unsigned int offowner; unsigned int offgroup; unsigned int endsid; unsigned int endacl; unsigned int attrsz; phead = (const SECURITY_DESCRIPTOR_RELATIVE*)attr; /* * First check group, which is the last field in all descriptors * we build, and in most descriptors built by Windows */ attrsz = sizeof(SECURITY_DESCRIPTOR_RELATIVE); offgroup = le32_to_cpu(phead->group); if (offgroup >= attrsz) { /* find end of GSID */ psid = (const SID*)&attr[offgroup]; endsid = offgroup + ntfs_sid_size(psid); if (endsid > attrsz) attrsz = endsid; } offowner = le32_to_cpu(phead->owner); if (offowner >= attrsz) { /* find end of USID */ psid = (const SID*)&attr[offowner]; endsid = offowner + ntfs_sid_size(psid); attrsz = endsid; } offsacl = le32_to_cpu(phead->sacl); if (offsacl >= attrsz) { /* find end of SACL */ psacl = (const ACL*)&attr[offsacl]; endacl = offsacl + le16_to_cpu(psacl->size); if (endacl > attrsz) attrsz = endacl; } /* find end of DACL */ offdacl = le32_to_cpu(phead->dacl); if (offdacl >= attrsz) { pdacl = (const ACL*)&attr[offdacl]; endacl = offdacl + le16_to_cpu(pdacl->size); if (endacl > attrsz) attrsz = endacl; } return (attrsz); } /* * Do sanity checks on a SID read from storage * (just check revision and number of authorities) */ BOOL ntfs_valid_sid(const SID *sid) { return ((sid->revision == SID_REVISION) && (sid->sub_authority_count >= 1) && (sid->sub_authority_count <= 8)); } /* * Check whether a SID is acceptable for an implicit * mapping pattern. * It should have been already checked it is a valid user SID. * * The last authority reference has to be >= 1000 (Windows usage) * and <= 0x7fffffff, so that 30 bits from a uid and 30 more bits * from a gid an be inserted with no overflow. */ BOOL ntfs_valid_pattern(const SID *sid) { int cnt; u32 auth; le32 leauth; cnt = sid->sub_authority_count; leauth = sid->sub_authority[cnt-1]; auth = le32_to_cpu(leauth); return ((auth >= 1000) && (auth <= 0x7fffffff)); } /* * Compute the uid or gid associated to a SID * through an implicit mapping * * Returns 0 (root) if it does not match pattern */ static u32 findimplicit(const SID *xsid, const SID *pattern, int parity) { BIGSID defsid; SID *psid; u32 xid; /* uid or gid */ int cnt; u32 carry; le32 leauth; u32 uauth; u32 xlast; u32 rlast; memcpy(&defsid,pattern,ntfs_sid_size(pattern)); psid = (SID*)&defsid; cnt = psid->sub_authority_count; xid = 0; if (xsid->sub_authority_count == cnt) { psid->sub_authority[cnt-1] = xsid->sub_authority[cnt-1]; leauth = xsid->sub_authority[cnt-1]; xlast = le32_to_cpu(leauth); leauth = pattern->sub_authority[cnt-1]; rlast = le32_to_cpu(leauth); if ((xlast > rlast) && !((xlast ^ rlast ^ parity) & 1)) { /* direct check for basic situation */ if (ntfs_same_sid(psid,xsid)) xid = ((xlast - rlast) >> 1) & 0x3fffffff; else { /* * check whether part of mapping had to be * recorded in a higher level authority */ carry = 1; do { leauth = psid->sub_authority[cnt-2]; uauth = le32_to_cpu(leauth) + 1; psid->sub_authority[cnt-2] = cpu_to_le32(uauth); } while (!ntfs_same_sid(psid,xsid) && (++carry < 4)); if (carry < 4) xid = (((xlast - rlast) >> 1) & 0x3fffffff) | (carry << 30); } } } return (xid); } /* * Find usid mapped to a Linux user * Returns NULL if not found */ const SID *ntfs_find_usid(const struct MAPPING* usermapping, uid_t uid, SID *defusid) { const struct MAPPING *p; const SID *sid; le32 leauth; u32 uauth; int cnt; if (!uid) sid = adminsid; else { p = usermapping; while (p && p->xid && ((uid_t)p->xid != uid)) p = p->next; if (p && !p->xid) { /* * default pattern has been reached : * build an implicit SID according to pattern * (the pattern format was checked while reading * the mapping file) */ memcpy(defusid, p->sid, ntfs_sid_size(p->sid)); cnt = defusid->sub_authority_count; leauth = defusid->sub_authority[cnt-1]; uauth = le32_to_cpu(leauth) + 2*(uid & 0x3fffffff); defusid->sub_authority[cnt-1] = cpu_to_le32(uauth); if (uid & 0xc0000000) { leauth = defusid->sub_authority[cnt-2]; uauth = le32_to_cpu(leauth) + ((uid >> 30) & 3); defusid->sub_authority[cnt-2] = cpu_to_le32(uauth); } sid = defusid; } else sid = (p ? p->sid : (const SID*)NULL); } return (sid); } /* * Find Linux group mapped to a gsid * Returns 0 (root) if not found */ const SID *ntfs_find_gsid(const struct MAPPING* groupmapping, gid_t gid, SID *defgsid) { const struct MAPPING *p; const SID *sid; le32 leauth; u32 uauth; int cnt; if (!gid) sid = adminsid; else { p = groupmapping; while (p && p->xid && ((gid_t)p->xid != gid)) p = p->next; if (p && !p->xid) { /* * default pattern has been reached : * build an implicit SID according to pattern * (the pattern format was checked while reading * the mapping file) */ memcpy(defgsid, p->sid, ntfs_sid_size(p->sid)); cnt = defgsid->sub_authority_count; leauth = defgsid->sub_authority[cnt-1]; uauth = le32_to_cpu(leauth) + 2*(gid & 0x3fffffff) + 1; defgsid->sub_authority[cnt-1] = cpu_to_le32(uauth); if (gid & 0xc0000000) { leauth = defgsid->sub_authority[cnt-2]; uauth = le32_to_cpu(leauth) + ((gid >> 30) & 3); defgsid->sub_authority[cnt-2] = cpu_to_le32(uauth); } sid = defgsid; } else sid = (p ? p->sid : (const SID*)NULL); } return (sid); } /* * Find Linux owner mapped to a usid * Returns 0 (root) if not found */ uid_t ntfs_find_user(const struct MAPPING* usermapping, const SID *usid) { uid_t uid; const struct MAPPING *p; p = usermapping; while (p && p->xid && !ntfs_same_sid(usid, p->sid)) p = p->next; if (p && !p->xid) /* * No explicit mapping found, try implicit mapping */ uid = findimplicit(usid,p->sid,0); else uid = (p ? p->xid : 0); return (uid); } /* * Find Linux group mapped to a gsid * Returns 0 (root) if not found */ gid_t ntfs_find_group(const struct MAPPING* groupmapping, const SID * gsid) { gid_t gid; const struct MAPPING *p; p = groupmapping; while (p && p->xid && !ntfs_same_sid(gsid, p->sid)) p = p->next; if (p && !p->xid) /* * No explicit mapping found, try implicit mapping */ gid = findimplicit(gsid,p->sid,1); else gid = (p ? p->xid : 0); return (gid); } /* * Check the validity of the ACEs in a DACL or SACL */ static BOOL valid_acl(const ACL *pacl, unsigned int end) { const ACCESS_ALLOWED_ACE *pace; unsigned int offace; unsigned int acecnt; unsigned int acesz; unsigned int nace; BOOL ok; ok = TRUE; acecnt = le16_to_cpu(pacl->ace_count); offace = sizeof(ACL); for (nace = 0; (nace < acecnt) && ok; nace++) { /* be sure the beginning is within range */ if ((offace + sizeof(ACCESS_ALLOWED_ACE)) > end) ok = FALSE; else { pace = (const ACCESS_ALLOWED_ACE*) &((const char*)pacl)[offace]; acesz = le16_to_cpu(pace->size); if (((offace + acesz) > end) || !ntfs_valid_sid(&pace->sid) || ((ntfs_sid_size(&pace->sid) + 8) != (int)acesz)) ok = FALSE; offace += acesz; } } return (ok); } /* * Do sanity checks on security descriptors read from storage * basically, we make sure that every field holds within * allocated storage * Should not be called with a NULL argument * returns TRUE if considered safe * if not, error should be logged by caller */ BOOL ntfs_valid_descr(const char *securattr, unsigned int attrsz) { const SECURITY_DESCRIPTOR_RELATIVE *phead; const ACL *pdacl; const ACL *psacl; unsigned int offdacl; unsigned int offsacl; unsigned int offowner; unsigned int offgroup; BOOL ok; ok = TRUE; /* * first check overall size if within allocation range * and a DACL is present * and owner and group SID are valid */ phead = (const SECURITY_DESCRIPTOR_RELATIVE*)securattr; offdacl = le32_to_cpu(phead->dacl); offsacl = le32_to_cpu(phead->sacl); offowner = le32_to_cpu(phead->owner); offgroup = le32_to_cpu(phead->group); pdacl = (const ACL*)&securattr[offdacl]; psacl = (const ACL*)&securattr[offsacl]; /* * size check occurs before the above pointers are used * * "DR Watson" standard directory on WinXP has an * old revision and no DACL though SE_DACL_PRESENT is set */ if ((attrsz >= sizeof(SECURITY_DESCRIPTOR_RELATIVE)) && (phead->revision == SECURITY_DESCRIPTOR_REVISION) && (offowner >= sizeof(SECURITY_DESCRIPTOR_RELATIVE)) && ((offowner + 2) < attrsz) && (offgroup >= sizeof(SECURITY_DESCRIPTOR_RELATIVE)) && ((offgroup + 2) < attrsz) && (!offdacl || ((offdacl >= sizeof(SECURITY_DESCRIPTOR_RELATIVE)) && (offdacl+sizeof(ACL) < attrsz))) && (!offsacl || ((offsacl >= sizeof(SECURITY_DESCRIPTOR_RELATIVE)) && (offsacl+sizeof(ACL) < attrsz))) && !(phead->owner & const_cpu_to_le32(3)) && !(phead->group & const_cpu_to_le32(3)) && !(phead->dacl & const_cpu_to_le32(3)) && !(phead->sacl & const_cpu_to_le32(3)) && (ntfs_attr_size(securattr) <= attrsz) && ntfs_valid_sid((const SID*)&securattr[offowner]) && ntfs_valid_sid((const SID*)&securattr[offgroup]) /* * if there is an ACL, as indicated by offdacl, * require SE_DACL_PRESENT * but "Dr Watson" has SE_DACL_PRESENT though no DACL */ && (!offdacl || ((phead->control & SE_DACL_PRESENT) && ((pdacl->revision == ACL_REVISION) || (pdacl->revision == ACL_REVISION_DS)))) /* same for SACL */ && (!offsacl || ((phead->control & SE_SACL_PRESENT) && ((psacl->revision == ACL_REVISION) || (psacl->revision == ACL_REVISION_DS))))) { /* * Check the DACL and SACL if present */ if ((offdacl && !valid_acl(pdacl,attrsz - offdacl)) || (offsacl && !valid_acl(psacl,attrsz - offsacl))) ok = FALSE; } else ok = FALSE; return (ok); } /* * Copy the inheritable parts of an ACL * * Returns the size of the new ACL * or zero if nothing is inheritable */ int ntfs_inherit_acl(const ACL *oldacl, ACL *newacl, const SID *usid, const SID *gsid, BOOL fordir) { unsigned int src; unsigned int dst; int oldcnt; int newcnt; unsigned int selection; int nace; int acesz; int usidsz; int gsidsz; const ACCESS_ALLOWED_ACE *poldace; ACCESS_ALLOWED_ACE *pnewace; usidsz = ntfs_sid_size(usid); gsidsz = ntfs_sid_size(gsid); /* ACL header */ newacl->revision = ACL_REVISION; newacl->alignment1 = 0; newacl->alignment2 = const_cpu_to_le16(0); src = dst = sizeof(ACL); selection = (fordir ? CONTAINER_INHERIT_ACE : OBJECT_INHERIT_ACE); newcnt = 0; oldcnt = le16_to_cpu(oldacl->ace_count); for (nace = 0; nace < oldcnt; nace++) { poldace = (const ACCESS_ALLOWED_ACE*)((const char*)oldacl + src); acesz = le16_to_cpu(poldace->size); /* inheritance for access */ if (poldace->flags & selection) { pnewace = (ACCESS_ALLOWED_ACE*) ((char*)newacl + dst); memcpy(pnewace,poldace,acesz); /* * Replace generic creator-owner and * creator-group by owner and group */ if (ntfs_same_sid(&pnewace->sid, ownersid)) { memcpy(&pnewace->sid, usid, usidsz); acesz = usidsz + 8; pnewace->size = cpu_to_le16(acesz); } if (ntfs_same_sid(&pnewace->sid, groupsid)) { memcpy(&pnewace->sid, gsid, gsidsz); acesz = gsidsz + 8; pnewace->size = cpu_to_le16(acesz); } if (pnewace->mask & GENERIC_ALL) { pnewace->mask &= ~GENERIC_ALL; if (fordir) pnewace->mask |= OWNER_RIGHTS | DIR_READ | DIR_WRITE | DIR_EXEC; else /* * The last flag is not defined for a file, * however Windows sets it, so do the same */ pnewace->mask |= OWNER_RIGHTS | FILE_READ | FILE_WRITE | FILE_EXEC | cpu_to_le32(0x40); } /* remove inheritance flags */ pnewace->flags &= ~(OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE); dst += acesz; newcnt++; } /* inheritance for further inheritance */ if (fordir && (poldace->flags & (CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE))) { pnewace = (ACCESS_ALLOWED_ACE*) ((char*)newacl + dst); memcpy(pnewace,poldace,acesz); /* * Replace generic creator-owner and * creator-group by owner and group */ if (ntfs_same_sid(&pnewace->sid, ownersid)) { memcpy(&pnewace->sid, usid, usidsz); acesz = usidsz + 8; } if (ntfs_same_sid(&pnewace->sid, groupsid)) { memcpy(&pnewace->sid, gsid, gsidsz); acesz = gsidsz + 8; } dst += acesz; newcnt++; } src += acesz; } /* * Adjust header if something was inherited */ if (dst > sizeof(ACL)) { newacl->ace_count = cpu_to_le16(newcnt); newacl->size = cpu_to_le16(dst); } else dst = 0; return (dst); } #if POSIXACLS /* * Do sanity checks on a Posix descriptor * Should not be called with a NULL argument * returns TRUE if considered safe * if not, error should be logged by caller */ BOOL ntfs_valid_posix(const struct POSIX_SECURITY *pxdesc) { const struct POSIX_ACL *pacl; int i; BOOL ok; u16 tag; u32 id; int perms; struct { u16 previous; u32 previousid; u16 tagsset; mode_t mode; int owners; int groups; int others; } checks[2], *pchk; for (i=0; i<2; i++) { checks[i].mode = 0; checks[i].tagsset = 0; checks[i].owners = 0; checks[i].groups = 0; checks[i].others = 0; checks[i].previous = 0; checks[i].previousid = 0; } ok = TRUE; pacl = &pxdesc->acl; /* * header (strict for now) */ if ((pacl->version != POSIX_VERSION) || (pacl->flags != 0) || (pacl->filler != 0)) ok = FALSE; /* * Reject multiple owner, group or other * but do not require them to be present * Also check the ACEs are in correct order * which implies there is no duplicates */ for (i=0; iacccnt + pxdesc->defcnt; i++) { if (i >= pxdesc->firstdef) pchk = &checks[1]; else pchk = &checks[0]; perms = pacl->ace[i].perms; tag = pacl->ace[i].tag; pchk->tagsset |= tag; id = pacl->ace[i].id; if (perms & ~7) ok = FALSE; if ((tag < pchk->previous) || ((tag == pchk->previous) && (id <= pchk->previousid))) ok = FALSE; pchk->previous = tag; pchk->previousid = id; switch (tag) { case POSIX_ACL_USER_OBJ : if (pchk->owners++) ok = FALSE; if (id != (u32)-1) ok = FALSE; pchk->mode |= perms << 6; break; case POSIX_ACL_GROUP_OBJ : if (pchk->groups++) ok = FALSE; if (id != (u32)-1) ok = FALSE; pchk->mode = (pchk->mode & 07707) | (perms << 3); break; case POSIX_ACL_OTHER : if (pchk->others++) ok = FALSE; if (id != (u32)-1) ok = FALSE; pchk->mode |= perms; break; case POSIX_ACL_USER : case POSIX_ACL_GROUP : if (id == (u32)-1) ok = FALSE; break; case POSIX_ACL_MASK : if (id != (u32)-1) ok = FALSE; pchk->mode = (pchk->mode & 07707) | (perms << 3); break; default : ok = FALSE; break; } } if ((pxdesc->acccnt > 0) && ((checks[0].owners != 1) || (checks[0].groups != 1) || (checks[0].others != 1))) ok = FALSE; /* do not check owner, group or other are present in */ /* the default ACL, Windows does not necessarily set them */ /* descriptor */ if (pxdesc->defcnt && (pxdesc->acccnt > pxdesc->firstdef)) ok = FALSE; if ((pxdesc->acccnt < 0) || (pxdesc->defcnt < 0)) ok = FALSE; /* check mode, unless null or no tag set */ if (pxdesc->mode && checks[0].tagsset && (checks[0].mode != (pxdesc->mode & 0777))) ok = FALSE; /* check tagsset */ if (pxdesc->tagsset != checks[0].tagsset) ok = FALSE; return (ok); } /* * Set standard header data into a Posix ACL * The mode argument should provide the 3 upper bits of target mode */ static mode_t posix_header(struct POSIX_SECURITY *pxdesc, mode_t basemode) { mode_t mode; u16 tagsset; struct POSIX_ACE *pace; int i; mode = basemode & 07000; tagsset = 0; for (i=0; iacccnt; i++) { pace = &pxdesc->acl.ace[i]; tagsset |= pace->tag; switch(pace->tag) { case POSIX_ACL_USER_OBJ : mode |= (pace->perms & 7) << 6; break; case POSIX_ACL_GROUP_OBJ : case POSIX_ACL_MASK : mode = (mode & 07707) | ((pace->perms & 7) << 3); break; case POSIX_ACL_OTHER : mode |= pace->perms & 7; break; default : break; } } pxdesc->tagsset = tagsset; pxdesc->mode = mode; pxdesc->acl.version = POSIX_VERSION; pxdesc->acl.flags = 0; pxdesc->acl.filler = 0; return (mode); } /* * Sort ACEs in a Posix ACL * This is useful for always getting reusable converted ACLs, * it also helps in merging ACEs. * Repeated tag+id are allowed and not merged here. * * Tags should be in ascending sequence and for a repeatable tag * ids should be in ascending sequence. */ void ntfs_sort_posix(struct POSIX_SECURITY *pxdesc) { struct POSIX_ACL *pacl; struct POSIX_ACE ace; int i; int offs; BOOL done; u16 tag; u16 previous; u32 id; u32 previousid; /* * Check sequencing of tag+id in access ACE's */ pacl = &pxdesc->acl; do { done = TRUE; previous = pacl->ace[0].tag; previousid = pacl->ace[0].id; for (i=1; iacccnt; i++) { tag = pacl->ace[i].tag; id = pacl->ace[i].id; if ((tag < previous) || ((tag == previous) && (id < previousid))) { done = FALSE; memcpy(&ace,&pacl->ace[i-1],sizeof(struct POSIX_ACE)); memcpy(&pacl->ace[i-1],&pacl->ace[i],sizeof(struct POSIX_ACE)); memcpy(&pacl->ace[i],&ace,sizeof(struct POSIX_ACE)); } else { previous = tag; previousid = id; } } } while (!done); /* * Same for default ACEs */ do { done = TRUE; if ((pxdesc->defcnt) > 1) { offs = pxdesc->firstdef; previous = pacl->ace[offs].tag; previousid = pacl->ace[offs].id; for (i=offs+1; idefcnt; i++) { tag = pacl->ace[i].tag; id = pacl->ace[i].id; if ((tag < previous) || ((tag == previous) && (id < previousid))) { done = FALSE; memcpy(&ace,&pacl->ace[i-1],sizeof(struct POSIX_ACE)); memcpy(&pacl->ace[i-1],&pacl->ace[i],sizeof(struct POSIX_ACE)); memcpy(&pacl->ace[i],&ace,sizeof(struct POSIX_ACE)); } else { previous = tag; previousid = id; } } } } while (!done); } /* * Merge a new mode into a Posix descriptor * The Posix descriptor is not reallocated, its size is unchanged * * returns 0 if ok */ int ntfs_merge_mode_posix(struct POSIX_SECURITY *pxdesc, mode_t mode) { int i; BOOL maskfound; struct POSIX_ACE *pace; int todo; maskfound = FALSE; todo = POSIX_ACL_USER_OBJ | POSIX_ACL_GROUP_OBJ | POSIX_ACL_OTHER; for (i=pxdesc->acccnt-1; i>=0; i--) { pace = &pxdesc->acl.ace[i]; switch(pace->tag) { case POSIX_ACL_USER_OBJ : pace->perms = (mode >> 6) & 7; todo &= ~POSIX_ACL_USER_OBJ; break; case POSIX_ACL_GROUP_OBJ : if (!maskfound) pace->perms = (mode >> 3) & 7; todo &= ~POSIX_ACL_GROUP_OBJ; break; case POSIX_ACL_MASK : pace->perms = (mode >> 3) & 7; maskfound = TRUE; break; case POSIX_ACL_OTHER : pace->perms = mode & 7; todo &= ~POSIX_ACL_OTHER; break; default : break; } } pxdesc->mode = mode; return (todo ? -1 : 0); } /* * Replace an access or default Posix ACL * The resulting ACL is checked for validity * * Returns a new ACL or NULL if there is a problem */ struct POSIX_SECURITY *ntfs_replace_acl(const struct POSIX_SECURITY *oldpxdesc, const struct POSIX_ACL *newacl, int count, BOOL deflt) { struct POSIX_SECURITY *newpxdesc; size_t newsize; int offset; int oldoffset; int i; if (deflt) newsize = sizeof(struct POSIX_SECURITY) + (oldpxdesc->acccnt + count)*sizeof(struct POSIX_ACE); else newsize = sizeof(struct POSIX_SECURITY) + (oldpxdesc->defcnt + count)*sizeof(struct POSIX_ACE); newpxdesc = (struct POSIX_SECURITY*)ntfs_malloc(newsize); if (newpxdesc) { if (deflt) { offset = oldpxdesc->acccnt; newpxdesc->acccnt = oldpxdesc->acccnt; newpxdesc->defcnt = count; newpxdesc->firstdef = offset; /* copy access ACEs */ for (i=0; iacccnt; i++) newpxdesc->acl.ace[i] = oldpxdesc->acl.ace[i]; /* copy default ACEs */ for (i=0; iacl.ace[i + offset] = newacl->ace[i]; } else { offset = count; newpxdesc->acccnt = count; newpxdesc->defcnt = oldpxdesc->defcnt; newpxdesc->firstdef = count; /* copy access ACEs */ for (i=0; iacl.ace[i] = newacl->ace[i]; /* copy default ACEs */ oldoffset = oldpxdesc->firstdef; for (i=0; idefcnt; i++) newpxdesc->acl.ace[i + offset] = oldpxdesc->acl.ace[i + oldoffset]; } /* assume special flags unchanged */ posix_header(newpxdesc, oldpxdesc->mode); if (!ntfs_valid_posix(newpxdesc)) { /* do not log, this is an application error */ free(newpxdesc); newpxdesc = (struct POSIX_SECURITY*)NULL; errno = EINVAL; } } else errno = ENOMEM; return (newpxdesc); } /* * Build an inherited Posix descriptor from parent * descriptor (if any) restricted to creation mode * * Returns the inherited descriptor or NULL if there is a problem */ struct POSIX_SECURITY *ntfs_build_inherited_posix( const struct POSIX_SECURITY *pxdesc, mode_t mode, mode_t mask, BOOL isdir) { struct POSIX_SECURITY *pydesc; struct POSIX_ACE *pyace; int count; int defcnt; int size; int i; s16 tagsset; if (pxdesc && pxdesc->defcnt) { if (isdir) count = 2*pxdesc->defcnt + 3; else count = pxdesc->defcnt + 3; } else count = 3; pydesc = (struct POSIX_SECURITY*)ntfs_malloc( sizeof(struct POSIX_SECURITY) + count*sizeof(struct POSIX_ACE)); if (pydesc) { /* * Copy inherited tags and adapt perms * Use requested mode, ignoring umask * (not possible with older versions of fuse) */ tagsset = 0; defcnt = (pxdesc ? pxdesc->defcnt : 0); for (i=defcnt-1; i>=0; i--) { pyace = &pydesc->acl.ace[i]; *pyace = pxdesc->acl.ace[pxdesc->firstdef + i]; switch (pyace->tag) { case POSIX_ACL_USER_OBJ : pyace->perms &= (mode >> 6) & 7; break; case POSIX_ACL_GROUP_OBJ : if (!(tagsset & POSIX_ACL_MASK)) pyace->perms &= (mode >> 3) & 7; break; case POSIX_ACL_OTHER : pyace->perms &= mode & 7; break; case POSIX_ACL_MASK : pyace->perms &= (mode >> 3) & 7; break; default : break; } tagsset |= pyace->tag; } pydesc->acccnt = defcnt; /* * If some standard tags were missing, append them from mode * and sort the list * Here we have to use the umask'ed mode */ if (~tagsset & (POSIX_ACL_USER_OBJ | POSIX_ACL_GROUP_OBJ | POSIX_ACL_OTHER)) { i = defcnt; /* owner was missing */ if (!(tagsset & POSIX_ACL_USER_OBJ)) { pyace = &pydesc->acl.ace[i]; pyace->tag = POSIX_ACL_USER_OBJ; pyace->id = -1; pyace->perms = ((mode & ~mask) >> 6) & 7; tagsset |= POSIX_ACL_USER_OBJ; i++; } /* owning group was missing */ if (!(tagsset & POSIX_ACL_GROUP_OBJ)) { pyace = &pydesc->acl.ace[i]; pyace->tag = POSIX_ACL_GROUP_OBJ; pyace->id = -1; pyace->perms = ((mode & ~mask) >> 3) & 7; tagsset |= POSIX_ACL_GROUP_OBJ; i++; } /* other was missing */ if (!(tagsset & POSIX_ACL_OTHER)) { pyace = &pydesc->acl.ace[i]; pyace->tag = POSIX_ACL_OTHER; pyace->id = -1; pyace->perms = mode & ~mask & 7; tagsset |= POSIX_ACL_OTHER; i++; } pydesc->acccnt = i; pydesc->firstdef = i; pydesc->defcnt = 0; ntfs_sort_posix(pydesc); } /* * append as a default ACL if a directory */ pydesc->firstdef = pydesc->acccnt; if (defcnt && isdir) { size = sizeof(struct POSIX_ACE)*defcnt; memcpy(&pydesc->acl.ace[pydesc->firstdef], &pxdesc->acl.ace[pxdesc->firstdef],size); pydesc->defcnt = defcnt; } else { pydesc->defcnt = 0; } /* assume special bits are not inherited */ posix_header(pydesc, mode & 07000); if (!ntfs_valid_posix(pydesc)) { ntfs_log_error("Error building an inherited Posix desc\n"); errno = EIO; free(pydesc); pydesc = (struct POSIX_SECURITY*)NULL; } } else errno = ENOMEM; return (pydesc); } static int merge_lists_posix(struct POSIX_ACE *targetace, const struct POSIX_ACE *firstace, const struct POSIX_ACE *secondace, int firstcnt, int secondcnt) { int k; k = 0; /* * No list is exhausted : * if same tag+id in both list : * ignore ACE from second list * else take the one with smaller tag+id */ while ((firstcnt > 0) && (secondcnt > 0)) if ((firstace->tag == secondace->tag) && (firstace->id == secondace->id)) { secondace++; secondcnt--; } else if ((firstace->tag < secondace->tag) || ((firstace->tag == secondace->tag) && (firstace->id < secondace->id))) { targetace->tag = firstace->tag; targetace->id = firstace->id; targetace->perms = firstace->perms; firstace++; targetace++; firstcnt--; k++; } else { targetace->tag = secondace->tag; targetace->id = secondace->id; targetace->perms = secondace->perms; secondace++; targetace++; secondcnt--; k++; } /* * One list is exhausted, copy the other one */ while (firstcnt > 0) { targetace->tag = firstace->tag; targetace->id = firstace->id; targetace->perms = firstace->perms; firstace++; targetace++; firstcnt--; k++; } while (secondcnt > 0) { targetace->tag = secondace->tag; targetace->id = secondace->id; targetace->perms = secondace->perms; secondace++; targetace++; secondcnt--; k++; } return (k); } /* * Merge two Posix ACLs * The input ACLs have to be adequately sorted * * Returns the merged ACL, which is allocated and has to be freed by caller, * or NULL if failed */ struct POSIX_SECURITY *ntfs_merge_descr_posix(const struct POSIX_SECURITY *first, const struct POSIX_SECURITY *second) { struct POSIX_SECURITY *pxdesc; struct POSIX_ACE *targetace; const struct POSIX_ACE *firstace; const struct POSIX_ACE *secondace; size_t size; int k; size = sizeof(struct POSIX_SECURITY) + (first->acccnt + first->defcnt + second->acccnt + second->defcnt)*sizeof(struct POSIX_ACE); pxdesc = (struct POSIX_SECURITY*)ntfs_malloc(size); if (pxdesc) { /* * merge access ACEs */ firstace = first->acl.ace; secondace = second->acl.ace; targetace = pxdesc->acl.ace; k = merge_lists_posix(targetace,firstace,secondace, first->acccnt,second->acccnt); pxdesc->acccnt = k; /* * merge default ACEs */ pxdesc->firstdef = k; firstace = &first->acl.ace[first->firstdef]; secondace = &second->acl.ace[second->firstdef]; targetace = &pxdesc->acl.ace[k]; k = merge_lists_posix(targetace,firstace,secondace, first->defcnt,second->defcnt); pxdesc->defcnt = k; /* * build header */ pxdesc->acl.version = POSIX_VERSION; pxdesc->acl.flags = 0; pxdesc->acl.filler = 0; pxdesc->mode = 0; pxdesc->tagsset = 0; } else errno = ENOMEM; return (pxdesc); } struct BUILD_CONTEXT { BOOL isdir; BOOL adminowns; BOOL groupowns; u16 selfuserperms; u16 selfgrpperms; u16 grpperms; u16 othperms; u16 mask; u16 designates; u16 withmask; u16 rootspecial; } ; static BOOL build_user_denials(ACL *pacl, const SID *usid, struct MAPPING* const mapping[], ACE_FLAGS flags, const struct POSIX_ACE *pxace, struct BUILD_CONTEXT *pset) { BIGSID defsid; ACCESS_ALLOWED_ACE *pdace; const SID *sid; int sidsz; int pos; int acecnt; le32 grants; le32 denials; u16 perms; u16 mixperms; u16 tag; BOOL rejected; BOOL rootuser; BOOL avoidmask; rejected = FALSE; tag = pxace->tag; perms = pxace->perms; rootuser = FALSE; pos = le16_to_cpu(pacl->size); acecnt = le16_to_cpu(pacl->ace_count); avoidmask = (pset->mask == (POSIX_PERM_R | POSIX_PERM_W | POSIX_PERM_X)) && ((pset->designates && pset->withmask) || (!pset->designates && !pset->withmask)); if (tag == POSIX_ACL_USER_OBJ) { sid = usid; sidsz = ntfs_sid_size(sid); grants = OWNER_RIGHTS; } else { if (pxace->id) { sid = NTFS_FIND_USID(mapping[MAPUSERS], pxace->id, (SID*)&defsid); grants = WORLD_RIGHTS; } else { sid = adminsid; rootuser = TRUE; grants = WORLD_RIGHTS & ~ROOT_OWNER_UNMARK; } if (sid) { sidsz = ntfs_sid_size(sid); /* * Insert denial of complement of mask for * each designated user (except root) * WRITE_OWNER is inserted so that * the mask can be identified */ if (!avoidmask && !rootuser) { denials = WRITE_OWNER; pdace = (ACCESS_DENIED_ACE*)&((char*)pacl)[pos]; if (pset->isdir) { if (!(pset->mask & POSIX_PERM_X)) denials |= DIR_EXEC; if (!(pset->mask & POSIX_PERM_W)) denials |= DIR_WRITE; if (!(pset->mask & POSIX_PERM_R)) denials |= DIR_READ; } else { if (!(pset->mask & POSIX_PERM_X)) denials |= FILE_EXEC; if (!(pset->mask & POSIX_PERM_W)) denials |= FILE_WRITE; if (!(pset->mask & POSIX_PERM_R)) denials |= FILE_READ; } if (rootuser) grants &= ~ROOT_OWNER_UNMARK; pdace->type = ACCESS_DENIED_ACE_TYPE; pdace->flags = flags; pdace->size = cpu_to_le16(sidsz + 8); pdace->mask = denials; memcpy((char*)&pdace->sid, sid, sidsz); pos += sidsz + 8; acecnt++; } } else rejected = TRUE; } if (!rejected) { if (pset->isdir) { if (perms & POSIX_PERM_X) grants |= DIR_EXEC; if (perms & POSIX_PERM_W) grants |= DIR_WRITE; if (perms & POSIX_PERM_R) grants |= DIR_READ; } else { if (perms & POSIX_PERM_X) grants |= FILE_EXEC; if (perms & POSIX_PERM_W) grants |= FILE_WRITE; if (perms & POSIX_PERM_R) grants |= FILE_READ; } /* a possible ACE to deny owner what he/she would */ /* induely get from administrator, group or world */ /* unless owner is administrator or group */ denials = const_cpu_to_le32(0); pdace = (ACCESS_DENIED_ACE*)&((char*)pacl)[pos]; if (!pset->adminowns && !rootuser) { if (!pset->groupowns) { mixperms = pset->grpperms | pset->othperms; if (tag == POSIX_ACL_USER_OBJ) mixperms |= pset->selfuserperms; if (pset->isdir) { if (mixperms & POSIX_PERM_X) denials |= DIR_EXEC; if (mixperms & POSIX_PERM_W) denials |= DIR_WRITE; if (mixperms & POSIX_PERM_R) denials |= DIR_READ; } else { if (mixperms & POSIX_PERM_X) denials |= FILE_EXEC; if (mixperms & POSIX_PERM_W) denials |= FILE_WRITE; if (mixperms & POSIX_PERM_R) denials |= FILE_READ; } } else { mixperms = ~pset->grpperms & pset->othperms; if (tag == POSIX_ACL_USER_OBJ) mixperms |= pset->selfuserperms; if (pset->isdir) { if (mixperms & POSIX_PERM_X) denials |= DIR_EXEC; if (mixperms & POSIX_PERM_W) denials |= DIR_WRITE; if (mixperms & POSIX_PERM_R) denials |= DIR_READ; } else { if (mixperms & POSIX_PERM_X) denials |= FILE_EXEC; if (mixperms & POSIX_PERM_W) denials |= FILE_WRITE; if (mixperms & POSIX_PERM_R) denials |= FILE_READ; } } denials &= ~grants; if (denials) { pdace->type = ACCESS_DENIED_ACE_TYPE; pdace->flags = flags; pdace->size = cpu_to_le16(sidsz + 8); pdace->mask = denials; memcpy((char*)&pdace->sid, sid, sidsz); pos += sidsz + 8; acecnt++; } } } pacl->size = cpu_to_le16(pos); pacl->ace_count = cpu_to_le16(acecnt); return (!rejected); } static BOOL build_user_grants(ACL *pacl, const SID *usid, struct MAPPING* const mapping[], ACE_FLAGS flags, const struct POSIX_ACE *pxace, struct BUILD_CONTEXT *pset) { BIGSID defsid; ACCESS_ALLOWED_ACE *pgace; const SID *sid; int sidsz; int pos; int acecnt; le32 grants; u16 perms; u16 tag; BOOL rejected; BOOL rootuser; rejected = FALSE; tag = pxace->tag; perms = pxace->perms; rootuser = FALSE; pos = le16_to_cpu(pacl->size); acecnt = le16_to_cpu(pacl->ace_count); if (tag == POSIX_ACL_USER_OBJ) { sid = usid; sidsz = ntfs_sid_size(sid); grants = OWNER_RIGHTS; } else { if (pxace->id) { sid = NTFS_FIND_USID(mapping[MAPUSERS], pxace->id, (SID*)&defsid); if (sid) sidsz = ntfs_sid_size(sid); else rejected = TRUE; grants = WORLD_RIGHTS; } else { sid = adminsid; sidsz = ntfs_sid_size(sid); rootuser = TRUE; grants = WORLD_RIGHTS & ~ROOT_OWNER_UNMARK; } } if (!rejected) { if (pset->isdir) { if (perms & POSIX_PERM_X) grants |= DIR_EXEC; if (perms & POSIX_PERM_W) grants |= DIR_WRITE; if (perms & POSIX_PERM_R) grants |= DIR_READ; } else { if (perms & POSIX_PERM_X) grants |= FILE_EXEC; if (perms & POSIX_PERM_W) grants |= FILE_WRITE; if (perms & POSIX_PERM_R) grants |= FILE_READ; } if (rootuser) grants &= ~ROOT_OWNER_UNMARK; pgace = (ACCESS_DENIED_ACE*)&((char*)pacl)[pos]; pgace->type = ACCESS_ALLOWED_ACE_TYPE; pgace->size = cpu_to_le16(sidsz + 8); pgace->flags = flags; pgace->mask = grants; memcpy((char*)&pgace->sid, sid, sidsz); pos += sidsz + 8; acecnt = le16_to_cpu(pacl->ace_count) + 1; pacl->ace_count = cpu_to_le16(acecnt); pacl->size = cpu_to_le16(pos); } return (!rejected); } /* a grant ACE for group */ /* unless group-obj has the same rights as world */ /* but present if group is owner or owner is administrator */ /* this ACE will be inserted after denials for group */ static BOOL build_group_denials_grant(ACL *pacl, const SID *gsid, struct MAPPING* const mapping[], ACE_FLAGS flags, const struct POSIX_ACE *pxace, struct BUILD_CONTEXT *pset) { BIGSID defsid; ACCESS_ALLOWED_ACE *pdace; ACCESS_ALLOWED_ACE *pgace; const SID *sid; int sidsz; int pos; int acecnt; le32 grants; le32 denials; u16 perms; u16 mixperms; u16 tag; BOOL avoidmask; BOOL rootgroup; BOOL rejected; rejected = FALSE; tag = pxace->tag; perms = pxace->perms; pos = le16_to_cpu(pacl->size); acecnt = le16_to_cpu(pacl->ace_count); rootgroup = FALSE; avoidmask = (pset->mask == (POSIX_PERM_R | POSIX_PERM_W | POSIX_PERM_X)) && ((pset->designates && pset->withmask) || (!pset->designates && !pset->withmask)); if (tag == POSIX_ACL_GROUP_OBJ) sid = gsid; else if (pxace->id) sid = NTFS_FIND_GSID(mapping[MAPGROUPS], pxace->id, (SID*)&defsid); else { sid = adminsid; rootgroup = TRUE; } if (sid) { sidsz = ntfs_sid_size(sid); /* * Insert denial of complement of mask for * each group * WRITE_OWNER is inserted so that * the mask can be identified * Note : this mask may lead on Windows to * deny rights to administrators belonging * to some user group */ if ((!avoidmask && !rootgroup) || (pset->rootspecial && (tag == POSIX_ACL_GROUP_OBJ))) { denials = WRITE_OWNER; pdace = (ACCESS_DENIED_ACE*)&((char*)pacl)[pos]; if (pset->isdir) { if (!(pset->mask & POSIX_PERM_X)) denials |= DIR_EXEC; if (!(pset->mask & POSIX_PERM_W)) denials |= DIR_WRITE; if (!(pset->mask & POSIX_PERM_R)) denials |= DIR_READ; } else { if (!(pset->mask & POSIX_PERM_X)) denials |= FILE_EXEC; if (!(pset->mask & POSIX_PERM_W)) denials |= FILE_WRITE; if (!(pset->mask & POSIX_PERM_R)) denials |= FILE_READ; } pdace->type = ACCESS_DENIED_ACE_TYPE; pdace->flags = flags; pdace->size = cpu_to_le16(sidsz + 8); pdace->mask = denials; memcpy((char*)&pdace->sid, sid, sidsz); pos += sidsz + 8; acecnt++; } } else rejected = TRUE; if (!rejected && (pset->adminowns || pset->groupowns || avoidmask || rootgroup || (perms != pset->othperms))) { grants = WORLD_RIGHTS; if (rootgroup) grants &= ~ROOT_GROUP_UNMARK; if (pset->isdir) { if (perms & POSIX_PERM_X) grants |= DIR_EXEC; if (perms & POSIX_PERM_W) grants |= DIR_WRITE; if (perms & POSIX_PERM_R) grants |= DIR_READ; } else { if (perms & POSIX_PERM_X) grants |= FILE_EXEC; if (perms & POSIX_PERM_W) grants |= FILE_WRITE; if (perms & POSIX_PERM_R) grants |= FILE_READ; } /* a possible ACE to deny group what it would get from world */ /* or administrator, unless owner is administrator or group */ denials = const_cpu_to_le32(0); pdace = (ACCESS_DENIED_ACE*)&((char*)pacl)[pos]; if (!pset->adminowns && !pset->groupowns && !rootgroup) { mixperms = pset->othperms; if (tag == POSIX_ACL_GROUP_OBJ) mixperms |= pset->selfgrpperms; if (pset->isdir) { if (mixperms & POSIX_PERM_X) denials |= DIR_EXEC; if (mixperms & POSIX_PERM_W) denials |= DIR_WRITE; if (mixperms & POSIX_PERM_R) denials |= DIR_READ; } else { if (mixperms & POSIX_PERM_X) denials |= FILE_EXEC; if (mixperms & POSIX_PERM_W) denials |= FILE_WRITE; if (mixperms & POSIX_PERM_R) denials |= FILE_READ; } denials &= ~(grants | OWNER_RIGHTS); if (denials) { pdace->type = ACCESS_DENIED_ACE_TYPE; pdace->flags = flags; pdace->size = cpu_to_le16(sidsz + 8); pdace->mask = denials; memcpy((char*)&pdace->sid, sid, sidsz); pos += sidsz + 8; acecnt++; } } /* now insert grants to group if more than world */ if (pset->adminowns || pset->groupowns || (avoidmask && (pset->designates || pset->withmask)) || (perms & ~pset->othperms) || (pset->rootspecial && (tag == POSIX_ACL_GROUP_OBJ)) || (tag == POSIX_ACL_GROUP)) { if (rootgroup) grants &= ~ROOT_GROUP_UNMARK; pgace = (ACCESS_DENIED_ACE*)&((char*)pacl)[pos]; pgace->type = ACCESS_ALLOWED_ACE_TYPE; pgace->flags = flags; pgace->size = cpu_to_le16(sidsz + 8); pgace->mask = grants; memcpy((char*)&pgace->sid, sid, sidsz); pos += sidsz + 8; acecnt++; } } pacl->size = cpu_to_le16(pos); pacl->ace_count = cpu_to_le16(acecnt); return (!rejected); } /* * Build an ACL composed of several ACE's * returns size of ACL or zero if failed * * Three schemes are defined : * * 1) if root is neither owner nor group up to 7 ACE's are set up : * - denials to owner (preventing grants to world or group to apply) * + mask denials to designated user (unless mask allows all) * + denials to designated user * - grants to owner (always present - first grant) * + grants to designated user * + mask denial to group (unless mask allows all) * - denials to group (preventing grants to world to apply) * - grants to group (unless group has no more than world rights) * + mask denials to designated group (unless mask allows all) * + grants to designated group * + denials to designated group * - grants to world (unless none) * - full privileges to administrator, always present * - full privileges to system, always present * * The same scheme is applied for Posix ACLs, with the mask represented * as denials prepended to grants for designated users and groups * * This is inspired by an Internet Draft from Marius Aamodt Eriksen * for mapping NFSv4 ACLs to Posix ACLs (draft-ietf-nfsv4-acl-mapping-00.txt) * More recent versions of the draft (draft-ietf-nfsv4-acl-mapping-05.txt) * are not followed, as they ignore the Posix mask and lead to * loss of compatibility with Linux implementations on other fs. * * Note that denials to group are located after grants to owner. * This only occurs in the unfrequent situation where world * has more rights than group and cannot be avoided if owner and other * have some common right which is denied to group (eg for mode 745 * executing has to be denied to group, but not to owner or world). * This rare situation is processed by Windows correctly, but * Windows utilities may want to change the order, with a * consequence of applying the group denials to the Windows owner. * The interpretation on Linux is not affected by the order change. * * 2) if root is either owner or group, two problems arise : * - granting full rights to administrator (as needed to transpose * to Windows rights bypassing granting to root) would imply * Linux permissions to always be seen as rwx, no matter the chmod * - there is no different SID to separate an administrator owner * from an administrator group. Hence Linux permissions for owner * would always be similar to permissions to group. * * as a work-around, up to 5 ACE's are set up if owner or group : * - grants to owner, always present at first position * - grants to group, always present * - grants to world, unless none * - full privileges to administrator, always present * - full privileges to system, always present * * On Windows, these ACE's are processed normally, though they * are redundant (owner, group and administrator are the same, * as a consequence any denials would damage administrator rights) * but on Linux, privileges to administrator are ignored (they * are not needed as root has always full privileges), and * neither grants to group are applied to owner, nor grants to * world are applied to owner or group. * * 3) finally a similar situation arises when group is owner (they * have the same SID), but is not root. * In this situation up to 6 ACE's are set up : * * - denials to owner (preventing grants to world to apply) * - grants to owner (always present) * - grants to group (unless groups has same rights as world) * - grants to world (unless none) * - full privileges to administrator, always present * - full privileges to system, always present * * On Windows, these ACE's are processed normally, though they * are redundant (as owner and group are the same), but this has * no impact on administrator rights * * Special flags (S_ISVTX, S_ISGID, S_ISUID) : * an extra null ACE is inserted to hold these flags, using * the same conventions as cygwin. * */ static int buildacls_posix(struct MAPPING* const mapping[], char *secattr, int offs, const struct POSIX_SECURITY *pxdesc, int isdir, const SID *usid, const SID *gsid) { struct BUILD_CONTEXT aceset[2], *pset; BOOL adminowns; BOOL groupowns; ACL *pacl; ACCESS_ALLOWED_ACE *pgace; ACCESS_ALLOWED_ACE *pdace; const struct POSIX_ACE *pxace; BOOL ok; mode_t mode; u16 tag; u16 perms; ACE_FLAGS flags; int pos; int i; int k; BIGSID defsid; const SID *sid; int acecnt; int usidsz; int wsidsz; int asidsz; int ssidsz; int nsidsz; le32 grants; usidsz = ntfs_sid_size(usid); wsidsz = ntfs_sid_size(worldsid); asidsz = ntfs_sid_size(adminsid); ssidsz = ntfs_sid_size(systemsid); mode = pxdesc->mode; /* adminowns and groupowns are used for both lists */ adminowns = ntfs_same_sid(usid, adminsid) || ntfs_same_sid(gsid, adminsid); groupowns = !adminowns && ntfs_same_sid(usid, gsid); ok = TRUE; /* ACL header */ pacl = (ACL*)&secattr[offs]; pacl->revision = ACL_REVISION; pacl->alignment1 = 0; pacl->size = cpu_to_le16(sizeof(ACL) + usidsz + 8); pacl->ace_count = const_cpu_to_le16(0); pacl->alignment2 = const_cpu_to_le16(0); /* * Determine what is allowed to some group or world * to prevent designated users or other groups to get * rights from groups or world * Do the same if owner and group appear as designated * user or group * Also get global mask */ for (k=0; k<2; k++) { pset = &aceset[k]; pset->selfuserperms = 0; pset->selfgrpperms = 0; pset->grpperms = 0; pset->othperms = 0; pset->mask = (POSIX_PERM_R | POSIX_PERM_W | POSIX_PERM_X); pset->designates = 0; pset->withmask = 0; pset->rootspecial = 0; pset->adminowns = adminowns; pset->groupowns = groupowns; pset->isdir = isdir; } for (i=pxdesc->acccnt+pxdesc->defcnt-1; i>=0; i--) { if (i >= pxdesc->acccnt) { pset = &aceset[1]; pxace = &pxdesc->acl.ace[i + pxdesc->firstdef - pxdesc->acccnt]; } else { pset = &aceset[0]; pxace = &pxdesc->acl.ace[i]; } switch (pxace->tag) { case POSIX_ACL_USER : pset->designates++; if (pxace->id) { sid = NTFS_FIND_USID(mapping[MAPUSERS], pxace->id, (SID*)&defsid); if (sid && ntfs_same_sid(sid,usid)) pset->selfuserperms |= pxace->perms; } else /* root as designated user is processed apart */ pset->rootspecial = TRUE; break; case POSIX_ACL_GROUP : pset->designates++; if (pxace->id) { sid = NTFS_FIND_GSID(mapping[MAPUSERS], pxace->id, (SID*)&defsid); if (sid && ntfs_same_sid(sid,gsid)) pset->selfgrpperms |= pxace->perms; } else /* root as designated group is processed apart */ pset->rootspecial = TRUE; /* fall through */ case POSIX_ACL_GROUP_OBJ : pset->grpperms |= pxace->perms; break; case POSIX_ACL_OTHER : pset->othperms = pxace->perms; break; case POSIX_ACL_MASK : pset->withmask++; pset->mask = pxace->perms; default : break; } } if (pxdesc->defcnt && (pxdesc->firstdef != pxdesc->acccnt)) { ntfs_log_error("** error : access and default not consecutive\n"); return (0); } /* * First insert all denials for owner and each * designated user (with mask if needed) */ pacl->ace_count = const_cpu_to_le16(0); pacl->size = const_cpu_to_le16(sizeof(ACL)); for (i=0; (i<(pxdesc->acccnt + pxdesc->defcnt)) && ok; i++) { if (i >= pxdesc->acccnt) { flags = INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE; pset = &aceset[1]; pxace = &pxdesc->acl.ace[i + pxdesc->firstdef - pxdesc->acccnt]; } else { if (pxdesc->defcnt) flags = NO_PROPAGATE_INHERIT_ACE; else flags = (isdir ? DIR_INHERITANCE : FILE_INHERITANCE); pset = &aceset[0]; pxace = &pxdesc->acl.ace[i]; } tag = pxace->tag; perms = pxace->perms; switch (tag) { /* insert denial ACEs for each owner or allowed user */ case POSIX_ACL_USER : case POSIX_ACL_USER_OBJ : ok = build_user_denials(pacl, usid, mapping, flags, pxace, pset); break; default : break; } } /* * for directories, insert a world execution denial * inherited to plain files. * This is to prevent Windows from granting execution * of files through inheritance from parent directory */ if (isdir && ok) { pos = le16_to_cpu(pacl->size); pdace = (ACCESS_DENIED_ACE*) &secattr[offs + pos]; pdace->type = ACCESS_DENIED_ACE_TYPE; pdace->flags = INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE; pdace->size = cpu_to_le16(wsidsz + 8); pdace->mask = FILE_EXEC; memcpy((char*)&pdace->sid, worldsid, wsidsz); pos += wsidsz + 8; acecnt = le16_to_cpu(pacl->ace_count) + 1; pacl->ace_count = cpu_to_le16(acecnt); pacl->size = cpu_to_le16(pos); } /* * now insert (if needed) * - grants to owner and designated users * - mask and denials for all groups * - grants to other */ for (i=0; (i<(pxdesc->acccnt + pxdesc->defcnt)) && ok; i++) { if (i >= pxdesc->acccnt) { flags = INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE; pset = &aceset[1]; pxace = &pxdesc->acl.ace[i + pxdesc->firstdef - pxdesc->acccnt]; } else { if (pxdesc->defcnt) flags = NO_PROPAGATE_INHERIT_ACE; else flags = (isdir ? DIR_INHERITANCE : FILE_INHERITANCE); pset = &aceset[0]; pxace = &pxdesc->acl.ace[i]; } tag = pxace->tag; perms = pxace->perms; switch (tag) { /* ACE for each owner or allowed user */ case POSIX_ACL_USER : case POSIX_ACL_USER_OBJ : ok = build_user_grants(pacl,usid, mapping,flags,pxace,pset); break; case POSIX_ACL_GROUP : case POSIX_ACL_GROUP_OBJ : /* denials and grants for groups */ ok = build_group_denials_grant(pacl,gsid, mapping,flags,pxace,pset); break; case POSIX_ACL_OTHER : /* grants for other users */ pos = le16_to_cpu(pacl->size); pgace = (ACCESS_ALLOWED_ACE*)&secattr[offs + pos]; grants = WORLD_RIGHTS; if (isdir) { if (perms & POSIX_PERM_X) grants |= DIR_EXEC; if (perms & POSIX_PERM_W) grants |= DIR_WRITE; if (perms & POSIX_PERM_R) grants |= DIR_READ; } else { if (perms & POSIX_PERM_X) grants |= FILE_EXEC; if (perms & POSIX_PERM_W) grants |= FILE_WRITE; if (perms & POSIX_PERM_R) grants |= FILE_READ; } pgace->type = ACCESS_ALLOWED_ACE_TYPE; pgace->flags = flags; pgace->size = cpu_to_le16(wsidsz + 8); pgace->mask = grants; memcpy((char*)&pgace->sid, worldsid, wsidsz); pos += wsidsz + 8; acecnt = le16_to_cpu(pacl->ace_count) + 1; pacl->ace_count = cpu_to_le16(acecnt); pacl->size = cpu_to_le16(pos); break; } } if (!ok) { errno = EINVAL; pos = 0; } else { /* an ACE for administrators */ /* always full access */ pos = le16_to_cpu(pacl->size); acecnt = le16_to_cpu(pacl->ace_count); if (isdir) flags = OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE; else flags = NO_PROPAGATE_INHERIT_ACE; pgace = (ACCESS_ALLOWED_ACE*)&secattr[offs + pos]; pgace->type = ACCESS_ALLOWED_ACE_TYPE; pgace->flags = flags; pgace->size = cpu_to_le16(asidsz + 8); grants = OWNER_RIGHTS | FILE_READ | FILE_WRITE | FILE_EXEC; pgace->mask = grants; memcpy((char*)&pgace->sid, adminsid, asidsz); pos += asidsz + 8; acecnt++; /* an ACE for system (needed ?) */ /* always full access */ pgace = (ACCESS_ALLOWED_ACE*)&secattr[offs + pos]; pgace->type = ACCESS_ALLOWED_ACE_TYPE; pgace->flags = flags; pgace->size = cpu_to_le16(ssidsz + 8); grants = OWNER_RIGHTS | FILE_READ | FILE_WRITE | FILE_EXEC; pgace->mask = grants; memcpy((char*)&pgace->sid, systemsid, ssidsz); pos += ssidsz + 8; acecnt++; /* a null ACE to hold special flags */ /* using the same representation as cygwin */ if (mode & (S_ISVTX | S_ISGID | S_ISUID)) { nsidsz = ntfs_sid_size(nullsid); pgace = (ACCESS_ALLOWED_ACE*)&secattr[offs + pos]; pgace->type = ACCESS_ALLOWED_ACE_TYPE; pgace->flags = NO_PROPAGATE_INHERIT_ACE; pgace->size = cpu_to_le16(nsidsz + 8); grants = const_cpu_to_le32(0); if (mode & S_ISUID) grants |= FILE_APPEND_DATA; if (mode & S_ISGID) grants |= FILE_WRITE_DATA; if (mode & S_ISVTX) grants |= FILE_READ_DATA; pgace->mask = grants; memcpy((char*)&pgace->sid, nullsid, nsidsz); pos += nsidsz + 8; acecnt++; } /* fix ACL header */ pacl->size = cpu_to_le16(pos); pacl->ace_count = cpu_to_le16(acecnt); } return (ok ? pos : 0); } #endif /* POSIXACLS */ static int buildacls(char *secattr, int offs, mode_t mode, int isdir, const SID * usid, const SID * gsid) { ACL *pacl; ACCESS_ALLOWED_ACE *pgace; ACCESS_ALLOWED_ACE *pdace; BOOL adminowns; BOOL groupowns; ACE_FLAGS gflags; int pos; int acecnt; int usidsz; int gsidsz; int wsidsz; int asidsz; int ssidsz; int nsidsz; le32 grants; le32 denials; usidsz = ntfs_sid_size(usid); gsidsz = ntfs_sid_size(gsid); wsidsz = ntfs_sid_size(worldsid); asidsz = ntfs_sid_size(adminsid); ssidsz = ntfs_sid_size(systemsid); adminowns = ntfs_same_sid(usid, adminsid) || ntfs_same_sid(gsid, adminsid); groupowns = !adminowns && ntfs_same_sid(usid, gsid); /* ACL header */ pacl = (ACL*)&secattr[offs]; pacl->revision = ACL_REVISION; pacl->alignment1 = 0; pacl->size = cpu_to_le16(sizeof(ACL) + usidsz + 8); pacl->ace_count = const_cpu_to_le16(1); pacl->alignment2 = const_cpu_to_le16(0); pos = sizeof(ACL); acecnt = 0; /* compute a grant ACE for owner */ /* this ACE will be inserted after denial for owner */ grants = OWNER_RIGHTS; if (isdir) { gflags = DIR_INHERITANCE; if (mode & S_IXUSR) grants |= DIR_EXEC; if (mode & S_IWUSR) grants |= DIR_WRITE; if (mode & S_IRUSR) grants |= DIR_READ; } else { gflags = FILE_INHERITANCE; if (mode & S_IXUSR) grants |= FILE_EXEC; if (mode & S_IWUSR) grants |= FILE_WRITE; if (mode & S_IRUSR) grants |= FILE_READ; } /* a possible ACE to deny owner what he/she would */ /* induely get from administrator, group or world */ /* unless owner is administrator or group */ denials = const_cpu_to_le32(0); pdace = (ACCESS_DENIED_ACE*) &secattr[offs + pos]; if (!adminowns) { if (!groupowns) { if (isdir) { pdace->flags = DIR_INHERITANCE; if (mode & (S_IXGRP | S_IXOTH)) denials |= DIR_EXEC; if (mode & (S_IWGRP | S_IWOTH)) denials |= DIR_WRITE; if (mode & (S_IRGRP | S_IROTH)) denials |= DIR_READ; } else { pdace->flags = FILE_INHERITANCE; if (mode & (S_IXGRP | S_IXOTH)) denials |= FILE_EXEC; if (mode & (S_IWGRP | S_IWOTH)) denials |= FILE_WRITE; if (mode & (S_IRGRP | S_IROTH)) denials |= FILE_READ; } } else { if (isdir) { pdace->flags = DIR_INHERITANCE; if ((mode & S_IXOTH) && !(mode & S_IXGRP)) denials |= DIR_EXEC; if ((mode & S_IWOTH) && !(mode & S_IWGRP)) denials |= DIR_WRITE; if ((mode & S_IROTH) && !(mode & S_IRGRP)) denials |= DIR_READ; } else { pdace->flags = FILE_INHERITANCE; if ((mode & S_IXOTH) && !(mode & S_IXGRP)) denials |= FILE_EXEC; if ((mode & S_IWOTH) && !(mode & S_IWGRP)) denials |= FILE_WRITE; if ((mode & S_IROTH) && !(mode & S_IRGRP)) denials |= FILE_READ; } } denials &= ~grants; if (denials) { pdace->type = ACCESS_DENIED_ACE_TYPE; pdace->size = cpu_to_le16(usidsz + 8); pdace->mask = denials; memcpy((char*)&pdace->sid, usid, usidsz); pos += usidsz + 8; acecnt++; } } /* * for directories, a world execution denial * inherited to plain files */ if (isdir) { pdace = (ACCESS_DENIED_ACE*) &secattr[offs + pos]; pdace->type = ACCESS_DENIED_ACE_TYPE; pdace->flags = INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE; pdace->size = cpu_to_le16(wsidsz + 8); pdace->mask = FILE_EXEC; memcpy((char*)&pdace->sid, worldsid, wsidsz); pos += wsidsz + 8; acecnt++; } /* now insert grants to owner */ pgace = (ACCESS_ALLOWED_ACE*) &secattr[offs + pos]; pgace->type = ACCESS_ALLOWED_ACE_TYPE; pgace->size = cpu_to_le16(usidsz + 8); pgace->flags = gflags; pgace->mask = grants; memcpy((char*)&pgace->sid, usid, usidsz); pos += usidsz + 8; acecnt++; /* a grant ACE for group */ /* unless group has the same rights as world */ /* but present if group is owner or owner is administrator */ /* this ACE will be inserted after denials for group */ if (adminowns || groupowns || (((mode >> 3) ^ mode) & 7)) { grants = WORLD_RIGHTS; if (isdir) { gflags = DIR_INHERITANCE; if (mode & S_IXGRP) grants |= DIR_EXEC; if (mode & S_IWGRP) grants |= DIR_WRITE; if (mode & S_IRGRP) grants |= DIR_READ; } else { gflags = FILE_INHERITANCE; if (mode & S_IXGRP) grants |= FILE_EXEC; if (mode & S_IWGRP) grants |= FILE_WRITE; if (mode & S_IRGRP) grants |= FILE_READ; } /* a possible ACE to deny group what it would get from world */ /* or administrator, unless owner is administrator or group */ denials = const_cpu_to_le32(0); pdace = (ACCESS_ALLOWED_ACE*)&secattr[offs + pos]; if (!adminowns && !groupowns) { if (isdir) { pdace->flags = DIR_INHERITANCE; if (mode & S_IXOTH) denials |= DIR_EXEC; if (mode & S_IWOTH) denials |= DIR_WRITE; if (mode & S_IROTH) denials |= DIR_READ; } else { pdace->flags = FILE_INHERITANCE; if (mode & S_IXOTH) denials |= FILE_EXEC; if (mode & S_IWOTH) denials |= FILE_WRITE; if (mode & S_IROTH) denials |= FILE_READ; } denials &= ~(grants | OWNER_RIGHTS); if (denials) { pdace->type = ACCESS_DENIED_ACE_TYPE; pdace->size = cpu_to_le16(gsidsz + 8); pdace->mask = denials; memcpy((char*)&pdace->sid, gsid, gsidsz); pos += gsidsz + 8; acecnt++; } } if (adminowns || groupowns || ((mode >> 3) & ~mode & 7)) { /* now insert grants to group */ /* if more rights than other */ pgace = (ACCESS_ALLOWED_ACE*)&secattr[offs + pos]; pgace->type = ACCESS_ALLOWED_ACE_TYPE; pgace->flags = gflags; pgace->size = cpu_to_le16(gsidsz + 8); pgace->mask = grants; memcpy((char*)&pgace->sid, gsid, gsidsz); pos += gsidsz + 8; acecnt++; } } /* an ACE for world users */ pgace = (ACCESS_ALLOWED_ACE*)&secattr[offs + pos]; pgace->type = ACCESS_ALLOWED_ACE_TYPE; grants = WORLD_RIGHTS; if (isdir) { pgace->flags = DIR_INHERITANCE; if (mode & S_IXOTH) grants |= DIR_EXEC; if (mode & S_IWOTH) grants |= DIR_WRITE; if (mode & S_IROTH) grants |= DIR_READ; } else { pgace->flags = FILE_INHERITANCE; if (mode & S_IXOTH) grants |= FILE_EXEC; if (mode & S_IWOTH) grants |= FILE_WRITE; if (mode & S_IROTH) grants |= FILE_READ; } pgace->size = cpu_to_le16(wsidsz + 8); pgace->mask = grants; memcpy((char*)&pgace->sid, worldsid, wsidsz); pos += wsidsz + 8; acecnt++; /* an ACE for administrators */ /* always full access */ pgace = (ACCESS_ALLOWED_ACE*)&secattr[offs + pos]; pgace->type = ACCESS_ALLOWED_ACE_TYPE; if (isdir) pgace->flags = DIR_INHERITANCE; else pgace->flags = FILE_INHERITANCE; pgace->size = cpu_to_le16(asidsz + 8); grants = OWNER_RIGHTS | FILE_READ | FILE_WRITE | FILE_EXEC; pgace->mask = grants; memcpy((char*)&pgace->sid, adminsid, asidsz); pos += asidsz + 8; acecnt++; /* an ACE for system (needed ?) */ /* always full access */ pgace = (ACCESS_ALLOWED_ACE*)&secattr[offs + pos]; pgace->type = ACCESS_ALLOWED_ACE_TYPE; if (isdir) pgace->flags = DIR_INHERITANCE; else pgace->flags = FILE_INHERITANCE; pgace->size = cpu_to_le16(ssidsz + 8); grants = OWNER_RIGHTS | FILE_READ | FILE_WRITE | FILE_EXEC; pgace->mask = grants; memcpy((char*)&pgace->sid, systemsid, ssidsz); pos += ssidsz + 8; acecnt++; /* a null ACE to hold special flags */ /* using the same representation as cygwin */ if (mode & (S_ISVTX | S_ISGID | S_ISUID)) { nsidsz = ntfs_sid_size(nullsid); pgace = (ACCESS_ALLOWED_ACE*)&secattr[offs + pos]; pgace->type = ACCESS_ALLOWED_ACE_TYPE; pgace->flags = NO_PROPAGATE_INHERIT_ACE; pgace->size = cpu_to_le16(nsidsz + 8); grants = const_cpu_to_le32(0); if (mode & S_ISUID) grants |= FILE_APPEND_DATA; if (mode & S_ISGID) grants |= FILE_WRITE_DATA; if (mode & S_ISVTX) grants |= FILE_READ_DATA; pgace->mask = grants; memcpy((char*)&pgace->sid, nullsid, nsidsz); pos += nsidsz + 8; acecnt++; } /* fix ACL header */ pacl->size = cpu_to_le16(pos); pacl->ace_count = cpu_to_le16(acecnt); return (pos); } #if POSIXACLS /* * Build a full security descriptor from a Posix ACL * returns descriptor in allocated memory, must free() after use */ char *ntfs_build_descr_posix(struct MAPPING* const mapping[], struct POSIX_SECURITY *pxdesc, int isdir, const SID *usid, const SID *gsid) { int newattrsz; SECURITY_DESCRIPTOR_RELATIVE *pnhead; char *newattr; int aclsz; int usidsz; int gsidsz; int wsidsz; int asidsz; int ssidsz; int k; usidsz = ntfs_sid_size(usid); gsidsz = ntfs_sid_size(gsid); wsidsz = ntfs_sid_size(worldsid); asidsz = ntfs_sid_size(adminsid); ssidsz = ntfs_sid_size(systemsid); /* allocate enough space for the new security attribute */ newattrsz = sizeof(SECURITY_DESCRIPTOR_RELATIVE) /* header */ + usidsz + gsidsz /* usid and gsid */ + sizeof(ACL) /* acl header */ + 2*(8 + usidsz) /* two possible ACE for user */ + 3*(8 + gsidsz) /* three possible ACE for group and mask */ + 8 + wsidsz /* one ACE for world */ + 8 + asidsz /* one ACE for admin */ + 8 + ssidsz; /* one ACE for system */ if (isdir) /* a world denial for directories */ newattrsz += 8 + wsidsz; if (pxdesc->mode & 07000) /* a NULL ACE for special modes */ newattrsz += 8 + ntfs_sid_size(nullsid); /* account for non-owning users and groups */ for (k=0; kacccnt; k++) { if ((pxdesc->acl.ace[k].tag == POSIX_ACL_USER) || (pxdesc->acl.ace[k].tag == POSIX_ACL_GROUP)) newattrsz += 3*40; /* fixme : maximum size */ } /* account for default ACE's */ newattrsz += 2*40*pxdesc->defcnt; /* fixme : maximum size */ newattr = (char*)ntfs_malloc(newattrsz); if (newattr) { /* build the main header part */ pnhead = (SECURITY_DESCRIPTOR_RELATIVE*)newattr; pnhead->revision = SECURITY_DESCRIPTOR_REVISION; pnhead->alignment = 0; /* * The flag SE_DACL_PROTECTED prevents the ACL * to be changed in an inheritance after creation */ pnhead->control = SE_DACL_PRESENT | SE_DACL_PROTECTED | SE_SELF_RELATIVE; /* * Windows prefers ACL first, do the same to * get the same hash value and avoid duplication */ /* build permissions */ aclsz = buildacls_posix(mapping,newattr, sizeof(SECURITY_DESCRIPTOR_RELATIVE), pxdesc, isdir, usid, gsid); if (aclsz && ((int)(sizeof(SECURITY_DESCRIPTOR_RELATIVE) + aclsz + usidsz + gsidsz) <= newattrsz)) { /* append usid and gsid */ memcpy(&newattr[sizeof(SECURITY_DESCRIPTOR_RELATIVE) + aclsz], usid, usidsz); memcpy(&newattr[sizeof(SECURITY_DESCRIPTOR_RELATIVE) + aclsz + usidsz], gsid, gsidsz); /* positions of ACL, USID and GSID into header */ pnhead->owner = cpu_to_le32(sizeof(SECURITY_DESCRIPTOR_RELATIVE) + aclsz); pnhead->group = cpu_to_le32(sizeof(SECURITY_DESCRIPTOR_RELATIVE) + aclsz + usidsz); pnhead->sacl = const_cpu_to_le32(0); pnhead->dacl = const_cpu_to_le32(sizeof(SECURITY_DESCRIPTOR_RELATIVE)); } else { /* ACL failure (errno set) or overflow */ free(newattr); newattr = (char*)NULL; if (aclsz) { /* hope error was detected before overflowing */ ntfs_log_error("Security descriptor is longer than expected\n"); errno = EIO; } } } else errno = ENOMEM; return (newattr); } #endif /* POSIXACLS */ /* * Build a full security descriptor * returns descriptor in allocated memory, must free() after use */ char *ntfs_build_descr(mode_t mode, int isdir, const SID * usid, const SID * gsid) { int newattrsz; SECURITY_DESCRIPTOR_RELATIVE *pnhead; char *newattr; int aclsz; int usidsz; int gsidsz; int wsidsz; int asidsz; int ssidsz; usidsz = ntfs_sid_size(usid); gsidsz = ntfs_sid_size(gsid); wsidsz = ntfs_sid_size(worldsid); asidsz = ntfs_sid_size(adminsid); ssidsz = ntfs_sid_size(systemsid); /* allocate enough space for the new security attribute */ newattrsz = sizeof(SECURITY_DESCRIPTOR_RELATIVE) /* header */ + usidsz + gsidsz /* usid and gsid */ + sizeof(ACL) /* acl header */ + 2*(8 + usidsz) /* two possible ACE for user */ + 2*(8 + gsidsz) /* two possible ACE for group */ + 8 + wsidsz /* one ACE for world */ + 8 + asidsz /* one ACE for admin */ + 8 + ssidsz; /* one ACE for system */ if (isdir) /* a world denial for directories */ newattrsz += 8 + wsidsz; if (mode & 07000) /* a NULL ACE for special modes */ newattrsz += 8 + ntfs_sid_size(nullsid); newattr = (char*)ntfs_malloc(newattrsz); if (newattr) { /* build the main header part */ pnhead = (SECURITY_DESCRIPTOR_RELATIVE*) newattr; pnhead->revision = SECURITY_DESCRIPTOR_REVISION; pnhead->alignment = 0; /* * The flag SE_DACL_PROTECTED prevents the ACL * to be changed in an inheritance after creation */ pnhead->control = SE_DACL_PRESENT | SE_DACL_PROTECTED | SE_SELF_RELATIVE; /* * Windows prefers ACL first, do the same to * get the same hash value and avoid duplication */ /* build permissions */ aclsz = buildacls(newattr, sizeof(SECURITY_DESCRIPTOR_RELATIVE), mode, isdir, usid, gsid); if (((int)sizeof(SECURITY_DESCRIPTOR_RELATIVE) + aclsz + usidsz + gsidsz) <= newattrsz) { /* append usid and gsid */ memcpy(&newattr[sizeof(SECURITY_DESCRIPTOR_RELATIVE) + aclsz], usid, usidsz); memcpy(&newattr[sizeof(SECURITY_DESCRIPTOR_RELATIVE) + aclsz + usidsz], gsid, gsidsz); /* positions of ACL, USID and GSID into header */ pnhead->owner = cpu_to_le32(sizeof(SECURITY_DESCRIPTOR_RELATIVE) + aclsz); pnhead->group = cpu_to_le32(sizeof(SECURITY_DESCRIPTOR_RELATIVE) + aclsz + usidsz); pnhead->sacl = const_cpu_to_le32(0); pnhead->dacl = const_cpu_to_le32(sizeof(SECURITY_DESCRIPTOR_RELATIVE)); } else { /* hope error was detected before overflowing */ free(newattr); newattr = (char*)NULL; ntfs_log_error("Security descriptor is longer than expected\n"); errno = EIO; } } else errno = ENOMEM; return (newattr); } /* * Create a mode_t permission set * from owner, group and world grants as represented in ACEs */ static int merge_permissions(BOOL isdir, le32 owner, le32 group, le32 world, le32 special) { int perm; perm = 0; /* build owner permission */ if (owner) { if (isdir) { /* exec if any of list, traverse */ if (owner & DIR_GEXEC) perm |= S_IXUSR; /* write if any of addfile, adddir, delchild */ if (owner & DIR_GWRITE) perm |= S_IWUSR; /* read if any of list */ if (owner & DIR_GREAD) perm |= S_IRUSR; } else { /* exec if execute or generic execute */ if (owner & FILE_GEXEC) perm |= S_IXUSR; /* write if any of writedata or generic write */ if (owner & FILE_GWRITE) perm |= S_IWUSR; /* read if any of readdata or generic read */ if (owner & FILE_GREAD) perm |= S_IRUSR; } } /* build group permission */ if (group) { if (isdir) { /* exec if any of list, traverse */ if (group & DIR_GEXEC) perm |= S_IXGRP; /* write if any of addfile, adddir, delchild */ if (group & DIR_GWRITE) perm |= S_IWGRP; /* read if any of list */ if (group & DIR_GREAD) perm |= S_IRGRP; } else { /* exec if execute */ if (group & FILE_GEXEC) perm |= S_IXGRP; /* write if any of writedata, appenddata */ if (group & FILE_GWRITE) perm |= S_IWGRP; /* read if any of readdata */ if (group & FILE_GREAD) perm |= S_IRGRP; } } /* build world permission */ if (world) { if (isdir) { /* exec if any of list, traverse */ if (world & DIR_GEXEC) perm |= S_IXOTH; /* write if any of addfile, adddir, delchild */ if (world & DIR_GWRITE) perm |= S_IWOTH; /* read if any of list */ if (world & DIR_GREAD) perm |= S_IROTH; } else { /* exec if execute */ if (world & FILE_GEXEC) perm |= S_IXOTH; /* write if any of writedata, appenddata */ if (world & FILE_GWRITE) perm |= S_IWOTH; /* read if any of readdata */ if (world & FILE_GREAD) perm |= S_IROTH; } } /* build special permission flags */ if (special) { if (special & FILE_APPEND_DATA) perm |= S_ISUID; if (special & FILE_WRITE_DATA) perm |= S_ISGID; if (special & FILE_READ_DATA) perm |= S_ISVTX; } return (perm); } #if POSIXACLS /* * Normalize a Posix ACL either from a sorted raw set of * access ACEs or default ACEs * (standard case : different owner, group and administrator) */ static int norm_std_permissions_posix(struct POSIX_SECURITY *posix_desc, BOOL groupowns, int start, int count, int target) { int j,k; s32 id; u16 tag; u16 tagsset; struct POSIX_ACE *pxace; mode_t grantgrps; mode_t grantwrld; mode_t denywrld; mode_t allow; mode_t deny; mode_t perms; mode_t mode; mode = 0; tagsset = 0; /* * Determine what is granted to some group or world * Also get denials to world which are meant to prevent * execution flags to be inherited by plain files */ pxace = posix_desc->acl.ace; grantgrps = 0; grantwrld = 0; denywrld = 0; for (j=start; j<(start + count); j++) { if (pxace[j].perms & POSIX_PERM_DENIAL) { /* deny world exec unless for default */ if ((pxace[j].tag == POSIX_ACL_OTHER) && !start) denywrld = pxace[j].perms; } else { switch (pxace[j].tag) { case POSIX_ACL_GROUP_OBJ : grantgrps |= pxace[j].perms; break; case POSIX_ACL_GROUP : if (pxace[j].id) grantgrps |= pxace[j].perms; break; case POSIX_ACL_OTHER : grantwrld = pxace[j].perms; break; default : break; } } } /* * Collect groups of ACEs related to the same id * and determine what is granted and what is denied. * It is important the ACEs have been sorted */ j = start; k = target; while (j < (start + count)) { tag = pxace[j].tag; id = pxace[j].id; if (pxace[j].perms & POSIX_PERM_DENIAL) { deny = pxace[j].perms | denywrld; allow = 0; } else { deny = denywrld; allow = pxace[j].perms; } j++; while ((j < (start + count)) && (pxace[j].tag == tag) && (pxace[j].id == id)) { if (pxace[j].perms & POSIX_PERM_DENIAL) deny |= pxace[j].perms; else allow |= pxace[j].perms; j++; } /* * Build the permissions equivalent to grants and denials */ if (groupowns) { if (tag == POSIX_ACL_MASK) perms = ~deny; else perms = allow & ~deny; } else switch (tag) { case POSIX_ACL_USER_OBJ : perms = (allow | grantgrps | grantwrld) & ~deny; break; case POSIX_ACL_USER : if (id) perms = (allow | grantgrps | grantwrld) & ~deny; else perms = allow; break; case POSIX_ACL_GROUP_OBJ : perms = (allow | grantwrld) & ~deny; break; case POSIX_ACL_GROUP : if (id) perms = (allow | grantwrld) & ~deny; else perms = allow; break; case POSIX_ACL_MASK : perms = ~deny; break; default : perms = allow & ~deny; break; } /* * Store into a Posix ACE */ if (tag != POSIX_ACL_SPECIAL) { pxace[k].tag = tag; pxace[k].id = id; pxace[k].perms = perms & (POSIX_PERM_R | POSIX_PERM_W | POSIX_PERM_X); tagsset |= tag; k++; } switch (tag) { case POSIX_ACL_USER_OBJ : mode |= ((perms & 7) << 6); break; case POSIX_ACL_GROUP_OBJ : case POSIX_ACL_MASK : mode = (mode & 07707) | ((perms & 7) << 3); break; case POSIX_ACL_OTHER : mode |= perms & 7; break; case POSIX_ACL_SPECIAL : mode |= (perms & (S_ISVTX | S_ISUID | S_ISGID)); break; default : break; } } if (!start) { /* not satisfactory */ posix_desc->mode = mode; posix_desc->tagsset = tagsset; } return (k - target); } #endif /* POSIXACLS */ /* * Interpret an ACL and extract meaningful grants * (standard case : different owner, group and administrator) */ static int build_std_permissions(const char *securattr, const SID *usid, const SID *gsid, BOOL isdir) { const SECURITY_DESCRIPTOR_RELATIVE *phead; const ACL *pacl; const ACCESS_ALLOWED_ACE *pace; int offdacl; int offace; int acecnt; int nace; BOOL noown; le32 special; le32 allowown, allowgrp, allowall; le32 denyown, denygrp, denyall; phead = (const SECURITY_DESCRIPTOR_RELATIVE*)securattr; offdacl = le32_to_cpu(phead->dacl); pacl = (const ACL*)&securattr[offdacl]; special = const_cpu_to_le32(0); allowown = allowgrp = allowall = const_cpu_to_le32(0); denyown = denygrp = denyall = const_cpu_to_le32(0); noown = TRUE; if (offdacl) { acecnt = le16_to_cpu(pacl->ace_count); offace = offdacl + sizeof(ACL); } else { acecnt = 0; offace = 0; } for (nace = 0; nace < acecnt; nace++) { pace = (const ACCESS_ALLOWED_ACE*)&securattr[offace]; if (!(pace->flags & INHERIT_ONLY_ACE)) { if (ntfs_same_sid(usid, &pace->sid) || ntfs_same_sid(ownersid, &pace->sid)) { noown = FALSE; if (pace->type == ACCESS_ALLOWED_ACE_TYPE) allowown |= pace->mask; else if (pace->type == ACCESS_DENIED_ACE_TYPE) denyown |= pace->mask; } else if (ntfs_same_sid(gsid, &pace->sid) && !(pace->mask & WRITE_OWNER)) { if (pace->type == ACCESS_ALLOWED_ACE_TYPE) allowgrp |= pace->mask; else if (pace->type == ACCESS_DENIED_ACE_TYPE) denygrp |= pace->mask; } else if (is_world_sid((const SID*)&pace->sid)) { if (pace->type == ACCESS_ALLOWED_ACE_TYPE) allowall |= pace->mask; else if (pace->type == ACCESS_DENIED_ACE_TYPE) denyall |= pace->mask; } else if ((ntfs_same_sid((const SID*)&pace->sid,nullsid)) && (pace->type == ACCESS_ALLOWED_ACE_TYPE)) special |= pace->mask; } offace += le16_to_cpu(pace->size); } /* * No indication about owner's rights : grant basic rights * This happens for files created by Windows in directories * created by Linux and owned by root, because Windows * merges the admin ACEs */ if (noown) allowown = (FILE_READ_DATA | FILE_WRITE_DATA | FILE_EXECUTE); /* * Add to owner rights granted to group or world * unless denied personaly, and add to group rights * granted to world unless denied specifically */ allowown |= (allowgrp | allowall); allowgrp |= allowall; return (merge_permissions(isdir, allowown & ~(denyown | denyall), allowgrp & ~(denygrp | denyall), allowall & ~denyall, special)); } /* * Interpret an ACL and extract meaningful grants * (special case : owner and group are the same, * and not administrator) */ static int build_owngrp_permissions(const char *securattr, const SID *usid, BOOL isdir) { const SECURITY_DESCRIPTOR_RELATIVE *phead; const ACL *pacl; const ACCESS_ALLOWED_ACE *pace; int offdacl; int offace; int acecnt; int nace; le32 special; BOOL grppresent; le32 allowown, allowgrp, allowall; le32 denyown, denygrp, denyall; phead = (const SECURITY_DESCRIPTOR_RELATIVE*)securattr; offdacl = le32_to_cpu(phead->dacl); pacl = (const ACL*)&securattr[offdacl]; special = const_cpu_to_le32(0); allowown = allowgrp = allowall = const_cpu_to_le32(0); denyown = denygrp = denyall = const_cpu_to_le32(0); grppresent = FALSE; if (offdacl) { acecnt = le16_to_cpu(pacl->ace_count); offace = offdacl + sizeof(ACL); } else acecnt = 0; for (nace = 0; nace < acecnt; nace++) { pace = (const ACCESS_ALLOWED_ACE*)&securattr[offace]; if (!(pace->flags & INHERIT_ONLY_ACE)) { if ((ntfs_same_sid(usid, &pace->sid) || ntfs_same_sid(ownersid, &pace->sid)) && (pace->mask & WRITE_OWNER)) { if (pace->type == ACCESS_ALLOWED_ACE_TYPE) allowown |= pace->mask; } else if (ntfs_same_sid(usid, &pace->sid) && (!(pace->mask & WRITE_OWNER))) { if (pace->type == ACCESS_ALLOWED_ACE_TYPE) { allowgrp |= pace->mask; grppresent = TRUE; } } else if (is_world_sid((const SID*)&pace->sid)) { if (pace->type == ACCESS_ALLOWED_ACE_TYPE) allowall |= pace->mask; else if (pace->type == ACCESS_DENIED_ACE_TYPE) denyall |= pace->mask; } else if ((ntfs_same_sid((const SID*)&pace->sid,nullsid)) && (pace->type == ACCESS_ALLOWED_ACE_TYPE)) special |= pace->mask; } offace += le16_to_cpu(pace->size); } if (!grppresent) allowgrp = allowall; return (merge_permissions(isdir, allowown & ~(denyown | denyall), allowgrp & ~(denygrp | denyall), allowall & ~denyall, special)); } #if POSIXACLS /* * Normalize a Posix ACL either from a sorted raw set of * access ACEs or default ACEs * (special case : owner or/and group is administrator) */ static int norm_ownadmin_permissions_posix(struct POSIX_SECURITY *posix_desc, int start, int count, int target) { int j,k; s32 id; u16 tag; u16 tagsset; struct POSIX_ACE *pxace; mode_t denywrld; mode_t allow; mode_t deny; mode_t perms; mode_t mode; mode = 0; pxace = posix_desc->acl.ace; tagsset = 0; denywrld = 0; /* * Get denials to world which are meant to prevent * execution flags to be inherited by plain files */ for (j=start; j<(start + count); j++) { if (pxace[j].perms & POSIX_PERM_DENIAL) { /* deny world exec not for default */ if ((pxace[j].tag == POSIX_ACL_OTHER) && !start) denywrld = pxace[j].perms; } } /* * Collect groups of ACEs related to the same id * and determine what is granted (denials are ignored) * It is important the ACEs have been sorted */ j = start; k = target; deny = 0; while (j < (start + count)) { allow = 0; tag = pxace[j].tag; id = pxace[j].id; if (tag == POSIX_ACL_MASK) { deny = pxace[j].perms; j++; while ((j < (start + count)) && (pxace[j].tag == POSIX_ACL_MASK)) j++; } else { if (!(pxace[j].perms & POSIX_PERM_DENIAL)) allow = pxace[j].perms; j++; while ((j < (start + count)) && (pxace[j].tag == tag) && (pxace[j].id == id)) { if (!(pxace[j].perms & POSIX_PERM_DENIAL)) allow |= pxace[j].perms; j++; } } /* * Store the grants into a Posix ACE */ if (tag == POSIX_ACL_MASK) perms = ~deny; else perms = allow & ~denywrld; if (tag != POSIX_ACL_SPECIAL) { pxace[k].tag = tag; pxace[k].id = id; pxace[k].perms = perms & (POSIX_PERM_R | POSIX_PERM_W | POSIX_PERM_X); tagsset |= tag; k++; } switch (tag) { case POSIX_ACL_USER_OBJ : mode |= ((perms & 7) << 6); break; case POSIX_ACL_GROUP_OBJ : case POSIX_ACL_MASK : mode = (mode & 07707) | ((perms & 7) << 3); break; case POSIX_ACL_OTHER : mode |= perms & 7; break; case POSIX_ACL_SPECIAL : mode |= perms & (S_ISVTX | S_ISUID | S_ISGID); break; default : break; } } if (!start) { /* not satisfactory */ posix_desc->mode = mode; posix_desc->tagsset = tagsset; } return (k - target); } #endif /* POSIXACLS */ /* * Interpret an ACL and extract meaningful grants * (special case : owner or/and group is administrator) */ static int build_ownadmin_permissions(const char *securattr, const SID *usid, const SID *gsid, BOOL isdir) { const SECURITY_DESCRIPTOR_RELATIVE *phead; const ACL *pacl; const ACCESS_ALLOWED_ACE *pace; int offdacl; int offace; int acecnt; int nace; BOOL firstapply; int isforeign; le32 special; le32 allowown, allowgrp, allowall; le32 denyown, denygrp, denyall; phead = (const SECURITY_DESCRIPTOR_RELATIVE*)securattr; offdacl = le32_to_cpu(phead->dacl); pacl = (const ACL*)&securattr[offdacl]; special = const_cpu_to_le32(0); allowown = allowgrp = allowall = const_cpu_to_le32(0); denyown = denygrp = denyall = const_cpu_to_le32(0); if (offdacl) { acecnt = le16_to_cpu(pacl->ace_count); offace = offdacl + sizeof(ACL); } else { acecnt = 0; offace = 0; } firstapply = TRUE; isforeign = 3; for (nace = 0; nace < acecnt; nace++) { pace = (const ACCESS_ALLOWED_ACE*)&securattr[offace]; if (!(pace->flags & INHERIT_ONLY_ACE) && !(~pace->mask & (ROOT_OWNER_UNMARK | ROOT_GROUP_UNMARK))) { if ((ntfs_same_sid(usid, &pace->sid) || ntfs_same_sid(ownersid, &pace->sid)) && (((pace->mask & WRITE_OWNER) && firstapply))) { if (pace->type == ACCESS_ALLOWED_ACE_TYPE) { allowown |= pace->mask; isforeign &= ~1; } else if (pace->type == ACCESS_DENIED_ACE_TYPE) denyown |= pace->mask; } else if (ntfs_same_sid(gsid, &pace->sid) && (!(pace->mask & WRITE_OWNER))) { if (pace->type == ACCESS_ALLOWED_ACE_TYPE) { allowgrp |= pace->mask; isforeign &= ~2; } else if (pace->type == ACCESS_DENIED_ACE_TYPE) denygrp |= pace->mask; } else if (is_world_sid((const SID*)&pace->sid)) { if (pace->type == ACCESS_ALLOWED_ACE_TYPE) allowall |= pace->mask; else if (pace->type == ACCESS_DENIED_ACE_TYPE) denyall |= pace->mask; } firstapply = FALSE; } else if (!(pace->flags & INHERIT_ONLY_ACE)) if ((ntfs_same_sid((const SID*)&pace->sid,nullsid)) && (pace->type == ACCESS_ALLOWED_ACE_TYPE)) special |= pace->mask; offace += le16_to_cpu(pace->size); } if (isforeign) { allowown |= (allowgrp | allowall); allowgrp |= allowall; } return (merge_permissions(isdir, allowown & ~(denyown | denyall), allowgrp & ~(denygrp | denyall), allowall & ~denyall, special)); } #if OWNERFROMACL /* * Define the owner of a file as the first user allowed * to change the owner, instead of the user defined as owner. * * This produces better approximations for files written by a * Windows user in an inheritable directory owned by another user, * as the access rights are inheritable but the ownership is not. * * An important case is the directories "Documents and Settings/user" * which the users must have access to, though Windows considers them * as owned by administrator. */ const SID *ntfs_acl_owner(const char *securattr) { const SECURITY_DESCRIPTOR_RELATIVE *phead; const SID *usid; const ACL *pacl; const ACCESS_ALLOWED_ACE *pace; int offdacl; int offace; int acecnt; int nace; BOOL found; found = FALSE; phead = (const SECURITY_DESCRIPTOR_RELATIVE*)securattr; offdacl = le32_to_cpu(phead->dacl); if (offdacl) { pacl = (const ACL*)&securattr[offdacl]; acecnt = le16_to_cpu(pacl->ace_count); offace = offdacl + sizeof(ACL); nace = 0; do { pace = (const ACCESS_ALLOWED_ACE*)&securattr[offace]; if ((pace->mask & WRITE_OWNER) && (pace->type == ACCESS_ALLOWED_ACE_TYPE) && ntfs_is_user_sid(&pace->sid)) found = TRUE; offace += le16_to_cpu(pace->size); } while (!found && (++nace < acecnt)); } if (found) usid = &pace->sid; else usid = (const SID*)&securattr[le32_to_cpu(phead->owner)]; return (usid); } #else /* * Special case for files owned by administrator with full * access granted to a mapped user : consider this user as the tenant * of the file. * * This situation cannot be represented with Linux concepts and can * only be found for files or directories created by Windows. * Typical situation : directory "Documents and Settings/user" which * is on the path to user's files and must be given access to user * only. * * Check file is owned by administrator and no user has rights before * calling. * Returns the uid of tenant or zero if none */ static uid_t find_tenant(struct MAPPING *const mapping[], const char *securattr) { const SECURITY_DESCRIPTOR_RELATIVE *phead; const ACL *pacl; const ACCESS_ALLOWED_ACE *pace; int offdacl; int offace; int acecnt; int nace; uid_t tid; uid_t xid; phead = (const SECURITY_DESCRIPTOR_RELATIVE*)securattr; offdacl = le32_to_cpu(phead->dacl); pacl = (const ACL*)&securattr[offdacl]; tid = 0; if (offdacl) { acecnt = le16_to_cpu(pacl->ace_count); offace = offdacl + sizeof(ACL); } else acecnt = 0; for (nace = 0; nace < acecnt; nace++) { pace = (const ACCESS_ALLOWED_ACE*)&securattr[offace]; if ((pace->type == ACCESS_ALLOWED_ACE_TYPE) && (pace->mask & DIR_WRITE)) { xid = NTFS_FIND_USER(mapping[MAPUSERS], &pace->sid); if (xid) tid = xid; } offace += le16_to_cpu(pace->size); } return (tid); } #endif /* OWNERFROMACL */ #if POSIXACLS /* * Build Posix permissions from an ACL * returns a pointer to the requested permissions * or a null pointer (with errno set) if there is a problem * * If the NTFS ACL was created according to our rules, the retrieved * Posix ACL should be the exact ACL which was set. However if * the NTFS ACL was built by a different tool, the result could * be a a poor approximation of what was expected */ struct POSIX_SECURITY *ntfs_build_permissions_posix( struct MAPPING *const mapping[], const char *securattr, const SID *usid, const SID *gsid, BOOL isdir) { const SECURITY_DESCRIPTOR_RELATIVE *phead; struct POSIX_SECURITY *pxdesc; const ACL *pacl; const ACCESS_ALLOWED_ACE *pace; struct POSIX_ACE *pxace; struct { uid_t prevuid; gid_t prevgid; int groupmasks; s16 tagsset; BOOL gotowner; BOOL gotownermask; BOOL gotgroup; mode_t permswrld; } ctx[2], *pctx; int offdacl; int offace; int alloccnt; int acecnt; uid_t uid; gid_t gid; int i,j; int k,l; BOOL ignore; BOOL adminowns; BOOL groupowns; BOOL firstinh; BOOL genericinh; phead = (const SECURITY_DESCRIPTOR_RELATIVE*)securattr; offdacl = le32_to_cpu(phead->dacl); if (offdacl) { pacl = (const ACL*)&securattr[offdacl]; acecnt = le16_to_cpu(pacl->ace_count); offace = offdacl + sizeof(ACL); } else { acecnt = 0; offace = 0; } adminowns = FALSE; groupowns = ntfs_same_sid(gsid,usid); firstinh = FALSE; genericinh = FALSE; /* * Build a raw posix security descriptor * by just translating permissions and ids * Add 2 to the count of ACE to be able to insert * a group ACE later in access and default ACLs * and add 2 more to be able to insert ACEs for owner * and 2 more for other */ alloccnt = acecnt + 6; pxdesc = (struct POSIX_SECURITY*)ntfs_malloc( sizeof(struct POSIX_SECURITY) + alloccnt*sizeof(struct POSIX_ACE)); k = 0; l = alloccnt; for (i=0; i<2; i++) { pctx = &ctx[i]; pctx->permswrld = 0; pctx->prevuid = -1; pctx->prevgid = -1; pctx->groupmasks = 0; pctx->tagsset = 0; pctx->gotowner = FALSE; pctx->gotgroup = FALSE; pctx->gotownermask = FALSE; } for (j=0; jflags & INHERIT_ONLY_ACE) { pxace = &pxdesc->acl.ace[l - 1]; pctx = &ctx[1]; } else { pxace = &pxdesc->acl.ace[k]; pctx = &ctx[0]; } ignore = FALSE; /* * grants for root as a designated user or group */ if ((~pace->mask & (ROOT_OWNER_UNMARK | ROOT_GROUP_UNMARK)) && (pace->type == ACCESS_ALLOWED_ACE_TYPE) && ntfs_same_sid(&pace->sid, adminsid)) { pxace->tag = (pace->mask & ROOT_OWNER_UNMARK ? POSIX_ACL_GROUP : POSIX_ACL_USER); pxace->id = 0; if ((pace->mask & (GENERIC_ALL | WRITE_OWNER)) && (pace->flags & INHERIT_ONLY_ACE)) ignore = genericinh = TRUE; } else if (ntfs_same_sid(usid, &pace->sid)) { pxace->id = -1; /* * Owner has no write-owner right : * a group was defined same as owner * or admin was owner or group : * denials are meant to owner * and grants are meant to group */ if (!(pace->mask & (WRITE_OWNER | GENERIC_ALL)) && (pace->type == ACCESS_ALLOWED_ACE_TYPE)) { if (ntfs_same_sid(gsid,usid)) { pxace->tag = POSIX_ACL_GROUP_OBJ; pxace->id = -1; } else { if (ntfs_same_sid(&pace->sid,usid)) groupowns = TRUE; gid = NTFS_FIND_GROUP(mapping[MAPGROUPS],&pace->sid); if (gid) { pxace->tag = POSIX_ACL_GROUP; pxace->id = gid; pctx->prevgid = gid; } else { uid = NTFS_FIND_USER(mapping[MAPUSERS],&pace->sid); if (uid) { pxace->tag = POSIX_ACL_USER; pxace->id = uid; } else ignore = TRUE; } } } else { /* * when group owns, late denials for owner * mean group mask */ if ((pace->type == ACCESS_DENIED_ACE_TYPE) && (pace->mask & WRITE_OWNER)) { pxace->tag = POSIX_ACL_MASK; pctx->gotownermask = TRUE; if (pctx->gotowner) pctx->groupmasks++; } else { if (pace->type == ACCESS_ALLOWED_ACE_TYPE) pctx->gotowner = TRUE; if (pctx->gotownermask && !pctx->gotowner) { uid = NTFS_FIND_USER(mapping[MAPUSERS],&pace->sid); pxace->id = uid; pxace->tag = POSIX_ACL_USER; } else pxace->tag = POSIX_ACL_USER_OBJ; /* system ignored, and admin */ /* ignored at first position */ if (pace->flags & INHERIT_ONLY_ACE) { if ((firstinh && ntfs_same_sid(&pace->sid,adminsid)) || ntfs_same_sid(&pace->sid,systemsid)) ignore = TRUE; if (!firstinh) { firstinh = TRUE; } } else { if ((adminowns && ntfs_same_sid(&pace->sid,adminsid)) || ntfs_same_sid(&pace->sid,systemsid)) ignore = TRUE; if (ntfs_same_sid(usid,adminsid)) adminowns = TRUE; } } } } else if (ntfs_same_sid(gsid, &pace->sid)) { if ((pace->type == ACCESS_DENIED_ACE_TYPE) && (pace->mask & WRITE_OWNER)) { pxace->tag = POSIX_ACL_MASK; pxace->id = -1; if (pctx->gotowner) pctx->groupmasks++; } else { if (pctx->gotgroup || (pctx->groupmasks > 1)) { gid = NTFS_FIND_GROUP(mapping[MAPGROUPS],&pace->sid); if (gid) { pxace->id = gid; pxace->tag = POSIX_ACL_GROUP; pctx->prevgid = gid; } else ignore = TRUE; } else { pxace->id = -1; pxace->tag = POSIX_ACL_GROUP_OBJ; if (pace->type == ACCESS_ALLOWED_ACE_TYPE) pctx->gotgroup = TRUE; } if (ntfs_same_sid(gsid,adminsid) || ntfs_same_sid(gsid,systemsid)) { if (pace->mask & (WRITE_OWNER | GENERIC_ALL)) ignore = TRUE; if (ntfs_same_sid(gsid,adminsid)) adminowns = TRUE; else genericinh = ignore; } } } else if (is_world_sid((const SID*)&pace->sid)) { pxace->id = -1; pxace->tag = POSIX_ACL_OTHER; if ((pace->type == ACCESS_DENIED_ACE_TYPE) && (pace->flags & INHERIT_ONLY_ACE)) ignore = TRUE; } else if (ntfs_same_sid((const SID*)&pace->sid,nullsid)) { pxace->id = -1; pxace->tag = POSIX_ACL_SPECIAL; } else { uid = NTFS_FIND_USER(mapping[MAPUSERS],&pace->sid); if (uid) { if ((pace->type == ACCESS_DENIED_ACE_TYPE) && (pace->mask & WRITE_OWNER) && (pctx->prevuid != uid)) { pxace->id = -1; pxace->tag = POSIX_ACL_MASK; } else { pxace->id = uid; pxace->tag = POSIX_ACL_USER; } pctx->prevuid = uid; } else { gid = NTFS_FIND_GROUP(mapping[MAPGROUPS],&pace->sid); if (gid) { if ((pace->type == ACCESS_DENIED_ACE_TYPE) && (pace->mask & WRITE_OWNER) && (pctx->prevgid != gid)) { pxace->tag = POSIX_ACL_MASK; pctx->groupmasks++; } else { pxace->tag = POSIX_ACL_GROUP; } pxace->id = gid; pctx->prevgid = gid; } else { /* * do not grant rights to unknown * people and do not define root as a * designated user or group */ ignore = TRUE; } } } if (!ignore) { pxace->perms = 0; /* specific decoding for vtx/uid/gid */ if (pxace->tag == POSIX_ACL_SPECIAL) { if (pace->mask & FILE_APPEND_DATA) pxace->perms |= S_ISUID; if (pace->mask & FILE_WRITE_DATA) pxace->perms |= S_ISGID; if (pace->mask & FILE_READ_DATA) pxace->perms |= S_ISVTX; } else if (isdir) { if (pace->mask & DIR_GEXEC) pxace->perms |= POSIX_PERM_X; if (pace->mask & DIR_GWRITE) pxace->perms |= POSIX_PERM_W; if (pace->mask & DIR_GREAD) pxace->perms |= POSIX_PERM_R; if ((pace->mask & GENERIC_ALL) && (pace->flags & INHERIT_ONLY_ACE)) pxace->perms |= POSIX_PERM_X | POSIX_PERM_W | POSIX_PERM_R; } else { if (pace->mask & FILE_GEXEC) pxace->perms |= POSIX_PERM_X; if (pace->mask & FILE_GWRITE) pxace->perms |= POSIX_PERM_W; if (pace->mask & FILE_GREAD) pxace->perms |= POSIX_PERM_R; } if (pace->type != ACCESS_ALLOWED_ACE_TYPE) pxace->perms |= POSIX_PERM_DENIAL; else if (pxace->tag == POSIX_ACL_OTHER) pctx->permswrld = pxace->perms; pctx->tagsset |= pxace->tag; if (pace->flags & INHERIT_ONLY_ACE) { l--; } else { k++; } } offace += le16_to_cpu(pace->size); } /* * Create world perms if none (both lists) */ for (i=0; i<2; i++) if ((genericinh || !i) && !(ctx[i].tagsset & POSIX_ACL_OTHER)) { if (i) pxace = &pxdesc->acl.ace[--l]; else pxace = &pxdesc->acl.ace[k++]; pxace->tag = POSIX_ACL_OTHER; pxace->id = -1; pxace->perms = 0; ctx[i].tagsset |= POSIX_ACL_OTHER; ctx[i].permswrld = 0; } /* * Set basic owner perms if none (both lists) * This happens for files created by Windows in directories * created by Linux and owned by root, because Windows * merges the admin ACEs */ for (i=0; i<2; i++) if (!(ctx[i].tagsset & POSIX_ACL_USER_OBJ) && (ctx[i].tagsset & POSIX_ACL_OTHER)) { if (i) pxace = &pxdesc->acl.ace[--l]; else pxace = &pxdesc->acl.ace[k++]; pxace->tag = POSIX_ACL_USER_OBJ; pxace->id = -1; pxace->perms = POSIX_PERM_R | POSIX_PERM_W | POSIX_PERM_X; ctx[i].tagsset |= POSIX_ACL_USER_OBJ; } /* * Duplicate world perms as group_obj perms if none */ for (i=0; i<2; i++) if ((ctx[i].tagsset & POSIX_ACL_OTHER) && !(ctx[i].tagsset & POSIX_ACL_GROUP_OBJ)) { if (i) pxace = &pxdesc->acl.ace[--l]; else pxace = &pxdesc->acl.ace[k++]; pxace->tag = POSIX_ACL_GROUP_OBJ; pxace->id = -1; pxace->perms = ctx[i].permswrld; ctx[i].tagsset |= POSIX_ACL_GROUP_OBJ; } /* * Also duplicate world perms as group perms if they * were converted to mask and not followed by a group entry */ if (ctx[0].groupmasks) { for (j=k-2; j>=0; j--) { if ((pxdesc->acl.ace[j].tag == POSIX_ACL_MASK) && (pxdesc->acl.ace[j].id != -1) && ((pxdesc->acl.ace[j+1].tag != POSIX_ACL_GROUP) || (pxdesc->acl.ace[j+1].id != pxdesc->acl.ace[j].id))) { pxace = &pxdesc->acl.ace[k]; pxace->tag = POSIX_ACL_GROUP; pxace->id = pxdesc->acl.ace[j].id; pxace->perms = ctx[0].permswrld; ctx[0].tagsset |= POSIX_ACL_GROUP; k++; } if (pxdesc->acl.ace[j].tag == POSIX_ACL_MASK) pxdesc->acl.ace[j].id = -1; } } if (ctx[1].groupmasks) { for (j=l; j<(alloccnt-1); j++) { if ((pxdesc->acl.ace[j].tag == POSIX_ACL_MASK) && (pxdesc->acl.ace[j].id != -1) && ((pxdesc->acl.ace[j+1].tag != POSIX_ACL_GROUP) || (pxdesc->acl.ace[j+1].id != pxdesc->acl.ace[j].id))) { pxace = &pxdesc->acl.ace[l - 1]; pxace->tag = POSIX_ACL_GROUP; pxace->id = pxdesc->acl.ace[j].id; pxace->perms = ctx[1].permswrld; ctx[1].tagsset |= POSIX_ACL_GROUP; l--; } if (pxdesc->acl.ace[j].tag == POSIX_ACL_MASK) pxdesc->acl.ace[j].id = -1; } } /* * Insert default mask if none present and * there are designated users or groups * (the space for it has not beed used) */ for (i=0; i<2; i++) if ((ctx[i].tagsset & (POSIX_ACL_USER | POSIX_ACL_GROUP)) && !(ctx[i].tagsset & POSIX_ACL_MASK)) { if (i) pxace = &pxdesc->acl.ace[--l]; else pxace = &pxdesc->acl.ace[k++]; pxace->tag = POSIX_ACL_MASK; pxace->id = -1; pxace->perms = POSIX_PERM_DENIAL; ctx[i].tagsset |= POSIX_ACL_MASK; } if (k > l) { ntfs_log_error("Posix descriptor is longer than expected\n"); errno = EIO; free(pxdesc); pxdesc = (struct POSIX_SECURITY*)NULL; } else { pxdesc->acccnt = k; pxdesc->defcnt = alloccnt - l; pxdesc->firstdef = l; pxdesc->tagsset = ctx[0].tagsset; pxdesc->acl.version = POSIX_VERSION; pxdesc->acl.flags = 0; pxdesc->acl.filler = 0; ntfs_sort_posix(pxdesc); if (adminowns) { k = norm_ownadmin_permissions_posix(pxdesc, 0, pxdesc->acccnt, 0); pxdesc->acccnt = k; l = norm_ownadmin_permissions_posix(pxdesc, pxdesc->firstdef, pxdesc->defcnt, k); pxdesc->firstdef = k; pxdesc->defcnt = l; } else { k = norm_std_permissions_posix(pxdesc,groupowns, 0, pxdesc->acccnt, 0); pxdesc->acccnt = k; l = norm_std_permissions_posix(pxdesc,groupowns, pxdesc->firstdef, pxdesc->defcnt, k); pxdesc->firstdef = k; pxdesc->defcnt = l; } } if (pxdesc && !ntfs_valid_posix(pxdesc)) { ntfs_log_error("Invalid Posix descriptor built\n"); errno = EIO; free(pxdesc); pxdesc = (struct POSIX_SECURITY*)NULL; } return (pxdesc); } #endif /* POSIXACLS */ /* * Build unix-style (mode_t) permissions from an ACL * returns the requested permissions * or a negative result (with errno set) if there is a problem */ int ntfs_build_permissions(const char *securattr, const SID *usid, const SID *gsid, BOOL isdir) { int perm; BOOL adminowns; BOOL groupowns; adminowns = ntfs_same_sid(usid,adminsid) || ntfs_same_sid(gsid,adminsid); groupowns = !adminowns && ntfs_same_sid(gsid,usid); if (adminowns) perm = build_ownadmin_permissions(securattr, usid, gsid, isdir); else if (groupowns) perm = build_owngrp_permissions(securattr, usid, isdir); else perm = build_std_permissions(securattr, usid, gsid, isdir); return (perm); } /* * The following must be in some library... */ static unsigned long atoul(const char *p) { /* must be somewhere ! */ unsigned long v; v = 0; while ((*p >= '0') && (*p <= '9')) v = v * 10 + (*p++) - '0'; return (v); } /* * Build an internal representation of a SID * Returns a copy in allocated memory if it succeeds * The SID is checked to be a valid user one. */ static SID *encodesid(const char *sidstr) { SID *sid; int cnt; BIGSID bigsid; SID *bsid; u32 auth; const char *p; sid = (SID*) NULL; if (!strncmp(sidstr, "S-1-", 4)) { bsid = (SID*)&bigsid; bsid->revision = SID_REVISION; p = &sidstr[4]; auth = atoul(p); bsid->identifier_authority.high_part = const_cpu_to_be16(0); bsid->identifier_authority.low_part = cpu_to_be32(auth); cnt = 0; p = strchr(p, '-'); while (p && (cnt < 8)) { p++; auth = atoul(p); bsid->sub_authority[cnt] = cpu_to_le32(auth); p = strchr(p, '-'); cnt++; } bsid->sub_authority_count = cnt; if ((cnt > 0) && ntfs_valid_sid(bsid) && ntfs_is_user_sid(bsid)) { sid = (SID*) ntfs_malloc(4 * cnt + 8); if (sid) memcpy(sid, bsid, 4 * cnt + 8); } } return (sid); } /* * Get a single mapping item from buffer * * Always reads a full line, truncating long lines * Refills buffer when exhausted * Returns pointer to item, or NULL when there is no more */ static struct MAPLIST *getmappingitem(FILEREADER reader, void *fileid, off_t *poffs, char *buf, int *psrc, s64 *psize) { int src; int dst; char *q; char *pu; char *pg; int gotend; struct MAPLIST *item; src = *psrc; dst = 0; /* allocate and get a full line */ item = (struct MAPLIST*)ntfs_malloc(sizeof(struct MAPLIST)); if (item) { do { gotend = 0; while ((src < *psize) && (buf[src] != '\n')) { if (dst < LINESZ) item->maptext[dst++] = buf[src]; src++; } if (src >= *psize) { *poffs += *psize; *psize = reader(fileid, buf, (size_t)BUFSZ, *poffs); src = 0; } else { gotend = 1; src++; item->maptext[dst] = '\0'; dst = 0; } } while (*psize && ((item->maptext[0] == '#') || !gotend)); if (gotend) { pu = pg = (char*)NULL; /* decompose into uid, gid and sid */ item->uidstr = item->maptext; item->gidstr = strchr(item->uidstr, ':'); if (item->gidstr) { pu = item->gidstr++; item->sidstr = strchr(item->gidstr, ':'); if (item->sidstr) { pg = item->sidstr++; q = strchr(item->sidstr, ':'); if (q) *q = 0; } } if (pu && pg) *pu = *pg = '\0'; else { ntfs_log_early_error("Bad mapping item \"%s\"\n", item->maptext); free(item); item = (struct MAPLIST*)NULL; } } else { free(item); /* free unused item */ item = (struct MAPLIST*)NULL; } } *psrc = src; return (item); } /* * Read user mapping file and split into their attribute. * Parameters are kept as text in a chained list until logins * are converted to uid. * Returns the head of list, if any * * If an absolute path is provided, the mapping file is assumed * to be located in another mounted file system, and plain read() * are used to get its contents. * If a relative path is provided, the mapping file is assumed * to be located on the current file system, and internal IO * have to be used since we are still mounting and we have not * entered the fuse loop yet. */ struct MAPLIST *ntfs_read_mapping(FILEREADER reader, void *fileid) { char buf[BUFSZ]; struct MAPLIST *item; struct MAPLIST *firstitem; struct MAPLIST *lastitem; int src; off_t offs; s64 size; firstitem = (struct MAPLIST*)NULL; lastitem = (struct MAPLIST*)NULL; offs = 0; size = reader(fileid, buf, (size_t)BUFSZ, (off_t)0); if (size > 0) { src = 0; do { item = getmappingitem(reader, fileid, &offs, buf, &src, &size); if (item) { item->next = (struct MAPLIST*)NULL; if (lastitem) lastitem->next = item; else firstitem = item; lastitem = item; } } while (item); } return (firstitem); } /* * Free memory used to store the user mapping * The only purpose is to facilitate the detection of memory leaks */ void ntfs_free_mapping(struct MAPPING *mapping[]) { struct MAPPING *user; struct MAPPING *group; /* free user mappings */ while (mapping[MAPUSERS]) { user = mapping[MAPUSERS]; /* do not free SIDs used for group mappings */ group = mapping[MAPGROUPS]; while (group && (group->sid != user->sid)) group = group->next; if (!group) free(user->sid); /* free group list if any */ if (user->grcnt) free(user->groups); /* unchain item and free */ mapping[MAPUSERS] = user->next; free(user); } /* free group mappings */ while (mapping[MAPGROUPS]) { group = mapping[MAPGROUPS]; free(group->sid); /* unchain item and free */ mapping[MAPGROUPS] = group->next; free(group); } } /* * Build the user mapping list * user identification may be given in symbolic or numeric format * * ! Note ! : does getpwnam() read /etc/passwd or some other file ? * if so there is a possible recursion into fuse if this * file is on NTFS, and fuse is not recursion safe. */ struct MAPPING *ntfs_do_user_mapping(struct MAPLIST *firstitem) { struct MAPLIST *item; struct MAPPING *firstmapping; struct MAPPING *lastmapping; struct MAPPING *mapping; struct passwd *pwd; SID *sid; int uid; firstmapping = (struct MAPPING*)NULL; lastmapping = (struct MAPPING*)NULL; for (item = firstitem; item; item = item->next) { if ((item->uidstr[0] >= '0') && (item->uidstr[0] <= '9')) uid = atoi(item->uidstr); else { uid = 0; if (item->uidstr[0]) { pwd = getpwnam(item->uidstr); if (pwd) uid = pwd->pw_uid; else ntfs_log_early_error("Invalid user \"%s\"\n", item->uidstr); } } /* * Records with no uid and no gid are inserted * to define the implicit mapping pattern */ if (uid || (!item->uidstr[0] && !item->gidstr[0])) { sid = encodesid(item->sidstr); if (sid && !item->uidstr[0] && !item->gidstr[0] && !ntfs_valid_pattern(sid)) { ntfs_log_error("Bad implicit SID pattern %s\n", item->sidstr); sid = (SID*)NULL; } if (sid) { mapping = (struct MAPPING*) ntfs_malloc(sizeof(struct MAPPING)); if (mapping) { mapping->sid = sid; mapping->xid = uid; mapping->grcnt = 0; mapping->next = (struct MAPPING*)NULL; if (lastmapping) lastmapping->next = mapping; else firstmapping = mapping; lastmapping = mapping; } } } } return (firstmapping); } /* * Build the group mapping list * group identification may be given in symbolic or numeric format * * gid not associated to a uid are processed first in order * to favour real groups * * ! Note ! : does getgrnam() read /etc/group or some other file ? * if so there is a possible recursion into fuse if this * file is on NTFS, and fuse is not recursion safe. */ struct MAPPING *ntfs_do_group_mapping(struct MAPLIST *firstitem) { struct MAPLIST *item; struct MAPPING *firstmapping; struct MAPPING *lastmapping; struct MAPPING *mapping; struct group *grp; BOOL secondstep; BOOL ok; int step; SID *sid; int gid; firstmapping = (struct MAPPING*)NULL; lastmapping = (struct MAPPING*)NULL; for (step=1; step<=2; step++) { for (item = firstitem; item; item = item->next) { secondstep = (item->uidstr[0] != '\0') || !item->gidstr[0]; ok = (step == 1 ? !secondstep : secondstep); if ((item->gidstr[0] >= '0') && (item->gidstr[0] <= '9')) gid = atoi(item->gidstr); else { gid = 0; if (item->gidstr[0]) { grp = getgrnam(item->gidstr); if (grp) gid = grp->gr_gid; else ntfs_log_early_error("Invalid group \"%s\"\n", item->gidstr); } } /* * Records with no uid and no gid are inserted in the * second step to define the implicit mapping pattern */ if (ok && (gid || (!item->uidstr[0] && !item->gidstr[0]))) { sid = encodesid(item->sidstr); if (sid && !item->uidstr[0] && !item->gidstr[0] && !ntfs_valid_pattern(sid)) { /* error already logged */ sid = (SID*)NULL; } if (sid) { mapping = (struct MAPPING*) ntfs_malloc(sizeof(struct MAPPING)); if (mapping) { mapping->sid = sid; mapping->xid = gid; mapping->grcnt = 0; mapping->next = (struct MAPPING*)NULL; if (lastmapping) lastmapping->next = mapping; else firstmapping = mapping; lastmapping = mapping; } } } } } return (firstmapping); }