diff --git a/fs/attr.c b/fs/attr.c index b4bbdbd4c8ca0..ec7c112de31ff 100644 --- a/fs/attr.c +++ b/fs/attr.c @@ -78,17 +78,63 @@ int setattr_prepare(struct dentry *dentry, struct iattr *attr) goto kill_priv; /* Make sure a caller can chown. */ +#if CONFIG_TRUENAS + /* + * Check for ACE4_WRITE_OWNER. RFC 5661 Section 6.2.1.3.1 + * On UNIX systems, this is the ability to execute chown() and + * chgrp(). + */ + if ((ia_valid & ATTR_UID) && !chown_ok(inode, attr->ia_uid)) { + if (!IS_NFSV4ACL(inode)) { + return -EPERM; + } + else if (inode_permission(inode, MAY_WRITE_OWNER)) { + return -EPERM; + } + } +#else if ((ia_valid & ATTR_UID) && !chown_ok(inode, attr->ia_uid)) return -EPERM; +#endif /* Make sure caller can chgrp. */ +#if CONFIG_TRUENAS + if ((ia_valid & ATTR_GID) && !chgrp_ok(inode, attr->ia_gid)) { + if (!IS_NFSV4ACL(inode)) { + return -EPERM; + } + else if (inode_permission(inode, MAY_WRITE_OWNER)) { + return -EPERM; + } + } +#else if ((ia_valid & ATTR_GID) && !chgrp_ok(inode, attr->ia_gid)) return -EPERM; +#endif /* Make sure a caller can chmod. */ if (ia_valid & ATTR_MODE) { +#if CONFIG_TRUENAS + /* + * Check for ACE4_WRITE_ACL. RFC 5661 Section 6.2.1.3.1 + * Permission to write the acl or mode attributes. + */ + if (IS_NFSV4ACL(inode)) { + if (!inode_owner_or_capable(inode)) { + if (inode_permission(inode, MAY_WRITE_ACL)) { + return -EPERM; + } + } + } + else { + if (!inode_owner_or_capable(inode)) + return -EPERM; + } + +#else if (!inode_owner_or_capable(inode)) return -EPERM; +#endif /* Also check the setgid bit! */ if (!in_group_p((ia_valid & ATTR_GID) ? attr->ia_gid : inode->i_gid) && @@ -98,6 +144,12 @@ int setattr_prepare(struct dentry *dentry, struct iattr *attr) /* Check for setting the inode time. */ if (ia_valid & (ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET)) { + /* + * Check for ACE4_WRITE_ATTRIBUTES. RFC 5661 Section 6.2.1.3.1 + * Users with ACE4_WRITE_ATTRIBUTES or ACE4_WRITE_DATA can + * change the times associated with a file to the _current_ + * server time. This permissions check happens in notify_change(). + */ if (!inode_owner_or_capable(inode)) return -EPERM; } @@ -244,7 +296,19 @@ int notify_change(struct dentry * dentry, struct iattr * attr, struct inode **de return -EPERM; if (!inode_owner_or_capable(inode)) { +#if CONFIG_TRUENAS + if (IS_NFSV4ACL(inode)) { + error = inode_permission(inode, MAY_WRITE); + if (error) { + error = inode_permission(inode, MAY_WRITE_ATTRS); + } + } + else { + error = inode_permission(inode, MAY_WRITE); + } +#else error = inode_permission(inode, MAY_WRITE); +#endif if (error) return error; } diff --git a/fs/namei.c b/fs/namei.c index d4a6dd7723038..59788ccaa28c5 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -415,7 +415,16 @@ static inline int do_inode_permission(struct inode *inode, int mask) */ static int sb_permission(struct super_block *sb, struct inode *inode, int mask) { +#if CONFIG_TRUENAS + /* + * NFSv4 ACLs have more granular write permissions. Same logic + * should apply here as with generic MAY_WRITE. Specifically, protect + * against changes to a readonly filesystem. + */ + if (unlikely(mask & (MAY_WRITE | NFS41ACL_WRITE_ALL))) { +#else if (unlikely(mask & MAY_WRITE)) { +#endif umode_t mode = inode->i_mode; /* Nobody gets write access to a read-only fs. */ @@ -444,7 +453,15 @@ int inode_permission(struct inode *inode, int mask) if (retval) return retval; +#if CONFIG_TRUENAS + /* + * NFSv4 ACLs have more granular write permissions. Same logic + * should apply here as with generic MAY_WRITE. + */ + if (unlikely(mask & (MAY_WRITE | NFS41ACL_WRITE_ALL))) { +#else if (unlikely(mask & MAY_WRITE)) { +#endif /* * Nobody gets write access to an immutable file. */ @@ -2697,8 +2714,35 @@ static int may_delete(struct inode *dir, struct dentry *victim, bool isdir) return -EOVERFLOW; audit_inode_child(dir, victim, AUDIT_TYPE_CHILD_DELETE); - +#if CONFIG_TRUENAS + if (IS_NFSV4ACL(inode)) { + /* + * See RFC 5661 Section 6.2.1.3.2 + * for implementation details of DELETE vs DELETE_CHILD. + * + * Since there may be a variety of ways to implement + * allow in VFS if MAY_DELETE is permitted on viction, + * MAY_DELETE_CHILD is permitted on directory, or MAY_WRITE + * and MAY_EXEC are permitted on directory. This allows + * filesystem to enforce stricter permissions if needed. + * + * MAY_WRITE|MAY_EXEC is checked first to give opportunity + * to perform check via generic_permission() first. + */ + error = inode_permission(dir, MAY_WRITE | MAY_EXEC); + if (error) { + error = inode_permission(inode, MAY_DELETE); + if (error) { + error = inode_permission(dir, MAY_DELETE_CHILD); + } + } + } + else { + error = inode_permission(dir, MAY_WRITE | MAY_EXEC); + } +#else error = inode_permission(dir, MAY_WRITE | MAY_EXEC); +#endif if (error) return error; if (IS_APPEND(dir)) diff --git a/fs/xattr.c b/fs/xattr.c index a3d537aa59816..b83043e2e3045 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -89,7 +89,11 @@ xattr_permission(struct inode *inode, const char *name, int mask) * We can never set or remove an extended attribute on a read-only * filesystem or on an immutable / append-only inode. */ +#if CONFIG_TRUENAS + if (mask & (MAY_WRITE | MAY_WRITE_NAMED_ATTRS)) { +#else if (mask & MAY_WRITE) { +#endif if (IS_IMMUTABLE(inode) || IS_APPEND(inode)) return -EPERM; /* @@ -114,7 +118,11 @@ xattr_permission(struct inode *inode, const char *name, int mask) */ if (!strncmp(name, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN)) { if (!capable(CAP_SYS_ADMIN)) +#if CONFIG_TRUENAS + return (mask & (MAY_WRITE | MAY_WRITE_NAMED_ATTRS)) ? -EPERM : -ENODATA; +#else return (mask & MAY_WRITE) ? -EPERM : -ENODATA; +#endif return 0; } @@ -125,9 +133,17 @@ xattr_permission(struct inode *inode, const char *name, int mask) */ if (!strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN)) { if (!S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode)) +#if CONFIG_TRUENAS + return (mask & (MAY_WRITE | MAY_WRITE_NAMED_ATTRS)) ? -EPERM : -ENODATA; +#else return (mask & MAY_WRITE) ? -EPERM : -ENODATA; +#endif if (S_ISDIR(inode->i_mode) && (inode->i_mode & S_ISVTX) && +#if CONFIG_TRUENAS + (mask & (MAY_WRITE | MAY_WRITE_NAMED_ATTRS)) && !inode_owner_or_capable(inode)) +#else (mask & MAY_WRITE) && !inode_owner_or_capable(inode)) +#endif return -EPERM; } @@ -250,8 +266,19 @@ __vfs_setxattr_locked(struct dentry *dentry, const char *name, { struct inode *inode = dentry->d_inode; int error; - +#if CONFIG_TRUENAS + if (IS_NFSV4ACL(inode)) { + error = xattr_permission(inode, name, MAY_WRITE); + if (error) { + error = xattr_permission(inode, name, MAY_WRITE_NAMED_ATTRS); + } + } + else { + error = xattr_permission(inode, name, MAY_WRITE); + } +#else error = xattr_permission(inode, name, MAY_WRITE); +#endif if (error) return error; @@ -457,8 +484,19 @@ __vfs_removexattr_locked(struct dentry *dentry, const char *name, { struct inode *inode = dentry->d_inode; int error; - +#if CONFIG_TRUENAS + if (IS_NFSV4ACL(inode)) { + error = xattr_permission(inode, name, MAY_WRITE); + if (error) { + error = xattr_permission(inode, name, MAY_WRITE_NAMED_ATTRS); + } + } + else { + error = xattr_permission(inode, name, MAY_WRITE); + } +#else error = xattr_permission(inode, name, MAY_WRITE); +#endif if (error) return error; diff --git a/include/linux/fs.h b/include/linux/fs.h index 61d0516fa897c..3d8d57b8e8400 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -102,6 +102,22 @@ typedef int (dio_iodone_t)(struct kiocb *iocb, loff_t offset, /* called from RCU mode, don't block */ #define MAY_NOT_BLOCK 0x00000080 +#if CONFIG_TRUENAS +/* + * Extended NFSv41 write permissions. These are used selectively + * for permissions checks where NFSv4 ACL handling is + * more nuanced than a simple POSIX permissions. + */ +#define MAY_WRITE_NAMED_ATTRS 0x00000100 +#define MAY_DELETE_CHILD 0x00000400 +#define MAY_WRITE_ATTRS 0x00001000 +#define MAY_DELETE 0x00100000 +#define MAY_WRITE_ACL 0x00400000 +#define MAY_WRITE_OWNER 0x00800000 +#define NFS41ACL_WRITE_ALL (MAY_DELETE_CHILD|MAY_WRITE_ATTRS|MAY_DELETE|\ + MAY_WRITE_ACL|MAY_WRITE_OWNER|MAY_WRITE_NAMED_ATTRS) +#endif + /* * flags in file.f_mode. Note that FMODE_READ and FMODE_WRITE must correspond * to O_WRONLY and O_RDWR via the strange trick in do_dentry_open() diff --git a/scripts/package/truenas/changelog b/scripts/package/truenas/changelog index 063d29ac61801..bd04f65381acd 100644 --- a/scripts/package/truenas/changelog +++ b/scripts/package/truenas/changelog @@ -1,3 +1,9 @@ +linux-5.10.18+truenas (5.10.18+truenas-2) sid; urgency=low + + * Implement NFS4 ACL support + + -- iXsystems engineering team Tue, 27 Apr 2021 10:21:00 -0500 + linux-5.10.18+truenas (5.10.18+truenas-1) sid; urgency=low * Import upstream Linux 5.10 branch.