diff options
Diffstat (limited to 'lib/file-has-acl.c')
-rw-r--r-- | lib/file-has-acl.c | 120 |
1 files changed, 119 insertions, 1 deletions
diff --git a/lib/file-has-acl.c b/lib/file-has-acl.c index e02f0626ad3..676523ba821 100644 --- a/lib/file-has-acl.c +++ b/lib/file-has-acl.c @@ -29,9 +29,97 @@ #include "acl-internal.h" -#if GETXATTR_WITH_POSIX_ACLS +#if USE_ACL && GETXATTR_WITH_POSIX_ACLS +# include <string.h> +# include <arpa/inet.h> # include <sys/xattr.h> # include <linux/xattr.h> +# ifndef XATTR_NAME_NFSV4_ACL +# define XATTR_NAME_NFSV4_ACL "system.nfs4_acl" +# endif + +enum { + /* ACE4_ACCESS_ALLOWED_ACE_TYPE = 0x00000000, */ + ACE4_ACCESS_DENIED_ACE_TYPE = 0x00000001, + ACE4_IDENTIFIER_GROUP = 0x00000040 +}; + +/* Return 1 if given ACL in XDR format is non-trivial, 0 if it is trivial. + -1 upon failure to determine it. Possibly change errno. Assume that + the ACL is valid, except avoid undefined behavior even if invalid. + + See <https://linux.die.net/man/5/nfs4_acl>. The NFSv4 acls are + defined in Internet RFC 7530 and as such, every NFSv4 server + supporting ACLs should support NFSv4 ACLs (they differ from from + POSIX draft ACLs). The ACLs can be obtained via the + nfsv4-acl-tools, e.g., the nfs4_getfacl command. Gnulib provides + only basic support of NFSv4 ACLs, i.e., recognize trivial vs + nontrivial ACLs. */ + +static int +acl_nfs4_nontrivial (uint32_t *xattr, ssize_t nbytes) +{ + enum { BYTES_PER_NETWORK_UINT = 4}; + + /* Grab the number of aces in the acl. */ + nbytes -= BYTES_PER_NETWORK_UINT; + if (nbytes < 0) + return -1; + uint32_t num_aces = ntohl (*xattr++); + if (6 < num_aces) + return 1; + int ace_found = 0; + + for (int ace_n = 0; ace_n < num_aces; ace_n++) + { + /* Get the acl type and flag. Skip the mask; it's too risky to + test it and it does not seem to be needed. Get the wholen. */ + nbytes -= 4 * BYTES_PER_NETWORK_UINT; + if (nbytes < 0) + return -1; + uint32_t type = ntohl (xattr[0]); + uint32_t flag = ntohl (xattr[1]); + uint32_t wholen = ntohl (xattr[3]); + xattr += 4; + int64_t wholen4 = wholen; + wholen4 = ((wholen4 + (BYTES_PER_NETWORK_UINT)) + & ~ (BYTES_PER_NETWORK_UINT - 1)); + + /* Trivial ACLs have only ACE4_ACCESS_ALLOWED_ACE_TYPE or + ACE4_ACCESS_DENIED_ACE_TYPE. */ + if (ACE4_ACCESS_DENIED_ACE_TYPE < type) + return 1; + + /* RFC 7530 says FLAG should be 0, but be generous to NetApp and + also accept the group flag. */ + if (flag & ~ACE4_IDENTIFIER_GROUP) + return 1; + + /* Get the who string. Check NBYTES - WHOLEN4 before storing + into NBYTES, to avoid truncation on conversion. */ + if (nbytes - wholen4 < 0) + return -1; + nbytes -= wholen4; + + /* For a trivial ACL, max 6 (typically 3) ACEs, 3 allow, 3 deny. + Check that there is at most one ACE of each TYPE and WHO. */ + int who2 + = (wholen == 6 && memcmp (xattr, "OWNER@", 6) == 0 ? 0 + : wholen == 6 && memcmp (xattr, "GROUP@", 6) == 0 ? 2 + : wholen == 9 && memcmp (xattr, "EVERYONE@", 9) == 0 ? 4 + : -1); + if (who2 < 0) + return 1; + int ace_found_bit = 1 << (who2 | type); + if (ace_found & ace_found_bit) + return 1; + ace_found |= ace_found_bit; + + xattr = (uint32_t *) ((char *) xattr + wholen4); + } + + return 0; +} #endif /* Return 1 if NAME has a nontrivial access control list, @@ -51,6 +139,7 @@ file_has_acl (char const *name, struct stat const *sb) # if GETXATTR_WITH_POSIX_ACLS ssize_t ret; + int initial_errno = errno; ret = getxattr (name, XATTR_NAME_POSIX_ACL_ACCESS, NULL, 0); if (ret < 0 && errno == ENODATA) @@ -68,6 +157,35 @@ file_has_acl (char const *name, struct stat const *sb) } if (ret < 0) + { + /* Check for NFSv4 ACLs. The max length of a trivial + ACL is 6 words for owner, 6 for group, 7 for everyone, + all times 2 because there are both allow and deny ACEs. + There are 6 words for owner because of type, flag, mask, + wholen, "OWNER@"+pad and similarly for group; everyone is + another word to hold "EVERYONE@". */ + uint32_t xattr[2 * (6 + 6 + 7)]; + + ret = getxattr (name, XATTR_NAME_NFSV4_ACL, xattr, sizeof xattr); + if (ret < 0) + switch (errno) + { + case ENODATA: return 0; + case ERANGE : return 1; /* ACL must be nontrivial. */ + } + else + { + /* It looks like a trivial ACL, but investigate further. */ + ret = acl_nfs4_nontrivial (xattr, ret); + if (ret < 0) + { + errno = EINVAL; + return ret; + } + errno = initial_errno; + } + } + if (ret < 0) return - acl_errno_valid (errno); return ret; |