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 <dev@ixsystems.com>  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.
