Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add nfsv4-specific permissions checks to kernel #2

Merged
merged 1 commit into from
May 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions fs/attr.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
rick-mesta marked this conversation as resolved.
Show resolved Hide resolved
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) &&
Expand All @@ -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().
*/
anodos325 marked this conversation as resolved.
Show resolved Hide resolved
if (!inode_owner_or_capable(inode))
return -EPERM;
}
Expand Down Expand Up @@ -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);
rick-mesta marked this conversation as resolved.
Show resolved Hide resolved
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;
}
Expand Down
46 changes: 45 additions & 1 deletion fs/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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))
Expand Down
42 changes: 40 additions & 2 deletions fs/xattr.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
/*
Expand All @@ -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;
}

Expand All @@ -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;
}

Expand Down Expand Up @@ -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) {
rick-mesta marked this conversation as resolved.
Show resolved Hide resolved
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;

Expand Down Expand Up @@ -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;

Expand Down
16 changes: 16 additions & 0 deletions include/linux/fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
rick-mesta marked this conversation as resolved.
Show resolved Hide resolved
#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()
Expand Down
6 changes: 6 additions & 0 deletions scripts/package/truenas/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
linux-5.10.18+truenas (5.10.18+truenas-2) sid; urgency=low

* Implement NFS4 ACL support

-- iXsystems engineering team <[email protected]> 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.
Expand Down