All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC v7 00/41] Richacls
@ 2015-09-05 10:26 ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:26 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

Hello,

here's another update of the richacl patch queue.  Changes since the last
posting (https://lwn.net/Articles/653459/):

 * The owner argument of richacl_compute_max_masks() has been
   removed; the maximum file masks are computed independent of
   the current file owner; with the previous version, the group mask
   could have ended up being too restrictive after an owner change.

 * Function richace_clear_inheritance_flags() has been replaced
   by the RICHACE_INHERITANCE_FLAGS define.

 * Definitions common to the kernel and to user space have been
    moved into uapi header files.

 * A few other minor fixes and clarifications.

In addition, the user-space utilitites now include a man page describing how
richacls work:

  https://github.com/andreas-gruenbacher/richacl/blob/master/man/richacl.7

The complete patch queue is available here:

  git://git.kernel.org/pub/scm/linux/kernel/git/agruen/linux-richacl.git \
        richacl-2015-09-xx

Open issues:

 * nfs: When a user or group name cannot be mapped, nfs's idmapper
   always maps it to nobody. That's good enough for mapping the file
   owner and owning group, but not for identifiers in acls. For now, to get
   the nfs richacl support somewhat working, I'm explicitly checking if
   mapping has resulted in uid/gid 99 in the kernel.

 * nfs: When the nfs server replies with NFS4ERR_BADNAME for any
   user or group name lookup, the client will stop sending numeric uids
   and gids to the server even when the lookup wasn't numeric.
   From then on, the client will translate uids and gids that have no
   mapping to the string "nobody", and the server will reject them.
   This problem is not specific to acls.

 * nfsd could implement creating a file without applying a file mode;
   this would make Automatic Inheritance work as intended. Right
   now, when no mode is specified, the file permission bits are set
   to 0.

 * It would be nice if the MAY_DELETE_SELF flag could override the
   sticky directory check as it did in the previous version of this patch
   queue.  I couldn't come up with a clean way of achieving that.

Thanks,
Andreas

Andreas Gruenbacher (39):
  vfs: Add IS_ACL() and IS_RICHACL() tests
  vfs: Add MAY_CREATE_FILE and MAY_CREATE_DIR permission flags
  vfs: Add MAY_DELETE_SELF and MAY_DELETE_CHILD permission flags
  vfs: Make the inode passed to inode_change_ok non-const
  vfs: Add permission flags for setting file attributes
  richacl: In-memory representation and helper functions
  richacl: Permission mapping functions
  richacl: Compute maximum file masks from an acl
  richacl: Update the file masks in chmod()
  richacl: Permission check algorithm
  vfs: Cache base_acl objects in inodes
  vfs: Cache richacl in struct inode
  richacl: Check if an acl is equivalent to a file mode
  richacl: Create-time inheritance
  richacl: Automatic Inheritance
  richacl: xattr mapping functions
  vfs: Add richacl permission checking
  richacl: acl editing helper functions
  richacl: Move everyone@ aces down the acl
  richacl: Propagate everyone@ permissions to other aces
  richacl: Set the owner permissions to the owner mask
  richacl: Set the other permissions to the other mask
  richacl: Isolate the owner and group classes
  richacl: Apply the file masks to a richacl
  richacl: Create richacl from mode values
  nfsd: Keep list of acls to dispose of in compoundargs
  nfsd: Use richacls as internal acl representation
  nfsd: Add richacl support
  nfsd: Add support for the v4.1 dacl attribute
  nfsd: Add support for the MAY_CREATE_{FILE,DIR} permissions
  richacl: Add support for unmapped identifiers
  ext4: Don't allow unmapped identifiers in richacls
  sunrpc: Allow to demand-allocate pages to encode into
  sunrpc: Add xdr_init_encode_pages
  nfs: Fix GETATTR bitmap verification
  nfs: Remove unused xdr page offsets in getacl/setacl arguments
  nfs: Add richacl support
  nfs: Add support for the v4.1 dacl attribute
  richacl: uapi header split

Aneesh Kumar K.V (2):
  ext4: Add richacl support
  ext4: Add richacl feature flag

 drivers/staging/lustre/lustre/llite/llite_lib.c |   2 +-
 fs/Kconfig                                      |   9 +
 fs/Makefile                                     |   3 +
 fs/attr.c                                       |  81 ++-
 fs/ext4/Kconfig                                 |  15 +
 fs/ext4/Makefile                                |   1 +
 fs/ext4/acl.c                                   |   6 +-
 fs/ext4/acl.h                                   |  12 +-
 fs/ext4/ext4.h                                  |   6 +-
 fs/ext4/file.c                                  |   6 +-
 fs/ext4/ialloc.c                                |   7 +-
 fs/ext4/inode.c                                 |  10 +-
 fs/ext4/namei.c                                 |  11 +-
 fs/ext4/richacl.c                               | 218 ++++++
 fs/ext4/richacl.h                               |  47 ++
 fs/ext4/super.c                                 |  42 +-
 fs/ext4/xattr.c                                 |   6 +
 fs/ext4/xattr.h                                 |   1 +
 fs/f2fs/acl.c                                   |   4 +-
 fs/inode.c                                      |  15 +-
 fs/jffs2/acl.c                                  |   6 +-
 fs/namei.c                                      | 109 ++-
 fs/nfs/inode.c                                  |   3 -
 fs/nfs/nfs4proc.c                               | 701 +++++++++++++-----
 fs/nfs/nfs4xdr.c                                | 257 ++++++-
 fs/nfs/super.c                                  |   4 +-
 fs/nfs_common/Makefile                          |   1 +
 fs/nfs_common/nfs4acl.c                         |  44 ++
 fs/nfsd/Kconfig                                 |   1 +
 fs/nfsd/acl.h                                   |  23 +-
 fs/nfsd/nfs4acl.c                               | 481 ++++++------
 fs/nfsd/nfs4proc.c                              |  24 +-
 fs/nfsd/nfs4xdr.c                               | 268 ++++---
 fs/nfsd/nfsd.h                                  |   6 +-
 fs/nfsd/nfsfh.c                                 |   8 +-
 fs/nfsd/vfs.c                                   |  28 +-
 fs/nfsd/vfs.h                                   |  17 +-
 fs/nfsd/xdr4.h                                  |  12 +-
 fs/posix_acl.c                                  |  26 +-
 fs/richacl_base.c                               | 683 +++++++++++++++++
 fs/richacl_compat.c                             | 928 ++++++++++++++++++++++++
 fs/richacl_inode.c                              | 296 ++++++++
 fs/richacl_xattr.c                              | 267 +++++++
 fs/xattr.c                                      |  34 +-
 include/linux/fs.h                              |  50 +-
 include/linux/nfs4.h                            |  24 +-
 include/linux/nfs4acl.h                         |   7 +
 include/linux/nfs_fs.h                          |   1 -
 include/linux/nfs_fs_sb.h                       |   2 +
 include/linux/nfs_xdr.h                         |  13 +-
 include/linux/posix_acl.h                       |  12 +-
 include/linux/richacl.h                         | 275 +++++++
 include/linux/richacl_compat.h                  |  40 +
 include/linux/richacl_xattr.h                   |  47 ++
 include/linux/sunrpc/xdr.h                      |   2 +
 include/uapi/linux/Kbuild                       |   2 +
 include/uapi/linux/fs.h                         |   3 +-
 include/uapi/linux/nfs4.h                       |   3 +-
 include/uapi/linux/richacl.h                    | 111 +++
 include/uapi/linux/richacl_xattr.h              |  43 ++
 include/uapi/linux/xattr.h                      |   2 +
 net/sunrpc/xdr.c                                |  34 +
 62 files changed, 4672 insertions(+), 728 deletions(-)
 create mode 100644 fs/ext4/richacl.c
 create mode 100644 fs/ext4/richacl.h
 create mode 100644 fs/nfs_common/nfs4acl.c
 create mode 100644 fs/richacl_base.c
 create mode 100644 fs/richacl_compat.c
 create mode 100644 fs/richacl_inode.c
 create mode 100644 fs/richacl_xattr.c
 create mode 100644 include/linux/nfs4acl.h
 create mode 100644 include/linux/richacl.h
 create mode 100644 include/linux/richacl_compat.h
 create mode 100644 include/linux/richacl_xattr.h
 create mode 100644 include/uapi/linux/richacl.h
 create mode 100644 include/uapi/linux/richacl_xattr.h

-- 
2.4.3

^ permalink raw reply	[flat|nested] 188+ messages in thread

* [RFC v7 00/41] Richacls
@ 2015-09-05 10:26 ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:26 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

Hello,

here's another update of the richacl patch queue.  Changes since the last
posting (https://lwn.net/Articles/653459/):

 * The owner argument of richacl_compute_max_masks() has been
   removed; the maximum file masks are computed independent of
   the current file owner; with the previous version, the group mask
   could have ended up being too restrictive after an owner change.

 * Function richace_clear_inheritance_flags() has been replaced
   by the RICHACE_INHERITANCE_FLAGS define.

 * Definitions common to the kernel and to user space have been
    moved into uapi header files.

 * A few other minor fixes and clarifications.

In addition, the user-space utilitites now include a man page describing how
richacls work:

  https://github.com/andreas-gruenbacher/richacl/blob/master/man/richacl.7

The complete patch queue is available here:

  git://git.kernel.org/pub/scm/linux/kernel/git/agruen/linux-richacl.git \
        richacl-2015-09-xx

Open issues:

 * nfs: When a user or group name cannot be mapped, nfs's idmapper
   always maps it to nobody. That's good enough for mapping the file
   owner and owning group, but not for identifiers in acls. For now, to get
   the nfs richacl support somewhat working, I'm explicitly checking if
   mapping has resulted in uid/gid 99 in the kernel.

 * nfs: When the nfs server replies with NFS4ERR_BADNAME for any
   user or group name lookup, the client will stop sending numeric uids
   and gids to the server even when the lookup wasn't numeric.
   From then on, the client will translate uids and gids that have no
   mapping to the string "nobody", and the server will reject them.
   This problem is not specific to acls.

 * nfsd could implement creating a file without applying a file mode;
   this would make Automatic Inheritance work as intended. Right
   now, when no mode is specified, the file permission bits are set
   to 0.

 * It would be nice if the MAY_DELETE_SELF flag could override the
   sticky directory check as it did in the previous version of this patch
   queue.  I couldn't come up with a clean way of achieving that.

Thanks,
Andreas

Andreas Gruenbacher (39):
  vfs: Add IS_ACL() and IS_RICHACL() tests
  vfs: Add MAY_CREATE_FILE and MAY_CREATE_DIR permission flags
  vfs: Add MAY_DELETE_SELF and MAY_DELETE_CHILD permission flags
  vfs: Make the inode passed to inode_change_ok non-const
  vfs: Add permission flags for setting file attributes
  richacl: In-memory representation and helper functions
  richacl: Permission mapping functions
  richacl: Compute maximum file masks from an acl
  richacl: Update the file masks in chmod()
  richacl: Permission check algorithm
  vfs: Cache base_acl objects in inodes
  vfs: Cache richacl in struct inode
  richacl: Check if an acl is equivalent to a file mode
  richacl: Create-time inheritance
  richacl: Automatic Inheritance
  richacl: xattr mapping functions
  vfs: Add richacl permission checking
  richacl: acl editing helper functions
  richacl: Move everyone@ aces down the acl
  richacl: Propagate everyone@ permissions to other aces
  richacl: Set the owner permissions to the owner mask
  richacl: Set the other permissions to the other mask
  richacl: Isolate the owner and group classes
  richacl: Apply the file masks to a richacl
  richacl: Create richacl from mode values
  nfsd: Keep list of acls to dispose of in compoundargs
  nfsd: Use richacls as internal acl representation
  nfsd: Add richacl support
  nfsd: Add support for the v4.1 dacl attribute
  nfsd: Add support for the MAY_CREATE_{FILE,DIR} permissions
  richacl: Add support for unmapped identifiers
  ext4: Don't allow unmapped identifiers in richacls
  sunrpc: Allow to demand-allocate pages to encode into
  sunrpc: Add xdr_init_encode_pages
  nfs: Fix GETATTR bitmap verification
  nfs: Remove unused xdr page offsets in getacl/setacl arguments
  nfs: Add richacl support
  nfs: Add support for the v4.1 dacl attribute
  richacl: uapi header split

Aneesh Kumar K.V (2):
  ext4: Add richacl support
  ext4: Add richacl feature flag

 drivers/staging/lustre/lustre/llite/llite_lib.c |   2 +-
 fs/Kconfig                                      |   9 +
 fs/Makefile                                     |   3 +
 fs/attr.c                                       |  81 ++-
 fs/ext4/Kconfig                                 |  15 +
 fs/ext4/Makefile                                |   1 +
 fs/ext4/acl.c                                   |   6 +-
 fs/ext4/acl.h                                   |  12 +-
 fs/ext4/ext4.h                                  |   6 +-
 fs/ext4/file.c                                  |   6 +-
 fs/ext4/ialloc.c                                |   7 +-
 fs/ext4/inode.c                                 |  10 +-
 fs/ext4/namei.c                                 |  11 +-
 fs/ext4/richacl.c                               | 218 ++++++
 fs/ext4/richacl.h                               |  47 ++
 fs/ext4/super.c                                 |  42 +-
 fs/ext4/xattr.c                                 |   6 +
 fs/ext4/xattr.h                                 |   1 +
 fs/f2fs/acl.c                                   |   4 +-
 fs/inode.c                                      |  15 +-
 fs/jffs2/acl.c                                  |   6 +-
 fs/namei.c                                      | 109 ++-
 fs/nfs/inode.c                                  |   3 -
 fs/nfs/nfs4proc.c                               | 701 +++++++++++++-----
 fs/nfs/nfs4xdr.c                                | 257 ++++++-
 fs/nfs/super.c                                  |   4 +-
 fs/nfs_common/Makefile                          |   1 +
 fs/nfs_common/nfs4acl.c                         |  44 ++
 fs/nfsd/Kconfig                                 |   1 +
 fs/nfsd/acl.h                                   |  23 +-
 fs/nfsd/nfs4acl.c                               | 481 ++++++------
 fs/nfsd/nfs4proc.c                              |  24 +-
 fs/nfsd/nfs4xdr.c                               | 268 ++++---
 fs/nfsd/nfsd.h                                  |   6 +-
 fs/nfsd/nfsfh.c                                 |   8 +-
 fs/nfsd/vfs.c                                   |  28 +-
 fs/nfsd/vfs.h                                   |  17 +-
 fs/nfsd/xdr4.h                                  |  12 +-
 fs/posix_acl.c                                  |  26 +-
 fs/richacl_base.c                               | 683 +++++++++++++++++
 fs/richacl_compat.c                             | 928 ++++++++++++++++++++++++
 fs/richacl_inode.c                              | 296 ++++++++
 fs/richacl_xattr.c                              | 267 +++++++
 fs/xattr.c                                      |  34 +-
 include/linux/fs.h                              |  50 +-
 include/linux/nfs4.h                            |  24 +-
 include/linux/nfs4acl.h                         |   7 +
 include/linux/nfs_fs.h                          |   1 -
 include/linux/nfs_fs_sb.h                       |   2 +
 include/linux/nfs_xdr.h                         |  13 +-
 include/linux/posix_acl.h                       |  12 +-
 include/linux/richacl.h                         | 275 +++++++
 include/linux/richacl_compat.h                  |  40 +
 include/linux/richacl_xattr.h                   |  47 ++
 include/linux/sunrpc/xdr.h                      |   2 +
 include/uapi/linux/Kbuild                       |   2 +
 include/uapi/linux/fs.h                         |   3 +-
 include/uapi/linux/nfs4.h                       |   3 +-
 include/uapi/linux/richacl.h                    | 111 +++
 include/uapi/linux/richacl_xattr.h              |  43 ++
 include/uapi/linux/xattr.h                      |   2 +
 net/sunrpc/xdr.c                                |  34 +
 62 files changed, 4672 insertions(+), 728 deletions(-)
 create mode 100644 fs/ext4/richacl.c
 create mode 100644 fs/ext4/richacl.h
 create mode 100644 fs/nfs_common/nfs4acl.c
 create mode 100644 fs/richacl_base.c
 create mode 100644 fs/richacl_compat.c
 create mode 100644 fs/richacl_inode.c
 create mode 100644 fs/richacl_xattr.c
 create mode 100644 include/linux/nfs4acl.h
 create mode 100644 include/linux/richacl.h
 create mode 100644 include/linux/richacl_compat.h
 create mode 100644 include/linux/richacl_xattr.h
 create mode 100644 include/uapi/linux/richacl.h
 create mode 100644 include/uapi/linux/richacl_xattr.h

-- 
2.4.3


^ permalink raw reply	[flat|nested] 188+ messages in thread

* [RFC v7 01/41] vfs: Add IS_ACL() and IS_RICHACL() tests
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:26     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:26 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

The vfs does not apply the umask for file systems that support acls. The
test used for this used to be called IS_POSIXACL(). Switch to a new
IS_ACL() test to check for either posix acls or richacls instead. Add a new
MS_RICHACL flag and IS_RICHACL() test for richacls alone. The IS_POSIXACL()
test is still needed by nfsd.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
Reviewed-by: J. Bruce Fields <bfields-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/Kconfig              |  3 +++
 fs/namei.c              |  8 ++++----
 include/linux/fs.h      | 12 ++++++++++++
 include/uapi/linux/fs.h |  3 ++-
 4 files changed, 21 insertions(+), 5 deletions(-)

diff --git a/fs/Kconfig b/fs/Kconfig
index 011f433..3e09c06 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -59,6 +59,9 @@ endif # BLOCK
 config FS_POSIX_ACL
 	def_bool n
 
+config FS_RICHACL
+	def_bool n
+
 config EXPORTFS
 	tristate
 
diff --git a/fs/namei.c b/fs/namei.c
index 1c2105e..480d2e8 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2771,7 +2771,7 @@ static int atomic_open(struct nameidata *nd, struct dentry *dentry,
 	}
 
 	mode = op->mode;
-	if ((open_flag & O_CREAT) && !IS_POSIXACL(dir))
+	if ((open_flag & O_CREAT) && !IS_ACL(dir))
 		mode &= ~current_umask();
 
 	excl = (open_flag & (O_EXCL | O_CREAT)) == (O_EXCL | O_CREAT);
@@ -2955,7 +2955,7 @@ static int lookup_open(struct nameidata *nd, struct path *path,
 	/* Negative dentry, just create the file */
 	if (!dentry->d_inode && (op->open_flag & O_CREAT)) {
 		umode_t mode = op->mode;
-		if (!IS_POSIXACL(dir->d_inode))
+		if (!IS_ACL(dir->d_inode))
 			mode &= ~current_umask();
 		/*
 		 * This write is needed to ensure that a
@@ -3526,7 +3526,7 @@ retry:
 	if (IS_ERR(dentry))
 		return PTR_ERR(dentry);
 
-	if (!IS_POSIXACL(path.dentry->d_inode))
+	if (!IS_ACL(path.dentry->d_inode))
 		mode &= ~current_umask();
 	error = security_path_mknod(&path, dentry, mode, dev);
 	if (error)
@@ -3595,7 +3595,7 @@ retry:
 	if (IS_ERR(dentry))
 		return PTR_ERR(dentry);
 
-	if (!IS_POSIXACL(path.dentry->d_inode))
+	if (!IS_ACL(path.dentry->d_inode))
 		mode &= ~current_umask();
 	error = security_path_mkdir(&path, dentry, mode);
 	if (!error)
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 84b783f..f98ebc1 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1771,6 +1771,12 @@ struct super_operations {
 #define IS_IMMUTABLE(inode)	((inode)->i_flags & S_IMMUTABLE)
 #define IS_POSIXACL(inode)	__IS_FLG(inode, MS_POSIXACL)
 
+#ifdef CONFIG_FS_RICHACL
+#define IS_RICHACL(inode)	__IS_FLG(inode, MS_RICHACL)
+#else
+#define IS_RICHACL(inode)	0
+#endif
+
 #define IS_DEADDIR(inode)	((inode)->i_flags & S_DEAD)
 #define IS_NOCMTIME(inode)	((inode)->i_flags & S_NOCMTIME)
 #define IS_SWAPFILE(inode)	((inode)->i_flags & S_SWAPFILE)
@@ -1784,6 +1790,12 @@ struct super_operations {
 				 (inode)->i_rdev == WHITEOUT_DEV)
 
 /*
+ * IS_ACL() tells the VFS to not apply the umask
+ * and use check_acl for acl permission checks when defined.
+ */
+#define IS_ACL(inode)		__IS_FLG(inode, MS_POSIXACL | MS_RICHACL)
+
+/*
  * Inode state bits.  Protected by inode->i_lock
  *
  * Three bits determine the dirty state of the inode, I_DIRTY_SYNC,
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index 9b964a5..6ac6bc9 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -81,7 +81,7 @@ struct inodes_stat_t {
 #define MS_VERBOSE	32768	/* War is peace. Verbosity is silence.
 				   MS_VERBOSE is deprecated. */
 #define MS_SILENT	32768
-#define MS_POSIXACL	(1<<16)	/* VFS does not apply the umask */
+#define MS_POSIXACL	(1<<16)	/* Supports POSIX ACLs */
 #define MS_UNBINDABLE	(1<<17)	/* change to unbindable */
 #define MS_PRIVATE	(1<<18)	/* change to private */
 #define MS_SLAVE	(1<<19)	/* change to slave */
@@ -91,6 +91,7 @@ struct inodes_stat_t {
 #define MS_I_VERSION	(1<<23) /* Update inode I_version field */
 #define MS_STRICTATIME	(1<<24) /* Always perform atime updates */
 #define MS_LAZYTIME	(1<<25) /* Update the on-disk [acm]times lazily */
+#define MS_RICHACL	(1<<26) /* Supports richacls */
 
 /* These sb flags are internal to the kernel */
 #define MS_NOSEC	(1<<28)
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 01/41] vfs: Add IS_ACL() and IS_RICHACL() tests
@ 2015-09-05 10:26     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:26 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

The vfs does not apply the umask for file systems that support acls. The
test used for this used to be called IS_POSIXACL(). Switch to a new
IS_ACL() test to check for either posix acls or richacls instead. Add a new
MS_RICHACL flag and IS_RICHACL() test for richacls alone. The IS_POSIXACL()
test is still needed by nfsd.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
Reviewed-by: J. Bruce Fields <bfields@redhat.com>
---
 fs/Kconfig              |  3 +++
 fs/namei.c              |  8 ++++----
 include/linux/fs.h      | 12 ++++++++++++
 include/uapi/linux/fs.h |  3 ++-
 4 files changed, 21 insertions(+), 5 deletions(-)

diff --git a/fs/Kconfig b/fs/Kconfig
index 011f433..3e09c06 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -59,6 +59,9 @@ endif # BLOCK
 config FS_POSIX_ACL
 	def_bool n
 
+config FS_RICHACL
+	def_bool n
+
 config EXPORTFS
 	tristate
 
diff --git a/fs/namei.c b/fs/namei.c
index 1c2105e..480d2e8 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2771,7 +2771,7 @@ static int atomic_open(struct nameidata *nd, struct dentry *dentry,
 	}
 
 	mode = op->mode;
-	if ((open_flag & O_CREAT) && !IS_POSIXACL(dir))
+	if ((open_flag & O_CREAT) && !IS_ACL(dir))
 		mode &= ~current_umask();
 
 	excl = (open_flag & (O_EXCL | O_CREAT)) == (O_EXCL | O_CREAT);
@@ -2955,7 +2955,7 @@ static int lookup_open(struct nameidata *nd, struct path *path,
 	/* Negative dentry, just create the file */
 	if (!dentry->d_inode && (op->open_flag & O_CREAT)) {
 		umode_t mode = op->mode;
-		if (!IS_POSIXACL(dir->d_inode))
+		if (!IS_ACL(dir->d_inode))
 			mode &= ~current_umask();
 		/*
 		 * This write is needed to ensure that a
@@ -3526,7 +3526,7 @@ retry:
 	if (IS_ERR(dentry))
 		return PTR_ERR(dentry);
 
-	if (!IS_POSIXACL(path.dentry->d_inode))
+	if (!IS_ACL(path.dentry->d_inode))
 		mode &= ~current_umask();
 	error = security_path_mknod(&path, dentry, mode, dev);
 	if (error)
@@ -3595,7 +3595,7 @@ retry:
 	if (IS_ERR(dentry))
 		return PTR_ERR(dentry);
 
-	if (!IS_POSIXACL(path.dentry->d_inode))
+	if (!IS_ACL(path.dentry->d_inode))
 		mode &= ~current_umask();
 	error = security_path_mkdir(&path, dentry, mode);
 	if (!error)
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 84b783f..f98ebc1 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1771,6 +1771,12 @@ struct super_operations {
 #define IS_IMMUTABLE(inode)	((inode)->i_flags & S_IMMUTABLE)
 #define IS_POSIXACL(inode)	__IS_FLG(inode, MS_POSIXACL)
 
+#ifdef CONFIG_FS_RICHACL
+#define IS_RICHACL(inode)	__IS_FLG(inode, MS_RICHACL)
+#else
+#define IS_RICHACL(inode)	0
+#endif
+
 #define IS_DEADDIR(inode)	((inode)->i_flags & S_DEAD)
 #define IS_NOCMTIME(inode)	((inode)->i_flags & S_NOCMTIME)
 #define IS_SWAPFILE(inode)	((inode)->i_flags & S_SWAPFILE)
@@ -1784,6 +1790,12 @@ struct super_operations {
 				 (inode)->i_rdev == WHITEOUT_DEV)
 
 /*
+ * IS_ACL() tells the VFS to not apply the umask
+ * and use check_acl for acl permission checks when defined.
+ */
+#define IS_ACL(inode)		__IS_FLG(inode, MS_POSIXACL | MS_RICHACL)
+
+/*
  * Inode state bits.  Protected by inode->i_lock
  *
  * Three bits determine the dirty state of the inode, I_DIRTY_SYNC,
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index 9b964a5..6ac6bc9 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -81,7 +81,7 @@ struct inodes_stat_t {
 #define MS_VERBOSE	32768	/* War is peace. Verbosity is silence.
 				   MS_VERBOSE is deprecated. */
 #define MS_SILENT	32768
-#define MS_POSIXACL	(1<<16)	/* VFS does not apply the umask */
+#define MS_POSIXACL	(1<<16)	/* Supports POSIX ACLs */
 #define MS_UNBINDABLE	(1<<17)	/* change to unbindable */
 #define MS_PRIVATE	(1<<18)	/* change to private */
 #define MS_SLAVE	(1<<19)	/* change to slave */
@@ -91,6 +91,7 @@ struct inodes_stat_t {
 #define MS_I_VERSION	(1<<23) /* Update inode I_version field */
 #define MS_STRICTATIME	(1<<24) /* Always perform atime updates */
 #define MS_LAZYTIME	(1<<25) /* Update the on-disk [acm]times lazily */
+#define MS_RICHACL	(1<<26) /* Supports richacls */
 
 /* These sb flags are internal to the kernel */
 #define MS_NOSEC	(1<<28)
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 02/41] vfs: Add MAY_CREATE_FILE and MAY_CREATE_DIR permission flags
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:26     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:26 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

Richacls distinguish between creating non-directories and directories. To
support that, add an isdir parameter to may_create(). When checking
inode_permission() for create permission, pass in an additional
MAY_CREATE_FILE or MAY_CREATE_DIR mask flag.

To allow checking for delete *and* create access when replacing an existing
file via vfs_rename(), add a replace parameter to may_delete().

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
Reviewed-by: J. Bruce Fields <bfields-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/namei.c         | 43 +++++++++++++++++++++++++------------------
 include/linux/fs.h |  2 ++
 2 files changed, 27 insertions(+), 18 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index 480d2e8..428b4a6 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -453,7 +453,9 @@ static int sb_permission(struct super_block *sb, struct inode *inode, int mask)
  * this, letting us set arbitrary permissions for filesystem access without
  * changing the "normal" UIDs which are used for other things.
  *
- * When checking for MAY_APPEND, MAY_WRITE must also be set in @mask.
+ * MAY_WRITE must be set in @mask whenever MAY_APPEND, MAY_CREATE_FILE, or
+ * MAY_CREATE_DIR are set.  That way, file systems that don't support these
+ * permissions will check for MAY_WRITE instead.
  */
 int inode_permission(struct inode *inode, int mask)
 {
@@ -2522,10 +2524,11 @@ EXPORT_SYMBOL(__check_sticky);
  * 10. We don't allow removal of NFS sillyrenamed files; it's handled by
  *     nfs_async_unlink().
  */
-static int may_delete(struct inode *dir, struct dentry *victim, bool isdir)
+static int may_delete(struct inode *dir, struct dentry *victim,
+		      bool isdir, bool replace)
 {
 	struct inode *inode = d_backing_inode(victim);
-	int error;
+	int error, mask = MAY_WRITE | MAY_EXEC;
 
 	if (d_is_negative(victim))
 		return -ENOENT;
@@ -2534,7 +2537,9 @@ static int may_delete(struct inode *dir, struct dentry *victim, bool isdir)
 	BUG_ON(victim->d_parent->d_inode != dir);
 	audit_inode_child(dir, victim, AUDIT_TYPE_CHILD_DELETE);
 
-	error = inode_permission(dir, MAY_WRITE | MAY_EXEC);
+	if (replace)
+		mask |= isdir ? MAY_CREATE_DIR : MAY_CREATE_FILE;
+	error = inode_permission(dir, mask);
 	if (error)
 		return error;
 	if (IS_APPEND(dir))
@@ -2565,14 +2570,16 @@ static int may_delete(struct inode *dir, struct dentry *victim, bool isdir)
  *  3. We should have write and exec permissions on dir
  *  4. We can't do it if dir is immutable (done in permission())
  */
-static inline int may_create(struct inode *dir, struct dentry *child)
+static inline int may_create(struct inode *dir, struct dentry *child, bool isdir)
 {
+	int mask = isdir ? MAY_CREATE_DIR : MAY_CREATE_FILE;
+
 	audit_inode_child(dir, child, AUDIT_TYPE_CHILD_CREATE);
 	if (child->d_inode)
 		return -EEXIST;
 	if (IS_DEADDIR(dir))
 		return -ENOENT;
-	return inode_permission(dir, MAY_WRITE | MAY_EXEC);
+	return inode_permission(dir, MAY_WRITE | MAY_EXEC | mask);
 }
 
 /*
@@ -2622,7 +2629,7 @@ EXPORT_SYMBOL(unlock_rename);
 int vfs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
 		bool want_excl)
 {
-	int error = may_create(dir, dentry);
+	int error = may_create(dir, dentry, false);
 	if (error)
 		return error;
 
@@ -3467,7 +3474,7 @@ EXPORT_SYMBOL(user_path_create);
 
 int vfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev)
 {
-	int error = may_create(dir, dentry);
+	int error = may_create(dir, dentry, false);
 
 	if (error)
 		return error;
@@ -3559,7 +3566,7 @@ SYSCALL_DEFINE3(mknod, const char __user *, filename, umode_t, mode, unsigned, d
 
 int vfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
 {
-	int error = may_create(dir, dentry);
+	int error = may_create(dir, dentry, true);
 	unsigned max_links = dir->i_sb->s_max_links;
 
 	if (error)
@@ -3640,7 +3647,7 @@ EXPORT_SYMBOL(dentry_unhash);
 
 int vfs_rmdir(struct inode *dir, struct dentry *dentry)
 {
-	int error = may_delete(dir, dentry, 1);
+	int error = may_delete(dir, dentry, true, false);
 
 	if (error)
 		return error;
@@ -3762,7 +3769,7 @@ SYSCALL_DEFINE1(rmdir, const char __user *, pathname)
 int vfs_unlink(struct inode *dir, struct dentry *dentry, struct inode **delegated_inode)
 {
 	struct inode *target = dentry->d_inode;
-	int error = may_delete(dir, dentry, 0);
+	int error = may_delete(dir, dentry, false, false);
 
 	if (error)
 		return error;
@@ -3896,7 +3903,7 @@ SYSCALL_DEFINE1(unlink, const char __user *, pathname)
 
 int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname)
 {
-	int error = may_create(dir, dentry);
+	int error = may_create(dir, dentry, false);
 
 	if (error)
 		return error;
@@ -3979,7 +3986,7 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de
 	if (!inode)
 		return -ENOENT;
 
-	error = may_create(dir, new_dentry);
+	error = may_create(dir, new_dentry, false);
 	if (error)
 		return error;
 
@@ -4167,19 +4174,19 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
 	if (source == target)
 		return 0;
 
-	error = may_delete(old_dir, old_dentry, is_dir);
+	error = may_delete(old_dir, old_dentry, is_dir, false);
 	if (error)
 		return error;
 
 	if (!target) {
-		error = may_create(new_dir, new_dentry);
+		error = may_create(new_dir, new_dentry, is_dir);
 	} else {
 		new_is_dir = d_is_dir(new_dentry);
 
 		if (!(flags & RENAME_EXCHANGE))
-			error = may_delete(new_dir, new_dentry, is_dir);
+			error = may_delete(new_dir, new_dentry, is_dir, true);
 		else
-			error = may_delete(new_dir, new_dentry, new_is_dir);
+			error = may_delete(new_dir, new_dentry, new_is_dir, true);
 	}
 	if (error)
 		return error;
@@ -4442,7 +4449,7 @@ SYSCALL_DEFINE2(rename, const char __user *, oldname, const char __user *, newna
 
 int vfs_whiteout(struct inode *dir, struct dentry *dentry)
 {
-	int error = may_create(dir, dentry);
+	int error = may_create(dir, dentry, false);
 	if (error)
 		return error;
 
diff --git a/include/linux/fs.h b/include/linux/fs.h
index f98ebc1..e4d701c 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -82,6 +82,8 @@ typedef void (dax_iodone_t)(struct buffer_head *bh_map, int uptodate);
 #define MAY_CHDIR		0x00000040
 /* called from RCU mode, don't block */
 #define MAY_NOT_BLOCK		0x00000080
+#define MAY_CREATE_FILE		0x00000100
+#define MAY_CREATE_DIR		0x00000200
 
 /*
  * flags in file.f_mode.  Note that FMODE_READ and FMODE_WRITE must correspond
-- 
2.4.3

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 02/41] vfs: Add MAY_CREATE_FILE and MAY_CREATE_DIR permission flags
@ 2015-09-05 10:26     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:26 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

Richacls distinguish between creating non-directories and directories. To
support that, add an isdir parameter to may_create(). When checking
inode_permission() for create permission, pass in an additional
MAY_CREATE_FILE or MAY_CREATE_DIR mask flag.

To allow checking for delete *and* create access when replacing an existing
file via vfs_rename(), add a replace parameter to may_delete().

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
Reviewed-by: J. Bruce Fields <bfields@redhat.com>
---
 fs/namei.c         | 43 +++++++++++++++++++++++++------------------
 include/linux/fs.h |  2 ++
 2 files changed, 27 insertions(+), 18 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index 480d2e8..428b4a6 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -453,7 +453,9 @@ static int sb_permission(struct super_block *sb, struct inode *inode, int mask)
  * this, letting us set arbitrary permissions for filesystem access without
  * changing the "normal" UIDs which are used for other things.
  *
- * When checking for MAY_APPEND, MAY_WRITE must also be set in @mask.
+ * MAY_WRITE must be set in @mask whenever MAY_APPEND, MAY_CREATE_FILE, or
+ * MAY_CREATE_DIR are set.  That way, file systems that don't support these
+ * permissions will check for MAY_WRITE instead.
  */
 int inode_permission(struct inode *inode, int mask)
 {
@@ -2522,10 +2524,11 @@ EXPORT_SYMBOL(__check_sticky);
  * 10. We don't allow removal of NFS sillyrenamed files; it's handled by
  *     nfs_async_unlink().
  */
-static int may_delete(struct inode *dir, struct dentry *victim, bool isdir)
+static int may_delete(struct inode *dir, struct dentry *victim,
+		      bool isdir, bool replace)
 {
 	struct inode *inode = d_backing_inode(victim);
-	int error;
+	int error, mask = MAY_WRITE | MAY_EXEC;
 
 	if (d_is_negative(victim))
 		return -ENOENT;
@@ -2534,7 +2537,9 @@ static int may_delete(struct inode *dir, struct dentry *victim, bool isdir)
 	BUG_ON(victim->d_parent->d_inode != dir);
 	audit_inode_child(dir, victim, AUDIT_TYPE_CHILD_DELETE);
 
-	error = inode_permission(dir, MAY_WRITE | MAY_EXEC);
+	if (replace)
+		mask |= isdir ? MAY_CREATE_DIR : MAY_CREATE_FILE;
+	error = inode_permission(dir, mask);
 	if (error)
 		return error;
 	if (IS_APPEND(dir))
@@ -2565,14 +2570,16 @@ static int may_delete(struct inode *dir, struct dentry *victim, bool isdir)
  *  3. We should have write and exec permissions on dir
  *  4. We can't do it if dir is immutable (done in permission())
  */
-static inline int may_create(struct inode *dir, struct dentry *child)
+static inline int may_create(struct inode *dir, struct dentry *child, bool isdir)
 {
+	int mask = isdir ? MAY_CREATE_DIR : MAY_CREATE_FILE;
+
 	audit_inode_child(dir, child, AUDIT_TYPE_CHILD_CREATE);
 	if (child->d_inode)
 		return -EEXIST;
 	if (IS_DEADDIR(dir))
 		return -ENOENT;
-	return inode_permission(dir, MAY_WRITE | MAY_EXEC);
+	return inode_permission(dir, MAY_WRITE | MAY_EXEC | mask);
 }
 
 /*
@@ -2622,7 +2629,7 @@ EXPORT_SYMBOL(unlock_rename);
 int vfs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
 		bool want_excl)
 {
-	int error = may_create(dir, dentry);
+	int error = may_create(dir, dentry, false);
 	if (error)
 		return error;
 
@@ -3467,7 +3474,7 @@ EXPORT_SYMBOL(user_path_create);
 
 int vfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev)
 {
-	int error = may_create(dir, dentry);
+	int error = may_create(dir, dentry, false);
 
 	if (error)
 		return error;
@@ -3559,7 +3566,7 @@ SYSCALL_DEFINE3(mknod, const char __user *, filename, umode_t, mode, unsigned, d
 
 int vfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
 {
-	int error = may_create(dir, dentry);
+	int error = may_create(dir, dentry, true);
 	unsigned max_links = dir->i_sb->s_max_links;
 
 	if (error)
@@ -3640,7 +3647,7 @@ EXPORT_SYMBOL(dentry_unhash);
 
 int vfs_rmdir(struct inode *dir, struct dentry *dentry)
 {
-	int error = may_delete(dir, dentry, 1);
+	int error = may_delete(dir, dentry, true, false);
 
 	if (error)
 		return error;
@@ -3762,7 +3769,7 @@ SYSCALL_DEFINE1(rmdir, const char __user *, pathname)
 int vfs_unlink(struct inode *dir, struct dentry *dentry, struct inode **delegated_inode)
 {
 	struct inode *target = dentry->d_inode;
-	int error = may_delete(dir, dentry, 0);
+	int error = may_delete(dir, dentry, false, false);
 
 	if (error)
 		return error;
@@ -3896,7 +3903,7 @@ SYSCALL_DEFINE1(unlink, const char __user *, pathname)
 
 int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname)
 {
-	int error = may_create(dir, dentry);
+	int error = may_create(dir, dentry, false);
 
 	if (error)
 		return error;
@@ -3979,7 +3986,7 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de
 	if (!inode)
 		return -ENOENT;
 
-	error = may_create(dir, new_dentry);
+	error = may_create(dir, new_dentry, false);
 	if (error)
 		return error;
 
@@ -4167,19 +4174,19 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
 	if (source == target)
 		return 0;
 
-	error = may_delete(old_dir, old_dentry, is_dir);
+	error = may_delete(old_dir, old_dentry, is_dir, false);
 	if (error)
 		return error;
 
 	if (!target) {
-		error = may_create(new_dir, new_dentry);
+		error = may_create(new_dir, new_dentry, is_dir);
 	} else {
 		new_is_dir = d_is_dir(new_dentry);
 
 		if (!(flags & RENAME_EXCHANGE))
-			error = may_delete(new_dir, new_dentry, is_dir);
+			error = may_delete(new_dir, new_dentry, is_dir, true);
 		else
-			error = may_delete(new_dir, new_dentry, new_is_dir);
+			error = may_delete(new_dir, new_dentry, new_is_dir, true);
 	}
 	if (error)
 		return error;
@@ -4442,7 +4449,7 @@ SYSCALL_DEFINE2(rename, const char __user *, oldname, const char __user *, newna
 
 int vfs_whiteout(struct inode *dir, struct dentry *dentry)
 {
-	int error = may_create(dir, dentry);
+	int error = may_create(dir, dentry, false);
 	if (error)
 		return error;
 
diff --git a/include/linux/fs.h b/include/linux/fs.h
index f98ebc1..e4d701c 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -82,6 +82,8 @@ typedef void (dax_iodone_t)(struct buffer_head *bh_map, int uptodate);
 #define MAY_CHDIR		0x00000040
 /* called from RCU mode, don't block */
 #define MAY_NOT_BLOCK		0x00000080
+#define MAY_CREATE_FILE		0x00000100
+#define MAY_CREATE_DIR		0x00000200
 
 /*
  * flags in file.f_mode.  Note that FMODE_READ and FMODE_WRITE must correspond
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 03/41] vfs: Add MAY_DELETE_SELF and MAY_DELETE_CHILD permission flags
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:26     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:26 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

Normally, deleting a file requires MAY_WRITE access to the parent
directory.  With richacls, a file may be deleted with MAY_DELETE_CHILD access
to the parent directory or with MAY_DELETE_SELF access to the file.

To support that, pass the MAY_DELETE_CHILD mask flag to inode_permission()
when checking for delete access inside a directory, and MAY_DELETE_SELF
when checking for delete access to a file itelf.

The MAY_DELETE_SELF permission does not override the sticky directory
check.  It probably should.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
Reviewed-by: J. Bruce Fields <bfields-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/namei.c         | 19 +++++++++++++------
 include/linux/fs.h |  2 ++
 2 files changed, 15 insertions(+), 6 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index 428b4a6..d7bd134 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -453,9 +453,9 @@ static int sb_permission(struct super_block *sb, struct inode *inode, int mask)
  * this, letting us set arbitrary permissions for filesystem access without
  * changing the "normal" UIDs which are used for other things.
  *
- * MAY_WRITE must be set in @mask whenever MAY_APPEND, MAY_CREATE_FILE, or
- * MAY_CREATE_DIR are set.  That way, file systems that don't support these
- * permissions will check for MAY_WRITE instead.
+ * MAY_WRITE must be set in @mask whenever MAY_APPEND, MAY_CREATE_FILE,
+ * MAY_CREATE_DIR, or MAY_DELETE_CHILD are set.  That way, file systems that
+ * don't support these permissions will check for MAY_WRITE instead.
  */
 int inode_permission(struct inode *inode, int mask)
 {
@@ -2528,7 +2528,7 @@ static int may_delete(struct inode *dir, struct dentry *victim,
 		      bool isdir, bool replace)
 {
 	struct inode *inode = d_backing_inode(victim);
-	int error, mask = MAY_WRITE | MAY_EXEC;
+	int error, mask = MAY_EXEC;
 
 	if (d_is_negative(victim))
 		return -ENOENT;
@@ -2538,8 +2538,15 @@ static int may_delete(struct inode *dir, struct dentry *victim,
 	audit_inode_child(dir, victim, AUDIT_TYPE_CHILD_DELETE);
 
 	if (replace)
-		mask |= isdir ? MAY_CREATE_DIR : MAY_CREATE_FILE;
-	error = inode_permission(dir, mask);
+		mask |= MAY_WRITE | (isdir ? MAY_CREATE_DIR : MAY_CREATE_FILE);
+	error = inode_permission(dir, mask | MAY_WRITE | MAY_DELETE_CHILD);
+	if (error && IS_RICHACL(inode)) {
+		/* Deleting is also permitted with MAY_EXEC on the directory
+		 * and MAY_DELETE_SELF on the inode.  */
+		if (!inode_permission(inode, MAY_DELETE_SELF) &&
+		    !inode_permission(dir, mask))
+			error = 0;
+	}
 	if (error)
 		return error;
 	if (IS_APPEND(dir))
diff --git a/include/linux/fs.h b/include/linux/fs.h
index e4d701c..8e130e1 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -84,6 +84,8 @@ typedef void (dax_iodone_t)(struct buffer_head *bh_map, int uptodate);
 #define MAY_NOT_BLOCK		0x00000080
 #define MAY_CREATE_FILE		0x00000100
 #define MAY_CREATE_DIR		0x00000200
+#define MAY_DELETE_CHILD	0x00000400
+#define MAY_DELETE_SELF		0x00000800
 
 /*
  * flags in file.f_mode.  Note that FMODE_READ and FMODE_WRITE must correspond
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 03/41] vfs: Add MAY_DELETE_SELF and MAY_DELETE_CHILD permission flags
@ 2015-09-05 10:26     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:26 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

Normally, deleting a file requires MAY_WRITE access to the parent
directory.  With richacls, a file may be deleted with MAY_DELETE_CHILD access
to the parent directory or with MAY_DELETE_SELF access to the file.

To support that, pass the MAY_DELETE_CHILD mask flag to inode_permission()
when checking for delete access inside a directory, and MAY_DELETE_SELF
when checking for delete access to a file itelf.

The MAY_DELETE_SELF permission does not override the sticky directory
check.  It probably should.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
Reviewed-by: J. Bruce Fields <bfields@redhat.com>
---
 fs/namei.c         | 19 +++++++++++++------
 include/linux/fs.h |  2 ++
 2 files changed, 15 insertions(+), 6 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index 428b4a6..d7bd134 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -453,9 +453,9 @@ static int sb_permission(struct super_block *sb, struct inode *inode, int mask)
  * this, letting us set arbitrary permissions for filesystem access without
  * changing the "normal" UIDs which are used for other things.
  *
- * MAY_WRITE must be set in @mask whenever MAY_APPEND, MAY_CREATE_FILE, or
- * MAY_CREATE_DIR are set.  That way, file systems that don't support these
- * permissions will check for MAY_WRITE instead.
+ * MAY_WRITE must be set in @mask whenever MAY_APPEND, MAY_CREATE_FILE,
+ * MAY_CREATE_DIR, or MAY_DELETE_CHILD are set.  That way, file systems that
+ * don't support these permissions will check for MAY_WRITE instead.
  */
 int inode_permission(struct inode *inode, int mask)
 {
@@ -2528,7 +2528,7 @@ static int may_delete(struct inode *dir, struct dentry *victim,
 		      bool isdir, bool replace)
 {
 	struct inode *inode = d_backing_inode(victim);
-	int error, mask = MAY_WRITE | MAY_EXEC;
+	int error, mask = MAY_EXEC;
 
 	if (d_is_negative(victim))
 		return -ENOENT;
@@ -2538,8 +2538,15 @@ static int may_delete(struct inode *dir, struct dentry *victim,
 	audit_inode_child(dir, victim, AUDIT_TYPE_CHILD_DELETE);
 
 	if (replace)
-		mask |= isdir ? MAY_CREATE_DIR : MAY_CREATE_FILE;
-	error = inode_permission(dir, mask);
+		mask |= MAY_WRITE | (isdir ? MAY_CREATE_DIR : MAY_CREATE_FILE);
+	error = inode_permission(dir, mask | MAY_WRITE | MAY_DELETE_CHILD);
+	if (error && IS_RICHACL(inode)) {
+		/* Deleting is also permitted with MAY_EXEC on the directory
+		 * and MAY_DELETE_SELF on the inode.  */
+		if (!inode_permission(inode, MAY_DELETE_SELF) &&
+		    !inode_permission(dir, mask))
+			error = 0;
+	}
 	if (error)
 		return error;
 	if (IS_APPEND(dir))
diff --git a/include/linux/fs.h b/include/linux/fs.h
index e4d701c..8e130e1 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -84,6 +84,8 @@ typedef void (dax_iodone_t)(struct buffer_head *bh_map, int uptodate);
 #define MAY_NOT_BLOCK		0x00000080
 #define MAY_CREATE_FILE		0x00000100
 #define MAY_CREATE_DIR		0x00000200
+#define MAY_DELETE_CHILD	0x00000400
+#define MAY_DELETE_SELF		0x00000800
 
 /*
  * flags in file.f_mode.  Note that FMODE_READ and FMODE_WRITE must correspond
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 04/41] vfs: Make the inode passed to inode_change_ok non-const
  2015-09-05 10:26 ` Andreas Gruenbacher
  (?)
@ 2015-09-05 10:26 ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:26 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

We will need to call iop->permission and iop->get_acl from
inode_change_ok() for additional permission checks, and both take a
non-const inode.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
Reviewed-by: J. Bruce Fields <bfields@redhat.com>
---
 fs/attr.c          | 2 +-
 include/linux/fs.h | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/fs/attr.c b/fs/attr.c
index 6530ced..328be71 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -28,7 +28,7 @@
  * Should be called as the first thing in ->setattr implementations,
  * possibly after taking additional locks.
  */
-int inode_change_ok(const struct inode *inode, struct iattr *attr)
+int inode_change_ok(struct inode *inode, struct iattr *attr)
 {
 	unsigned int ia_valid = attr->ia_valid;
 
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 8e130e1..f9ed0ae 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2874,7 +2874,7 @@ extern int buffer_migrate_page(struct address_space *,
 #define buffer_migrate_page NULL
 #endif
 
-extern int inode_change_ok(const struct inode *, struct iattr *);
+extern int inode_change_ok(struct inode *, struct iattr *);
 extern int inode_newsize_ok(const struct inode *, loff_t offset);
 extern void setattr_copy(struct inode *inode, const struct iattr *attr);
 
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 05/41] vfs: Add permission flags for setting file attributes
  2015-09-05 10:26 ` Andreas Gruenbacher
  (?)
  (?)
@ 2015-09-05 10:27 ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

Richacls support permissions that allow to take ownership of a file,
change the file permissions, and set the file timestamps.  Support that
by introducing new permission mask flags and by checking for those mask
flags in inode_change_ok().

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
Reviewed-by: J. Bruce Fields <bfields@redhat.com>
---
 fs/attr.c          | 79 +++++++++++++++++++++++++++++++++++++++++++++---------
 include/linux/fs.h |  3 +++
 2 files changed, 70 insertions(+), 12 deletions(-)

diff --git a/fs/attr.c b/fs/attr.c
index 328be71..85483e0 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -17,6 +17,65 @@
 #include <linux/ima.h>
 
 /**
+ * inode_extended_permission  -  permissions beyond read/write/execute
+ *
+ * Check for permissions that only richacls can currently grant.
+ */
+static int inode_extended_permission(struct inode *inode, int mask)
+{
+	if (!IS_RICHACL(inode))
+		return -EPERM;
+	return inode_permission(inode, mask);
+}
+
+static bool inode_uid_change_ok(struct inode *inode, kuid_t ia_uid)
+{
+	if (uid_eq(current_fsuid(), inode->i_uid) &&
+	    uid_eq(ia_uid, inode->i_uid))
+		return true;
+	if (uid_eq(current_fsuid(), ia_uid) &&
+	    inode_extended_permission(inode, MAY_TAKE_OWNERSHIP) == 0)
+		return true;
+	if (capable_wrt_inode_uidgid(inode, CAP_CHOWN))
+		return true;
+	return false;
+}
+
+static bool inode_gid_change_ok(struct inode *inode, kgid_t ia_gid)
+{
+	int in_group = in_group_p(ia_gid);
+	if (uid_eq(current_fsuid(), inode->i_uid) &&
+	    (in_group || gid_eq(ia_gid, inode->i_gid)))
+		return true;
+	if (in_group && inode_extended_permission(inode, MAY_TAKE_OWNERSHIP) == 0)
+		return true;
+	if (capable_wrt_inode_uidgid(inode, CAP_CHOWN))
+		return true;
+	return false;
+}
+
+/**
+ * inode_owner_permitted_or_capable
+ *
+ * Check for permissions implicitly granted to the owner, like MAY_CHMOD or
+ * MAY_SET_TIMES.  Equivalent to inode_owner_or_capable for file systems
+ * without support for those permissions.
+ */
+static bool inode_owner_permitted_or_capable(struct inode *inode, int mask)
+{
+	struct user_namespace *ns;
+
+	if (uid_eq(current_fsuid(), inode->i_uid))
+		return true;
+	if (inode_extended_permission(inode, mask) == 0)
+		return true;
+	ns = current_user_ns();
+	if (ns_capable(ns, CAP_FOWNER) && kuid_has_mapping(ns, inode->i_uid))
+		return true;
+	return false;
+}
+
+/**
  * inode_change_ok - check if attribute changes to an inode are allowed
  * @inode:	inode to check
  * @attr:	attributes to change
@@ -47,22 +106,18 @@ int inode_change_ok(struct inode *inode, struct iattr *attr)
 		return 0;
 
 	/* Make sure a caller can chown. */
-	if ((ia_valid & ATTR_UID) &&
-	    (!uid_eq(current_fsuid(), inode->i_uid) ||
-	     !uid_eq(attr->ia_uid, inode->i_uid)) &&
-	    !capable_wrt_inode_uidgid(inode, CAP_CHOWN))
-		return -EPERM;
+	if (ia_valid & ATTR_UID)
+		if (!inode_uid_change_ok(inode, attr->ia_uid))
+			return -EPERM;
 
 	/* Make sure caller can chgrp. */
-	if ((ia_valid & ATTR_GID) &&
-	    (!uid_eq(current_fsuid(), inode->i_uid) ||
-	    (!in_group_p(attr->ia_gid) && !gid_eq(attr->ia_gid, inode->i_gid))) &&
-	    !capable_wrt_inode_uidgid(inode, CAP_CHOWN))
-		return -EPERM;
+	if (ia_valid & ATTR_GID)
+		if (!inode_gid_change_ok(inode, attr->ia_gid))
+			return -EPERM;
 
 	/* Make sure a caller can chmod. */
 	if (ia_valid & ATTR_MODE) {
-		if (!inode_owner_or_capable(inode))
+		if (!inode_owner_permitted_or_capable(inode, MAY_CHMOD))
 			return -EPERM;
 		/* Also check the setgid bit! */
 		if (!in_group_p((ia_valid & ATTR_GID) ? attr->ia_gid :
@@ -73,7 +128,7 @@ int inode_change_ok(struct inode *inode, struct iattr *attr)
 
 	/* Check for setting the inode time. */
 	if (ia_valid & (ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET)) {
-		if (!inode_owner_or_capable(inode))
+		if (!inode_owner_permitted_or_capable(inode, MAY_SET_TIMES))
 			return -EPERM;
 	}
 
diff --git a/include/linux/fs.h b/include/linux/fs.h
index f9ed0ae..add0a02 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -86,6 +86,9 @@ typedef void (dax_iodone_t)(struct buffer_head *bh_map, int uptodate);
 #define MAY_CREATE_DIR		0x00000200
 #define MAY_DELETE_CHILD	0x00000400
 #define MAY_DELETE_SELF		0x00000800
+#define MAY_TAKE_OWNERSHIP	0x00001000
+#define MAY_CHMOD		0x00002000
+#define MAY_SET_TIMES		0x00004000
 
 /*
  * flags in file.f_mode.  Note that FMODE_READ and FMODE_WRITE must correspond
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 06/41] richacl: In-memory representation and helper functions
  2015-09-05 10:26 ` Andreas Gruenbacher
                   ` (2 preceding siblings ...)
  (?)
@ 2015-09-05 10:27 ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

A richacl consists of an NFSv4 acl and an owner, group, and other mask.
These three masks correspond to the owner, group, and other file
permission bits, but they contain NFSv4 permissions instead of POSIX
permissions.

Each entry in the NFSv4 acl applies to the file owner (OWNER@), the
owning group (GROUP@), everyone (EVERYONE@), or to a specific uid or
gid.

As in the standard POSIX file permission model, each process is the
owner, group, or other file class.  A richacl grants a requested access
only if the NFSv4 acl in the richacl grants the access (according to the
NFSv4 permission check algorithm), and the file mask that applies to the
process includes the requested permissions.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
Reviewed-by: J. Bruce Fields <bfields@redhat.com>
---
 fs/Makefile             |   2 +
 fs/richacl_base.c       |  67 +++++++++++++
 include/linux/richacl.h | 256 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 325 insertions(+)
 create mode 100644 fs/richacl_base.c
 create mode 100644 include/linux/richacl.h

diff --git a/fs/Makefile b/fs/Makefile
index cb20e4b..ddc43d8 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -47,6 +47,8 @@ obj-$(CONFIG_COREDUMP)		+= coredump.o
 obj-$(CONFIG_SYSCTL)		+= drop_caches.o
 
 obj-$(CONFIG_FHANDLE)		+= fhandle.o
+obj-$(CONFIG_FS_RICHACL)	+= richacl.o
+richacl-y			:= richacl_base.o
 
 obj-y				+= quota/
 
diff --git a/fs/richacl_base.c b/fs/richacl_base.c
new file mode 100644
index 0000000..6d9a073
--- /dev/null
+++ b/fs/richacl_base.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2006, 2010  Novell, Inc.
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Written by Andreas Gruenbacher <agruen@kernel.org>
+ *
+ * This program 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, or (at your option) any
+ * later version.
+ *
+ * This program 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.
+ */
+
+#include <linux/sched.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/richacl.h>
+
+MODULE_LICENSE("GPL");
+
+/**
+ * richacl_alloc  -  allocate a richacl
+ * @count:	number of entries
+ */
+struct richacl *
+richacl_alloc(int count, gfp_t gfp)
+{
+	size_t size = sizeof(struct richacl) + count * sizeof(struct richace);
+	struct richacl *acl = kzalloc(size, gfp);
+
+	if (acl) {
+		atomic_set(&acl->a_refcount, 1);
+		acl->a_count = count;
+	}
+	return acl;
+}
+EXPORT_SYMBOL_GPL(richacl_alloc);
+
+/**
+ * richacl_clone  -  create a copy of a richacl
+ */
+struct richacl *
+richacl_clone(const struct richacl *acl, gfp_t gfp)
+{
+	int count = acl->a_count;
+	size_t size = sizeof(struct richacl) + count * sizeof(struct richace);
+	struct richacl *dup = kmalloc(size, gfp);
+
+	if (dup) {
+		memcpy(dup, acl, size);
+		atomic_set(&dup->a_refcount, 1);
+	}
+	return dup;
+}
+
+/**
+ * richace_copy  -  copy an acl entry
+ */
+void
+richace_copy(struct richace *to, const struct richace *from)
+{
+	memcpy(to, from, sizeof(struct richace));
+}
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
new file mode 100644
index 0000000..bfa94bb
--- /dev/null
+++ b/include/linux/richacl.h
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2006, 2010  Novell, Inc.
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Written by Andreas Gruenbacher <agruen@kernel.org>
+ *
+ * This program 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, or (at your option) any
+ * later version.
+ *
+ * This program 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.
+ */
+
+#ifndef __RICHACL_H
+#define __RICHACL_H
+
+#define RICHACE_OWNER_SPECIAL_ID	0
+#define RICHACE_GROUP_SPECIAL_ID	1
+#define RICHACE_EVERYONE_SPECIAL_ID	2
+
+struct richace {
+	unsigned short	e_type;
+	unsigned short	e_flags;
+	unsigned int	e_mask;
+	union {
+		kuid_t		uid;
+		kgid_t		gid;
+		unsigned int	special;
+	} e_id;
+};
+
+struct richacl {
+	atomic_t	a_refcount;
+	unsigned int	a_owner_mask;
+	unsigned int	a_group_mask;
+	unsigned int	a_other_mask;
+	unsigned short	a_count;
+	unsigned short	a_flags;
+	struct richace	a_entries[0];
+};
+
+#define richacl_for_each_entry(_ace, _acl) \
+	for (_ace = (_acl)->a_entries; \
+	     _ace != (_acl)->a_entries + (_acl)->a_count; \
+	     _ace++)
+
+#define richacl_for_each_entry_reverse(_ace, _acl) \
+	for (_ace = (_acl)->a_entries + (_acl)->a_count - 1; \
+	     _ace != (_acl)->a_entries - 1; \
+	     _ace--)
+
+/* a_flags values */
+#define RICHACL_WRITE_THROUGH			0x40
+#define RICHACL_MASKED				0x80
+
+#define RICHACL_VALID_FLAGS (					\
+		RICHACL_WRITE_THROUGH |				\
+		RICHACL_MASKED)
+
+/* e_type values */
+#define RICHACE_ACCESS_ALLOWED_ACE_TYPE		0x0000
+#define RICHACE_ACCESS_DENIED_ACE_TYPE		0x0001
+
+/* e_flags bitflags */
+#define RICHACE_FILE_INHERIT_ACE		0x0001
+#define RICHACE_DIRECTORY_INHERIT_ACE		0x0002
+#define RICHACE_NO_PROPAGATE_INHERIT_ACE	0x0004
+#define RICHACE_INHERIT_ONLY_ACE		0x0008
+#define RICHACE_IDENTIFIER_GROUP		0x0040
+#define RICHACE_SPECIAL_WHO			0x4000
+
+#define RICHACE_VALID_FLAGS (					\
+	RICHACE_FILE_INHERIT_ACE |				\
+	RICHACE_DIRECTORY_INHERIT_ACE |				\
+	RICHACE_NO_PROPAGATE_INHERIT_ACE |			\
+	RICHACE_INHERIT_ONLY_ACE |				\
+	RICHACE_IDENTIFIER_GROUP |				\
+	RICHACE_SPECIAL_WHO)
+
+#define RICHACE_INHERITANCE_FLAGS (				\
+	RICHACE_FILE_INHERIT_ACE |				\
+	RICHACE_DIRECTORY_INHERIT_ACE |				\
+	RICHACE_NO_PROPAGATE_INHERIT_ACE |			\
+	RICHACE_INHERIT_ONLY_ACE )
+
+/* e_mask bitflags */
+#define RICHACE_READ_DATA			0x00000001
+#define RICHACE_LIST_DIRECTORY			0x00000001
+#define RICHACE_WRITE_DATA			0x00000002
+#define RICHACE_ADD_FILE			0x00000002
+#define RICHACE_APPEND_DATA			0x00000004
+#define RICHACE_ADD_SUBDIRECTORY		0x00000004
+#define RICHACE_READ_NAMED_ATTRS		0x00000008
+#define RICHACE_WRITE_NAMED_ATTRS		0x00000010
+#define RICHACE_EXECUTE				0x00000020
+#define RICHACE_DELETE_CHILD			0x00000040
+#define RICHACE_READ_ATTRIBUTES			0x00000080
+#define RICHACE_WRITE_ATTRIBUTES		0x00000100
+#define RICHACE_WRITE_RETENTION			0x00000200
+#define RICHACE_WRITE_RETENTION_HOLD		0x00000400
+#define RICHACE_DELETE				0x00010000
+#define RICHACE_READ_ACL			0x00020000
+#define RICHACE_WRITE_ACL			0x00040000
+#define RICHACE_WRITE_OWNER			0x00080000
+#define RICHACE_SYNCHRONIZE			0x00100000
+
+/* Valid RICHACE_* flags for directories and non-directories */
+#define RICHACE_VALID_MASK (					\
+	RICHACE_READ_DATA | RICHACE_LIST_DIRECTORY |		\
+	RICHACE_WRITE_DATA | RICHACE_ADD_FILE |			\
+	RICHACE_APPEND_DATA | RICHACE_ADD_SUBDIRECTORY |	\
+	RICHACE_READ_NAMED_ATTRS |				\
+	RICHACE_WRITE_NAMED_ATTRS |				\
+	RICHACE_EXECUTE |					\
+	RICHACE_DELETE_CHILD |					\
+	RICHACE_READ_ATTRIBUTES |				\
+	RICHACE_WRITE_ATTRIBUTES |				\
+	RICHACE_WRITE_RETENTION |				\
+	RICHACE_WRITE_RETENTION_HOLD |				\
+	RICHACE_DELETE |					\
+	RICHACE_READ_ACL |					\
+	RICHACE_WRITE_ACL |					\
+	RICHACE_WRITE_OWNER |					\
+	RICHACE_SYNCHRONIZE)
+
+/**
+ * richacl_get  -  grab another reference to a richacl handle
+ */
+static inline struct richacl *
+richacl_get(struct richacl *acl)
+{
+	if (acl)
+		atomic_inc(&acl->a_refcount);
+	return acl;
+}
+
+/**
+ * richacl_put  -  free a richacl handle
+ */
+static inline void
+richacl_put(struct richacl *acl)
+{
+	if (acl && atomic_dec_and_test(&acl->a_refcount))
+		kfree(acl);
+}
+
+/**
+ * richace_is_owner  -  check if @ace is an OWNER@ entry
+ */
+static inline bool
+richace_is_owner(const struct richace *ace)
+{
+	return (ace->e_flags & RICHACE_SPECIAL_WHO) &&
+	       ace->e_id.special == RICHACE_OWNER_SPECIAL_ID;
+}
+
+/**
+ * richace_is_group  -  check if @ace is a GROUP@ entry
+ */
+static inline bool
+richace_is_group(const struct richace *ace)
+{
+	return (ace->e_flags & RICHACE_SPECIAL_WHO) &&
+	       ace->e_id.special == RICHACE_GROUP_SPECIAL_ID;
+}
+
+/**
+ * richace_is_everyone  -  check if @ace is an EVERYONE@ entry
+ */
+static inline bool
+richace_is_everyone(const struct richace *ace)
+{
+	return (ace->e_flags & RICHACE_SPECIAL_WHO) &&
+	       ace->e_id.special == RICHACE_EVERYONE_SPECIAL_ID;
+}
+
+/**
+ * richace_is_unix_user  -  check if @ace applies to a specific user
+ */
+static inline bool
+richace_is_unix_user(const struct richace *ace)
+{
+	return !(ace->e_flags & RICHACE_SPECIAL_WHO) &&
+	       !(ace->e_flags & RICHACE_IDENTIFIER_GROUP);
+}
+
+/**
+ * richace_is_unix_group  -  check if @ace applies to a specific group
+ */
+static inline bool
+richace_is_unix_group(const struct richace *ace)
+{
+	return !(ace->e_flags & RICHACE_SPECIAL_WHO) &&
+	       (ace->e_flags & RICHACE_IDENTIFIER_GROUP);
+}
+
+/**
+ * richace_is_inherit_only  -  check if @ace is for inheritance only
+ *
+ * ACEs with the %RICHACE_INHERIT_ONLY_ACE flag set have no effect during
+ * permission checking.
+ */
+static inline bool
+richace_is_inherit_only(const struct richace *ace)
+{
+	return ace->e_flags & RICHACE_INHERIT_ONLY_ACE;
+}
+
+/**
+ * richace_is_inheritable  -  check if @ace is inheritable
+ */
+static inline bool
+richace_is_inheritable(const struct richace *ace)
+{
+	return ace->e_flags & (RICHACE_FILE_INHERIT_ACE |
+			       RICHACE_DIRECTORY_INHERIT_ACE);
+}
+
+/**
+ * richace_is_allow  -  check if @ace is an %ALLOW type entry
+ */
+static inline bool
+richace_is_allow(const struct richace *ace)
+{
+	return ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+}
+
+/**
+ * richace_is_deny  -  check if @ace is a %DENY type entry
+ */
+static inline bool
+richace_is_deny(const struct richace *ace)
+{
+	return ace->e_type == RICHACE_ACCESS_DENIED_ACE_TYPE;
+}
+
+/**
+ * richace_is_same_identifier  -  are both identifiers the same?
+ */
+static inline bool
+richace_is_same_identifier(const struct richace *a, const struct richace *b)
+{
+	return !((a->e_flags ^ b->e_flags) &
+		 (RICHACE_SPECIAL_WHO | RICHACE_IDENTIFIER_GROUP)) &&
+	       !memcmp(&a->e_id, &b->e_id, sizeof(a->e_id));
+}
+
+extern struct richacl *richacl_alloc(int, gfp_t);
+extern struct richacl *richacl_clone(const struct richacl *, gfp_t);
+extern void richace_copy(struct richace *, const struct richace *);
+
+
+#endif /* __RICHACL_H */
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 07/41] richacl: Permission mapping functions
  2015-09-05 10:26 ` Andreas Gruenbacher
                   ` (3 preceding siblings ...)
  (?)
@ 2015-09-05 10:27 ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

We need to map from POSIX permissions to NFSv4 permissions when a
chmod() is done, from NFSv4 permissions to POSIX permissions when an acl
is set (which implicitly sets the file permission bits), and from the
MAY_READ/MAY_WRITE/MAY_EXEC/MAY_APPEND flags to NFSv4 permissions when
doing an access check in a richacl.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
Reviewed-by: J. Bruce Fields <bfields@redhat.com>
---
 fs/richacl_base.c       | 117 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl.h |  47 ++++++++++++++++++-
 2 files changed, 163 insertions(+), 1 deletion(-)

diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index 6d9a073..063dbe4 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -65,3 +65,120 @@ richace_copy(struct richace *to, const struct richace *from)
 {
 	memcpy(to, from, sizeof(struct richace));
 }
+
+/*
+ * richacl_mask_to_mode  -  compute the file permission bits from mask
+ * @mask:	%RICHACE_* permission mask
+ *
+ * Compute the file permission bits corresponding to a particular set of
+ * richacl permissions.
+ *
+ * See richacl_masks_to_mode().
+ */
+static int
+richacl_mask_to_mode(unsigned int mask)
+{
+	int mode = 0;
+
+	if (mask & RICHACE_POSIX_MODE_READ)
+		mode |= S_IROTH;
+	if (mask & RICHACE_POSIX_MODE_WRITE)
+		mode |= S_IWOTH;
+	if (mask & RICHACE_POSIX_MODE_EXEC)
+		mode |= S_IXOTH;
+
+	return mode;
+}
+
+/**
+ * richacl_masks_to_mode  -  compute file permission bits from file masks
+ *
+ * When setting a richacl, we set the file permission bits to indicate maximum
+ * permissions: for example, we set the Write permission when a mask contains
+ * RICHACE_APPEND_DATA even if it does not also contain RICHACE_WRITE_DATA.
+ *
+ * Permissions which are not in RICHACE_POSIX_MODE_READ,
+ * RICHACE_POSIX_MODE_WRITE, or RICHACE_POSIX_MODE_EXEC cannot be represented
+ * in the file permission bits.  Such permissions can still be effective, but
+ * not for new files or after a chmod(); they must be explicitly enabled in the
+ * richacl.
+ */
+int
+richacl_masks_to_mode(const struct richacl *acl)
+{
+	return richacl_mask_to_mode(acl->a_owner_mask) << 6 |
+	       richacl_mask_to_mode(acl->a_group_mask) << 3 |
+	       richacl_mask_to_mode(acl->a_other_mask);
+}
+EXPORT_SYMBOL_GPL(richacl_masks_to_mode);
+
+/**
+ * richacl_mode_to_mask  - compute a file mask from the lowest three mode bits
+ *
+ * When the file permission bits of a file are set with chmod(), this specifies
+ * the maximum permissions that processes will get.  All permissions beyond
+ * that will be removed from the file masks, and become ineffective.
+ */
+unsigned int
+richacl_mode_to_mask(mode_t mode)
+{
+	unsigned int mask = 0;
+
+	if (mode & S_IROTH)
+		mask |= RICHACE_POSIX_MODE_READ;
+	if (mode & S_IWOTH)
+		mask |= RICHACE_POSIX_MODE_WRITE;
+	if (mode & S_IXOTH)
+		mask |= RICHACE_POSIX_MODE_EXEC;
+
+	return mask;
+}
+
+/**
+ * richacl_want_to_mask  - convert the iop->permission want argument to a mask
+ * @want:	@want argument of the permission inode operation
+ *
+ * When checking for append, @want is (MAY_WRITE | MAY_APPEND).
+ *
+ * Richacls use the iop->may_create and iop->may_delete hooks which are used
+ * for checking if creating and deleting files is allowed.  These hooks do not
+ * use richacl_want_to_mask(), so we do not have to deal with mapping MAY_WRITE
+ * to RICHACE_ADD_FILE, RICHACE_ADD_SUBDIRECTORY, and RICHACE_DELETE_CHILD
+ * here.
+ */
+unsigned int
+richacl_want_to_mask(unsigned int want)
+{
+	unsigned int mask = 0;
+
+	if (want & MAY_READ)
+		mask |= RICHACE_READ_DATA;
+	if (want & MAY_DELETE_SELF)
+		mask |= RICHACE_DELETE;
+	if (want & MAY_TAKE_OWNERSHIP)
+		mask |= RICHACE_WRITE_OWNER;
+	if (want & MAY_CHMOD)
+		mask |= RICHACE_WRITE_ACL;
+	if (want & MAY_SET_TIMES)
+		mask |= RICHACE_WRITE_ATTRIBUTES;
+	if (want & MAY_EXEC)
+		mask |= RICHACE_EXECUTE;
+	/*
+	 * differentiate MAY_WRITE from these request
+	 */
+	if (want & (MAY_APPEND |
+		    MAY_CREATE_FILE | MAY_CREATE_DIR |
+		    MAY_DELETE_CHILD)) {
+		if (want & MAY_APPEND)
+			mask |= RICHACE_APPEND_DATA;
+		if (want & MAY_CREATE_FILE)
+			mask |= RICHACE_ADD_FILE;
+		if (want & MAY_CREATE_DIR)
+			mask |= RICHACE_ADD_SUBDIRECTORY;
+		if (want & MAY_DELETE_CHILD)
+			mask |= RICHACE_DELETE_CHILD;
+	} else if (want & MAY_WRITE)
+		mask |= RICHACE_WRITE_DATA;
+	return mask;
+}
+EXPORT_SYMBOL_GPL(richacl_want_to_mask);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index bfa94bb..9c8f298 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -126,6 +126,49 @@ struct richacl {
 	RICHACE_WRITE_OWNER |					\
 	RICHACE_SYNCHRONIZE)
 
+/*
+ * The POSIX permissions are supersets of the following NFSv4 permissions:
+ *
+ *  - MAY_READ maps to READ_DATA or LIST_DIRECTORY, depending on the type
+ *    of the file system object.
+ *
+ *  - MAY_WRITE maps to WRITE_DATA or RICHACE_APPEND_DATA for files, and to
+ *    ADD_FILE, RICHACE_ADD_SUBDIRECTORY, or RICHACE_DELETE_CHILD for directories.
+ *
+ *  - MAY_EXECUTE maps to RICHACE_EXECUTE.
+ *
+ *  (Some of these NFSv4 permissions have the same bit values.)
+ */
+#define RICHACE_POSIX_MODE_READ (			\
+		RICHACE_READ_DATA |		\
+		RICHACE_LIST_DIRECTORY)
+#define RICHACE_POSIX_MODE_WRITE (			\
+		RICHACE_WRITE_DATA |		\
+		RICHACE_ADD_FILE |			\
+		RICHACE_APPEND_DATA |		\
+		RICHACE_ADD_SUBDIRECTORY |		\
+		RICHACE_DELETE_CHILD)
+#define RICHACE_POSIX_MODE_EXEC RICHACE_EXECUTE
+#define RICHACE_POSIX_MODE_ALL (			\
+		RICHACE_POSIX_MODE_READ |		\
+		RICHACE_POSIX_MODE_WRITE |		\
+		RICHACE_POSIX_MODE_EXEC)
+/*
+ * These permissions are always allowed
+ * no matter what the acl says.
+ */
+#define RICHACE_POSIX_ALWAYS_ALLOWED (	\
+		RICHACE_SYNCHRONIZE |	\
+		RICHACE_READ_ATTRIBUTES |	\
+		RICHACE_READ_ACL)
+/*
+ * The owner is implicitly granted
+ * these permissions under POSIX.
+ */
+#define RICHACE_POSIX_OWNER_ALLOWED (		\
+		RICHACE_WRITE_ATTRIBUTES |		\
+		RICHACE_WRITE_OWNER |		\
+		RICHACE_WRITE_ACL)
 /**
  * richacl_get  -  grab another reference to a richacl handle
  */
@@ -251,6 +294,8 @@ richace_is_same_identifier(const struct richace *a, const struct richace *b)
 extern struct richacl *richacl_alloc(int, gfp_t);
 extern struct richacl *richacl_clone(const struct richacl *, gfp_t);
 extern void richace_copy(struct richace *, const struct richace *);
-
+extern int richacl_masks_to_mode(const struct richacl *);
+extern unsigned int richacl_mode_to_mask(mode_t);
+extern unsigned int richacl_want_to_mask(unsigned int);
 
 #endif /* __RICHACL_H */
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 08/41] richacl: Compute maximum file masks from an acl
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

Compute upper bound owner, group, and other file masks with as few
permissions as possible without denying any permissions that the NFSv4
acl in a richacl grants.

This algorithm is used when a file inherits an acl at create time and
when an acl is set via a mechanism that does not provide file masks
(such as setting an acl via nfsd).  When user-space sets an acl via
setxattr, the extended attribute already includes the file masks.

Setting an acl also sets the file mode permission bits: they are
determined by the file masks; see richacl_masks_to_mode().

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
Reviewed-by: J. Bruce Fields <bfields-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/richacl_base.c       | 157 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl.h |   1 +
 2 files changed, 158 insertions(+)

diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index 063dbe4..fc544f7 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -182,3 +182,160 @@ richacl_want_to_mask(unsigned int want)
 	return mask;
 }
 EXPORT_SYMBOL_GPL(richacl_want_to_mask);
+
+/*
+ * Note: functions like richacl_allowed_to_who(), richacl_group_class_allowed(),
+ * and richacl_compute_max_masks() iterate through the entire acl in reverse
+ * order as an optimization.
+ *
+ * In the standard algorithm, aces are considered in forward order.  When a
+ * process matches an ace, the permissions in the ace are either allowed or
+ * denied depending on the ace type.  Once a permission has been allowed or
+ * denied, it is no longer considered in further aces.
+ *
+ * By iterating through the acl in reverse order, we can compute the same
+ * result without having to keep track of which permissions have been allowed
+ * and denied already.
+ */
+
+/**
+ * richacl_allowed_to_who  -  permissions allowed to a specific who value
+ *
+ * Compute the maximum mask values allowed to a specific who value, taking
+ * everyone@ aces into account.
+ */
+static unsigned int richacl_allowed_to_who(struct richacl *acl,
+					   struct richace *who)
+{
+	struct richace *ace;
+	unsigned int allowed = 0;
+
+	richacl_for_each_entry_reverse(ace, acl) {
+		if (richace_is_inherit_only(ace))
+			continue;
+		if (richace_is_same_identifier(ace, who) ||
+		    richace_is_everyone(ace)) {
+			if (richace_is_allow(ace))
+				allowed |= ace->e_mask;
+			else if (richace_is_deny(ace))
+				allowed &= ~ace->e_mask;
+		}
+	}
+	return allowed;
+}
+
+/**
+ * richacl_group_class_allowed  -  maximum permissions of the group class
+ *
+ * Compute the maximum mask values allowed to a process in the group class
+ * (i.e., a process which is not the owner but is in the owning group or
+ * matches a user or group acl entry).  This includes permissions granted or
+ * denied by everyone@ aces.
+ *
+ * See richacl_compute_max_masks().
+ */
+static unsigned int richacl_group_class_allowed(struct richacl *acl)
+{
+	struct richace *ace;
+	unsigned int everyone_allowed = 0, group_class_allowed = 0;
+	int had_group_ace = 0;
+
+	richacl_for_each_entry_reverse(ace, acl) {
+		if (richace_is_inherit_only(ace) ||
+		    richace_is_owner(ace))
+			continue;
+
+		if (richace_is_everyone(ace)) {
+			if (richace_is_allow(ace))
+				everyone_allowed |= ace->e_mask;
+			else if (richace_is_deny(ace))
+				everyone_allowed &= ~ace->e_mask;
+		} else {
+			group_class_allowed |=
+				richacl_allowed_to_who(acl, ace);
+
+			if (richace_is_group(ace))
+				had_group_ace = 1;
+		}
+	}
+	/*
+	 * If the acl doesn't contain any group@ aces, richacl_allowed_to_who()
+	 * wasn't called for the owning group.  We could make that call now, but
+	 * we already know the result (everyone_allowed).
+	 */
+	if (!had_group_ace)
+		group_class_allowed |= everyone_allowed;
+	return group_class_allowed;
+}
+
+/**
+ * richacl_compute_max_masks  -  compute upper bound masks
+ *
+ * Computes upper bound owner, group, and other masks so that none of the
+ * permissions allowed by the acl are disabled.
+ *
+ * We don't make assumptions about who the owner is so that the owner can
+ * change with no effect on the file masks or file mode permission bits; this
+ * means that we must assume that all entries can match the owner.
+ */
+void richacl_compute_max_masks(struct richacl *acl)
+{
+	unsigned int gmask = ~0;
+	struct richace *ace;
+
+	/*
+	 * @gmask contains all permissions which the group class is ever
+	 * allowed.  We use it to avoid adding permissions to the group mask
+	 * from everyone@ allow aces which the group class is always denied
+	 * through other aces.  For example, the following acl would otherwise
+	 * result in a group mask of rw:
+	 *
+	 *	group@:w::deny
+	 *	everyone@:rw::allow
+	 *
+	 * Avoid computing @gmask for acls which do not include any group class
+	 * deny aces: in such acls, the group class is never denied any
+	 * permissions from everyone@ allow aces, and the group class cannot
+	 * have fewer permissions than the other class.
+	 */
+
+restart:
+	acl->a_owner_mask = 0;
+	acl->a_group_mask = 0;
+	acl->a_other_mask = 0;
+
+	richacl_for_each_entry_reverse(ace, acl) {
+		if (richace_is_inherit_only(ace))
+			continue;
+
+		if (richace_is_owner(ace)) {
+			if (richace_is_allow(ace))
+				acl->a_owner_mask |= ace->e_mask;
+			else if (richace_is_deny(ace))
+				acl->a_owner_mask &= ~ace->e_mask;
+		} else if (richace_is_everyone(ace)) {
+			if (richace_is_allow(ace)) {
+				acl->a_owner_mask |= ace->e_mask;
+				acl->a_group_mask |= ace->e_mask & gmask;
+				acl->a_other_mask |= ace->e_mask;
+			} else if (richace_is_deny(ace)) {
+				acl->a_owner_mask &= ~ace->e_mask;
+				acl->a_group_mask &= ~ace->e_mask;
+				acl->a_other_mask &= ~ace->e_mask;
+			}
+		} else {
+			if (richace_is_allow(ace)) {
+				acl->a_owner_mask |= ace->e_mask & gmask;
+				acl->a_group_mask |= ace->e_mask & gmask;
+			} else if (richace_is_deny(ace) && gmask == ~0) {
+				gmask = richacl_group_class_allowed(acl);
+				if (likely(gmask != ~0))
+					/* should always be true */
+					goto restart;
+			}
+		}
+	}
+
+	acl->a_flags &= ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED);
+}
+EXPORT_SYMBOL_GPL(richacl_compute_max_masks);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index 9c8f298..e81144a 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -297,5 +297,6 @@ extern void richace_copy(struct richace *, const struct richace *);
 extern int richacl_masks_to_mode(const struct richacl *);
 extern unsigned int richacl_mode_to_mask(mode_t);
 extern unsigned int richacl_want_to_mask(unsigned int);
+extern void richacl_compute_max_masks(struct richacl *);
 
 #endif /* __RICHACL_H */
-- 
2.4.3

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 08/41] richacl: Compute maximum file masks from an acl
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

Compute upper bound owner, group, and other file masks with as few
permissions as possible without denying any permissions that the NFSv4
acl in a richacl grants.

This algorithm is used when a file inherits an acl at create time and
when an acl is set via a mechanism that does not provide file masks
(such as setting an acl via nfsd).  When user-space sets an acl via
setxattr, the extended attribute already includes the file masks.

Setting an acl also sets the file mode permission bits: they are
determined by the file masks; see richacl_masks_to_mode().

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
Reviewed-by: J. Bruce Fields <bfields@redhat.com>
---
 fs/richacl_base.c       | 157 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl.h |   1 +
 2 files changed, 158 insertions(+)

diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index 063dbe4..fc544f7 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -182,3 +182,160 @@ richacl_want_to_mask(unsigned int want)
 	return mask;
 }
 EXPORT_SYMBOL_GPL(richacl_want_to_mask);
+
+/*
+ * Note: functions like richacl_allowed_to_who(), richacl_group_class_allowed(),
+ * and richacl_compute_max_masks() iterate through the entire acl in reverse
+ * order as an optimization.
+ *
+ * In the standard algorithm, aces are considered in forward order.  When a
+ * process matches an ace, the permissions in the ace are either allowed or
+ * denied depending on the ace type.  Once a permission has been allowed or
+ * denied, it is no longer considered in further aces.
+ *
+ * By iterating through the acl in reverse order, we can compute the same
+ * result without having to keep track of which permissions have been allowed
+ * and denied already.
+ */
+
+/**
+ * richacl_allowed_to_who  -  permissions allowed to a specific who value
+ *
+ * Compute the maximum mask values allowed to a specific who value, taking
+ * everyone@ aces into account.
+ */
+static unsigned int richacl_allowed_to_who(struct richacl *acl,
+					   struct richace *who)
+{
+	struct richace *ace;
+	unsigned int allowed = 0;
+
+	richacl_for_each_entry_reverse(ace, acl) {
+		if (richace_is_inherit_only(ace))
+			continue;
+		if (richace_is_same_identifier(ace, who) ||
+		    richace_is_everyone(ace)) {
+			if (richace_is_allow(ace))
+				allowed |= ace->e_mask;
+			else if (richace_is_deny(ace))
+				allowed &= ~ace->e_mask;
+		}
+	}
+	return allowed;
+}
+
+/**
+ * richacl_group_class_allowed  -  maximum permissions of the group class
+ *
+ * Compute the maximum mask values allowed to a process in the group class
+ * (i.e., a process which is not the owner but is in the owning group or
+ * matches a user or group acl entry).  This includes permissions granted or
+ * denied by everyone@ aces.
+ *
+ * See richacl_compute_max_masks().
+ */
+static unsigned int richacl_group_class_allowed(struct richacl *acl)
+{
+	struct richace *ace;
+	unsigned int everyone_allowed = 0, group_class_allowed = 0;
+	int had_group_ace = 0;
+
+	richacl_for_each_entry_reverse(ace, acl) {
+		if (richace_is_inherit_only(ace) ||
+		    richace_is_owner(ace))
+			continue;
+
+		if (richace_is_everyone(ace)) {
+			if (richace_is_allow(ace))
+				everyone_allowed |= ace->e_mask;
+			else if (richace_is_deny(ace))
+				everyone_allowed &= ~ace->e_mask;
+		} else {
+			group_class_allowed |=
+				richacl_allowed_to_who(acl, ace);
+
+			if (richace_is_group(ace))
+				had_group_ace = 1;
+		}
+	}
+	/*
+	 * If the acl doesn't contain any group@ aces, richacl_allowed_to_who()
+	 * wasn't called for the owning group.  We could make that call now, but
+	 * we already know the result (everyone_allowed).
+	 */
+	if (!had_group_ace)
+		group_class_allowed |= everyone_allowed;
+	return group_class_allowed;
+}
+
+/**
+ * richacl_compute_max_masks  -  compute upper bound masks
+ *
+ * Computes upper bound owner, group, and other masks so that none of the
+ * permissions allowed by the acl are disabled.
+ *
+ * We don't make assumptions about who the owner is so that the owner can
+ * change with no effect on the file masks or file mode permission bits; this
+ * means that we must assume that all entries can match the owner.
+ */
+void richacl_compute_max_masks(struct richacl *acl)
+{
+	unsigned int gmask = ~0;
+	struct richace *ace;
+
+	/*
+	 * @gmask contains all permissions which the group class is ever
+	 * allowed.  We use it to avoid adding permissions to the group mask
+	 * from everyone@ allow aces which the group class is always denied
+	 * through other aces.  For example, the following acl would otherwise
+	 * result in a group mask of rw:
+	 *
+	 *	group@:w::deny
+	 *	everyone@:rw::allow
+	 *
+	 * Avoid computing @gmask for acls which do not include any group class
+	 * deny aces: in such acls, the group class is never denied any
+	 * permissions from everyone@ allow aces, and the group class cannot
+	 * have fewer permissions than the other class.
+	 */
+
+restart:
+	acl->a_owner_mask = 0;
+	acl->a_group_mask = 0;
+	acl->a_other_mask = 0;
+
+	richacl_for_each_entry_reverse(ace, acl) {
+		if (richace_is_inherit_only(ace))
+			continue;
+
+		if (richace_is_owner(ace)) {
+			if (richace_is_allow(ace))
+				acl->a_owner_mask |= ace->e_mask;
+			else if (richace_is_deny(ace))
+				acl->a_owner_mask &= ~ace->e_mask;
+		} else if (richace_is_everyone(ace)) {
+			if (richace_is_allow(ace)) {
+				acl->a_owner_mask |= ace->e_mask;
+				acl->a_group_mask |= ace->e_mask & gmask;
+				acl->a_other_mask |= ace->e_mask;
+			} else if (richace_is_deny(ace)) {
+				acl->a_owner_mask &= ~ace->e_mask;
+				acl->a_group_mask &= ~ace->e_mask;
+				acl->a_other_mask &= ~ace->e_mask;
+			}
+		} else {
+			if (richace_is_allow(ace)) {
+				acl->a_owner_mask |= ace->e_mask & gmask;
+				acl->a_group_mask |= ace->e_mask & gmask;
+			} else if (richace_is_deny(ace) && gmask == ~0) {
+				gmask = richacl_group_class_allowed(acl);
+				if (likely(gmask != ~0))
+					/* should always be true */
+					goto restart;
+			}
+		}
+	}
+
+	acl->a_flags &= ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED);
+}
+EXPORT_SYMBOL_GPL(richacl_compute_max_masks);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index 9c8f298..e81144a 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -297,5 +297,6 @@ extern void richace_copy(struct richace *, const struct richace *);
 extern int richacl_masks_to_mode(const struct richacl *);
 extern unsigned int richacl_mode_to_mask(mode_t);
 extern unsigned int richacl_want_to_mask(unsigned int);
+extern void richacl_compute_max_masks(struct richacl *);
 
 #endif /* __RICHACL_H */
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 09/41] richacl: Update the file masks in chmod()
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

Doing a chmod() sets the file mode, which includes the file permission
bits.  When a file has a richacl, the permissions that the richacl
grants need to be limited to what the new file permission bits allow.

This is done by setting the file masks in the richacl to what the file
permission bits map to.  The richacl access check algorithm takes the
file masks into account, which ensures that the richacl cannot grant too
many permissions.

It is possible to explicitly add permissions to the file masks which go
beyond what the file permission bits can grant (like the
RICHACE_WRITE_ACL permission).  The POSIX.1 standard calls this an
alternate file access control mechanism.  A subsequent chmod() would
ensure that those permissions are disabled again.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/richacl_base.c       | 40 ++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl.h |  1 +
 2 files changed, 41 insertions(+)

diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index fc544f7..6c69234 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -339,3 +339,43 @@ restart:
 	acl->a_flags &= ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED);
 }
 EXPORT_SYMBOL_GPL(richacl_compute_max_masks);
+
+/**
+ * richacl_chmod  -  update the file masks to reflect the new mode
+ * @mode:	new file permission bits including the file type
+ *
+ * Return a copy of @acl where the file masks have been replaced by the file
+ * masks corresponding to the file permission bits in @mode, or returns @acl
+ * itself if the file masks are already up to date.  Takes over a reference
+ * to @acl.
+ */
+struct richacl *
+richacl_chmod(struct richacl *acl, mode_t mode)
+{
+	unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
+	unsigned int owner_mask, group_mask, other_mask;
+	struct richacl *clone;
+
+	owner_mask = richacl_mode_to_mask(mode >> 6) & ~x;
+	group_mask = richacl_mode_to_mask(mode >> 3) & ~x;
+	other_mask = richacl_mode_to_mask(mode)      & ~x;
+
+	if (acl->a_owner_mask == owner_mask &&
+	    acl->a_group_mask == group_mask &&
+	    acl->a_other_mask == other_mask &&
+	    (acl->a_flags & (RICHACL_WRITE_THROUGH | RICHACL_MASKED)))
+		return acl;
+
+	clone = richacl_clone(acl, GFP_KERNEL);
+	richacl_put(acl);
+	if (!clone)
+		return ERR_PTR(-ENOMEM);
+
+	clone->a_flags |= (RICHACL_WRITE_THROUGH | RICHACL_MASKED);
+	clone->a_owner_mask = owner_mask;
+	clone->a_group_mask = group_mask;
+	clone->a_other_mask = other_mask;
+
+	return clone;
+}
+EXPORT_SYMBOL_GPL(richacl_chmod);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index e81144a..e00f313 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -298,5 +298,6 @@ extern int richacl_masks_to_mode(const struct richacl *);
 extern unsigned int richacl_mode_to_mask(mode_t);
 extern unsigned int richacl_want_to_mask(unsigned int);
 extern void richacl_compute_max_masks(struct richacl *);
+extern struct richacl *richacl_chmod(struct richacl *, mode_t);
 
 #endif /* __RICHACL_H */
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 09/41] richacl: Update the file masks in chmod()
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

Doing a chmod() sets the file mode, which includes the file permission
bits.  When a file has a richacl, the permissions that the richacl
grants need to be limited to what the new file permission bits allow.

This is done by setting the file masks in the richacl to what the file
permission bits map to.  The richacl access check algorithm takes the
file masks into account, which ensures that the richacl cannot grant too
many permissions.

It is possible to explicitly add permissions to the file masks which go
beyond what the file permission bits can grant (like the
RICHACE_WRITE_ACL permission).  The POSIX.1 standard calls this an
alternate file access control mechanism.  A subsequent chmod() would
ensure that those permissions are disabled again.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/richacl_base.c       | 40 ++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl.h |  1 +
 2 files changed, 41 insertions(+)

diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index fc544f7..6c69234 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -339,3 +339,43 @@ restart:
 	acl->a_flags &= ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED);
 }
 EXPORT_SYMBOL_GPL(richacl_compute_max_masks);
+
+/**
+ * richacl_chmod  -  update the file masks to reflect the new mode
+ * @mode:	new file permission bits including the file type
+ *
+ * Return a copy of @acl where the file masks have been replaced by the file
+ * masks corresponding to the file permission bits in @mode, or returns @acl
+ * itself if the file masks are already up to date.  Takes over a reference
+ * to @acl.
+ */
+struct richacl *
+richacl_chmod(struct richacl *acl, mode_t mode)
+{
+	unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
+	unsigned int owner_mask, group_mask, other_mask;
+	struct richacl *clone;
+
+	owner_mask = richacl_mode_to_mask(mode >> 6) & ~x;
+	group_mask = richacl_mode_to_mask(mode >> 3) & ~x;
+	other_mask = richacl_mode_to_mask(mode)      & ~x;
+
+	if (acl->a_owner_mask == owner_mask &&
+	    acl->a_group_mask == group_mask &&
+	    acl->a_other_mask == other_mask &&
+	    (acl->a_flags & (RICHACL_WRITE_THROUGH | RICHACL_MASKED)))
+		return acl;
+
+	clone = richacl_clone(acl, GFP_KERNEL);
+	richacl_put(acl);
+	if (!clone)
+		return ERR_PTR(-ENOMEM);
+
+	clone->a_flags |= (RICHACL_WRITE_THROUGH | RICHACL_MASKED);
+	clone->a_owner_mask = owner_mask;
+	clone->a_group_mask = group_mask;
+	clone->a_other_mask = other_mask;
+
+	return clone;
+}
+EXPORT_SYMBOL_GPL(richacl_chmod);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index e81144a..e00f313 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -298,5 +298,6 @@ extern int richacl_masks_to_mode(const struct richacl *);
 extern unsigned int richacl_mode_to_mask(mode_t);
 extern unsigned int richacl_want_to_mask(unsigned int);
 extern void richacl_compute_max_masks(struct richacl *);
+extern struct richacl *richacl_chmod(struct richacl *, mode_t);
 
 #endif /* __RICHACL_H */
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 10/41] richacl: Permission check algorithm
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

A richacl roughly grants a requested access if the NFSv4 acl in the
richacl grants the requested permissions according to the NFSv4
permission check algorithm and the file mask that applies to the process
includes the requested permissions.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
Acked-by: "J. Bruce Fields" <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
---
 fs/Makefile             |   2 +-
 fs/richacl_inode.c      | 147 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl.h |   3 +
 3 files changed, 151 insertions(+), 1 deletion(-)
 create mode 100644 fs/richacl_inode.c

diff --git a/fs/Makefile b/fs/Makefile
index ddc43d8..1305047 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -48,7 +48,7 @@ obj-$(CONFIG_SYSCTL)		+= drop_caches.o
 
 obj-$(CONFIG_FHANDLE)		+= fhandle.o
 obj-$(CONFIG_FS_RICHACL)	+= richacl.o
-richacl-y			:= richacl_base.o
+richacl-y			:= richacl_base.o richacl_inode.o
 
 obj-y				+= quota/
 
diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c
new file mode 100644
index 0000000..ba05993
--- /dev/null
+++ b/fs/richacl_inode.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2010  Novell, Inc.
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Written by Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
+ *
+ * This program 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, or (at your option) any
+ * later version.
+ *
+ * This program 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.
+ */
+
+#include <linux/sched.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/richacl.h>
+
+/**
+ * richacl_permission  -  richacl permission check algorithm
+ * @inode:	inode to check
+ * @acl:	rich acl of the inode
+ * @want:	requested access (MAY_* flags)
+ *
+ * Checks if the current process is granted @mask flags in @acl.
+ */
+int
+richacl_permission(struct inode *inode, const struct richacl *acl,
+		   int want)
+{
+	const struct richace *ace;
+	unsigned int mask = richacl_want_to_mask(want);
+	unsigned int requested = mask, denied = 0;
+	int in_owning_group = in_group_p(inode->i_gid);
+	int in_owner_or_group_class = in_owning_group;
+
+	/*
+	 * A process is
+	 *   - in the owner file class if it owns the file,
+	 *   - in the group file class if it is in the file's owning group or
+	 *     it matches any of the user or group entries, and
+	 *   - in the other file class otherwise.
+	 * The file class is only relevant for determining which file mask to
+	 * apply, which only happens for masked acls.
+	 */
+	if (acl->a_flags & RICHACL_MASKED) {
+		if ((acl->a_flags & RICHACL_WRITE_THROUGH) &&
+		    uid_eq(current_fsuid(), inode->i_uid)) {
+			denied = requested & ~acl->a_owner_mask;
+			goto out;
+		}
+	} else {
+		/*
+		 * When the acl is not masked, there is no need to determine if
+		 * the process is in the group class and we can break out
+		 * earlier of the loop below.
+		 */
+		in_owner_or_group_class = 1;
+	}
+
+	/*
+	 * Check if the acl grants the requested access and determine which
+	 * file class the process is in.
+	 */
+	richacl_for_each_entry(ace, acl) {
+		unsigned int ace_mask = ace->e_mask;
+
+		if (richace_is_inherit_only(ace))
+			continue;
+		if (richace_is_owner(ace)) {
+			if (!uid_eq(current_fsuid(), inode->i_uid))
+				continue;
+			goto entry_matches_owner;
+		} else if (richace_is_group(ace)) {
+			if (!in_owning_group)
+				continue;
+		} else if (richace_is_unix_user(ace)) {
+			if (!uid_eq(current_fsuid(), ace->e_id.uid))
+				continue;
+		} else if (richace_is_unix_group(ace)) {
+			if (!in_group_p(ace->e_id.gid))
+				continue;
+		} else
+			goto entry_matches_everyone;
+
+		/*
+		 * Apply the group file mask to entries other than owner@ and
+		 * everyone@ or user entries matching the owner.  This ensures
+		 * that we grant the same permissions as the acl computed by
+		 * richacl_apply_masks().
+		 *
+		 * Without this restriction, the following richacl would grant
+		 * rw access to processes which are both the owner and in the
+		 * owning group, but not to other users in the owning group,
+		 * which could not be represented without masks:
+		 *
+		 *  owner:rw::mask
+		 *  group@:rw::allow
+		 */
+		if ((acl->a_flags & RICHACL_MASKED) && richace_is_allow(ace))
+			ace_mask &= acl->a_group_mask;
+
+entry_matches_owner:
+		/* The process is in the owner or group file class. */
+		in_owner_or_group_class = 1;
+
+entry_matches_everyone:
+		/* Check which mask flags the ACE allows or denies. */
+		if (richace_is_deny(ace))
+			denied |= ace_mask & mask;
+		mask &= ~ace_mask;
+
+		/*
+		 * Keep going until we know which file class
+		 * the process is in.
+		 */
+		if (!mask && in_owner_or_group_class)
+			break;
+	}
+	denied |= mask;
+
+	if (acl->a_flags & RICHACL_MASKED) {
+		/*
+		 * The file class a process is in determines which file mask
+		 * applies.  Check if that file mask also grants the requested
+		 * access.
+		 */
+		if (uid_eq(current_fsuid(), inode->i_uid))
+			denied |= requested & ~acl->a_owner_mask;
+		else if (in_owner_or_group_class)
+			denied |= requested & ~acl->a_group_mask;
+		else {
+			if (acl->a_flags & RICHACL_WRITE_THROUGH)
+				denied = requested & ~acl->a_other_mask;
+			else
+				denied |= requested & ~acl->a_other_mask;
+		}
+	}
+
+out:
+	return denied ? -EACCES : 0;
+}
+EXPORT_SYMBOL_GPL(richacl_permission);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index e00f313..9768eeb 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -300,4 +300,7 @@ extern unsigned int richacl_want_to_mask(unsigned int);
 extern void richacl_compute_max_masks(struct richacl *);
 extern struct richacl *richacl_chmod(struct richacl *, mode_t);
 
+/* richacl_inode.c */
+extern int richacl_permission(struct inode *, const struct richacl *, int);
+
 #endif /* __RICHACL_H */
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 10/41] richacl: Permission check algorithm
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

A richacl roughly grants a requested access if the NFSv4 acl in the
richacl grants the requested permissions according to the NFSv4
permission check algorithm and the file mask that applies to the process
includes the requested permissions.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
Acked-by: "J. Bruce Fields" <bfields@fieldses.org>
---
 fs/Makefile             |   2 +-
 fs/richacl_inode.c      | 147 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl.h |   3 +
 3 files changed, 151 insertions(+), 1 deletion(-)
 create mode 100644 fs/richacl_inode.c

diff --git a/fs/Makefile b/fs/Makefile
index ddc43d8..1305047 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -48,7 +48,7 @@ obj-$(CONFIG_SYSCTL)		+= drop_caches.o
 
 obj-$(CONFIG_FHANDLE)		+= fhandle.o
 obj-$(CONFIG_FS_RICHACL)	+= richacl.o
-richacl-y			:= richacl_base.o
+richacl-y			:= richacl_base.o richacl_inode.o
 
 obj-y				+= quota/
 
diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c
new file mode 100644
index 0000000..ba05993
--- /dev/null
+++ b/fs/richacl_inode.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2010  Novell, Inc.
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Written by Andreas Gruenbacher <agruen@kernel.org>
+ *
+ * This program 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, or (at your option) any
+ * later version.
+ *
+ * This program 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.
+ */
+
+#include <linux/sched.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/richacl.h>
+
+/**
+ * richacl_permission  -  richacl permission check algorithm
+ * @inode:	inode to check
+ * @acl:	rich acl of the inode
+ * @want:	requested access (MAY_* flags)
+ *
+ * Checks if the current process is granted @mask flags in @acl.
+ */
+int
+richacl_permission(struct inode *inode, const struct richacl *acl,
+		   int want)
+{
+	const struct richace *ace;
+	unsigned int mask = richacl_want_to_mask(want);
+	unsigned int requested = mask, denied = 0;
+	int in_owning_group = in_group_p(inode->i_gid);
+	int in_owner_or_group_class = in_owning_group;
+
+	/*
+	 * A process is
+	 *   - in the owner file class if it owns the file,
+	 *   - in the group file class if it is in the file's owning group or
+	 *     it matches any of the user or group entries, and
+	 *   - in the other file class otherwise.
+	 * The file class is only relevant for determining which file mask to
+	 * apply, which only happens for masked acls.
+	 */
+	if (acl->a_flags & RICHACL_MASKED) {
+		if ((acl->a_flags & RICHACL_WRITE_THROUGH) &&
+		    uid_eq(current_fsuid(), inode->i_uid)) {
+			denied = requested & ~acl->a_owner_mask;
+			goto out;
+		}
+	} else {
+		/*
+		 * When the acl is not masked, there is no need to determine if
+		 * the process is in the group class and we can break out
+		 * earlier of the loop below.
+		 */
+		in_owner_or_group_class = 1;
+	}
+
+	/*
+	 * Check if the acl grants the requested access and determine which
+	 * file class the process is in.
+	 */
+	richacl_for_each_entry(ace, acl) {
+		unsigned int ace_mask = ace->e_mask;
+
+		if (richace_is_inherit_only(ace))
+			continue;
+		if (richace_is_owner(ace)) {
+			if (!uid_eq(current_fsuid(), inode->i_uid))
+				continue;
+			goto entry_matches_owner;
+		} else if (richace_is_group(ace)) {
+			if (!in_owning_group)
+				continue;
+		} else if (richace_is_unix_user(ace)) {
+			if (!uid_eq(current_fsuid(), ace->e_id.uid))
+				continue;
+		} else if (richace_is_unix_group(ace)) {
+			if (!in_group_p(ace->e_id.gid))
+				continue;
+		} else
+			goto entry_matches_everyone;
+
+		/*
+		 * Apply the group file mask to entries other than owner@ and
+		 * everyone@ or user entries matching the owner.  This ensures
+		 * that we grant the same permissions as the acl computed by
+		 * richacl_apply_masks().
+		 *
+		 * Without this restriction, the following richacl would grant
+		 * rw access to processes which are both the owner and in the
+		 * owning group, but not to other users in the owning group,
+		 * which could not be represented without masks:
+		 *
+		 *  owner:rw::mask
+		 *  group@:rw::allow
+		 */
+		if ((acl->a_flags & RICHACL_MASKED) && richace_is_allow(ace))
+			ace_mask &= acl->a_group_mask;
+
+entry_matches_owner:
+		/* The process is in the owner or group file class. */
+		in_owner_or_group_class = 1;
+
+entry_matches_everyone:
+		/* Check which mask flags the ACE allows or denies. */
+		if (richace_is_deny(ace))
+			denied |= ace_mask & mask;
+		mask &= ~ace_mask;
+
+		/*
+		 * Keep going until we know which file class
+		 * the process is in.
+		 */
+		if (!mask && in_owner_or_group_class)
+			break;
+	}
+	denied |= mask;
+
+	if (acl->a_flags & RICHACL_MASKED) {
+		/*
+		 * The file class a process is in determines which file mask
+		 * applies.  Check if that file mask also grants the requested
+		 * access.
+		 */
+		if (uid_eq(current_fsuid(), inode->i_uid))
+			denied |= requested & ~acl->a_owner_mask;
+		else if (in_owner_or_group_class)
+			denied |= requested & ~acl->a_group_mask;
+		else {
+			if (acl->a_flags & RICHACL_WRITE_THROUGH)
+				denied = requested & ~acl->a_other_mask;
+			else
+				denied |= requested & ~acl->a_other_mask;
+		}
+	}
+
+out:
+	return denied ? -EACCES : 0;
+}
+EXPORT_SYMBOL_GPL(richacl_permission);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index e00f313..9768eeb 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -300,4 +300,7 @@ extern unsigned int richacl_want_to_mask(unsigned int);
 extern void richacl_compute_max_masks(struct richacl *);
 extern struct richacl *richacl_chmod(struct richacl *, mode_t);
 
+/* richacl_inode.c */
+extern int richacl_permission(struct inode *, const struct richacl *, int);
+
 #endif /* __RICHACL_H */
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 11/41] vfs: Cache base_acl objects in inodes
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

POSIX ACLs and richacls are both objects allocated by kmalloc() with a
reference count which are freed by kfree_rcu().  An inode can either
cache an access and a default POSIX ACL, or a richacl (richacls do not
have default acls).  To allow an inode to cache either of the two kinds
of acls, introduce a new base_acl type and convert i_acl and
i_default_acl to that type. In most cases, the vfs then doesn't have to
care which kind of acl an inode caches (if any).

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 drivers/staging/lustre/lustre/llite/llite_lib.c |  2 +-
 fs/f2fs/acl.c                                   |  4 ++--
 fs/inode.c                                      |  4 ++--
 fs/jffs2/acl.c                                  |  6 ++++--
 fs/posix_acl.c                                  | 18 +++++++++---------
 include/linux/fs.h                              | 25 ++++++++++++++++++++++---
 include/linux/posix_acl.h                       | 12 ++++--------
 include/linux/richacl.h                         |  2 +-
 8 files changed, 45 insertions(+), 28 deletions(-)

diff --git a/drivers/staging/lustre/lustre/llite/llite_lib.c b/drivers/staging/lustre/lustre/llite/llite_lib.c
index 2513988..e198f63 100644
--- a/drivers/staging/lustre/lustre/llite/llite_lib.c
+++ b/drivers/staging/lustre/lustre/llite/llite_lib.c
@@ -1130,7 +1130,7 @@ void ll_clear_inode(struct inode *inode)
 	}
 #ifdef CONFIG_FS_POSIX_ACL
 	else if (lli->lli_posix_acl) {
-		LASSERT(atomic_read(&lli->lli_posix_acl->a_refcount) == 1);
+		LASSERT(atomic_read(&lli->lli_posix_acl->a_base.ba_refcount) == 1);
 		LASSERT(lli->lli_remote_perms == NULL);
 		posix_acl_release(lli->lli_posix_acl);
 		lli->lli_posix_acl = NULL;
diff --git a/fs/f2fs/acl.c b/fs/f2fs/acl.c
index c8f25f7..a4207de 100644
--- a/fs/f2fs/acl.c
+++ b/fs/f2fs/acl.c
@@ -270,7 +270,7 @@ static struct posix_acl *f2fs_acl_clone(const struct posix_acl *acl,
 				sizeof(struct posix_acl_entry);
 		clone = kmemdup(acl, size, flags);
 		if (clone)
-			atomic_set(&clone->a_refcount, 1);
+			atomic_set(&clone->a_base.ba_refcount, 1);
 	}
 	return clone;
 }
@@ -282,7 +282,7 @@ static int f2fs_acl_create_masq(struct posix_acl *acl, umode_t *mode_p)
 	umode_t mode = *mode_p;
 	int not_equiv = 0;
 
-	/* assert(atomic_read(acl->a_refcount) == 1); */
+	/* assert(atomic_read(acl->a_base.ba_refcount) == 1); */
 
 	FOREACH_ACL_ENTRY(pa, acl, pe) {
 		switch(pa->e_tag) {
diff --git a/fs/inode.c b/fs/inode.c
index d30640f..00b442d 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -235,9 +235,9 @@ void __destroy_inode(struct inode *inode)
 
 #ifdef CONFIG_FS_POSIX_ACL
 	if (inode->i_acl && inode->i_acl != ACL_NOT_CACHED)
-		posix_acl_release(inode->i_acl);
+		put_base_acl(inode->i_acl);
 	if (inode->i_default_acl && inode->i_default_acl != ACL_NOT_CACHED)
-		posix_acl_release(inode->i_default_acl);
+		put_base_acl(inode->i_default_acl);
 #endif
 	this_cpu_dec(nr_inodes);
 }
diff --git a/fs/jffs2/acl.c b/fs/jffs2/acl.c
index 2f7a3c0..04a5836 100644
--- a/fs/jffs2/acl.c
+++ b/fs/jffs2/acl.c
@@ -294,13 +294,15 @@ int jffs2_init_acl_post(struct inode *inode)
 	int rc;
 
 	if (inode->i_default_acl) {
-		rc = __jffs2_set_acl(inode, JFFS2_XPREFIX_ACL_DEFAULT, inode->i_default_acl);
+		rc = __jffs2_set_acl(inode, JFFS2_XPREFIX_ACL_DEFAULT,
+				     *acl_by_type(inode, ACL_TYPE_DEFAULT));
 		if (rc)
 			return rc;
 	}
 
 	if (inode->i_acl) {
-		rc = __jffs2_set_acl(inode, JFFS2_XPREFIX_ACL_ACCESS, inode->i_acl);
+		rc = __jffs2_set_acl(inode, JFFS2_XPREFIX_ACL_ACCESS,
+				     *acl_by_type(inode, ACL_TYPE_ACCESS));
 		if (rc)
 			return rc;
 	}
diff --git a/fs/posix_acl.c b/fs/posix_acl.c
index 4fb17de..b3b2265 100644
--- a/fs/posix_acl.c
+++ b/fs/posix_acl.c
@@ -25,9 +25,9 @@ struct posix_acl **acl_by_type(struct inode *inode, int type)
 {
 	switch (type) {
 	case ACL_TYPE_ACCESS:
-		return &inode->i_acl;
+		return (struct posix_acl **)&inode->i_acl;
 	case ACL_TYPE_DEFAULT:
-		return &inode->i_default_acl;
+		return (struct posix_acl **)&inode->i_default_acl;
 	default:
 		BUG();
 	}
@@ -83,16 +83,16 @@ EXPORT_SYMBOL(forget_cached_acl);
 
 void forget_all_cached_acls(struct inode *inode)
 {
-	struct posix_acl *old_access, *old_default;
+	struct base_acl *old_access, *old_default;
 	spin_lock(&inode->i_lock);
 	old_access = inode->i_acl;
 	old_default = inode->i_default_acl;
 	inode->i_acl = inode->i_default_acl = ACL_NOT_CACHED;
 	spin_unlock(&inode->i_lock);
 	if (old_access != ACL_NOT_CACHED)
-		posix_acl_release(old_access);
+		put_base_acl(old_access);
 	if (old_default != ACL_NOT_CACHED)
-		posix_acl_release(old_default);
+		put_base_acl(old_default);
 }
 EXPORT_SYMBOL(forget_all_cached_acls);
 
@@ -129,7 +129,7 @@ EXPORT_SYMBOL(get_acl);
 void
 posix_acl_init(struct posix_acl *acl, int count)
 {
-	atomic_set(&acl->a_refcount, 1);
+	atomic_set(&acl->a_base.ba_refcount, 1);
 	acl->a_count = count;
 }
 EXPORT_SYMBOL(posix_acl_init);
@@ -162,7 +162,7 @@ posix_acl_clone(const struct posix_acl *acl, gfp_t flags)
 		           sizeof(struct posix_acl_entry);
 		clone = kmemdup(acl, size, flags);
 		if (clone)
-			atomic_set(&clone->a_refcount, 1);
+			atomic_set(&clone->a_base.ba_refcount, 1);
 	}
 	return clone;
 }
@@ -384,7 +384,7 @@ static int posix_acl_create_masq(struct posix_acl *acl, umode_t *mode_p)
 	umode_t mode = *mode_p;
 	int not_equiv = 0;
 
-	/* assert(atomic_read(acl->a_refcount) == 1); */
+	/* assert(atomic_read(acl->a_base.ba_refcount) == 1); */
 
 	FOREACH_ACL_ENTRY(pa, acl, pe) {
                 switch(pa->e_tag) {
@@ -439,7 +439,7 @@ static int __posix_acl_chmod_masq(struct posix_acl *acl, umode_t mode)
 	struct posix_acl_entry *group_obj = NULL, *mask_obj = NULL;
 	struct posix_acl_entry *pa, *pe;
 
-	/* assert(atomic_read(acl->a_refcount) == 1); */
+	/* assert(atomic_read(acl->a_base.ba_refcount) == 1); */
 
 	FOREACH_ACL_ENTRY(pa, acl, pe) {
 		switch(pa->e_tag) {
diff --git a/include/linux/fs.h b/include/linux/fs.h
index add0a02..04dddfa 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -576,6 +576,12 @@ static inline void mapping_allow_writable(struct address_space *mapping)
 #define i_size_ordered_init(inode) do { } while (0)
 #endif
 
+struct base_acl {
+	union {
+		atomic_t ba_refcount;
+		struct rcu_head ba_rcu;
+	};
+};
 struct posix_acl;
 #define ACL_NOT_CACHED ((void *)(-1))
 
@@ -595,9 +601,9 @@ struct inode {
 	kgid_t			i_gid;
 	unsigned int		i_flags;
 
-#ifdef CONFIG_FS_POSIX_ACL
-	struct posix_acl	*i_acl;
-	struct posix_acl	*i_default_acl;
+#if defined(CONFIG_FS_POSIX_ACL)
+	struct base_acl *i_acl;
+	struct base_acl *i_default_acl;
 #endif
 
 	const struct inode_operations	*i_op;
@@ -3060,4 +3066,17 @@ static inline bool dir_relax(struct inode *inode)
 	return !IS_DEADDIR(inode);
 }
 
+static inline struct base_acl *get_base_acl(struct base_acl *acl)
+{
+	if (acl)
+		atomic_inc(&acl->ba_refcount);
+	return acl;
+}
+
+static inline void put_base_acl(struct base_acl *acl)
+{
+	if (acl && atomic_dec_and_test(&acl->ba_refcount))
+		kfree_rcu(acl, ba_rcu);
+}
+
 #endif /* _LINUX_FS_H */
diff --git a/include/linux/posix_acl.h b/include/linux/posix_acl.h
index 3e96a6a..2c46441 100644
--- a/include/linux/posix_acl.h
+++ b/include/linux/posix_acl.h
@@ -43,10 +43,7 @@ struct posix_acl_entry {
 };
 
 struct posix_acl {
-	union {
-		atomic_t		a_refcount;
-		struct rcu_head		a_rcu;
-	};
+	struct base_acl		a_base;
 	unsigned int		a_count;
 	struct posix_acl_entry	a_entries[0];
 };
@@ -61,8 +58,7 @@ struct posix_acl {
 static inline struct posix_acl *
 posix_acl_dup(struct posix_acl *acl)
 {
-	if (acl)
-		atomic_inc(&acl->a_refcount);
+	get_base_acl(&acl->a_base);
 	return acl;
 }
 
@@ -72,8 +68,8 @@ posix_acl_dup(struct posix_acl *acl)
 static inline void
 posix_acl_release(struct posix_acl *acl)
 {
-	if (acl && atomic_dec_and_test(&acl->a_refcount))
-		kfree_rcu(acl, a_rcu);
+	BUILD_BUG_ON(offsetof(struct posix_acl, a_base) != 0);
+	put_base_acl(&acl->a_base);
 }
 
 
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index 9768eeb..61836f1 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -176,7 +176,7 @@ static inline struct richacl *
 richacl_get(struct richacl *acl)
 {
 	if (acl)
-		atomic_inc(&acl->a_refcount);
+		atomic_inc(&acl->a_base.ba_refcount);
 	return acl;
 }
 
-- 
2.4.3

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 11/41] vfs: Cache base_acl objects in inodes
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

POSIX ACLs and richacls are both objects allocated by kmalloc() with a
reference count which are freed by kfree_rcu().  An inode can either
cache an access and a default POSIX ACL, or a richacl (richacls do not
have default acls).  To allow an inode to cache either of the two kinds
of acls, introduce a new base_acl type and convert i_acl and
i_default_acl to that type. In most cases, the vfs then doesn't have to
care which kind of acl an inode caches (if any).

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 drivers/staging/lustre/lustre/llite/llite_lib.c |  2 +-
 fs/f2fs/acl.c                                   |  4 ++--
 fs/inode.c                                      |  4 ++--
 fs/jffs2/acl.c                                  |  6 ++++--
 fs/posix_acl.c                                  | 18 +++++++++---------
 include/linux/fs.h                              | 25 ++++++++++++++++++++++---
 include/linux/posix_acl.h                       | 12 ++++--------
 include/linux/richacl.h                         |  2 +-
 8 files changed, 45 insertions(+), 28 deletions(-)

diff --git a/drivers/staging/lustre/lustre/llite/llite_lib.c b/drivers/staging/lustre/lustre/llite/llite_lib.c
index 2513988..e198f63 100644
--- a/drivers/staging/lustre/lustre/llite/llite_lib.c
+++ b/drivers/staging/lustre/lustre/llite/llite_lib.c
@@ -1130,7 +1130,7 @@ void ll_clear_inode(struct inode *inode)
 	}
 #ifdef CONFIG_FS_POSIX_ACL
 	else if (lli->lli_posix_acl) {
-		LASSERT(atomic_read(&lli->lli_posix_acl->a_refcount) == 1);
+		LASSERT(atomic_read(&lli->lli_posix_acl->a_base.ba_refcount) == 1);
 		LASSERT(lli->lli_remote_perms == NULL);
 		posix_acl_release(lli->lli_posix_acl);
 		lli->lli_posix_acl = NULL;
diff --git a/fs/f2fs/acl.c b/fs/f2fs/acl.c
index c8f25f7..a4207de 100644
--- a/fs/f2fs/acl.c
+++ b/fs/f2fs/acl.c
@@ -270,7 +270,7 @@ static struct posix_acl *f2fs_acl_clone(const struct posix_acl *acl,
 				sizeof(struct posix_acl_entry);
 		clone = kmemdup(acl, size, flags);
 		if (clone)
-			atomic_set(&clone->a_refcount, 1);
+			atomic_set(&clone->a_base.ba_refcount, 1);
 	}
 	return clone;
 }
@@ -282,7 +282,7 @@ static int f2fs_acl_create_masq(struct posix_acl *acl, umode_t *mode_p)
 	umode_t mode = *mode_p;
 	int not_equiv = 0;
 
-	/* assert(atomic_read(acl->a_refcount) == 1); */
+	/* assert(atomic_read(acl->a_base.ba_refcount) == 1); */
 
 	FOREACH_ACL_ENTRY(pa, acl, pe) {
 		switch(pa->e_tag) {
diff --git a/fs/inode.c b/fs/inode.c
index d30640f..00b442d 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -235,9 +235,9 @@ void __destroy_inode(struct inode *inode)
 
 #ifdef CONFIG_FS_POSIX_ACL
 	if (inode->i_acl && inode->i_acl != ACL_NOT_CACHED)
-		posix_acl_release(inode->i_acl);
+		put_base_acl(inode->i_acl);
 	if (inode->i_default_acl && inode->i_default_acl != ACL_NOT_CACHED)
-		posix_acl_release(inode->i_default_acl);
+		put_base_acl(inode->i_default_acl);
 #endif
 	this_cpu_dec(nr_inodes);
 }
diff --git a/fs/jffs2/acl.c b/fs/jffs2/acl.c
index 2f7a3c0..04a5836 100644
--- a/fs/jffs2/acl.c
+++ b/fs/jffs2/acl.c
@@ -294,13 +294,15 @@ int jffs2_init_acl_post(struct inode *inode)
 	int rc;
 
 	if (inode->i_default_acl) {
-		rc = __jffs2_set_acl(inode, JFFS2_XPREFIX_ACL_DEFAULT, inode->i_default_acl);
+		rc = __jffs2_set_acl(inode, JFFS2_XPREFIX_ACL_DEFAULT,
+				     *acl_by_type(inode, ACL_TYPE_DEFAULT));
 		if (rc)
 			return rc;
 	}
 
 	if (inode->i_acl) {
-		rc = __jffs2_set_acl(inode, JFFS2_XPREFIX_ACL_ACCESS, inode->i_acl);
+		rc = __jffs2_set_acl(inode, JFFS2_XPREFIX_ACL_ACCESS,
+				     *acl_by_type(inode, ACL_TYPE_ACCESS));
 		if (rc)
 			return rc;
 	}
diff --git a/fs/posix_acl.c b/fs/posix_acl.c
index 4fb17de..b3b2265 100644
--- a/fs/posix_acl.c
+++ b/fs/posix_acl.c
@@ -25,9 +25,9 @@ struct posix_acl **acl_by_type(struct inode *inode, int type)
 {
 	switch (type) {
 	case ACL_TYPE_ACCESS:
-		return &inode->i_acl;
+		return (struct posix_acl **)&inode->i_acl;
 	case ACL_TYPE_DEFAULT:
-		return &inode->i_default_acl;
+		return (struct posix_acl **)&inode->i_default_acl;
 	default:
 		BUG();
 	}
@@ -83,16 +83,16 @@ EXPORT_SYMBOL(forget_cached_acl);
 
 void forget_all_cached_acls(struct inode *inode)
 {
-	struct posix_acl *old_access, *old_default;
+	struct base_acl *old_access, *old_default;
 	spin_lock(&inode->i_lock);
 	old_access = inode->i_acl;
 	old_default = inode->i_default_acl;
 	inode->i_acl = inode->i_default_acl = ACL_NOT_CACHED;
 	spin_unlock(&inode->i_lock);
 	if (old_access != ACL_NOT_CACHED)
-		posix_acl_release(old_access);
+		put_base_acl(old_access);
 	if (old_default != ACL_NOT_CACHED)
-		posix_acl_release(old_default);
+		put_base_acl(old_default);
 }
 EXPORT_SYMBOL(forget_all_cached_acls);
 
@@ -129,7 +129,7 @@ EXPORT_SYMBOL(get_acl);
 void
 posix_acl_init(struct posix_acl *acl, int count)
 {
-	atomic_set(&acl->a_refcount, 1);
+	atomic_set(&acl->a_base.ba_refcount, 1);
 	acl->a_count = count;
 }
 EXPORT_SYMBOL(posix_acl_init);
@@ -162,7 +162,7 @@ posix_acl_clone(const struct posix_acl *acl, gfp_t flags)
 		           sizeof(struct posix_acl_entry);
 		clone = kmemdup(acl, size, flags);
 		if (clone)
-			atomic_set(&clone->a_refcount, 1);
+			atomic_set(&clone->a_base.ba_refcount, 1);
 	}
 	return clone;
 }
@@ -384,7 +384,7 @@ static int posix_acl_create_masq(struct posix_acl *acl, umode_t *mode_p)
 	umode_t mode = *mode_p;
 	int not_equiv = 0;
 
-	/* assert(atomic_read(acl->a_refcount) == 1); */
+	/* assert(atomic_read(acl->a_base.ba_refcount) == 1); */
 
 	FOREACH_ACL_ENTRY(pa, acl, pe) {
                 switch(pa->e_tag) {
@@ -439,7 +439,7 @@ static int __posix_acl_chmod_masq(struct posix_acl *acl, umode_t mode)
 	struct posix_acl_entry *group_obj = NULL, *mask_obj = NULL;
 	struct posix_acl_entry *pa, *pe;
 
-	/* assert(atomic_read(acl->a_refcount) == 1); */
+	/* assert(atomic_read(acl->a_base.ba_refcount) == 1); */
 
 	FOREACH_ACL_ENTRY(pa, acl, pe) {
 		switch(pa->e_tag) {
diff --git a/include/linux/fs.h b/include/linux/fs.h
index add0a02..04dddfa 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -576,6 +576,12 @@ static inline void mapping_allow_writable(struct address_space *mapping)
 #define i_size_ordered_init(inode) do { } while (0)
 #endif
 
+struct base_acl {
+	union {
+		atomic_t ba_refcount;
+		struct rcu_head ba_rcu;
+	};
+};
 struct posix_acl;
 #define ACL_NOT_CACHED ((void *)(-1))
 
@@ -595,9 +601,9 @@ struct inode {
 	kgid_t			i_gid;
 	unsigned int		i_flags;
 
-#ifdef CONFIG_FS_POSIX_ACL
-	struct posix_acl	*i_acl;
-	struct posix_acl	*i_default_acl;
+#if defined(CONFIG_FS_POSIX_ACL)
+	struct base_acl *i_acl;
+	struct base_acl *i_default_acl;
 #endif
 
 	const struct inode_operations	*i_op;
@@ -3060,4 +3066,17 @@ static inline bool dir_relax(struct inode *inode)
 	return !IS_DEADDIR(inode);
 }
 
+static inline struct base_acl *get_base_acl(struct base_acl *acl)
+{
+	if (acl)
+		atomic_inc(&acl->ba_refcount);
+	return acl;
+}
+
+static inline void put_base_acl(struct base_acl *acl)
+{
+	if (acl && atomic_dec_and_test(&acl->ba_refcount))
+		kfree_rcu(acl, ba_rcu);
+}
+
 #endif /* _LINUX_FS_H */
diff --git a/include/linux/posix_acl.h b/include/linux/posix_acl.h
index 3e96a6a..2c46441 100644
--- a/include/linux/posix_acl.h
+++ b/include/linux/posix_acl.h
@@ -43,10 +43,7 @@ struct posix_acl_entry {
 };
 
 struct posix_acl {
-	union {
-		atomic_t		a_refcount;
-		struct rcu_head		a_rcu;
-	};
+	struct base_acl		a_base;
 	unsigned int		a_count;
 	struct posix_acl_entry	a_entries[0];
 };
@@ -61,8 +58,7 @@ struct posix_acl {
 static inline struct posix_acl *
 posix_acl_dup(struct posix_acl *acl)
 {
-	if (acl)
-		atomic_inc(&acl->a_refcount);
+	get_base_acl(&acl->a_base);
 	return acl;
 }
 
@@ -72,8 +68,8 @@ posix_acl_dup(struct posix_acl *acl)
 static inline void
 posix_acl_release(struct posix_acl *acl)
 {
-	if (acl && atomic_dec_and_test(&acl->a_refcount))
-		kfree_rcu(acl, a_rcu);
+	BUILD_BUG_ON(offsetof(struct posix_acl, a_base) != 0);
+	put_base_acl(&acl->a_base);
 }
 
 
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index 9768eeb..61836f1 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -176,7 +176,7 @@ static inline struct richacl *
 richacl_get(struct richacl *acl)
 {
 	if (acl)
-		atomic_inc(&acl->a_refcount);
+		atomic_inc(&acl->a_base.ba_refcount);
 	return acl;
 }
 
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 12/41] vfs: Cache richacl in struct inode
  2015-09-05 10:26 ` Andreas Gruenbacher
                   ` (5 preceding siblings ...)
  (?)
@ 2015-09-05 10:27 ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

Cache richacls in struct inode so that this doesn't have to be done
individually in each filesystem.  This is similar to POSIX ACLs.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/inode.c              | 11 ++++++--
 fs/posix_acl.c          |  2 +-
 fs/richacl_base.c       |  4 +--
 fs/richacl_inode.c      | 75 +++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/fs.h      |  6 +++-
 include/linux/richacl.h | 15 ++++++----
 6 files changed, 101 insertions(+), 12 deletions(-)

diff --git a/fs/inode.c b/fs/inode.c
index 00b442d..3bcd7ee 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -176,8 +176,11 @@ int inode_init_always(struct super_block *sb, struct inode *inode)
 	inode->i_private = NULL;
 	inode->i_mapping = mapping;
 	INIT_HLIST_HEAD(&inode->i_dentry);	/* buggered by rcu freeing */
-#ifdef CONFIG_FS_POSIX_ACL
-	inode->i_acl = inode->i_default_acl = ACL_NOT_CACHED;
+#if defined(CONFIG_FS_POSIX_ACL) || defined(CONFIG_FS_RICHACL)
+	inode->i_acl = ACL_NOT_CACHED;
+# if defined(CONFIG_FS_POSIX_ACL)
+	inode->i_default_acl = ACL_NOT_CACHED;
+# endif
 #endif
 
 #ifdef CONFIG_FSNOTIFY
@@ -233,11 +236,13 @@ void __destroy_inode(struct inode *inode)
 		atomic_long_dec(&inode->i_sb->s_remove_count);
 	}
 
-#ifdef CONFIG_FS_POSIX_ACL
+#if defined(CONFIG_FS_POSIX_ACL) || defined(CONFIG_FS_RICHACL)
 	if (inode->i_acl && inode->i_acl != ACL_NOT_CACHED)
 		put_base_acl(inode->i_acl);
+# if defined(CONFIG_FS_POSIX_ACL)
 	if (inode->i_default_acl && inode->i_default_acl != ACL_NOT_CACHED)
 		put_base_acl(inode->i_default_acl);
+# endif
 #endif
 	this_cpu_dec(nr_inodes);
 }
diff --git a/fs/posix_acl.c b/fs/posix_acl.c
index b3b2265..1d766a5 100644
--- a/fs/posix_acl.c
+++ b/fs/posix_acl.c
@@ -38,7 +38,7 @@ struct posix_acl *get_cached_acl(struct inode *inode, int type)
 {
 	struct posix_acl **p = acl_by_type(inode, type);
 	struct posix_acl *acl = ACCESS_ONCE(*p);
-	if (acl) {
+	if (acl && IS_POSIXACL(inode)) {
 		spin_lock(&inode->i_lock);
 		acl = *p;
 		if (acl != ACL_NOT_CACHED)
diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index 6c69234..3163152 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -33,7 +33,7 @@ richacl_alloc(int count, gfp_t gfp)
 	struct richacl *acl = kzalloc(size, gfp);
 
 	if (acl) {
-		atomic_set(&acl->a_refcount, 1);
+		atomic_set(&acl->a_base.ba_refcount, 1);
 		acl->a_count = count;
 	}
 	return acl;
@@ -52,7 +52,7 @@ richacl_clone(const struct richacl *acl, gfp_t gfp)
 
 	if (dup) {
 		memcpy(dup, acl, size);
-		atomic_set(&dup->a_refcount, 1);
+		atomic_set(&dup->a_base.ba_refcount, 1);
 	}
 	return dup;
 }
diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c
index ba05993..dc2a69f 100644
--- a/fs/richacl_inode.c
+++ b/fs/richacl_inode.c
@@ -20,6 +20,81 @@
 #include <linux/slab.h>
 #include <linux/richacl.h>
 
+struct richacl *get_cached_richacl(struct inode *inode)
+{
+	struct richacl *acl;
+
+	acl = (struct richacl *)ACCESS_ONCE(inode->i_acl);
+	if (acl && IS_RICHACL(inode)) {
+		spin_lock(&inode->i_lock);
+		acl = (struct richacl *)inode->i_acl;
+		if (acl != ACL_NOT_CACHED)
+			acl = richacl_get(acl);
+		spin_unlock(&inode->i_lock);
+	}
+	return acl;
+}
+EXPORT_SYMBOL_GPL(get_cached_richacl);
+
+struct richacl *get_cached_richacl_rcu(struct inode *inode)
+{
+	return (struct richacl *)rcu_dereference(inode->i_acl);
+}
+EXPORT_SYMBOL_GPL(get_cached_richacl_rcu);
+
+void set_cached_richacl(struct inode *inode, struct richacl *acl)
+{
+	struct base_acl *old = NULL;
+
+	spin_lock(&inode->i_lock);
+	old = inode->i_acl;
+	rcu_assign_pointer(inode->i_acl, &richacl_get(acl)->a_base);
+	spin_unlock(&inode->i_lock);
+	if (old != ACL_NOT_CACHED)
+		put_base_acl(old);
+}
+EXPORT_SYMBOL_GPL(set_cached_richacl);
+
+void forget_cached_richacl(struct inode *inode)
+{
+	struct base_acl *old = NULL;
+
+	spin_lock(&inode->i_lock);
+	old = inode->i_acl;
+	inode->i_acl = ACL_NOT_CACHED;
+	spin_unlock(&inode->i_lock);
+	if (old != ACL_NOT_CACHED)
+		put_base_acl(old);
+}
+EXPORT_SYMBOL_GPL(forget_cached_richacl);
+
+struct richacl *get_richacl(struct inode *inode)
+{
+	struct richacl *acl;
+
+	acl = get_cached_richacl(inode);
+	if (acl != ACL_NOT_CACHED)
+		return acl;
+
+	if (!IS_RICHACL(inode))
+		return NULL;
+
+	/*
+	 * A filesystem can force a ACL callback by just never filling the
+	 * ACL cache. But normally you'd fill the cache either at inode
+	 * instantiation time, or on the first ->get_richacl call.
+	 *
+	 * If the filesystem doesn't have a get_richacl() function at all,
+	 * we'll just create the negative cache entry.
+	 */
+	if (!inode->i_op->get_richacl) {
+		set_cached_richacl(inode, NULL);
+		return NULL;
+	}
+	return inode->i_op->get_richacl(inode);
+}
+EXPORT_SYMBOL_GPL(get_richacl);
+
 /**
  * richacl_permission  -  richacl permission check algorithm
  * @inode:	inode to check
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 04dddfa..4982c15 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -583,6 +583,7 @@ struct base_acl {
 	};
 };
 struct posix_acl;
+struct richacl;
 #define ACL_NOT_CACHED ((void *)(-1))
 
 #define IOP_FASTPERM	0x0001
@@ -601,9 +602,11 @@ struct inode {
 	kgid_t			i_gid;
 	unsigned int		i_flags;
 
-#if defined(CONFIG_FS_POSIX_ACL)
+#if defined(CONFIG_FS_POSIX_ACL) || defined(CONFIG_FS_RICHACL)
 	struct base_acl *i_acl;
+# if defined(CONFIG_FS_POSIX_ACL)
 	struct base_acl *i_default_acl;
+# endif
 #endif
 
 	const struct inode_operations	*i_op;
@@ -1652,6 +1655,7 @@ struct inode_operations {
 	const char * (*follow_link) (struct dentry *, void **);
 	int (*permission) (struct inode *, int);
 	struct posix_acl * (*get_acl)(struct inode *, int);
+	struct richacl * (*get_richacl)(struct inode *);
 
 	int (*readlink) (struct dentry *, char __user *,int);
 	void (*put_link) (struct inode *, void *);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index 61836f1..d4a576c 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -33,7 +33,7 @@ struct richace {
 };
 
 struct richacl {
-	atomic_t	a_refcount;
+	struct base_acl	a_base;
 	unsigned int	a_owner_mask;
 	unsigned int	a_group_mask;
 	unsigned int	a_other_mask;
@@ -175,8 +175,7 @@ struct richacl {
 static inline struct richacl *
 richacl_get(struct richacl *acl)
 {
-	if (acl)
-		atomic_inc(&acl->a_base.ba_refcount);
+	get_base_acl(&acl->a_base);
 	return acl;
 }
 
@@ -186,10 +185,16 @@ richacl_get(struct richacl *acl)
 static inline void
 richacl_put(struct richacl *acl)
 {
-	if (acl && atomic_dec_and_test(&acl->a_refcount))
-		kfree(acl);
+	BUILD_BUG_ON(offsetof(struct richacl, a_base) != 0);
+	put_base_acl(&acl->a_base);
 }
 
+extern struct richacl *get_cached_richacl(struct inode *);
+extern struct richacl *get_cached_richacl_rcu(struct inode *);
+extern void set_cached_richacl(struct inode *, struct richacl *);
+extern void forget_cached_richacl(struct inode *);
+extern struct richacl *get_richacl(struct inode *);
+
 /**
  * richace_is_owner  -  check if @ace is an OWNER@ entry
  */
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 13/41] richacl: Check if an acl is equivalent to a file mode
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

ACLs are considered equivalent to file modes if they only consist of
owner@, group@, and everyone@ entries, the owner@ permissions do not
depend on whether the owner is a member in the owning group, and no
inheritance flags are set.  This test is used to avoid storing richacls
if the acl can be computed from the file permission bits.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/richacl_base.c       | 104 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl.h |   1 +
 2 files changed, 105 insertions(+)

diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index 3163152..106e988 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -379,3 +379,107 @@ richacl_chmod(struct richacl *acl, mode_t mode)
 	return clone;
 }
 EXPORT_SYMBOL_GPL(richacl_chmod);
+
+/**
+ * richacl_equiv_mode  -  compute the mode equivalent of @acl
+ *
+ * An acl is considered equivalent to a file mode if it only consists of
+ * owner@, group@, and everyone@ entries and the owner@ permissions do not
+ * depend on whether the owner is a member in the owning group.
+ */
+int
+richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
+{
+	mode_t mode = *mode_p;
+
+	/*
+	 * The RICHACE_DELETE_CHILD flag is meaningless for non-directories, so
+	 * we ignore it.
+	 */
+	unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
+	struct {
+		unsigned int allowed;
+		unsigned int defined;  /* allowed or denied */
+	} owner = {
+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED |
+			   RICHACE_POSIX_OWNER_ALLOWED  | x,
+	}, group = {
+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
+	}, everyone = {
+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
+	};
+	const struct richace *ace;
+
+	if (acl->a_flags & ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED))
+		return -1;
+
+	richacl_for_each_entry(ace, acl) {
+		if (ace->e_flags & ~RICHACE_SPECIAL_WHO)
+			return -1;
+
+		if (richace_is_owner(ace) || richace_is_everyone(ace)) {
+			x = ace->e_mask & ~owner.defined;
+			if (richace_is_allow(ace)) {
+				unsigned int group_denied =
+					group.defined & ~group.allowed;
+
+				if (x & group_denied)
+					return -1;
+				owner.allowed |= x;
+			} else /* if (richace_is_deny(ace)) */ {
+				if (x & group.allowed)
+					return -1;
+			}
+			owner.defined |= x;
+
+			if (richace_is_everyone(ace)) {
+				x = ace->e_mask;
+				if (richace_is_allow(ace)) {
+					group.allowed |=
+						x & ~group.defined;
+					everyone.allowed |=
+						x & ~everyone.defined;
+				}
+				group.defined |= x;
+				everyone.defined |= x;
+			}
+		} else if (richace_is_group(ace)) {
+			x = ace->e_mask & ~group.defined;
+			if (richace_is_allow(ace))
+				group.allowed |= x;
+			group.defined |= x;
+		} else
+			return -1;
+	}
+
+	if (group.allowed & ~owner.defined)
+		return -1;
+
+	if (acl->a_flags & RICHACL_MASKED) {
+		if (acl->a_flags & RICHACL_WRITE_THROUGH) {
+			owner.allowed = acl->a_owner_mask;
+			everyone.allowed = acl->a_other_mask;
+		} else {
+			owner.allowed &= acl->a_owner_mask;
+			everyone.allowed &= acl->a_other_mask;
+		}
+		group.allowed &= acl->a_group_mask;
+	}
+
+	mode = (mode & ~S_IRWXUGO) |
+	       (richacl_mask_to_mode(owner.allowed) << 6) |
+	       (richacl_mask_to_mode(group.allowed) << 3) |
+		richacl_mask_to_mode(everyone.allowed);
+
+	/* Mask flags we can ignore */
+	x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
+
+	if (((richacl_mode_to_mask(mode >> 6) ^ owner.allowed)    & ~x) ||
+	    ((richacl_mode_to_mask(mode >> 3) ^ group.allowed)    & ~x) ||
+	    ((richacl_mode_to_mask(mode)      ^ everyone.allowed) & ~x))
+		return -1;
+
+	*mode_p = mode;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(richacl_equiv_mode);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index d4a576c..6535ce5 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -304,6 +304,7 @@ extern unsigned int richacl_mode_to_mask(mode_t);
 extern unsigned int richacl_want_to_mask(unsigned int);
 extern void richacl_compute_max_masks(struct richacl *);
 extern struct richacl *richacl_chmod(struct richacl *, mode_t);
+extern int richacl_equiv_mode(const struct richacl *, mode_t *);
 
 /* richacl_inode.c */
 extern int richacl_permission(struct inode *, const struct richacl *, int);
-- 
2.4.3

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 13/41] richacl: Check if an acl is equivalent to a file mode
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

ACLs are considered equivalent to file modes if they only consist of
owner@, group@, and everyone@ entries, the owner@ permissions do not
depend on whether the owner is a member in the owning group, and no
inheritance flags are set.  This test is used to avoid storing richacls
if the acl can be computed from the file permission bits.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/richacl_base.c       | 104 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl.h |   1 +
 2 files changed, 105 insertions(+)

diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index 3163152..106e988 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -379,3 +379,107 @@ richacl_chmod(struct richacl *acl, mode_t mode)
 	return clone;
 }
 EXPORT_SYMBOL_GPL(richacl_chmod);
+
+/**
+ * richacl_equiv_mode  -  compute the mode equivalent of @acl
+ *
+ * An acl is considered equivalent to a file mode if it only consists of
+ * owner@, group@, and everyone@ entries and the owner@ permissions do not
+ * depend on whether the owner is a member in the owning group.
+ */
+int
+richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
+{
+	mode_t mode = *mode_p;
+
+	/*
+	 * The RICHACE_DELETE_CHILD flag is meaningless for non-directories, so
+	 * we ignore it.
+	 */
+	unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
+	struct {
+		unsigned int allowed;
+		unsigned int defined;  /* allowed or denied */
+	} owner = {
+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED |
+			   RICHACE_POSIX_OWNER_ALLOWED  | x,
+	}, group = {
+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
+	}, everyone = {
+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
+	};
+	const struct richace *ace;
+
+	if (acl->a_flags & ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED))
+		return -1;
+
+	richacl_for_each_entry(ace, acl) {
+		if (ace->e_flags & ~RICHACE_SPECIAL_WHO)
+			return -1;
+
+		if (richace_is_owner(ace) || richace_is_everyone(ace)) {
+			x = ace->e_mask & ~owner.defined;
+			if (richace_is_allow(ace)) {
+				unsigned int group_denied =
+					group.defined & ~group.allowed;
+
+				if (x & group_denied)
+					return -1;
+				owner.allowed |= x;
+			} else /* if (richace_is_deny(ace)) */ {
+				if (x & group.allowed)
+					return -1;
+			}
+			owner.defined |= x;
+
+			if (richace_is_everyone(ace)) {
+				x = ace->e_mask;
+				if (richace_is_allow(ace)) {
+					group.allowed |=
+						x & ~group.defined;
+					everyone.allowed |=
+						x & ~everyone.defined;
+				}
+				group.defined |= x;
+				everyone.defined |= x;
+			}
+		} else if (richace_is_group(ace)) {
+			x = ace->e_mask & ~group.defined;
+			if (richace_is_allow(ace))
+				group.allowed |= x;
+			group.defined |= x;
+		} else
+			return -1;
+	}
+
+	if (group.allowed & ~owner.defined)
+		return -1;
+
+	if (acl->a_flags & RICHACL_MASKED) {
+		if (acl->a_flags & RICHACL_WRITE_THROUGH) {
+			owner.allowed = acl->a_owner_mask;
+			everyone.allowed = acl->a_other_mask;
+		} else {
+			owner.allowed &= acl->a_owner_mask;
+			everyone.allowed &= acl->a_other_mask;
+		}
+		group.allowed &= acl->a_group_mask;
+	}
+
+	mode = (mode & ~S_IRWXUGO) |
+	       (richacl_mask_to_mode(owner.allowed) << 6) |
+	       (richacl_mask_to_mode(group.allowed) << 3) |
+		richacl_mask_to_mode(everyone.allowed);
+
+	/* Mask flags we can ignore */
+	x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
+
+	if (((richacl_mode_to_mask(mode >> 6) ^ owner.allowed)    & ~x) ||
+	    ((richacl_mode_to_mask(mode >> 3) ^ group.allowed)    & ~x) ||
+	    ((richacl_mode_to_mask(mode)      ^ everyone.allowed) & ~x))
+		return -1;
+
+	*mode_p = mode;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(richacl_equiv_mode);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index d4a576c..6535ce5 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -304,6 +304,7 @@ extern unsigned int richacl_mode_to_mask(mode_t);
 extern unsigned int richacl_want_to_mask(unsigned int);
 extern void richacl_compute_max_masks(struct richacl *);
 extern struct richacl *richacl_chmod(struct richacl *, mode_t);
+extern int richacl_equiv_mode(const struct richacl *, mode_t *);
 
 /* richacl_inode.c */
 extern int richacl_permission(struct inode *, const struct richacl *, int);
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 14/41] richacl: Create-time inheritance
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

When a new file is created, it can inherit an acl from its parent
directory; this is similar to how default acls work in POSIX (draft)
ACLs.

As with POSIX ACLs, if a file inherits an acl from its parent directory,
the intersection between the create mode and the permissions granted by
the inherited acl determines the file masks and file permission bits,
and the umask is ignored.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/richacl_base.c       | 69 +++++++++++++++++++++++++++++++++++++++++++++++++
 fs/richacl_inode.c      | 65 ++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl.h |  2 ++
 3 files changed, 136 insertions(+)

diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index 106e988..fda407d 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -483,3 +483,72 @@ richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
 	return 0;
 }
 EXPORT_SYMBOL_GPL(richacl_equiv_mode);
+
+/**
+ * richacl_inherit  -  compute the inherited acl of a new file
+ * @dir_acl:	acl of the containing directory
+ * @isdir:	inherit by a directory or non-directory?
+ *
+ * A directory can have acl entries which files and/or directories created
+ * inside the directory will inherit.  This function computes the acl for such
+ * a new file.  If there is no inheritable acl, it will return %NULL.
+ */
+struct richacl *
+richacl_inherit(const struct richacl *dir_acl, int isdir)
+{
+	const struct richace *dir_ace;
+	struct richacl *acl = NULL;
+	struct richace *ace;
+	int count = 0;
+
+	if (isdir) {
+		richacl_for_each_entry(dir_ace, dir_acl) {
+			if (!richace_is_inheritable(dir_ace))
+				continue;
+			count++;
+		}
+		if (!count)
+			return NULL;
+		acl = richacl_alloc(count, GFP_KERNEL);
+		if (!acl)
+			return ERR_PTR(-ENOMEM);
+		ace = acl->a_entries;
+		richacl_for_each_entry(dir_ace, dir_acl) {
+			if (!richace_is_inheritable(dir_ace))
+				continue;
+			richace_copy(ace, dir_ace);
+			if (dir_ace->e_flags & RICHACE_NO_PROPAGATE_INHERIT_ACE)
+				ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
+			else if ((dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE) &&
+				 !(dir_ace->e_flags & RICHACE_DIRECTORY_INHERIT_ACE))
+				ace->e_flags |= RICHACE_INHERIT_ONLY_ACE;
+			ace++;
+		}
+	} else {
+		richacl_for_each_entry(dir_ace, dir_acl) {
+			if (!(dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE))
+				continue;
+			count++;
+		}
+		if (!count)
+			return NULL;
+		acl = richacl_alloc(count, GFP_KERNEL);
+		if (!acl)
+			return ERR_PTR(-ENOMEM);
+		ace = acl->a_entries;
+		richacl_for_each_entry(dir_ace, dir_acl) {
+			if (!(dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE))
+				continue;
+			richace_copy(ace, dir_ace);
+			ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
+			/*
+			 * RICHACE_DELETE_CHILD is meaningless for
+			 * non-directories, so clear it.
+			 */
+			ace->e_mask &= ~RICHACE_DELETE_CHILD;
+			ace++;
+		}
+	}
+
+	return acl;
+}
diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c
index dc2a69f..f3f1f84 100644
--- a/fs/richacl_inode.c
+++ b/fs/richacl_inode.c
@@ -220,3 +220,68 @@ out:
 	return denied ? -EACCES : 0;
 }
 EXPORT_SYMBOL_GPL(richacl_permission);
+
+/**
+ * richacl_inherit_inode  -  compute inherited acl and file mode
+ * @dir_acl:	acl of the containing directory
+ * @inode:	inode of the new file (create mode in i_mode)
+ *
+ * The file permission bits in inode->i_mode must be set to the create mode by
+ * the caller.
+ *
+ * If there is an inheritable acl, the maximum permissions that the acl grants
+ * will be computed and permissions not granted by the acl will be removed from
+ * inode->i_mode.  If there is no inheritable acl, the umask will be applied
+ * instead.
+ */
+static struct richacl *
+richacl_inherit_inode(const struct richacl *dir_acl, struct inode *inode)
+{
+	struct richacl *acl;
+	mode_t mask;
+
+	acl = richacl_inherit(dir_acl, S_ISDIR(inode->i_mode));
+	if (acl) {
+		mask = inode->i_mode;
+		if (richacl_equiv_mode(acl, &mask) == 0) {
+			richacl_put(acl);
+			acl = NULL;
+		} else {
+			richacl_compute_max_masks(acl);
+			/*
+			 * Ensure that the acl will not grant any permissions
+			 * beyond the create mode.
+			 */
+			acl->a_flags |= RICHACL_MASKED;
+			acl->a_owner_mask &=
+				richacl_mode_to_mask(inode->i_mode >> 6);
+			acl->a_group_mask &=
+				richacl_mode_to_mask(inode->i_mode >> 3);
+			acl->a_other_mask &=
+				richacl_mode_to_mask(inode->i_mode);
+			mask = ~S_IRWXUGO | richacl_masks_to_mode(acl);
+		}
+	} else
+		mask = ~current_umask();
+
+	inode->i_mode &= mask;
+	return acl;
+}
+
+struct richacl *richacl_create(struct inode *inode, struct inode *dir)
+{
+	struct richacl *dir_acl, *acl = NULL;
+
+	if (S_ISLNK(inode->i_mode))
+		return NULL;
+	dir_acl = get_richacl(dir);
+	if (dir_acl) {
+		if (IS_ERR(dir_acl))
+			return dir_acl;
+		acl = richacl_inherit_inode(dir_acl, inode);
+		richacl_put(dir_acl);
+	} else
+		inode->i_mode &= ~current_umask();
+	return acl;
+}
+EXPORT_SYMBOL_GPL(richacl_create);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index 6535ce5..9bf95c2 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -305,8 +305,10 @@ extern unsigned int richacl_want_to_mask(unsigned int);
 extern void richacl_compute_max_masks(struct richacl *);
 extern struct richacl *richacl_chmod(struct richacl *, mode_t);
 extern int richacl_equiv_mode(const struct richacl *, mode_t *);
+extern struct richacl *richacl_inherit(const struct richacl *, int);
 
 /* richacl_inode.c */
 extern int richacl_permission(struct inode *, const struct richacl *, int);
+extern struct richacl *richacl_create(struct inode *, struct inode *);
 
 #endif /* __RICHACL_H */
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 14/41] richacl: Create-time inheritance
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

When a new file is created, it can inherit an acl from its parent
directory; this is similar to how default acls work in POSIX (draft)
ACLs.

As with POSIX ACLs, if a file inherits an acl from its parent directory,
the intersection between the create mode and the permissions granted by
the inherited acl determines the file masks and file permission bits,
and the umask is ignored.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/richacl_base.c       | 69 +++++++++++++++++++++++++++++++++++++++++++++++++
 fs/richacl_inode.c      | 65 ++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl.h |  2 ++
 3 files changed, 136 insertions(+)

diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index 106e988..fda407d 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -483,3 +483,72 @@ richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
 	return 0;
 }
 EXPORT_SYMBOL_GPL(richacl_equiv_mode);
+
+/**
+ * richacl_inherit  -  compute the inherited acl of a new file
+ * @dir_acl:	acl of the containing directory
+ * @isdir:	inherit by a directory or non-directory?
+ *
+ * A directory can have acl entries which files and/or directories created
+ * inside the directory will inherit.  This function computes the acl for such
+ * a new file.  If there is no inheritable acl, it will return %NULL.
+ */
+struct richacl *
+richacl_inherit(const struct richacl *dir_acl, int isdir)
+{
+	const struct richace *dir_ace;
+	struct richacl *acl = NULL;
+	struct richace *ace;
+	int count = 0;
+
+	if (isdir) {
+		richacl_for_each_entry(dir_ace, dir_acl) {
+			if (!richace_is_inheritable(dir_ace))
+				continue;
+			count++;
+		}
+		if (!count)
+			return NULL;
+		acl = richacl_alloc(count, GFP_KERNEL);
+		if (!acl)
+			return ERR_PTR(-ENOMEM);
+		ace = acl->a_entries;
+		richacl_for_each_entry(dir_ace, dir_acl) {
+			if (!richace_is_inheritable(dir_ace))
+				continue;
+			richace_copy(ace, dir_ace);
+			if (dir_ace->e_flags & RICHACE_NO_PROPAGATE_INHERIT_ACE)
+				ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
+			else if ((dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE) &&
+				 !(dir_ace->e_flags & RICHACE_DIRECTORY_INHERIT_ACE))
+				ace->e_flags |= RICHACE_INHERIT_ONLY_ACE;
+			ace++;
+		}
+	} else {
+		richacl_for_each_entry(dir_ace, dir_acl) {
+			if (!(dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE))
+				continue;
+			count++;
+		}
+		if (!count)
+			return NULL;
+		acl = richacl_alloc(count, GFP_KERNEL);
+		if (!acl)
+			return ERR_PTR(-ENOMEM);
+		ace = acl->a_entries;
+		richacl_for_each_entry(dir_ace, dir_acl) {
+			if (!(dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE))
+				continue;
+			richace_copy(ace, dir_ace);
+			ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
+			/*
+			 * RICHACE_DELETE_CHILD is meaningless for
+			 * non-directories, so clear it.
+			 */
+			ace->e_mask &= ~RICHACE_DELETE_CHILD;
+			ace++;
+		}
+	}
+
+	return acl;
+}
diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c
index dc2a69f..f3f1f84 100644
--- a/fs/richacl_inode.c
+++ b/fs/richacl_inode.c
@@ -220,3 +220,68 @@ out:
 	return denied ? -EACCES : 0;
 }
 EXPORT_SYMBOL_GPL(richacl_permission);
+
+/**
+ * richacl_inherit_inode  -  compute inherited acl and file mode
+ * @dir_acl:	acl of the containing directory
+ * @inode:	inode of the new file (create mode in i_mode)
+ *
+ * The file permission bits in inode->i_mode must be set to the create mode by
+ * the caller.
+ *
+ * If there is an inheritable acl, the maximum permissions that the acl grants
+ * will be computed and permissions not granted by the acl will be removed from
+ * inode->i_mode.  If there is no inheritable acl, the umask will be applied
+ * instead.
+ */
+static struct richacl *
+richacl_inherit_inode(const struct richacl *dir_acl, struct inode *inode)
+{
+	struct richacl *acl;
+	mode_t mask;
+
+	acl = richacl_inherit(dir_acl, S_ISDIR(inode->i_mode));
+	if (acl) {
+		mask = inode->i_mode;
+		if (richacl_equiv_mode(acl, &mask) == 0) {
+			richacl_put(acl);
+			acl = NULL;
+		} else {
+			richacl_compute_max_masks(acl);
+			/*
+			 * Ensure that the acl will not grant any permissions
+			 * beyond the create mode.
+			 */
+			acl->a_flags |= RICHACL_MASKED;
+			acl->a_owner_mask &=
+				richacl_mode_to_mask(inode->i_mode >> 6);
+			acl->a_group_mask &=
+				richacl_mode_to_mask(inode->i_mode >> 3);
+			acl->a_other_mask &=
+				richacl_mode_to_mask(inode->i_mode);
+			mask = ~S_IRWXUGO | richacl_masks_to_mode(acl);
+		}
+	} else
+		mask = ~current_umask();
+
+	inode->i_mode &= mask;
+	return acl;
+}
+
+struct richacl *richacl_create(struct inode *inode, struct inode *dir)
+{
+	struct richacl *dir_acl, *acl = NULL;
+
+	if (S_ISLNK(inode->i_mode))
+		return NULL;
+	dir_acl = get_richacl(dir);
+	if (dir_acl) {
+		if (IS_ERR(dir_acl))
+			return dir_acl;
+		acl = richacl_inherit_inode(dir_acl, inode);
+		richacl_put(dir_acl);
+	} else
+		inode->i_mode &= ~current_umask();
+	return acl;
+}
+EXPORT_SYMBOL_GPL(richacl_create);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index 6535ce5..9bf95c2 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -305,8 +305,10 @@ extern unsigned int richacl_want_to_mask(unsigned int);
 extern void richacl_compute_max_masks(struct richacl *);
 extern struct richacl *richacl_chmod(struct richacl *, mode_t);
 extern int richacl_equiv_mode(const struct richacl *, mode_t *);
+extern struct richacl *richacl_inherit(const struct richacl *, int);
 
 /* richacl_inode.c */
 extern int richacl_permission(struct inode *, const struct richacl *, int);
+extern struct richacl *richacl_create(struct inode *, struct inode *);
 
 #endif /* __RICHACL_H */
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 15/41] richacl: Automatic Inheritance
  2015-09-05 10:26 ` Andreas Gruenbacher
                   ` (6 preceding siblings ...)
  (?)
@ 2015-09-05 10:27 ` Andreas Gruenbacher
  2015-09-18 18:40   ` J. Bruce Fields
  -1 siblings, 1 reply; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

Automatic Inheritance (AI) allows changes to the acl of a directory to
propagate down to children.

This is mostly implemented in user space: when a process changes the
permissions of a directory and Automatic Inheritance is enabled for that
directory, the process must propagate those changes to all children,
recursively.

The kernel enables this by keeping track of which permissions have been
inherited at file create time.  In addition, it makes sure that
permission propagation is turned off when the permissions of a file are
set explicitly (for example, upon create or chmod).

Automatic Inheritance works as follows:

 - When the RICHACL_AUTO_INHERIT flag in the acl of a file is not set,
   the file is not affected by AI.

 - When the RICHACL_AUTO_INHERIT flag in the acl of a directory is set
   and a file or subdirectory is created in that directory, the
   inherited acl will have the RICHACL_AUTO_INHERIT flag set, and all
   inherited aces will have the RICHACE_INHERITED_ACE flag set.  This
   allows user space to distinguish between aces which have been
   inherited and aces which have been explicitly added.

 - When the RICHACL_PROTECTED acl flag in the acl of a file is set, AI
   will not modify the acl of the file.  This does not affect
   propagation of permissions from the file to its children (if the file
   is a directory).

Linux does not have a way of creating files without setting the file
permission bits, so all files created inside a directory with
RICHACL_AUTO_INHERIT set will have the RICHACL_PROTECTED flag set.  This
effectively disables Automatic Inheritance.

Protocols which support creating files without specifying permissions
can explicitly clear the RICHACL_PROTECTED flag after creating a file
and reset the file masks to "undo" applying the create mode; see
richacl_compute_max_masks().  They should set the RICHACL_DEFAULTED
flag.  This is a workaround; a mechanism that would allow a process to
indicate to the kernel to ignore the create mode when there are
inherited permissions would fix this problem.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/richacl_base.c       | 10 +++++++++-
 fs/richacl_inode.c      |  7 +++++++
 include/linux/richacl.h | 23 ++++++++++++++++++++++-
 3 files changed, 38 insertions(+), 2 deletions(-)

diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index fda407d..a85b7a3 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -363,7 +363,8 @@ richacl_chmod(struct richacl *acl, mode_t mode)
 	if (acl->a_owner_mask == owner_mask &&
 	    acl->a_group_mask == group_mask &&
 	    acl->a_other_mask == other_mask &&
-	    (acl->a_flags & (RICHACL_WRITE_THROUGH | RICHACL_MASKED)))
+	    (acl->a_flags & (RICHACL_WRITE_THROUGH | RICHACL_MASKED)) &&
+	    (!richacl_is_auto_inherit(acl) || richacl_is_protected(acl)))
 		return acl;
 
 	clone = richacl_clone(acl, GFP_KERNEL);
@@ -375,6 +376,8 @@ richacl_chmod(struct richacl *acl, mode_t mode)
 	clone->a_owner_mask = owner_mask;
 	clone->a_group_mask = group_mask;
 	clone->a_other_mask = other_mask;
+	if (richacl_is_auto_inherit(clone))
+		clone->a_flags |= RICHACL_PROTECTED;
 
 	return clone;
 }
@@ -549,6 +552,11 @@ richacl_inherit(const struct richacl *dir_acl, int isdir)
 			ace++;
 		}
 	}
+	if (richacl_is_auto_inherit(dir_acl)) {
+		acl->a_flags = RICHACL_AUTO_INHERIT;
+		richacl_for_each_entry(ace, acl)
+			ace->e_flags |= RICHACE_INHERITED_ACE;
+	}
 
 	return acl;
 }
diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c
index f3f1f84..df175c1 100644
--- a/fs/richacl_inode.c
+++ b/fs/richacl_inode.c
@@ -247,6 +247,13 @@ richacl_inherit_inode(const struct richacl *dir_acl, struct inode *inode)
 			richacl_put(acl);
 			acl = NULL;
 		} else {
+			/*
+			 * We need to set RICHACL_PROTECTED because we are
+			 * doing an implicit chmod
+			 */
+			if (richacl_is_auto_inherit(acl))
+				acl->a_flags |= RICHACL_PROTECTED;
+
 			richacl_compute_max_masks(acl);
 			/*
 			 * Ensure that the acl will not grant any permissions
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index 9bf95c2..832b06c 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -53,10 +53,16 @@ struct richacl {
 	     _ace--)
 
 /* a_flags values */
+#define RICHACL_AUTO_INHERIT			0x01
+#define RICHACL_PROTECTED			0x02
+#define RICHACL_DEFAULTED			0x04
 #define RICHACL_WRITE_THROUGH			0x40
 #define RICHACL_MASKED				0x80
 
 #define RICHACL_VALID_FLAGS (					\
+		RICHACL_AUTO_INHERIT |				\
+		RICHACL_PROTECTED |				\
+		RICHACL_DEFAULTED |				\
 		RICHACL_WRITE_THROUGH |				\
 		RICHACL_MASKED)
 
@@ -70,6 +76,7 @@ struct richacl {
 #define RICHACE_NO_PROPAGATE_INHERIT_ACE	0x0004
 #define RICHACE_INHERIT_ONLY_ACE		0x0008
 #define RICHACE_IDENTIFIER_GROUP		0x0040
+#define RICHACE_INHERITED_ACE			0x0080
 #define RICHACE_SPECIAL_WHO			0x4000
 
 #define RICHACE_VALID_FLAGS (					\
@@ -78,13 +85,15 @@ struct richacl {
 	RICHACE_NO_PROPAGATE_INHERIT_ACE |			\
 	RICHACE_INHERIT_ONLY_ACE |				\
 	RICHACE_IDENTIFIER_GROUP |				\
+	RICHACE_INHERITED_ACE |					\
 	RICHACE_SPECIAL_WHO)
 
 #define RICHACE_INHERITANCE_FLAGS (				\
 	RICHACE_FILE_INHERIT_ACE |				\
 	RICHACE_DIRECTORY_INHERIT_ACE |				\
 	RICHACE_NO_PROPAGATE_INHERIT_ACE |			\
-	RICHACE_INHERIT_ONLY_ACE )
+	RICHACE_INHERIT_ONLY_ACE |				\
+	RICHACE_INHERITED_ACE )
 
 /* e_mask bitflags */
 #define RICHACE_READ_DATA			0x00000001
@@ -195,6 +204,18 @@ extern void set_cached_richacl(struct inode *, struct richacl *);
 extern void forget_cached_richacl(struct inode *);
 extern struct richacl *get_richacl(struct inode *);
 
+static inline int
+richacl_is_auto_inherit(const struct richacl *acl)
+{
+	return acl->a_flags & RICHACL_AUTO_INHERIT;
+}
+
+static inline int
+richacl_is_protected(const struct richacl *acl)
+{
+	return acl->a_flags & RICHACL_PROTECTED;
+}
+
 /**
  * richace_is_owner  -  check if @ace is an OWNER@ entry
  */
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 16/41] richacl: xattr mapping functions
  2015-09-05 10:26 ` Andreas Gruenbacher
                   ` (7 preceding siblings ...)
  (?)
@ 2015-09-05 10:27 ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

Map between "system.richacl" xattrs and the in-kernel representation.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/Makefile                   |   2 +-
 fs/richacl_xattr.c            | 220 ++++++++++++++++++++++++++++++++++++++++++
 fs/xattr.c                    |  34 +++++--
 include/linux/richacl_xattr.h |  62 ++++++++++++
 include/uapi/linux/xattr.h    |   2 +
 5 files changed, 313 insertions(+), 7 deletions(-)
 create mode 100644 fs/richacl_xattr.c
 create mode 100644 include/linux/richacl_xattr.h

diff --git a/fs/Makefile b/fs/Makefile
index 1305047..baf385a 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -48,7 +48,7 @@ obj-$(CONFIG_SYSCTL)		+= drop_caches.o
 
 obj-$(CONFIG_FHANDLE)		+= fhandle.o
 obj-$(CONFIG_FS_RICHACL)	+= richacl.o
-richacl-y			:= richacl_base.o richacl_inode.o
+richacl-y			:= richacl_base.o richacl_inode.o richacl_xattr.o
 
 obj-y				+= quota/
 
diff --git a/fs/richacl_xattr.c b/fs/richacl_xattr.c
new file mode 100644
index 0000000..cd9979d
--- /dev/null
+++ b/fs/richacl_xattr.c
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2006, 2010  Novell, Inc.
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Written by Andreas Gruenbacher <agruen@kernel.org>
+ *
+ * This program 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, or (at your option) any
+ * later version.
+ *
+ * This program 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.
+ */
+
+#include <linux/sched.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/richacl_xattr.h>
+
+MODULE_LICENSE("GPL");
+
+/**
+ * richacl_from_xattr  -  convert a richacl xattr into the in-memory representation
+ */
+struct richacl *
+richacl_from_xattr(struct user_namespace *user_ns,
+		   const void *value, size_t size)
+{
+	const struct richacl_xattr *xattr_acl = value;
+	const struct richace_xattr *xattr_ace = (void *)(xattr_acl + 1);
+	struct richacl *acl;
+	struct richace *ace;
+	int count;
+
+	if (size < sizeof(*xattr_acl) ||
+	    xattr_acl->a_version != RICHACL_XATTR_VERSION ||
+	    (xattr_acl->a_flags & ~RICHACL_VALID_FLAGS))
+		return ERR_PTR(-EINVAL);
+	size -= sizeof(*xattr_acl);
+	count = le16_to_cpu(xattr_acl->a_count);
+	if (count > RICHACL_XATTR_MAX_COUNT)
+		return ERR_PTR(-EINVAL);
+	if (size != count * sizeof(*xattr_ace))
+		return ERR_PTR(-EINVAL);
+
+	acl = richacl_alloc(count, GFP_NOFS);
+	if (!acl)
+		return ERR_PTR(-ENOMEM);
+
+	acl->a_flags = xattr_acl->a_flags;
+	acl->a_owner_mask = le32_to_cpu(xattr_acl->a_owner_mask);
+	if (acl->a_owner_mask & ~RICHACE_VALID_MASK)
+		goto fail_einval;
+	acl->a_group_mask = le32_to_cpu(xattr_acl->a_group_mask);
+	if (acl->a_group_mask & ~RICHACE_VALID_MASK)
+		goto fail_einval;
+	acl->a_other_mask = le32_to_cpu(xattr_acl->a_other_mask);
+	if (acl->a_other_mask & ~RICHACE_VALID_MASK)
+		goto fail_einval;
+
+	richacl_for_each_entry(ace, acl) {
+		ace->e_type  = le16_to_cpu(xattr_ace->e_type);
+		ace->e_flags = le16_to_cpu(xattr_ace->e_flags);
+		ace->e_mask  = le32_to_cpu(xattr_ace->e_mask);
+
+		if (ace->e_flags & ~RICHACE_VALID_FLAGS)
+			goto fail_einval;
+		if (ace->e_flags & RICHACE_SPECIAL_WHO) {
+			ace->e_id.special = le32_to_cpu(xattr_ace->e_id);
+			if (ace->e_id.special > RICHACE_EVERYONE_SPECIAL_ID)
+				goto fail_einval;
+		} else if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
+			u32 id = le32_to_cpu(xattr_ace->e_id);
+
+			ace->e_id.gid = make_kgid(user_ns, id);
+			if (!gid_valid(ace->e_id.gid))
+				goto fail_einval;
+		} else {
+			u32 id = le32_to_cpu(xattr_ace->e_id);
+
+			ace->e_id.uid = make_kuid(user_ns, id);
+			if (!uid_valid(ace->e_id.uid))
+				goto fail_einval;
+		}
+		if (ace->e_type > RICHACE_ACCESS_DENIED_ACE_TYPE ||
+		    (ace->e_mask & ~RICHACE_VALID_MASK))
+			goto fail_einval;
+
+		xattr_ace++;
+	}
+
+	return acl;
+
+fail_einval:
+	richacl_put(acl);
+	return ERR_PTR(-EINVAL);
+}
+EXPORT_SYMBOL_GPL(richacl_from_xattr);
+
+/**
+ * richacl_xattr_size  -  compute the size of the xattr representation of @acl
+ */
+size_t
+richacl_xattr_size(const struct richacl *acl)
+{
+	size_t size = sizeof(struct richacl_xattr);
+
+	size += sizeof(struct richace_xattr) * acl->a_count;
+	return size;
+}
+EXPORT_SYMBOL_GPL(richacl_xattr_size);
+
+/**
+ * richacl_to_xattr  -  convert @acl into its xattr representation
+ * @acl:	the richacl to convert
+ * @buffer:	buffer for the result
+ * @size:	size of @buffer
+ */
+int
+richacl_to_xattr(struct user_namespace *user_ns,
+		 const struct richacl *acl, void *buffer, size_t size)
+{
+	struct richacl_xattr *xattr_acl = buffer;
+	struct richace_xattr *xattr_ace;
+	const struct richace *ace;
+	size_t real_size;
+
+	real_size = richacl_xattr_size(acl);
+	if (!buffer)
+		return real_size;
+	if (real_size > size)
+		return -ERANGE;
+
+	xattr_acl->a_version = RICHACL_XATTR_VERSION;
+	xattr_acl->a_flags = acl->a_flags;
+	xattr_acl->a_count = cpu_to_le16(acl->a_count);
+
+	xattr_acl->a_owner_mask = cpu_to_le32(acl->a_owner_mask);
+	xattr_acl->a_group_mask = cpu_to_le32(acl->a_group_mask);
+	xattr_acl->a_other_mask = cpu_to_le32(acl->a_other_mask);
+
+	xattr_ace = (void *)(xattr_acl + 1);
+	richacl_for_each_entry(ace, acl) {
+		xattr_ace->e_type = cpu_to_le16(ace->e_type);
+		xattr_ace->e_flags = cpu_to_le16(ace->e_flags);
+		xattr_ace->e_mask = cpu_to_le32(ace->e_mask);
+		if (ace->e_flags & RICHACE_SPECIAL_WHO)
+			xattr_ace->e_id = cpu_to_le32(ace->e_id.special);
+		else if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
+			xattr_ace->e_id =
+				cpu_to_le32(from_kgid(user_ns, ace->e_id.gid));
+		else
+			xattr_ace->e_id =
+				cpu_to_le32(from_kuid(user_ns, ace->e_id.uid));
+		xattr_ace++;
+	}
+	return real_size;
+}
+EXPORT_SYMBOL_GPL(richacl_to_xattr);
+
+/*
+ * Fix up the uids and gids in richacl extended attributes in place.
+ */
+static void richacl_fix_xattr_userns(
+	struct user_namespace *to, struct user_namespace *from,
+	void *value, size_t size)
+{
+	struct richacl_xattr *xattr_acl = value;
+	struct richace_xattr *xattr_ace =
+		(struct richace_xattr *)(xattr_acl + 1);
+	unsigned int count;
+
+	if (!value)
+		return;
+	if (size < sizeof(*xattr_acl))
+		return;
+	if (xattr_acl->a_version != cpu_to_le32(RICHACL_XATTR_VERSION))
+		return;
+	size -= sizeof(*xattr_acl);
+	if (size % sizeof(*xattr_ace))
+		return;
+	count = size / sizeof(*xattr_ace);
+	for (; count; count--, xattr_ace++) {
+		if (xattr_ace->e_flags & cpu_to_le16(RICHACE_SPECIAL_WHO))
+			continue;
+		if (xattr_ace->e_flags &
+		    cpu_to_le16(RICHACE_IDENTIFIER_GROUP)) {
+			u32 id = le32_to_cpu(xattr_ace->e_id);
+			kgid_t gid = make_kgid(from, id);
+
+			xattr_ace->e_id = cpu_to_le32(from_kgid(to, gid));
+		} else {
+			u32 id = le32_to_cpu(xattr_ace->e_id);
+			kuid_t uid = make_kuid(from, id);
+
+			xattr_ace->e_id = cpu_to_le32(from_kuid(to, uid));
+		}
+	}
+}
+
+void richacl_fix_xattr_from_user(void *value, size_t size)
+{
+	struct user_namespace *user_ns = current_user_ns();
+
+	if (user_ns == &init_user_ns)
+		return;
+	richacl_fix_xattr_userns(&init_user_ns, user_ns, value, size);
+}
+
+void richacl_fix_xattr_to_user(void *value, size_t size)
+{
+	struct user_namespace *user_ns = current_user_ns();
+
+	if (user_ns == &init_user_ns)
+		return;
+	richacl_fix_xattr_userns(user_ns, &init_user_ns, value, size);
+}
diff --git a/fs/xattr.c b/fs/xattr.c
index 072fee1..f2313c6 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -21,6 +21,7 @@
 #include <linux/audit.h>
 #include <linux/vmalloc.h>
 #include <linux/posix_acl_xattr.h>
+#include <linux/richacl_xattr.h>
 
 #include <asm/uaccess.h>
 
@@ -314,6 +315,18 @@ out:
 }
 EXPORT_SYMBOL_GPL(vfs_removexattr);
 
+static void
+fix_xattr_from_user(const char *kname, void *kvalue, size_t size)
+{
+	if (strncmp(kname, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN))
+		return;
+	kname += XATTR_SYSTEM_PREFIX_LEN;
+	if (!strcmp(kname, XATTR_POSIX_ACL_ACCESS) ||
+	    !strcmp(kname, XATTR_POSIX_ACL_DEFAULT))
+		posix_acl_fix_xattr_from_user(kvalue, size);
+	else if (!strcmp(kname, XATTR_RICHACL))
+		richacl_fix_xattr_from_user(kvalue, size);
+}
 
 /*
  * Extended attribute SET operations
@@ -350,9 +363,7 @@ setxattr(struct dentry *d, const char __user *name, const void __user *value,
 			error = -EFAULT;
 			goto out;
 		}
-		if ((strcmp(kname, XATTR_NAME_POSIX_ACL_ACCESS) == 0) ||
-		    (strcmp(kname, XATTR_NAME_POSIX_ACL_DEFAULT) == 0))
-			posix_acl_fix_xattr_from_user(kvalue, size);
+		fix_xattr_from_user(kname, kvalue, size);
 	}
 
 	error = vfs_setxattr(d, kname, kvalue, size, flags);
@@ -419,6 +430,19 @@ SYSCALL_DEFINE5(fsetxattr, int, fd, const char __user *, name,
 	return error;
 }
 
+static void
+fix_xattr_to_user(const char *kname, void *kvalue, size_t size)
+{
+	if (strncmp(kname, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN))
+		return;
+	kname += XATTR_SYSTEM_PREFIX_LEN;
+	if (!strcmp(kname, XATTR_POSIX_ACL_ACCESS) ||
+	    !strcmp(kname, XATTR_POSIX_ACL_DEFAULT))
+		posix_acl_fix_xattr_to_user(kvalue, size);
+	else if (!strcmp(kname, XATTR_RICHACL))
+		richacl_fix_xattr_to_user(kvalue, size);
+}
+
 /*
  * Extended attribute GET operations
  */
@@ -451,9 +475,7 @@ getxattr(struct dentry *d, const char __user *name, void __user *value,
 
 	error = vfs_getxattr(d, kname, kvalue, size);
 	if (error > 0) {
-		if ((strcmp(kname, XATTR_NAME_POSIX_ACL_ACCESS) == 0) ||
-		    (strcmp(kname, XATTR_NAME_POSIX_ACL_DEFAULT) == 0))
-			posix_acl_fix_xattr_to_user(kvalue, size);
+		fix_xattr_to_user(kname, kvalue, size);
 		if (size && copy_to_user(value, kvalue, error))
 			error = -EFAULT;
 	} else if (error == -ERANGE && size >= XATTR_SIZE_MAX) {
diff --git a/include/linux/richacl_xattr.h b/include/linux/richacl_xattr.h
new file mode 100644
index 0000000..f84cc21
--- /dev/null
+++ b/include/linux/richacl_xattr.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2006, 2010  Novell, Inc.
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Written by Andreas Gruenbacher <agruen@kernel.org>
+ *
+ * This program 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, or (at your option) any
+ * later version.
+ *
+ * This program 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.
+ */
+
+#ifndef __RICHACL_XATTR_H
+#define __RICHACL_XATTR_H
+
+#include <linux/richacl.h>
+
+struct richace_xattr {
+	__le16		e_type;
+	__le16		e_flags;
+	__le32		e_mask;
+	__le32		e_id;
+};
+
+struct richacl_xattr {
+	unsigned char	a_version;
+	unsigned char	a_flags;
+	__le16		a_count;
+	__le32		a_owner_mask;
+	__le32		a_group_mask;
+	__le32		a_other_mask;
+};
+
+#define RICHACL_XATTR_VERSION	0
+#define RICHACL_XATTR_MAX_COUNT \
+	((XATTR_SIZE_MAX - sizeof(struct richacl_xattr)) / \
+	 sizeof(struct richace_xattr))
+
+extern struct richacl *richacl_from_xattr(struct user_namespace *, const void *,
+					  size_t);
+extern size_t richacl_xattr_size(const struct richacl *);
+extern int richacl_to_xattr(struct user_namespace *, const struct richacl *,
+			    void *, size_t);
+
+#ifdef CONFIG_FS_RICHACL
+extern void richacl_fix_xattr_from_user(void *, size_t);
+extern void richacl_fix_xattr_to_user(void *, size_t);
+#else
+static inline void richacl_fix_xattr_from_user(void *value, size_t size)
+{
+}
+
+static inline void richacl_fix_xattr_to_user(void *value, size_t size)
+{
+}
+#endif
+
+#endif /* __RICHACL_XATTR_H */
diff --git a/include/uapi/linux/xattr.h b/include/uapi/linux/xattr.h
index 1590c49..1996903 100644
--- a/include/uapi/linux/xattr.h
+++ b/include/uapi/linux/xattr.h
@@ -73,5 +73,7 @@
 #define XATTR_POSIX_ACL_DEFAULT  "posix_acl_default"
 #define XATTR_NAME_POSIX_ACL_DEFAULT XATTR_SYSTEM_PREFIX XATTR_POSIX_ACL_DEFAULT
 
+#define XATTR_RICHACL "richacl"
+#define XATTR_NAME_RICHACL XATTR_SYSTEM_PREFIX XATTR_RICHACL
 
 #endif /* _UAPI_LINUX_XATTR_H */
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 17/41] vfs: Add richacl permission checking
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

Hook the richacl permission checking function into the vfs.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/namei.c     | 51 +++++++++++++++++++++++++++++++++++++++++++++++++--
 fs/posix_acl.c |  6 +++---
 2 files changed, 52 insertions(+), 5 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index d7bd134..4b8238c 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -35,6 +35,7 @@
 #include <linux/fs_struct.h>
 #include <linux/posix_acl.h>
 #include <linux/hash.h>
+#include <linux/richacl.h>
 #include <asm/uaccess.h>
 
 #include "internal.h"
@@ -255,7 +256,40 @@ void putname(struct filename *name)
 		__putname(name);
 }
 
-static int check_acl(struct inode *inode, int mask)
+static int check_richacl(struct inode *inode, int mask)
+{
+#ifdef CONFIG_FS_RICHACL
+	struct richacl *acl;
+
+	if (mask & MAY_NOT_BLOCK) {
+		acl = get_cached_richacl_rcu(inode);
+		if (!acl)
+			goto no_acl;
+		/* no ->get_richacl() calls in RCU mode... */
+		if (acl == ACL_NOT_CACHED)
+			return -ECHILD;
+		return richacl_permission(inode, acl, mask & ~MAY_NOT_BLOCK);
+	}
+
+	acl = get_richacl(inode);
+	if (IS_ERR(acl))
+		return PTR_ERR(acl);
+	if (acl) {
+		int error = richacl_permission(inode, acl, mask);
+		richacl_put(acl);
+		return error;
+	}
+no_acl:
+#endif
+	if (mask & (MAY_DELETE_SELF | MAY_TAKE_OWNERSHIP |
+		    MAY_CHMOD | MAY_SET_TIMES)) {
+		/* File permission bits cannot grant this. */
+		return -EACCES;
+	}
+	return -EAGAIN;
+}
+
+static int check_posix_acl(struct inode *inode, int mask)
 {
 #ifdef CONFIG_FS_POSIX_ACL
 	struct posix_acl *acl;
@@ -290,11 +324,24 @@ static int acl_permission_check(struct inode *inode, int mask)
 {
 	unsigned int mode = inode->i_mode;
 
+	/*
+	 * With POSIX ACLs, the (mode & S_IRWXU) bits exactly match the owner
+	 * permissions, and we can skip checking posix acls for the owner.
+	 * With richacls, the owner may be granted fewer permissions than the
+	 * mode bits seem to suggest (for example, append but not write), and
+	 * we always need to check the richacl.
+	 */
+
+	if (IS_RICHACL(inode)) {
+		int error = check_richacl(inode, mask);
+		if (error != -EAGAIN)
+			return error;
+	}
 	if (likely(uid_eq(current_fsuid(), inode->i_uid)))
 		mode >>= 6;
 	else {
 		if (IS_POSIXACL(inode) && (mode & S_IRWXG)) {
-			int error = check_acl(inode, mask);
+			int error = check_posix_acl(inode, mask);
 			if (error != -EAGAIN)
 				return error;
 		}
diff --git a/fs/posix_acl.c b/fs/posix_acl.c
index 1d766a5..3459bd5 100644
--- a/fs/posix_acl.c
+++ b/fs/posix_acl.c
@@ -100,13 +100,13 @@ struct posix_acl *get_acl(struct inode *inode, int type)
 {
 	struct posix_acl *acl;
 
+	if (!IS_POSIXACL(inode))
+		return NULL;
+
 	acl = get_cached_acl(inode, type);
 	if (acl != ACL_NOT_CACHED)
 		return acl;
 
-	if (!IS_POSIXACL(inode))
-		return NULL;
-
 	/*
 	 * A filesystem can force a ACL callback by just never filling the
 	 * ACL cache. But normally you'd fill the cache either at inode
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 17/41] vfs: Add richacl permission checking
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

Hook the richacl permission checking function into the vfs.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/namei.c     | 51 +++++++++++++++++++++++++++++++++++++++++++++++++--
 fs/posix_acl.c |  6 +++---
 2 files changed, 52 insertions(+), 5 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index d7bd134..4b8238c 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -35,6 +35,7 @@
 #include <linux/fs_struct.h>
 #include <linux/posix_acl.h>
 #include <linux/hash.h>
+#include <linux/richacl.h>
 #include <asm/uaccess.h>
 
 #include "internal.h"
@@ -255,7 +256,40 @@ void putname(struct filename *name)
 		__putname(name);
 }
 
-static int check_acl(struct inode *inode, int mask)
+static int check_richacl(struct inode *inode, int mask)
+{
+#ifdef CONFIG_FS_RICHACL
+	struct richacl *acl;
+
+	if (mask & MAY_NOT_BLOCK) {
+		acl = get_cached_richacl_rcu(inode);
+		if (!acl)
+			goto no_acl;
+		/* no ->get_richacl() calls in RCU mode... */
+		if (acl == ACL_NOT_CACHED)
+			return -ECHILD;
+		return richacl_permission(inode, acl, mask & ~MAY_NOT_BLOCK);
+	}
+
+	acl = get_richacl(inode);
+	if (IS_ERR(acl))
+		return PTR_ERR(acl);
+	if (acl) {
+		int error = richacl_permission(inode, acl, mask);
+		richacl_put(acl);
+		return error;
+	}
+no_acl:
+#endif
+	if (mask & (MAY_DELETE_SELF | MAY_TAKE_OWNERSHIP |
+		    MAY_CHMOD | MAY_SET_TIMES)) {
+		/* File permission bits cannot grant this. */
+		return -EACCES;
+	}
+	return -EAGAIN;
+}
+
+static int check_posix_acl(struct inode *inode, int mask)
 {
 #ifdef CONFIG_FS_POSIX_ACL
 	struct posix_acl *acl;
@@ -290,11 +324,24 @@ static int acl_permission_check(struct inode *inode, int mask)
 {
 	unsigned int mode = inode->i_mode;
 
+	/*
+	 * With POSIX ACLs, the (mode & S_IRWXU) bits exactly match the owner
+	 * permissions, and we can skip checking posix acls for the owner.
+	 * With richacls, the owner may be granted fewer permissions than the
+	 * mode bits seem to suggest (for example, append but not write), and
+	 * we always need to check the richacl.
+	 */
+
+	if (IS_RICHACL(inode)) {
+		int error = check_richacl(inode, mask);
+		if (error != -EAGAIN)
+			return error;
+	}
 	if (likely(uid_eq(current_fsuid(), inode->i_uid)))
 		mode >>= 6;
 	else {
 		if (IS_POSIXACL(inode) && (mode & S_IRWXG)) {
-			int error = check_acl(inode, mask);
+			int error = check_posix_acl(inode, mask);
 			if (error != -EAGAIN)
 				return error;
 		}
diff --git a/fs/posix_acl.c b/fs/posix_acl.c
index 1d766a5..3459bd5 100644
--- a/fs/posix_acl.c
+++ b/fs/posix_acl.c
@@ -100,13 +100,13 @@ struct posix_acl *get_acl(struct inode *inode, int type)
 {
 	struct posix_acl *acl;
 
+	if (!IS_POSIXACL(inode))
+		return NULL;
+
 	acl = get_cached_acl(inode, type);
 	if (acl != ACL_NOT_CACHED)
 		return acl;
 
-	if (!IS_POSIXACL(inode))
-		return NULL;
-
 	/*
 	 * A filesystem can force a ACL callback by just never filling the
 	 * ACL cache. But normally you'd fill the cache either at inode
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 18/41] ext4: Add richacl support
  2015-09-05 10:26 ` Andreas Gruenbacher
                   ` (8 preceding siblings ...)
  (?)
@ 2015-09-05 10:27 ` Andreas Gruenbacher
       [not found]   ` <1441448856-13478-19-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
  -1 siblings, 1 reply; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module
  Cc: Aneesh Kumar K.V

From: "Aneesh Kumar K.V" <aneesh.kumar@linux.vnet.ibm.com>

Support the richacl permission model in ext4.  The richacls are stored
in "system.richacl" xattrs.  Richacls need to be enabled by tune2fs or
at file system create time.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/ext4/Kconfig   |  15 ++++
 fs/ext4/Makefile  |   1 +
 fs/ext4/acl.c     |   6 +-
 fs/ext4/acl.h     |  12 +--
 fs/ext4/file.c    |   6 +-
 fs/ext4/ialloc.c  |   7 +-
 fs/ext4/inode.c   |  10 ++-
 fs/ext4/namei.c   |  11 ++-
 fs/ext4/richacl.c | 213 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 fs/ext4/richacl.h |  47 ++++++++++++
 fs/ext4/xattr.c   |   6 ++
 fs/ext4/xattr.h   |   1 +
 12 files changed, 316 insertions(+), 19 deletions(-)
 create mode 100644 fs/ext4/richacl.c
 create mode 100644 fs/ext4/richacl.h

diff --git a/fs/ext4/Kconfig b/fs/ext4/Kconfig
index bf8bc8a..77a386d 100644
--- a/fs/ext4/Kconfig
+++ b/fs/ext4/Kconfig
@@ -96,3 +96,18 @@ config EXT4_DEBUG
 	  If you select Y here, then you will be able to turn on debugging
 	  with a command such as:
 		echo 1 > /sys/module/ext4/parameters/mballoc_debug
+
+config EXT4_FS_RICHACL
+	bool "Ext4 Rich Access Control Lists (EXPERIMENTAL)"
+	depends on EXT4_FS
+	select FS_RICHACL
+	help
+	  Rich ACLs are an implementation of NFSv4 ACLs, extended by file masks
+	  to fit into the standard POSIX file permission model.  They are
+	  designed to work seamlessly locally as well as across the NFSv4 and
+	  CIFS/SMB2 network file system protocols.
+
+	  To learn more about Rich ACL, visit
+	  http://acl.bestbits.at/richacl/
+
+	  If you don't know what Rich ACLs are, say N
diff --git a/fs/ext4/Makefile b/fs/ext4/Makefile
index 75285ea..ea0d539 100644
--- a/fs/ext4/Makefile
+++ b/fs/ext4/Makefile
@@ -14,3 +14,4 @@ ext4-$(CONFIG_EXT4_FS_POSIX_ACL)	+= acl.o
 ext4-$(CONFIG_EXT4_FS_SECURITY)		+= xattr_security.o
 ext4-$(CONFIG_EXT4_FS_ENCRYPTION)	+= crypto_policy.o crypto.o \
 		crypto_key.o crypto_fname.o
+ext4-$(CONFIG_EXT4_FS_RICHACL) 		+= richacl.o
diff --git a/fs/ext4/acl.c b/fs/ext4/acl.c
index 69b1e73..d965fa6 100644
--- a/fs/ext4/acl.c
+++ b/fs/ext4/acl.c
@@ -140,7 +140,7 @@ fail:
  * inode->i_mutex: don't care
  */
 struct posix_acl *
-ext4_get_acl(struct inode *inode, int type)
+ext4_get_posix_acl(struct inode *inode, int type)
 {
 	int name_index;
 	char *value = NULL;
@@ -234,7 +234,7 @@ __ext4_set_acl(handle_t *handle, struct inode *inode, int type,
 }
 
 int
-ext4_set_acl(struct inode *inode, struct posix_acl *acl, int type)
+ext4_set_posix_acl(struct inode *inode, struct posix_acl *acl, int type)
 {
 	handle_t *handle;
 	int error, retries = 0;
@@ -259,7 +259,7 @@ retry:
  * inode->i_mutex: up (access to inode is still exclusive)
  */
 int
-ext4_init_acl(handle_t *handle, struct inode *inode, struct inode *dir)
+ext4_init_posix_acl(handle_t *handle, struct inode *inode, struct inode *dir)
 {
 	struct posix_acl *default_acl, *acl;
 	int error;
diff --git a/fs/ext4/acl.h b/fs/ext4/acl.h
index da2c795..450b4d1 100644
--- a/fs/ext4/acl.h
+++ b/fs/ext4/acl.h
@@ -54,17 +54,17 @@ static inline int ext4_acl_count(size_t size)
 #ifdef CONFIG_EXT4_FS_POSIX_ACL
 
 /* acl.c */
-struct posix_acl *ext4_get_acl(struct inode *inode, int type);
-int ext4_set_acl(struct inode *inode, struct posix_acl *acl, int type);
-extern int ext4_init_acl(handle_t *, struct inode *, struct inode *);
+struct posix_acl *ext4_get_posix_acl(struct inode *inode, int type);
+int ext4_set_posix_acl(struct inode *inode, struct posix_acl *acl, int type);
+extern int ext4_init_posix_acl(handle_t *, struct inode *, struct inode *);
 
 #else  /* CONFIG_EXT4_FS_POSIX_ACL */
 #include <linux/sched.h>
-#define ext4_get_acl NULL
-#define ext4_set_acl NULL
+#define ext4_get_posix_acl NULL
+#define ext4_set_posix_acl NULL
 
 static inline int
-ext4_init_acl(handle_t *handle, struct inode *inode, struct inode *dir)
+ext4_init_posix_acl(handle_t *handle, struct inode *inode, struct inode *dir)
 {
 	return 0;
 }
diff --git a/fs/ext4/file.c b/fs/ext4/file.c
index bc313ac..3d3fcc8 100644
--- a/fs/ext4/file.c
+++ b/fs/ext4/file.c
@@ -29,6 +29,7 @@
 #include "ext4_jbd2.h"
 #include "xattr.h"
 #include "acl.h"
+#include "richacl.h"
 
 /*
  * Called when an inode is released. Note that this is different
@@ -659,8 +660,9 @@ const struct inode_operations ext4_file_inode_operations = {
 	.getxattr	= generic_getxattr,
 	.listxattr	= ext4_listxattr,
 	.removexattr	= generic_removexattr,
-	.get_acl	= ext4_get_acl,
-	.set_acl	= ext4_set_acl,
+	.get_acl	= ext4_get_posix_acl,
+	.set_acl	= ext4_set_posix_acl,
+	.get_richacl	= ext4_get_richacl,
 	.fiemap		= ext4_fiemap,
 };
 
diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c
index 173c1ae..aa5d1e1 100644
--- a/fs/ext4/ialloc.c
+++ b/fs/ext4/ialloc.c
@@ -27,6 +27,7 @@
 #include "ext4_jbd2.h"
 #include "xattr.h"
 #include "acl.h"
+#include "richacl.h"
 
 #include <trace/events/ext4.h>
 
@@ -1050,7 +1051,11 @@ got:
 	if (err)
 		goto fail_drop;
 
-	err = ext4_init_acl(handle, inode, dir);
+	if (EXT4_IS_RICHACL(dir))
+		err = ext4_init_richacl(handle, inode, dir);
+	else
+		err = ext4_init_posix_acl(handle, inode, dir);
+
 	if (err)
 		goto fail_free_drop;
 
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index cecf9aa..7f6bbe8 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -41,6 +41,7 @@
 #include "xattr.h"
 #include "acl.h"
 #include "truncate.h"
+#include "richacl.h"
 
 #include <trace/events/ext4.h>
 
@@ -4782,9 +4783,12 @@ int ext4_setattr(struct dentry *dentry, struct iattr *attr)
 	if (orphan && inode->i_nlink)
 		ext4_orphan_del(NULL, inode);
 
-	if (!rc && (ia_valid & ATTR_MODE))
-		rc = posix_acl_chmod(inode, inode->i_mode);
-
+	if (!rc && (ia_valid & ATTR_MODE)) {
+		if (EXT4_IS_RICHACL(inode))
+			rc = ext4_richacl_chmod(inode);
+		else
+			rc = posix_acl_chmod(inode, inode->i_mode);
+	}
 err_out:
 	ext4_std_error(inode->i_sb, error);
 	if (!error)
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index 011dcfb..9be6a8a 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -38,6 +38,7 @@
 
 #include "xattr.h"
 #include "acl.h"
+#include "richacl.h"
 
 #include <trace/events/ext4.h>
 /*
@@ -3821,8 +3822,9 @@ const struct inode_operations ext4_dir_inode_operations = {
 	.getxattr	= generic_getxattr,
 	.listxattr	= ext4_listxattr,
 	.removexattr	= generic_removexattr,
-	.get_acl	= ext4_get_acl,
-	.set_acl	= ext4_set_acl,
+	.get_acl	= ext4_get_posix_acl,
+	.set_acl	= ext4_set_posix_acl,
+	.get_richacl	= ext4_get_richacl,
 	.fiemap         = ext4_fiemap,
 };
 
@@ -3832,6 +3834,7 @@ const struct inode_operations ext4_special_inode_operations = {
 	.getxattr	= generic_getxattr,
 	.listxattr	= ext4_listxattr,
 	.removexattr	= generic_removexattr,
-	.get_acl	= ext4_get_acl,
-	.set_acl	= ext4_set_acl,
+	.get_acl	= ext4_get_posix_acl,
+	.set_acl	= ext4_set_posix_acl,
+	.get_richacl	= ext4_get_richacl,
 };
diff --git a/fs/ext4/richacl.c b/fs/ext4/richacl.c
new file mode 100644
index 0000000..6758def
--- /dev/null
+++ b/fs/ext4/richacl.c
@@ -0,0 +1,213 @@
+/*
+ * Copyright IBM Corporation, 2010
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/richacl_xattr.h>
+
+#include "ext4.h"
+#include "ext4_jbd2.h"
+#include "xattr.h"
+#include "acl.h"
+#include "richacl.h"
+
+struct richacl *
+ext4_get_richacl(struct inode *inode)
+{
+	const int name_index = EXT4_XATTR_INDEX_RICHACL;
+	void *value = NULL;
+	struct richacl *acl;
+	int retval;
+
+	if (!IS_RICHACL(inode))
+		return ERR_PTR(-EOPNOTSUPP);
+	acl = get_cached_richacl(inode);
+	if (acl != ACL_NOT_CACHED)
+		return acl;
+	retval = ext4_xattr_get(inode, name_index, "", NULL, 0);
+	if (retval > 0) {
+		value = kmalloc(retval, GFP_NOFS);
+		if (!value)
+			return ERR_PTR(-ENOMEM);
+		retval = ext4_xattr_get(inode, name_index, "", value, retval);
+	}
+	if (retval > 0) {
+		acl = richacl_from_xattr(&init_user_ns, value, retval);
+		if (acl == ERR_PTR(-EINVAL))
+			acl = ERR_PTR(-EIO);
+	} else if (retval == -ENODATA || retval == -ENOSYS)
+		acl = NULL;
+	else
+		acl = ERR_PTR(retval);
+	kfree(value);
+
+	if (!IS_ERR(acl))
+		set_cached_richacl(inode, acl);
+
+	return acl;
+}
+
+static int
+ext4_set_richacl(handle_t *handle, struct inode *inode, struct richacl *acl)
+{
+	const int name_index = EXT4_XATTR_INDEX_RICHACL;
+	size_t size = 0;
+	void *value = NULL;
+	int retval;
+
+	if (acl) {
+		mode_t mode = inode->i_mode;
+
+		if (richacl_equiv_mode(acl, &mode) == 0) {
+			inode->i_mode = mode;
+			ext4_mark_inode_dirty(handle, inode);
+			acl = NULL;
+		}
+	}
+	if (acl) {
+		size = richacl_xattr_size(acl);
+		value = kmalloc(size, GFP_NOFS);
+		if (!value)
+			return -ENOMEM;
+		richacl_to_xattr(&init_user_ns, acl, value, size);
+	}
+	if (handle)
+		retval = ext4_xattr_set_handle(handle, inode, name_index, "",
+					       value, size, 0);
+	else
+		retval = ext4_xattr_set(inode, name_index, "", value, size, 0);
+	kfree(value);
+	if (!retval)
+		set_cached_richacl(inode, acl);
+
+	return retval;
+}
+
+int
+ext4_init_richacl(handle_t *handle, struct inode *inode, struct inode *dir)
+{
+	struct richacl *acl = richacl_create(inode, dir);
+	int error;
+
+	error = PTR_ERR(acl);
+	if (IS_ERR(acl))
+		return error;
+	if (acl) {
+		error = ext4_set_richacl(handle, inode, acl);
+		richacl_put(acl);
+	}
+	return error;
+}
+
+int
+ext4_richacl_chmod(struct inode *inode)
+{
+	struct richacl *acl;
+	int retval;
+
+	if (S_ISLNK(inode->i_mode))
+		return -EOPNOTSUPP;
+	acl = ext4_get_richacl(inode);
+	if (IS_ERR_OR_NULL(acl))
+		return PTR_ERR(acl);
+	acl = richacl_chmod(acl, inode->i_mode);
+	if (IS_ERR(acl))
+		return PTR_ERR(acl);
+	retval = ext4_set_richacl(NULL, inode, acl);
+	richacl_put(acl);
+
+	return retval;
+}
+
+static size_t
+ext4_xattr_list_richacl(struct dentry *dentry, char *list, size_t list_len,
+			const char *name, size_t name_len, int type)
+{
+	const size_t size = sizeof(XATTR_NAME_RICHACL);
+
+	if (!IS_RICHACL(d_inode(dentry)))
+		return 0;
+	if (list && size <= list_len)
+		memcpy(list, XATTR_NAME_RICHACL, size);
+	return size;
+}
+
+static int
+ext4_xattr_get_richacl(struct dentry *dentry, const char *name, void *buffer,
+		size_t buffer_size, int type)
+{
+	struct richacl *acl;
+	int error;
+
+	if (strcmp(name, "") != 0)
+		return -EINVAL;
+	acl = ext4_get_richacl(d_inode(dentry));
+	if (IS_ERR(acl))
+		return PTR_ERR(acl);
+	if (acl == NULL)
+		return -ENODATA;
+
+	error = richacl_to_xattr(&init_user_ns, acl, buffer, buffer_size);
+	richacl_put(acl);
+	return error;
+}
+
+static int
+ext4_xattr_set_richacl(struct dentry *dentry, const char *name,
+		const void *value, size_t size, int flags, int type)
+{
+	handle_t *handle;
+	struct richacl *acl = NULL;
+	int retval, retries = 0;
+	struct inode *inode = d_inode(dentry);
+
+	if (!IS_RICHACL(d_inode(dentry)))
+		return -EOPNOTSUPP;
+	if (S_ISLNK(inode->i_mode))
+		return -EOPNOTSUPP;
+	if (strcmp(name, "") != 0)
+		return -EINVAL;
+	if (!uid_eq(current_fsuid(), inode->i_uid) &&
+	    inode_permission(inode, MAY_CHMOD) &&
+	    !capable(CAP_FOWNER))
+		return -EPERM;
+	if (value) {
+		acl = richacl_from_xattr(&init_user_ns, value, size);
+		if (IS_ERR(acl))
+			return PTR_ERR(acl);
+
+		inode->i_mode &= ~S_IRWXUGO;
+		inode->i_mode |= richacl_masks_to_mode(acl);
+	}
+
+retry:
+	handle = ext4_journal_start(inode, EXT4_HT_XATTR,
+				    EXT4_DATA_TRANS_BLOCKS(inode->i_sb));
+	if (IS_ERR(handle))
+		return PTR_ERR(handle);
+	retval = ext4_set_richacl(handle, inode, acl);
+	ext4_journal_stop(handle);
+	if (retval == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries))
+		goto retry;
+	richacl_put(acl);
+	return retval;
+}
+
+const struct xattr_handler ext4_richacl_xattr_handler = {
+	.prefix	= XATTR_NAME_RICHACL,
+	.list	= ext4_xattr_list_richacl,
+	.get	= ext4_xattr_get_richacl,
+	.set	= ext4_xattr_set_richacl,
+};
diff --git a/fs/ext4/richacl.h b/fs/ext4/richacl.h
new file mode 100644
index 0000000..09a5cad
--- /dev/null
+++ b/fs/ext4/richacl.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright IBM Corporation, 2010
+ * Copyright (C)  2015 Red Hat, Inc.
+ * Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+#ifndef __FS_EXT4_RICHACL_H
+#define __FS_EXT4_RICHACL_H
+
+#include <linux/richacl.h>
+
+#ifdef CONFIG_EXT4_FS_RICHACL
+
+#define EXT4_IS_RICHACL(inode) IS_RICHACL(inode)
+
+extern struct richacl *ext4_get_richacl(struct inode *);
+extern int ext4_init_richacl(handle_t *, struct inode *, struct inode *);
+extern int ext4_richacl_chmod(struct inode *);
+
+#else  /* CONFIG_FS_EXT4_RICHACL */
+
+#define EXT4_IS_RICHACL(inode) (0)
+#define ext4_get_richacl   NULL
+
+static inline int
+ext4_init_richacl(handle_t *handle, struct inode *inode, struct inode *dir)
+{
+	return 0;
+}
+
+static inline int
+ext4_richacl_chmod(struct inode *inode)
+{
+	return 0;
+}
+
+#endif  /* CONFIG_FS_EXT4_RICHACL */
+#endif  /* __FS_EXT4_RICHACL_H */
diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c
index 16e28c0..c1dee9b 100644
--- a/fs/ext4/xattr.c
+++ b/fs/ext4/xattr.c
@@ -99,6 +99,9 @@ static const struct xattr_handler *ext4_xattr_handler_map[] = {
 #ifdef CONFIG_EXT4_FS_SECURITY
 	[EXT4_XATTR_INDEX_SECURITY]	     = &ext4_xattr_security_handler,
 #endif
+#ifdef CONFIG_EXT4_FS_RICHACL
+	[EXT4_XATTR_INDEX_RICHACL]           = &ext4_richacl_xattr_handler,
+#endif
 };
 
 const struct xattr_handler *ext4_xattr_handlers[] = {
@@ -111,6 +114,9 @@ const struct xattr_handler *ext4_xattr_handlers[] = {
 #ifdef CONFIG_EXT4_FS_SECURITY
 	&ext4_xattr_security_handler,
 #endif
+#ifdef CONFIG_EXT4_FS_RICHACL
+	&ext4_richacl_xattr_handler,
+#endif
 	NULL
 };
 
diff --git a/fs/ext4/xattr.h b/fs/ext4/xattr.h
index ddc0957..f315493 100644
--- a/fs/ext4/xattr.h
+++ b/fs/ext4/xattr.h
@@ -98,6 +98,7 @@ struct ext4_xattr_ibody_find {
 extern const struct xattr_handler ext4_xattr_user_handler;
 extern const struct xattr_handler ext4_xattr_trusted_handler;
 extern const struct xattr_handler ext4_xattr_security_handler;
+extern const struct xattr_handler ext4_richacl_xattr_handler;
 
 #define EXT4_XATTR_NAME_ENCRYPTION_CONTEXT "c"
 
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 19/41] ext4: Add richacl feature flag
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA
  Cc: Aneesh Kumar K.V

From: "Aneesh Kumar K.V" <aneesh.kumar-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8@public.gmane.org>

This feature flag selects richacl instead of posix acl support on the
file system. In addition, the "acl" mount option is needed for enabling
either of the two kinds of acls.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/ext4/ext4.h  |  6 ++++--
 fs/ext4/super.c | 42 +++++++++++++++++++++++++++++++++---------
 2 files changed, 37 insertions(+), 11 deletions(-)

diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index f5e9f04..e69c8ea 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -991,7 +991,7 @@ struct ext4_inode_info {
 #define EXT4_MOUNT_UPDATE_JOURNAL	0x01000	/* Update the journal format */
 #define EXT4_MOUNT_NO_UID32		0x02000  /* Disable 32-bit UIDs */
 #define EXT4_MOUNT_XATTR_USER		0x04000	/* Extended user attributes */
-#define EXT4_MOUNT_POSIX_ACL		0x08000	/* POSIX Access Control Lists */
+#define EXT4_MOUNT_ACL			0x08000	/* Access Control Lists */
 #define EXT4_MOUNT_NO_AUTO_DA_ALLOC	0x10000	/* No auto delalloc mapping */
 #define EXT4_MOUNT_BARRIER		0x20000 /* Use block barriers */
 #define EXT4_MOUNT_QUOTA		0x80000 /* Some quota option set */
@@ -1582,6 +1582,7 @@ static inline int ext4_encrypted_inode(struct inode *inode)
 #define EXT4_FEATURE_INCOMPAT_LARGEDIR		0x4000 /* >2GB or 3-lvl htree */
 #define EXT4_FEATURE_INCOMPAT_INLINE_DATA	0x8000 /* data in inode */
 #define EXT4_FEATURE_INCOMPAT_ENCRYPT		0x10000
+#define EXT4_FEATURE_INCOMPAT_RICHACL		0x20000
 
 #define EXT2_FEATURE_COMPAT_SUPP	EXT4_FEATURE_COMPAT_EXT_ATTR
 #define EXT2_FEATURE_INCOMPAT_SUPP	(EXT4_FEATURE_INCOMPAT_FILETYPE| \
@@ -1607,7 +1608,8 @@ static inline int ext4_encrypted_inode(struct inode *inode)
 					 EXT4_FEATURE_INCOMPAT_FLEX_BG| \
 					 EXT4_FEATURE_INCOMPAT_MMP | \
 					 EXT4_FEATURE_INCOMPAT_INLINE_DATA | \
-					 EXT4_FEATURE_INCOMPAT_ENCRYPT)
+					 EXT4_FEATURE_INCOMPAT_ENCRYPT | \
+					 EXT4_FEATURE_INCOMPAT_RICHACL)
 #define EXT4_FEATURE_RO_COMPAT_SUPP	(EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER| \
 					 EXT4_FEATURE_RO_COMPAT_LARGE_FILE| \
 					 EXT4_FEATURE_RO_COMPAT_GDT_CSUM| \
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index 58987b5..05d6537 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1257,6 +1257,28 @@ static ext4_fsblk_t get_sb_block(void **data)
 	return sb_block;
 }
 
+static int enable_acl(struct super_block *sb)
+{
+	sb->s_flags &= ~(MS_POSIXACL | MS_RICHACL);
+	if (test_opt(sb, ACL)) {
+		if (EXT4_HAS_INCOMPAT_FEATURE(sb,
+					      EXT4_FEATURE_INCOMPAT_RICHACL)) {
+#ifdef CONFIG_EXT4_FS_RICHACL
+			sb->s_flags |= MS_RICHACL;
+#else
+			return -EOPNOTSUPP;
+#endif
+		} else {
+#ifdef CONFIG_EXT4_FS_POSIX_ACL
+			sb->s_flags |= MS_POSIXACL;
+#else
+			return -EOPNOTSUPP;
+#endif
+		}
+	}
+	return 0;
+}
+
 #define DEFAULT_JOURNAL_IOPRIO (IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 3))
 static char deprecated_msg[] = "Mount option \"%s\" will be removed by %s\n"
 	"Contact linux-ext4-u79uwXL29TY76Z2rM5mHXA@public.gmane.org if you think we should keep it.\n";
@@ -1403,9 +1425,9 @@ static const struct mount_opts {
 	 MOPT_NO_EXT2 | MOPT_DATAJ},
 	{Opt_user_xattr, EXT4_MOUNT_XATTR_USER, MOPT_SET},
 	{Opt_nouser_xattr, EXT4_MOUNT_XATTR_USER, MOPT_CLEAR},
-#ifdef CONFIG_EXT4_FS_POSIX_ACL
-	{Opt_acl, EXT4_MOUNT_POSIX_ACL, MOPT_SET},
-	{Opt_noacl, EXT4_MOUNT_POSIX_ACL, MOPT_CLEAR},
+#if defined(CONFIG_EXT4_FS_POSIX_ACL) || defined(CONFIG_EXT4_FS_RICHACL)
+	{Opt_acl, EXT4_MOUNT_ACL, MOPT_SET},
+	{Opt_noacl, EXT4_MOUNT_ACL, MOPT_CLEAR},
 #else
 	{Opt_acl, 0, MOPT_NOSUPPORT},
 	{Opt_noacl, 0, MOPT_NOSUPPORT},
@@ -3563,8 +3585,8 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
 		set_opt(sb, NO_UID32);
 	/* xattr user namespace & acls are now defaulted on */
 	set_opt(sb, XATTR_USER);
-#ifdef CONFIG_EXT4_FS_POSIX_ACL
-	set_opt(sb, POSIX_ACL);
+#if defined(CONFIG_EXT4_FS_POSIX_ACL) || defined(CONFIG_EXT4_FS_RICHACL)
+	set_opt(sb, ACL);
 #endif
 	/* don't forget to enable journal_csum when metadata_csum is enabled. */
 	if (ext4_has_metadata_csum(sb))
@@ -3645,8 +3667,9 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
 			clear_opt(sb, DELALLOC);
 	}
 
-	sb->s_flags = (sb->s_flags & ~MS_POSIXACL) |
-		(test_opt(sb, POSIX_ACL) ? MS_POSIXACL : 0);
+	err = enable_acl(sb);
+	if (err)
+		goto failed_mount;
 
 	if (le32_to_cpu(es->s_rev_level) == EXT4_GOOD_OLD_REV &&
 	    (EXT4_HAS_COMPAT_FEATURE(sb, ~0U) ||
@@ -4960,8 +4983,9 @@ static int ext4_remount(struct super_block *sb, int *flags, char *data)
 	if (sbi->s_mount_flags & EXT4_MF_FS_ABORTED)
 		ext4_abort(sb, "Abort forced by user");
 
-	sb->s_flags = (sb->s_flags & ~MS_POSIXACL) |
-		(test_opt(sb, POSIX_ACL) ? MS_POSIXACL : 0);
+	err = enable_acl(sb);
+	if (err)
+		goto restore_opts;
 
 	es = sbi->s_es;
 
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 19/41] ext4: Add richacl feature flag
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module
  Cc: Aneesh Kumar K.V

From: "Aneesh Kumar K.V" <aneesh.kumar@linux.vnet.ibm.com>

This feature flag selects richacl instead of posix acl support on the
file system. In addition, the "acl" mount option is needed for enabling
either of the two kinds of acls.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/ext4/ext4.h  |  6 ++++--
 fs/ext4/super.c | 42 +++++++++++++++++++++++++++++++++---------
 2 files changed, 37 insertions(+), 11 deletions(-)

diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index f5e9f04..e69c8ea 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -991,7 +991,7 @@ struct ext4_inode_info {
 #define EXT4_MOUNT_UPDATE_JOURNAL	0x01000	/* Update the journal format */
 #define EXT4_MOUNT_NO_UID32		0x02000  /* Disable 32-bit UIDs */
 #define EXT4_MOUNT_XATTR_USER		0x04000	/* Extended user attributes */
-#define EXT4_MOUNT_POSIX_ACL		0x08000	/* POSIX Access Control Lists */
+#define EXT4_MOUNT_ACL			0x08000	/* Access Control Lists */
 #define EXT4_MOUNT_NO_AUTO_DA_ALLOC	0x10000	/* No auto delalloc mapping */
 #define EXT4_MOUNT_BARRIER		0x20000 /* Use block barriers */
 #define EXT4_MOUNT_QUOTA		0x80000 /* Some quota option set */
@@ -1582,6 +1582,7 @@ static inline int ext4_encrypted_inode(struct inode *inode)
 #define EXT4_FEATURE_INCOMPAT_LARGEDIR		0x4000 /* >2GB or 3-lvl htree */
 #define EXT4_FEATURE_INCOMPAT_INLINE_DATA	0x8000 /* data in inode */
 #define EXT4_FEATURE_INCOMPAT_ENCRYPT		0x10000
+#define EXT4_FEATURE_INCOMPAT_RICHACL		0x20000
 
 #define EXT2_FEATURE_COMPAT_SUPP	EXT4_FEATURE_COMPAT_EXT_ATTR
 #define EXT2_FEATURE_INCOMPAT_SUPP	(EXT4_FEATURE_INCOMPAT_FILETYPE| \
@@ -1607,7 +1608,8 @@ static inline int ext4_encrypted_inode(struct inode *inode)
 					 EXT4_FEATURE_INCOMPAT_FLEX_BG| \
 					 EXT4_FEATURE_INCOMPAT_MMP | \
 					 EXT4_FEATURE_INCOMPAT_INLINE_DATA | \
-					 EXT4_FEATURE_INCOMPAT_ENCRYPT)
+					 EXT4_FEATURE_INCOMPAT_ENCRYPT | \
+					 EXT4_FEATURE_INCOMPAT_RICHACL)
 #define EXT4_FEATURE_RO_COMPAT_SUPP	(EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER| \
 					 EXT4_FEATURE_RO_COMPAT_LARGE_FILE| \
 					 EXT4_FEATURE_RO_COMPAT_GDT_CSUM| \
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index 58987b5..05d6537 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1257,6 +1257,28 @@ static ext4_fsblk_t get_sb_block(void **data)
 	return sb_block;
 }
 
+static int enable_acl(struct super_block *sb)
+{
+	sb->s_flags &= ~(MS_POSIXACL | MS_RICHACL);
+	if (test_opt(sb, ACL)) {
+		if (EXT4_HAS_INCOMPAT_FEATURE(sb,
+					      EXT4_FEATURE_INCOMPAT_RICHACL)) {
+#ifdef CONFIG_EXT4_FS_RICHACL
+			sb->s_flags |= MS_RICHACL;
+#else
+			return -EOPNOTSUPP;
+#endif
+		} else {
+#ifdef CONFIG_EXT4_FS_POSIX_ACL
+			sb->s_flags |= MS_POSIXACL;
+#else
+			return -EOPNOTSUPP;
+#endif
+		}
+	}
+	return 0;
+}
+
 #define DEFAULT_JOURNAL_IOPRIO (IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 3))
 static char deprecated_msg[] = "Mount option \"%s\" will be removed by %s\n"
 	"Contact linux-ext4@vger.kernel.org if you think we should keep it.\n";
@@ -1403,9 +1425,9 @@ static const struct mount_opts {
 	 MOPT_NO_EXT2 | MOPT_DATAJ},
 	{Opt_user_xattr, EXT4_MOUNT_XATTR_USER, MOPT_SET},
 	{Opt_nouser_xattr, EXT4_MOUNT_XATTR_USER, MOPT_CLEAR},
-#ifdef CONFIG_EXT4_FS_POSIX_ACL
-	{Opt_acl, EXT4_MOUNT_POSIX_ACL, MOPT_SET},
-	{Opt_noacl, EXT4_MOUNT_POSIX_ACL, MOPT_CLEAR},
+#if defined(CONFIG_EXT4_FS_POSIX_ACL) || defined(CONFIG_EXT4_FS_RICHACL)
+	{Opt_acl, EXT4_MOUNT_ACL, MOPT_SET},
+	{Opt_noacl, EXT4_MOUNT_ACL, MOPT_CLEAR},
 #else
 	{Opt_acl, 0, MOPT_NOSUPPORT},
 	{Opt_noacl, 0, MOPT_NOSUPPORT},
@@ -3563,8 +3585,8 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
 		set_opt(sb, NO_UID32);
 	/* xattr user namespace & acls are now defaulted on */
 	set_opt(sb, XATTR_USER);
-#ifdef CONFIG_EXT4_FS_POSIX_ACL
-	set_opt(sb, POSIX_ACL);
+#if defined(CONFIG_EXT4_FS_POSIX_ACL) || defined(CONFIG_EXT4_FS_RICHACL)
+	set_opt(sb, ACL);
 #endif
 	/* don't forget to enable journal_csum when metadata_csum is enabled. */
 	if (ext4_has_metadata_csum(sb))
@@ -3645,8 +3667,9 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
 			clear_opt(sb, DELALLOC);
 	}
 
-	sb->s_flags = (sb->s_flags & ~MS_POSIXACL) |
-		(test_opt(sb, POSIX_ACL) ? MS_POSIXACL : 0);
+	err = enable_acl(sb);
+	if (err)
+		goto failed_mount;
 
 	if (le32_to_cpu(es->s_rev_level) == EXT4_GOOD_OLD_REV &&
 	    (EXT4_HAS_COMPAT_FEATURE(sb, ~0U) ||
@@ -4960,8 +4983,9 @@ static int ext4_remount(struct super_block *sb, int *flags, char *data)
 	if (sbi->s_mount_flags & EXT4_MF_FS_ABORTED)
 		ext4_abort(sb, "Abort forced by user");
 
-	sb->s_flags = (sb->s_flags & ~MS_POSIXACL) |
-		(test_opt(sb, POSIX_ACL) ? MS_POSIXACL : 0);
+	err = enable_acl(sb);
+	if (err)
+		goto restore_opts;
 
 	es = sbi->s_es;
 
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 20/41] richacl: acl editing helper functions
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA
  Cc: Andreas Gruenbacher

The file masks in richacls make chmod and creating new files more
efficient than having to apply file permission bits to the acl directly.
They also allow us to regain permissions from an acl even after a
restrictive chmod, because the permissions in the acl itself are not
being destroyed.  In POSIX ACLs, the mask entry has a similar function.

Protocols like nfsv4 do not understand file masks.  For those protocols,
we need to compute nfs4 acls which represent the effective permissions
granted by a richacl: we need to "apply" the file masks to the acl.

This is the first in a series of richacl transformation patches; it
implements basic richacl editing functions.  The following patches
implement algorithms for transforming a richacl so that it can be
evaluated as a plain nfs4 acl, with identical permission check results.

Signed-off-by: Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
---
 fs/Makefile                    |   3 +-
 fs/richacl_compat.c            | 155 +++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl_compat.h |  40 +++++++++++
 3 files changed, 197 insertions(+), 1 deletion(-)
 create mode 100644 fs/richacl_compat.c
 create mode 100644 include/linux/richacl_compat.h

diff --git a/fs/Makefile b/fs/Makefile
index baf385a..2d08c70 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -48,7 +48,8 @@ obj-$(CONFIG_SYSCTL)		+= drop_caches.o
 
 obj-$(CONFIG_FHANDLE)		+= fhandle.o
 obj-$(CONFIG_FS_RICHACL)	+= richacl.o
-richacl-y			:= richacl_base.o richacl_inode.o richacl_xattr.o
+richacl-y			:= richacl_base.o richacl_inode.o \
+				   richacl_xattr.o richacl_compat.o
 
 obj-y				+= quota/
 
diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
new file mode 100644
index 0000000..341e429
--- /dev/null
+++ b/fs/richacl_compat.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2006, 2010  Novell, Inc.
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Written by Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
+ *
+ * This program 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, or (at your option) any
+ * later version.
+ *
+ * This program 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.
+ */
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/richacl_compat.h>
+
+/**
+ * richacl_prepare  -  allocate richacl being constructed
+ *
+ * Allocate a richacl which can hold @count entries but which is initially
+ * empty.
+ */
+struct richacl *richacl_prepare(struct richacl_alloc *alloc, unsigned int count)
+{
+	alloc->acl = richacl_alloc(count, GFP_KERNEL);
+	if (!alloc->acl)
+		return NULL;
+	alloc->acl->a_count = 0;
+	alloc->count = count;
+	return alloc->acl;
+}
+EXPORT_SYMBOL_GPL(richacl_prepare);
+
+/**
+ * richacl_delete_entry  -  delete an entry in an acl
+ * @alloc:	acl and number of allocated entries
+ * @ace:	an entry in @alloc->acl
+ *
+ * Updates @ace so that it points to the entry before the deleted entry
+ * on return. (When deleting the first entry, @ace will point to the
+ * (non-existent) entry before the first entry). This behavior is the
+ * expected behavior when deleting entries while forward iterating over
+ * an acl.
+ */
+void
+richacl_delete_entry(struct richacl_alloc *alloc, struct richace **ace)
+{
+	void *end = alloc->acl->a_entries + alloc->acl->a_count;
+
+	memmove(*ace, *ace + 1, end - (void *)(*ace + 1));
+	(*ace)--;
+	alloc->acl->a_count--;
+}
+EXPORT_SYMBOL_GPL(richacl_delete_entry);
+
+/**
+ * richacl_insert_entry  -  insert an entry in an acl
+ * @alloc:	acl and number of allocated entries
+ * @ace:	entry before which the new entry shall be inserted
+ *
+ * Insert a new entry in @alloc->acl at position @ace and zero-initialize
+ * it.  This may require reallocating @alloc->acl.
+ */
+int
+richacl_insert_entry(struct richacl_alloc *alloc, struct richace **ace)
+{
+	struct richacl *acl = alloc->acl;
+	unsigned int index = *ace - acl->a_entries;
+	size_t tail_size = (acl->a_count - index) * sizeof(struct richace);
+
+	if (alloc->count == acl->a_count) {
+		size_t new_size = sizeof(struct richacl) +
+			(acl->a_count + 1) * sizeof(struct richace);
+
+		acl = krealloc(acl, new_size, GFP_KERNEL);
+		if (!acl)
+			return -1;
+		*ace = acl->a_entries + index;
+		alloc->acl = acl;
+		alloc->count++;
+	}
+
+	memmove(*ace + 1, *ace, tail_size);
+	memset(*ace, 0, sizeof(**ace));
+	acl->a_count++;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(richacl_insert_entry);
+
+/**
+ * richacl_append_entry  -  append an entry to an acl
+ * @alloc:		acl and number of allocated entries
+ *
+ * This may require reallocating @alloc->acl.
+ */
+struct richace *richacl_append_entry(struct richacl_alloc *alloc)
+{
+	struct richacl *acl = alloc->acl;
+	struct richace *ace = acl->a_entries + acl->a_count;
+
+	if (alloc->count > alloc->acl->a_count) {
+		acl->a_count++;
+		return ace;
+	}
+	return richacl_insert_entry(alloc, &ace) ? NULL : ace;
+}
+EXPORT_SYMBOL_GPL(richacl_append_entry);
+
+/**
+ * richace_change_mask  -  set the mask of @ace to @mask
+ * @alloc:	acl and number of allocated entries
+ * @ace:	entry to modify
+ * @mask:	new mask for @ace
+ *
+ * If @ace is inheritable, a inherit-only ace is inserted before @ace which
+ * includes the inheritable permissions of @ace and the inheritance flags of
+ * @ace are cleared before changing the mask.
+ *
+ * If @mask is 0, the original ace is turned into an inherit-only entry if
+ * there are any inheritable permissions, and removed otherwise.
+ *
+ * The returned @ace points to the modified or inserted effective-only acl
+ * entry if that entry exists, to the entry that has become inheritable-only,
+ * or else to the previous entry in the acl.
+ */
+static int
+richace_change_mask(struct richacl_alloc *alloc, struct richace **ace,
+			   unsigned int mask)
+{
+	if (mask && (*ace)->e_mask == mask)
+		(*ace)->e_flags &= ~RICHACE_INHERIT_ONLY_ACE;
+	else if (mask & ~RICHACE_POSIX_ALWAYS_ALLOWED) {
+		if (richace_is_inheritable(*ace)) {
+			if (richacl_insert_entry(alloc, ace))
+				return -1;
+			richace_copy(*ace, *ace + 1);
+			(*ace)->e_flags |= RICHACE_INHERIT_ONLY_ACE;
+			(*ace)++;
+			(*ace)->e_flags &= ~RICHACE_INHERITANCE_FLAGS |
+					   RICHACE_INHERITED_ACE;
+		}
+		(*ace)->e_mask = mask;
+	} else {
+		if (richace_is_inheritable(*ace))
+			(*ace)->e_flags |= RICHACE_INHERIT_ONLY_ACE;
+		else
+			richacl_delete_entry(alloc, ace);
+	}
+	return 0;
+}
diff --git a/include/linux/richacl_compat.h b/include/linux/richacl_compat.h
new file mode 100644
index 0000000..a9ff630
--- /dev/null
+++ b/include/linux/richacl_compat.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Written by Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+ *
+ * This program 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, or (at your option) any
+ * later version.
+ *
+ * This program 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.
+ */
+
+#ifndef __RICHACL_COMPAT_H
+#define __RICHACL_COMPAT_H
+
+#include <linux/richacl.h>
+
+/**
+ * struct richacl_alloc  -  remember how many entries are actually allocated
+ * @acl:	acl with a_count <= @count
+ * @count:	the actual number of entries allocated in @acl
+ *
+ * We pass around this structure while modifying an acl so that we do
+ * not have to reallocate when we remove existing entries followed by
+ * adding new entries.
+ */
+struct richacl_alloc {
+	struct richacl *acl;
+	unsigned int count;
+};
+
+struct richacl *richacl_prepare(struct richacl_alloc *, unsigned int);
+struct richace *richacl_append_entry(struct richacl_alloc *);
+int richacl_insert_entry(struct richacl_alloc *, struct richace **);
+void richacl_delete_entry(struct richacl_alloc *, struct richace **);
+
+#endif  /* __RICHACL_COMPAT_H */
-- 
2.4.3

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 20/41] richacl: acl editing helper functions
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module
  Cc: Andreas Gruenbacher

The file masks in richacls make chmod and creating new files more
efficient than having to apply file permission bits to the acl directly.
They also allow us to regain permissions from an acl even after a
restrictive chmod, because the permissions in the acl itself are not
being destroyed.  In POSIX ACLs, the mask entry has a similar function.

Protocols like nfsv4 do not understand file masks.  For those protocols,
we need to compute nfs4 acls which represent the effective permissions
granted by a richacl: we need to "apply" the file masks to the acl.

This is the first in a series of richacl transformation patches; it
implements basic richacl editing functions.  The following patches
implement algorithms for transforming a richacl so that it can be
evaluated as a plain nfs4 acl, with identical permission check results.

Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
---
 fs/Makefile                    |   3 +-
 fs/richacl_compat.c            | 155 +++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl_compat.h |  40 +++++++++++
 3 files changed, 197 insertions(+), 1 deletion(-)
 create mode 100644 fs/richacl_compat.c
 create mode 100644 include/linux/richacl_compat.h

diff --git a/fs/Makefile b/fs/Makefile
index baf385a..2d08c70 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -48,7 +48,8 @@ obj-$(CONFIG_SYSCTL)		+= drop_caches.o
 
 obj-$(CONFIG_FHANDLE)		+= fhandle.o
 obj-$(CONFIG_FS_RICHACL)	+= richacl.o
-richacl-y			:= richacl_base.o richacl_inode.o richacl_xattr.o
+richacl-y			:= richacl_base.o richacl_inode.o \
+				   richacl_xattr.o richacl_compat.o
 
 obj-y				+= quota/
 
diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
new file mode 100644
index 0000000..341e429
--- /dev/null
+++ b/fs/richacl_compat.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2006, 2010  Novell, Inc.
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Written by Andreas Gruenbacher <agruen@kernel.org>
+ *
+ * This program 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, or (at your option) any
+ * later version.
+ *
+ * This program 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.
+ */
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/richacl_compat.h>
+
+/**
+ * richacl_prepare  -  allocate richacl being constructed
+ *
+ * Allocate a richacl which can hold @count entries but which is initially
+ * empty.
+ */
+struct richacl *richacl_prepare(struct richacl_alloc *alloc, unsigned int count)
+{
+	alloc->acl = richacl_alloc(count, GFP_KERNEL);
+	if (!alloc->acl)
+		return NULL;
+	alloc->acl->a_count = 0;
+	alloc->count = count;
+	return alloc->acl;
+}
+EXPORT_SYMBOL_GPL(richacl_prepare);
+
+/**
+ * richacl_delete_entry  -  delete an entry in an acl
+ * @alloc:	acl and number of allocated entries
+ * @ace:	an entry in @alloc->acl
+ *
+ * Updates @ace so that it points to the entry before the deleted entry
+ * on return. (When deleting the first entry, @ace will point to the
+ * (non-existent) entry before the first entry). This behavior is the
+ * expected behavior when deleting entries while forward iterating over
+ * an acl.
+ */
+void
+richacl_delete_entry(struct richacl_alloc *alloc, struct richace **ace)
+{
+	void *end = alloc->acl->a_entries + alloc->acl->a_count;
+
+	memmove(*ace, *ace + 1, end - (void *)(*ace + 1));
+	(*ace)--;
+	alloc->acl->a_count--;
+}
+EXPORT_SYMBOL_GPL(richacl_delete_entry);
+
+/**
+ * richacl_insert_entry  -  insert an entry in an acl
+ * @alloc:	acl and number of allocated entries
+ * @ace:	entry before which the new entry shall be inserted
+ *
+ * Insert a new entry in @alloc->acl at position @ace and zero-initialize
+ * it.  This may require reallocating @alloc->acl.
+ */
+int
+richacl_insert_entry(struct richacl_alloc *alloc, struct richace **ace)
+{
+	struct richacl *acl = alloc->acl;
+	unsigned int index = *ace - acl->a_entries;
+	size_t tail_size = (acl->a_count - index) * sizeof(struct richace);
+
+	if (alloc->count == acl->a_count) {
+		size_t new_size = sizeof(struct richacl) +
+			(acl->a_count + 1) * sizeof(struct richace);
+
+		acl = krealloc(acl, new_size, GFP_KERNEL);
+		if (!acl)
+			return -1;
+		*ace = acl->a_entries + index;
+		alloc->acl = acl;
+		alloc->count++;
+	}
+
+	memmove(*ace + 1, *ace, tail_size);
+	memset(*ace, 0, sizeof(**ace));
+	acl->a_count++;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(richacl_insert_entry);
+
+/**
+ * richacl_append_entry  -  append an entry to an acl
+ * @alloc:		acl and number of allocated entries
+ *
+ * This may require reallocating @alloc->acl.
+ */
+struct richace *richacl_append_entry(struct richacl_alloc *alloc)
+{
+	struct richacl *acl = alloc->acl;
+	struct richace *ace = acl->a_entries + acl->a_count;
+
+	if (alloc->count > alloc->acl->a_count) {
+		acl->a_count++;
+		return ace;
+	}
+	return richacl_insert_entry(alloc, &ace) ? NULL : ace;
+}
+EXPORT_SYMBOL_GPL(richacl_append_entry);
+
+/**
+ * richace_change_mask  -  set the mask of @ace to @mask
+ * @alloc:	acl and number of allocated entries
+ * @ace:	entry to modify
+ * @mask:	new mask for @ace
+ *
+ * If @ace is inheritable, a inherit-only ace is inserted before @ace which
+ * includes the inheritable permissions of @ace and the inheritance flags of
+ * @ace are cleared before changing the mask.
+ *
+ * If @mask is 0, the original ace is turned into an inherit-only entry if
+ * there are any inheritable permissions, and removed otherwise.
+ *
+ * The returned @ace points to the modified or inserted effective-only acl
+ * entry if that entry exists, to the entry that has become inheritable-only,
+ * or else to the previous entry in the acl.
+ */
+static int
+richace_change_mask(struct richacl_alloc *alloc, struct richace **ace,
+			   unsigned int mask)
+{
+	if (mask && (*ace)->e_mask == mask)
+		(*ace)->e_flags &= ~RICHACE_INHERIT_ONLY_ACE;
+	else if (mask & ~RICHACE_POSIX_ALWAYS_ALLOWED) {
+		if (richace_is_inheritable(*ace)) {
+			if (richacl_insert_entry(alloc, ace))
+				return -1;
+			richace_copy(*ace, *ace + 1);
+			(*ace)->e_flags |= RICHACE_INHERIT_ONLY_ACE;
+			(*ace)++;
+			(*ace)->e_flags &= ~RICHACE_INHERITANCE_FLAGS |
+					   RICHACE_INHERITED_ACE;
+		}
+		(*ace)->e_mask = mask;
+	} else {
+		if (richace_is_inheritable(*ace))
+			(*ace)->e_flags |= RICHACE_INHERIT_ONLY_ACE;
+		else
+			richacl_delete_entry(alloc, ace);
+	}
+	return 0;
+}
diff --git a/include/linux/richacl_compat.h b/include/linux/richacl_compat.h
new file mode 100644
index 0000000..a9ff630
--- /dev/null
+++ b/include/linux/richacl_compat.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Written by Andreas Gruenbacher <agruenba@redhat.com>
+ *
+ * This program 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, or (at your option) any
+ * later version.
+ *
+ * This program 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.
+ */
+
+#ifndef __RICHACL_COMPAT_H
+#define __RICHACL_COMPAT_H
+
+#include <linux/richacl.h>
+
+/**
+ * struct richacl_alloc  -  remember how many entries are actually allocated
+ * @acl:	acl with a_count <= @count
+ * @count:	the actual number of entries allocated in @acl
+ *
+ * We pass around this structure while modifying an acl so that we do
+ * not have to reallocate when we remove existing entries followed by
+ * adding new entries.
+ */
+struct richacl_alloc {
+	struct richacl *acl;
+	unsigned int count;
+};
+
+struct richacl *richacl_prepare(struct richacl_alloc *, unsigned int);
+struct richace *richacl_append_entry(struct richacl_alloc *);
+int richacl_insert_entry(struct richacl_alloc *, struct richace **);
+void richacl_delete_entry(struct richacl_alloc *, struct richace **);
+
+#endif  /* __RICHACL_COMPAT_H */
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 21/41] richacl: Move everyone@ aces down the acl
  2015-09-05 10:26 ` Andreas Gruenbacher
                   ` (9 preceding siblings ...)
  (?)
@ 2015-09-05 10:27 ` Andreas Gruenbacher
  2015-09-18 19:35   ` J. Bruce Fields
  -1 siblings, 1 reply; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module
  Cc: Andreas Gruenbacher

The POSIX standard puts processes which are not the owner or a member in
the owning group or which match any ace other then everyone@ on the
other file class.  We only know if a process is in the other class after
processing the entire acl.

Move all everyone@ aces in the acl down in the acl so that at most a
single everyone@ allow ace remains at the end.  Permissions which are
not explicitly allowed are implicitly denied, so an everyone@ deny ace
is unneeded.

The everyone@ aces can be moved down the acl without changing the
permissions that the acl grants.  This transformation simplifies the
following algorithms, and eventually allows us to turn the final
everyone@ allow ace into an entry for the other class.

Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
---
 fs/richacl_compat.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 65 insertions(+)

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 341e429..4f0acf5 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -153,3 +153,68 @@ richace_change_mask(struct richacl_alloc *alloc, struct richace **ace,
 	}
 	return 0;
 }
+
+/**
+ * richacl_move_everyone_aces_down  -  move everyone@ aces to the end of the acl
+ * @alloc:	acl and number of allocated entries
+ *
+ * Move all everyone aces to the end of the acl so that only a single everyone@
+ * allow ace remains at the end, and update the mask fields of all aces on the
+ * way.  The last ace of the resulting acl will be an everyone@ allow ace only
+ * if @acl grants any permissions to @everyone.  No @everyone deny aces will
+ * remain.
+ *
+ * This transformation does not alter the permissions that the acl grants.
+ * Having at most one everyone@ allow ace at the end of the acl helps us in the
+ * following algorithms.
+ */
+static int
+richacl_move_everyone_aces_down(struct richacl_alloc *alloc)
+{
+	struct richace *ace;
+	unsigned int allowed = 0, denied = 0;
+
+	richacl_for_each_entry(ace, alloc->acl) {
+		if (richace_is_inherit_only(ace))
+			continue;
+		if (richace_is_everyone(ace)) {
+			if (richace_is_allow(ace))
+				allowed |= (ace->e_mask & ~denied);
+			else if (richace_is_deny(ace))
+				denied |= (ace->e_mask & ~allowed);
+			else
+				continue;
+			if (richace_change_mask(alloc, &ace, 0))
+				return -1;
+		} else {
+			if (richace_is_allow(ace)) {
+				if (richace_change_mask(alloc, &ace, allowed |
+						(ace->e_mask & ~denied)))
+					return -1;
+			} else if (richace_is_deny(ace)) {
+				if (richace_change_mask(alloc, &ace, denied |
+						(ace->e_mask & ~allowed)))
+					return -1;
+			}
+		}
+	}
+	if (allowed & ~RICHACE_POSIX_ALWAYS_ALLOWED) {
+		struct richace *last_ace = ace - 1;
+
+		if (alloc->acl->a_entries &&
+		    richace_is_everyone(last_ace) &&
+		    richace_is_allow(last_ace) &&
+		    richace_is_inherit_only(last_ace) &&
+		    last_ace->e_mask == allowed)
+			last_ace->e_flags &= ~RICHACE_INHERIT_ONLY_ACE;
+		else {
+			if (richacl_insert_entry(alloc, &ace))
+				return -1;
+			ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+			ace->e_flags = RICHACE_SPECIAL_WHO;
+			ace->e_mask = allowed;
+			ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
+		}
+	}
+	return 0;
+}
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 22/41] richacl: Propagate everyone@ permissions to other aces
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA
  Cc: Andreas Gruenbacher

The trailing everyone@ allow ace can grant permissions to all file
classes including the owner and group class.  Before we can apply the
other mask to this entry to turn it into an "other class" entry, we need
to ensure that members of the owner or group class will not lose any
permissions from that ace.

Conceptually, we do this by inserting additional <who>:<allow>::allow
entries before the trailing everyone@ allow ace with the same
permissions as the trailing everyone@ allow ace for owner@, group@, and
all explicitly mentioned users and groups.  (In practice, we will rarely
need to insert any additional aces in this step.)

Signed-off-by: Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
---
 fs/richacl_compat.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 195 insertions(+)

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 4f0acf5..9b76fc0 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -218,3 +218,198 @@ richacl_move_everyone_aces_down(struct richacl_alloc *alloc)
 	}
 	return 0;
 }
+
+/**
+ * __richacl_propagate_everyone  -  propagate everyone@ permissions up for @who
+ * @alloc:	acl and number of allocated entries
+ * @who:	identifier to propagate permissions for
+ * @allow:	permissions to propagate up
+ *
+ * Propagate the permissions in @allow up from the end of the acl to the start
+ * for the specified principal @who.
+ *
+ * The simplest possible approach to achieve this would be to insert a
+ * "<who>:<allow>::allow" ace before the final everyone@ allow ace.  Since this
+ * would often result in aces which are not needed or which could be merged
+ * with an existing ace, we make the following optimizations:
+ *
+ *   - We go through the acl and determine which permissions are already
+ *     allowed or denied to @who, and we remove those permissions from
+ *     @allow.
+ *
+ *   - If the acl contains an allow ace for @who and no aces after this entry
+ *     deny permissions in @allow, we add the permissions in @allow to this
+ *     ace.  (Propagating permissions across a deny ace which can match the
+ *     process can elevate permissions.)
+ *
+ * This transformation does not alter the permissions that the acl grants.
+ */
+static int
+__richacl_propagate_everyone(struct richacl_alloc *alloc, struct richace *who,
+			     unsigned int allow)
+{
+	struct richace *allow_last = NULL, *ace;
+	struct richacl *acl = alloc->acl;
+
+	/*
+	 * Remove the permissions from allow that are already determined for
+	 * this who value, and figure out if there is an allow entry for
+	 * this who value that is "reachable" from the trailing everyone@
+	 * allow ace.
+	 */
+	richacl_for_each_entry(ace, acl) {
+		if (richace_is_inherit_only(ace))
+			continue;
+		if (richace_is_allow(ace)) {
+			if (richace_is_same_identifier(ace, who)) {
+				allow &= ~ace->e_mask;
+				allow_last = ace;
+			}
+		} else if (richace_is_deny(ace)) {
+			if (richace_is_same_identifier(ace, who))
+				allow &= ~ace->e_mask;
+			else if (allow & ace->e_mask)
+				allow_last = NULL;
+		}
+	}
+	ace--;
+
+	/*
+	 * If for group class entries, all the remaining permissions will
+	 * remain granted by the trailing everyone@ ace, no additional entry is
+	 * needed.
+	 */
+	if (!richace_is_owner(who) &&
+	    richace_is_everyone(ace) && richace_is_allow(ace) &&
+	    !(allow & ~(ace->e_mask & acl->a_other_mask)))
+		allow = 0;
+
+	if (allow) {
+		if (allow_last)
+			return richace_change_mask(alloc, &allow_last,
+						   allow_last->e_mask | allow);
+		else {
+			struct richace who_copy;
+
+			richace_copy(&who_copy, who);
+			ace = acl->a_entries + acl->a_count - 1;
+			if (richacl_insert_entry(alloc, &ace))
+				return -1;
+			richace_copy(ace, &who_copy);
+			ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+			ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
+			ace->e_mask = allow;
+		}
+	}
+	return 0;
+}
+
+/**
+ * richacl_propagate_everyone  -  propagate everyone@ permissions up the acl
+ * @alloc:	acl and number of allocated entries
+ *
+ * Make sure that group@ and all other users and groups mentioned in the acl
+ * will not lose any permissions when finally applying the other mask to the
+ * everyone@ allow ace at the end of the acl.  We modify the permissions of
+ * existing entries or add new entries before the final everyone@ allow ace to
+ * achieve that.
+ *
+ * For example, the following acl implicitly grants everyone rwpx access:
+ *
+ *    joe:r::allow
+ *    everyone@:rwpx::allow
+ *
+ * When applying mode 0660 to this acl, group@ would lose rwp access, and joe
+ * would lose wp access even though the mode does not exclude those
+ * permissions.  After propagating the everyone@ permissions, the result for
+ * applying mode 0660 becomes:
+ *
+ *    owner@:rwp::allow
+ *    joe:rwp::allow
+ *    group@:rwp::allow
+ *
+ * Deny aces complicate the matter.  For example, the following acl grants
+ * everyone but joe write access:
+ *
+ *    joe:wp::deny
+ *    everyone@:rwpx::allow
+ *
+ * When applying mode 0660 to this acl, group@ would lose rwp access, and joe
+ * would lose r access.  After propagating the everyone@ permissions, the
+ * result for applying mode 0660 becomes:
+ *
+ *    owner@:rwp::allow
+ *    joe:w::deny
+ *    group@:rwp::allow
+ *    joe:r::allow
+ */
+static int
+richacl_propagate_everyone(struct richacl_alloc *alloc)
+{
+	struct richace who = { .e_flags = RICHACE_SPECIAL_WHO };
+	struct richacl *acl = alloc->acl;
+	struct richace *ace;
+	unsigned int owner_allow, group_allow;
+
+	/*
+	 * If the owner mask contains permissions which are not in the group
+	 * mask, the group mask contains permissions which are not in the other
+	 * mask, or the owner class contains permissions which are not in the
+	 * other mask, we may need to propagate permissions up from the
+	 * everyone@ allow ace.  The third condition is implied by the first
+	 * two.
+	 */
+	if (!((acl->a_owner_mask & ~acl->a_group_mask) ||
+	      (acl->a_group_mask & ~acl->a_other_mask)))
+		return 0;
+	if (!acl->a_count)
+		return 0;
+	ace = acl->a_entries + acl->a_count - 1;
+	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
+		return 0;
+
+	owner_allow = ace->e_mask & acl->a_owner_mask;
+	group_allow = ace->e_mask & acl->a_group_mask;
+
+	if (owner_allow & ~(acl->a_group_mask & acl->a_other_mask)) {
+		/* Propagate everyone@ permissions through to owner@. */
+		who.e_id.special = RICHACE_OWNER_SPECIAL_ID;
+		if (__richacl_propagate_everyone(alloc, &who, owner_allow))
+			return -1;
+		acl = alloc->acl;
+	}
+
+	if (group_allow & ~acl->a_other_mask) {
+		int n;
+
+		/* Propagate everyone@ permissions through to group@. */
+		who.e_id.special = RICHACE_GROUP_SPECIAL_ID;
+		if (__richacl_propagate_everyone(alloc, &who, group_allow))
+			return -1;
+		acl = alloc->acl;
+
+		/*
+		 * Start from the entry before the trailing everyone@ allow
+		 * entry. We will not hit everyone@ entries in the loop.
+		 */
+		for (n = acl->a_count - 2; n != -1; n--) {
+			ace = acl->a_entries + n;
+
+			if (richace_is_inherit_only(ace) ||
+			    richace_is_owner(ace) ||
+			    richace_is_group(ace))
+				continue;
+			if (richace_is_allow(ace) || richace_is_deny(ace)) {
+				/*
+				 * Any inserted entry will end up below the
+				 * current entry
+				 */
+				if (__richacl_propagate_everyone(alloc, ace,
+								 group_allow))
+					return -1;
+				acl = alloc->acl;
+			}
+		}
+	}
+	return 0;
+}
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 22/41] richacl: Propagate everyone@ permissions to other aces
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module
  Cc: Andreas Gruenbacher

The trailing everyone@ allow ace can grant permissions to all file
classes including the owner and group class.  Before we can apply the
other mask to this entry to turn it into an "other class" entry, we need
to ensure that members of the owner or group class will not lose any
permissions from that ace.

Conceptually, we do this by inserting additional <who>:<allow>::allow
entries before the trailing everyone@ allow ace with the same
permissions as the trailing everyone@ allow ace for owner@, group@, and
all explicitly mentioned users and groups.  (In practice, we will rarely
need to insert any additional aces in this step.)

Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
---
 fs/richacl_compat.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 195 insertions(+)

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 4f0acf5..9b76fc0 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -218,3 +218,198 @@ richacl_move_everyone_aces_down(struct richacl_alloc *alloc)
 	}
 	return 0;
 }
+
+/**
+ * __richacl_propagate_everyone  -  propagate everyone@ permissions up for @who
+ * @alloc:	acl and number of allocated entries
+ * @who:	identifier to propagate permissions for
+ * @allow:	permissions to propagate up
+ *
+ * Propagate the permissions in @allow up from the end of the acl to the start
+ * for the specified principal @who.
+ *
+ * The simplest possible approach to achieve this would be to insert a
+ * "<who>:<allow>::allow" ace before the final everyone@ allow ace.  Since this
+ * would often result in aces which are not needed or which could be merged
+ * with an existing ace, we make the following optimizations:
+ *
+ *   - We go through the acl and determine which permissions are already
+ *     allowed or denied to @who, and we remove those permissions from
+ *     @allow.
+ *
+ *   - If the acl contains an allow ace for @who and no aces after this entry
+ *     deny permissions in @allow, we add the permissions in @allow to this
+ *     ace.  (Propagating permissions across a deny ace which can match the
+ *     process can elevate permissions.)
+ *
+ * This transformation does not alter the permissions that the acl grants.
+ */
+static int
+__richacl_propagate_everyone(struct richacl_alloc *alloc, struct richace *who,
+			     unsigned int allow)
+{
+	struct richace *allow_last = NULL, *ace;
+	struct richacl *acl = alloc->acl;
+
+	/*
+	 * Remove the permissions from allow that are already determined for
+	 * this who value, and figure out if there is an allow entry for
+	 * this who value that is "reachable" from the trailing everyone@
+	 * allow ace.
+	 */
+	richacl_for_each_entry(ace, acl) {
+		if (richace_is_inherit_only(ace))
+			continue;
+		if (richace_is_allow(ace)) {
+			if (richace_is_same_identifier(ace, who)) {
+				allow &= ~ace->e_mask;
+				allow_last = ace;
+			}
+		} else if (richace_is_deny(ace)) {
+			if (richace_is_same_identifier(ace, who))
+				allow &= ~ace->e_mask;
+			else if (allow & ace->e_mask)
+				allow_last = NULL;
+		}
+	}
+	ace--;
+
+	/*
+	 * If for group class entries, all the remaining permissions will
+	 * remain granted by the trailing everyone@ ace, no additional entry is
+	 * needed.
+	 */
+	if (!richace_is_owner(who) &&
+	    richace_is_everyone(ace) && richace_is_allow(ace) &&
+	    !(allow & ~(ace->e_mask & acl->a_other_mask)))
+		allow = 0;
+
+	if (allow) {
+		if (allow_last)
+			return richace_change_mask(alloc, &allow_last,
+						   allow_last->e_mask | allow);
+		else {
+			struct richace who_copy;
+
+			richace_copy(&who_copy, who);
+			ace = acl->a_entries + acl->a_count - 1;
+			if (richacl_insert_entry(alloc, &ace))
+				return -1;
+			richace_copy(ace, &who_copy);
+			ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+			ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
+			ace->e_mask = allow;
+		}
+	}
+	return 0;
+}
+
+/**
+ * richacl_propagate_everyone  -  propagate everyone@ permissions up the acl
+ * @alloc:	acl and number of allocated entries
+ *
+ * Make sure that group@ and all other users and groups mentioned in the acl
+ * will not lose any permissions when finally applying the other mask to the
+ * everyone@ allow ace at the end of the acl.  We modify the permissions of
+ * existing entries or add new entries before the final everyone@ allow ace to
+ * achieve that.
+ *
+ * For example, the following acl implicitly grants everyone rwpx access:
+ *
+ *    joe:r::allow
+ *    everyone@:rwpx::allow
+ *
+ * When applying mode 0660 to this acl, group@ would lose rwp access, and joe
+ * would lose wp access even though the mode does not exclude those
+ * permissions.  After propagating the everyone@ permissions, the result for
+ * applying mode 0660 becomes:
+ *
+ *    owner@:rwp::allow
+ *    joe:rwp::allow
+ *    group@:rwp::allow
+ *
+ * Deny aces complicate the matter.  For example, the following acl grants
+ * everyone but joe write access:
+ *
+ *    joe:wp::deny
+ *    everyone@:rwpx::allow
+ *
+ * When applying mode 0660 to this acl, group@ would lose rwp access, and joe
+ * would lose r access.  After propagating the everyone@ permissions, the
+ * result for applying mode 0660 becomes:
+ *
+ *    owner@:rwp::allow
+ *    joe:w::deny
+ *    group@:rwp::allow
+ *    joe:r::allow
+ */
+static int
+richacl_propagate_everyone(struct richacl_alloc *alloc)
+{
+	struct richace who = { .e_flags = RICHACE_SPECIAL_WHO };
+	struct richacl *acl = alloc->acl;
+	struct richace *ace;
+	unsigned int owner_allow, group_allow;
+
+	/*
+	 * If the owner mask contains permissions which are not in the group
+	 * mask, the group mask contains permissions which are not in the other
+	 * mask, or the owner class contains permissions which are not in the
+	 * other mask, we may need to propagate permissions up from the
+	 * everyone@ allow ace.  The third condition is implied by the first
+	 * two.
+	 */
+	if (!((acl->a_owner_mask & ~acl->a_group_mask) ||
+	      (acl->a_group_mask & ~acl->a_other_mask)))
+		return 0;
+	if (!acl->a_count)
+		return 0;
+	ace = acl->a_entries + acl->a_count - 1;
+	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
+		return 0;
+
+	owner_allow = ace->e_mask & acl->a_owner_mask;
+	group_allow = ace->e_mask & acl->a_group_mask;
+
+	if (owner_allow & ~(acl->a_group_mask & acl->a_other_mask)) {
+		/* Propagate everyone@ permissions through to owner@. */
+		who.e_id.special = RICHACE_OWNER_SPECIAL_ID;
+		if (__richacl_propagate_everyone(alloc, &who, owner_allow))
+			return -1;
+		acl = alloc->acl;
+	}
+
+	if (group_allow & ~acl->a_other_mask) {
+		int n;
+
+		/* Propagate everyone@ permissions through to group@. */
+		who.e_id.special = RICHACE_GROUP_SPECIAL_ID;
+		if (__richacl_propagate_everyone(alloc, &who, group_allow))
+			return -1;
+		acl = alloc->acl;
+
+		/*
+		 * Start from the entry before the trailing everyone@ allow
+		 * entry. We will not hit everyone@ entries in the loop.
+		 */
+		for (n = acl->a_count - 2; n != -1; n--) {
+			ace = acl->a_entries + n;
+
+			if (richace_is_inherit_only(ace) ||
+			    richace_is_owner(ace) ||
+			    richace_is_group(ace))
+				continue;
+			if (richace_is_allow(ace) || richace_is_deny(ace)) {
+				/*
+				 * Any inserted entry will end up below the
+				 * current entry
+				 */
+				if (__richacl_propagate_everyone(alloc, ace,
+								 group_allow))
+					return -1;
+				acl = alloc->acl;
+			}
+		}
+	}
+	return 0;
+}
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 23/41] richacl: Set the owner permissions to the owner mask
  2015-09-05 10:26 ` Andreas Gruenbacher
                   ` (10 preceding siblings ...)
  (?)
@ 2015-09-05 10:27 ` Andreas Gruenbacher
  2015-09-21 21:00   ` J. Bruce Fields
  -1 siblings, 1 reply; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

Change the acl so that owner@ is granted the permissions set in the
owner mask.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/richacl_compat.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 46 insertions(+)

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 9b76fc0..14a1b4b 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -413,3 +413,49 @@ richacl_propagate_everyone(struct richacl_alloc *alloc)
 	}
 	return 0;
 }
+
+/**
+ * richacl_set_owner_permissions  -  set the owner permissions to the owner mask
+ *
+ * Change the acl so that owner@ is granted the permissions set in the owner
+ * mask.  This leaves at most one efective owner@ allow entry at the beginning
+ * of the acl.
+ */
+static int
+richacl_set_owner_permissions(struct richacl_alloc *alloc)
+{
+	unsigned int x = RICHACE_POSIX_ALWAYS_ALLOWED;
+	unsigned int owner_mask = alloc->acl->a_owner_mask & ~x;
+	unsigned int denied = 0;
+	struct richace *ace;
+
+	if (!((alloc->acl->a_flags & RICHACL_WRITE_THROUGH) &&
+	      (alloc->acl->a_flags & RICHACL_MASKED)))
+		return 0;
+
+	richacl_for_each_entry(ace, alloc->acl) {
+		if (richace_is_owner(ace)) {
+			if (richace_is_allow(ace) && !(owner_mask & denied)) {
+				richace_change_mask(alloc, &ace, owner_mask);
+				owner_mask = 0;
+			} else
+				richace_change_mask(alloc, &ace, 0);
+		} else {
+			if (richace_is_deny(ace))
+				denied |= ace->e_mask;
+		}
+	}
+
+	if (owner_mask & (denied |
+			  ~alloc->acl->a_other_mask |
+			  ~alloc->acl->a_group_mask)) {
+		ace = alloc->acl->a_entries;
+		if (richacl_insert_entry(alloc, &ace))
+			return -1;
+		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+		ace->e_flags = RICHACE_SPECIAL_WHO;
+		ace->e_mask = owner_mask;
+		ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
+	}
+	return 0;
+}
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 24/41] richacl: Set the other permissions to the other mask
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

Change the acl so that everyone@ is granted the permissions set in the
other mask.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/richacl_compat.c | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 14a1b4b..30bdc95 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -459,3 +459,38 @@ richacl_set_owner_permissions(struct richacl_alloc *alloc)
 	}
 	return 0;
 }
+
+/**
+ * richacl_set_other_permissions  -  set the other permissions to the other mask
+ *
+ * Change the acl so that everyone@ is granted the permissions set in the other
+ * mask.  This leaves at most one efective everyone@ allow entry at the end of
+ * the acl.
+ */
+static int
+richacl_set_other_permissions(struct richacl_alloc *alloc)
+{
+	struct richacl *acl = alloc->acl;
+	unsigned int x = RICHACE_POSIX_ALWAYS_ALLOWED;
+	unsigned int other_mask = acl->a_other_mask & ~x;
+	struct richace *ace = acl->a_entries + acl->a_count - 1;
+
+	if (!(other_mask &&
+	      (acl->a_flags & RICHACL_WRITE_THROUGH) &&
+	      (acl->a_flags & RICHACL_MASKED)))
+		return 0;
+
+	if (acl->a_count == 0 ||
+	    !richace_is_everyone(ace) ||
+	    richace_is_inherit_only(ace)) {
+		ace++;
+		if (richacl_insert_entry(alloc, &ace))
+			return -1;
+		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+		ace->e_flags = RICHACE_SPECIAL_WHO;
+		ace->e_mask = other_mask;
+		ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
+	} else
+		richace_change_mask(alloc, &ace, other_mask);
+	return 0;
+}
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 24/41] richacl: Set the other permissions to the other mask
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

Change the acl so that everyone@ is granted the permissions set in the
other mask.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/richacl_compat.c | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 14a1b4b..30bdc95 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -459,3 +459,38 @@ richacl_set_owner_permissions(struct richacl_alloc *alloc)
 	}
 	return 0;
 }
+
+/**
+ * richacl_set_other_permissions  -  set the other permissions to the other mask
+ *
+ * Change the acl so that everyone@ is granted the permissions set in the other
+ * mask.  This leaves at most one efective everyone@ allow entry at the end of
+ * the acl.
+ */
+static int
+richacl_set_other_permissions(struct richacl_alloc *alloc)
+{
+	struct richacl *acl = alloc->acl;
+	unsigned int x = RICHACE_POSIX_ALWAYS_ALLOWED;
+	unsigned int other_mask = acl->a_other_mask & ~x;
+	struct richace *ace = acl->a_entries + acl->a_count - 1;
+
+	if (!(other_mask &&
+	      (acl->a_flags & RICHACL_WRITE_THROUGH) &&
+	      (acl->a_flags & RICHACL_MASKED)))
+		return 0;
+
+	if (acl->a_count == 0 ||
+	    !richace_is_everyone(ace) ||
+	    richace_is_inherit_only(ace)) {
+		ace++;
+		if (richacl_insert_entry(alloc, &ace))
+			return -1;
+		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+		ace->e_flags = RICHACE_SPECIAL_WHO;
+		ace->e_mask = other_mask;
+		ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
+	} else
+		richace_change_mask(alloc, &ace, other_mask);
+	return 0;
+}
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 25/41] richacl: Isolate the owner and group classes
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA
  Cc: Andreas Gruenbacher

When applying the file masks to an acl, we need to ensure that no
process gets more permissions than allowed by its file mask.

This may require inserting an owner@ deny ace to ensure this if the
owner mask contains fewer permissions than the group or other mask.  For
example, when applying mode 0466 to the following acl:

   everyone@:rw::allow

A deny ace needs to be inserted so that the owner won't get elevated
write access:

   owner@:w::deny
   everyone@:rw::allow

Likewise, we may need to insert group class deny aces if the group mask
contains fewer permissions than the other mask.  For example, when
applying mode 0646 to the following acl:

   owner@:rw::allow
   everyone@:rw::allow

A deny ace needs to be inserted so that the owning group won't get
elevated write access:

   owner@:rw::allow
   group@:w::deny
   everyone@:rw::allow

Signed-off-by: Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
---
 fs/richacl_compat.c | 236 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 236 insertions(+)

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 30bdc95..412844c 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -494,3 +494,239 @@ richacl_set_other_permissions(struct richacl_alloc *alloc)
 		richace_change_mask(alloc, &ace, other_mask);
 	return 0;
 }
+
+/**
+ * richacl_max_allowed  -  maximum permissions that anybody is allowed
+ */
+static unsigned int
+richacl_max_allowed(struct richacl *acl)
+{
+	struct richace *ace;
+	unsigned int allowed = 0;
+
+	richacl_for_each_entry_reverse(ace, acl) {
+		if (richace_is_inherit_only(ace))
+			continue;
+		if (richace_is_allow(ace))
+			allowed |= ace->e_mask;
+		else if (richace_is_deny(ace)) {
+			if (richace_is_everyone(ace))
+				allowed &= ~ace->e_mask;
+		}
+	}
+	return allowed;
+}
+
+/**
+ * richacl_isolate_owner_class  -  limit the owner class to the owner file mask
+ * @alloc:	acl and number of allocated entries
+ *
+ * POSIX requires that after a chmod, the owner class is granted no more
+ * permissions than the owner file permission bits.  For richacls, this
+ * means that the owner class must not be granted any permissions that the
+ * owner mask does not include.
+ *
+ * When we apply file masks to an acl which grant more permissions to the group
+ * or other class than to the owner class, we may end up in a situation where
+ * the owner is granted additional permissions from other aces.  For example,
+ * given this acl:
+ *
+ *    everyone:rwx::allow
+ *
+ * when file masks corresponding to mode 0466 are applied, after
+ * richacl_propagate_everyone() and __richacl_apply_masks(), we end up with:
+ *
+ *    owner@:r::allow
+ *    everyone@:rw::allow
+ *
+ * This acl still grants the owner rw access through the everyone@ allow ace.
+ * To fix this, we must deny the owner w access:
+ *
+ *    owner@:w::deny
+ *    owner@:r::allow
+ *    everyone@:rw::allow
+ */
+static int
+richacl_isolate_owner_class(struct richacl_alloc *alloc)
+{
+	struct richace *ace;
+	unsigned int allowed = 0;
+
+	allowed = richacl_max_allowed(alloc->acl);
+	if (allowed & ~alloc->acl->a_owner_mask) {
+		/*
+		 * Figure out if we can update an existig OWNER@ DENY entry.
+		 */
+		richacl_for_each_entry(ace, alloc->acl) {
+			if (richace_is_inherit_only(ace))
+				continue;
+			if (richace_is_deny(ace)) {
+				if (richace_is_owner(ace))
+					break;
+			} else if (richace_is_allow(ace)) {
+				ace = alloc->acl->a_entries +
+				      alloc->acl->a_count;
+				break;
+			}
+		}
+		if (ace != alloc->acl->a_entries + alloc->acl->a_count) {
+			if (richace_change_mask(alloc, &ace, ace->e_mask |
+					(allowed & ~alloc->acl->a_owner_mask)))
+				return -1;
+		} else {
+			/* Insert an owner@ deny entry at the front. */
+			ace = alloc->acl->a_entries;
+			if (richacl_insert_entry(alloc, &ace))
+				return -1;
+			ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+			ace->e_flags = RICHACE_SPECIAL_WHO;
+			ace->e_mask = allowed & ~alloc->acl->a_owner_mask;
+			ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
+		}
+	}
+	return 0;
+}
+
+/**
+ * __richacl_isolate_who  -  isolate entry from everyone@ allow entry
+ * @alloc:	acl and number of allocated entries
+ * @who:	identifier to isolate
+ * @deny:	permissions this identifier should not be allowed
+ *
+ * See richacl_isolate_group_class().
+ */
+static int
+__richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
+		      unsigned int deny)
+{
+	struct richacl *acl = alloc->acl;
+	struct richace *ace;
+	int n;
+	/*
+	 * Compute the permissions already denied to @who.
+	 */
+	richacl_for_each_entry(ace, acl) {
+		if (richace_is_inherit_only(ace))
+			continue;
+		if (richace_is_same_identifier(ace, who) &&
+		    richace_is_deny(ace))
+			deny &= ~ace->e_mask;
+	}
+	if (!deny)
+		return 0;
+
+	/*
+	 * Figure out if we can update an existig deny entry.  Start from the
+	 * entry before the trailing everyone@ allow entry. We will not hit
+	 * everyone@ entries in the loop.
+	 */
+	for (n = acl->a_count - 2; n != -1; n--) {
+		ace = acl->a_entries + n;
+		if (richace_is_inherit_only(ace))
+			continue;
+		if (richace_is_deny(ace)) {
+			if (richace_is_same_identifier(ace, who))
+				break;
+		} else if (richace_is_allow(ace) &&
+			   (ace->e_mask & deny)) {
+			n = -1;
+			break;
+		}
+	}
+	if (n != -1) {
+		if (richace_change_mask(alloc, &ace, ace->e_mask | deny))
+			return -1;
+	} else {
+		/*
+		 * Insert a new entry before the trailing everyone@ deny entry.
+		 */
+		struct richace who_copy;
+
+		richace_copy(&who_copy, who);
+		ace = acl->a_entries + acl->a_count - 1;
+		if (richacl_insert_entry(alloc, &ace))
+			return -1;
+		richace_copy(ace, &who_copy);
+		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+		ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
+		ace->e_mask = deny;
+	}
+	return 0;
+}
+
+/**
+ * richacl_isolate_group_class  -  limit the group class to the group file mask
+ * @alloc:	acl and number of allocated entries
+ *
+ * POSIX requires that after a chmod, the group class is granted no more
+ * permissions than the group file permission bits.  For richacls, this
+ * means that the group class must not be granted any permissions that the
+ * group mask does not include.
+ *
+ * When we apply file masks to an acl which grant more permissions to the other
+ * class than to the group class, we may end up in a situation where processes
+ * in the group class are granted additional permission from other aces.  For
+ * example, given this acl:
+ *
+ *    joe:rwx::allow
+ *    everyone:rwx::allow
+ *
+ * when file masks corresponding to mode 0646 are applied, after
+ * richacl_propagate_everyone() and __richacl_apply_masks(), we end up with:
+ *
+ *    joe:r::allow
+ *    owner@:rw::allow
+ *    group@:r::allow
+ *    everyone@:rw::allow
+ *
+ * This acl still grants joe and group@ rw access through the everyone@ allow
+ * ace.  To fix this, we must deny w access to group class aces before the
+ * everyone@ allow ace at the end of the acl:
+ *
+ *    joe:r::allow
+ *    owner@:rw::allow
+ *    group@:r::allow
+ *    joe:w::deny
+ *    group@:w::deny
+ *    everyone@:rw::allow
+ */
+static int
+richacl_isolate_group_class(struct richacl_alloc *alloc)
+{
+	struct richace who = {
+		.e_flags = RICHACE_SPECIAL_WHO,
+		.e_id.special = RICHACE_GROUP_SPECIAL_ID,
+	};
+	struct richace *ace;
+	unsigned int deny;
+
+	if (!alloc->acl->a_count)
+		return 0;
+	ace = alloc->acl->a_entries + alloc->acl->a_count - 1;
+	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
+		return 0;
+	deny = ace->e_mask & ~alloc->acl->a_group_mask;
+
+	if (deny) {
+		unsigned int n;
+
+		if (__richacl_isolate_who(alloc, &who, deny))
+			return -1;
+		/*
+		 * Start from the entry before the trailing everyone@ allow
+		 * entry.  We will not hit everyone@ entries in the loop.
+		 */
+		for (n = alloc->acl->a_count - 2; n != -1; n--) {
+			ace = alloc->acl->a_entries + n;
+
+			if (richace_is_inherit_only(ace) ||
+			    richace_is_owner(ace) ||
+			    richace_is_group(ace) ||
+			    richace_is_everyone(ace))
+				continue;
+			if (__richacl_isolate_who(alloc, ace, deny))
+				return -1;
+		}
+	}
+	return 0;
+}
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 25/41] richacl: Isolate the owner and group classes
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module
  Cc: Andreas Gruenbacher

When applying the file masks to an acl, we need to ensure that no
process gets more permissions than allowed by its file mask.

This may require inserting an owner@ deny ace to ensure this if the
owner mask contains fewer permissions than the group or other mask.  For
example, when applying mode 0466 to the following acl:

   everyone@:rw::allow

A deny ace needs to be inserted so that the owner won't get elevated
write access:

   owner@:w::deny
   everyone@:rw::allow

Likewise, we may need to insert group class deny aces if the group mask
contains fewer permissions than the other mask.  For example, when
applying mode 0646 to the following acl:

   owner@:rw::allow
   everyone@:rw::allow

A deny ace needs to be inserted so that the owning group won't get
elevated write access:

   owner@:rw::allow
   group@:w::deny
   everyone@:rw::allow

Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
---
 fs/richacl_compat.c | 236 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 236 insertions(+)

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 30bdc95..412844c 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -494,3 +494,239 @@ richacl_set_other_permissions(struct richacl_alloc *alloc)
 		richace_change_mask(alloc, &ace, other_mask);
 	return 0;
 }
+
+/**
+ * richacl_max_allowed  -  maximum permissions that anybody is allowed
+ */
+static unsigned int
+richacl_max_allowed(struct richacl *acl)
+{
+	struct richace *ace;
+	unsigned int allowed = 0;
+
+	richacl_for_each_entry_reverse(ace, acl) {
+		if (richace_is_inherit_only(ace))
+			continue;
+		if (richace_is_allow(ace))
+			allowed |= ace->e_mask;
+		else if (richace_is_deny(ace)) {
+			if (richace_is_everyone(ace))
+				allowed &= ~ace->e_mask;
+		}
+	}
+	return allowed;
+}
+
+/**
+ * richacl_isolate_owner_class  -  limit the owner class to the owner file mask
+ * @alloc:	acl and number of allocated entries
+ *
+ * POSIX requires that after a chmod, the owner class is granted no more
+ * permissions than the owner file permission bits.  For richacls, this
+ * means that the owner class must not be granted any permissions that the
+ * owner mask does not include.
+ *
+ * When we apply file masks to an acl which grant more permissions to the group
+ * or other class than to the owner class, we may end up in a situation where
+ * the owner is granted additional permissions from other aces.  For example,
+ * given this acl:
+ *
+ *    everyone:rwx::allow
+ *
+ * when file masks corresponding to mode 0466 are applied, after
+ * richacl_propagate_everyone() and __richacl_apply_masks(), we end up with:
+ *
+ *    owner@:r::allow
+ *    everyone@:rw::allow
+ *
+ * This acl still grants the owner rw access through the everyone@ allow ace.
+ * To fix this, we must deny the owner w access:
+ *
+ *    owner@:w::deny
+ *    owner@:r::allow
+ *    everyone@:rw::allow
+ */
+static int
+richacl_isolate_owner_class(struct richacl_alloc *alloc)
+{
+	struct richace *ace;
+	unsigned int allowed = 0;
+
+	allowed = richacl_max_allowed(alloc->acl);
+	if (allowed & ~alloc->acl->a_owner_mask) {
+		/*
+		 * Figure out if we can update an existig OWNER@ DENY entry.
+		 */
+		richacl_for_each_entry(ace, alloc->acl) {
+			if (richace_is_inherit_only(ace))
+				continue;
+			if (richace_is_deny(ace)) {
+				if (richace_is_owner(ace))
+					break;
+			} else if (richace_is_allow(ace)) {
+				ace = alloc->acl->a_entries +
+				      alloc->acl->a_count;
+				break;
+			}
+		}
+		if (ace != alloc->acl->a_entries + alloc->acl->a_count) {
+			if (richace_change_mask(alloc, &ace, ace->e_mask |
+					(allowed & ~alloc->acl->a_owner_mask)))
+				return -1;
+		} else {
+			/* Insert an owner@ deny entry at the front. */
+			ace = alloc->acl->a_entries;
+			if (richacl_insert_entry(alloc, &ace))
+				return -1;
+			ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+			ace->e_flags = RICHACE_SPECIAL_WHO;
+			ace->e_mask = allowed & ~alloc->acl->a_owner_mask;
+			ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
+		}
+	}
+	return 0;
+}
+
+/**
+ * __richacl_isolate_who  -  isolate entry from everyone@ allow entry
+ * @alloc:	acl and number of allocated entries
+ * @who:	identifier to isolate
+ * @deny:	permissions this identifier should not be allowed
+ *
+ * See richacl_isolate_group_class().
+ */
+static int
+__richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
+		      unsigned int deny)
+{
+	struct richacl *acl = alloc->acl;
+	struct richace *ace;
+	int n;
+	/*
+	 * Compute the permissions already denied to @who.
+	 */
+	richacl_for_each_entry(ace, acl) {
+		if (richace_is_inherit_only(ace))
+			continue;
+		if (richace_is_same_identifier(ace, who) &&
+		    richace_is_deny(ace))
+			deny &= ~ace->e_mask;
+	}
+	if (!deny)
+		return 0;
+
+	/*
+	 * Figure out if we can update an existig deny entry.  Start from the
+	 * entry before the trailing everyone@ allow entry. We will not hit
+	 * everyone@ entries in the loop.
+	 */
+	for (n = acl->a_count - 2; n != -1; n--) {
+		ace = acl->a_entries + n;
+		if (richace_is_inherit_only(ace))
+			continue;
+		if (richace_is_deny(ace)) {
+			if (richace_is_same_identifier(ace, who))
+				break;
+		} else if (richace_is_allow(ace) &&
+			   (ace->e_mask & deny)) {
+			n = -1;
+			break;
+		}
+	}
+	if (n != -1) {
+		if (richace_change_mask(alloc, &ace, ace->e_mask | deny))
+			return -1;
+	} else {
+		/*
+		 * Insert a new entry before the trailing everyone@ deny entry.
+		 */
+		struct richace who_copy;
+
+		richace_copy(&who_copy, who);
+		ace = acl->a_entries + acl->a_count - 1;
+		if (richacl_insert_entry(alloc, &ace))
+			return -1;
+		richace_copy(ace, &who_copy);
+		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+		ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
+		ace->e_mask = deny;
+	}
+	return 0;
+}
+
+/**
+ * richacl_isolate_group_class  -  limit the group class to the group file mask
+ * @alloc:	acl and number of allocated entries
+ *
+ * POSIX requires that after a chmod, the group class is granted no more
+ * permissions than the group file permission bits.  For richacls, this
+ * means that the group class must not be granted any permissions that the
+ * group mask does not include.
+ *
+ * When we apply file masks to an acl which grant more permissions to the other
+ * class than to the group class, we may end up in a situation where processes
+ * in the group class are granted additional permission from other aces.  For
+ * example, given this acl:
+ *
+ *    joe:rwx::allow
+ *    everyone:rwx::allow
+ *
+ * when file masks corresponding to mode 0646 are applied, after
+ * richacl_propagate_everyone() and __richacl_apply_masks(), we end up with:
+ *
+ *    joe:r::allow
+ *    owner@:rw::allow
+ *    group@:r::allow
+ *    everyone@:rw::allow
+ *
+ * This acl still grants joe and group@ rw access through the everyone@ allow
+ * ace.  To fix this, we must deny w access to group class aces before the
+ * everyone@ allow ace at the end of the acl:
+ *
+ *    joe:r::allow
+ *    owner@:rw::allow
+ *    group@:r::allow
+ *    joe:w::deny
+ *    group@:w::deny
+ *    everyone@:rw::allow
+ */
+static int
+richacl_isolate_group_class(struct richacl_alloc *alloc)
+{
+	struct richace who = {
+		.e_flags = RICHACE_SPECIAL_WHO,
+		.e_id.special = RICHACE_GROUP_SPECIAL_ID,
+	};
+	struct richace *ace;
+	unsigned int deny;
+
+	if (!alloc->acl->a_count)
+		return 0;
+	ace = alloc->acl->a_entries + alloc->acl->a_count - 1;
+	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
+		return 0;
+	deny = ace->e_mask & ~alloc->acl->a_group_mask;
+
+	if (deny) {
+		unsigned int n;
+
+		if (__richacl_isolate_who(alloc, &who, deny))
+			return -1;
+		/*
+		 * Start from the entry before the trailing everyone@ allow
+		 * entry.  We will not hit everyone@ entries in the loop.
+		 */
+		for (n = alloc->acl->a_count - 2; n != -1; n--) {
+			ace = alloc->acl->a_entries + n;
+
+			if (richace_is_inherit_only(ace) ||
+			    richace_is_owner(ace) ||
+			    richace_is_group(ace) ||
+			    richace_is_everyone(ace))
+				continue;
+			if (__richacl_isolate_who(alloc, ace, deny))
+				return -1;
+		}
+	}
+	return 0;
+}
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 26/41] richacl: Apply the file masks to a richacl
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA
  Cc: Andreas Gruenbacher

Put all the pieces of the acl transformation puzzle together for
computing a richacl which has the file masks "applied" so that the
standard nfsv4 access check algorithm can be used on the richacl.

Signed-off-by: Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
---
 fs/richacl_compat.c     | 110 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl.h |   3 ++
 2 files changed, 113 insertions(+)

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 412844c..9681efe 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -730,3 +730,113 @@ richacl_isolate_group_class(struct richacl_alloc *alloc)
 	}
 	return 0;
 }
+
+/**
+ * __richacl_apply_masks  -  apply the file masks to all aces
+ * @alloc:	acl and number of allocated entries
+ *
+ * Apply the owner mask to owner@ aces, the other mask to
+ * everyone@ aces, and the group mask to all other aces.
+ *
+ * The previous transformations have brought the acl into a
+ * form in which applying the masks will not lead to the
+ * accidental loss of permissions anymore.
+ */
+static int
+__richacl_apply_masks(struct richacl_alloc *alloc, kuid_t owner)
+{
+	struct richace *ace;
+
+	richacl_for_each_entry(ace, alloc->acl) {
+		unsigned int mask;
+
+		if (richace_is_inherit_only(ace) || !richace_is_allow(ace))
+			continue;
+		if (richace_is_owner(ace) ||
+		    (richace_is_unix_user(ace) && uid_eq(owner, ace->e_id.uid)))
+			mask = alloc->acl->a_owner_mask;
+		else if (richace_is_everyone(ace))
+			mask = alloc->acl->a_other_mask;
+		else
+			mask = alloc->acl->a_group_mask;
+		if (richace_change_mask(alloc, &ace, ace->e_mask & mask))
+			return -1;
+	}
+	return 0;
+}
+
+/**
+ * richacl_apply_masks  -  apply the masks to the acl
+ *
+ * Transform @acl so that the standard NFSv4 permission check algorithm (which
+ * is not aware of file masks) will compute the same access decisions as the
+ * richacl permission check algorithm (which looks at the acl and the file
+ * masks).
+ *
+ * This algorithm is split into several steps:
+ *
+ *   - Move everyone@ aces to the end of the acl.  This simplifies the other
+ *     transformations, and allows the everyone@ allow ace at the end of the
+ *     acl to eventually allow permissions to the other class only.
+ *
+ *   - Propagate everyone@ permissions up the acl.  This transformation makes
+ *     sure that the owner and group class aces won't lose any permissions when
+ *     we apply the other mask to the everyone@ allow ace at the end of the acl.
+ *
+ *   - Apply the file masks to all aces.
+ *
+ *   - Make sure the owner is granted the owner mask permissions.
+ *
+ *   - Make sure everyone is granted the other mask permissions.
+ *
+ *   - Make sure that the owner is not granted any permissions beyond the owner
+ *     mask from group class aces or from everyone@.
+ *
+ *   - Make sure that the group class is not granted any permissions from
+ *     everyone@.
+ *
+ * The algorithm is exact except for richacls which cannot be represented as an
+ * acl alone: for example, given this acl:
+ *
+ *    group@:rw::allow
+ *
+ * when file masks corresponding to mode 0600 are applied, the owner would only
+ * get rw access if he is a member of the owning group.  This algorithm would
+ * produce an empty acl in this case.  We fix this case by modifying
+ * richacl_permission() so that the group mask is always applied to group class
+ * aces.  With this fix, the owner would not have any access (beyond the
+ * implicit permissions always granted to owners).
+ *
+ * NOTE: Depending on the acl and file masks, this algorithm can increase the
+ * number of aces by almost a factor of three in the worst case. This may make
+ * the acl too large for some purposes.
+ */
+int
+richacl_apply_masks(struct richacl **acl, kuid_t owner)
+{
+	if ((*acl)->a_flags & RICHACL_MASKED) {
+		struct richacl_alloc alloc = {
+			.acl = richacl_clone(*acl, GFP_KERNEL),
+			.count = (*acl)->a_count,
+		};
+		if (!alloc.acl)
+			return -ENOMEM;
+
+		if (richacl_move_everyone_aces_down(&alloc) ||
+		    richacl_propagate_everyone(&alloc) ||
+		    __richacl_apply_masks(&alloc, owner) ||
+		    richacl_set_owner_permissions(&alloc) ||
+		    richacl_set_other_permissions(&alloc) ||
+		    richacl_isolate_owner_class(&alloc) ||
+		    richacl_isolate_group_class(&alloc)) {
+			richacl_put(alloc.acl);
+			return -ENOMEM;
+		}
+
+		alloc.acl->a_flags &= ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED);
+		richacl_put(*acl);
+		*acl = alloc.acl;
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(richacl_apply_masks);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index 832b06c..a945f3c 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -332,4 +332,7 @@ extern struct richacl *richacl_inherit(const struct richacl *, int);
 extern int richacl_permission(struct inode *, const struct richacl *, int);
 extern struct richacl *richacl_create(struct inode *, struct inode *);
 
+/* richacl_compat.c */
+extern int richacl_apply_masks(struct richacl **, kuid_t);
+
 #endif /* __RICHACL_H */
-- 
2.4.3

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 26/41] richacl: Apply the file masks to a richacl
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module
  Cc: Andreas Gruenbacher

Put all the pieces of the acl transformation puzzle together for
computing a richacl which has the file masks "applied" so that the
standard nfsv4 access check algorithm can be used on the richacl.

Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
---
 fs/richacl_compat.c     | 110 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl.h |   3 ++
 2 files changed, 113 insertions(+)

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 412844c..9681efe 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -730,3 +730,113 @@ richacl_isolate_group_class(struct richacl_alloc *alloc)
 	}
 	return 0;
 }
+
+/**
+ * __richacl_apply_masks  -  apply the file masks to all aces
+ * @alloc:	acl and number of allocated entries
+ *
+ * Apply the owner mask to owner@ aces, the other mask to
+ * everyone@ aces, and the group mask to all other aces.
+ *
+ * The previous transformations have brought the acl into a
+ * form in which applying the masks will not lead to the
+ * accidental loss of permissions anymore.
+ */
+static int
+__richacl_apply_masks(struct richacl_alloc *alloc, kuid_t owner)
+{
+	struct richace *ace;
+
+	richacl_for_each_entry(ace, alloc->acl) {
+		unsigned int mask;
+
+		if (richace_is_inherit_only(ace) || !richace_is_allow(ace))
+			continue;
+		if (richace_is_owner(ace) ||
+		    (richace_is_unix_user(ace) && uid_eq(owner, ace->e_id.uid)))
+			mask = alloc->acl->a_owner_mask;
+		else if (richace_is_everyone(ace))
+			mask = alloc->acl->a_other_mask;
+		else
+			mask = alloc->acl->a_group_mask;
+		if (richace_change_mask(alloc, &ace, ace->e_mask & mask))
+			return -1;
+	}
+	return 0;
+}
+
+/**
+ * richacl_apply_masks  -  apply the masks to the acl
+ *
+ * Transform @acl so that the standard NFSv4 permission check algorithm (which
+ * is not aware of file masks) will compute the same access decisions as the
+ * richacl permission check algorithm (which looks at the acl and the file
+ * masks).
+ *
+ * This algorithm is split into several steps:
+ *
+ *   - Move everyone@ aces to the end of the acl.  This simplifies the other
+ *     transformations, and allows the everyone@ allow ace at the end of the
+ *     acl to eventually allow permissions to the other class only.
+ *
+ *   - Propagate everyone@ permissions up the acl.  This transformation makes
+ *     sure that the owner and group class aces won't lose any permissions when
+ *     we apply the other mask to the everyone@ allow ace at the end of the acl.
+ *
+ *   - Apply the file masks to all aces.
+ *
+ *   - Make sure the owner is granted the owner mask permissions.
+ *
+ *   - Make sure everyone is granted the other mask permissions.
+ *
+ *   - Make sure that the owner is not granted any permissions beyond the owner
+ *     mask from group class aces or from everyone@.
+ *
+ *   - Make sure that the group class is not granted any permissions from
+ *     everyone@.
+ *
+ * The algorithm is exact except for richacls which cannot be represented as an
+ * acl alone: for example, given this acl:
+ *
+ *    group@:rw::allow
+ *
+ * when file masks corresponding to mode 0600 are applied, the owner would only
+ * get rw access if he is a member of the owning group.  This algorithm would
+ * produce an empty acl in this case.  We fix this case by modifying
+ * richacl_permission() so that the group mask is always applied to group class
+ * aces.  With this fix, the owner would not have any access (beyond the
+ * implicit permissions always granted to owners).
+ *
+ * NOTE: Depending on the acl and file masks, this algorithm can increase the
+ * number of aces by almost a factor of three in the worst case. This may make
+ * the acl too large for some purposes.
+ */
+int
+richacl_apply_masks(struct richacl **acl, kuid_t owner)
+{
+	if ((*acl)->a_flags & RICHACL_MASKED) {
+		struct richacl_alloc alloc = {
+			.acl = richacl_clone(*acl, GFP_KERNEL),
+			.count = (*acl)->a_count,
+		};
+		if (!alloc.acl)
+			return -ENOMEM;
+
+		if (richacl_move_everyone_aces_down(&alloc) ||
+		    richacl_propagate_everyone(&alloc) ||
+		    __richacl_apply_masks(&alloc, owner) ||
+		    richacl_set_owner_permissions(&alloc) ||
+		    richacl_set_other_permissions(&alloc) ||
+		    richacl_isolate_owner_class(&alloc) ||
+		    richacl_isolate_group_class(&alloc)) {
+			richacl_put(alloc.acl);
+			return -ENOMEM;
+		}
+
+		alloc.acl->a_flags &= ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED);
+		richacl_put(*acl);
+		*acl = alloc.acl;
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(richacl_apply_masks);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index 832b06c..a945f3c 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -332,4 +332,7 @@ extern struct richacl *richacl_inherit(const struct richacl *, int);
 extern int richacl_permission(struct inode *, const struct richacl *, int);
 extern struct richacl *richacl_create(struct inode *, struct inode *);
 
+/* richacl_compat.c */
+extern int richacl_apply_masks(struct richacl **, kuid_t);
+
 #endif /* __RICHACL_H */
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 27/41] richacl: Create richacl from mode values
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

A file can have "no acl" in the sense that only the file mode permission
bits determine access.  In that case, the getxattr system call fails with
errno == ENODATA (No such attribute).

Over the NFSv4 protocol, a file always has an acl, and we convert the file
mode permission bits into an equivalent acl with richacl_from_mode.  Such
"trivial" acls can be converted back to a file mode with
richacl_equiv_mode.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/richacl_compat.c     | 88 +++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl.h |  1 +
 2 files changed, 89 insertions(+)

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 9681efe..632409f 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -840,3 +840,91 @@ richacl_apply_masks(struct richacl **acl, kuid_t owner)
 	return 0;
 }
 EXPORT_SYMBOL_GPL(richacl_apply_masks);
+
+/**
+ * richacl_from_mode  -  create an acl which corresponds to @mode
+ *
+ * The resulting acl doesn't have the RICHACL_MASKED flag set.
+ *
+ * @mode:	file mode including the file type
+ */
+struct richacl *
+richacl_from_mode(mode_t mode)
+{
+	unsigned int owner_mask = richacl_mode_to_mask(mode >> 6);
+	unsigned int group_mask = richacl_mode_to_mask(mode >> 3);
+	unsigned int other_mask = richacl_mode_to_mask(mode);
+	unsigned int denied;
+	unsigned int entries = 0;
+	struct richacl *acl;
+	struct richace *ace;
+
+	/* RICHACE_DELETE_CHILD is meaningless for non-directories. */
+	if (!S_ISDIR(mode)) {
+		owner_mask &= ~RICHACE_DELETE_CHILD;
+		group_mask &= ~RICHACE_DELETE_CHILD;
+		other_mask &= ~RICHACE_DELETE_CHILD;
+	}
+
+	denied = ~owner_mask & (group_mask | other_mask);
+	if (denied)
+		entries++;  /* owner@ deny entry needed */
+	if (owner_mask & ~(group_mask & other_mask))
+		entries++;  /* owner@ allow entry needed */
+	denied = ~group_mask & other_mask;
+	if (denied)
+		entries++;  /* group@ deny entry needed */
+	if (group_mask & ~other_mask)
+		entries++;  /* group@ allow entry needed */
+	if (other_mask)
+		entries++;  /* everyone@ allow entry needed */
+
+	acl = richacl_alloc(entries, GFP_KERNEL);
+	if (!acl)
+		return NULL;
+	acl->a_owner_mask = owner_mask;
+	acl->a_group_mask = group_mask;
+	acl->a_other_mask = other_mask;
+	ace = acl->a_entries;
+
+	denied = ~owner_mask & (group_mask | other_mask);
+	if (denied) {
+		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+		ace->e_flags = RICHACE_SPECIAL_WHO;
+		ace->e_mask = denied;
+		ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
+		ace++;
+	}
+	if (owner_mask & ~(group_mask & other_mask)) {
+		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+		ace->e_flags = RICHACE_SPECIAL_WHO;
+		ace->e_mask = owner_mask;
+		ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
+		ace++;
+	}
+	denied = ~group_mask & other_mask;
+	if (denied) {
+		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+		ace->e_flags = RICHACE_SPECIAL_WHO;
+		ace->e_mask = denied;
+		ace->e_id.special = RICHACE_GROUP_SPECIAL_ID;
+		ace++;
+	}
+	if (group_mask & ~other_mask) {
+		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+		ace->e_flags = RICHACE_SPECIAL_WHO;
+		ace->e_mask = group_mask;
+		ace->e_id.special = RICHACE_GROUP_SPECIAL_ID;
+		ace++;
+	}
+	if (other_mask) {
+		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+		ace->e_flags = RICHACE_SPECIAL_WHO;
+		ace->e_mask = other_mask;
+		ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
+		ace++;
+	}
+
+	return acl;
+}
+EXPORT_SYMBOL_GPL(richacl_from_mode);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index a945f3c..f48c04e 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -334,5 +334,6 @@ extern struct richacl *richacl_create(struct inode *, struct inode *);
 
 /* richacl_compat.c */
 extern int richacl_apply_masks(struct richacl **, kuid_t);
+extern struct richacl *richacl_from_mode(mode_t);
 
 #endif /* __RICHACL_H */
-- 
2.4.3

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 27/41] richacl: Create richacl from mode values
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

A file can have "no acl" in the sense that only the file mode permission
bits determine access.  In that case, the getxattr system call fails with
errno == ENODATA (No such attribute).

Over the NFSv4 protocol, a file always has an acl, and we convert the file
mode permission bits into an equivalent acl with richacl_from_mode.  Such
"trivial" acls can be converted back to a file mode with
richacl_equiv_mode.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/richacl_compat.c     | 88 +++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl.h |  1 +
 2 files changed, 89 insertions(+)

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 9681efe..632409f 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -840,3 +840,91 @@ richacl_apply_masks(struct richacl **acl, kuid_t owner)
 	return 0;
 }
 EXPORT_SYMBOL_GPL(richacl_apply_masks);
+
+/**
+ * richacl_from_mode  -  create an acl which corresponds to @mode
+ *
+ * The resulting acl doesn't have the RICHACL_MASKED flag set.
+ *
+ * @mode:	file mode including the file type
+ */
+struct richacl *
+richacl_from_mode(mode_t mode)
+{
+	unsigned int owner_mask = richacl_mode_to_mask(mode >> 6);
+	unsigned int group_mask = richacl_mode_to_mask(mode >> 3);
+	unsigned int other_mask = richacl_mode_to_mask(mode);
+	unsigned int denied;
+	unsigned int entries = 0;
+	struct richacl *acl;
+	struct richace *ace;
+
+	/* RICHACE_DELETE_CHILD is meaningless for non-directories. */
+	if (!S_ISDIR(mode)) {
+		owner_mask &= ~RICHACE_DELETE_CHILD;
+		group_mask &= ~RICHACE_DELETE_CHILD;
+		other_mask &= ~RICHACE_DELETE_CHILD;
+	}
+
+	denied = ~owner_mask & (group_mask | other_mask);
+	if (denied)
+		entries++;  /* owner@ deny entry needed */
+	if (owner_mask & ~(group_mask & other_mask))
+		entries++;  /* owner@ allow entry needed */
+	denied = ~group_mask & other_mask;
+	if (denied)
+		entries++;  /* group@ deny entry needed */
+	if (group_mask & ~other_mask)
+		entries++;  /* group@ allow entry needed */
+	if (other_mask)
+		entries++;  /* everyone@ allow entry needed */
+
+	acl = richacl_alloc(entries, GFP_KERNEL);
+	if (!acl)
+		return NULL;
+	acl->a_owner_mask = owner_mask;
+	acl->a_group_mask = group_mask;
+	acl->a_other_mask = other_mask;
+	ace = acl->a_entries;
+
+	denied = ~owner_mask & (group_mask | other_mask);
+	if (denied) {
+		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+		ace->e_flags = RICHACE_SPECIAL_WHO;
+		ace->e_mask = denied;
+		ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
+		ace++;
+	}
+	if (owner_mask & ~(group_mask & other_mask)) {
+		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+		ace->e_flags = RICHACE_SPECIAL_WHO;
+		ace->e_mask = owner_mask;
+		ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
+		ace++;
+	}
+	denied = ~group_mask & other_mask;
+	if (denied) {
+		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+		ace->e_flags = RICHACE_SPECIAL_WHO;
+		ace->e_mask = denied;
+		ace->e_id.special = RICHACE_GROUP_SPECIAL_ID;
+		ace++;
+	}
+	if (group_mask & ~other_mask) {
+		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+		ace->e_flags = RICHACE_SPECIAL_WHO;
+		ace->e_mask = group_mask;
+		ace->e_id.special = RICHACE_GROUP_SPECIAL_ID;
+		ace++;
+	}
+	if (other_mask) {
+		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+		ace->e_flags = RICHACE_SPECIAL_WHO;
+		ace->e_mask = other_mask;
+		ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
+		ace++;
+	}
+
+	return acl;
+}
+EXPORT_SYMBOL_GPL(richacl_from_mode);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index a945f3c..f48c04e 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -334,5 +334,6 @@ extern struct richacl *richacl_create(struct inode *, struct inode *);
 
 /* richacl_compat.c */
 extern int richacl_apply_masks(struct richacl **, kuid_t);
+extern struct richacl *richacl_from_mode(mode_t);
 
 #endif /* __RICHACL_H */
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 28/41] nfsd: Keep list of acls to dispose of in compoundargs
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

We will decode acls in requests into richacls.  Even if unlikely, there
can be more than one acl in a single request; those richacls need to be
richacl_put() at the end of the request instead of kfree()d, so keep a
list of acls in compoundargs for that.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/nfsd/nfs4xdr.c | 27 +++++++++++++++++++++++++++
 fs/nfsd/xdr4.h    |  6 ++++++
 2 files changed, 33 insertions(+)

diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 75e0563..0768251 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -40,6 +40,7 @@
 #include <linux/utsname.h>
 #include <linux/pagemap.h>
 #include <linux/sunrpc/svcauth_gss.h>
+#include <linux/richacl.h>
 
 #include "idmap.h"
 #include "acl.h"
@@ -196,6 +197,24 @@ svcxdr_tmpalloc(struct nfsd4_compoundargs *argp, u32 len)
 	return tb->buf;
 }
 
+static struct richacl *
+svcxdr_alloc_richacl(struct nfsd4_compoundargs *argp, u32 nace)
+{
+	struct svcxdr_richacl *acls;
+
+	acls = kmalloc(sizeof(*acls), GFP_KERNEL);
+	if (!acls)
+		return NULL;
+	acls->acl = richacl_alloc(nace, GFP_KERNEL);
+	if (!acls->acl) {
+		kfree(acls);
+		return NULL;
+	}
+	acls->next = argp->acls;
+	argp->acls = acls;
+	return acls->acl;
+}
+
 /*
  * For xdr strings that need to be passed to other kernel api's
  * as null-terminated strings.
@@ -4395,6 +4414,13 @@ int nfsd4_release_compoundargs(void *rq, __be32 *p, void *resp)
 		args->to_free = tb->next;
 		kfree(tb);
 	}
+	while (args->acls) {
+		struct svcxdr_richacl *acls = args->acls;
+
+		args->acls = acls->next;
+		richacl_put(acls->acl);
+		kfree(acls);
+	}
 	return 1;
 }
 
@@ -4413,6 +4439,7 @@ nfs4svc_decode_compoundargs(struct svc_rqst *rqstp, __be32 *p, struct nfsd4_comp
 	args->pagelen = rqstp->rq_arg.page_len;
 	args->tmpp = NULL;
 	args->to_free = NULL;
+	args->acls = NULL;
 	args->ops = args->iops;
 	args->rqstp = rqstp;
 
diff --git a/fs/nfsd/xdr4.h b/fs/nfsd/xdr4.h
index 9f99100..b698585 100644
--- a/fs/nfsd/xdr4.h
+++ b/fs/nfsd/xdr4.h
@@ -570,6 +570,11 @@ struct svcxdr_tmpbuf {
 	char buf[];
 };
 
+struct svcxdr_richacl {
+	struct svcxdr_richacl *next;
+	struct richacl *acl;
+};
+
 struct nfsd4_compoundargs {
 	/* scratch variables for XDR decode */
 	__be32 *			p;
@@ -579,6 +584,7 @@ struct nfsd4_compoundargs {
 	__be32				tmp[8];
 	__be32 *			tmpp;
 	struct svcxdr_tmpbuf		*to_free;
+	struct svcxdr_richacl		*acls;
 
 	struct svc_rqst			*rqstp;
 
-- 
2.4.3

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 28/41] nfsd: Keep list of acls to dispose of in compoundargs
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

We will decode acls in requests into richacls.  Even if unlikely, there
can be more than one acl in a single request; those richacls need to be
richacl_put() at the end of the request instead of kfree()d, so keep a
list of acls in compoundargs for that.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/nfsd/nfs4xdr.c | 27 +++++++++++++++++++++++++++
 fs/nfsd/xdr4.h    |  6 ++++++
 2 files changed, 33 insertions(+)

diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 75e0563..0768251 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -40,6 +40,7 @@
 #include <linux/utsname.h>
 #include <linux/pagemap.h>
 #include <linux/sunrpc/svcauth_gss.h>
+#include <linux/richacl.h>
 
 #include "idmap.h"
 #include "acl.h"
@@ -196,6 +197,24 @@ svcxdr_tmpalloc(struct nfsd4_compoundargs *argp, u32 len)
 	return tb->buf;
 }
 
+static struct richacl *
+svcxdr_alloc_richacl(struct nfsd4_compoundargs *argp, u32 nace)
+{
+	struct svcxdr_richacl *acls;
+
+	acls = kmalloc(sizeof(*acls), GFP_KERNEL);
+	if (!acls)
+		return NULL;
+	acls->acl = richacl_alloc(nace, GFP_KERNEL);
+	if (!acls->acl) {
+		kfree(acls);
+		return NULL;
+	}
+	acls->next = argp->acls;
+	argp->acls = acls;
+	return acls->acl;
+}
+
 /*
  * For xdr strings that need to be passed to other kernel api's
  * as null-terminated strings.
@@ -4395,6 +4414,13 @@ int nfsd4_release_compoundargs(void *rq, __be32 *p, void *resp)
 		args->to_free = tb->next;
 		kfree(tb);
 	}
+	while (args->acls) {
+		struct svcxdr_richacl *acls = args->acls;
+
+		args->acls = acls->next;
+		richacl_put(acls->acl);
+		kfree(acls);
+	}
 	return 1;
 }
 
@@ -4413,6 +4439,7 @@ nfs4svc_decode_compoundargs(struct svc_rqst *rqstp, __be32 *p, struct nfsd4_comp
 	args->pagelen = rqstp->rq_arg.page_len;
 	args->tmpp = NULL;
 	args->to_free = NULL;
+	args->acls = NULL;
 	args->ops = args->iops;
 	args->rqstp = rqstp;
 
diff --git a/fs/nfsd/xdr4.h b/fs/nfsd/xdr4.h
index 9f99100..b698585 100644
--- a/fs/nfsd/xdr4.h
+++ b/fs/nfsd/xdr4.h
@@ -570,6 +570,11 @@ struct svcxdr_tmpbuf {
 	char buf[];
 };
 
+struct svcxdr_richacl {
+	struct svcxdr_richacl *next;
+	struct richacl *acl;
+};
+
 struct nfsd4_compoundargs {
 	/* scratch variables for XDR decode */
 	__be32 *			p;
@@ -579,6 +584,7 @@ struct nfsd4_compoundargs {
 	__be32				tmp[8];
 	__be32 *			tmpp;
 	struct svcxdr_tmpbuf		*to_free;
+	struct svcxdr_richacl		*acls;
 
 	struct svc_rqst			*rqstp;
 
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 29/41] nfsd: Use richacls as internal acl representation
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

When converting from NFSv4 ACLs to POSIX ACLs, nfsd so far was using
struct nfs4_acl as its internal representation. This representation is a
subset of richacls, so get rid of struct nfs4_acl. Richacls even have a
more compact in-memory representation, so a few more ACL entries can
easily be supported.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/Kconfig              |   6 +
 fs/nfs_common/Makefile  |   1 +
 fs/nfs_common/nfs4acl.c |  44 ++++++
 fs/nfsd/Kconfig         |   1 +
 fs/nfsd/acl.h           |  24 ++--
 fs/nfsd/nfs4acl.c       | 367 ++++++++++++++++++++++--------------------------
 fs/nfsd/nfs4proc.c      |  15 +-
 fs/nfsd/nfs4xdr.c       |  64 +++------
 fs/nfsd/xdr4.h          |   6 +-
 include/linux/nfs4.h    |  23 ---
 include/linux/nfs4acl.h |   7 +
 11 files changed, 273 insertions(+), 285 deletions(-)
 create mode 100644 fs/nfs_common/nfs4acl.c
 create mode 100644 include/linux/nfs4acl.h

diff --git a/fs/Kconfig b/fs/Kconfig
index 3e09c06..dd3f2d6 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -268,6 +268,12 @@ config NFS_COMMON
 	depends on NFSD || NFS_FS || LOCKD
 	default y
 
+config NFS_RICHACL
+	bool
+	depends on NFSD_V4 || NFS_V4
+	select FS_RICHACL
+	default y
+
 source "net/sunrpc/Kconfig"
 source "fs/ceph/Kconfig"
 source "fs/cifs/Kconfig"
diff --git a/fs/nfs_common/Makefile b/fs/nfs_common/Makefile
index d153ca3..e055139 100644
--- a/fs/nfs_common/Makefile
+++ b/fs/nfs_common/Makefile
@@ -4,5 +4,6 @@
 
 obj-$(CONFIG_NFS_ACL_SUPPORT) += nfs_acl.o
 nfs_acl-objs := nfsacl.o
+obj-$(CONFIG_NFS_RICHACL) += nfs4acl.o
 
 obj-$(CONFIG_GRACE_PERIOD) += grace.o
diff --git a/fs/nfs_common/nfs4acl.c b/fs/nfs_common/nfs4acl.c
new file mode 100644
index 0000000..02df064
--- /dev/null
+++ b/fs/nfs_common/nfs4acl.c
@@ -0,0 +1,44 @@
+#include <linux/fs.h>
+#include <linux/richacl.h>
+#include <linux/nfs4acl.h>
+
+static struct special_id {
+	char *who;
+	int   len;
+} special_who_map[] = {
+	[RICHACE_OWNER_SPECIAL_ID] = {
+		.who = "OWNER@",
+		.len = sizeof("OWNER@") - 1 },
+	[RICHACE_GROUP_SPECIAL_ID] = {
+		.who = "GROUP@",
+		.len = sizeof("GROUP@") - 1 },
+	[RICHACE_EVERYONE_SPECIAL_ID] = {
+		.who = "EVERYONE@",
+		.len = sizeof("EVERYONE@") - 1 }
+};
+
+int nfs4acl_who_to_special_id(const char *who, u32 len)
+{
+	int n;
+
+	for (n = 0; n < ARRAY_SIZE(special_who_map); n++) {
+		if (len == special_who_map[n].len &&
+		    !memcmp(who, special_who_map[n].who, len))
+			return n;
+	}
+	return -1;
+}
+EXPORT_SYMBOL(nfs4acl_who_to_special_id);
+
+bool nfs4acl_special_id_to_who(unsigned int special_who,
+			       const char **who, unsigned int *len)
+{
+	struct special_id *special = &special_who_map[special_who];
+
+	if (special_who > ARRAY_SIZE(special_who_map) || !special->len)
+		return false;
+	*who = special->who;
+	*len = special->len;
+	return true;
+}
+EXPORT_SYMBOL(nfs4acl_special_id_to_who);
diff --git a/fs/nfsd/Kconfig b/fs/nfsd/Kconfig
index a0b77fc..811379a 100644
--- a/fs/nfsd/Kconfig
+++ b/fs/nfsd/Kconfig
@@ -70,6 +70,7 @@ config NFSD_V4
 	depends on NFSD && PROC_FS
 	select NFSD_V3
 	select FS_POSIX_ACL
+	select FS_RICHACL
 	select SUNRPC_GSS
 	select CRYPTO
 	select GRACE_PERIOD
diff --git a/fs/nfsd/acl.h b/fs/nfsd/acl.h
index 4cd7c69..1c5deb5 100644
--- a/fs/nfsd/acl.h
+++ b/fs/nfsd/acl.h
@@ -35,25 +35,27 @@
 #ifndef LINUX_NFS4_ACL_H
 #define LINUX_NFS4_ACL_H
 
-struct nfs4_acl;
+struct richacl;
+struct richace;
 struct svc_fh;
 struct svc_rqst;
 
 /*
  * Maximum ACL we'll accept from a client; chosen (somewhat
  * arbitrarily) so that kmalloc'ing the ACL shouldn't require a
- * high-order allocation.  This allows 204 ACEs on x86_64:
+ * high-order allocation.  This allows 339 ACEs on x86_64:
  */
-#define NFS4_ACL_MAX ((PAGE_SIZE - sizeof(struct nfs4_acl)) \
-			/ sizeof(struct nfs4_ace))
+#define NFSD4_ACL_MAX ((PAGE_SIZE - sizeof(struct richacl)) \
+			/ sizeof(struct richace))
 
-int nfs4_acl_bytes(int entries);
-int nfs4_acl_get_whotype(char *, u32);
-__be32 nfs4_acl_write_who(struct xdr_stream *xdr, int who);
+__be32 nfsd4_decode_ace_who(struct richace *ace, struct svc_rqst *rqstp,
+			    char *who, u32 len);
+__be32 nfsd4_encode_ace_who(struct xdr_stream *xdr, struct svc_rqst *rqstp,
+			    struct richace *ace);
 
-int nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
-		struct nfs4_acl **acl);
-__be32 nfsd4_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
-		struct nfs4_acl *acl);
+int nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
+		  struct richacl **acl);
+__be32 nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
+		     struct richacl *acl);
 
 #endif /* LINUX_NFS4_ACL_H */
diff --git a/fs/nfsd/nfs4acl.c b/fs/nfsd/nfs4acl.c
index eb5accf..582f772 100644
--- a/fs/nfsd/nfs4acl.c
+++ b/fs/nfsd/nfs4acl.c
@@ -36,45 +36,48 @@
 
 #include <linux/slab.h>
 #include <linux/nfs_fs.h>
+#include <linux/richacl_compat.h>
+#include <linux/nfs4acl.h>
 #include "nfsfh.h"
 #include "nfsd.h"
+#include "idmap.h"
 #include "acl.h"
 #include "vfs.h"
 
-#define NFS4_ACL_TYPE_DEFAULT	0x01
-#define NFS4_ACL_DIR		0x02
-#define NFS4_ACL_OWNER		0x04
+#define FLAG_DEFAULT_ACL	0x01
+#define FLAG_DIRECTORY		0x02
+#define FLAG_OWNER		0x04
 
 /* mode bit translations: */
-#define NFS4_READ_MODE (NFS4_ACE_READ_DATA)
-#define NFS4_WRITE_MODE (NFS4_ACE_WRITE_DATA | NFS4_ACE_APPEND_DATA)
-#define NFS4_EXECUTE_MODE NFS4_ACE_EXECUTE
-#define NFS4_ANYONE_MODE (NFS4_ACE_READ_ATTRIBUTES | NFS4_ACE_READ_ACL | NFS4_ACE_SYNCHRONIZE)
-#define NFS4_OWNER_MODE (NFS4_ACE_WRITE_ATTRIBUTES | NFS4_ACE_WRITE_ACL)
+#define RICHACE_READ_MODE (RICHACE_READ_DATA)
+#define RICHACE_WRITE_MODE (RICHACE_WRITE_DATA | RICHACE_APPEND_DATA)
+#define RICHACE_EXECUTE_MODE RICHACE_EXECUTE
+#define RICHACE_ANYONE_MODE (RICHACE_READ_ATTRIBUTES | RICHACE_READ_ACL | RICHACE_SYNCHRONIZE)
+#define RICHACE_OWNER_MODE (RICHACE_WRITE_ATTRIBUTES | RICHACE_WRITE_ACL)
 
 /* flags used to simulate posix default ACLs */
-#define NFS4_INHERITANCE_FLAGS (NFS4_ACE_FILE_INHERIT_ACE \
-		| NFS4_ACE_DIRECTORY_INHERIT_ACE)
-
-#define NFS4_SUPPORTED_FLAGS (NFS4_INHERITANCE_FLAGS \
-		| NFS4_ACE_INHERIT_ONLY_ACE \
-		| NFS4_ACE_IDENTIFIER_GROUP)
+#define RICHACE_SUPPORTED_FLAGS (		\
+	RICHACE_FILE_INHERIT_ACE |		\
+	RICHACE_DIRECTORY_INHERIT_ACE |		\
+	RICHACE_INHERIT_ONLY_ACE |		\
+	RICHACE_IDENTIFIER_GROUP |		\
+	RICHACE_SPECIAL_WHO)
 
 static u32
 mask_from_posix(unsigned short perm, unsigned int flags)
 {
-	int mask = NFS4_ANYONE_MODE;
+	int mask = RICHACE_ANYONE_MODE;
 
-	if (flags & NFS4_ACL_OWNER)
-		mask |= NFS4_OWNER_MODE;
+	if (flags & FLAG_OWNER)
+		mask |= RICHACE_OWNER_MODE;
 	if (perm & ACL_READ)
-		mask |= NFS4_READ_MODE;
+		mask |= RICHACE_READ_MODE;
 	if (perm & ACL_WRITE)
-		mask |= NFS4_WRITE_MODE;
-	if ((perm & ACL_WRITE) && (flags & NFS4_ACL_DIR))
-		mask |= NFS4_ACE_DELETE_CHILD;
+		mask |= RICHACE_WRITE_MODE;
+	if ((perm & ACL_WRITE) && (flags & FLAG_DIRECTORY))
+		mask |= RICHACE_DELETE_CHILD;
 	if (perm & ACL_EXECUTE)
-		mask |= NFS4_EXECUTE_MODE;
+		mask |= RICHACE_EXECUTE_MODE;
 	return mask;
 }
 
@@ -84,13 +87,13 @@ deny_mask_from_posix(unsigned short perm, u32 flags)
 	u32 mask = 0;
 
 	if (perm & ACL_READ)
-		mask |= NFS4_READ_MODE;
+		mask |= RICHACE_READ_MODE;
 	if (perm & ACL_WRITE)
-		mask |= NFS4_WRITE_MODE;
-	if ((perm & ACL_WRITE) && (flags & NFS4_ACL_DIR))
-		mask |= NFS4_ACE_DELETE_CHILD;
+		mask |= RICHACE_WRITE_MODE;
+	if ((perm & ACL_WRITE) && (flags & FLAG_DIRECTORY))
+		mask |= RICHACE_DELETE_CHILD;
 	if (perm & ACL_EXECUTE)
-		mask |= NFS4_EXECUTE_MODE;
+		mask |= RICHACE_EXECUTE_MODE;
 	return mask;
 }
 
@@ -106,32 +109,33 @@ deny_mask_from_posix(unsigned short perm, u32 flags)
 static void
 low_mode_from_nfs4(u32 perm, unsigned short *mode, unsigned int flags)
 {
-	u32 write_mode = NFS4_WRITE_MODE;
+	u32 write_mode = RICHACE_WRITE_MODE;
 
-	if (flags & NFS4_ACL_DIR)
-		write_mode |= NFS4_ACE_DELETE_CHILD;
+	if (flags & FLAG_DIRECTORY)
+		write_mode |= RICHACE_DELETE_CHILD;
 	*mode = 0;
-	if ((perm & NFS4_READ_MODE) == NFS4_READ_MODE)
+	if ((perm & RICHACE_READ_MODE) == RICHACE_READ_MODE)
 		*mode |= ACL_READ;
 	if ((perm & write_mode) == write_mode)
 		*mode |= ACL_WRITE;
-	if ((perm & NFS4_EXECUTE_MODE) == NFS4_EXECUTE_MODE)
+	if ((perm & RICHACE_EXECUTE_MODE) == RICHACE_EXECUTE_MODE)
 		*mode |= ACL_EXECUTE;
 }
 
-static short ace2type(struct nfs4_ace *);
-static void _posix_to_nfsv4_one(struct posix_acl *, struct nfs4_acl *,
+static short ace2type(struct richace *);
+static void _posix_to_richacl_one(struct posix_acl *, struct richacl_alloc *,
 				unsigned int);
 
 int
-nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
-		struct nfs4_acl **acl)
+nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
+	      struct richacl **acl)
 {
 	struct inode *inode = d_inode(dentry);
 	int error = 0;
 	struct posix_acl *pacl = NULL, *dpacl = NULL;
+	struct richacl_alloc alloc;
 	unsigned int flags = 0;
-	int size = 0;
+	int count;
 
 	pacl = get_acl(inode, ACL_TYPE_ACCESS);
 	if (!pacl)
@@ -141,10 +145,10 @@ nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
 		return PTR_ERR(pacl);
 
 	/* allocate for worst case: one (deny, allow) pair each: */
-	size += 2 * pacl->a_count;
+	count = 2 * pacl->a_count;
 
 	if (S_ISDIR(inode->i_mode)) {
-		flags = NFS4_ACL_DIR;
+		flags = FLAG_DIRECTORY;
 		dpacl = get_acl(inode, ACL_TYPE_DEFAULT);
 		if (IS_ERR(dpacl)) {
 			error = PTR_ERR(dpacl);
@@ -152,20 +156,20 @@ nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
 		}
 
 		if (dpacl)
-			size += 2 * dpacl->a_count;
+			count += 2 * dpacl->a_count;
 	}
 
-	*acl = kmalloc(nfs4_acl_bytes(size), GFP_KERNEL);
-	if (*acl == NULL) {
+	if (!richacl_prepare(&alloc, count)) {
 		error = -ENOMEM;
 		goto out;
 	}
-	(*acl)->naces = 0;
 
-	_posix_to_nfsv4_one(pacl, *acl, flags & ~NFS4_ACL_TYPE_DEFAULT);
+	_posix_to_richacl_one(pacl, &alloc, flags);
 
 	if (dpacl)
-		_posix_to_nfsv4_one(dpacl, *acl, flags | NFS4_ACL_TYPE_DEFAULT);
+		_posix_to_richacl_one(dpacl, &alloc, flags | FLAG_DEFAULT_ACL);
+
+	*acl = alloc.acl;
 
 out:
 	posix_acl_release(dpacl);
@@ -228,21 +232,22 @@ summarize_posix_acl(struct posix_acl *acl, struct posix_acl_summary *pas)
 
 /* We assume the acl has been verified with posix_acl_valid. */
 static void
-_posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
-						unsigned int flags)
+_posix_to_richacl_one(struct posix_acl *pacl, struct richacl_alloc *alloc,
+		unsigned int flags)
 {
 	struct posix_acl_entry *pa, *group_owner_entry;
-	struct nfs4_ace *ace;
+	struct richace *ace;
 	struct posix_acl_summary pas;
 	unsigned short deny;
-	int eflag = ((flags & NFS4_ACL_TYPE_DEFAULT) ?
-		NFS4_INHERITANCE_FLAGS | NFS4_ACE_INHERIT_ONLY_ACE : 0);
+	int e_flags = ((flags & FLAG_DEFAULT_ACL) ?
+		       (RICHACE_FILE_INHERIT_ACE |
+		        RICHACE_DIRECTORY_INHERIT_ACE |
+		        RICHACE_INHERIT_ONLY_ACE) : 0);
 
 	BUG_ON(pacl->a_count < 3);
 	summarize_posix_acl(pacl, &pas);
 
 	pa = pacl->a_entries;
-	ace = acl->aces + acl->naces;
 
 	/* We could deny everything not granted by the owner: */
 	deny = ~pas.owner;
@@ -252,42 +257,35 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
 	 */
 	deny &= pas.users | pas.group | pas.groups | pas.other;
 	if (deny) {
-		ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
-		ace->flag = eflag;
-		ace->access_mask = deny_mask_from_posix(deny, flags);
-		ace->whotype = NFS4_ACL_WHO_OWNER;
-		ace++;
-		acl->naces++;
+		ace = richacl_append_entry(alloc);
+		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+		ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
+		ace->e_mask = deny_mask_from_posix(deny, flags);
+		ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
 	}
 
-	ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
-	ace->flag = eflag;
-	ace->access_mask = mask_from_posix(pa->e_perm, flags | NFS4_ACL_OWNER);
-	ace->whotype = NFS4_ACL_WHO_OWNER;
-	ace++;
-	acl->naces++;
+	ace = richacl_append_entry(alloc);
+	ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+	ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
+	ace->e_mask = mask_from_posix(pa->e_perm, flags | FLAG_OWNER);
+	ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
 	pa++;
 
 	while (pa->e_tag == ACL_USER) {
 		deny = ~(pa->e_perm & pas.mask);
 		deny &= pas.groups | pas.group | pas.other;
 		if (deny) {
-			ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
-			ace->flag = eflag;
-			ace->access_mask = deny_mask_from_posix(deny, flags);
-			ace->whotype = NFS4_ACL_WHO_NAMED;
-			ace->who_uid = pa->e_uid;
-			ace++;
-			acl->naces++;
+			ace = richacl_append_entry(alloc);
+			ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+			ace->e_flags = e_flags;
+			ace->e_mask = deny_mask_from_posix(deny, flags);
+			ace->e_id.uid = pa->e_uid;
 		}
-		ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
-		ace->flag = eflag;
-		ace->access_mask = mask_from_posix(pa->e_perm & pas.mask,
-						   flags);
-		ace->whotype = NFS4_ACL_WHO_NAMED;
-		ace->who_uid = pa->e_uid;
-		ace++;
-		acl->naces++;
+		ace = richacl_append_entry(alloc);
+		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+		ace->e_flags = e_flags;
+		ace->e_mask = mask_from_posix(pa->e_perm & pas.mask, flags);
+		ace->e_id.uid = pa->e_uid;
 		pa++;
 	}
 
@@ -298,23 +296,19 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
 
 	group_owner_entry = pa;
 
-	ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
-	ace->flag = eflag;
-	ace->access_mask = mask_from_posix(pas.group, flags);
-	ace->whotype = NFS4_ACL_WHO_GROUP;
-	ace++;
-	acl->naces++;
+	ace = richacl_append_entry(alloc);
+	ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+	ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
+	ace->e_mask = mask_from_posix(pas.group, flags);
+	ace->e_id.special = RICHACE_GROUP_SPECIAL_ID;
 	pa++;
 
 	while (pa->e_tag == ACL_GROUP) {
-		ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
-		ace->flag = eflag | NFS4_ACE_IDENTIFIER_GROUP;
-		ace->access_mask = mask_from_posix(pa->e_perm & pas.mask,
-						   flags);
-		ace->whotype = NFS4_ACL_WHO_NAMED;
-		ace->who_gid = pa->e_gid;
-		ace++;
-		acl->naces++;
+		ace = richacl_append_entry(alloc);
+		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+		ace->e_flags = e_flags | RICHACE_IDENTIFIER_GROUP;
+		ace->e_mask = mask_from_posix(pa->e_perm & pas.mask, flags);
+		ace->e_id.gid = pa->e_gid;
 		pa++;
 	}
 
@@ -324,12 +318,11 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
 
 	deny = ~pas.group & pas.other;
 	if (deny) {
-		ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
-		ace->flag = eflag;
-		ace->access_mask = deny_mask_from_posix(deny, flags);
-		ace->whotype = NFS4_ACL_WHO_GROUP;
-		ace++;
-		acl->naces++;
+		ace = richacl_append_entry(alloc);
+		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+		ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
+		ace->e_mask = deny_mask_from_posix(deny, flags);
+		ace->e_id.special = RICHACE_GROUP_SPECIAL_ID;
 	}
 	pa++;
 
@@ -337,24 +330,22 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
 		deny = ~(pa->e_perm & pas.mask);
 		deny &= pas.other;
 		if (deny) {
-			ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
-			ace->flag = eflag | NFS4_ACE_IDENTIFIER_GROUP;
-			ace->access_mask = deny_mask_from_posix(deny, flags);
-			ace->whotype = NFS4_ACL_WHO_NAMED;
-			ace->who_gid = pa->e_gid;
-			ace++;
-			acl->naces++;
+			ace = richacl_append_entry(alloc);
+			ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+			ace->e_flags = e_flags | RICHACE_IDENTIFIER_GROUP;
+			ace->e_mask = deny_mask_from_posix(deny, flags);
+			ace->e_id.gid = pa->e_gid;
 		}
 		pa++;
 	}
 
 	if (pa->e_tag == ACL_MASK)
 		pa++;
-	ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
-	ace->flag = eflag;
-	ace->access_mask = mask_from_posix(pa->e_perm, flags);
-	ace->whotype = NFS4_ACL_WHO_EVERYONE;
-	acl->naces++;
+	ace = richacl_append_entry(alloc);
+	ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+	ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
+	ace->e_mask = mask_from_posix(pa->e_perm, flags);
+	ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
 }
 
 static bool
@@ -498,7 +489,7 @@ posix_state_to_acl(struct posix_acl_state *state, unsigned int flags)
 	 * and effective cases: when there are no inheritable ACEs,
 	 * calls ->set_acl with a NULL ACL structure.
 	 */
-	if (state->empty && (flags & NFS4_ACL_TYPE_DEFAULT))
+	if (state->empty && (flags & FLAG_DEFAULT_ACL))
 		return NULL;
 
 	/*
@@ -617,24 +608,24 @@ static void allow_bits_array(struct posix_ace_state_array *a, u32 mask)
 }
 
 static void process_one_v4_ace(struct posix_acl_state *state,
-				struct nfs4_ace *ace)
+				struct richace *ace)
 {
-	u32 mask = ace->access_mask;
+	u32 mask = ace->e_mask;
 	int i;
 
 	state->empty = 0;
 
 	switch (ace2type(ace)) {
 	case ACL_USER_OBJ:
-		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
+		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
 			allow_bits(&state->owner, mask);
 		} else {
 			deny_bits(&state->owner, mask);
 		}
 		break;
 	case ACL_USER:
-		i = find_uid(state, ace->who_uid);
-		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
+		i = find_uid(state, ace->e_id.uid);
+		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
 			allow_bits(&state->users->aces[i].perms, mask);
 		} else {
 			deny_bits(&state->users->aces[i].perms, mask);
@@ -643,7 +634,7 @@ static void process_one_v4_ace(struct posix_acl_state *state,
 		}
 		break;
 	case ACL_GROUP_OBJ:
-		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
+		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
 			allow_bits(&state->group, mask);
 		} else {
 			deny_bits(&state->group, mask);
@@ -655,8 +646,8 @@ static void process_one_v4_ace(struct posix_acl_state *state,
 		}
 		break;
 	case ACL_GROUP:
-		i = find_gid(state, ace->who_gid);
-		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
+		i = find_gid(state, ace->e_id.gid);
+		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
 			allow_bits(&state->groups->aces[i].perms, mask);
 		} else {
 			deny_bits(&state->groups->aces[i].perms, mask);
@@ -669,7 +660,7 @@ static void process_one_v4_ace(struct posix_acl_state *state,
 		}
 		break;
 	case ACL_OTHER:
-		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
+		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
 			allow_bits(&state->owner, mask);
 			allow_bits(&state->group, mask);
 			allow_bits(&state->other, mask);
@@ -687,32 +678,33 @@ static void process_one_v4_ace(struct posix_acl_state *state,
 	}
 }
 
-static int nfs4_acl_nfsv4_to_posix(struct nfs4_acl *acl,
+static int nfs4_richacl_to_posix(struct richacl *acl,
 		struct posix_acl **pacl, struct posix_acl **dpacl,
 		unsigned int flags)
 {
 	struct posix_acl_state effective_acl_state, default_acl_state;
-	struct nfs4_ace *ace;
+	struct richace *ace;
 	int ret;
 
-	ret = init_state(&effective_acl_state, acl->naces);
+	ret = init_state(&effective_acl_state, acl->a_count);
 	if (ret)
 		return ret;
-	ret = init_state(&default_acl_state, acl->naces);
+	ret = init_state(&default_acl_state, acl->a_count);
 	if (ret)
 		goto out_estate;
 	ret = -EINVAL;
-	for (ace = acl->aces; ace < acl->aces + acl->naces; ace++) {
-		if (ace->type != NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE &&
-		    ace->type != NFS4_ACE_ACCESS_DENIED_ACE_TYPE)
+	richacl_for_each_entry(ace, acl) {
+		if (ace->e_type != RICHACE_ACCESS_ALLOWED_ACE_TYPE &&
+		    ace->e_type != RICHACE_ACCESS_DENIED_ACE_TYPE)
 			goto out_dstate;
-		if (ace->flag & ~NFS4_SUPPORTED_FLAGS)
+		if (ace->e_flags & ~RICHACE_SUPPORTED_FLAGS)
 			goto out_dstate;
-		if ((ace->flag & NFS4_INHERITANCE_FLAGS) == 0) {
+		if ((ace->e_flags & (RICHACE_FILE_INHERIT_ACE |
+				     RICHACE_DIRECTORY_INHERIT_ACE)) == 0) {
 			process_one_v4_ace(&effective_acl_state, ace);
 			continue;
 		}
-		if (!(flags & NFS4_ACL_DIR))
+		if (!(flags & FLAG_DIRECTORY))
 			goto out_dstate;
 		/*
 		 * Note that when only one of FILE_INHERIT or DIRECTORY_INHERIT
@@ -721,7 +713,7 @@ static int nfs4_acl_nfsv4_to_posix(struct nfs4_acl *acl,
 		 */
 		process_one_v4_ace(&default_acl_state, ace);
 
-		if (!(ace->flag & NFS4_ACE_INHERIT_ONLY_ACE))
+		if (!(ace->e_flags & RICHACE_INHERIT_ONLY_ACE))
 			process_one_v4_ace(&effective_acl_state, ace);
 	}
 	*pacl = posix_state_to_acl(&effective_acl_state, flags);
@@ -731,7 +723,7 @@ static int nfs4_acl_nfsv4_to_posix(struct nfs4_acl *acl,
 		goto out_dstate;
 	}
 	*dpacl = posix_state_to_acl(&default_acl_state,
-						flags | NFS4_ACL_TYPE_DEFAULT);
+						flags | FLAG_DEFAULT_ACL);
 	if (IS_ERR(*dpacl)) {
 		ret = PTR_ERR(*dpacl);
 		*dpacl = NULL;
@@ -750,8 +742,7 @@ out_estate:
 }
 
 __be32
-nfsd4_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
-		struct nfs4_acl *acl)
+nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp, struct richacl *acl)
 {
 	__be32 error;
 	int host_error;
@@ -772,9 +763,9 @@ nfsd4_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
 		return nfserr_attrnotsupp;
 
 	if (S_ISDIR(inode->i_mode))
-		flags = NFS4_ACL_DIR;
+		flags = FLAG_DIRECTORY;
 
-	host_error = nfs4_acl_nfsv4_to_posix(acl, &pacl, &dpacl, flags);
+	host_error = nfs4_richacl_to_posix(acl, &pacl, &dpacl, flags);
 	if (host_error == -EINVAL)
 		return nfserr_attrnotsupp;
 	if (host_error < 0)
@@ -801,82 +792,62 @@ out_nfserr:
 
 
 static short
-ace2type(struct nfs4_ace *ace)
+ace2type(struct richace *ace)
 {
-	switch (ace->whotype) {
-		case NFS4_ACL_WHO_NAMED:
-			return (ace->flag & NFS4_ACE_IDENTIFIER_GROUP ?
-					ACL_GROUP : ACL_USER);
-		case NFS4_ACL_WHO_OWNER:
+	if (ace->e_flags & RICHACE_SPECIAL_WHO) {
+		switch (ace->e_id.special) {
+		case RICHACE_OWNER_SPECIAL_ID:
 			return ACL_USER_OBJ;
-		case NFS4_ACL_WHO_GROUP:
+		case RICHACE_GROUP_SPECIAL_ID:
 			return ACL_GROUP_OBJ;
-		case NFS4_ACL_WHO_EVERYONE:
+		case RICHACE_EVERYONE_SPECIAL_ID:
 			return ACL_OTHER;
+		default:
+			BUG();
+		}
 	}
-	BUG();
-	return -1;
-}
-
-/*
- * return the size of the struct nfs4_acl required to represent an acl
- * with @entries entries.
- */
-int nfs4_acl_bytes(int entries)
-{
-	return sizeof(struct nfs4_acl) + entries * sizeof(struct nfs4_ace);
+	return ace->e_flags & RICHACE_IDENTIFIER_GROUP ? ACL_GROUP : ACL_USER;
 }
 
-static struct {
-	char *string;
-	int   stringlen;
-	int type;
-} s2t_map[] = {
-	{
-		.string    = "OWNER@",
-		.stringlen = sizeof("OWNER@") - 1,
-		.type      = NFS4_ACL_WHO_OWNER,
-	},
-	{
-		.string    = "GROUP@",
-		.stringlen = sizeof("GROUP@") - 1,
-		.type      = NFS4_ACL_WHO_GROUP,
-	},
-	{
-		.string    = "EVERYONE@",
-		.stringlen = sizeof("EVERYONE@") - 1,
-		.type      = NFS4_ACL_WHO_EVERYONE,
-	},
-};
-
-int
-nfs4_acl_get_whotype(char *p, u32 len)
+__be32 nfsd4_decode_ace_who(struct richace *ace, struct svc_rqst *rqstp,
+			    char *who, u32 len)
 {
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(s2t_map); i++) {
-		if (s2t_map[i].stringlen == len &&
-				0 == memcmp(s2t_map[i].string, p, len))
-			return s2t_map[i].type;
+	int special_id;
+
+	special_id = nfs4acl_who_to_special_id(who, len);
+	if (special_id >= 0) {
+		ace->e_flags |= RICHACE_SPECIAL_WHO;
+		ace->e_flags &= ~RICHACE_IDENTIFIER_GROUP;
+		ace->e_id.special = special_id;
+		return nfs_ok;
 	}
-	return NFS4_ACL_WHO_NAMED;
+	if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
+		return nfsd_map_name_to_gid(rqstp, who, len, &ace->e_id.gid);
+	else
+		return nfsd_map_name_to_uid(rqstp, who, len, &ace->e_id.uid);
 }
 
-__be32 nfs4_acl_write_who(struct xdr_stream *xdr, int who)
+__be32 nfsd4_encode_ace_who(struct xdr_stream *xdr, struct svc_rqst *rqstp,
+			    struct richace *ace)
 {
-	__be32 *p;
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(s2t_map); i++) {
-		if (s2t_map[i].type != who)
-			continue;
-		p = xdr_reserve_space(xdr, s2t_map[i].stringlen + 4);
+	if (ace->e_flags & RICHACE_SPECIAL_WHO) {
+		unsigned int special_id = ace->e_id.special;
+		const char *who;
+		unsigned int len;
+		__be32 *p;
+
+		if (!nfs4acl_special_id_to_who(special_id, &who, &len)) {
+			WARN_ON_ONCE(1);
+			return nfserr_serverfault;
+		}
+		p = xdr_reserve_space(xdr, len + 4);
 		if (!p)
 			return nfserr_resource;
-		p = xdr_encode_opaque(p, s2t_map[i].string,
-					s2t_map[i].stringlen);
+		p = xdr_encode_opaque(p, who, len);
 		return 0;
 	}
-	WARN_ON_ONCE(1);
-	return nfserr_serverfault;
+	if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
+		return nfsd4_encode_group(xdr, rqstp, ace->e_id.gid);
+	else
+		return nfsd4_encode_user(xdr, rqstp, ace->e_id.uid);
 }
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 90cfda7..8c2cb16 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -159,12 +159,12 @@ is_create_with_attrs(struct nfsd4_open *open)
  * in the returned attr bitmap.
  */
 static void
-do_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
-		struct nfs4_acl *acl, u32 *bmval)
+do_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp, struct richacl *acl,
+	   u32 *bmval)
 {
 	__be32 status;
 
-	status = nfsd4_set_nfs4_acl(rqstp, fhp, acl);
+	status = nfsd4_set_acl(rqstp, fhp, acl);
 	if (status)
 		/*
 		 * We should probably fail the whole open at this point,
@@ -299,7 +299,7 @@ do_open_lookup(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, stru
 		goto out;
 
 	if (is_create_with_attrs(open) && open->op_acl != NULL)
-		do_set_nfs4_acl(rqstp, *resfh, open->op_acl, open->op_bmval);
+		do_set_acl(rqstp, *resfh, open->op_acl, open->op_bmval);
 
 	nfsd4_set_open_owner_reply_cache(cstate, open, *resfh);
 	accmode = NFSD_MAY_NOP;
@@ -674,8 +674,7 @@ nfsd4_create(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 		nfsd4_security_inode_setsecctx(&resfh, &create->cr_label, create->cr_bmval);
 
 	if (create->cr_acl != NULL)
-		do_set_nfs4_acl(rqstp, &resfh, create->cr_acl,
-				create->cr_bmval);
+		do_set_acl(rqstp, &resfh, create->cr_acl, create->cr_bmval);
 
 	fh_unlock(&cstate->current_fh);
 	set_change_info(&create->cr_cinfo, &cstate->current_fh);
@@ -940,8 +939,8 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 		goto out;
 
 	if (setattr->sa_acl != NULL)
-		status = nfsd4_set_nfs4_acl(rqstp, &cstate->current_fh,
-					    setattr->sa_acl);
+		status = nfsd4_set_acl(rqstp, &cstate->current_fh,
+				       setattr->sa_acl);
 	if (status)
 		goto out;
 	if (setattr->sa_label.len)
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 0768251..465f82a 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -303,7 +303,7 @@ nfsd4_decode_bitmap(struct nfsd4_compoundargs *argp, u32 *bmval)
 
 static __be32
 nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
-		   struct iattr *iattr, struct nfs4_acl **acl,
+		   struct iattr *iattr, struct richacl **acl,
 		   struct xdr_netobj *label)
 {
 	int expected_len, len = 0;
@@ -326,38 +326,31 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
 	}
 	if (bmval[0] & FATTR4_WORD0_ACL) {
 		u32 nace;
-		struct nfs4_ace *ace;
+		struct richace *ace;
 
 		READ_BUF(4); len += 4;
 		nace = be32_to_cpup(p++);
 
-		if (nace > NFS4_ACL_MAX)
+		if (nace > NFSD4_ACL_MAX)
 			return nfserr_fbig;
 
-		*acl = svcxdr_tmpalloc(argp, nfs4_acl_bytes(nace));
+		*acl = svcxdr_alloc_richacl(argp, nace);
 		if (*acl == NULL)
 			return nfserr_jukebox;
 
-		(*acl)->naces = nace;
-		for (ace = (*acl)->aces; ace < (*acl)->aces + nace; ace++) {
+		richacl_for_each_entry(ace, *acl) {
 			READ_BUF(16); len += 16;
-			ace->type = be32_to_cpup(p++);
-			ace->flag = be32_to_cpup(p++);
-			ace->access_mask = be32_to_cpup(p++);
+			ace->e_type = be32_to_cpup(p++);
+			ace->e_flags = be32_to_cpup(p++);
+			ace->e_mask = be32_to_cpup(p++);
+			if (ace->e_flags & RICHACE_SPECIAL_WHO)
+				return nfserr_inval;
 			dummy32 = be32_to_cpup(p++);
 			READ_BUF(dummy32);
 			len += XDR_QUADLEN(dummy32) << 2;
 			READMEM(buf, dummy32);
-			ace->whotype = nfs4_acl_get_whotype(buf, dummy32);
-			status = nfs_ok;
-			if (ace->whotype != NFS4_ACL_WHO_NAMED)
-				;
-			else if (ace->flag & NFS4_ACE_IDENTIFIER_GROUP)
-				status = nfsd_map_name_to_gid(argp->rqstp,
-						buf, dummy32, &ace->who_gid);
-			else
-				status = nfsd_map_name_to_uid(argp->rqstp,
-						buf, dummy32, &ace->who_uid);
+			status = nfsd4_decode_ace_who(ace, argp->rqstp,
+						      buf, dummy32);
 			if (status)
 				return status;
 		}
@@ -2147,18 +2140,6 @@ static u32 nfs4_file_type(umode_t mode)
 	};
 }
 
-static inline __be32
-nfsd4_encode_aclname(struct xdr_stream *xdr, struct svc_rqst *rqstp,
-		     struct nfs4_ace *ace)
-{
-	if (ace->whotype != NFS4_ACL_WHO_NAMED)
-		return nfs4_acl_write_who(xdr, ace->whotype);
-	else if (ace->flag & NFS4_ACE_IDENTIFIER_GROUP)
-		return nfsd4_encode_group(xdr, rqstp, ace->who_gid);
-	else
-		return nfsd4_encode_user(xdr, rqstp, ace->who_uid);
-}
-
 #define WORD0_ABSENT_FS_ATTRS (FATTR4_WORD0_FS_LOCATIONS | FATTR4_WORD0_FSID | \
 			      FATTR4_WORD0_RDATTR_ERROR)
 #define WORD1_ABSENT_FS_ATTRS FATTR4_WORD1_MOUNTED_ON_FILEID
@@ -2249,7 +2230,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 	u32 rdattr_err = 0;
 	__be32 status;
 	int err;
-	struct nfs4_acl *acl = NULL;
+	struct richacl *acl = NULL;
 	void *context = NULL;
 	int contextlen;
 	bool contextsupport = false;
@@ -2295,7 +2276,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 		fhp = tempfh;
 	}
 	if (bmval0 & FATTR4_WORD0_ACL) {
-		err = nfsd4_get_nfs4_acl(rqstp, dentry, &acl);
+		err = nfsd4_get_acl(rqstp, dentry, &acl);
 		if (err == -EOPNOTSUPP)
 			bmval0 &= ~FATTR4_WORD0_ACL;
 		else if (err == -EINVAL) {
@@ -2469,7 +2450,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 		*p++ = cpu_to_be32(rdattr_err);
 	}
 	if (bmval0 & FATTR4_WORD0_ACL) {
-		struct nfs4_ace *ace;
+		struct richace *ace;
 
 		if (acl == NULL) {
 			p = xdr_reserve_space(xdr, 4);
@@ -2482,17 +2463,16 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 		p = xdr_reserve_space(xdr, 4);
 		if (!p)
 			goto out_resource;
-		*p++ = cpu_to_be32(acl->naces);
+		*p++ = cpu_to_be32(acl->a_count);
 
-		for (ace = acl->aces; ace < acl->aces + acl->naces; ace++) {
+		richacl_for_each_entry(ace, acl) {
 			p = xdr_reserve_space(xdr, 4*3);
 			if (!p)
 				goto out_resource;
-			*p++ = cpu_to_be32(ace->type);
-			*p++ = cpu_to_be32(ace->flag);
-			*p++ = cpu_to_be32(ace->access_mask &
-							NFS4_ACE_MASK_ALL);
-			status = nfsd4_encode_aclname(xdr, rqstp, ace);
+			*p++ = cpu_to_be32(ace->e_type);
+			*p++ = cpu_to_be32(ace->e_flags & ~RICHACE_SPECIAL_WHO);
+			*p++ = cpu_to_be32(ace->e_mask & NFS4_ACE_MASK_ALL);
+			status = nfsd4_encode_ace_who(xdr, rqstp, ace);
 			if (status)
 				goto out;
 		}
@@ -2755,7 +2735,7 @@ out:
 	if (context)
 		security_release_secctx(context, contextlen);
 #endif /* CONFIG_NFSD_V4_SECURITY_LABEL */
-	kfree(acl);
+	richacl_put(acl);
 	if (tempfh) {
 		fh_put(tempfh);
 		kfree(tempfh);
diff --git a/fs/nfsd/xdr4.h b/fs/nfsd/xdr4.h
index b698585..c311066 100644
--- a/fs/nfsd/xdr4.h
+++ b/fs/nfsd/xdr4.h
@@ -118,7 +118,7 @@ struct nfsd4_create {
 	u32		cr_bmval[3];        /* request */
 	struct iattr	cr_iattr;           /* request */
 	struct nfsd4_change_info  cr_cinfo; /* response */
-	struct nfs4_acl *cr_acl;
+	struct richacl *cr_acl;
 	struct xdr_netobj cr_label;
 };
 #define cr_datalen	u.link.datalen
@@ -248,7 +248,7 @@ struct nfsd4_open {
 	struct nfs4_file *op_file;          /* used during processing */
 	struct nfs4_ol_stateid *op_stp;	    /* used during processing */
 	struct nfs4_clnt_odstate *op_odstate; /* used during processing */
-	struct nfs4_acl *op_acl;
+	struct richacl *op_acl;
 	struct xdr_netobj op_label;
 };
 
@@ -332,7 +332,7 @@ struct nfsd4_setattr {
 	stateid_t	sa_stateid;         /* request */
 	u32		sa_bmval[3];        /* request */
 	struct iattr	sa_iattr;           /* request */
-	struct nfs4_acl *sa_acl;
+	struct richacl *sa_acl;
 	struct xdr_netobj sa_label;
 };
 
diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h
index b8e72aa..992ddc4 100644
--- a/include/linux/nfs4.h
+++ b/include/linux/nfs4.h
@@ -16,29 +16,6 @@
 #include <linux/uidgid.h>
 #include <uapi/linux/nfs4.h>
 
-enum nfs4_acl_whotype {
-	NFS4_ACL_WHO_NAMED = 0,
-	NFS4_ACL_WHO_OWNER,
-	NFS4_ACL_WHO_GROUP,
-	NFS4_ACL_WHO_EVERYONE,
-};
-
-struct nfs4_ace {
-	uint32_t	type;
-	uint32_t	flag;
-	uint32_t	access_mask;
-	int		whotype;
-	union {
-		kuid_t	who_uid;
-		kgid_t	who_gid;
-	};
-};
-
-struct nfs4_acl {
-	uint32_t	naces;
-	struct nfs4_ace	aces[0];
-};
-
 #define NFS4_MAXLABELLEN	2048
 
 struct nfs4_label {
diff --git a/include/linux/nfs4acl.h b/include/linux/nfs4acl.h
new file mode 100644
index 0000000..db9f9a6
--- /dev/null
+++ b/include/linux/nfs4acl.h
@@ -0,0 +1,7 @@
+#ifndef __LINUX_NFS4ACL_H
+#define __LINUX_NFS4ACL_H
+
+int nfs4acl_who_to_special_id(const char *, u32);
+bool nfs4acl_special_id_to_who(unsigned int, const char **, unsigned int *);
+
+#endif
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 29/41] nfsd: Use richacls as internal acl representation
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

When converting from NFSv4 ACLs to POSIX ACLs, nfsd so far was using
struct nfs4_acl as its internal representation. This representation is a
subset of richacls, so get rid of struct nfs4_acl. Richacls even have a
more compact in-memory representation, so a few more ACL entries can
easily be supported.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/Kconfig              |   6 +
 fs/nfs_common/Makefile  |   1 +
 fs/nfs_common/nfs4acl.c |  44 ++++++
 fs/nfsd/Kconfig         |   1 +
 fs/nfsd/acl.h           |  24 ++--
 fs/nfsd/nfs4acl.c       | 367 ++++++++++++++++++++++--------------------------
 fs/nfsd/nfs4proc.c      |  15 +-
 fs/nfsd/nfs4xdr.c       |  64 +++------
 fs/nfsd/xdr4.h          |   6 +-
 include/linux/nfs4.h    |  23 ---
 include/linux/nfs4acl.h |   7 +
 11 files changed, 273 insertions(+), 285 deletions(-)
 create mode 100644 fs/nfs_common/nfs4acl.c
 create mode 100644 include/linux/nfs4acl.h

diff --git a/fs/Kconfig b/fs/Kconfig
index 3e09c06..dd3f2d6 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -268,6 +268,12 @@ config NFS_COMMON
 	depends on NFSD || NFS_FS || LOCKD
 	default y
 
+config NFS_RICHACL
+	bool
+	depends on NFSD_V4 || NFS_V4
+	select FS_RICHACL
+	default y
+
 source "net/sunrpc/Kconfig"
 source "fs/ceph/Kconfig"
 source "fs/cifs/Kconfig"
diff --git a/fs/nfs_common/Makefile b/fs/nfs_common/Makefile
index d153ca3..e055139 100644
--- a/fs/nfs_common/Makefile
+++ b/fs/nfs_common/Makefile
@@ -4,5 +4,6 @@
 
 obj-$(CONFIG_NFS_ACL_SUPPORT) += nfs_acl.o
 nfs_acl-objs := nfsacl.o
+obj-$(CONFIG_NFS_RICHACL) += nfs4acl.o
 
 obj-$(CONFIG_GRACE_PERIOD) += grace.o
diff --git a/fs/nfs_common/nfs4acl.c b/fs/nfs_common/nfs4acl.c
new file mode 100644
index 0000000..02df064
--- /dev/null
+++ b/fs/nfs_common/nfs4acl.c
@@ -0,0 +1,44 @@
+#include <linux/fs.h>
+#include <linux/richacl.h>
+#include <linux/nfs4acl.h>
+
+static struct special_id {
+	char *who;
+	int   len;
+} special_who_map[] = {
+	[RICHACE_OWNER_SPECIAL_ID] = {
+		.who = "OWNER@",
+		.len = sizeof("OWNER@") - 1 },
+	[RICHACE_GROUP_SPECIAL_ID] = {
+		.who = "GROUP@",
+		.len = sizeof("GROUP@") - 1 },
+	[RICHACE_EVERYONE_SPECIAL_ID] = {
+		.who = "EVERYONE@",
+		.len = sizeof("EVERYONE@") - 1 }
+};
+
+int nfs4acl_who_to_special_id(const char *who, u32 len)
+{
+	int n;
+
+	for (n = 0; n < ARRAY_SIZE(special_who_map); n++) {
+		if (len == special_who_map[n].len &&
+		    !memcmp(who, special_who_map[n].who, len))
+			return n;
+	}
+	return -1;
+}
+EXPORT_SYMBOL(nfs4acl_who_to_special_id);
+
+bool nfs4acl_special_id_to_who(unsigned int special_who,
+			       const char **who, unsigned int *len)
+{
+	struct special_id *special = &special_who_map[special_who];
+
+	if (special_who > ARRAY_SIZE(special_who_map) || !special->len)
+		return false;
+	*who = special->who;
+	*len = special->len;
+	return true;
+}
+EXPORT_SYMBOL(nfs4acl_special_id_to_who);
diff --git a/fs/nfsd/Kconfig b/fs/nfsd/Kconfig
index a0b77fc..811379a 100644
--- a/fs/nfsd/Kconfig
+++ b/fs/nfsd/Kconfig
@@ -70,6 +70,7 @@ config NFSD_V4
 	depends on NFSD && PROC_FS
 	select NFSD_V3
 	select FS_POSIX_ACL
+	select FS_RICHACL
 	select SUNRPC_GSS
 	select CRYPTO
 	select GRACE_PERIOD
diff --git a/fs/nfsd/acl.h b/fs/nfsd/acl.h
index 4cd7c69..1c5deb5 100644
--- a/fs/nfsd/acl.h
+++ b/fs/nfsd/acl.h
@@ -35,25 +35,27 @@
 #ifndef LINUX_NFS4_ACL_H
 #define LINUX_NFS4_ACL_H
 
-struct nfs4_acl;
+struct richacl;
+struct richace;
 struct svc_fh;
 struct svc_rqst;
 
 /*
  * Maximum ACL we'll accept from a client; chosen (somewhat
  * arbitrarily) so that kmalloc'ing the ACL shouldn't require a
- * high-order allocation.  This allows 204 ACEs on x86_64:
+ * high-order allocation.  This allows 339 ACEs on x86_64:
  */
-#define NFS4_ACL_MAX ((PAGE_SIZE - sizeof(struct nfs4_acl)) \
-			/ sizeof(struct nfs4_ace))
+#define NFSD4_ACL_MAX ((PAGE_SIZE - sizeof(struct richacl)) \
+			/ sizeof(struct richace))
 
-int nfs4_acl_bytes(int entries);
-int nfs4_acl_get_whotype(char *, u32);
-__be32 nfs4_acl_write_who(struct xdr_stream *xdr, int who);
+__be32 nfsd4_decode_ace_who(struct richace *ace, struct svc_rqst *rqstp,
+			    char *who, u32 len);
+__be32 nfsd4_encode_ace_who(struct xdr_stream *xdr, struct svc_rqst *rqstp,
+			    struct richace *ace);
 
-int nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
-		struct nfs4_acl **acl);
-__be32 nfsd4_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
-		struct nfs4_acl *acl);
+int nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
+		  struct richacl **acl);
+__be32 nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
+		     struct richacl *acl);
 
 #endif /* LINUX_NFS4_ACL_H */
diff --git a/fs/nfsd/nfs4acl.c b/fs/nfsd/nfs4acl.c
index eb5accf..582f772 100644
--- a/fs/nfsd/nfs4acl.c
+++ b/fs/nfsd/nfs4acl.c
@@ -36,45 +36,48 @@
 
 #include <linux/slab.h>
 #include <linux/nfs_fs.h>
+#include <linux/richacl_compat.h>
+#include <linux/nfs4acl.h>
 #include "nfsfh.h"
 #include "nfsd.h"
+#include "idmap.h"
 #include "acl.h"
 #include "vfs.h"
 
-#define NFS4_ACL_TYPE_DEFAULT	0x01
-#define NFS4_ACL_DIR		0x02
-#define NFS4_ACL_OWNER		0x04
+#define FLAG_DEFAULT_ACL	0x01
+#define FLAG_DIRECTORY		0x02
+#define FLAG_OWNER		0x04
 
 /* mode bit translations: */
-#define NFS4_READ_MODE (NFS4_ACE_READ_DATA)
-#define NFS4_WRITE_MODE (NFS4_ACE_WRITE_DATA | NFS4_ACE_APPEND_DATA)
-#define NFS4_EXECUTE_MODE NFS4_ACE_EXECUTE
-#define NFS4_ANYONE_MODE (NFS4_ACE_READ_ATTRIBUTES | NFS4_ACE_READ_ACL | NFS4_ACE_SYNCHRONIZE)
-#define NFS4_OWNER_MODE (NFS4_ACE_WRITE_ATTRIBUTES | NFS4_ACE_WRITE_ACL)
+#define RICHACE_READ_MODE (RICHACE_READ_DATA)
+#define RICHACE_WRITE_MODE (RICHACE_WRITE_DATA | RICHACE_APPEND_DATA)
+#define RICHACE_EXECUTE_MODE RICHACE_EXECUTE
+#define RICHACE_ANYONE_MODE (RICHACE_READ_ATTRIBUTES | RICHACE_READ_ACL | RICHACE_SYNCHRONIZE)
+#define RICHACE_OWNER_MODE (RICHACE_WRITE_ATTRIBUTES | RICHACE_WRITE_ACL)
 
 /* flags used to simulate posix default ACLs */
-#define NFS4_INHERITANCE_FLAGS (NFS4_ACE_FILE_INHERIT_ACE \
-		| NFS4_ACE_DIRECTORY_INHERIT_ACE)
-
-#define NFS4_SUPPORTED_FLAGS (NFS4_INHERITANCE_FLAGS \
-		| NFS4_ACE_INHERIT_ONLY_ACE \
-		| NFS4_ACE_IDENTIFIER_GROUP)
+#define RICHACE_SUPPORTED_FLAGS (		\
+	RICHACE_FILE_INHERIT_ACE |		\
+	RICHACE_DIRECTORY_INHERIT_ACE |		\
+	RICHACE_INHERIT_ONLY_ACE |		\
+	RICHACE_IDENTIFIER_GROUP |		\
+	RICHACE_SPECIAL_WHO)
 
 static u32
 mask_from_posix(unsigned short perm, unsigned int flags)
 {
-	int mask = NFS4_ANYONE_MODE;
+	int mask = RICHACE_ANYONE_MODE;
 
-	if (flags & NFS4_ACL_OWNER)
-		mask |= NFS4_OWNER_MODE;
+	if (flags & FLAG_OWNER)
+		mask |= RICHACE_OWNER_MODE;
 	if (perm & ACL_READ)
-		mask |= NFS4_READ_MODE;
+		mask |= RICHACE_READ_MODE;
 	if (perm & ACL_WRITE)
-		mask |= NFS4_WRITE_MODE;
-	if ((perm & ACL_WRITE) && (flags & NFS4_ACL_DIR))
-		mask |= NFS4_ACE_DELETE_CHILD;
+		mask |= RICHACE_WRITE_MODE;
+	if ((perm & ACL_WRITE) && (flags & FLAG_DIRECTORY))
+		mask |= RICHACE_DELETE_CHILD;
 	if (perm & ACL_EXECUTE)
-		mask |= NFS4_EXECUTE_MODE;
+		mask |= RICHACE_EXECUTE_MODE;
 	return mask;
 }
 
@@ -84,13 +87,13 @@ deny_mask_from_posix(unsigned short perm, u32 flags)
 	u32 mask = 0;
 
 	if (perm & ACL_READ)
-		mask |= NFS4_READ_MODE;
+		mask |= RICHACE_READ_MODE;
 	if (perm & ACL_WRITE)
-		mask |= NFS4_WRITE_MODE;
-	if ((perm & ACL_WRITE) && (flags & NFS4_ACL_DIR))
-		mask |= NFS4_ACE_DELETE_CHILD;
+		mask |= RICHACE_WRITE_MODE;
+	if ((perm & ACL_WRITE) && (flags & FLAG_DIRECTORY))
+		mask |= RICHACE_DELETE_CHILD;
 	if (perm & ACL_EXECUTE)
-		mask |= NFS4_EXECUTE_MODE;
+		mask |= RICHACE_EXECUTE_MODE;
 	return mask;
 }
 
@@ -106,32 +109,33 @@ deny_mask_from_posix(unsigned short perm, u32 flags)
 static void
 low_mode_from_nfs4(u32 perm, unsigned short *mode, unsigned int flags)
 {
-	u32 write_mode = NFS4_WRITE_MODE;
+	u32 write_mode = RICHACE_WRITE_MODE;
 
-	if (flags & NFS4_ACL_DIR)
-		write_mode |= NFS4_ACE_DELETE_CHILD;
+	if (flags & FLAG_DIRECTORY)
+		write_mode |= RICHACE_DELETE_CHILD;
 	*mode = 0;
-	if ((perm & NFS4_READ_MODE) == NFS4_READ_MODE)
+	if ((perm & RICHACE_READ_MODE) == RICHACE_READ_MODE)
 		*mode |= ACL_READ;
 	if ((perm & write_mode) == write_mode)
 		*mode |= ACL_WRITE;
-	if ((perm & NFS4_EXECUTE_MODE) == NFS4_EXECUTE_MODE)
+	if ((perm & RICHACE_EXECUTE_MODE) == RICHACE_EXECUTE_MODE)
 		*mode |= ACL_EXECUTE;
 }
 
-static short ace2type(struct nfs4_ace *);
-static void _posix_to_nfsv4_one(struct posix_acl *, struct nfs4_acl *,
+static short ace2type(struct richace *);
+static void _posix_to_richacl_one(struct posix_acl *, struct richacl_alloc *,
 				unsigned int);
 
 int
-nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
-		struct nfs4_acl **acl)
+nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
+	      struct richacl **acl)
 {
 	struct inode *inode = d_inode(dentry);
 	int error = 0;
 	struct posix_acl *pacl = NULL, *dpacl = NULL;
+	struct richacl_alloc alloc;
 	unsigned int flags = 0;
-	int size = 0;
+	int count;
 
 	pacl = get_acl(inode, ACL_TYPE_ACCESS);
 	if (!pacl)
@@ -141,10 +145,10 @@ nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
 		return PTR_ERR(pacl);
 
 	/* allocate for worst case: one (deny, allow) pair each: */
-	size += 2 * pacl->a_count;
+	count = 2 * pacl->a_count;
 
 	if (S_ISDIR(inode->i_mode)) {
-		flags = NFS4_ACL_DIR;
+		flags = FLAG_DIRECTORY;
 		dpacl = get_acl(inode, ACL_TYPE_DEFAULT);
 		if (IS_ERR(dpacl)) {
 			error = PTR_ERR(dpacl);
@@ -152,20 +156,20 @@ nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
 		}
 
 		if (dpacl)
-			size += 2 * dpacl->a_count;
+			count += 2 * dpacl->a_count;
 	}
 
-	*acl = kmalloc(nfs4_acl_bytes(size), GFP_KERNEL);
-	if (*acl == NULL) {
+	if (!richacl_prepare(&alloc, count)) {
 		error = -ENOMEM;
 		goto out;
 	}
-	(*acl)->naces = 0;
 
-	_posix_to_nfsv4_one(pacl, *acl, flags & ~NFS4_ACL_TYPE_DEFAULT);
+	_posix_to_richacl_one(pacl, &alloc, flags);
 
 	if (dpacl)
-		_posix_to_nfsv4_one(dpacl, *acl, flags | NFS4_ACL_TYPE_DEFAULT);
+		_posix_to_richacl_one(dpacl, &alloc, flags | FLAG_DEFAULT_ACL);
+
+	*acl = alloc.acl;
 
 out:
 	posix_acl_release(dpacl);
@@ -228,21 +232,22 @@ summarize_posix_acl(struct posix_acl *acl, struct posix_acl_summary *pas)
 
 /* We assume the acl has been verified with posix_acl_valid. */
 static void
-_posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
-						unsigned int flags)
+_posix_to_richacl_one(struct posix_acl *pacl, struct richacl_alloc *alloc,
+		unsigned int flags)
 {
 	struct posix_acl_entry *pa, *group_owner_entry;
-	struct nfs4_ace *ace;
+	struct richace *ace;
 	struct posix_acl_summary pas;
 	unsigned short deny;
-	int eflag = ((flags & NFS4_ACL_TYPE_DEFAULT) ?
-		NFS4_INHERITANCE_FLAGS | NFS4_ACE_INHERIT_ONLY_ACE : 0);
+	int e_flags = ((flags & FLAG_DEFAULT_ACL) ?
+		       (RICHACE_FILE_INHERIT_ACE |
+		        RICHACE_DIRECTORY_INHERIT_ACE |
+		        RICHACE_INHERIT_ONLY_ACE) : 0);
 
 	BUG_ON(pacl->a_count < 3);
 	summarize_posix_acl(pacl, &pas);
 
 	pa = pacl->a_entries;
-	ace = acl->aces + acl->naces;
 
 	/* We could deny everything not granted by the owner: */
 	deny = ~pas.owner;
@@ -252,42 +257,35 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
 	 */
 	deny &= pas.users | pas.group | pas.groups | pas.other;
 	if (deny) {
-		ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
-		ace->flag = eflag;
-		ace->access_mask = deny_mask_from_posix(deny, flags);
-		ace->whotype = NFS4_ACL_WHO_OWNER;
-		ace++;
-		acl->naces++;
+		ace = richacl_append_entry(alloc);
+		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+		ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
+		ace->e_mask = deny_mask_from_posix(deny, flags);
+		ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
 	}
 
-	ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
-	ace->flag = eflag;
-	ace->access_mask = mask_from_posix(pa->e_perm, flags | NFS4_ACL_OWNER);
-	ace->whotype = NFS4_ACL_WHO_OWNER;
-	ace++;
-	acl->naces++;
+	ace = richacl_append_entry(alloc);
+	ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+	ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
+	ace->e_mask = mask_from_posix(pa->e_perm, flags | FLAG_OWNER);
+	ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
 	pa++;
 
 	while (pa->e_tag == ACL_USER) {
 		deny = ~(pa->e_perm & pas.mask);
 		deny &= pas.groups | pas.group | pas.other;
 		if (deny) {
-			ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
-			ace->flag = eflag;
-			ace->access_mask = deny_mask_from_posix(deny, flags);
-			ace->whotype = NFS4_ACL_WHO_NAMED;
-			ace->who_uid = pa->e_uid;
-			ace++;
-			acl->naces++;
+			ace = richacl_append_entry(alloc);
+			ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+			ace->e_flags = e_flags;
+			ace->e_mask = deny_mask_from_posix(deny, flags);
+			ace->e_id.uid = pa->e_uid;
 		}
-		ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
-		ace->flag = eflag;
-		ace->access_mask = mask_from_posix(pa->e_perm & pas.mask,
-						   flags);
-		ace->whotype = NFS4_ACL_WHO_NAMED;
-		ace->who_uid = pa->e_uid;
-		ace++;
-		acl->naces++;
+		ace = richacl_append_entry(alloc);
+		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+		ace->e_flags = e_flags;
+		ace->e_mask = mask_from_posix(pa->e_perm & pas.mask, flags);
+		ace->e_id.uid = pa->e_uid;
 		pa++;
 	}
 
@@ -298,23 +296,19 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
 
 	group_owner_entry = pa;
 
-	ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
-	ace->flag = eflag;
-	ace->access_mask = mask_from_posix(pas.group, flags);
-	ace->whotype = NFS4_ACL_WHO_GROUP;
-	ace++;
-	acl->naces++;
+	ace = richacl_append_entry(alloc);
+	ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+	ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
+	ace->e_mask = mask_from_posix(pas.group, flags);
+	ace->e_id.special = RICHACE_GROUP_SPECIAL_ID;
 	pa++;
 
 	while (pa->e_tag == ACL_GROUP) {
-		ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
-		ace->flag = eflag | NFS4_ACE_IDENTIFIER_GROUP;
-		ace->access_mask = mask_from_posix(pa->e_perm & pas.mask,
-						   flags);
-		ace->whotype = NFS4_ACL_WHO_NAMED;
-		ace->who_gid = pa->e_gid;
-		ace++;
-		acl->naces++;
+		ace = richacl_append_entry(alloc);
+		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+		ace->e_flags = e_flags | RICHACE_IDENTIFIER_GROUP;
+		ace->e_mask = mask_from_posix(pa->e_perm & pas.mask, flags);
+		ace->e_id.gid = pa->e_gid;
 		pa++;
 	}
 
@@ -324,12 +318,11 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
 
 	deny = ~pas.group & pas.other;
 	if (deny) {
-		ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
-		ace->flag = eflag;
-		ace->access_mask = deny_mask_from_posix(deny, flags);
-		ace->whotype = NFS4_ACL_WHO_GROUP;
-		ace++;
-		acl->naces++;
+		ace = richacl_append_entry(alloc);
+		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+		ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
+		ace->e_mask = deny_mask_from_posix(deny, flags);
+		ace->e_id.special = RICHACE_GROUP_SPECIAL_ID;
 	}
 	pa++;
 
@@ -337,24 +330,22 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
 		deny = ~(pa->e_perm & pas.mask);
 		deny &= pas.other;
 		if (deny) {
-			ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
-			ace->flag = eflag | NFS4_ACE_IDENTIFIER_GROUP;
-			ace->access_mask = deny_mask_from_posix(deny, flags);
-			ace->whotype = NFS4_ACL_WHO_NAMED;
-			ace->who_gid = pa->e_gid;
-			ace++;
-			acl->naces++;
+			ace = richacl_append_entry(alloc);
+			ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+			ace->e_flags = e_flags | RICHACE_IDENTIFIER_GROUP;
+			ace->e_mask = deny_mask_from_posix(deny, flags);
+			ace->e_id.gid = pa->e_gid;
 		}
 		pa++;
 	}
 
 	if (pa->e_tag == ACL_MASK)
 		pa++;
-	ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
-	ace->flag = eflag;
-	ace->access_mask = mask_from_posix(pa->e_perm, flags);
-	ace->whotype = NFS4_ACL_WHO_EVERYONE;
-	acl->naces++;
+	ace = richacl_append_entry(alloc);
+	ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+	ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
+	ace->e_mask = mask_from_posix(pa->e_perm, flags);
+	ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
 }
 
 static bool
@@ -498,7 +489,7 @@ posix_state_to_acl(struct posix_acl_state *state, unsigned int flags)
 	 * and effective cases: when there are no inheritable ACEs,
 	 * calls ->set_acl with a NULL ACL structure.
 	 */
-	if (state->empty && (flags & NFS4_ACL_TYPE_DEFAULT))
+	if (state->empty && (flags & FLAG_DEFAULT_ACL))
 		return NULL;
 
 	/*
@@ -617,24 +608,24 @@ static void allow_bits_array(struct posix_ace_state_array *a, u32 mask)
 }
 
 static void process_one_v4_ace(struct posix_acl_state *state,
-				struct nfs4_ace *ace)
+				struct richace *ace)
 {
-	u32 mask = ace->access_mask;
+	u32 mask = ace->e_mask;
 	int i;
 
 	state->empty = 0;
 
 	switch (ace2type(ace)) {
 	case ACL_USER_OBJ:
-		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
+		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
 			allow_bits(&state->owner, mask);
 		} else {
 			deny_bits(&state->owner, mask);
 		}
 		break;
 	case ACL_USER:
-		i = find_uid(state, ace->who_uid);
-		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
+		i = find_uid(state, ace->e_id.uid);
+		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
 			allow_bits(&state->users->aces[i].perms, mask);
 		} else {
 			deny_bits(&state->users->aces[i].perms, mask);
@@ -643,7 +634,7 @@ static void process_one_v4_ace(struct posix_acl_state *state,
 		}
 		break;
 	case ACL_GROUP_OBJ:
-		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
+		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
 			allow_bits(&state->group, mask);
 		} else {
 			deny_bits(&state->group, mask);
@@ -655,8 +646,8 @@ static void process_one_v4_ace(struct posix_acl_state *state,
 		}
 		break;
 	case ACL_GROUP:
-		i = find_gid(state, ace->who_gid);
-		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
+		i = find_gid(state, ace->e_id.gid);
+		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
 			allow_bits(&state->groups->aces[i].perms, mask);
 		} else {
 			deny_bits(&state->groups->aces[i].perms, mask);
@@ -669,7 +660,7 @@ static void process_one_v4_ace(struct posix_acl_state *state,
 		}
 		break;
 	case ACL_OTHER:
-		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
+		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
 			allow_bits(&state->owner, mask);
 			allow_bits(&state->group, mask);
 			allow_bits(&state->other, mask);
@@ -687,32 +678,33 @@ static void process_one_v4_ace(struct posix_acl_state *state,
 	}
 }
 
-static int nfs4_acl_nfsv4_to_posix(struct nfs4_acl *acl,
+static int nfs4_richacl_to_posix(struct richacl *acl,
 		struct posix_acl **pacl, struct posix_acl **dpacl,
 		unsigned int flags)
 {
 	struct posix_acl_state effective_acl_state, default_acl_state;
-	struct nfs4_ace *ace;
+	struct richace *ace;
 	int ret;
 
-	ret = init_state(&effective_acl_state, acl->naces);
+	ret = init_state(&effective_acl_state, acl->a_count);
 	if (ret)
 		return ret;
-	ret = init_state(&default_acl_state, acl->naces);
+	ret = init_state(&default_acl_state, acl->a_count);
 	if (ret)
 		goto out_estate;
 	ret = -EINVAL;
-	for (ace = acl->aces; ace < acl->aces + acl->naces; ace++) {
-		if (ace->type != NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE &&
-		    ace->type != NFS4_ACE_ACCESS_DENIED_ACE_TYPE)
+	richacl_for_each_entry(ace, acl) {
+		if (ace->e_type != RICHACE_ACCESS_ALLOWED_ACE_TYPE &&
+		    ace->e_type != RICHACE_ACCESS_DENIED_ACE_TYPE)
 			goto out_dstate;
-		if (ace->flag & ~NFS4_SUPPORTED_FLAGS)
+		if (ace->e_flags & ~RICHACE_SUPPORTED_FLAGS)
 			goto out_dstate;
-		if ((ace->flag & NFS4_INHERITANCE_FLAGS) == 0) {
+		if ((ace->e_flags & (RICHACE_FILE_INHERIT_ACE |
+				     RICHACE_DIRECTORY_INHERIT_ACE)) == 0) {
 			process_one_v4_ace(&effective_acl_state, ace);
 			continue;
 		}
-		if (!(flags & NFS4_ACL_DIR))
+		if (!(flags & FLAG_DIRECTORY))
 			goto out_dstate;
 		/*
 		 * Note that when only one of FILE_INHERIT or DIRECTORY_INHERIT
@@ -721,7 +713,7 @@ static int nfs4_acl_nfsv4_to_posix(struct nfs4_acl *acl,
 		 */
 		process_one_v4_ace(&default_acl_state, ace);
 
-		if (!(ace->flag & NFS4_ACE_INHERIT_ONLY_ACE))
+		if (!(ace->e_flags & RICHACE_INHERIT_ONLY_ACE))
 			process_one_v4_ace(&effective_acl_state, ace);
 	}
 	*pacl = posix_state_to_acl(&effective_acl_state, flags);
@@ -731,7 +723,7 @@ static int nfs4_acl_nfsv4_to_posix(struct nfs4_acl *acl,
 		goto out_dstate;
 	}
 	*dpacl = posix_state_to_acl(&default_acl_state,
-						flags | NFS4_ACL_TYPE_DEFAULT);
+						flags | FLAG_DEFAULT_ACL);
 	if (IS_ERR(*dpacl)) {
 		ret = PTR_ERR(*dpacl);
 		*dpacl = NULL;
@@ -750,8 +742,7 @@ out_estate:
 }
 
 __be32
-nfsd4_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
-		struct nfs4_acl *acl)
+nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp, struct richacl *acl)
 {
 	__be32 error;
 	int host_error;
@@ -772,9 +763,9 @@ nfsd4_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
 		return nfserr_attrnotsupp;
 
 	if (S_ISDIR(inode->i_mode))
-		flags = NFS4_ACL_DIR;
+		flags = FLAG_DIRECTORY;
 
-	host_error = nfs4_acl_nfsv4_to_posix(acl, &pacl, &dpacl, flags);
+	host_error = nfs4_richacl_to_posix(acl, &pacl, &dpacl, flags);
 	if (host_error == -EINVAL)
 		return nfserr_attrnotsupp;
 	if (host_error < 0)
@@ -801,82 +792,62 @@ out_nfserr:
 
 
 static short
-ace2type(struct nfs4_ace *ace)
+ace2type(struct richace *ace)
 {
-	switch (ace->whotype) {
-		case NFS4_ACL_WHO_NAMED:
-			return (ace->flag & NFS4_ACE_IDENTIFIER_GROUP ?
-					ACL_GROUP : ACL_USER);
-		case NFS4_ACL_WHO_OWNER:
+	if (ace->e_flags & RICHACE_SPECIAL_WHO) {
+		switch (ace->e_id.special) {
+		case RICHACE_OWNER_SPECIAL_ID:
 			return ACL_USER_OBJ;
-		case NFS4_ACL_WHO_GROUP:
+		case RICHACE_GROUP_SPECIAL_ID:
 			return ACL_GROUP_OBJ;
-		case NFS4_ACL_WHO_EVERYONE:
+		case RICHACE_EVERYONE_SPECIAL_ID:
 			return ACL_OTHER;
+		default:
+			BUG();
+		}
 	}
-	BUG();
-	return -1;
-}
-
-/*
- * return the size of the struct nfs4_acl required to represent an acl
- * with @entries entries.
- */
-int nfs4_acl_bytes(int entries)
-{
-	return sizeof(struct nfs4_acl) + entries * sizeof(struct nfs4_ace);
+	return ace->e_flags & RICHACE_IDENTIFIER_GROUP ? ACL_GROUP : ACL_USER;
 }
 
-static struct {
-	char *string;
-	int   stringlen;
-	int type;
-} s2t_map[] = {
-	{
-		.string    = "OWNER@",
-		.stringlen = sizeof("OWNER@") - 1,
-		.type      = NFS4_ACL_WHO_OWNER,
-	},
-	{
-		.string    = "GROUP@",
-		.stringlen = sizeof("GROUP@") - 1,
-		.type      = NFS4_ACL_WHO_GROUP,
-	},
-	{
-		.string    = "EVERYONE@",
-		.stringlen = sizeof("EVERYONE@") - 1,
-		.type      = NFS4_ACL_WHO_EVERYONE,
-	},
-};
-
-int
-nfs4_acl_get_whotype(char *p, u32 len)
+__be32 nfsd4_decode_ace_who(struct richace *ace, struct svc_rqst *rqstp,
+			    char *who, u32 len)
 {
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(s2t_map); i++) {
-		if (s2t_map[i].stringlen == len &&
-				0 == memcmp(s2t_map[i].string, p, len))
-			return s2t_map[i].type;
+	int special_id;
+
+	special_id = nfs4acl_who_to_special_id(who, len);
+	if (special_id >= 0) {
+		ace->e_flags |= RICHACE_SPECIAL_WHO;
+		ace->e_flags &= ~RICHACE_IDENTIFIER_GROUP;
+		ace->e_id.special = special_id;
+		return nfs_ok;
 	}
-	return NFS4_ACL_WHO_NAMED;
+	if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
+		return nfsd_map_name_to_gid(rqstp, who, len, &ace->e_id.gid);
+	else
+		return nfsd_map_name_to_uid(rqstp, who, len, &ace->e_id.uid);
 }
 
-__be32 nfs4_acl_write_who(struct xdr_stream *xdr, int who)
+__be32 nfsd4_encode_ace_who(struct xdr_stream *xdr, struct svc_rqst *rqstp,
+			    struct richace *ace)
 {
-	__be32 *p;
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(s2t_map); i++) {
-		if (s2t_map[i].type != who)
-			continue;
-		p = xdr_reserve_space(xdr, s2t_map[i].stringlen + 4);
+	if (ace->e_flags & RICHACE_SPECIAL_WHO) {
+		unsigned int special_id = ace->e_id.special;
+		const char *who;
+		unsigned int len;
+		__be32 *p;
+
+		if (!nfs4acl_special_id_to_who(special_id, &who, &len)) {
+			WARN_ON_ONCE(1);
+			return nfserr_serverfault;
+		}
+		p = xdr_reserve_space(xdr, len + 4);
 		if (!p)
 			return nfserr_resource;
-		p = xdr_encode_opaque(p, s2t_map[i].string,
-					s2t_map[i].stringlen);
+		p = xdr_encode_opaque(p, who, len);
 		return 0;
 	}
-	WARN_ON_ONCE(1);
-	return nfserr_serverfault;
+	if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
+		return nfsd4_encode_group(xdr, rqstp, ace->e_id.gid);
+	else
+		return nfsd4_encode_user(xdr, rqstp, ace->e_id.uid);
 }
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 90cfda7..8c2cb16 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -159,12 +159,12 @@ is_create_with_attrs(struct nfsd4_open *open)
  * in the returned attr bitmap.
  */
 static void
-do_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
-		struct nfs4_acl *acl, u32 *bmval)
+do_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp, struct richacl *acl,
+	   u32 *bmval)
 {
 	__be32 status;
 
-	status = nfsd4_set_nfs4_acl(rqstp, fhp, acl);
+	status = nfsd4_set_acl(rqstp, fhp, acl);
 	if (status)
 		/*
 		 * We should probably fail the whole open at this point,
@@ -299,7 +299,7 @@ do_open_lookup(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, stru
 		goto out;
 
 	if (is_create_with_attrs(open) && open->op_acl != NULL)
-		do_set_nfs4_acl(rqstp, *resfh, open->op_acl, open->op_bmval);
+		do_set_acl(rqstp, *resfh, open->op_acl, open->op_bmval);
 
 	nfsd4_set_open_owner_reply_cache(cstate, open, *resfh);
 	accmode = NFSD_MAY_NOP;
@@ -674,8 +674,7 @@ nfsd4_create(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 		nfsd4_security_inode_setsecctx(&resfh, &create->cr_label, create->cr_bmval);
 
 	if (create->cr_acl != NULL)
-		do_set_nfs4_acl(rqstp, &resfh, create->cr_acl,
-				create->cr_bmval);
+		do_set_acl(rqstp, &resfh, create->cr_acl, create->cr_bmval);
 
 	fh_unlock(&cstate->current_fh);
 	set_change_info(&create->cr_cinfo, &cstate->current_fh);
@@ -940,8 +939,8 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 		goto out;
 
 	if (setattr->sa_acl != NULL)
-		status = nfsd4_set_nfs4_acl(rqstp, &cstate->current_fh,
-					    setattr->sa_acl);
+		status = nfsd4_set_acl(rqstp, &cstate->current_fh,
+				       setattr->sa_acl);
 	if (status)
 		goto out;
 	if (setattr->sa_label.len)
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 0768251..465f82a 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -303,7 +303,7 @@ nfsd4_decode_bitmap(struct nfsd4_compoundargs *argp, u32 *bmval)
 
 static __be32
 nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
-		   struct iattr *iattr, struct nfs4_acl **acl,
+		   struct iattr *iattr, struct richacl **acl,
 		   struct xdr_netobj *label)
 {
 	int expected_len, len = 0;
@@ -326,38 +326,31 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
 	}
 	if (bmval[0] & FATTR4_WORD0_ACL) {
 		u32 nace;
-		struct nfs4_ace *ace;
+		struct richace *ace;
 
 		READ_BUF(4); len += 4;
 		nace = be32_to_cpup(p++);
 
-		if (nace > NFS4_ACL_MAX)
+		if (nace > NFSD4_ACL_MAX)
 			return nfserr_fbig;
 
-		*acl = svcxdr_tmpalloc(argp, nfs4_acl_bytes(nace));
+		*acl = svcxdr_alloc_richacl(argp, nace);
 		if (*acl == NULL)
 			return nfserr_jukebox;
 
-		(*acl)->naces = nace;
-		for (ace = (*acl)->aces; ace < (*acl)->aces + nace; ace++) {
+		richacl_for_each_entry(ace, *acl) {
 			READ_BUF(16); len += 16;
-			ace->type = be32_to_cpup(p++);
-			ace->flag = be32_to_cpup(p++);
-			ace->access_mask = be32_to_cpup(p++);
+			ace->e_type = be32_to_cpup(p++);
+			ace->e_flags = be32_to_cpup(p++);
+			ace->e_mask = be32_to_cpup(p++);
+			if (ace->e_flags & RICHACE_SPECIAL_WHO)
+				return nfserr_inval;
 			dummy32 = be32_to_cpup(p++);
 			READ_BUF(dummy32);
 			len += XDR_QUADLEN(dummy32) << 2;
 			READMEM(buf, dummy32);
-			ace->whotype = nfs4_acl_get_whotype(buf, dummy32);
-			status = nfs_ok;
-			if (ace->whotype != NFS4_ACL_WHO_NAMED)
-				;
-			else if (ace->flag & NFS4_ACE_IDENTIFIER_GROUP)
-				status = nfsd_map_name_to_gid(argp->rqstp,
-						buf, dummy32, &ace->who_gid);
-			else
-				status = nfsd_map_name_to_uid(argp->rqstp,
-						buf, dummy32, &ace->who_uid);
+			status = nfsd4_decode_ace_who(ace, argp->rqstp,
+						      buf, dummy32);
 			if (status)
 				return status;
 		}
@@ -2147,18 +2140,6 @@ static u32 nfs4_file_type(umode_t mode)
 	};
 }
 
-static inline __be32
-nfsd4_encode_aclname(struct xdr_stream *xdr, struct svc_rqst *rqstp,
-		     struct nfs4_ace *ace)
-{
-	if (ace->whotype != NFS4_ACL_WHO_NAMED)
-		return nfs4_acl_write_who(xdr, ace->whotype);
-	else if (ace->flag & NFS4_ACE_IDENTIFIER_GROUP)
-		return nfsd4_encode_group(xdr, rqstp, ace->who_gid);
-	else
-		return nfsd4_encode_user(xdr, rqstp, ace->who_uid);
-}
-
 #define WORD0_ABSENT_FS_ATTRS (FATTR4_WORD0_FS_LOCATIONS | FATTR4_WORD0_FSID | \
 			      FATTR4_WORD0_RDATTR_ERROR)
 #define WORD1_ABSENT_FS_ATTRS FATTR4_WORD1_MOUNTED_ON_FILEID
@@ -2249,7 +2230,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 	u32 rdattr_err = 0;
 	__be32 status;
 	int err;
-	struct nfs4_acl *acl = NULL;
+	struct richacl *acl = NULL;
 	void *context = NULL;
 	int contextlen;
 	bool contextsupport = false;
@@ -2295,7 +2276,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 		fhp = tempfh;
 	}
 	if (bmval0 & FATTR4_WORD0_ACL) {
-		err = nfsd4_get_nfs4_acl(rqstp, dentry, &acl);
+		err = nfsd4_get_acl(rqstp, dentry, &acl);
 		if (err == -EOPNOTSUPP)
 			bmval0 &= ~FATTR4_WORD0_ACL;
 		else if (err == -EINVAL) {
@@ -2469,7 +2450,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 		*p++ = cpu_to_be32(rdattr_err);
 	}
 	if (bmval0 & FATTR4_WORD0_ACL) {
-		struct nfs4_ace *ace;
+		struct richace *ace;
 
 		if (acl == NULL) {
 			p = xdr_reserve_space(xdr, 4);
@@ -2482,17 +2463,16 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 		p = xdr_reserve_space(xdr, 4);
 		if (!p)
 			goto out_resource;
-		*p++ = cpu_to_be32(acl->naces);
+		*p++ = cpu_to_be32(acl->a_count);
 
-		for (ace = acl->aces; ace < acl->aces + acl->naces; ace++) {
+		richacl_for_each_entry(ace, acl) {
 			p = xdr_reserve_space(xdr, 4*3);
 			if (!p)
 				goto out_resource;
-			*p++ = cpu_to_be32(ace->type);
-			*p++ = cpu_to_be32(ace->flag);
-			*p++ = cpu_to_be32(ace->access_mask &
-							NFS4_ACE_MASK_ALL);
-			status = nfsd4_encode_aclname(xdr, rqstp, ace);
+			*p++ = cpu_to_be32(ace->e_type);
+			*p++ = cpu_to_be32(ace->e_flags & ~RICHACE_SPECIAL_WHO);
+			*p++ = cpu_to_be32(ace->e_mask & NFS4_ACE_MASK_ALL);
+			status = nfsd4_encode_ace_who(xdr, rqstp, ace);
 			if (status)
 				goto out;
 		}
@@ -2755,7 +2735,7 @@ out:
 	if (context)
 		security_release_secctx(context, contextlen);
 #endif /* CONFIG_NFSD_V4_SECURITY_LABEL */
-	kfree(acl);
+	richacl_put(acl);
 	if (tempfh) {
 		fh_put(tempfh);
 		kfree(tempfh);
diff --git a/fs/nfsd/xdr4.h b/fs/nfsd/xdr4.h
index b698585..c311066 100644
--- a/fs/nfsd/xdr4.h
+++ b/fs/nfsd/xdr4.h
@@ -118,7 +118,7 @@ struct nfsd4_create {
 	u32		cr_bmval[3];        /* request */
 	struct iattr	cr_iattr;           /* request */
 	struct nfsd4_change_info  cr_cinfo; /* response */
-	struct nfs4_acl *cr_acl;
+	struct richacl *cr_acl;
 	struct xdr_netobj cr_label;
 };
 #define cr_datalen	u.link.datalen
@@ -248,7 +248,7 @@ struct nfsd4_open {
 	struct nfs4_file *op_file;          /* used during processing */
 	struct nfs4_ol_stateid *op_stp;	    /* used during processing */
 	struct nfs4_clnt_odstate *op_odstate; /* used during processing */
-	struct nfs4_acl *op_acl;
+	struct richacl *op_acl;
 	struct xdr_netobj op_label;
 };
 
@@ -332,7 +332,7 @@ struct nfsd4_setattr {
 	stateid_t	sa_stateid;         /* request */
 	u32		sa_bmval[3];        /* request */
 	struct iattr	sa_iattr;           /* request */
-	struct nfs4_acl *sa_acl;
+	struct richacl *sa_acl;
 	struct xdr_netobj sa_label;
 };
 
diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h
index b8e72aa..992ddc4 100644
--- a/include/linux/nfs4.h
+++ b/include/linux/nfs4.h
@@ -16,29 +16,6 @@
 #include <linux/uidgid.h>
 #include <uapi/linux/nfs4.h>
 
-enum nfs4_acl_whotype {
-	NFS4_ACL_WHO_NAMED = 0,
-	NFS4_ACL_WHO_OWNER,
-	NFS4_ACL_WHO_GROUP,
-	NFS4_ACL_WHO_EVERYONE,
-};
-
-struct nfs4_ace {
-	uint32_t	type;
-	uint32_t	flag;
-	uint32_t	access_mask;
-	int		whotype;
-	union {
-		kuid_t	who_uid;
-		kgid_t	who_gid;
-	};
-};
-
-struct nfs4_acl {
-	uint32_t	naces;
-	struct nfs4_ace	aces[0];
-};
-
 #define NFS4_MAXLABELLEN	2048
 
 struct nfs4_label {
diff --git a/include/linux/nfs4acl.h b/include/linux/nfs4acl.h
new file mode 100644
index 0000000..db9f9a6
--- /dev/null
+++ b/include/linux/nfs4acl.h
@@ -0,0 +1,7 @@
+#ifndef __LINUX_NFS4ACL_H
+#define __LINUX_NFS4ACL_H
+
+int nfs4acl_who_to_special_id(const char *, u32);
+bool nfs4acl_special_id_to_who(unsigned int, const char **, unsigned int *);
+
+#endif
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 30/41] nfsd: Add richacl support
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

On file systems with richacls enabled, get and set richacls directly
instead of converting from / to posix acls.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/nfsd/acl.h      |   3 +-
 fs/nfsd/nfs4acl.c  | 124 ++++++++++++++++++++++++++++++++++++++---------------
 fs/nfsd/nfs4proc.c |   2 +-
 fs/nfsd/nfs4xdr.c  |  34 +++++++++++----
 4 files changed, 117 insertions(+), 46 deletions(-)

diff --git a/fs/nfsd/acl.h b/fs/nfsd/acl.h
index 1c5deb5..d73c664 100644
--- a/fs/nfsd/acl.h
+++ b/fs/nfsd/acl.h
@@ -53,8 +53,7 @@ __be32 nfsd4_decode_ace_who(struct richace *ace, struct svc_rqst *rqstp,
 __be32 nfsd4_encode_ace_who(struct xdr_stream *xdr, struct svc_rqst *rqstp,
 			    struct richace *ace);
 
-int nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
-		  struct richacl **acl);
+struct richacl *nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry);
 __be32 nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
 		     struct richacl *acl);
 
diff --git a/fs/nfsd/nfs4acl.c b/fs/nfsd/nfs4acl.c
index 582f772..a8d596c 100644
--- a/fs/nfsd/nfs4acl.c
+++ b/fs/nfsd/nfs4acl.c
@@ -38,6 +38,8 @@
 #include <linux/nfs_fs.h>
 #include <linux/richacl_compat.h>
 #include <linux/nfs4acl.h>
+#include <linux/xattr.h>
+#include <linux/richacl_xattr.h>
 #include "nfsfh.h"
 #include "nfsd.h"
 #include "idmap.h"
@@ -126,32 +128,28 @@ static short ace2type(struct richace *);
 static void _posix_to_richacl_one(struct posix_acl *, struct richacl_alloc *,
 				unsigned int);
 
-int
-nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
-	      struct richacl **acl)
+static struct richacl *
+nfsd4_get_posix_acl(struct svc_rqst *rqstp, struct dentry *dentry)
 {
 	struct inode *inode = d_inode(dentry);
-	int error = 0;
 	struct posix_acl *pacl = NULL, *dpacl = NULL;
 	struct richacl_alloc alloc;
 	unsigned int flags = 0;
 	int count;
 
 	pacl = get_acl(inode, ACL_TYPE_ACCESS);
-	if (!pacl)
-		pacl = posix_acl_from_mode(inode->i_mode, GFP_KERNEL);
-
-	if (IS_ERR(pacl))
-		return PTR_ERR(pacl);
+	if (IS_ERR_OR_NULL(pacl))
+		return (void *)pacl;
 
-	/* allocate for worst case: one (deny, allow) pair each: */
+	/* Allocate for worst case: one (deny, allow) pair each.  The resulting
+	   acl will be released shortly and won't be cached. */
 	count = 2 * pacl->a_count;
 
 	if (S_ISDIR(inode->i_mode)) {
 		flags = FLAG_DIRECTORY;
 		dpacl = get_acl(inode, ACL_TYPE_DEFAULT);
 		if (IS_ERR(dpacl)) {
-			error = PTR_ERR(dpacl);
+			alloc.acl = (void *)dpacl;
 			goto rel_pacl;
 		}
 
@@ -160,7 +158,7 @@ nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
 	}
 
 	if (!richacl_prepare(&alloc, count)) {
-		error = -ENOMEM;
+		alloc.acl = ERR_PTR(-ENOMEM);
 		goto out;
 	}
 
@@ -169,13 +167,37 @@ nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
 	if (dpacl)
 		_posix_to_richacl_one(dpacl, &alloc, flags | FLAG_DEFAULT_ACL);
 
-	*acl = alloc.acl;
-
 out:
 	posix_acl_release(dpacl);
 rel_pacl:
 	posix_acl_release(pacl);
-	return error;
+	return alloc.acl;
+}
+
+struct richacl *
+nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry)
+{
+	struct inode *inode = d_inode(dentry);
+	struct richacl *acl;
+	int error;
+
+	if (IS_RICHACL(inode))
+		acl = get_richacl(inode);
+	else
+		acl = nfsd4_get_posix_acl(rqstp, dentry);
+	if (IS_ERR(acl))
+		return acl;
+	else if (acl == NULL) {
+		acl = richacl_from_mode(inode->i_mode);
+		if (acl == NULL)
+			acl = ERR_PTR(-ENOMEM);
+	}
+	error = richacl_apply_masks(&acl, inode->i_uid);
+	if (error) {
+		richacl_put(acl);
+		acl = ERR_PTR(error);
+	}
+	return acl;
 }
 
 struct posix_acl_summary {
@@ -741,56 +763,88 @@ out_estate:
 	return ret;
 }
 
-__be32
-nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp, struct richacl *acl)
+static int
+nfsd4_set_posix_acl(struct svc_rqst *rqstp, struct dentry *dentry,
+		    struct richacl *acl)
 {
-	__be32 error;
 	int host_error;
-	struct dentry *dentry;
-	struct inode *inode;
+	struct inode *inode = d_inode(dentry);
 	struct posix_acl *pacl = NULL, *dpacl = NULL;
 	unsigned int flags = 0;
 
-	/* Get inode */
-	error = fh_verify(rqstp, fhp, 0, NFSD_MAY_SATTR);
-	if (error)
-		return error;
-
-	dentry = fhp->fh_dentry;
-	inode = d_inode(dentry);
-
 	if (!inode->i_op->set_acl || !IS_POSIXACL(inode))
-		return nfserr_attrnotsupp;
+		return -EOPNOTSUPP;
 
 	if (S_ISDIR(inode->i_mode))
 		flags = FLAG_DIRECTORY;
 
 	host_error = nfs4_richacl_to_posix(acl, &pacl, &dpacl, flags);
 	if (host_error == -EINVAL)
-		return nfserr_attrnotsupp;
+		return -EOPNOTSUPP;
 	if (host_error < 0)
-		goto out_nfserr;
+		return host_error;
 
 	host_error = inode->i_op->set_acl(inode, pacl, ACL_TYPE_ACCESS);
 	if (host_error < 0)
 		goto out_release;
 
-	if (S_ISDIR(inode->i_mode)) {
+	if (S_ISDIR(inode->i_mode))
 		host_error = inode->i_op->set_acl(inode, dpacl,
 						  ACL_TYPE_DEFAULT);
-	}
 
 out_release:
 	posix_acl_release(pacl);
 	posix_acl_release(dpacl);
-out_nfserr:
+	return host_error;
+}
+
+static int
+nfsd4_set_richacl(struct svc_rqst *rqstp, struct dentry *dentry,
+		  struct richacl *acl)
+{
+	int host_error;
+	struct inode *inode = d_inode(dentry);
+	size_t size = richacl_xattr_size(acl);
+	char *buffer;
+
+	if (!inode->i_op->setxattr || !IS_RICHACL(inode))
+		return -EOPNOTSUPP;
+
+	richacl_compute_max_masks(acl);
+
+	buffer = kmalloc(size, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+	richacl_to_xattr(&init_user_ns, acl, buffer, size);
+	host_error = inode->i_op->setxattr(dentry, XATTR_NAME_RICHACL,
+					   buffer, size, 0);
+	kfree(buffer);
+	return host_error;
+}
+
+__be32
+nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp, struct richacl *acl)
+{
+	struct dentry *dentry;
+	int host_error;
+	__be32 error;
+
+	error = fh_verify(rqstp, fhp, 0, NFSD_MAY_SATTR);
+	if (error)
+		return error;
+	dentry = fhp->fh_dentry;
+
+	if (IS_RICHACL(d_inode(dentry)))
+		host_error = nfsd4_set_richacl(rqstp, dentry, acl);
+	else
+		host_error = nfsd4_set_posix_acl(rqstp, dentry, acl);
+
 	if (host_error == -EOPNOTSUPP)
 		return nfserr_attrnotsupp;
 	else
 		return nfserrno(host_error);
 }
 
-
 static short
 ace2type(struct richace *ace)
 {
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 8c2cb16..9f8f0a8 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -110,7 +110,7 @@ check_attr_support(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 	 * in current environment or not.
 	 */
 	if (bmval[0] & FATTR4_WORD0_ACL) {
-		if (!IS_POSIXACL(d_inode(dentry)))
+		if (!IS_ACL(d_inode(dentry)))
 			return nfserr_attrnotsupp;
 	}
 
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 465f82a..3280c30 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -340,11 +340,24 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
 
 		richacl_for_each_entry(ace, *acl) {
 			READ_BUF(16); len += 16;
-			ace->e_type = be32_to_cpup(p++);
-			ace->e_flags = be32_to_cpup(p++);
-			ace->e_mask = be32_to_cpup(p++);
-			if (ace->e_flags & RICHACE_SPECIAL_WHO)
+
+			dummy32 = be32_to_cpup(p++);
+			if (dummy32 > RICHACE_ACCESS_DENIED_ACE_TYPE)
+				return nfserr_inval;
+			ace->e_type = dummy32;
+
+			dummy32 = be32_to_cpup(p++);
+			if (dummy32 & (~RICHACE_VALID_FLAGS |
+				       RICHACE_INHERITED_ACE |
+				       RICHACE_SPECIAL_WHO))
 				return nfserr_inval;
+			ace->e_flags = dummy32;
+
+			dummy32 = be32_to_cpup(p++);
+			if (dummy32 & ~NFS4_ACE_MASK_ALL)
+				return nfserr_inval;
+			ace->e_mask = dummy32;
+
 			dummy32 = be32_to_cpup(p++);
 			READ_BUF(dummy32);
 			len += XDR_QUADLEN(dummy32) << 2;
@@ -2276,7 +2289,11 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 		fhp = tempfh;
 	}
 	if (bmval0 & FATTR4_WORD0_ACL) {
-		err = nfsd4_get_acl(rqstp, dentry, &acl);
+		acl = nfsd4_get_acl(rqstp, dentry);
+		if (IS_ERR(acl)) {
+			err = PTR_ERR(acl);
+			acl = NULL;
+		}
 		if (err == -EOPNOTSUPP)
 			bmval0 &= ~FATTR4_WORD0_ACL;
 		else if (err == -EINVAL) {
@@ -2335,7 +2352,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 		u32 word1 = nfsd_suppattrs1(minorversion);
 		u32 word2 = nfsd_suppattrs2(minorversion);
 
-		if (!IS_POSIXACL(dentry->d_inode))
+		if (!IS_ACL(d_inode(dentry)))
 			word0 &= ~FATTR4_WORD0_ACL;
 		if (!contextsupport)
 			word2 &= ~FATTR4_WORD2_SECURITY_LABEL;
@@ -2470,7 +2487,8 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 			if (!p)
 				goto out_resource;
 			*p++ = cpu_to_be32(ace->e_type);
-			*p++ = cpu_to_be32(ace->e_flags & ~RICHACE_SPECIAL_WHO);
+			*p++ = cpu_to_be32(ace->e_flags &
+				~(RICHACE_SPECIAL_WHO | RICHACE_INHERITED_ACE));
 			*p++ = cpu_to_be32(ace->e_mask & NFS4_ACE_MASK_ALL);
 			status = nfsd4_encode_ace_who(xdr, rqstp, ace);
 			if (status)
@@ -2482,7 +2500,7 @@ out_acl:
 		p = xdr_reserve_space(xdr, 4);
 		if (!p)
 			goto out_resource;
-		*p++ = cpu_to_be32(IS_POSIXACL(dentry->d_inode) ?
+		*p++ = cpu_to_be32(IS_ACL(d_inode(dentry)) ?
 			ACL4_SUPPORT_ALLOW_ACL|ACL4_SUPPORT_DENY_ACL : 0);
 	}
 	if (bmval0 & FATTR4_WORD0_CANSETTIME) {
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 30/41] nfsd: Add richacl support
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On file systems with richacls enabled, get and set richacls directly
instead of converting from / to posix acls.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/nfsd/acl.h      |   3 +-
 fs/nfsd/nfs4acl.c  | 124 ++++++++++++++++++++++++++++++++++++++---------------
 fs/nfsd/nfs4proc.c |   2 +-
 fs/nfsd/nfs4xdr.c  |  34 +++++++++++----
 4 files changed, 117 insertions(+), 46 deletions(-)

diff --git a/fs/nfsd/acl.h b/fs/nfsd/acl.h
index 1c5deb5..d73c664 100644
--- a/fs/nfsd/acl.h
+++ b/fs/nfsd/acl.h
@@ -53,8 +53,7 @@ __be32 nfsd4_decode_ace_who(struct richace *ace, struct svc_rqst *rqstp,
 __be32 nfsd4_encode_ace_who(struct xdr_stream *xdr, struct svc_rqst *rqstp,
 			    struct richace *ace);
 
-int nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
-		  struct richacl **acl);
+struct richacl *nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry);
 __be32 nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
 		     struct richacl *acl);
 
diff --git a/fs/nfsd/nfs4acl.c b/fs/nfsd/nfs4acl.c
index 582f772..a8d596c 100644
--- a/fs/nfsd/nfs4acl.c
+++ b/fs/nfsd/nfs4acl.c
@@ -38,6 +38,8 @@
 #include <linux/nfs_fs.h>
 #include <linux/richacl_compat.h>
 #include <linux/nfs4acl.h>
+#include <linux/xattr.h>
+#include <linux/richacl_xattr.h>
 #include "nfsfh.h"
 #include "nfsd.h"
 #include "idmap.h"
@@ -126,32 +128,28 @@ static short ace2type(struct richace *);
 static void _posix_to_richacl_one(struct posix_acl *, struct richacl_alloc *,
 				unsigned int);
 
-int
-nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
-	      struct richacl **acl)
+static struct richacl *
+nfsd4_get_posix_acl(struct svc_rqst *rqstp, struct dentry *dentry)
 {
 	struct inode *inode = d_inode(dentry);
-	int error = 0;
 	struct posix_acl *pacl = NULL, *dpacl = NULL;
 	struct richacl_alloc alloc;
 	unsigned int flags = 0;
 	int count;
 
 	pacl = get_acl(inode, ACL_TYPE_ACCESS);
-	if (!pacl)
-		pacl = posix_acl_from_mode(inode->i_mode, GFP_KERNEL);
-
-	if (IS_ERR(pacl))
-		return PTR_ERR(pacl);
+	if (IS_ERR_OR_NULL(pacl))
+		return (void *)pacl;
 
-	/* allocate for worst case: one (deny, allow) pair each: */
+	/* Allocate for worst case: one (deny, allow) pair each.  The resulting
+	   acl will be released shortly and won't be cached. */
 	count = 2 * pacl->a_count;
 
 	if (S_ISDIR(inode->i_mode)) {
 		flags = FLAG_DIRECTORY;
 		dpacl = get_acl(inode, ACL_TYPE_DEFAULT);
 		if (IS_ERR(dpacl)) {
-			error = PTR_ERR(dpacl);
+			alloc.acl = (void *)dpacl;
 			goto rel_pacl;
 		}
 
@@ -160,7 +158,7 @@ nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
 	}
 
 	if (!richacl_prepare(&alloc, count)) {
-		error = -ENOMEM;
+		alloc.acl = ERR_PTR(-ENOMEM);
 		goto out;
 	}
 
@@ -169,13 +167,37 @@ nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
 	if (dpacl)
 		_posix_to_richacl_one(dpacl, &alloc, flags | FLAG_DEFAULT_ACL);
 
-	*acl = alloc.acl;
-
 out:
 	posix_acl_release(dpacl);
 rel_pacl:
 	posix_acl_release(pacl);
-	return error;
+	return alloc.acl;
+}
+
+struct richacl *
+nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry)
+{
+	struct inode *inode = d_inode(dentry);
+	struct richacl *acl;
+	int error;
+
+	if (IS_RICHACL(inode))
+		acl = get_richacl(inode);
+	else
+		acl = nfsd4_get_posix_acl(rqstp, dentry);
+	if (IS_ERR(acl))
+		return acl;
+	else if (acl == NULL) {
+		acl = richacl_from_mode(inode->i_mode);
+		if (acl == NULL)
+			acl = ERR_PTR(-ENOMEM);
+	}
+	error = richacl_apply_masks(&acl, inode->i_uid);
+	if (error) {
+		richacl_put(acl);
+		acl = ERR_PTR(error);
+	}
+	return acl;
 }
 
 struct posix_acl_summary {
@@ -741,56 +763,88 @@ out_estate:
 	return ret;
 }
 
-__be32
-nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp, struct richacl *acl)
+static int
+nfsd4_set_posix_acl(struct svc_rqst *rqstp, struct dentry *dentry,
+		    struct richacl *acl)
 {
-	__be32 error;
 	int host_error;
-	struct dentry *dentry;
-	struct inode *inode;
+	struct inode *inode = d_inode(dentry);
 	struct posix_acl *pacl = NULL, *dpacl = NULL;
 	unsigned int flags = 0;
 
-	/* Get inode */
-	error = fh_verify(rqstp, fhp, 0, NFSD_MAY_SATTR);
-	if (error)
-		return error;
-
-	dentry = fhp->fh_dentry;
-	inode = d_inode(dentry);
-
 	if (!inode->i_op->set_acl || !IS_POSIXACL(inode))
-		return nfserr_attrnotsupp;
+		return -EOPNOTSUPP;
 
 	if (S_ISDIR(inode->i_mode))
 		flags = FLAG_DIRECTORY;
 
 	host_error = nfs4_richacl_to_posix(acl, &pacl, &dpacl, flags);
 	if (host_error == -EINVAL)
-		return nfserr_attrnotsupp;
+		return -EOPNOTSUPP;
 	if (host_error < 0)
-		goto out_nfserr;
+		return host_error;
 
 	host_error = inode->i_op->set_acl(inode, pacl, ACL_TYPE_ACCESS);
 	if (host_error < 0)
 		goto out_release;
 
-	if (S_ISDIR(inode->i_mode)) {
+	if (S_ISDIR(inode->i_mode))
 		host_error = inode->i_op->set_acl(inode, dpacl,
 						  ACL_TYPE_DEFAULT);
-	}
 
 out_release:
 	posix_acl_release(pacl);
 	posix_acl_release(dpacl);
-out_nfserr:
+	return host_error;
+}
+
+static int
+nfsd4_set_richacl(struct svc_rqst *rqstp, struct dentry *dentry,
+		  struct richacl *acl)
+{
+	int host_error;
+	struct inode *inode = d_inode(dentry);
+	size_t size = richacl_xattr_size(acl);
+	char *buffer;
+
+	if (!inode->i_op->setxattr || !IS_RICHACL(inode))
+		return -EOPNOTSUPP;
+
+	richacl_compute_max_masks(acl);
+
+	buffer = kmalloc(size, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+	richacl_to_xattr(&init_user_ns, acl, buffer, size);
+	host_error = inode->i_op->setxattr(dentry, XATTR_NAME_RICHACL,
+					   buffer, size, 0);
+	kfree(buffer);
+	return host_error;
+}
+
+__be32
+nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp, struct richacl *acl)
+{
+	struct dentry *dentry;
+	int host_error;
+	__be32 error;
+
+	error = fh_verify(rqstp, fhp, 0, NFSD_MAY_SATTR);
+	if (error)
+		return error;
+	dentry = fhp->fh_dentry;
+
+	if (IS_RICHACL(d_inode(dentry)))
+		host_error = nfsd4_set_richacl(rqstp, dentry, acl);
+	else
+		host_error = nfsd4_set_posix_acl(rqstp, dentry, acl);
+
 	if (host_error == -EOPNOTSUPP)
 		return nfserr_attrnotsupp;
 	else
 		return nfserrno(host_error);
 }
 
-
 static short
 ace2type(struct richace *ace)
 {
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 8c2cb16..9f8f0a8 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -110,7 +110,7 @@ check_attr_support(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 	 * in current environment or not.
 	 */
 	if (bmval[0] & FATTR4_WORD0_ACL) {
-		if (!IS_POSIXACL(d_inode(dentry)))
+		if (!IS_ACL(d_inode(dentry)))
 			return nfserr_attrnotsupp;
 	}
 
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 465f82a..3280c30 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -340,11 +340,24 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
 
 		richacl_for_each_entry(ace, *acl) {
 			READ_BUF(16); len += 16;
-			ace->e_type = be32_to_cpup(p++);
-			ace->e_flags = be32_to_cpup(p++);
-			ace->e_mask = be32_to_cpup(p++);
-			if (ace->e_flags & RICHACE_SPECIAL_WHO)
+
+			dummy32 = be32_to_cpup(p++);
+			if (dummy32 > RICHACE_ACCESS_DENIED_ACE_TYPE)
+				return nfserr_inval;
+			ace->e_type = dummy32;
+
+			dummy32 = be32_to_cpup(p++);
+			if (dummy32 & (~RICHACE_VALID_FLAGS |
+				       RICHACE_INHERITED_ACE |
+				       RICHACE_SPECIAL_WHO))
 				return nfserr_inval;
+			ace->e_flags = dummy32;
+
+			dummy32 = be32_to_cpup(p++);
+			if (dummy32 & ~NFS4_ACE_MASK_ALL)
+				return nfserr_inval;
+			ace->e_mask = dummy32;
+
 			dummy32 = be32_to_cpup(p++);
 			READ_BUF(dummy32);
 			len += XDR_QUADLEN(dummy32) << 2;
@@ -2276,7 +2289,11 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 		fhp = tempfh;
 	}
 	if (bmval0 & FATTR4_WORD0_ACL) {
-		err = nfsd4_get_acl(rqstp, dentry, &acl);
+		acl = nfsd4_get_acl(rqstp, dentry);
+		if (IS_ERR(acl)) {
+			err = PTR_ERR(acl);
+			acl = NULL;
+		}
 		if (err == -EOPNOTSUPP)
 			bmval0 &= ~FATTR4_WORD0_ACL;
 		else if (err == -EINVAL) {
@@ -2335,7 +2352,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 		u32 word1 = nfsd_suppattrs1(minorversion);
 		u32 word2 = nfsd_suppattrs2(minorversion);
 
-		if (!IS_POSIXACL(dentry->d_inode))
+		if (!IS_ACL(d_inode(dentry)))
 			word0 &= ~FATTR4_WORD0_ACL;
 		if (!contextsupport)
 			word2 &= ~FATTR4_WORD2_SECURITY_LABEL;
@@ -2470,7 +2487,8 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 			if (!p)
 				goto out_resource;
 			*p++ = cpu_to_be32(ace->e_type);
-			*p++ = cpu_to_be32(ace->e_flags & ~RICHACE_SPECIAL_WHO);
+			*p++ = cpu_to_be32(ace->e_flags &
+				~(RICHACE_SPECIAL_WHO | RICHACE_INHERITED_ACE));
 			*p++ = cpu_to_be32(ace->e_mask & NFS4_ACE_MASK_ALL);
 			status = nfsd4_encode_ace_who(xdr, rqstp, ace);
 			if (status)
@@ -2482,7 +2500,7 @@ out_acl:
 		p = xdr_reserve_space(xdr, 4);
 		if (!p)
 			goto out_resource;
-		*p++ = cpu_to_be32(IS_POSIXACL(dentry->d_inode) ?
+		*p++ = cpu_to_be32(IS_ACL(d_inode(dentry)) ?
 			ACL4_SUPPORT_ALLOW_ACL|ACL4_SUPPORT_DENY_ACL : 0);
 	}
 	if (bmval0 & FATTR4_WORD0_CANSETTIME) {
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 31/41] nfsd: Add support for the v4.1 dacl attribute
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

Richacls support the Automatic Inheritance permission propagation
mechanism as specified in NFSv4.1.  Over NFS, this requires support for
the dacl attribute: compared to the acl attribute, the dacl attribute
has an additional flags field which indicates when Automatic Inheritance
is in use.

The server will only indicate dacl attribute support in protocol version
4.1 and later, on file systems with richacl support.

This commit also adds support for the NFSv4.1 NFS4_ACE_WRITE_RETENTION
and NFS4_ACE_WRITE_RETENTION_HOLD ACL permissions.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/nfsd/nfs4proc.c        |   2 +
 fs/nfsd/nfs4xdr.c         | 219 ++++++++++++++++++++++++++++++----------------
 fs/nfsd/nfsd.h            |   6 +-
 include/linux/nfs4.h      |   1 +
 include/uapi/linux/nfs4.h |   3 +-
 5 files changed, 155 insertions(+), 76 deletions(-)

diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 9f8f0a8..ef9e6cd 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -1804,6 +1804,8 @@ static inline u32 nfsd4_getattr_rsize(struct svc_rqst *rqstp,
 		ret += NFS4_FHSIZE + 4;
 		bmap0 &= ~FATTR4_WORD0_FILEHANDLE;
 	}
+	if (bmap1 & FATTR4_WORD1_DACL)
+		return svc_max_payload(rqstp);
 	if (bmap2 & FATTR4_WORD2_SECURITY_LABEL) {
 		ret += NFS4_MAXLABELLEN + 12;
 		bmap2 &= ~FATTR4_WORD2_SECURITY_LABEL;
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 3280c30..b66063d 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -301,6 +301,68 @@ nfsd4_decode_bitmap(struct nfsd4_compoundargs *argp, u32 *bmval)
 	DECODE_TAIL;
 }
 
+static unsigned int
+nfsd4_ace_mask(int minorversion)
+{
+	return minorversion == 0 ?  NFS40_ACE_MASK_ALL : NFS4_ACE_MASK_ALL;
+}
+
+static __be32
+nfsd4_decode_acl_entries(struct nfsd4_compoundargs *argp, struct richacl **acl,
+			 unsigned short flags_mask, unsigned int ace_mask,
+			 int *plen)
+{
+	struct richace *ace;
+	u32 dummy32;
+	char *buf;
+	int len = 0;
+
+	DECODE_HEAD;
+
+	flags_mask &= RICHACE_VALID_FLAGS & ~RICHACE_SPECIAL_WHO;
+
+	READ_BUF(4); len += 4;
+	dummy32 = be32_to_cpup(p++);
+
+	if (dummy32 > NFSD4_ACL_MAX)
+		return nfserr_fbig;
+
+	*acl = svcxdr_alloc_richacl(argp, dummy32);
+	if (*acl == NULL)
+		return nfserr_jukebox;
+
+	richacl_for_each_entry(ace, *acl) {
+		READ_BUF(16); len += 16;
+
+		dummy32 = be32_to_cpup(p++);
+		if (dummy32 > RICHACE_ACCESS_DENIED_ACE_TYPE)
+			return nfserr_inval;
+		ace->e_type = dummy32;
+
+		dummy32 = be32_to_cpup(p++);
+		if (dummy32 & ~flags_mask)
+			return nfserr_inval;
+		ace->e_flags = dummy32;
+
+		dummy32 = be32_to_cpup(p++);
+		if (dummy32 & ~ace_mask)
+			return nfserr_inval;
+		ace->e_mask = dummy32;
+
+		dummy32 = be32_to_cpup(p++);
+		READ_BUF(dummy32);
+		len += XDR_QUADLEN(dummy32) << 2;
+		READMEM(buf, dummy32);
+		status = nfsd4_decode_ace_who(ace, argp->rqstp,
+					      buf, dummy32);
+		if (status)
+			return status;
+	}
+	*plen += len;
+
+	DECODE_TAIL;
+}
+
 static __be32
 nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
 		   struct iattr *iattr, struct richacl **acl,
@@ -312,6 +374,7 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
 
 	DECODE_HEAD;
 	iattr->ia_valid = 0;
+	*acl = NULL;
 	if ((status = nfsd4_decode_bitmap(argp, bmval)))
 		return status;
 
@@ -325,50 +388,18 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
 		iattr->ia_valid |= ATTR_SIZE;
 	}
 	if (bmval[0] & FATTR4_WORD0_ACL) {
-		u32 nace;
-		struct richace *ace;
-
-		READ_BUF(4); len += 4;
-		nace = be32_to_cpup(p++);
-
-		if (nace > NFSD4_ACL_MAX)
-			return nfserr_fbig;
+		if (bmval[1] & FATTR4_WORD1_DACL)
+			return nfserr_inval;
 
-		*acl = svcxdr_alloc_richacl(argp, nace);
-		if (*acl == NULL)
+		status = nfsd4_decode_acl_entries(argp, acl,
+				~NFS4_ACE_INHERITED_ACE,
+				nfsd4_ace_mask(argp->minorversion),
+				&len);
+		if (status)
+			return status;
+		else if (*acl == NULL)
 			return nfserr_jukebox;
-
-		richacl_for_each_entry(ace, *acl) {
-			READ_BUF(16); len += 16;
-
-			dummy32 = be32_to_cpup(p++);
-			if (dummy32 > RICHACE_ACCESS_DENIED_ACE_TYPE)
-				return nfserr_inval;
-			ace->e_type = dummy32;
-
-			dummy32 = be32_to_cpup(p++);
-			if (dummy32 & (~RICHACE_VALID_FLAGS |
-				       RICHACE_INHERITED_ACE |
-				       RICHACE_SPECIAL_WHO))
-				return nfserr_inval;
-			ace->e_flags = dummy32;
-
-			dummy32 = be32_to_cpup(p++);
-			if (dummy32 & ~NFS4_ACE_MASK_ALL)
-				return nfserr_inval;
-			ace->e_mask = dummy32;
-
-			dummy32 = be32_to_cpup(p++);
-			READ_BUF(dummy32);
-			len += XDR_QUADLEN(dummy32) << 2;
-			READMEM(buf, dummy32);
-			status = nfsd4_decode_ace_who(ace, argp->rqstp,
-						      buf, dummy32);
-			if (status)
-				return status;
-		}
-	} else
-		*acl = NULL;
+	}
 	if (bmval[1] & FATTR4_WORD1_MODE) {
 		READ_BUF(4);
 		len += 4;
@@ -436,6 +467,22 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
 			goto xdr_error;
 		}
 	}
+	if (bmval[1] & FATTR4_WORD1_DACL) {
+		READ_BUF(4);
+		len += 4;
+		dummy32 = be32_to_cpup(p++);
+		if (dummy32 & (~RICHACL_VALID_FLAGS | RICHACL_MASKED))
+			return nfserr_inval;
+		status = nfsd4_decode_acl_entries(argp, acl,
+				~0,
+				nfsd4_ace_mask(argp->minorversion),
+				&len);
+		if (status)
+			return status;
+		else if (*acl == NULL)
+			return nfserr_jukebox;
+		(*acl)->a_flags = dummy32;
+	}
 
 	label->len = 0;
 #ifdef CONFIG_NFSD_V4_SECURITY_LABEL
@@ -2218,6 +2265,42 @@ static int get_parent_attributes(struct svc_export *exp, struct kstat *stat)
 	return err;
 }
 
+static __be32 nfsd4_encode_acl_entries(struct xdr_stream *xdr,
+		struct richacl *acl, struct svc_rqst *rqstp,
+		unsigned short flags_mask, unsigned int ace_mask)
+{
+	__be32 *p;
+
+	flags_mask &= ~RICHACE_SPECIAL_WHO;
+
+	p = xdr_reserve_space(xdr, 4);
+	if (!p)
+		return nfserr_resource;
+
+	if (acl == NULL) {
+		*p++ = cpu_to_be32(0);
+	} else {
+		struct richace *ace;
+
+		*p++ = cpu_to_be32(acl->a_count);
+
+		richacl_for_each_entry(ace, acl) {
+			__be32 status;
+
+			p = xdr_reserve_space(xdr, 4*3);
+			if (!p)
+				return nfserr_resource;
+			*p++ = cpu_to_be32(ace->e_type);
+			*p++ = cpu_to_be32(ace->e_flags & flags_mask);
+			*p++ = cpu_to_be32(ace->e_mask & ace_mask);
+			status = nfsd4_encode_ace_who(xdr, rqstp, ace);
+			if (status)
+				return status;
+		}
+	}
+	return 0;
+}
+
 /*
  * Note: @fhp can be NULL; in this case, we might have to compose the filehandle
  * ourselves.
@@ -2288,15 +2371,16 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 			goto out;
 		fhp = tempfh;
 	}
-	if (bmval0 & FATTR4_WORD0_ACL) {
+	if ((bmval0 & FATTR4_WORD0_ACL) || (bmval1 & FATTR4_WORD1_DACL)) {
 		acl = nfsd4_get_acl(rqstp, dentry);
 		if (IS_ERR(acl)) {
 			err = PTR_ERR(acl);
 			acl = NULL;
 		}
-		if (err == -EOPNOTSUPP)
+		if (err == -EOPNOTSUPP) {
 			bmval0 &= ~FATTR4_WORD0_ACL;
-		else if (err == -EINVAL) {
+			bmval1 &= ~FATTR4_WORD1_DACL;
+		} else if (err == -EINVAL) {
 			status = nfserr_attrnotsupp;
 			goto out;
 		} else if (err != 0)
@@ -2354,6 +2438,8 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 
 		if (!IS_ACL(d_inode(dentry)))
 			word0 &= ~FATTR4_WORD0_ACL;
+		if (!IS_RICHACL(d_inode(dentry)))
+			word1 &= ~FATTR4_WORD1_DACL;
 		if (!contextsupport)
 			word2 &= ~FATTR4_WORD2_SECURITY_LABEL;
 		if (!word2) {
@@ -2467,35 +2553,12 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 		*p++ = cpu_to_be32(rdattr_err);
 	}
 	if (bmval0 & FATTR4_WORD0_ACL) {
-		struct richace *ace;
-
-		if (acl == NULL) {
-			p = xdr_reserve_space(xdr, 4);
-			if (!p)
-				goto out_resource;
-
-			*p++ = cpu_to_be32(0);
-			goto out_acl;
-		}
-		p = xdr_reserve_space(xdr, 4);
-		if (!p)
-			goto out_resource;
-		*p++ = cpu_to_be32(acl->a_count);
-
-		richacl_for_each_entry(ace, acl) {
-			p = xdr_reserve_space(xdr, 4*3);
-			if (!p)
-				goto out_resource;
-			*p++ = cpu_to_be32(ace->e_type);
-			*p++ = cpu_to_be32(ace->e_flags &
-				~(RICHACE_SPECIAL_WHO | RICHACE_INHERITED_ACE));
-			*p++ = cpu_to_be32(ace->e_mask & NFS4_ACE_MASK_ALL);
-			status = nfsd4_encode_ace_who(xdr, rqstp, ace);
-			if (status)
-				goto out;
-		}
+		status = nfsd4_encode_acl_entries(xdr, acl, rqstp,
+				~NFS4_ACE_INHERITED_ACE,
+				nfsd4_ace_mask(minorversion));
+		if (status)
+			goto out;
 	}
-out_acl:
 	if (bmval0 & FATTR4_WORD0_ACLSUPPORT) {
 		p = xdr_reserve_space(xdr, 4);
 		if (!p)
@@ -2704,6 +2767,16 @@ out_acl:
 			get_parent_attributes(exp, &stat);
 		p = xdr_encode_hyper(p, stat.ino);
 	}
+	if (bmval1 & FATTR4_WORD1_DACL) {
+		p = xdr_reserve_space(xdr, 4);
+		if (!p)
+			goto out_resource;
+		*p++ = cpu_to_be32(acl->a_flags);
+		status = nfsd4_encode_acl_entries(xdr, acl, rqstp,
+				~0, nfsd4_ace_mask(minorversion));
+		if (status)
+			goto out;
+	}
 #ifdef CONFIG_NFSD_PNFS
 	if ((bmval1 & FATTR4_WORD1_FS_LAYOUT_TYPES) ||
 	    (bmval2 & FATTR4_WORD2_LAYOUT_TYPES)) {
diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h
index cf98052..cb5c3ed 100644
--- a/fs/nfsd/nfsd.h
+++ b/fs/nfsd/nfsd.h
@@ -339,7 +339,8 @@ void		nfsd_lockd_shutdown(void);
 	NFSD4_SUPPORTED_ATTRS_WORD0
 
 #define NFSD4_1_SUPPORTED_ATTRS_WORD1 \
-	(NFSD4_SUPPORTED_ATTRS_WORD1	| PNFSD_SUPPORTED_ATTRS_WORD1)
+	(NFSD4_SUPPORTED_ATTRS_WORD1	| PNFSD_SUPPORTED_ATTRS_WORD1 | \
+	 FATTR4_WORD1_DACL)
 
 #define NFSD4_1_SUPPORTED_ATTRS_WORD2 \
 	(NFSD4_SUPPORTED_ATTRS_WORD2	| PNFSD_SUPPORTED_ATTRS_WORD2 | \
@@ -386,7 +387,8 @@ static inline u32 nfsd_suppattrs2(u32 minorversion)
 	(FATTR4_WORD0_SIZE | FATTR4_WORD0_ACL)
 #define NFSD_WRITEABLE_ATTRS_WORD1 \
 	(FATTR4_WORD1_MODE | FATTR4_WORD1_OWNER | FATTR4_WORD1_OWNER_GROUP \
-	| FATTR4_WORD1_TIME_ACCESS_SET | FATTR4_WORD1_TIME_MODIFY_SET)
+	| FATTR4_WORD1_TIME_ACCESS_SET | FATTR4_WORD1_TIME_MODIFY_SET \
+	| FATTR4_WORD1_DACL)
 #ifdef CONFIG_NFSD_V4_SECURITY_LABEL
 #define NFSD_WRITEABLE_ATTRS_WORD2 FATTR4_WORD2_SECURITY_LABEL
 #else
diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h
index 992ddc4..1bd4aea 100644
--- a/include/linux/nfs4.h
+++ b/include/linux/nfs4.h
@@ -394,6 +394,7 @@ enum lock_type4 {
 #define FATTR4_WORD1_TIME_MODIFY        (1UL << 21)
 #define FATTR4_WORD1_TIME_MODIFY_SET    (1UL << 22)
 #define FATTR4_WORD1_MOUNTED_ON_FILEID  (1UL << 23)
+#define FATTR4_WORD1_DACL               (1UL << 26)
 #define FATTR4_WORD1_FS_LAYOUT_TYPES    (1UL << 30)
 #define FATTR4_WORD2_LAYOUT_TYPES       (1UL << 0)
 #define FATTR4_WORD2_LAYOUT_BLKSIZE     (1UL << 1)
diff --git a/include/uapi/linux/nfs4.h b/include/uapi/linux/nfs4.h
index 2119c7c..64e4c6c 100644
--- a/include/uapi/linux/nfs4.h
+++ b/include/uapi/linux/nfs4.h
@@ -121,7 +121,8 @@
 #define NFS4_ACE_GENERIC_READ                 0x00120081
 #define NFS4_ACE_GENERIC_WRITE                0x00160106
 #define NFS4_ACE_GENERIC_EXECUTE              0x001200A0
-#define NFS4_ACE_MASK_ALL                     0x001F01FF
+#define NFS40_ACE_MASK_ALL                    0x001F01FF
+#define NFS4_ACE_MASK_ALL                     0x001F07FF
 
 #define EXCHGID4_FLAG_SUPP_MOVED_REFER		0x00000001
 #define EXCHGID4_FLAG_SUPP_MOVED_MIGR		0x00000002
-- 
2.4.3

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 31/41] nfsd: Add support for the v4.1 dacl attribute
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

Richacls support the Automatic Inheritance permission propagation
mechanism as specified in NFSv4.1.  Over NFS, this requires support for
the dacl attribute: compared to the acl attribute, the dacl attribute
has an additional flags field which indicates when Automatic Inheritance
is in use.

The server will only indicate dacl attribute support in protocol version
4.1 and later, on file systems with richacl support.

This commit also adds support for the NFSv4.1 NFS4_ACE_WRITE_RETENTION
and NFS4_ACE_WRITE_RETENTION_HOLD ACL permissions.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/nfsd/nfs4proc.c        |   2 +
 fs/nfsd/nfs4xdr.c         | 219 ++++++++++++++++++++++++++++++----------------
 fs/nfsd/nfsd.h            |   6 +-
 include/linux/nfs4.h      |   1 +
 include/uapi/linux/nfs4.h |   3 +-
 5 files changed, 155 insertions(+), 76 deletions(-)

diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 9f8f0a8..ef9e6cd 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -1804,6 +1804,8 @@ static inline u32 nfsd4_getattr_rsize(struct svc_rqst *rqstp,
 		ret += NFS4_FHSIZE + 4;
 		bmap0 &= ~FATTR4_WORD0_FILEHANDLE;
 	}
+	if (bmap1 & FATTR4_WORD1_DACL)
+		return svc_max_payload(rqstp);
 	if (bmap2 & FATTR4_WORD2_SECURITY_LABEL) {
 		ret += NFS4_MAXLABELLEN + 12;
 		bmap2 &= ~FATTR4_WORD2_SECURITY_LABEL;
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 3280c30..b66063d 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -301,6 +301,68 @@ nfsd4_decode_bitmap(struct nfsd4_compoundargs *argp, u32 *bmval)
 	DECODE_TAIL;
 }
 
+static unsigned int
+nfsd4_ace_mask(int minorversion)
+{
+	return minorversion == 0 ?  NFS40_ACE_MASK_ALL : NFS4_ACE_MASK_ALL;
+}
+
+static __be32
+nfsd4_decode_acl_entries(struct nfsd4_compoundargs *argp, struct richacl **acl,
+			 unsigned short flags_mask, unsigned int ace_mask,
+			 int *plen)
+{
+	struct richace *ace;
+	u32 dummy32;
+	char *buf;
+	int len = 0;
+
+	DECODE_HEAD;
+
+	flags_mask &= RICHACE_VALID_FLAGS & ~RICHACE_SPECIAL_WHO;
+
+	READ_BUF(4); len += 4;
+	dummy32 = be32_to_cpup(p++);
+
+	if (dummy32 > NFSD4_ACL_MAX)
+		return nfserr_fbig;
+
+	*acl = svcxdr_alloc_richacl(argp, dummy32);
+	if (*acl == NULL)
+		return nfserr_jukebox;
+
+	richacl_for_each_entry(ace, *acl) {
+		READ_BUF(16); len += 16;
+
+		dummy32 = be32_to_cpup(p++);
+		if (dummy32 > RICHACE_ACCESS_DENIED_ACE_TYPE)
+			return nfserr_inval;
+		ace->e_type = dummy32;
+
+		dummy32 = be32_to_cpup(p++);
+		if (dummy32 & ~flags_mask)
+			return nfserr_inval;
+		ace->e_flags = dummy32;
+
+		dummy32 = be32_to_cpup(p++);
+		if (dummy32 & ~ace_mask)
+			return nfserr_inval;
+		ace->e_mask = dummy32;
+
+		dummy32 = be32_to_cpup(p++);
+		READ_BUF(dummy32);
+		len += XDR_QUADLEN(dummy32) << 2;
+		READMEM(buf, dummy32);
+		status = nfsd4_decode_ace_who(ace, argp->rqstp,
+					      buf, dummy32);
+		if (status)
+			return status;
+	}
+	*plen += len;
+
+	DECODE_TAIL;
+}
+
 static __be32
 nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
 		   struct iattr *iattr, struct richacl **acl,
@@ -312,6 +374,7 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
 
 	DECODE_HEAD;
 	iattr->ia_valid = 0;
+	*acl = NULL;
 	if ((status = nfsd4_decode_bitmap(argp, bmval)))
 		return status;
 
@@ -325,50 +388,18 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
 		iattr->ia_valid |= ATTR_SIZE;
 	}
 	if (bmval[0] & FATTR4_WORD0_ACL) {
-		u32 nace;
-		struct richace *ace;
-
-		READ_BUF(4); len += 4;
-		nace = be32_to_cpup(p++);
-
-		if (nace > NFSD4_ACL_MAX)
-			return nfserr_fbig;
+		if (bmval[1] & FATTR4_WORD1_DACL)
+			return nfserr_inval;
 
-		*acl = svcxdr_alloc_richacl(argp, nace);
-		if (*acl == NULL)
+		status = nfsd4_decode_acl_entries(argp, acl,
+				~NFS4_ACE_INHERITED_ACE,
+				nfsd4_ace_mask(argp->minorversion),
+				&len);
+		if (status)
+			return status;
+		else if (*acl == NULL)
 			return nfserr_jukebox;
-
-		richacl_for_each_entry(ace, *acl) {
-			READ_BUF(16); len += 16;
-
-			dummy32 = be32_to_cpup(p++);
-			if (dummy32 > RICHACE_ACCESS_DENIED_ACE_TYPE)
-				return nfserr_inval;
-			ace->e_type = dummy32;
-
-			dummy32 = be32_to_cpup(p++);
-			if (dummy32 & (~RICHACE_VALID_FLAGS |
-				       RICHACE_INHERITED_ACE |
-				       RICHACE_SPECIAL_WHO))
-				return nfserr_inval;
-			ace->e_flags = dummy32;
-
-			dummy32 = be32_to_cpup(p++);
-			if (dummy32 & ~NFS4_ACE_MASK_ALL)
-				return nfserr_inval;
-			ace->e_mask = dummy32;
-
-			dummy32 = be32_to_cpup(p++);
-			READ_BUF(dummy32);
-			len += XDR_QUADLEN(dummy32) << 2;
-			READMEM(buf, dummy32);
-			status = nfsd4_decode_ace_who(ace, argp->rqstp,
-						      buf, dummy32);
-			if (status)
-				return status;
-		}
-	} else
-		*acl = NULL;
+	}
 	if (bmval[1] & FATTR4_WORD1_MODE) {
 		READ_BUF(4);
 		len += 4;
@@ -436,6 +467,22 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
 			goto xdr_error;
 		}
 	}
+	if (bmval[1] & FATTR4_WORD1_DACL) {
+		READ_BUF(4);
+		len += 4;
+		dummy32 = be32_to_cpup(p++);
+		if (dummy32 & (~RICHACL_VALID_FLAGS | RICHACL_MASKED))
+			return nfserr_inval;
+		status = nfsd4_decode_acl_entries(argp, acl,
+				~0,
+				nfsd4_ace_mask(argp->minorversion),
+				&len);
+		if (status)
+			return status;
+		else if (*acl == NULL)
+			return nfserr_jukebox;
+		(*acl)->a_flags = dummy32;
+	}
 
 	label->len = 0;
 #ifdef CONFIG_NFSD_V4_SECURITY_LABEL
@@ -2218,6 +2265,42 @@ static int get_parent_attributes(struct svc_export *exp, struct kstat *stat)
 	return err;
 }
 
+static __be32 nfsd4_encode_acl_entries(struct xdr_stream *xdr,
+		struct richacl *acl, struct svc_rqst *rqstp,
+		unsigned short flags_mask, unsigned int ace_mask)
+{
+	__be32 *p;
+
+	flags_mask &= ~RICHACE_SPECIAL_WHO;
+
+	p = xdr_reserve_space(xdr, 4);
+	if (!p)
+		return nfserr_resource;
+
+	if (acl == NULL) {
+		*p++ = cpu_to_be32(0);
+	} else {
+		struct richace *ace;
+
+		*p++ = cpu_to_be32(acl->a_count);
+
+		richacl_for_each_entry(ace, acl) {
+			__be32 status;
+
+			p = xdr_reserve_space(xdr, 4*3);
+			if (!p)
+				return nfserr_resource;
+			*p++ = cpu_to_be32(ace->e_type);
+			*p++ = cpu_to_be32(ace->e_flags & flags_mask);
+			*p++ = cpu_to_be32(ace->e_mask & ace_mask);
+			status = nfsd4_encode_ace_who(xdr, rqstp, ace);
+			if (status)
+				return status;
+		}
+	}
+	return 0;
+}
+
 /*
  * Note: @fhp can be NULL; in this case, we might have to compose the filehandle
  * ourselves.
@@ -2288,15 +2371,16 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 			goto out;
 		fhp = tempfh;
 	}
-	if (bmval0 & FATTR4_WORD0_ACL) {
+	if ((bmval0 & FATTR4_WORD0_ACL) || (bmval1 & FATTR4_WORD1_DACL)) {
 		acl = nfsd4_get_acl(rqstp, dentry);
 		if (IS_ERR(acl)) {
 			err = PTR_ERR(acl);
 			acl = NULL;
 		}
-		if (err == -EOPNOTSUPP)
+		if (err == -EOPNOTSUPP) {
 			bmval0 &= ~FATTR4_WORD0_ACL;
-		else if (err == -EINVAL) {
+			bmval1 &= ~FATTR4_WORD1_DACL;
+		} else if (err == -EINVAL) {
 			status = nfserr_attrnotsupp;
 			goto out;
 		} else if (err != 0)
@@ -2354,6 +2438,8 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 
 		if (!IS_ACL(d_inode(dentry)))
 			word0 &= ~FATTR4_WORD0_ACL;
+		if (!IS_RICHACL(d_inode(dentry)))
+			word1 &= ~FATTR4_WORD1_DACL;
 		if (!contextsupport)
 			word2 &= ~FATTR4_WORD2_SECURITY_LABEL;
 		if (!word2) {
@@ -2467,35 +2553,12 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 		*p++ = cpu_to_be32(rdattr_err);
 	}
 	if (bmval0 & FATTR4_WORD0_ACL) {
-		struct richace *ace;
-
-		if (acl == NULL) {
-			p = xdr_reserve_space(xdr, 4);
-			if (!p)
-				goto out_resource;
-
-			*p++ = cpu_to_be32(0);
-			goto out_acl;
-		}
-		p = xdr_reserve_space(xdr, 4);
-		if (!p)
-			goto out_resource;
-		*p++ = cpu_to_be32(acl->a_count);
-
-		richacl_for_each_entry(ace, acl) {
-			p = xdr_reserve_space(xdr, 4*3);
-			if (!p)
-				goto out_resource;
-			*p++ = cpu_to_be32(ace->e_type);
-			*p++ = cpu_to_be32(ace->e_flags &
-				~(RICHACE_SPECIAL_WHO | RICHACE_INHERITED_ACE));
-			*p++ = cpu_to_be32(ace->e_mask & NFS4_ACE_MASK_ALL);
-			status = nfsd4_encode_ace_who(xdr, rqstp, ace);
-			if (status)
-				goto out;
-		}
+		status = nfsd4_encode_acl_entries(xdr, acl, rqstp,
+				~NFS4_ACE_INHERITED_ACE,
+				nfsd4_ace_mask(minorversion));
+		if (status)
+			goto out;
 	}
-out_acl:
 	if (bmval0 & FATTR4_WORD0_ACLSUPPORT) {
 		p = xdr_reserve_space(xdr, 4);
 		if (!p)
@@ -2704,6 +2767,16 @@ out_acl:
 			get_parent_attributes(exp, &stat);
 		p = xdr_encode_hyper(p, stat.ino);
 	}
+	if (bmval1 & FATTR4_WORD1_DACL) {
+		p = xdr_reserve_space(xdr, 4);
+		if (!p)
+			goto out_resource;
+		*p++ = cpu_to_be32(acl->a_flags);
+		status = nfsd4_encode_acl_entries(xdr, acl, rqstp,
+				~0, nfsd4_ace_mask(minorversion));
+		if (status)
+			goto out;
+	}
 #ifdef CONFIG_NFSD_PNFS
 	if ((bmval1 & FATTR4_WORD1_FS_LAYOUT_TYPES) ||
 	    (bmval2 & FATTR4_WORD2_LAYOUT_TYPES)) {
diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h
index cf98052..cb5c3ed 100644
--- a/fs/nfsd/nfsd.h
+++ b/fs/nfsd/nfsd.h
@@ -339,7 +339,8 @@ void		nfsd_lockd_shutdown(void);
 	NFSD4_SUPPORTED_ATTRS_WORD0
 
 #define NFSD4_1_SUPPORTED_ATTRS_WORD1 \
-	(NFSD4_SUPPORTED_ATTRS_WORD1	| PNFSD_SUPPORTED_ATTRS_WORD1)
+	(NFSD4_SUPPORTED_ATTRS_WORD1	| PNFSD_SUPPORTED_ATTRS_WORD1 | \
+	 FATTR4_WORD1_DACL)
 
 #define NFSD4_1_SUPPORTED_ATTRS_WORD2 \
 	(NFSD4_SUPPORTED_ATTRS_WORD2	| PNFSD_SUPPORTED_ATTRS_WORD2 | \
@@ -386,7 +387,8 @@ static inline u32 nfsd_suppattrs2(u32 minorversion)
 	(FATTR4_WORD0_SIZE | FATTR4_WORD0_ACL)
 #define NFSD_WRITEABLE_ATTRS_WORD1 \
 	(FATTR4_WORD1_MODE | FATTR4_WORD1_OWNER | FATTR4_WORD1_OWNER_GROUP \
-	| FATTR4_WORD1_TIME_ACCESS_SET | FATTR4_WORD1_TIME_MODIFY_SET)
+	| FATTR4_WORD1_TIME_ACCESS_SET | FATTR4_WORD1_TIME_MODIFY_SET \
+	| FATTR4_WORD1_DACL)
 #ifdef CONFIG_NFSD_V4_SECURITY_LABEL
 #define NFSD_WRITEABLE_ATTRS_WORD2 FATTR4_WORD2_SECURITY_LABEL
 #else
diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h
index 992ddc4..1bd4aea 100644
--- a/include/linux/nfs4.h
+++ b/include/linux/nfs4.h
@@ -394,6 +394,7 @@ enum lock_type4 {
 #define FATTR4_WORD1_TIME_MODIFY        (1UL << 21)
 #define FATTR4_WORD1_TIME_MODIFY_SET    (1UL << 22)
 #define FATTR4_WORD1_MOUNTED_ON_FILEID  (1UL << 23)
+#define FATTR4_WORD1_DACL               (1UL << 26)
 #define FATTR4_WORD1_FS_LAYOUT_TYPES    (1UL << 30)
 #define FATTR4_WORD2_LAYOUT_TYPES       (1UL << 0)
 #define FATTR4_WORD2_LAYOUT_BLKSIZE     (1UL << 1)
diff --git a/include/uapi/linux/nfs4.h b/include/uapi/linux/nfs4.h
index 2119c7c..64e4c6c 100644
--- a/include/uapi/linux/nfs4.h
+++ b/include/uapi/linux/nfs4.h
@@ -121,7 +121,8 @@
 #define NFS4_ACE_GENERIC_READ                 0x00120081
 #define NFS4_ACE_GENERIC_WRITE                0x00160106
 #define NFS4_ACE_GENERIC_EXECUTE              0x001200A0
-#define NFS4_ACE_MASK_ALL                     0x001F01FF
+#define NFS40_ACE_MASK_ALL                    0x001F01FF
+#define NFS4_ACE_MASK_ALL                     0x001F07FF
 
 #define EXCHGID4_FLAG_SUPP_MOVED_REFER		0x00000001
 #define EXCHGID4_FLAG_SUPP_MOVED_MIGR		0x00000002
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 32/41] nfsd: Add support for the MAY_CREATE_{FILE,DIR} permissions
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

For local file systems, the vfs performs the necessary permission checks
for operations like creating files and directories.  NFSd duplicates
several of those checks.  The vfs checks have been extended to check for
additional permissions like MAY_CREATE_FILE and MY_CREATE_DIR; the nfsd
checks currently lack those extensions.

Ideally, all duplicate checks should be removed; for now, just fix the
duplicate checks instead though.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/nfsd/nfs4proc.c |  5 +++--
 fs/nfsd/nfsfh.c    |  8 ++++----
 fs/nfsd/vfs.c      | 28 ++++++++++++++++++++--------
 fs/nfsd/vfs.h      | 17 +++++++++--------
 4 files changed, 36 insertions(+), 22 deletions(-)

diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index ef9e6cd..5213945 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -601,14 +601,15 @@ static __be32
 nfsd4_create(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 	     struct nfsd4_create *create)
 {
+	int access = create->cr_type == NF4DIR ?
+		NFSD_MAY_CREATE_DIR : NFSD_MAY_CREATE_FILE;
 	struct svc_fh resfh;
 	__be32 status;
 	dev_t rdev;
 
 	fh_init(&resfh, NFS4_FHSIZE);
 
-	status = fh_verify(rqstp, &cstate->current_fh, S_IFDIR,
-			   NFSD_MAY_CREATE);
+	status = fh_verify(rqstp, &cstate->current_fh, S_IFDIR, access);
 	if (status)
 		return status;
 
diff --git a/fs/nfsd/nfsfh.c b/fs/nfsd/nfsfh.c
index 350041a..7159316 100644
--- a/fs/nfsd/nfsfh.c
+++ b/fs/nfsd/nfsfh.c
@@ -319,10 +319,10 @@ fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, umode_t type, int access)
 	/*
 	 * We still have to do all these permission checks, even when
 	 * fh_dentry is already set:
-	 * 	- fh_verify may be called multiple times with different
-	 * 	  "access" arguments (e.g. nfsd_proc_create calls
-	 * 	  fh_verify(...,NFSD_MAY_EXEC) first, then later (in
-	 * 	  nfsd_create) calls fh_verify(...,NFSD_MAY_CREATE).
+	 *	- fh_verify may be called multiple times with different
+	 *	  "access" arguments (e.g. nfsd_proc_create calls
+	 *	  fh_verify(...,NFSD_MAY_EXEC) first, then later (in
+	 *	  nfsd_create) calls fh_verify(...,NFSD_MAY_CREATE_FILE).
 	 *	- in the NFSv4 case, the filehandle may have been filled
 	 *	  in by fh_compose, and given a dentry, but further
 	 *	  compound operations performed with that filehandle
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index b5e077a..bd19096 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -1128,6 +1128,8 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
 	__be32		err;
 	__be32		err2;
 	int		host_err;
+	int access = (type == S_IFDIR) ?
+		NFSD_MAY_CREATE_DIR : NFSD_MAY_CREATE_FILE;
 
 	err = nfserr_perm;
 	if (!flen)
@@ -1136,7 +1138,7 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
 	if (isdotent(fname, flen))
 		goto out;
 
-	err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE);
+	err = fh_verify(rqstp, fhp, S_IFDIR, access);
 	if (err)
 		goto out;
 
@@ -1307,7 +1309,7 @@ do_nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
 
 	/* If file doesn't exist, check for permissions to create one */
 	if (d_really_is_negative(dchild)) {
-		err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE);
+		err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE_FILE);
 		if (err)
 			goto out;
 	}
@@ -1491,7 +1493,7 @@ nfsd_symlink(struct svc_rqst *rqstp, struct svc_fh *fhp,
 	if (isdotent(fname, flen))
 		goto out;
 
-	err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE);
+	err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE_FILE);
 	if (err)
 		goto out;
 
@@ -1538,7 +1540,7 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp,
 	__be32		err;
 	int		host_err;
 
-	err = fh_verify(rqstp, ffhp, S_IFDIR, NFSD_MAY_CREATE);
+	err = fh_verify(rqstp, ffhp, S_IFDIR, NFSD_MAY_CREATE_FILE);
 	if (err)
 		goto out;
 	err = fh_verify(rqstp, tfhp, 0, NFSD_MAY_NOP);
@@ -1610,11 +1612,12 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
 	struct inode	*fdir, *tdir;
 	__be32		err;
 	int		host_err;
+	int		access;
 
 	err = fh_verify(rqstp, ffhp, S_IFDIR, NFSD_MAY_REMOVE);
 	if (err)
 		goto out;
-	err = fh_verify(rqstp, tfhp, S_IFDIR, NFSD_MAY_CREATE);
+	err = fh_verify(rqstp, tfhp, S_IFDIR, NFSD_MAY_NOP);
 	if (err)
 		goto out;
 
@@ -1653,6 +1656,13 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
 	if (odentry == trap)
 		goto out_dput_old;
 
+	host_err = 0;
+	access = S_ISDIR(d_inode(odentry)->i_mode) ?
+		NFSD_MAY_CREATE_DIR : NFSD_MAY_CREATE_FILE;
+	err = fh_verify(rqstp, tfhp, S_IFDIR, access);
+	if (err)
+		goto out_dput_old;
+
 	ndentry = lookup_one_len(tname, tdentry, tlen);
 	host_err = PTR_ERR(ndentry);
 	if (IS_ERR(ndentry))
@@ -1678,7 +1688,8 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
  out_dput_old:
 	dput(odentry);
  out_nfserr:
-	err = nfserrno(host_err);
+	if (host_err)
+		err = nfserrno(host_err);
 	/*
 	 * We cannot rely on fh_unlock on the two filehandles,
 	 * as that would do the wrong thing if the two directories
@@ -2011,8 +2022,9 @@ nfsd_permission(struct svc_rqst *rqstp, struct svc_export *exp,
 	    uid_eq(inode->i_uid, current_fsuid()))
 		return 0;
 
-	/* This assumes  NFSD_MAY_{READ,WRITE,EXEC} == MAY_{READ,WRITE,EXEC} */
-	err = inode_permission(inode, acc & (MAY_READ|MAY_WRITE|MAY_EXEC));
+	/* This assumes NFSD_MAY_{READ,WRITE,EXEC} == MAY_{READ,WRITE,EXEC}. */
+	err = inode_permission(inode, acc & (MAY_READ|MAY_WRITE|MAY_EXEC|
+					     MAY_CREATE_DIR|MAY_CREATE_FILE));
 
 	/* Allow read access to binaries even when mode 111 */
 	if (err == -EACCES && S_ISREG(inode->i_mode) &&
diff --git a/fs/nfsd/vfs.h b/fs/nfsd/vfs.h
index 5be875e..384fda4 100644
--- a/fs/nfsd/vfs.h
+++ b/fs/nfsd/vfs.h
@@ -19,18 +19,19 @@
 #define NFSD_MAY_TRUNC			0x010
 #define NFSD_MAY_LOCK			0x020
 #define NFSD_MAY_MASK			0x03f
+#define NFSD_MAY_CREATE_FILE		0x103 /* == MAY_{EXEC|WRITE|CREATE_FILE} */
+#define NFSD_MAY_CREATE_DIR		0x203 /* == MAY_{EXEC|WRITE|CREATE_DIR} */
 
 /* extra hints to permission and open routines: */
-#define NFSD_MAY_OWNER_OVERRIDE		0x040
-#define NFSD_MAY_LOCAL_ACCESS		0x080 /* for device special files */
-#define NFSD_MAY_BYPASS_GSS_ON_ROOT	0x100
-#define NFSD_MAY_NOT_BREAK_LEASE	0x200
-#define NFSD_MAY_BYPASS_GSS		0x400
-#define NFSD_MAY_READ_IF_EXEC		0x800
+#define NFSD_MAY_OWNER_OVERRIDE		0x04000
+#define NFSD_MAY_LOCAL_ACCESS		0x08000 /* for device special files */
+#define NFSD_MAY_BYPASS_GSS_ON_ROOT	0x10000
+#define NFSD_MAY_NOT_BREAK_LEASE	0x20000
+#define NFSD_MAY_BYPASS_GSS		0x40000
+#define NFSD_MAY_READ_IF_EXEC		0x80000
 
-#define NFSD_MAY_64BIT_COOKIE		0x1000 /* 64 bit readdir cookies for >= NFSv3 */
+#define NFSD_MAY_64BIT_COOKIE		0x100000 /* 64 bit readdir cookies for >= NFSv3 */
 
-#define NFSD_MAY_CREATE		(NFSD_MAY_EXEC|NFSD_MAY_WRITE)
 #define NFSD_MAY_REMOVE		(NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC)
 
 /*
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 32/41] nfsd: Add support for the MAY_CREATE_{FILE,DIR} permissions
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

For local file systems, the vfs performs the necessary permission checks
for operations like creating files and directories.  NFSd duplicates
several of those checks.  The vfs checks have been extended to check for
additional permissions like MAY_CREATE_FILE and MY_CREATE_DIR; the nfsd
checks currently lack those extensions.

Ideally, all duplicate checks should be removed; for now, just fix the
duplicate checks instead though.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/nfsd/nfs4proc.c |  5 +++--
 fs/nfsd/nfsfh.c    |  8 ++++----
 fs/nfsd/vfs.c      | 28 ++++++++++++++++++++--------
 fs/nfsd/vfs.h      | 17 +++++++++--------
 4 files changed, 36 insertions(+), 22 deletions(-)

diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index ef9e6cd..5213945 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -601,14 +601,15 @@ static __be32
 nfsd4_create(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 	     struct nfsd4_create *create)
 {
+	int access = create->cr_type == NF4DIR ?
+		NFSD_MAY_CREATE_DIR : NFSD_MAY_CREATE_FILE;
 	struct svc_fh resfh;
 	__be32 status;
 	dev_t rdev;
 
 	fh_init(&resfh, NFS4_FHSIZE);
 
-	status = fh_verify(rqstp, &cstate->current_fh, S_IFDIR,
-			   NFSD_MAY_CREATE);
+	status = fh_verify(rqstp, &cstate->current_fh, S_IFDIR, access);
 	if (status)
 		return status;
 
diff --git a/fs/nfsd/nfsfh.c b/fs/nfsd/nfsfh.c
index 350041a..7159316 100644
--- a/fs/nfsd/nfsfh.c
+++ b/fs/nfsd/nfsfh.c
@@ -319,10 +319,10 @@ fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, umode_t type, int access)
 	/*
 	 * We still have to do all these permission checks, even when
 	 * fh_dentry is already set:
-	 * 	- fh_verify may be called multiple times with different
-	 * 	  "access" arguments (e.g. nfsd_proc_create calls
-	 * 	  fh_verify(...,NFSD_MAY_EXEC) first, then later (in
-	 * 	  nfsd_create) calls fh_verify(...,NFSD_MAY_CREATE).
+	 *	- fh_verify may be called multiple times with different
+	 *	  "access" arguments (e.g. nfsd_proc_create calls
+	 *	  fh_verify(...,NFSD_MAY_EXEC) first, then later (in
+	 *	  nfsd_create) calls fh_verify(...,NFSD_MAY_CREATE_FILE).
 	 *	- in the NFSv4 case, the filehandle may have been filled
 	 *	  in by fh_compose, and given a dentry, but further
 	 *	  compound operations performed with that filehandle
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index b5e077a..bd19096 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -1128,6 +1128,8 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
 	__be32		err;
 	__be32		err2;
 	int		host_err;
+	int access = (type == S_IFDIR) ?
+		NFSD_MAY_CREATE_DIR : NFSD_MAY_CREATE_FILE;
 
 	err = nfserr_perm;
 	if (!flen)
@@ -1136,7 +1138,7 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
 	if (isdotent(fname, flen))
 		goto out;
 
-	err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE);
+	err = fh_verify(rqstp, fhp, S_IFDIR, access);
 	if (err)
 		goto out;
 
@@ -1307,7 +1309,7 @@ do_nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
 
 	/* If file doesn't exist, check for permissions to create one */
 	if (d_really_is_negative(dchild)) {
-		err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE);
+		err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE_FILE);
 		if (err)
 			goto out;
 	}
@@ -1491,7 +1493,7 @@ nfsd_symlink(struct svc_rqst *rqstp, struct svc_fh *fhp,
 	if (isdotent(fname, flen))
 		goto out;
 
-	err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE);
+	err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE_FILE);
 	if (err)
 		goto out;
 
@@ -1538,7 +1540,7 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp,
 	__be32		err;
 	int		host_err;
 
-	err = fh_verify(rqstp, ffhp, S_IFDIR, NFSD_MAY_CREATE);
+	err = fh_verify(rqstp, ffhp, S_IFDIR, NFSD_MAY_CREATE_FILE);
 	if (err)
 		goto out;
 	err = fh_verify(rqstp, tfhp, 0, NFSD_MAY_NOP);
@@ -1610,11 +1612,12 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
 	struct inode	*fdir, *tdir;
 	__be32		err;
 	int		host_err;
+	int		access;
 
 	err = fh_verify(rqstp, ffhp, S_IFDIR, NFSD_MAY_REMOVE);
 	if (err)
 		goto out;
-	err = fh_verify(rqstp, tfhp, S_IFDIR, NFSD_MAY_CREATE);
+	err = fh_verify(rqstp, tfhp, S_IFDIR, NFSD_MAY_NOP);
 	if (err)
 		goto out;
 
@@ -1653,6 +1656,13 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
 	if (odentry == trap)
 		goto out_dput_old;
 
+	host_err = 0;
+	access = S_ISDIR(d_inode(odentry)->i_mode) ?
+		NFSD_MAY_CREATE_DIR : NFSD_MAY_CREATE_FILE;
+	err = fh_verify(rqstp, tfhp, S_IFDIR, access);
+	if (err)
+		goto out_dput_old;
+
 	ndentry = lookup_one_len(tname, tdentry, tlen);
 	host_err = PTR_ERR(ndentry);
 	if (IS_ERR(ndentry))
@@ -1678,7 +1688,8 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
  out_dput_old:
 	dput(odentry);
  out_nfserr:
-	err = nfserrno(host_err);
+	if (host_err)
+		err = nfserrno(host_err);
 	/*
 	 * We cannot rely on fh_unlock on the two filehandles,
 	 * as that would do the wrong thing if the two directories
@@ -2011,8 +2022,9 @@ nfsd_permission(struct svc_rqst *rqstp, struct svc_export *exp,
 	    uid_eq(inode->i_uid, current_fsuid()))
 		return 0;
 
-	/* This assumes  NFSD_MAY_{READ,WRITE,EXEC} == MAY_{READ,WRITE,EXEC} */
-	err = inode_permission(inode, acc & (MAY_READ|MAY_WRITE|MAY_EXEC));
+	/* This assumes NFSD_MAY_{READ,WRITE,EXEC} == MAY_{READ,WRITE,EXEC}. */
+	err = inode_permission(inode, acc & (MAY_READ|MAY_WRITE|MAY_EXEC|
+					     MAY_CREATE_DIR|MAY_CREATE_FILE));
 
 	/* Allow read access to binaries even when mode 111 */
 	if (err == -EACCES && S_ISREG(inode->i_mode) &&
diff --git a/fs/nfsd/vfs.h b/fs/nfsd/vfs.h
index 5be875e..384fda4 100644
--- a/fs/nfsd/vfs.h
+++ b/fs/nfsd/vfs.h
@@ -19,18 +19,19 @@
 #define NFSD_MAY_TRUNC			0x010
 #define NFSD_MAY_LOCK			0x020
 #define NFSD_MAY_MASK			0x03f
+#define NFSD_MAY_CREATE_FILE		0x103 /* == MAY_{EXEC|WRITE|CREATE_FILE} */
+#define NFSD_MAY_CREATE_DIR		0x203 /* == MAY_{EXEC|WRITE|CREATE_DIR} */
 
 /* extra hints to permission and open routines: */
-#define NFSD_MAY_OWNER_OVERRIDE		0x040
-#define NFSD_MAY_LOCAL_ACCESS		0x080 /* for device special files */
-#define NFSD_MAY_BYPASS_GSS_ON_ROOT	0x100
-#define NFSD_MAY_NOT_BREAK_LEASE	0x200
-#define NFSD_MAY_BYPASS_GSS		0x400
-#define NFSD_MAY_READ_IF_EXEC		0x800
+#define NFSD_MAY_OWNER_OVERRIDE		0x04000
+#define NFSD_MAY_LOCAL_ACCESS		0x08000 /* for device special files */
+#define NFSD_MAY_BYPASS_GSS_ON_ROOT	0x10000
+#define NFSD_MAY_NOT_BREAK_LEASE	0x20000
+#define NFSD_MAY_BYPASS_GSS		0x40000
+#define NFSD_MAY_READ_IF_EXEC		0x80000
 
-#define NFSD_MAY_64BIT_COOKIE		0x1000 /* 64 bit readdir cookies for >= NFSv3 */
+#define NFSD_MAY_64BIT_COOKIE		0x100000 /* 64 bit readdir cookies for >= NFSv3 */
 
-#define NFSD_MAY_CREATE		(NFSD_MAY_EXEC|NFSD_MAY_WRITE)
 #define NFSD_MAY_REMOVE		(NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC)
 
 /*
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 33/41] richacl: Add support for unmapped identifiers
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

Some remote file systems like nfs may return user or group identifiers
that cannot be mapped to local uids / gids.  Allow to represent such
unmapped identifiers in richacls.  (We still cannot represent unmapped
owners and owning groups, however.)

In the in-memory representation, the richacl is followed by a list of
NUL-terminated strings, with no padding.  Entries with an unmapped
identifier have the RICHACE_UNMAPPED_WHO flag set, and ace->e_id.offs
specifies the offset into this list.  Multiple entries can refer to the
same offset.

The xattr representation is similar, but ace->e_id is ignored, and the
list of unmapped identifier strings contains a string for each acl entry
whose RICHACE_UNMAPPED_WHO flag is set.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/richacl_base.c       | 139 ++++++++++++++++++++++++++++++++++++++++++++----
 fs/richacl_compat.c     |  18 +++----
 fs/richacl_inode.c      |   4 +-
 fs/richacl_xattr.c      |  69 ++++++++++++++++++++----
 include/linux/richacl.h |  33 ++++++++++--
 5 files changed, 227 insertions(+), 36 deletions(-)

diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index a85b7a3..281e396 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -23,22 +23,25 @@
 MODULE_LICENSE("GPL");
 
 /**
- * richacl_alloc  -  allocate a richacl
+ * __richacl_alloc  -  allocate a richacl
  * @count:	number of entries
+ * @unmapped_size:	size to reserve for unmapped identifiers
  */
 struct richacl *
-richacl_alloc(int count, gfp_t gfp)
+__richacl_alloc(unsigned int count, size_t unmapped_size, gfp_t gfp)
 {
-	size_t size = sizeof(struct richacl) + count * sizeof(struct richace);
+	size_t size = sizeof(struct richacl) + count * sizeof(struct richace) +
+		      unmapped_size;
 	struct richacl *acl = kzalloc(size, gfp);
 
 	if (acl) {
 		atomic_set(&acl->a_base.ba_refcount, 1);
 		acl->a_count = count;
+		acl->a_unmapped_size = unmapped_size;
 	}
 	return acl;
 }
-EXPORT_SYMBOL_GPL(richacl_alloc);
+EXPORT_SYMBOL_GPL(__richacl_alloc);
 
 /**
  * richacl_clone  -  create a copy of a richacl
@@ -47,7 +50,8 @@ struct richacl *
 richacl_clone(const struct richacl *acl, gfp_t gfp)
 {
 	int count = acl->a_count;
-	size_t size = sizeof(struct richacl) + count * sizeof(struct richace);
+	size_t size = sizeof(struct richacl) + count * sizeof(struct richace) +
+		      acl->a_unmapped_size;
 	struct richacl *dup = kmalloc(size, gfp);
 
 	if (dup) {
@@ -59,6 +63,9 @@ richacl_clone(const struct richacl *acl, gfp_t gfp)
 
 /**
  * richace_copy  -  copy an acl entry
+ *
+ * If @from has an unmapped who value (from->e_flags & RICHACE_UNMAPPED_WHO),
+ * it can only be copied within the same acl!
  */
 void
 richace_copy(struct richace *to, const struct richace *from)
@@ -66,6 +73,82 @@ richace_copy(struct richace *to, const struct richace *from)
 	memcpy(to, from, sizeof(struct richace));
 }
 
+/**
+ * richacl_add_unmapped_identifier
+ * @pacl:	Pointer to an acl
+ * @pace:	acl entry within @acl
+ * @who:	unmapped identifier
+ * @len:	length of @who
+ * @gfp:	memory allocation flags
+ *
+ * Add an unmapped identifier to an acl, possibly reallocating the acl.
+ */
+int richacl_add_unmapped_identifier(struct richacl **pacl,
+				    struct richace **pace,
+				    const char *who,
+				    unsigned int len, gfp_t gfp)
+{
+	struct richacl *acl = *pacl;
+	size_t size = sizeof(struct richacl) +
+		      acl->a_count * sizeof(struct richace) +
+		      acl->a_unmapped_size + len + 1;
+	unsigned int index = *pace - acl->a_entries;
+
+	acl = krealloc(*pacl, size, gfp);
+	if (acl) {
+		char *unmapped = (char *)(acl->a_entries + acl->a_count);
+		struct richace *ace = acl->a_entries + index;
+
+		ace->e_flags |= RICHACE_UNMAPPED_WHO;
+		ace->e_flags &= ~RICHACE_SPECIAL_WHO;
+		ace->e_id.offs = acl->a_unmapped_size;
+		memcpy(unmapped + ace->e_id.offs, who, len);
+		unmapped[ace->e_id.offs + len] = 0;
+		acl->a_unmapped_size += len + 1;
+		*pace = ace;
+		*pacl = acl;
+		return 0;
+	}
+	return -1;
+}
+EXPORT_SYMBOL_GPL(richacl_add_unmapped_identifier);
+
+/**
+ * richace_unmapped_identifier  -  get unmapped identifier
+ * @acl:	acl containing @ace
+ * @ace:	acl entry
+ *
+ * Get the unmapped identifier of @ace as a NUL-terminated string, or NULL if
+ * @ace doesn't have an unmapped identifier.
+ */
+const char *richace_unmapped_identifier(const struct richace *ace,
+					const struct richacl *acl)
+{
+	const char *unmapped = (char *)(acl->a_entries + acl->a_count);
+
+	if (!(ace->e_flags & RICHACE_UNMAPPED_WHO))
+		return NULL;
+	return unmapped + ace->e_id.offs;
+}
+EXPORT_SYMBOL(richace_unmapped_identifier);
+
+/**
+ * richacl_has_unmapped_identifiers
+ *
+ * Check if an acl has unmapped identifiers.
+ */
+bool richacl_has_unmapped_identifiers(struct richacl *acl)
+{
+	struct richace *ace;
+
+	richacl_for_each_entry(ace, acl) {
+		if (ace->e_flags & RICHACE_UNMAPPED_WHO)
+			return true;
+	}
+	return false;
+}
+EXPORT_SYMBOL_GPL(richacl_has_unmapped_identifiers);
+
 /*
  * richacl_mask_to_mode  -  compute the file permission bits from mask
  * @mask:	%RICHACE_* permission mask
@@ -213,7 +296,7 @@ static unsigned int richacl_allowed_to_who(struct richacl *acl,
 	richacl_for_each_entry_reverse(ace, acl) {
 		if (richace_is_inherit_only(ace))
 			continue;
-		if (richace_is_same_identifier(ace, who) ||
+		if (richace_is_same_identifier(acl, ace, who) ||
 		    richace_is_everyone(ace)) {
 			if (richace_is_allow(ace))
 				allowed |= ace->e_mask;
@@ -502,46 +585,73 @@ richacl_inherit(const struct richacl *dir_acl, int isdir)
 	const struct richace *dir_ace;
 	struct richacl *acl = NULL;
 	struct richace *ace;
-	int count = 0;
+	unsigned int count = 0, unmapped_size = 0, offset = 0;
+	const char *dir_unmapped;
+	char *unmapped;
 
 	if (isdir) {
 		richacl_for_each_entry(dir_ace, dir_acl) {
 			if (!richace_is_inheritable(dir_ace))
 				continue;
+
 			count++;
+			dir_unmapped =
+				richace_unmapped_identifier(dir_ace, dir_acl);
+			if (dir_unmapped)
+				unmapped_size += strlen(dir_unmapped) + 1;
 		}
 		if (!count)
 			return NULL;
-		acl = richacl_alloc(count, GFP_KERNEL);
+		acl = __richacl_alloc(count, unmapped_size, GFP_KERNEL);
 		if (!acl)
 			return ERR_PTR(-ENOMEM);
 		ace = acl->a_entries;
+		unmapped = (char *)(acl->a_entries + acl->a_count);
 		richacl_for_each_entry(dir_ace, dir_acl) {
 			if (!richace_is_inheritable(dir_ace))
 				continue;
+
 			richace_copy(ace, dir_ace);
 			if (dir_ace->e_flags & RICHACE_NO_PROPAGATE_INHERIT_ACE)
 				ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
 			else if ((dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE) &&
 				 !(dir_ace->e_flags & RICHACE_DIRECTORY_INHERIT_ACE))
 				ace->e_flags |= RICHACE_INHERIT_ONLY_ACE;
+
+			dir_unmapped =
+				richace_unmapped_identifier(dir_ace, dir_acl);
+			if (dir_unmapped) {
+				size_t sz = strlen(dir_unmapped) + 1;
+
+				ace->e_id.offs = offset;
+				memcpy(unmapped, dir_unmapped, sz);
+				unmapped += sz;
+				offset += sz;
+			}
 			ace++;
 		}
 	} else {
 		richacl_for_each_entry(dir_ace, dir_acl) {
 			if (!(dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE))
 				continue;
+
 			count++;
+			dir_unmapped =
+				richace_unmapped_identifier(dir_ace, dir_acl);
+			if (dir_unmapped)
+				unmapped_size += strlen(dir_unmapped) + 1;
 		}
 		if (!count)
 			return NULL;
-		acl = richacl_alloc(count, GFP_KERNEL);
+		acl = __richacl_alloc(count, unmapped_size, GFP_KERNEL);
 		if (!acl)
 			return ERR_PTR(-ENOMEM);
 		ace = acl->a_entries;
+		unmapped = (char *)(acl->a_entries + acl->a_count);
 		richacl_for_each_entry(dir_ace, dir_acl) {
 			if (!(dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE))
 				continue;
+
 			richace_copy(ace, dir_ace);
 			ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
 			/*
@@ -549,6 +659,17 @@ richacl_inherit(const struct richacl *dir_acl, int isdir)
 			 * non-directories, so clear it.
 			 */
 			ace->e_mask &= ~RICHACE_DELETE_CHILD;
+
+			dir_unmapped =
+				richace_unmapped_identifier(dir_ace, dir_acl);
+			if (dir_unmapped) {
+				size_t sz = strlen(dir_unmapped) + 1;
+
+				ace->e_id.offs = offset;
+				memcpy(unmapped, dir_unmapped, sz);
+				unmapped += sz;
+				offset += sz;
+			}
 			ace++;
 		}
 	}
diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 632409f..8aeafa8 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -71,11 +71,13 @@ richacl_insert_entry(struct richacl_alloc *alloc, struct richace **ace)
 {
 	struct richacl *acl = alloc->acl;
 	unsigned int index = *ace - acl->a_entries;
-	size_t tail_size = (acl->a_count - index) * sizeof(struct richace);
+	size_t tail_size = (acl->a_count - index) * sizeof(struct richace) +
+			   acl->a_unmapped_size;
 
 	if (alloc->count == acl->a_count) {
 		size_t new_size = sizeof(struct richacl) +
-			(acl->a_count + 1) * sizeof(struct richace);
+			(acl->a_count + 1) * sizeof(struct richace) +
+			acl->a_unmapped_size;
 
 		acl = krealloc(acl, new_size, GFP_KERNEL);
 		if (!acl)
@@ -103,10 +105,6 @@ struct richace *richacl_append_entry(struct richacl_alloc *alloc)
 	struct richacl *acl = alloc->acl;
 	struct richace *ace = acl->a_entries + acl->a_count;
 
-	if (alloc->count > alloc->acl->a_count) {
-		acl->a_count++;
-		return ace;
-	}
 	return richacl_insert_entry(alloc, &ace) ? NULL : ace;
 }
 EXPORT_SYMBOL_GPL(richacl_append_entry);
@@ -261,12 +259,12 @@ __richacl_propagate_everyone(struct richacl_alloc *alloc, struct richace *who,
 		if (richace_is_inherit_only(ace))
 			continue;
 		if (richace_is_allow(ace)) {
-			if (richace_is_same_identifier(ace, who)) {
+			if (richace_is_same_identifier(acl, ace, who)) {
 				allow &= ~ace->e_mask;
 				allow_last = ace;
 			}
 		} else if (richace_is_deny(ace)) {
-			if (richace_is_same_identifier(ace, who))
+			if (richace_is_same_identifier(acl, ace, who))
 				allow &= ~ace->e_mask;
 			else if (allow & ace->e_mask)
 				allow_last = NULL;
@@ -608,7 +606,7 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
 	richacl_for_each_entry(ace, acl) {
 		if (richace_is_inherit_only(ace))
 			continue;
-		if (richace_is_same_identifier(ace, who) &&
+		if (richace_is_same_identifier(acl, ace, who) &&
 		    richace_is_deny(ace))
 			deny &= ~ace->e_mask;
 	}
@@ -625,7 +623,7 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
 		if (richace_is_inherit_only(ace))
 			continue;
 		if (richace_is_deny(ace)) {
-			if (richace_is_same_identifier(ace, who))
+			if (richace_is_same_identifier(acl, ace, who))
 				break;
 		} else if (richace_is_allow(ace) &&
 			   (ace->e_mask & deny)) {
diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c
index df175c1..ff8b192 100644
--- a/fs/richacl_inode.c
+++ b/fs/richacl_inode.c
@@ -159,8 +159,10 @@ richacl_permission(struct inode *inode, const struct richacl *acl,
 		} else if (richace_is_unix_group(ace)) {
 			if (!in_group_p(ace->e_id.gid))
 				continue;
-		} else
+		} else if (richace_is_everyone(ace))
 			goto entry_matches_everyone;
+		else
+			continue;
 
 		/*
 		 * Apply the group file mask to entries other than owner@ and
diff --git a/fs/richacl_xattr.c b/fs/richacl_xattr.c
index cd9979d..e9622f6 100644
--- a/fs/richacl_xattr.c
+++ b/fs/richacl_xattr.c
@@ -33,7 +33,8 @@ richacl_from_xattr(struct user_namespace *user_ns,
 	const struct richace_xattr *xattr_ace = (void *)(xattr_acl + 1);
 	struct richacl *acl;
 	struct richace *ace;
-	int count;
+	unsigned int count, offset;
+	char *unmapped;
 
 	if (size < sizeof(*xattr_acl) ||
 	    xattr_acl->a_version != RICHACL_XATTR_VERSION ||
@@ -43,10 +44,11 @@ richacl_from_xattr(struct user_namespace *user_ns,
 	count = le16_to_cpu(xattr_acl->a_count);
 	if (count > RICHACL_XATTR_MAX_COUNT)
 		return ERR_PTR(-EINVAL);
-	if (size != count * sizeof(*xattr_ace))
+	if (size < count * sizeof(*xattr_ace))
 		return ERR_PTR(-EINVAL);
+	size -= count * sizeof(*xattr_ace);
 
-	acl = richacl_alloc(count, GFP_NOFS);
+	acl = __richacl_alloc(count, size, GFP_NOFS);
 	if (!acl)
 		return ERR_PTR(-ENOMEM);
 
@@ -61,6 +63,16 @@ richacl_from_xattr(struct user_namespace *user_ns,
 	if (acl->a_other_mask & ~RICHACE_VALID_MASK)
 		goto fail_einval;
 
+	unmapped = (char *)(acl->a_entries + count);
+	if (size) {
+		char *xattr_unmapped = (char *)(xattr_ace + count);
+
+		if (xattr_unmapped[size - 1] != 0)
+			goto fail_einval;
+		memcpy(unmapped, xattr_unmapped, size);
+	}
+	offset = 0;
+
 	richacl_for_each_entry(ace, acl) {
 		ace->e_type  = le16_to_cpu(xattr_ace->e_type);
 		ace->e_flags = le16_to_cpu(xattr_ace->e_flags);
@@ -72,6 +84,15 @@ richacl_from_xattr(struct user_namespace *user_ns,
 			ace->e_id.special = le32_to_cpu(xattr_ace->e_id);
 			if (ace->e_id.special > RICHACE_EVERYONE_SPECIAL_ID)
 				goto fail_einval;
+		} else if (ace->e_flags & RICHACE_UNMAPPED_WHO) {
+			size_t sz;
+
+			if (offset == size)
+				goto fail_einval;
+			ace->e_id.offs = offset;
+			sz = strlen(unmapped) + 1;
+			unmapped += sz;
+			offset += sz;
 		} else if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
 			u32 id = le32_to_cpu(xattr_ace->e_id);
 
@@ -88,10 +109,12 @@ richacl_from_xattr(struct user_namespace *user_ns,
 		if (ace->e_type > RICHACE_ACCESS_DENIED_ACE_TYPE ||
 		    (ace->e_mask & ~RICHACE_VALID_MASK))
 			goto fail_einval;
-
 		xattr_ace++;
 	}
 
+	if (offset != size)
+		goto fail_einval;
+
 	return acl;
 
 fail_einval:
@@ -107,8 +130,15 @@ size_t
 richacl_xattr_size(const struct richacl *acl)
 {
 	size_t size = sizeof(struct richacl_xattr);
+	const struct richace *ace;
 
 	size += sizeof(struct richace_xattr) * acl->a_count;
+	richacl_for_each_entry(ace, acl) {
+		const char *unmapped = richace_unmapped_identifier(ace, acl);
+
+		if (unmapped)
+			size += strlen(unmapped) + 1;
+	}
 	return size;
 }
 EXPORT_SYMBOL_GPL(richacl_xattr_size);
@@ -127,6 +157,7 @@ richacl_to_xattr(struct user_namespace *user_ns,
 	struct richace_xattr *xattr_ace;
 	const struct richace *ace;
 	size_t real_size;
+	char *xattr_unmapped;
 
 	real_size = richacl_xattr_size(acl);
 	if (!buffer)
@@ -143,18 +174,33 @@ richacl_to_xattr(struct user_namespace *user_ns,
 	xattr_acl->a_other_mask = cpu_to_le32(acl->a_other_mask);
 
 	xattr_ace = (void *)(xattr_acl + 1);
+	xattr_unmapped = (char *)(xattr_ace + acl->a_count);
 	richacl_for_each_entry(ace, acl) {
+		const char *who;
+
 		xattr_ace->e_type = cpu_to_le16(ace->e_type);
 		xattr_ace->e_flags = cpu_to_le16(ace->e_flags);
 		xattr_ace->e_mask = cpu_to_le32(ace->e_mask);
 		if (ace->e_flags & RICHACE_SPECIAL_WHO)
 			xattr_ace->e_id = cpu_to_le32(ace->e_id.special);
-		else if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
-			xattr_ace->e_id =
-				cpu_to_le32(from_kgid(user_ns, ace->e_id.gid));
-		else
-			xattr_ace->e_id =
-				cpu_to_le32(from_kuid(user_ns, ace->e_id.uid));
+		else {
+			who = richace_unmapped_identifier(ace, acl);
+			if (who) {
+				size_t sz = strlen(who) + 1;
+
+				xattr_ace->e_id = 0;
+				memcpy(xattr_unmapped, who, sz);
+				xattr_unmapped += sz;
+			} else if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
+				u32 id = from_kgid(user_ns, ace->e_id.gid);
+
+				xattr_ace->e_id = cpu_to_le32(id);
+			} else {
+				u32 id = from_kuid(user_ns, ace->e_id.uid);
+
+				xattr_ace->e_id = cpu_to_le32(id);
+			}
+		}
 		xattr_ace++;
 	}
 	return real_size;
@@ -184,7 +230,8 @@ static void richacl_fix_xattr_userns(
 		return;
 	count = size / sizeof(*xattr_ace);
 	for (; count; count--, xattr_ace++) {
-		if (xattr_ace->e_flags & cpu_to_le16(RICHACE_SPECIAL_WHO))
+		if (xattr_ace->e_flags & cpu_to_le16(RICHACE_SPECIAL_WHO |
+						     RICHACE_UNMAPPED_WHO))
 			continue;
 		if (xattr_ace->e_flags &
 		    cpu_to_le16(RICHACE_IDENTIFIER_GROUP)) {
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index f48c04e..1822666 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -29,6 +29,7 @@ struct richace {
 		kuid_t		uid;
 		kgid_t		gid;
 		unsigned int	special;
+		unsigned short	offs;  /* unmapped offset */
 	} e_id;
 };
 
@@ -39,6 +40,7 @@ struct richacl {
 	unsigned int	a_other_mask;
 	unsigned short	a_count;
 	unsigned short	a_flags;
+	unsigned short	a_unmapped_size;
 	struct richace	a_entries[0];
 };
 
@@ -77,6 +79,7 @@ struct richacl {
 #define RICHACE_INHERIT_ONLY_ACE		0x0008
 #define RICHACE_IDENTIFIER_GROUP		0x0040
 #define RICHACE_INHERITED_ACE			0x0080
+#define RICHACE_UNMAPPED_WHO			0x2000
 #define RICHACE_SPECIAL_WHO			0x4000
 
 #define RICHACE_VALID_FLAGS (					\
@@ -86,6 +89,7 @@ struct richacl {
 	RICHACE_INHERIT_ONLY_ACE |				\
 	RICHACE_IDENTIFIER_GROUP |				\
 	RICHACE_INHERITED_ACE |					\
+	RICHACE_UNMAPPED_WHO |					\
 	RICHACE_SPECIAL_WHO)
 
 #define RICHACE_INHERITANCE_FLAGS (				\
@@ -310,14 +314,28 @@ richace_is_deny(const struct richace *ace)
  * richace_is_same_identifier  -  are both identifiers the same?
  */
 static inline bool
-richace_is_same_identifier(const struct richace *a, const struct richace *b)
+richace_is_same_identifier(const struct richacl *acl,
+			   const struct richace *ace1,
+			   const struct richace *ace2)
 {
-	return !((a->e_flags ^ b->e_flags) &
-		 (RICHACE_SPECIAL_WHO | RICHACE_IDENTIFIER_GROUP)) &&
-	       !memcmp(&a->e_id, &b->e_id, sizeof(a->e_id));
+	const char *unmapped = (char *)(acl->a_entries + acl->a_count);
+
+	return !((ace1->e_flags ^ ace2->e_flags) &
+		 (RICHACE_SPECIAL_WHO |
+		  RICHACE_IDENTIFIER_GROUP |
+		  RICHACE_UNMAPPED_WHO)) &&
+	       ((ace1->e_flags & RICHACE_UNMAPPED_WHO) ?
+		!strcmp(unmapped + ace1->e_id.offs,
+			unmapped + ace2->e_id.offs) :
+		!memcmp(&ace1->e_id, &ace2->e_id, sizeof(ace1->e_id)));
+}
+
+extern struct richacl *__richacl_alloc(unsigned int, size_t, gfp_t);
+static inline struct richacl *richacl_alloc(unsigned int count, gfp_t gfp)
+{
+	return __richacl_alloc(count, 0, gfp);
 }
 
-extern struct richacl *richacl_alloc(int, gfp_t);
 extern struct richacl *richacl_clone(const struct richacl *, gfp_t);
 extern void richace_copy(struct richace *, const struct richace *);
 extern int richacl_masks_to_mode(const struct richacl *);
@@ -327,6 +345,11 @@ extern void richacl_compute_max_masks(struct richacl *);
 extern struct richacl *richacl_chmod(struct richacl *, mode_t);
 extern int richacl_equiv_mode(const struct richacl *, mode_t *);
 extern struct richacl *richacl_inherit(const struct richacl *, int);
+extern int richacl_add_unmapped_identifier(struct richacl **, struct richace **,
+					   const char *, unsigned int, gfp_t);
+extern const char *richace_unmapped_identifier(const struct richace *,
+					       const struct richacl *);
+extern bool richacl_has_unmapped_identifiers(struct richacl *);
 
 /* richacl_inode.c */
 extern int richacl_permission(struct inode *, const struct richacl *, int);
-- 
2.4.3

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 33/41] richacl: Add support for unmapped identifiers
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

Some remote file systems like nfs may return user or group identifiers
that cannot be mapped to local uids / gids.  Allow to represent such
unmapped identifiers in richacls.  (We still cannot represent unmapped
owners and owning groups, however.)

In the in-memory representation, the richacl is followed by a list of
NUL-terminated strings, with no padding.  Entries with an unmapped
identifier have the RICHACE_UNMAPPED_WHO flag set, and ace->e_id.offs
specifies the offset into this list.  Multiple entries can refer to the
same offset.

The xattr representation is similar, but ace->e_id is ignored, and the
list of unmapped identifier strings contains a string for each acl entry
whose RICHACE_UNMAPPED_WHO flag is set.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/richacl_base.c       | 139 ++++++++++++++++++++++++++++++++++++++++++++----
 fs/richacl_compat.c     |  18 +++----
 fs/richacl_inode.c      |   4 +-
 fs/richacl_xattr.c      |  69 ++++++++++++++++++++----
 include/linux/richacl.h |  33 ++++++++++--
 5 files changed, 227 insertions(+), 36 deletions(-)

diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index a85b7a3..281e396 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -23,22 +23,25 @@
 MODULE_LICENSE("GPL");
 
 /**
- * richacl_alloc  -  allocate a richacl
+ * __richacl_alloc  -  allocate a richacl
  * @count:	number of entries
+ * @unmapped_size:	size to reserve for unmapped identifiers
  */
 struct richacl *
-richacl_alloc(int count, gfp_t gfp)
+__richacl_alloc(unsigned int count, size_t unmapped_size, gfp_t gfp)
 {
-	size_t size = sizeof(struct richacl) + count * sizeof(struct richace);
+	size_t size = sizeof(struct richacl) + count * sizeof(struct richace) +
+		      unmapped_size;
 	struct richacl *acl = kzalloc(size, gfp);
 
 	if (acl) {
 		atomic_set(&acl->a_base.ba_refcount, 1);
 		acl->a_count = count;
+		acl->a_unmapped_size = unmapped_size;
 	}
 	return acl;
 }
-EXPORT_SYMBOL_GPL(richacl_alloc);
+EXPORT_SYMBOL_GPL(__richacl_alloc);
 
 /**
  * richacl_clone  -  create a copy of a richacl
@@ -47,7 +50,8 @@ struct richacl *
 richacl_clone(const struct richacl *acl, gfp_t gfp)
 {
 	int count = acl->a_count;
-	size_t size = sizeof(struct richacl) + count * sizeof(struct richace);
+	size_t size = sizeof(struct richacl) + count * sizeof(struct richace) +
+		      acl->a_unmapped_size;
 	struct richacl *dup = kmalloc(size, gfp);
 
 	if (dup) {
@@ -59,6 +63,9 @@ richacl_clone(const struct richacl *acl, gfp_t gfp)
 
 /**
  * richace_copy  -  copy an acl entry
+ *
+ * If @from has an unmapped who value (from->e_flags & RICHACE_UNMAPPED_WHO),
+ * it can only be copied within the same acl!
  */
 void
 richace_copy(struct richace *to, const struct richace *from)
@@ -66,6 +73,82 @@ richace_copy(struct richace *to, const struct richace *from)
 	memcpy(to, from, sizeof(struct richace));
 }
 
+/**
+ * richacl_add_unmapped_identifier
+ * @pacl:	Pointer to an acl
+ * @pace:	acl entry within @acl
+ * @who:	unmapped identifier
+ * @len:	length of @who
+ * @gfp:	memory allocation flags
+ *
+ * Add an unmapped identifier to an acl, possibly reallocating the acl.
+ */
+int richacl_add_unmapped_identifier(struct richacl **pacl,
+				    struct richace **pace,
+				    const char *who,
+				    unsigned int len, gfp_t gfp)
+{
+	struct richacl *acl = *pacl;
+	size_t size = sizeof(struct richacl) +
+		      acl->a_count * sizeof(struct richace) +
+		      acl->a_unmapped_size + len + 1;
+	unsigned int index = *pace - acl->a_entries;
+
+	acl = krealloc(*pacl, size, gfp);
+	if (acl) {
+		char *unmapped = (char *)(acl->a_entries + acl->a_count);
+		struct richace *ace = acl->a_entries + index;
+
+		ace->e_flags |= RICHACE_UNMAPPED_WHO;
+		ace->e_flags &= ~RICHACE_SPECIAL_WHO;
+		ace->e_id.offs = acl->a_unmapped_size;
+		memcpy(unmapped + ace->e_id.offs, who, len);
+		unmapped[ace->e_id.offs + len] = 0;
+		acl->a_unmapped_size += len + 1;
+		*pace = ace;
+		*pacl = acl;
+		return 0;
+	}
+	return -1;
+}
+EXPORT_SYMBOL_GPL(richacl_add_unmapped_identifier);
+
+/**
+ * richace_unmapped_identifier  -  get unmapped identifier
+ * @acl:	acl containing @ace
+ * @ace:	acl entry
+ *
+ * Get the unmapped identifier of @ace as a NUL-terminated string, or NULL if
+ * @ace doesn't have an unmapped identifier.
+ */
+const char *richace_unmapped_identifier(const struct richace *ace,
+					const struct richacl *acl)
+{
+	const char *unmapped = (char *)(acl->a_entries + acl->a_count);
+
+	if (!(ace->e_flags & RICHACE_UNMAPPED_WHO))
+		return NULL;
+	return unmapped + ace->e_id.offs;
+}
+EXPORT_SYMBOL(richace_unmapped_identifier);
+
+/**
+ * richacl_has_unmapped_identifiers
+ *
+ * Check if an acl has unmapped identifiers.
+ */
+bool richacl_has_unmapped_identifiers(struct richacl *acl)
+{
+	struct richace *ace;
+
+	richacl_for_each_entry(ace, acl) {
+		if (ace->e_flags & RICHACE_UNMAPPED_WHO)
+			return true;
+	}
+	return false;
+}
+EXPORT_SYMBOL_GPL(richacl_has_unmapped_identifiers);
+
 /*
  * richacl_mask_to_mode  -  compute the file permission bits from mask
  * @mask:	%RICHACE_* permission mask
@@ -213,7 +296,7 @@ static unsigned int richacl_allowed_to_who(struct richacl *acl,
 	richacl_for_each_entry_reverse(ace, acl) {
 		if (richace_is_inherit_only(ace))
 			continue;
-		if (richace_is_same_identifier(ace, who) ||
+		if (richace_is_same_identifier(acl, ace, who) ||
 		    richace_is_everyone(ace)) {
 			if (richace_is_allow(ace))
 				allowed |= ace->e_mask;
@@ -502,46 +585,73 @@ richacl_inherit(const struct richacl *dir_acl, int isdir)
 	const struct richace *dir_ace;
 	struct richacl *acl = NULL;
 	struct richace *ace;
-	int count = 0;
+	unsigned int count = 0, unmapped_size = 0, offset = 0;
+	const char *dir_unmapped;
+	char *unmapped;
 
 	if (isdir) {
 		richacl_for_each_entry(dir_ace, dir_acl) {
 			if (!richace_is_inheritable(dir_ace))
 				continue;
+
 			count++;
+			dir_unmapped =
+				richace_unmapped_identifier(dir_ace, dir_acl);
+			if (dir_unmapped)
+				unmapped_size += strlen(dir_unmapped) + 1;
 		}
 		if (!count)
 			return NULL;
-		acl = richacl_alloc(count, GFP_KERNEL);
+		acl = __richacl_alloc(count, unmapped_size, GFP_KERNEL);
 		if (!acl)
 			return ERR_PTR(-ENOMEM);
 		ace = acl->a_entries;
+		unmapped = (char *)(acl->a_entries + acl->a_count);
 		richacl_for_each_entry(dir_ace, dir_acl) {
 			if (!richace_is_inheritable(dir_ace))
 				continue;
+
 			richace_copy(ace, dir_ace);
 			if (dir_ace->e_flags & RICHACE_NO_PROPAGATE_INHERIT_ACE)
 				ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
 			else if ((dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE) &&
 				 !(dir_ace->e_flags & RICHACE_DIRECTORY_INHERIT_ACE))
 				ace->e_flags |= RICHACE_INHERIT_ONLY_ACE;
+
+			dir_unmapped =
+				richace_unmapped_identifier(dir_ace, dir_acl);
+			if (dir_unmapped) {
+				size_t sz = strlen(dir_unmapped) + 1;
+
+				ace->e_id.offs = offset;
+				memcpy(unmapped, dir_unmapped, sz);
+				unmapped += sz;
+				offset += sz;
+			}
 			ace++;
 		}
 	} else {
 		richacl_for_each_entry(dir_ace, dir_acl) {
 			if (!(dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE))
 				continue;
+
 			count++;
+			dir_unmapped =
+				richace_unmapped_identifier(dir_ace, dir_acl);
+			if (dir_unmapped)
+				unmapped_size += strlen(dir_unmapped) + 1;
 		}
 		if (!count)
 			return NULL;
-		acl = richacl_alloc(count, GFP_KERNEL);
+		acl = __richacl_alloc(count, unmapped_size, GFP_KERNEL);
 		if (!acl)
 			return ERR_PTR(-ENOMEM);
 		ace = acl->a_entries;
+		unmapped = (char *)(acl->a_entries + acl->a_count);
 		richacl_for_each_entry(dir_ace, dir_acl) {
 			if (!(dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE))
 				continue;
+
 			richace_copy(ace, dir_ace);
 			ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
 			/*
@@ -549,6 +659,17 @@ richacl_inherit(const struct richacl *dir_acl, int isdir)
 			 * non-directories, so clear it.
 			 */
 			ace->e_mask &= ~RICHACE_DELETE_CHILD;
+
+			dir_unmapped =
+				richace_unmapped_identifier(dir_ace, dir_acl);
+			if (dir_unmapped) {
+				size_t sz = strlen(dir_unmapped) + 1;
+
+				ace->e_id.offs = offset;
+				memcpy(unmapped, dir_unmapped, sz);
+				unmapped += sz;
+				offset += sz;
+			}
 			ace++;
 		}
 	}
diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 632409f..8aeafa8 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -71,11 +71,13 @@ richacl_insert_entry(struct richacl_alloc *alloc, struct richace **ace)
 {
 	struct richacl *acl = alloc->acl;
 	unsigned int index = *ace - acl->a_entries;
-	size_t tail_size = (acl->a_count - index) * sizeof(struct richace);
+	size_t tail_size = (acl->a_count - index) * sizeof(struct richace) +
+			   acl->a_unmapped_size;
 
 	if (alloc->count == acl->a_count) {
 		size_t new_size = sizeof(struct richacl) +
-			(acl->a_count + 1) * sizeof(struct richace);
+			(acl->a_count + 1) * sizeof(struct richace) +
+			acl->a_unmapped_size;
 
 		acl = krealloc(acl, new_size, GFP_KERNEL);
 		if (!acl)
@@ -103,10 +105,6 @@ struct richace *richacl_append_entry(struct richacl_alloc *alloc)
 	struct richacl *acl = alloc->acl;
 	struct richace *ace = acl->a_entries + acl->a_count;
 
-	if (alloc->count > alloc->acl->a_count) {
-		acl->a_count++;
-		return ace;
-	}
 	return richacl_insert_entry(alloc, &ace) ? NULL : ace;
 }
 EXPORT_SYMBOL_GPL(richacl_append_entry);
@@ -261,12 +259,12 @@ __richacl_propagate_everyone(struct richacl_alloc *alloc, struct richace *who,
 		if (richace_is_inherit_only(ace))
 			continue;
 		if (richace_is_allow(ace)) {
-			if (richace_is_same_identifier(ace, who)) {
+			if (richace_is_same_identifier(acl, ace, who)) {
 				allow &= ~ace->e_mask;
 				allow_last = ace;
 			}
 		} else if (richace_is_deny(ace)) {
-			if (richace_is_same_identifier(ace, who))
+			if (richace_is_same_identifier(acl, ace, who))
 				allow &= ~ace->e_mask;
 			else if (allow & ace->e_mask)
 				allow_last = NULL;
@@ -608,7 +606,7 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
 	richacl_for_each_entry(ace, acl) {
 		if (richace_is_inherit_only(ace))
 			continue;
-		if (richace_is_same_identifier(ace, who) &&
+		if (richace_is_same_identifier(acl, ace, who) &&
 		    richace_is_deny(ace))
 			deny &= ~ace->e_mask;
 	}
@@ -625,7 +623,7 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
 		if (richace_is_inherit_only(ace))
 			continue;
 		if (richace_is_deny(ace)) {
-			if (richace_is_same_identifier(ace, who))
+			if (richace_is_same_identifier(acl, ace, who))
 				break;
 		} else if (richace_is_allow(ace) &&
 			   (ace->e_mask & deny)) {
diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c
index df175c1..ff8b192 100644
--- a/fs/richacl_inode.c
+++ b/fs/richacl_inode.c
@@ -159,8 +159,10 @@ richacl_permission(struct inode *inode, const struct richacl *acl,
 		} else if (richace_is_unix_group(ace)) {
 			if (!in_group_p(ace->e_id.gid))
 				continue;
-		} else
+		} else if (richace_is_everyone(ace))
 			goto entry_matches_everyone;
+		else
+			continue;
 
 		/*
 		 * Apply the group file mask to entries other than owner@ and
diff --git a/fs/richacl_xattr.c b/fs/richacl_xattr.c
index cd9979d..e9622f6 100644
--- a/fs/richacl_xattr.c
+++ b/fs/richacl_xattr.c
@@ -33,7 +33,8 @@ richacl_from_xattr(struct user_namespace *user_ns,
 	const struct richace_xattr *xattr_ace = (void *)(xattr_acl + 1);
 	struct richacl *acl;
 	struct richace *ace;
-	int count;
+	unsigned int count, offset;
+	char *unmapped;
 
 	if (size < sizeof(*xattr_acl) ||
 	    xattr_acl->a_version != RICHACL_XATTR_VERSION ||
@@ -43,10 +44,11 @@ richacl_from_xattr(struct user_namespace *user_ns,
 	count = le16_to_cpu(xattr_acl->a_count);
 	if (count > RICHACL_XATTR_MAX_COUNT)
 		return ERR_PTR(-EINVAL);
-	if (size != count * sizeof(*xattr_ace))
+	if (size < count * sizeof(*xattr_ace))
 		return ERR_PTR(-EINVAL);
+	size -= count * sizeof(*xattr_ace);
 
-	acl = richacl_alloc(count, GFP_NOFS);
+	acl = __richacl_alloc(count, size, GFP_NOFS);
 	if (!acl)
 		return ERR_PTR(-ENOMEM);
 
@@ -61,6 +63,16 @@ richacl_from_xattr(struct user_namespace *user_ns,
 	if (acl->a_other_mask & ~RICHACE_VALID_MASK)
 		goto fail_einval;
 
+	unmapped = (char *)(acl->a_entries + count);
+	if (size) {
+		char *xattr_unmapped = (char *)(xattr_ace + count);
+
+		if (xattr_unmapped[size - 1] != 0)
+			goto fail_einval;
+		memcpy(unmapped, xattr_unmapped, size);
+	}
+	offset = 0;
+
 	richacl_for_each_entry(ace, acl) {
 		ace->e_type  = le16_to_cpu(xattr_ace->e_type);
 		ace->e_flags = le16_to_cpu(xattr_ace->e_flags);
@@ -72,6 +84,15 @@ richacl_from_xattr(struct user_namespace *user_ns,
 			ace->e_id.special = le32_to_cpu(xattr_ace->e_id);
 			if (ace->e_id.special > RICHACE_EVERYONE_SPECIAL_ID)
 				goto fail_einval;
+		} else if (ace->e_flags & RICHACE_UNMAPPED_WHO) {
+			size_t sz;
+
+			if (offset == size)
+				goto fail_einval;
+			ace->e_id.offs = offset;
+			sz = strlen(unmapped) + 1;
+			unmapped += sz;
+			offset += sz;
 		} else if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
 			u32 id = le32_to_cpu(xattr_ace->e_id);
 
@@ -88,10 +109,12 @@ richacl_from_xattr(struct user_namespace *user_ns,
 		if (ace->e_type > RICHACE_ACCESS_DENIED_ACE_TYPE ||
 		    (ace->e_mask & ~RICHACE_VALID_MASK))
 			goto fail_einval;
-
 		xattr_ace++;
 	}
 
+	if (offset != size)
+		goto fail_einval;
+
 	return acl;
 
 fail_einval:
@@ -107,8 +130,15 @@ size_t
 richacl_xattr_size(const struct richacl *acl)
 {
 	size_t size = sizeof(struct richacl_xattr);
+	const struct richace *ace;
 
 	size += sizeof(struct richace_xattr) * acl->a_count;
+	richacl_for_each_entry(ace, acl) {
+		const char *unmapped = richace_unmapped_identifier(ace, acl);
+
+		if (unmapped)
+			size += strlen(unmapped) + 1;
+	}
 	return size;
 }
 EXPORT_SYMBOL_GPL(richacl_xattr_size);
@@ -127,6 +157,7 @@ richacl_to_xattr(struct user_namespace *user_ns,
 	struct richace_xattr *xattr_ace;
 	const struct richace *ace;
 	size_t real_size;
+	char *xattr_unmapped;
 
 	real_size = richacl_xattr_size(acl);
 	if (!buffer)
@@ -143,18 +174,33 @@ richacl_to_xattr(struct user_namespace *user_ns,
 	xattr_acl->a_other_mask = cpu_to_le32(acl->a_other_mask);
 
 	xattr_ace = (void *)(xattr_acl + 1);
+	xattr_unmapped = (char *)(xattr_ace + acl->a_count);
 	richacl_for_each_entry(ace, acl) {
+		const char *who;
+
 		xattr_ace->e_type = cpu_to_le16(ace->e_type);
 		xattr_ace->e_flags = cpu_to_le16(ace->e_flags);
 		xattr_ace->e_mask = cpu_to_le32(ace->e_mask);
 		if (ace->e_flags & RICHACE_SPECIAL_WHO)
 			xattr_ace->e_id = cpu_to_le32(ace->e_id.special);
-		else if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
-			xattr_ace->e_id =
-				cpu_to_le32(from_kgid(user_ns, ace->e_id.gid));
-		else
-			xattr_ace->e_id =
-				cpu_to_le32(from_kuid(user_ns, ace->e_id.uid));
+		else {
+			who = richace_unmapped_identifier(ace, acl);
+			if (who) {
+				size_t sz = strlen(who) + 1;
+
+				xattr_ace->e_id = 0;
+				memcpy(xattr_unmapped, who, sz);
+				xattr_unmapped += sz;
+			} else if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
+				u32 id = from_kgid(user_ns, ace->e_id.gid);
+
+				xattr_ace->e_id = cpu_to_le32(id);
+			} else {
+				u32 id = from_kuid(user_ns, ace->e_id.uid);
+
+				xattr_ace->e_id = cpu_to_le32(id);
+			}
+		}
 		xattr_ace++;
 	}
 	return real_size;
@@ -184,7 +230,8 @@ static void richacl_fix_xattr_userns(
 		return;
 	count = size / sizeof(*xattr_ace);
 	for (; count; count--, xattr_ace++) {
-		if (xattr_ace->e_flags & cpu_to_le16(RICHACE_SPECIAL_WHO))
+		if (xattr_ace->e_flags & cpu_to_le16(RICHACE_SPECIAL_WHO |
+						     RICHACE_UNMAPPED_WHO))
 			continue;
 		if (xattr_ace->e_flags &
 		    cpu_to_le16(RICHACE_IDENTIFIER_GROUP)) {
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index f48c04e..1822666 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -29,6 +29,7 @@ struct richace {
 		kuid_t		uid;
 		kgid_t		gid;
 		unsigned int	special;
+		unsigned short	offs;  /* unmapped offset */
 	} e_id;
 };
 
@@ -39,6 +40,7 @@ struct richacl {
 	unsigned int	a_other_mask;
 	unsigned short	a_count;
 	unsigned short	a_flags;
+	unsigned short	a_unmapped_size;
 	struct richace	a_entries[0];
 };
 
@@ -77,6 +79,7 @@ struct richacl {
 #define RICHACE_INHERIT_ONLY_ACE		0x0008
 #define RICHACE_IDENTIFIER_GROUP		0x0040
 #define RICHACE_INHERITED_ACE			0x0080
+#define RICHACE_UNMAPPED_WHO			0x2000
 #define RICHACE_SPECIAL_WHO			0x4000
 
 #define RICHACE_VALID_FLAGS (					\
@@ -86,6 +89,7 @@ struct richacl {
 	RICHACE_INHERIT_ONLY_ACE |				\
 	RICHACE_IDENTIFIER_GROUP |				\
 	RICHACE_INHERITED_ACE |					\
+	RICHACE_UNMAPPED_WHO |					\
 	RICHACE_SPECIAL_WHO)
 
 #define RICHACE_INHERITANCE_FLAGS (				\
@@ -310,14 +314,28 @@ richace_is_deny(const struct richace *ace)
  * richace_is_same_identifier  -  are both identifiers the same?
  */
 static inline bool
-richace_is_same_identifier(const struct richace *a, const struct richace *b)
+richace_is_same_identifier(const struct richacl *acl,
+			   const struct richace *ace1,
+			   const struct richace *ace2)
 {
-	return !((a->e_flags ^ b->e_flags) &
-		 (RICHACE_SPECIAL_WHO | RICHACE_IDENTIFIER_GROUP)) &&
-	       !memcmp(&a->e_id, &b->e_id, sizeof(a->e_id));
+	const char *unmapped = (char *)(acl->a_entries + acl->a_count);
+
+	return !((ace1->e_flags ^ ace2->e_flags) &
+		 (RICHACE_SPECIAL_WHO |
+		  RICHACE_IDENTIFIER_GROUP |
+		  RICHACE_UNMAPPED_WHO)) &&
+	       ((ace1->e_flags & RICHACE_UNMAPPED_WHO) ?
+		!strcmp(unmapped + ace1->e_id.offs,
+			unmapped + ace2->e_id.offs) :
+		!memcmp(&ace1->e_id, &ace2->e_id, sizeof(ace1->e_id)));
+}
+
+extern struct richacl *__richacl_alloc(unsigned int, size_t, gfp_t);
+static inline struct richacl *richacl_alloc(unsigned int count, gfp_t gfp)
+{
+	return __richacl_alloc(count, 0, gfp);
 }
 
-extern struct richacl *richacl_alloc(int, gfp_t);
 extern struct richacl *richacl_clone(const struct richacl *, gfp_t);
 extern void richace_copy(struct richace *, const struct richace *);
 extern int richacl_masks_to_mode(const struct richacl *);
@@ -327,6 +345,11 @@ extern void richacl_compute_max_masks(struct richacl *);
 extern struct richacl *richacl_chmod(struct richacl *, mode_t);
 extern int richacl_equiv_mode(const struct richacl *, mode_t *);
 extern struct richacl *richacl_inherit(const struct richacl *, int);
+extern int richacl_add_unmapped_identifier(struct richacl **, struct richace **,
+					   const char *, unsigned int, gfp_t);
+extern const char *richace_unmapped_identifier(const struct richace *,
+					       const struct richacl *);
+extern bool richacl_has_unmapped_identifiers(struct richacl *);
 
 /* richacl_inode.c */
 extern int richacl_permission(struct inode *, const struct richacl *, int);
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 34/41] ext4: Don't allow unmapped identifiers in richacls
  2015-09-05 10:26 ` Andreas Gruenbacher
                   ` (11 preceding siblings ...)
  (?)
@ 2015-09-05 10:27 ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

Don't allow acls which contain unmapped identifiers: they are meaningful
for remote file systems only.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/ext4/richacl.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/fs/ext4/richacl.c b/fs/ext4/richacl.c
index 6758def..ca6e0cb 100644
--- a/fs/ext4/richacl.c
+++ b/fs/ext4/richacl.c
@@ -68,8 +68,13 @@ ext4_set_richacl(handle_t *handle, struct inode *inode, struct richacl *acl)
 	int retval;
 
 	if (acl) {
-		mode_t mode = inode->i_mode;
+		mode_t mode;
 
+		/* Don't allow acls with unmapped identifiers. */
+		if (richacl_has_unmapped_identifiers(acl))
+			return -EINVAL;
+
+		mode = inode->i_mode;
 		if (richacl_equiv_mode(acl, &mode) == 0) {
 			inode->i_mode = mode;
 			ext4_mark_inode_dirty(handle, inode);
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 35/41] sunrpc: Allow to demand-allocate pages to encode into
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

When encoding large, variable-length objects such as acls into xdr_bufs,
it is easier to allocate buffer pages on demand rather than precomputing
the required buffer size.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 net/sunrpc/xdr.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c
index 4439ac4..63c1c36 100644
--- a/net/sunrpc/xdr.c
+++ b/net/sunrpc/xdr.c
@@ -537,6 +537,15 @@ static __be32 *xdr_get_next_encode_buffer(struct xdr_stream *xdr,
 	 */
 	xdr->scratch.iov_base = xdr->p;
 	xdr->scratch.iov_len = frag1bytes;
+
+	if (!*xdr->page_ptr) {
+		struct page *page = alloc_page(GFP_NOFS);
+
+		if (!page)
+			return NULL;
+		*xdr->page_ptr = page;
+	}
+
 	p = page_address(*xdr->page_ptr);
 	/*
 	 * Note this is where the next encode will start after we've
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 35/41] sunrpc: Allow to demand-allocate pages to encode into
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

When encoding large, variable-length objects such as acls into xdr_bufs,
it is easier to allocate buffer pages on demand rather than precomputing
the required buffer size.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 net/sunrpc/xdr.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c
index 4439ac4..63c1c36 100644
--- a/net/sunrpc/xdr.c
+++ b/net/sunrpc/xdr.c
@@ -537,6 +537,15 @@ static __be32 *xdr_get_next_encode_buffer(struct xdr_stream *xdr,
 	 */
 	xdr->scratch.iov_base = xdr->p;
 	xdr->scratch.iov_len = frag1bytes;
+
+	if (!*xdr->page_ptr) {
+		struct page *page = alloc_page(GFP_NOFS);
+
+		if (!page)
+			return NULL;
+		*xdr->page_ptr = page;
+	}
+
 	p = page_address(*xdr->page_ptr);
 	/*
 	 * Note this is where the next encode will start after we've
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 36/41] sunrpc: Add xdr_init_encode_pages
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

Initialize xdr_stream and xdr_buf from a pages array, for encoding into
the pages.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 include/linux/sunrpc/xdr.h |  2 ++
 net/sunrpc/xdr.c           | 25 +++++++++++++++++++++++++
 2 files changed, 27 insertions(+)

diff --git a/include/linux/sunrpc/xdr.h b/include/linux/sunrpc/xdr.h
index 70c6b92..2c99cff 100644
--- a/include/linux/sunrpc/xdr.h
+++ b/include/linux/sunrpc/xdr.h
@@ -214,6 +214,8 @@ typedef void	(*kxdreproc_t)(void *rqstp, struct xdr_stream *xdr, void *obj);
 typedef int	(*kxdrdproc_t)(void *rqstp, struct xdr_stream *xdr, void *obj);
 
 extern void xdr_init_encode(struct xdr_stream *xdr, struct xdr_buf *buf, __be32 *p);
+extern void xdr_init_encode_pages(struct xdr_stream *xdr, struct xdr_buf *buf,
+				  struct page **pages, unsigned int len);
 extern __be32 *xdr_reserve_space(struct xdr_stream *xdr, size_t nbytes);
 extern void xdr_commit_encode(struct xdr_stream *xdr);
 extern void xdr_truncate_encode(struct xdr_stream *xdr, size_t len);
diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c
index 63c1c36..f97b96b 100644
--- a/net/sunrpc/xdr.c
+++ b/net/sunrpc/xdr.c
@@ -483,6 +483,31 @@ void xdr_init_encode(struct xdr_stream *xdr, struct xdr_buf *buf, __be32 *p)
 EXPORT_SYMBOL_GPL(xdr_init_encode);
 
 /**
+ * xdr_init_encode_pages - Initialize struct xdr_stream for encoding into pages
+ * @xdr: pointer to xdr_stream struct
+ * @buf: pointer to XDR buffer in which to encode data
+ * @pages: pages array in which to encode
+ * @len: maximum length of @buf
+ *
+ * Initialize @xdr and @buf for encoding into the @pages array.  If a
+ * page in @pages is NULL, it will be allocated on demand.
+ */
+void xdr_init_encode_pages(struct xdr_stream *xdr, struct xdr_buf *buf,
+			   struct page **pages, unsigned int len)
+{
+	memset(buf, 0, sizeof(*buf));
+	buf->pages = pages;
+	buf->page_len = len;
+	buf->buflen = len;
+
+	memset(xdr, 0, sizeof(*xdr));
+	xdr->buf = buf;
+	xdr->iov = buf->head;
+	xdr->page_ptr = pages - 1;
+}
+EXPORT_SYMBOL_GPL(xdr_init_encode_pages);
+
+/**
  * xdr_commit_encode - Ensure all data is written to buffer
  * @xdr: pointer to xdr_stream
  *
-- 
2.4.3

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 36/41] sunrpc: Add xdr_init_encode_pages
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

Initialize xdr_stream and xdr_buf from a pages array, for encoding into
the pages.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 include/linux/sunrpc/xdr.h |  2 ++
 net/sunrpc/xdr.c           | 25 +++++++++++++++++++++++++
 2 files changed, 27 insertions(+)

diff --git a/include/linux/sunrpc/xdr.h b/include/linux/sunrpc/xdr.h
index 70c6b92..2c99cff 100644
--- a/include/linux/sunrpc/xdr.h
+++ b/include/linux/sunrpc/xdr.h
@@ -214,6 +214,8 @@ typedef void	(*kxdreproc_t)(void *rqstp, struct xdr_stream *xdr, void *obj);
 typedef int	(*kxdrdproc_t)(void *rqstp, struct xdr_stream *xdr, void *obj);
 
 extern void xdr_init_encode(struct xdr_stream *xdr, struct xdr_buf *buf, __be32 *p);
+extern void xdr_init_encode_pages(struct xdr_stream *xdr, struct xdr_buf *buf,
+				  struct page **pages, unsigned int len);
 extern __be32 *xdr_reserve_space(struct xdr_stream *xdr, size_t nbytes);
 extern void xdr_commit_encode(struct xdr_stream *xdr);
 extern void xdr_truncate_encode(struct xdr_stream *xdr, size_t len);
diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c
index 63c1c36..f97b96b 100644
--- a/net/sunrpc/xdr.c
+++ b/net/sunrpc/xdr.c
@@ -483,6 +483,31 @@ void xdr_init_encode(struct xdr_stream *xdr, struct xdr_buf *buf, __be32 *p)
 EXPORT_SYMBOL_GPL(xdr_init_encode);
 
 /**
+ * xdr_init_encode_pages - Initialize struct xdr_stream for encoding into pages
+ * @xdr: pointer to xdr_stream struct
+ * @buf: pointer to XDR buffer in which to encode data
+ * @pages: pages array in which to encode
+ * @len: maximum length of @buf
+ *
+ * Initialize @xdr and @buf for encoding into the @pages array.  If a
+ * page in @pages is NULL, it will be allocated on demand.
+ */
+void xdr_init_encode_pages(struct xdr_stream *xdr, struct xdr_buf *buf,
+			   struct page **pages, unsigned int len)
+{
+	memset(buf, 0, sizeof(*buf));
+	buf->pages = pages;
+	buf->page_len = len;
+	buf->buflen = len;
+
+	memset(xdr, 0, sizeof(*xdr));
+	xdr->buf = buf;
+	xdr->iov = buf->head;
+	xdr->page_ptr = pages - 1;
+}
+EXPORT_SYMBOL_GPL(xdr_init_encode_pages);
+
+/**
  * xdr_commit_encode - Ensure all data is written to buffer
  * @xdr: pointer to xdr_stream
  *
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 37/41] nfs: Fix GETATTR bitmap verification
  2015-09-05 10:26 ` Andreas Gruenbacher
                   ` (12 preceding siblings ...)
  (?)
@ 2015-09-05 10:27 ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

When decoding GETATTR replies, the client checks the attribute bitmap
for which attributes the server has sent.  It misses bits at the word
boundaries, though; fix that.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/nfs/nfs4xdr.c | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
index 558cd65d..67197b8 100644
--- a/fs/nfs/nfs4xdr.c
+++ b/fs/nfs/nfs4xdr.c
@@ -4346,6 +4346,11 @@ static int decode_statfs(struct xdr_stream *xdr, struct nfs_fsstat *fsstat)
 		goto xdr_error;
 	if ((status = decode_attr_files_total(xdr, bitmap, &fsstat->tfiles)) != 0)
 		goto xdr_error;
+
+	status = -EIO;
+	if (unlikely(bitmap[0]))
+		goto xdr_error;
+
 	if ((status = decode_attr_space_avail(xdr, bitmap, &fsstat->abytes)) != 0)
 		goto xdr_error;
 	if ((status = decode_attr_space_free(xdr, bitmap, &fsstat->fbytes)) != 0)
@@ -4545,6 +4550,10 @@ static int decode_getfattr_attrs(struct xdr_stream *xdr, uint32_t *bitmap,
 		goto xdr_error;
 	fattr->valid |= status;
 
+	status = -EIO;
+	if (unlikely(bitmap[0]))
+		goto xdr_error;
+
 	status = decode_attr_mode(xdr, bitmap, &fmode);
 	if (status < 0)
 		goto xdr_error;
@@ -4598,6 +4607,10 @@ static int decode_getfattr_attrs(struct xdr_stream *xdr, uint32_t *bitmap,
 		goto xdr_error;
 	fattr->valid |= status;
 
+	status = -EIO;
+	if (unlikely(bitmap[1]))
+		goto xdr_error;
+
 	status = decode_attr_mdsthreshold(xdr, bitmap, fattr->mdsthreshold);
 	if (status < 0)
 		goto xdr_error;
@@ -4760,12 +4773,22 @@ static int decode_fsinfo(struct xdr_stream *xdr, struct nfs_fsinfo *fsinfo)
 	if ((status = decode_attr_maxwrite(xdr, bitmap, &fsinfo->wtmax)) != 0)
 		goto xdr_error;
 	fsinfo->wtpref = fsinfo->wtmax;
+
+	status = -EIO;
+	if (unlikely(bitmap[0]))
+		goto xdr_error;
+
 	status = decode_attr_time_delta(xdr, bitmap, &fsinfo->time_delta);
 	if (status != 0)
 		goto xdr_error;
 	status = decode_attr_pnfstype(xdr, bitmap, &fsinfo->layouttype);
 	if (status != 0)
 		goto xdr_error;
+
+	status = -EIO;
+	if (unlikely(bitmap[1]))
+		goto xdr_error;
+
 	status = decode_attr_layout_blksize(xdr, bitmap, &fsinfo->blksize);
 	if (status)
 		goto xdr_error;
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 38/41] nfs: Remove unused xdr page offsets in getacl/setacl arguments
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

The arguments passed around for getacl and setacl xdr encoding, struct
nfs_setaclargs and struct nfs_getaclargs, both contain an array of
pages, an offset into the first page, and the length of the page data.
The offset is unused as it is always zero; remove it.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/nfs/nfs4proc.c       | 5 ++---
 fs/nfs/nfs4xdr.c        | 4 ++--
 include/linux/nfs_xdr.h | 2 --
 3 files changed, 4 insertions(+), 7 deletions(-)

diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index 3acb1eb..f675e92 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -4456,7 +4456,7 @@ static inline int nfs4_server_supports_acls(struct nfs_server *server)
 #define NFS4ACL_MAXPAGES DIV_ROUND_UP(XATTR_SIZE_MAX, PAGE_SIZE)
 
 static int buf_to_pages_noslab(const void *buf, size_t buflen,
-		struct page **pages, unsigned int *pgbase)
+		struct page **pages)
 {
 	struct page *newpage, **spages;
 	int rc = 0;
@@ -4600,7 +4600,6 @@ static ssize_t __nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t bu
 		goto out_free;
 
 	args.acl_len = npages * PAGE_SIZE;
-	args.acl_pgbase = 0;
 
 	dprintk("%s  buf %p buflen %zu npages %d args.acl_len %zu\n",
 		__func__, buf, buflen, npages, args.acl_len);
@@ -4692,7 +4691,7 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
 		return -EOPNOTSUPP;
 	if (npages > ARRAY_SIZE(pages))
 		return -ERANGE;
-	i = buf_to_pages_noslab(buf, buflen, arg.acl_pages, &arg.acl_pgbase);
+	i = buf_to_pages_noslab(buf, buflen, arg.acl_pages);
 	if (i < 0)
 		return i;
 	nfs4_inode_return_delegation(inode);
diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
index 67197b8..b2a243b 100644
--- a/fs/nfs/nfs4xdr.c
+++ b/fs/nfs/nfs4xdr.c
@@ -1646,7 +1646,7 @@ encode_setacl(struct xdr_stream *xdr, struct nfs_setaclargs *arg, struct compoun
 	*p = cpu_to_be32(FATTR4_WORD0_ACL);
 	p = reserve_space(xdr, 4);
 	*p = cpu_to_be32(arg->acl_len);
-	xdr_write_pages(xdr, arg->acl_pages, arg->acl_pgbase, arg->acl_len);
+	xdr_write_pages(xdr, arg->acl_pages, 0, arg->acl_len);
 }
 
 static void
@@ -2478,7 +2478,7 @@ static void nfs4_xdr_enc_getacl(struct rpc_rqst *req, struct xdr_stream *xdr,
 	encode_getattr_two(xdr, FATTR4_WORD0_ACL, 0, &hdr);
 
 	xdr_inline_pages(&req->rq_rcv_buf, replen << 2,
-		args->acl_pages, args->acl_pgbase, args->acl_len);
+		args->acl_pages, 0, args->acl_len);
 
 	encode_nops(&hdr);
 }
diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
index 7bbe505..f10bbac 100644
--- a/include/linux/nfs_xdr.h
+++ b/include/linux/nfs_xdr.h
@@ -685,7 +685,6 @@ struct nfs_setaclargs {
 	struct nfs4_sequence_args	seq_args;
 	struct nfs_fh *			fh;
 	size_t				acl_len;
-	unsigned int			acl_pgbase;
 	struct page **			acl_pages;
 };
 
@@ -697,7 +696,6 @@ struct nfs_getaclargs {
 	struct nfs4_sequence_args 	seq_args;
 	struct nfs_fh *			fh;
 	size_t				acl_len;
-	unsigned int			acl_pgbase;
 	struct page **			acl_pages;
 };
 
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 38/41] nfs: Remove unused xdr page offsets in getacl/setacl arguments
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

The arguments passed around for getacl and setacl xdr encoding, struct
nfs_setaclargs and struct nfs_getaclargs, both contain an array of
pages, an offset into the first page, and the length of the page data.
The offset is unused as it is always zero; remove it.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/nfs/nfs4proc.c       | 5 ++---
 fs/nfs/nfs4xdr.c        | 4 ++--
 include/linux/nfs_xdr.h | 2 --
 3 files changed, 4 insertions(+), 7 deletions(-)

diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index 3acb1eb..f675e92 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -4456,7 +4456,7 @@ static inline int nfs4_server_supports_acls(struct nfs_server *server)
 #define NFS4ACL_MAXPAGES DIV_ROUND_UP(XATTR_SIZE_MAX, PAGE_SIZE)
 
 static int buf_to_pages_noslab(const void *buf, size_t buflen,
-		struct page **pages, unsigned int *pgbase)
+		struct page **pages)
 {
 	struct page *newpage, **spages;
 	int rc = 0;
@@ -4600,7 +4600,6 @@ static ssize_t __nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t bu
 		goto out_free;
 
 	args.acl_len = npages * PAGE_SIZE;
-	args.acl_pgbase = 0;
 
 	dprintk("%s  buf %p buflen %zu npages %d args.acl_len %zu\n",
 		__func__, buf, buflen, npages, args.acl_len);
@@ -4692,7 +4691,7 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
 		return -EOPNOTSUPP;
 	if (npages > ARRAY_SIZE(pages))
 		return -ERANGE;
-	i = buf_to_pages_noslab(buf, buflen, arg.acl_pages, &arg.acl_pgbase);
+	i = buf_to_pages_noslab(buf, buflen, arg.acl_pages);
 	if (i < 0)
 		return i;
 	nfs4_inode_return_delegation(inode);
diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
index 67197b8..b2a243b 100644
--- a/fs/nfs/nfs4xdr.c
+++ b/fs/nfs/nfs4xdr.c
@@ -1646,7 +1646,7 @@ encode_setacl(struct xdr_stream *xdr, struct nfs_setaclargs *arg, struct compoun
 	*p = cpu_to_be32(FATTR4_WORD0_ACL);
 	p = reserve_space(xdr, 4);
 	*p = cpu_to_be32(arg->acl_len);
-	xdr_write_pages(xdr, arg->acl_pages, arg->acl_pgbase, arg->acl_len);
+	xdr_write_pages(xdr, arg->acl_pages, 0, arg->acl_len);
 }
 
 static void
@@ -2478,7 +2478,7 @@ static void nfs4_xdr_enc_getacl(struct rpc_rqst *req, struct xdr_stream *xdr,
 	encode_getattr_two(xdr, FATTR4_WORD0_ACL, 0, &hdr);
 
 	xdr_inline_pages(&req->rq_rcv_buf, replen << 2,
-		args->acl_pages, args->acl_pgbase, args->acl_len);
+		args->acl_pages, 0, args->acl_len);
 
 	encode_nops(&hdr);
 }
diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
index 7bbe505..f10bbac 100644
--- a/include/linux/nfs_xdr.h
+++ b/include/linux/nfs_xdr.h
@@ -685,7 +685,6 @@ struct nfs_setaclargs {
 	struct nfs4_sequence_args	seq_args;
 	struct nfs_fh *			fh;
 	size_t				acl_len;
-	unsigned int			acl_pgbase;
 	struct page **			acl_pages;
 };
 
@@ -697,7 +696,6 @@ struct nfs_getaclargs {
 	struct nfs4_sequence_args 	seq_args;
 	struct nfs_fh *			fh;
 	size_t				acl_len;
-	unsigned int			acl_pgbase;
 	struct page **			acl_pages;
 };
 
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 39/41] nfs: Add richacl support
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

Add support for the "system.richacl" xattr in nfs.  The existing
"system.nfs4_acl" xattr on nfs doesn't map user and group names to uids
and gids; the "system.richacl" xattr does, and only keeps the
on-the-wire names when there is no mapping.  This allows to copy
permissions across different file systems.

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/nfs/inode.c            |   3 -
 fs/nfs/nfs4proc.c         | 698 +++++++++++++++++++++++++++++++++-------------
 fs/nfs/nfs4xdr.c          | 179 ++++++++++--
 fs/nfs/super.c            |   4 +-
 include/linux/nfs_fs.h    |   1 -
 include/linux/nfs_fs_sb.h |   2 +
 include/linux/nfs_xdr.h   |   9 +-
 7 files changed, 673 insertions(+), 223 deletions(-)

diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index 0adc7d2..f6b710d 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -1845,9 +1845,6 @@ struct inode *nfs_alloc_inode(struct super_block *sb)
 		return NULL;
 	nfsi->flags = 0UL;
 	nfsi->cache_validity = 0UL;
-#if IS_ENABLED(CONFIG_NFS_V4)
-	nfsi->nfs4_acl = NULL;
-#endif /* CONFIG_NFS_V4 */
 	return &nfsi->vfs_inode;
 }
 EXPORT_SYMBOL_GPL(nfs_alloc_inode);
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index f675e92..f680364 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -55,6 +55,9 @@
 #include <linux/xattr.h>
 #include <linux/utsname.h>
 #include <linux/freezer.h>
+#include <linux/richacl.h>
+#include <linux/richacl_xattr.h>
+#include <linux/nfs4acl.h>
 
 #include "nfs4_fs.h"
 #include "delegation.h"
@@ -2909,15 +2912,18 @@ static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *f
 			res.attr_bitmask[2] &= FATTR4_WORD2_NFS42_MASK;
 		}
 		memcpy(server->attr_bitmask, res.attr_bitmask, sizeof(server->attr_bitmask));
-		server->caps &= ~(NFS_CAP_ACLS|NFS_CAP_HARDLINKS|
-				NFS_CAP_SYMLINKS|NFS_CAP_FILEID|
+		server->caps &= ~(NFS_CAP_ALLOW_ACLS|NFS_CAP_DENY_ACLS|
+				NFS_CAP_HARDLINKS|NFS_CAP_SYMLINKS|NFS_CAP_FILEID|
 				NFS_CAP_MODE|NFS_CAP_NLINK|NFS_CAP_OWNER|
 				NFS_CAP_OWNER_GROUP|NFS_CAP_ATIME|
 				NFS_CAP_CTIME|NFS_CAP_MTIME|
 				NFS_CAP_SECURITY_LABEL);
-		if (res.attr_bitmask[0] & FATTR4_WORD0_ACL &&
-				res.acl_bitmask & ACL4_SUPPORT_ALLOW_ACL)
-			server->caps |= NFS_CAP_ACLS;
+		if (res.attr_bitmask[0] & FATTR4_WORD0_ACL) {
+			if (res.acl_bitmask & ACL4_SUPPORT_ALLOW_ACL)
+				server->caps |= NFS_CAP_ALLOW_ACLS;
+			if (res.acl_bitmask & ACL4_SUPPORT_DENY_ACL)
+				server->caps |= NFS_CAP_DENY_ACLS;
+		}
 		if (res.has_links != 0)
 			server->caps |= NFS_CAP_HARDLINKS;
 		if (res.has_symlinks != 0)
@@ -4444,45 +4450,11 @@ static int nfs4_proc_renew(struct nfs_client *clp, struct rpc_cred *cred)
 	return 0;
 }
 
-static inline int nfs4_server_supports_acls(struct nfs_server *server)
-{
-	return server->caps & NFS_CAP_ACLS;
-}
-
-/* Assuming that XATTR_SIZE_MAX is a multiple of PAGE_SIZE, and that
- * it's OK to put sizeof(void) * (XATTR_SIZE_MAX/PAGE_SIZE) bytes on
- * the stack.
+/* A arbitrary limit; we allocate at most DIV_ROUND_UP(NFS4ACL_SIZE_MAX,
+ * PAGE_SIZE) pages and put an array of DIV_ROUND_UP(NFS4ACL_SIZE_MAX,
+ * PAGE_SIZE) pages on the stack when encoding or decoding acls.
  */
-#define NFS4ACL_MAXPAGES DIV_ROUND_UP(XATTR_SIZE_MAX, PAGE_SIZE)
-
-static int buf_to_pages_noslab(const void *buf, size_t buflen,
-		struct page **pages)
-{
-	struct page *newpage, **spages;
-	int rc = 0;
-	size_t len;
-	spages = pages;
-
-	do {
-		len = min_t(size_t, PAGE_SIZE, buflen);
-		newpage = alloc_page(GFP_KERNEL);
-
-		if (newpage == NULL)
-			goto unwind;
-		memcpy(page_address(newpage), buf, len);
-                buf += len;
-                buflen -= len;
-		*pages++ = newpage;
-		rc++;
-	} while (buflen != 0);
-
-	return rc;
-
-unwind:
-	for(; rc > 0; rc--)
-		__free_page(spages[rc-1]);
-	return -ENOMEM;
-}
+#define NFS4ACL_SIZE_MAX 65536
 
 struct nfs4_cached_acl {
 	int cached;
@@ -4490,66 +4462,9 @@ struct nfs4_cached_acl {
 	char data[0];
 };
 
-static void nfs4_set_cached_acl(struct inode *inode, struct nfs4_cached_acl *acl)
-{
-	struct nfs_inode *nfsi = NFS_I(inode);
-
-	spin_lock(&inode->i_lock);
-	kfree(nfsi->nfs4_acl);
-	nfsi->nfs4_acl = acl;
-	spin_unlock(&inode->i_lock);
-}
-
 static void nfs4_zap_acl_attr(struct inode *inode)
 {
-	nfs4_set_cached_acl(inode, NULL);
-}
-
-static inline ssize_t nfs4_read_cached_acl(struct inode *inode, char *buf, size_t buflen)
-{
-	struct nfs_inode *nfsi = NFS_I(inode);
-	struct nfs4_cached_acl *acl;
-	int ret = -ENOENT;
-
-	spin_lock(&inode->i_lock);
-	acl = nfsi->nfs4_acl;
-	if (acl == NULL)
-		goto out;
-	if (buf == NULL) /* user is just asking for length */
-		goto out_len;
-	if (acl->cached == 0)
-		goto out;
-	ret = -ERANGE; /* see getxattr(2) man page */
-	if (acl->len > buflen)
-		goto out;
-	memcpy(buf, acl->data, acl->len);
-out_len:
-	ret = acl->len;
-out:
-	spin_unlock(&inode->i_lock);
-	return ret;
-}
-
-static void nfs4_write_cached_acl(struct inode *inode, struct page **pages, size_t pgbase, size_t acl_len)
-{
-	struct nfs4_cached_acl *acl;
-	size_t buflen = sizeof(*acl) + acl_len;
-
-	if (buflen <= PAGE_SIZE) {
-		acl = kmalloc(buflen, GFP_KERNEL);
-		if (acl == NULL)
-			goto out;
-		acl->cached = 1;
-		_copy_from_pages(acl->data, pages, pgbase, acl_len);
-	} else {
-		acl = kmalloc(sizeof(*acl), GFP_KERNEL);
-		if (acl == NULL)
-			goto out;
-		acl->cached = 0;
-	}
-	acl->len = acl_len;
-out:
-	nfs4_set_cached_acl(inode, acl);
+	forget_cached_richacl(inode);
 }
 
 /*
@@ -4562,121 +4477,269 @@ out:
  * length. The next getxattr call will then produce another round trip to
  * the server, this time with the input buf of the required size.
  */
-static ssize_t __nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen)
+static struct richacl *__nfs4_get_acl_uncached(struct inode *inode)
 {
-	struct page *pages[NFS4ACL_MAXPAGES] = {NULL, };
+	struct nfs_server *server = NFS_SERVER(inode);
+	struct page *pages[DIV_ROUND_UP(NFS4ACL_SIZE_MAX, PAGE_SIZE)] = {};
 	struct nfs_getaclargs args = {
 		.fh = NFS_FH(inode),
 		.acl_pages = pages,
-		.acl_len = buflen,
+		.acl_len = ARRAY_SIZE(pages) * PAGE_SIZE,
 	};
 	struct nfs_getaclres res = {
-		.acl_len = buflen,
+		.server = server,
 	};
 	struct rpc_message msg = {
 		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_GETACL],
 		.rpc_argp = &args,
 		.rpc_resp = &res,
 	};
-	unsigned int npages = DIV_ROUND_UP(buflen, PAGE_SIZE);
-	int ret = -ENOMEM, i;
+	int err, i;
 
-	/* As long as we're doing a round trip to the server anyway,
-	 * let's be prepared for a page of acl data. */
-	if (npages == 0)
-		npages = 1;
-	if (npages > ARRAY_SIZE(pages))
-		return -ERANGE;
-
-	for (i = 0; i < npages; i++) {
-		pages[i] = alloc_page(GFP_KERNEL);
-		if (!pages[i])
+	if (ARRAY_SIZE(pages) > 1) {
+		/* for decoding across pages */
+		res.acl_scratch = alloc_page(GFP_KERNEL);
+		err = -ENOMEM;
+		if (!res.acl_scratch)
 			goto out_free;
 	}
 
-	/* for decoding across pages */
-	res.acl_scratch = alloc_page(GFP_KERNEL);
-	if (!res.acl_scratch)
-		goto out_free;
-
-	args.acl_len = npages * PAGE_SIZE;
-
-	dprintk("%s  buf %p buflen %zu npages %d args.acl_len %zu\n",
-		__func__, buf, buflen, npages, args.acl_len);
-	ret = nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode),
+	dprintk("%s  args.acl_len %zu\n",
+		__func__, args.acl_len);
+	err = nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode),
 			     &msg, &args.seq_args, &res.seq_res, 0);
-	if (ret)
+	if (err)
 		goto out_free;
 
-	/* Handle the case where the passed-in buffer is too short */
-	if (res.acl_flags & NFS4_ACL_TRUNC) {
-		/* Did the user only issue a request for the acl length? */
-		if (buf == NULL)
-			goto out_ok;
-		ret = -ERANGE;
-		goto out_free;
-	}
-	nfs4_write_cached_acl(inode, pages, res.acl_data_offset, res.acl_len);
-	if (buf) {
-		if (res.acl_len > buflen) {
-			ret = -ERANGE;
-			goto out_free;
-		}
-		_copy_from_pages(buf, pages, res.acl_data_offset, res.acl_len);
-	}
-out_ok:
-	ret = res.acl_len;
+	richacl_compute_max_masks(res.acl);
+	/* FIXME: Set inode->i_mode from res->mode?  */
+	set_cached_richacl(inode, res.acl);
+	err = 0;
+
 out_free:
-	for (i = 0; i < npages; i++)
-		if (pages[i])
-			__free_page(pages[i]);
+	if (err) {
+		richacl_put(res.acl);
+		res.acl = ERR_PTR(err);
+	}
+	for (i = 0; i < ARRAY_SIZE(pages) && pages[i]; i++)
+		__free_page(pages[i]);
 	if (res.acl_scratch)
 		__free_page(res.acl_scratch);
-	return ret;
+	return res.acl;
 }
 
-static ssize_t nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen)
+static struct richacl *nfs4_get_acl_uncached(struct inode *inode)
 {
 	struct nfs4_exception exception = { };
-	ssize_t ret;
+	struct richacl *acl;
 	do {
-		ret = __nfs4_get_acl_uncached(inode, buf, buflen);
-		trace_nfs4_get_acl(inode, ret);
-		if (ret >= 0)
+		acl = __nfs4_get_acl_uncached(inode);
+		trace_nfs4_get_acl(inode, IS_ERR(acl) ? PTR_ERR(acl) : 0);
+		if (!IS_ERR(acl))
 			break;
-		ret = nfs4_handle_exception(NFS_SERVER(inode), ret, &exception);
+		acl = ERR_PTR(nfs4_handle_exception(NFS_SERVER(inode),
+			      PTR_ERR(acl), &exception));
 	} while (exception.retry);
-	return ret;
+	return acl;
 }
 
-static ssize_t nfs4_proc_get_acl(struct inode *inode, void *buf, size_t buflen)
+static struct richacl *nfs4_proc_get_acl(struct inode *inode)
 {
 	struct nfs_server *server = NFS_SERVER(inode);
+	struct richacl *acl;
 	int ret;
 
-	if (!nfs4_server_supports_acls(server))
-		return -EOPNOTSUPP;
+	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
+		return ERR_PTR(-EOPNOTSUPP);
 	ret = nfs_revalidate_inode(server, inode);
 	if (ret < 0)
-		return ret;
+		return ERR_PTR(ret);
 	if (NFS_I(inode)->cache_validity & NFS_INO_INVALID_ACL)
 		nfs_zap_acl_cache(inode);
-	ret = nfs4_read_cached_acl(inode, buf, buflen);
-	if (ret != -ENOENT)
-		/* -ENOENT is returned if there is no ACL or if there is an ACL
-		 * but no cached acl data, just the acl length */
-		return ret;
-	return nfs4_get_acl_uncached(inode, buf, buflen);
+	acl = get_cached_richacl(inode);
+	if (acl != ACL_NOT_CACHED)
+		return acl;
+	return nfs4_get_acl_uncached(inode);
+}
+
+static int
+richacl_supported(struct nfs_server *server, struct richacl *acl)
+{
+	struct richace *ace;
+
+	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
+		return -EOPNOTSUPP;
+
+	richacl_for_each_entry(ace, acl) {
+		if (richace_is_allow(ace)) {
+			if (!(server->caps & NFS_CAP_ALLOW_ACLS))
+				return -EINVAL;
+		} else if (richace_is_deny(ace)) {
+			if (!(server->caps & NFS_CAP_DENY_ACLS))
+				return -EINVAL;
+		} else
+			return -EINVAL;
+	}
+	return 0;
+}
+
+static int
+nfs4_encode_user(struct xdr_stream *xdr, const struct nfs_server *server,
+		 kuid_t uid)
+{
+	char name[IDMAP_NAMESZ];
+	int len;
+	__be32 *p;
+
+	len = nfs_map_uid_to_name(server, uid, name, IDMAP_NAMESZ);
+	if (len < 0) {
+		dprintk("nfs: couldn't resolve uid %d to string\n",
+				from_kuid(&init_user_ns, uid));
+		return -ENOENT;
+	}
+	p = xdr_reserve_space(xdr, 4 + len);
+	if (!p)
+		return -EIO;
+	p = xdr_encode_opaque(p, name, len);
+	return 0;
+}
+
+static int
+nfs4_encode_group(struct xdr_stream *xdr, const struct nfs_server *server,
+		  kgid_t gid)
+{
+	char name[IDMAP_NAMESZ];
+	int len;
+	__be32 *p;
+
+	len = nfs_map_gid_to_group(server, gid, name, IDMAP_NAMESZ);
+	if (len < 0) {
+		dprintk("nfs: couldn't resolve gid %d to string\n",
+				from_kgid(&init_user_ns, gid));
+		return -ENOENT;
+	}
+	p = xdr_reserve_space(xdr, 4 + len);
+	if (!p)
+		return -EIO;
+	p = xdr_encode_opaque(p, name, len);
+	return 0;
+}
+
+static unsigned int
+nfs4_ace_mask(int minorversion)
+{
+	return minorversion == 0 ? NFS40_ACE_MASK_ALL : NFS4_ACE_MASK_ALL;
+}
+
+static int
+nfs4_encode_ace_who(struct xdr_stream *xdr, const struct nfs_server *server,
+		    struct richace *ace, struct richacl *acl)
+{
+	const char *who;
+	__be32 *p;
+
+	if (ace->e_flags & RICHACE_SPECIAL_WHO) {
+		unsigned int special_id = ace->e_id.special;
+		const char *who;
+		unsigned int len;
+
+		if (!nfs4acl_special_id_to_who(special_id, &who, &len)) {
+			WARN_ON_ONCE(1);
+			return -EIO;
+		}
+		p = xdr_reserve_space(xdr, 4 + len);
+		if (!p)
+			return -EIO;
+		xdr_encode_opaque(p, who, len);
+		return 0;
+	} else {
+		who = richace_unmapped_identifier(ace, acl);
+		if (who) {
+			unsigned int len = strlen(who);
+
+			p = xdr_reserve_space(xdr, 4 + len);
+			if (!p)
+				return -EIO;
+			xdr_encode_opaque(p, who, len);
+			return 0;
+		} else if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
+			return nfs4_encode_group(xdr, server, ace->e_id.gid);
+		else
+			return nfs4_encode_user(xdr, server, ace->e_id.uid);
+	}
+}
+
+static int
+nfs4_encode_acl(struct page **pages, unsigned int len, struct richacl *acl,
+		const struct nfs_server *server)
+{
+	int minorversion = server->nfs_client->cl_minorversion;
+	unsigned int ace_mask = nfs4_ace_mask(minorversion);
+	struct xdr_stream xdr;
+	struct xdr_buf buf;
+	__be32 *p;
+	struct richace *ace;
+
+	/* Reject acls not understood by the server */
+	if (server->attr_bitmask[1] & FATTR4_WORD1_DACL) {
+		BUILD_BUG_ON(NFS4_ACE_MASK_ALL != RICHACE_VALID_MASK);
+	} else {
+		if (acl->a_flags)
+			return -EINVAL;
+		richacl_for_each_entry(ace, acl) {
+			if (ace->e_flags & RICHACE_INHERITED_ACE)
+				return -EINVAL;
+		}
+	}
+	richacl_for_each_entry(ace, acl) {
+		if (ace->e_mask & ~ace_mask)
+			return -EINVAL;
+	}
+
+	xdr_init_encode_pages(&xdr, &buf, pages, len);
+
+	if (server->attr_bitmask[1] & FATTR4_WORD1_DACL) {
+		p = xdr_reserve_space(&xdr, 4);
+		if (!p)
+			goto fail;
+		*p = cpu_to_be32(acl ? acl->a_flags : 0);
+	}
+
+	p = xdr_reserve_space(&xdr, 4);
+	if (!p)
+		goto fail;
+	if (!acl) {
+		*p++ = cpu_to_be32(0);
+		return buf.len;
+	}
+	*p++ = cpu_to_be32(acl->a_count);
+
+	richacl_for_each_entry(ace, acl) {
+		p = xdr_reserve_space(&xdr, 4*3);
+		if (!p)
+			goto fail;
+		*p++ = cpu_to_be32(ace->e_type);
+		*p++ = cpu_to_be32(ace->e_flags &
+			~(RICHACE_SPECIAL_WHO | RICHACE_UNMAPPED_WHO));
+		*p++ = cpu_to_be32(ace->e_mask & NFS4_ACE_MASK_ALL);
+		if (nfs4_encode_ace_who(&xdr, server, ace, acl) != 0)
+			goto fail;
+	}
+
+	return buf.len;
+
+fail:
+	return -ENOMEM;
 }
 
-static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t buflen)
+static int __nfs4_proc_set_acl(struct inode *inode, struct richacl *acl)
 {
 	struct nfs_server *server = NFS_SERVER(inode);
-	struct page *pages[NFS4ACL_MAXPAGES];
+	struct page *pages[DIV_ROUND_UP(NFS4ACL_SIZE_MAX, PAGE_SIZE) + 1 /* scratch */] = {};
 	struct nfs_setaclargs arg = {
+		.server		= server,
 		.fh		= NFS_FH(inode),
 		.acl_pages	= pages,
-		.acl_len	= buflen,
 	};
 	struct nfs_setaclres res;
 	struct rpc_message msg = {
@@ -4684,16 +4747,20 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
 		.rpc_argp	= &arg,
 		.rpc_resp	= &res,
 	};
-	unsigned int npages = DIV_ROUND_UP(buflen, PAGE_SIZE);
 	int ret, i;
 
-	if (!nfs4_server_supports_acls(server))
-		return -EOPNOTSUPP;
-	if (npages > ARRAY_SIZE(pages))
-		return -ERANGE;
-	i = buf_to_pages_noslab(buf, buflen, arg.acl_pages);
-	if (i < 0)
-		return i;
+	ret = richacl_supported(server, acl);
+	if (ret)
+		return ret;
+
+	ret = nfs4_encode_acl(pages, NFS4ACL_SIZE_MAX, acl, server);
+	if (ret < 0) {
+		for (i = 0; i < ARRAY_SIZE(pages) && pages[i]; i++)
+			put_page(pages[i]);
+		return ret;
+	}
+	arg.acl_len = ret;
+
 	nfs4_inode_return_delegation(inode);
 	ret = nfs4_call_sync(server->client, server, &msg, &arg.seq_args, &res.seq_res, 1);
 
@@ -4701,8 +4768,8 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
 	 * Free each page after tx, so the only ref left is
 	 * held by the network stack
 	 */
-	for (; i > 0; i--)
-		put_page(pages[i-1]);
+	for (i = 0; i < ARRAY_SIZE(pages) && pages[i]; i++)
+		put_page(pages[i]);
 
 	/*
 	 * Acl update can result in inode attribute update.
@@ -4716,12 +4783,12 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
 	return ret;
 }
 
-static int nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t buflen)
+static int nfs4_proc_set_acl(struct inode *inode, struct richacl *acl)
 {
 	struct nfs4_exception exception = { };
 	int err;
 	do {
-		err = __nfs4_proc_set_acl(inode, buf, buflen);
+		err = __nfs4_proc_set_acl(inode, acl);
 		trace_nfs4_set_acl(inode, err);
 		err = nfs4_handle_exception(NFS_SERVER(inode), err,
 				&exception);
@@ -6198,34 +6265,283 @@ nfs4_release_lockowner(struct nfs_server *server, struct nfs4_lock_state *lsp)
 	rpc_call_async(server->client, &msg, 0, &nfs4_release_lockowner_ops, data);
 }
 
+static int nfs4_xattr_set_richacl(struct dentry *dentry, const char *key,
+				  const void *buf, size_t buflen,
+				  int flags, int handler_flags)
+{
+	struct inode *inode = d_inode(dentry);
+	struct richacl *acl;
+	int error;
+
+	if (strcmp(key, "") != 0)
+		return -EINVAL;
+
+	if (buf) {
+		acl = richacl_from_xattr(&init_user_ns, buf, buflen);
+		if (IS_ERR(acl))
+			return PTR_ERR(acl);
+		error = richacl_apply_masks(&acl, inode->i_uid);
+	} else {
+		/*
+		 * "Remove the acl"; only permissions granted by the mode
+		 * remain.  We are using the cached mode here which could be
+		 * outdated; should we do a GETATTR first to narrow down the
+		 * race window?
+		 */
+		acl = richacl_from_mode(inode->i_mode);
+		error = 0;
+	}
+
+	if (!error)
+		error = nfs4_proc_set_acl(inode, acl);
+	richacl_put(acl);
+	return error;
+}
+
+static int nfs4_xattr_get_richacl(struct dentry *dentry, const char *key,
+				  void *buf, size_t buflen, int handler_flags)
+{
+	struct inode *inode = d_inode(dentry);
+	struct richacl *acl;
+	int error;
+	mode_t mode = inode->i_mode & S_IFMT;
+
+	if (strcmp(key, "") != 0)
+		return -EINVAL;
+
+	acl = nfs4_proc_get_acl(inode);
+	if (IS_ERR(acl))
+		return PTR_ERR(acl);
+	if (acl == NULL)
+		return -ENODATA;
+	error = -ENODATA;
+	if (richacl_equiv_mode(acl, &mode) == 0 &&
+	    ((mode ^ inode->i_mode) & S_IRWXUGO) == 0)
+		goto out;
+	error = richacl_to_xattr(&init_user_ns, acl, buf, buflen);
+out:
+	richacl_put(acl);
+	return error;
+}
+
+static size_t nfs4_xattr_list_richacl(struct dentry *dentry, char *list,
+				      size_t list_len, const char *name,
+				      size_t name_len, int handler_flags)
+{
+	struct nfs_server *server = NFS_SERVER(d_inode(dentry));
+	size_t len = sizeof(XATTR_NAME_RICHACL);
+
+	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
+		return 0;
+
+	if (list && len <= list_len)
+		memcpy(list, XATTR_NAME_RICHACL, len);
+	return len;
+}
+
 #define XATTR_NAME_NFSV4_ACL "system.nfs4_acl"
 
+static int richacl_to_nfs4_acl(struct nfs_server *server,
+			       const struct richacl *acl,
+			       void *buf, size_t buflen)
+{
+	const struct richace *ace;
+	__be32 *p = buf;
+	size_t size = 0;
+
+	size += sizeof(*p);
+	if (buflen >= size)
+		*p++ = cpu_to_be32(acl->a_count);
+
+	richacl_for_each_entry(ace, acl) {
+		char who_buf[IDMAP_NAMESZ];
+		const char *who = who_buf;
+		int who_len;
+
+		size += 3 * sizeof(*p);
+		if (buflen >= size) {
+			*p++ = cpu_to_be32(ace->e_type);
+			*p++ = cpu_to_be32(ace->e_flags &
+					   ~(RICHACE_INHERITED_ACE |
+					     RICHACE_UNMAPPED_WHO |
+					     RICHACE_SPECIAL_WHO));
+			*p++ = cpu_to_be32(ace->e_mask);
+		}
+
+		if (richace_is_unix_user(ace)) {
+			who_len = nfs_map_uid_to_name(server, ace->e_id.uid,
+						      who_buf, sizeof(who_buf));
+			if (who_len < 0)
+				return -EIO;
+		} else if (richace_is_unix_group(ace)) {
+			who_len = nfs_map_gid_to_group(server, ace->e_id.gid,
+						       who_buf, sizeof(who_buf));
+			if (who_len < 0)
+				return -EIO;
+		} else if (ace->e_flags & RICHACE_SPECIAL_WHO) {
+			if (!nfs4acl_special_id_to_who(ace->e_id.special,
+						       &who, &who_len))
+				return -EIO;
+		} else {
+			who = richace_unmapped_identifier(ace, acl);
+			if (who)
+				who_len = strlen(who);
+			else
+				return -EIO;
+		}
+
+		size += sizeof(*p) + ALIGN(who_len, sizeof(*p));
+		if (buflen >= size) {
+			unsigned int padding = -who_len & (sizeof(*p) - 1);
+
+			*p++ = cpu_to_be32(who_len);
+			memcpy(p, who, who_len);
+			memset((char *)p + who_len, 0, padding);
+			p += DIV_ROUND_UP(who_len, sizeof(*p));
+		}
+	}
+	if (buflen && buflen < size)
+		return -ERANGE;
+	return size;
+}
+
+static struct richacl *richacl_from_nfs4_acl(struct nfs_server *server,
+					     const void *buf, size_t buflen)
+{
+	struct richacl *acl = NULL;
+	struct richace *ace;
+	const __be32 *p = buf;
+	int count, err;
+
+	if (buflen < sizeof(*p))
+		return ERR_PTR(-EINVAL);
+	count = be32_to_cpu(*p++);
+	if (count > RICHACL_XATTR_MAX_COUNT)
+		return ERR_PTR(-EINVAL);
+	buflen -= sizeof(*p);
+	acl = richacl_alloc(count, GFP_NOFS);
+	if (!acl)
+		return ERR_PTR(-ENOMEM);
+	richacl_for_each_entry(ace, acl) {
+		u32 who_len, size;
+		int special_id;
+		char *who;
+
+		err = -EINVAL;
+		if (buflen < 4 * sizeof(*p))
+			goto out;
+		ace->e_type = be32_to_cpu(*p++);
+		ace->e_flags = be32_to_cpu(*p++);
+		if (ace->e_flags & (RICHACE_SPECIAL_WHO | RICHACE_UNMAPPED_WHO))
+			goto out;
+		ace->e_mask = be32_to_cpu(*p++);
+		who_len = be32_to_cpu(*p++);
+		buflen -= 4 * sizeof(*p);
+		size = ALIGN(who_len, 4);
+		if (buflen < size || size == 0)
+			goto out;
+		who = (char *)p;
+		special_id = nfs4acl_who_to_special_id(who, who_len);
+		if (special_id >= 0) {
+			ace->e_flags |= RICHACE_SPECIAL_WHO;
+			ace->e_id.special = special_id;
+		} else {
+			bool unmappable;
+
+			if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
+				err = nfs_map_group_to_gid(server, who, who_len,
+							   &ace->e_id.gid);
+				if (err) {
+					dprintk("%s: nfs_map_group_to_gid "
+						"failed!\n", __func__);
+					goto out;
+				}
+				/* FIXME: nfsidmap doesn't distinguish between
+					  group nobody and unmappable groups! */
+				unmappable = gid_eq(ace->e_id.gid,
+					make_kgid(&init_user_ns, 99));
+			} else {
+				err = nfs_map_name_to_uid(server, who, who_len,
+							  &ace->e_id.uid);
+				if (err) {
+					dprintk("%s: nfs_map_name_to_gid "
+						"failed!\n", __func__);
+					goto out;
+				}
+				/* FIXME: nfsidmap doesn't distinguish between
+					  user nobody and unmappable users! */
+				unmappable = uid_eq(ace->e_id.uid,
+					make_kuid(&init_user_ns, 99));
+			}
+			if (unmappable) {
+				err = -ENOMEM;
+				if (richacl_add_unmapped_identifier(&acl, &ace,
+					who, who_len, GFP_NOFS))
+					goto out;
+			}
+		}
+		p += size / sizeof(*p);
+		buflen -= size;
+	}
+	err = -EINVAL;
+	if (buflen != 0)
+		goto out;
+	err = 0;
+
+out:
+	if (err) {
+		richacl_put(acl);
+		acl = ERR_PTR(err);
+	}
+	return acl;
+}
+
 static int nfs4_xattr_set_nfs4_acl(struct dentry *dentry, const char *key,
 				   const void *buf, size_t buflen,
 				   int flags, int type)
 {
-	if (strcmp(key, "") != 0)
+	struct inode *inode = d_inode(dentry);
+	struct richacl *acl;
+	int error;
+
+	if (!buf || strcmp(key, "") != 0)
 		return -EINVAL;
 
-	return nfs4_proc_set_acl(d_inode(dentry), buf, buflen);
+	acl = richacl_from_nfs4_acl(NFS_SERVER(inode), (void *)buf, buflen);
+	if (IS_ERR(acl))
+		return PTR_ERR(acl);
+	error = nfs4_proc_set_acl(inode, acl);
+	richacl_put(acl);
+	return error;
 }
 
 static int nfs4_xattr_get_nfs4_acl(struct dentry *dentry, const char *key,
 				   void *buf, size_t buflen, int type)
 {
+	struct inode *inode = d_inode(dentry);
+	struct richacl *acl;
+	int error;
+
 	if (strcmp(key, "") != 0)
 		return -EINVAL;
-
-	return nfs4_proc_get_acl(d_inode(dentry), buf, buflen);
+	acl = nfs4_proc_get_acl(inode);
+	if (IS_ERR(acl))
+		return PTR_ERR(acl);
+	if (acl == NULL)
+		return -ENODATA;
+	error = richacl_to_nfs4_acl(NFS_SERVER(inode), acl, buf, buflen);
+	richacl_put(acl);
+	return error;
 }
 
 static size_t nfs4_xattr_list_nfs4_acl(struct dentry *dentry, char *list,
 				       size_t list_len, const char *name,
 				       size_t name_len, int type)
 {
+	struct nfs_server *server = NFS_SERVER(d_inode(dentry));
 	size_t len = sizeof(XATTR_NAME_NFSV4_ACL);
 
-	if (!nfs4_server_supports_acls(NFS_SERVER(d_inode(dentry))))
+	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
 		return 0;
 
 	if (list && len <= list_len)
@@ -8757,6 +9073,13 @@ const struct nfs_rpc_ops nfs_v4_clientops = {
 	.clone_server	= nfs_clone_server,
 };
 
+static const struct xattr_handler nfs4_xattr_richacl_handler = {
+	.prefix	= XATTR_NAME_RICHACL,
+	.list	= nfs4_xattr_list_richacl,
+	.get	= nfs4_xattr_get_richacl,
+	.set	= nfs4_xattr_set_richacl,
+};
+
 static const struct xattr_handler nfs4_xattr_nfs4_acl_handler = {
 	.prefix	= XATTR_NAME_NFSV4_ACL,
 	.list	= nfs4_xattr_list_nfs4_acl,
@@ -8765,6 +9088,7 @@ static const struct xattr_handler nfs4_xattr_nfs4_acl_handler = {
 };
 
 const struct xattr_handler *nfs4_xattr_handlers[] = {
+	&nfs4_xattr_richacl_handler,
 	&nfs4_xattr_nfs4_acl_handler,
 #ifdef CONFIG_NFS_V4_SECURITY_LABEL
 	&nfs4_xattr_nfs4_label_handler,
diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
index b2a243b..78a9fbd 100644
--- a/fs/nfs/nfs4xdr.c
+++ b/fs/nfs/nfs4xdr.c
@@ -52,6 +52,10 @@
 #include <linux/nfs.h>
 #include <linux/nfs4.h>
 #include <linux/nfs_fs.h>
+#include <linux/nfs_idmap.h>
+#include <linux/richacl.h>
+#include <linux/richacl_xattr.h>  /* for RICHACL_XATTR_MAX_COUNT */
+#include <linux/nfs4acl.h>
 
 #include "nfs4_fs.h"
 #include "internal.h"
@@ -1637,16 +1641,24 @@ encode_restorefh(struct xdr_stream *xdr, struct compound_hdr *hdr)
 static void
 encode_setacl(struct xdr_stream *xdr, struct nfs_setaclargs *arg, struct compound_hdr *hdr)
 {
-	__be32 *p;
+	int attrlen_offset;
+	__be32 attrlen, *p;
 
 	encode_op_hdr(xdr, OP_SETATTR, decode_setacl_maxsz, hdr);
 	encode_nfs4_stateid(xdr, &zero_stateid);
+
+	/* Encode attribute bitmap. */
 	p = reserve_space(xdr, 2*4);
 	*p++ = cpu_to_be32(1);
 	*p = cpu_to_be32(FATTR4_WORD0_ACL);
-	p = reserve_space(xdr, 4);
-	*p = cpu_to_be32(arg->acl_len);
+
+	attrlen_offset = xdr->buf->len;
+	xdr_reserve_space(xdr, 4);  /* to be backfilled later */
+
 	xdr_write_pages(xdr, arg->acl_pages, 0, arg->acl_len);
+
+	attrlen = htonl(xdr->buf->len - attrlen_offset - 4);
+	write_bytes_to_xdr_buf(xdr->buf, attrlen_offset, &attrlen, 4);
 }
 
 static void
@@ -2475,7 +2487,7 @@ static void nfs4_xdr_enc_getacl(struct rpc_rqst *req, struct xdr_stream *xdr,
 	encode_sequence(xdr, &args->seq_args, &hdr);
 	encode_putfh(xdr, args->fh, &hdr);
 	replen = hdr.replen + op_decode_hdr_maxsz + 1;
-	encode_getattr_two(xdr, FATTR4_WORD0_ACL, 0, &hdr);
+	encode_getattr_two(xdr, FATTR4_WORD0_ACL, FATTR4_WORD1_MODE, &hdr);
 
 	xdr_inline_pages(&req->rq_rcv_buf, replen << 2,
 		args->acl_pages, 0, args->acl_len);
@@ -5227,24 +5239,135 @@ decode_restorefh(struct xdr_stream *xdr)
 	return decode_op_hdr(xdr, OP_RESTOREFH);
 }
 
+static int
+nfs4_decode_ace_who(struct richace *ace,
+		    const char **unmapped, unsigned int *unmapped_len,
+		    const struct nfs_server *server,
+		    struct xdr_stream *xdr)
+{
+	char *who;
+	u32 len;
+	int special_id;
+	__be32 *p;
+	int error;
+
+	p = xdr_inline_decode(xdr, 4);
+	if (!p)
+		return -ENOMEM;  /* acl truncated */
+	len = be32_to_cpup(p++);
+	if (len >= XDR_MAX_NETOBJ) {
+		dprintk("%s: name too long (%u)!\n",
+			__func__, len);
+		return -EIO;
+	}
+	who = (char *)xdr_inline_decode(xdr, len);
+	if (!who)
+		return -ENOMEM;  /* acl truncated */
+
+	special_id = nfs4acl_who_to_special_id(who, len);
+	if (special_id >= 0) {
+		ace->e_flags |= RICHACE_SPECIAL_WHO;
+		ace->e_flags &= ~RICHACE_IDENTIFIER_GROUP;
+		ace->e_id.special = special_id;
+		return 0;
+	}
+	if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
+		error = nfs_map_group_to_gid(server, who, len, &ace->e_id.gid);
+		if (error) {
+			dprintk("%s: nfs_map_group_to_gid failed!\n",
+					__func__);
+			return error;
+		}
+		/* FIXME: nfsidmap doesn't distinguish between group nobody and
+			  unmappable groups! */
+		if (gid_eq(ace->e_id.gid, make_kgid(&init_user_ns, 99))) {
+			*unmapped = who;
+			*unmapped_len = len;
+		}
+	} else {
+		error = nfs_map_name_to_uid(server, who, len, &ace->e_id.uid);
+		if (error) {
+			dprintk("%s: nfs_map_name_to_uid failed!\n",
+					__func__);
+			return error;
+		}
+		/* FIXME: nfsidmap doesn't distinguish between user nobody and
+			  unmappable users! */
+		if (uid_eq(ace->e_id.uid, make_kuid(&init_user_ns, 99))) {
+			*unmapped = who;
+			*unmapped_len = len;
+		}
+	}
+	return 0;
+}
+
+static struct richacl *
+decode_acl_entries(struct xdr_stream *xdr, const struct nfs_server *server)
+{
+	struct richacl *acl;
+	struct richace *ace;
+	uint32_t count;
+	__be32 *p;
+	int status;
+
+	p = xdr_inline_decode(xdr, 4);
+	if (unlikely(!p))
+		return ERR_PTR(-ENOMEM);  /* acl truncated */
+	count = be32_to_cpup(p);
+	if (count > RICHACL_XATTR_MAX_COUNT)
+		return ERR_PTR(-EIO);
+	acl = richacl_alloc(count, GFP_NOFS);
+	if (!acl)
+		return ERR_PTR(-ENOMEM);
+	richacl_for_each_entry(ace, acl) {
+		const char *unmapped = NULL;
+		unsigned int unmapped_len;
+
+		p = xdr_inline_decode(xdr, 4*3);
+		status = -ENOMEM;
+		if (unlikely(!p))
+			goto out;  /* acl truncated */
+		ace->e_type = be32_to_cpup(p++);
+		ace->e_flags = be32_to_cpup(p++);
+		status = -EIO;
+		if (ace->e_flags &
+		    (RICHACE_SPECIAL_WHO | RICHACE_UNMAPPED_WHO))
+			goto out;
+		ace->e_mask = be32_to_cpup(p++);
+		status = nfs4_decode_ace_who(ace, &unmapped,
+					     &unmapped_len, server,
+					     xdr);
+		if (status)
+			goto out;
+		if (unmapped) {
+			status = -ENOMEM;
+			if (richacl_add_unmapped_identifier(&acl, &ace,
+					unmapped, unmapped_len,
+					GFP_NOFS))
+				goto out;
+		}
+	}
+	status = 0;
+
+out:
+	if (status) {
+		richacl_put(acl);
+		acl = ERR_PTR(status);
+	}
+	return acl;
+}
+
 static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req,
 			 struct nfs_getaclres *res)
 {
 	unsigned int savep;
 	uint32_t attrlen,
 		 bitmap[3] = {0};
+	struct richacl *acl = NULL;
 	int status;
-	unsigned int pg_offset;
 
-	res->acl_len = 0;
 	if ((status = decode_op_hdr(xdr, OP_GETATTR)) != 0)
 		goto out;
-
-	xdr_enter_page(xdr, xdr->buf->page_len);
-
-	/* Calculate the offset of the page data */
-	pg_offset = xdr->buf->head[0].iov_len;
-
 	if ((status = decode_attr_bitmap(xdr, bitmap)) != 0)
 		goto out;
 	if ((status = decode_attr_length(xdr, &attrlen, &savep)) != 0)
@@ -5253,24 +5376,28 @@ static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req,
 	if (unlikely(bitmap[0] & (FATTR4_WORD0_ACL - 1U)))
 		return -EIO;
 	if (likely(bitmap[0] & FATTR4_WORD0_ACL)) {
-
-		/* The bitmap (xdr len + bitmaps) and the attr xdr len words
-		 * are stored with the acl data to handle the problem of
-		 * variable length bitmaps.*/
-		res->acl_data_offset = xdr_stream_pos(xdr) - pg_offset;
-		res->acl_len = attrlen;
-
-		/* Check for receive buffer overflow */
-		if (res->acl_len > (xdr->nwords << 2) ||
-		    res->acl_len + res->acl_data_offset > xdr->buf->page_len) {
-			res->acl_flags |= NFS4_ACL_TRUNC;
-			dprintk("NFS: acl reply: attrlen %u > page_len %u\n",
-					attrlen, xdr->nwords << 2);
-		}
+		acl = decode_acl_entries(xdr, res->server);
+		status = PTR_ERR(acl);
+		if (IS_ERR(acl))
+			goto out;
+		bitmap[0] &= ~FATTR4_WORD0_ACL;
 	} else
 		status = -EOPNOTSUPP;
 
+	status = -EIO;
+	if (unlikely(bitmap[0]))
+		goto out;
+
+	status = decode_attr_mode(xdr, bitmap, &res->mode);
+	if (status < 0)
+		goto out;
+	status = 0;
+
 out:
+	if (status == 0)
+		res->acl = acl;
+	else
+		richacl_put(acl);
 	return status;
 }
 
diff --git a/fs/nfs/super.c b/fs/nfs/super.c
index aa62004..fbbcac9 100644
--- a/fs/nfs/super.c
+++ b/fs/nfs/super.c
@@ -2316,7 +2316,7 @@ void nfs_fill_super(struct super_block *sb, struct nfs_mount_info *mount_info)
 		/* The VFS shouldn't apply the umask to mode bits. We will do
 		 * so ourselves when necessary.
 		 */
-		sb->s_flags |= MS_POSIXACL;
+		sb->s_flags |= MS_RICHACL;
 		sb->s_time_gran = 1;
 	}
 
@@ -2343,7 +2343,7 @@ void nfs_clone_super(struct super_block *sb, struct nfs_mount_info *mount_info)
 		/* The VFS shouldn't apply the umask to mode bits. We will do
 		 * so ourselves when necessary.
 		 */
-		sb->s_flags |= MS_POSIXACL;
+		sb->s_flags |= MS_RICHACL;
 	}
 
  	nfs_initialise_sb(sb);
diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h
index 874b772..eb923c6 100644
--- a/include/linux/nfs_fs.h
+++ b/include/linux/nfs_fs.h
@@ -176,7 +176,6 @@ struct nfs_inode {
 	wait_queue_head_t	waitqueue;
 
 #if IS_ENABLED(CONFIG_NFS_V4)
-	struct nfs4_cached_acl	*nfs4_acl;
         /* NFSv4 state */
 	struct list_head	open_states;
 	struct nfs_delegation __rcu *delegation;
diff --git a/include/linux/nfs_fs_sb.h b/include/linux/nfs_fs_sb.h
index 20bc8e5..f128a49 100644
--- a/include/linux/nfs_fs_sb.h
+++ b/include/linux/nfs_fs_sb.h
@@ -238,5 +238,7 @@ struct nfs_server {
 #define NFS_CAP_ALLOCATE	(1U << 20)
 #define NFS_CAP_DEALLOCATE	(1U << 21)
 #define NFS_CAP_LAYOUTSTATS	(1U << 22)
+#define NFS_CAP_ALLOW_ACLS	(1U << 23)
+#define NFS_CAP_DENY_ACLS	(1U << 24)
 
 #endif
diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
index f10bbac..f1c8f93 100644
--- a/include/linux/nfs_xdr.h
+++ b/include/linux/nfs_xdr.h
@@ -683,9 +683,10 @@ struct nfs_setattrargs {
 
 struct nfs_setaclargs {
 	struct nfs4_sequence_args	seq_args;
+	const struct nfs_server *	server;
 	struct nfs_fh *			fh;
-	size_t				acl_len;
 	struct page **			acl_pages;
+	size_t				acl_len;
 };
 
 struct nfs_setaclres {
@@ -703,9 +704,9 @@ struct nfs_getaclargs {
 #define NFS4_ACL_TRUNC		0x0001	/* ACL was truncated */
 struct nfs_getaclres {
 	struct nfs4_sequence_res	seq_res;
-	size_t				acl_len;
-	size_t				acl_data_offset;
-	int				acl_flags;
+	const struct nfs_server *	server;
+	struct richacl *		acl;
+	umode_t				mode;
 	struct page *			acl_scratch;
 };
 
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 39/41] nfs: Add richacl support
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

Add support for the "system.richacl" xattr in nfs.  The existing
"system.nfs4_acl" xattr on nfs doesn't map user and group names to uids
and gids; the "system.richacl" xattr does, and only keeps the
on-the-wire names when there is no mapping.  This allows to copy
permissions across different file systems.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/nfs/inode.c            |   3 -
 fs/nfs/nfs4proc.c         | 698 +++++++++++++++++++++++++++++++++-------------
 fs/nfs/nfs4xdr.c          | 179 ++++++++++--
 fs/nfs/super.c            |   4 +-
 include/linux/nfs_fs.h    |   1 -
 include/linux/nfs_fs_sb.h |   2 +
 include/linux/nfs_xdr.h   |   9 +-
 7 files changed, 673 insertions(+), 223 deletions(-)

diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index 0adc7d2..f6b710d 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -1845,9 +1845,6 @@ struct inode *nfs_alloc_inode(struct super_block *sb)
 		return NULL;
 	nfsi->flags = 0UL;
 	nfsi->cache_validity = 0UL;
-#if IS_ENABLED(CONFIG_NFS_V4)
-	nfsi->nfs4_acl = NULL;
-#endif /* CONFIG_NFS_V4 */
 	return &nfsi->vfs_inode;
 }
 EXPORT_SYMBOL_GPL(nfs_alloc_inode);
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index f675e92..f680364 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -55,6 +55,9 @@
 #include <linux/xattr.h>
 #include <linux/utsname.h>
 #include <linux/freezer.h>
+#include <linux/richacl.h>
+#include <linux/richacl_xattr.h>
+#include <linux/nfs4acl.h>
 
 #include "nfs4_fs.h"
 #include "delegation.h"
@@ -2909,15 +2912,18 @@ static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *f
 			res.attr_bitmask[2] &= FATTR4_WORD2_NFS42_MASK;
 		}
 		memcpy(server->attr_bitmask, res.attr_bitmask, sizeof(server->attr_bitmask));
-		server->caps &= ~(NFS_CAP_ACLS|NFS_CAP_HARDLINKS|
-				NFS_CAP_SYMLINKS|NFS_CAP_FILEID|
+		server->caps &= ~(NFS_CAP_ALLOW_ACLS|NFS_CAP_DENY_ACLS|
+				NFS_CAP_HARDLINKS|NFS_CAP_SYMLINKS|NFS_CAP_FILEID|
 				NFS_CAP_MODE|NFS_CAP_NLINK|NFS_CAP_OWNER|
 				NFS_CAP_OWNER_GROUP|NFS_CAP_ATIME|
 				NFS_CAP_CTIME|NFS_CAP_MTIME|
 				NFS_CAP_SECURITY_LABEL);
-		if (res.attr_bitmask[0] & FATTR4_WORD0_ACL &&
-				res.acl_bitmask & ACL4_SUPPORT_ALLOW_ACL)
-			server->caps |= NFS_CAP_ACLS;
+		if (res.attr_bitmask[0] & FATTR4_WORD0_ACL) {
+			if (res.acl_bitmask & ACL4_SUPPORT_ALLOW_ACL)
+				server->caps |= NFS_CAP_ALLOW_ACLS;
+			if (res.acl_bitmask & ACL4_SUPPORT_DENY_ACL)
+				server->caps |= NFS_CAP_DENY_ACLS;
+		}
 		if (res.has_links != 0)
 			server->caps |= NFS_CAP_HARDLINKS;
 		if (res.has_symlinks != 0)
@@ -4444,45 +4450,11 @@ static int nfs4_proc_renew(struct nfs_client *clp, struct rpc_cred *cred)
 	return 0;
 }
 
-static inline int nfs4_server_supports_acls(struct nfs_server *server)
-{
-	return server->caps & NFS_CAP_ACLS;
-}
-
-/* Assuming that XATTR_SIZE_MAX is a multiple of PAGE_SIZE, and that
- * it's OK to put sizeof(void) * (XATTR_SIZE_MAX/PAGE_SIZE) bytes on
- * the stack.
+/* A arbitrary limit; we allocate at most DIV_ROUND_UP(NFS4ACL_SIZE_MAX,
+ * PAGE_SIZE) pages and put an array of DIV_ROUND_UP(NFS4ACL_SIZE_MAX,
+ * PAGE_SIZE) pages on the stack when encoding or decoding acls.
  */
-#define NFS4ACL_MAXPAGES DIV_ROUND_UP(XATTR_SIZE_MAX, PAGE_SIZE)
-
-static int buf_to_pages_noslab(const void *buf, size_t buflen,
-		struct page **pages)
-{
-	struct page *newpage, **spages;
-	int rc = 0;
-	size_t len;
-	spages = pages;
-
-	do {
-		len = min_t(size_t, PAGE_SIZE, buflen);
-		newpage = alloc_page(GFP_KERNEL);
-
-		if (newpage == NULL)
-			goto unwind;
-		memcpy(page_address(newpage), buf, len);
-                buf += len;
-                buflen -= len;
-		*pages++ = newpage;
-		rc++;
-	} while (buflen != 0);
-
-	return rc;
-
-unwind:
-	for(; rc > 0; rc--)
-		__free_page(spages[rc-1]);
-	return -ENOMEM;
-}
+#define NFS4ACL_SIZE_MAX 65536
 
 struct nfs4_cached_acl {
 	int cached;
@@ -4490,66 +4462,9 @@ struct nfs4_cached_acl {
 	char data[0];
 };
 
-static void nfs4_set_cached_acl(struct inode *inode, struct nfs4_cached_acl *acl)
-{
-	struct nfs_inode *nfsi = NFS_I(inode);
-
-	spin_lock(&inode->i_lock);
-	kfree(nfsi->nfs4_acl);
-	nfsi->nfs4_acl = acl;
-	spin_unlock(&inode->i_lock);
-}
-
 static void nfs4_zap_acl_attr(struct inode *inode)
 {
-	nfs4_set_cached_acl(inode, NULL);
-}
-
-static inline ssize_t nfs4_read_cached_acl(struct inode *inode, char *buf, size_t buflen)
-{
-	struct nfs_inode *nfsi = NFS_I(inode);
-	struct nfs4_cached_acl *acl;
-	int ret = -ENOENT;
-
-	spin_lock(&inode->i_lock);
-	acl = nfsi->nfs4_acl;
-	if (acl == NULL)
-		goto out;
-	if (buf == NULL) /* user is just asking for length */
-		goto out_len;
-	if (acl->cached == 0)
-		goto out;
-	ret = -ERANGE; /* see getxattr(2) man page */
-	if (acl->len > buflen)
-		goto out;
-	memcpy(buf, acl->data, acl->len);
-out_len:
-	ret = acl->len;
-out:
-	spin_unlock(&inode->i_lock);
-	return ret;
-}
-
-static void nfs4_write_cached_acl(struct inode *inode, struct page **pages, size_t pgbase, size_t acl_len)
-{
-	struct nfs4_cached_acl *acl;
-	size_t buflen = sizeof(*acl) + acl_len;
-
-	if (buflen <= PAGE_SIZE) {
-		acl = kmalloc(buflen, GFP_KERNEL);
-		if (acl == NULL)
-			goto out;
-		acl->cached = 1;
-		_copy_from_pages(acl->data, pages, pgbase, acl_len);
-	} else {
-		acl = kmalloc(sizeof(*acl), GFP_KERNEL);
-		if (acl == NULL)
-			goto out;
-		acl->cached = 0;
-	}
-	acl->len = acl_len;
-out:
-	nfs4_set_cached_acl(inode, acl);
+	forget_cached_richacl(inode);
 }
 
 /*
@@ -4562,121 +4477,269 @@ out:
  * length. The next getxattr call will then produce another round trip to
  * the server, this time with the input buf of the required size.
  */
-static ssize_t __nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen)
+static struct richacl *__nfs4_get_acl_uncached(struct inode *inode)
 {
-	struct page *pages[NFS4ACL_MAXPAGES] = {NULL, };
+	struct nfs_server *server = NFS_SERVER(inode);
+	struct page *pages[DIV_ROUND_UP(NFS4ACL_SIZE_MAX, PAGE_SIZE)] = {};
 	struct nfs_getaclargs args = {
 		.fh = NFS_FH(inode),
 		.acl_pages = pages,
-		.acl_len = buflen,
+		.acl_len = ARRAY_SIZE(pages) * PAGE_SIZE,
 	};
 	struct nfs_getaclres res = {
-		.acl_len = buflen,
+		.server = server,
 	};
 	struct rpc_message msg = {
 		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_GETACL],
 		.rpc_argp = &args,
 		.rpc_resp = &res,
 	};
-	unsigned int npages = DIV_ROUND_UP(buflen, PAGE_SIZE);
-	int ret = -ENOMEM, i;
+	int err, i;
 
-	/* As long as we're doing a round trip to the server anyway,
-	 * let's be prepared for a page of acl data. */
-	if (npages == 0)
-		npages = 1;
-	if (npages > ARRAY_SIZE(pages))
-		return -ERANGE;
-
-	for (i = 0; i < npages; i++) {
-		pages[i] = alloc_page(GFP_KERNEL);
-		if (!pages[i])
+	if (ARRAY_SIZE(pages) > 1) {
+		/* for decoding across pages */
+		res.acl_scratch = alloc_page(GFP_KERNEL);
+		err = -ENOMEM;
+		if (!res.acl_scratch)
 			goto out_free;
 	}
 
-	/* for decoding across pages */
-	res.acl_scratch = alloc_page(GFP_KERNEL);
-	if (!res.acl_scratch)
-		goto out_free;
-
-	args.acl_len = npages * PAGE_SIZE;
-
-	dprintk("%s  buf %p buflen %zu npages %d args.acl_len %zu\n",
-		__func__, buf, buflen, npages, args.acl_len);
-	ret = nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode),
+	dprintk("%s  args.acl_len %zu\n",
+		__func__, args.acl_len);
+	err = nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode),
 			     &msg, &args.seq_args, &res.seq_res, 0);
-	if (ret)
+	if (err)
 		goto out_free;
 
-	/* Handle the case where the passed-in buffer is too short */
-	if (res.acl_flags & NFS4_ACL_TRUNC) {
-		/* Did the user only issue a request for the acl length? */
-		if (buf == NULL)
-			goto out_ok;
-		ret = -ERANGE;
-		goto out_free;
-	}
-	nfs4_write_cached_acl(inode, pages, res.acl_data_offset, res.acl_len);
-	if (buf) {
-		if (res.acl_len > buflen) {
-			ret = -ERANGE;
-			goto out_free;
-		}
-		_copy_from_pages(buf, pages, res.acl_data_offset, res.acl_len);
-	}
-out_ok:
-	ret = res.acl_len;
+	richacl_compute_max_masks(res.acl);
+	/* FIXME: Set inode->i_mode from res->mode?  */
+	set_cached_richacl(inode, res.acl);
+	err = 0;
+
 out_free:
-	for (i = 0; i < npages; i++)
-		if (pages[i])
-			__free_page(pages[i]);
+	if (err) {
+		richacl_put(res.acl);
+		res.acl = ERR_PTR(err);
+	}
+	for (i = 0; i < ARRAY_SIZE(pages) && pages[i]; i++)
+		__free_page(pages[i]);
 	if (res.acl_scratch)
 		__free_page(res.acl_scratch);
-	return ret;
+	return res.acl;
 }
 
-static ssize_t nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen)
+static struct richacl *nfs4_get_acl_uncached(struct inode *inode)
 {
 	struct nfs4_exception exception = { };
-	ssize_t ret;
+	struct richacl *acl;
 	do {
-		ret = __nfs4_get_acl_uncached(inode, buf, buflen);
-		trace_nfs4_get_acl(inode, ret);
-		if (ret >= 0)
+		acl = __nfs4_get_acl_uncached(inode);
+		trace_nfs4_get_acl(inode, IS_ERR(acl) ? PTR_ERR(acl) : 0);
+		if (!IS_ERR(acl))
 			break;
-		ret = nfs4_handle_exception(NFS_SERVER(inode), ret, &exception);
+		acl = ERR_PTR(nfs4_handle_exception(NFS_SERVER(inode),
+			      PTR_ERR(acl), &exception));
 	} while (exception.retry);
-	return ret;
+	return acl;
 }
 
-static ssize_t nfs4_proc_get_acl(struct inode *inode, void *buf, size_t buflen)
+static struct richacl *nfs4_proc_get_acl(struct inode *inode)
 {
 	struct nfs_server *server = NFS_SERVER(inode);
+	struct richacl *acl;
 	int ret;
 
-	if (!nfs4_server_supports_acls(server))
-		return -EOPNOTSUPP;
+	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
+		return ERR_PTR(-EOPNOTSUPP);
 	ret = nfs_revalidate_inode(server, inode);
 	if (ret < 0)
-		return ret;
+		return ERR_PTR(ret);
 	if (NFS_I(inode)->cache_validity & NFS_INO_INVALID_ACL)
 		nfs_zap_acl_cache(inode);
-	ret = nfs4_read_cached_acl(inode, buf, buflen);
-	if (ret != -ENOENT)
-		/* -ENOENT is returned if there is no ACL or if there is an ACL
-		 * but no cached acl data, just the acl length */
-		return ret;
-	return nfs4_get_acl_uncached(inode, buf, buflen);
+	acl = get_cached_richacl(inode);
+	if (acl != ACL_NOT_CACHED)
+		return acl;
+	return nfs4_get_acl_uncached(inode);
+}
+
+static int
+richacl_supported(struct nfs_server *server, struct richacl *acl)
+{
+	struct richace *ace;
+
+	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
+		return -EOPNOTSUPP;
+
+	richacl_for_each_entry(ace, acl) {
+		if (richace_is_allow(ace)) {
+			if (!(server->caps & NFS_CAP_ALLOW_ACLS))
+				return -EINVAL;
+		} else if (richace_is_deny(ace)) {
+			if (!(server->caps & NFS_CAP_DENY_ACLS))
+				return -EINVAL;
+		} else
+			return -EINVAL;
+	}
+	return 0;
+}
+
+static int
+nfs4_encode_user(struct xdr_stream *xdr, const struct nfs_server *server,
+		 kuid_t uid)
+{
+	char name[IDMAP_NAMESZ];
+	int len;
+	__be32 *p;
+
+	len = nfs_map_uid_to_name(server, uid, name, IDMAP_NAMESZ);
+	if (len < 0) {
+		dprintk("nfs: couldn't resolve uid %d to string\n",
+				from_kuid(&init_user_ns, uid));
+		return -ENOENT;
+	}
+	p = xdr_reserve_space(xdr, 4 + len);
+	if (!p)
+		return -EIO;
+	p = xdr_encode_opaque(p, name, len);
+	return 0;
+}
+
+static int
+nfs4_encode_group(struct xdr_stream *xdr, const struct nfs_server *server,
+		  kgid_t gid)
+{
+	char name[IDMAP_NAMESZ];
+	int len;
+	__be32 *p;
+
+	len = nfs_map_gid_to_group(server, gid, name, IDMAP_NAMESZ);
+	if (len < 0) {
+		dprintk("nfs: couldn't resolve gid %d to string\n",
+				from_kgid(&init_user_ns, gid));
+		return -ENOENT;
+	}
+	p = xdr_reserve_space(xdr, 4 + len);
+	if (!p)
+		return -EIO;
+	p = xdr_encode_opaque(p, name, len);
+	return 0;
+}
+
+static unsigned int
+nfs4_ace_mask(int minorversion)
+{
+	return minorversion == 0 ? NFS40_ACE_MASK_ALL : NFS4_ACE_MASK_ALL;
+}
+
+static int
+nfs4_encode_ace_who(struct xdr_stream *xdr, const struct nfs_server *server,
+		    struct richace *ace, struct richacl *acl)
+{
+	const char *who;
+	__be32 *p;
+
+	if (ace->e_flags & RICHACE_SPECIAL_WHO) {
+		unsigned int special_id = ace->e_id.special;
+		const char *who;
+		unsigned int len;
+
+		if (!nfs4acl_special_id_to_who(special_id, &who, &len)) {
+			WARN_ON_ONCE(1);
+			return -EIO;
+		}
+		p = xdr_reserve_space(xdr, 4 + len);
+		if (!p)
+			return -EIO;
+		xdr_encode_opaque(p, who, len);
+		return 0;
+	} else {
+		who = richace_unmapped_identifier(ace, acl);
+		if (who) {
+			unsigned int len = strlen(who);
+
+			p = xdr_reserve_space(xdr, 4 + len);
+			if (!p)
+				return -EIO;
+			xdr_encode_opaque(p, who, len);
+			return 0;
+		} else if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
+			return nfs4_encode_group(xdr, server, ace->e_id.gid);
+		else
+			return nfs4_encode_user(xdr, server, ace->e_id.uid);
+	}
+}
+
+static int
+nfs4_encode_acl(struct page **pages, unsigned int len, struct richacl *acl,
+		const struct nfs_server *server)
+{
+	int minorversion = server->nfs_client->cl_minorversion;
+	unsigned int ace_mask = nfs4_ace_mask(minorversion);
+	struct xdr_stream xdr;
+	struct xdr_buf buf;
+	__be32 *p;
+	struct richace *ace;
+
+	/* Reject acls not understood by the server */
+	if (server->attr_bitmask[1] & FATTR4_WORD1_DACL) {
+		BUILD_BUG_ON(NFS4_ACE_MASK_ALL != RICHACE_VALID_MASK);
+	} else {
+		if (acl->a_flags)
+			return -EINVAL;
+		richacl_for_each_entry(ace, acl) {
+			if (ace->e_flags & RICHACE_INHERITED_ACE)
+				return -EINVAL;
+		}
+	}
+	richacl_for_each_entry(ace, acl) {
+		if (ace->e_mask & ~ace_mask)
+			return -EINVAL;
+	}
+
+	xdr_init_encode_pages(&xdr, &buf, pages, len);
+
+	if (server->attr_bitmask[1] & FATTR4_WORD1_DACL) {
+		p = xdr_reserve_space(&xdr, 4);
+		if (!p)
+			goto fail;
+		*p = cpu_to_be32(acl ? acl->a_flags : 0);
+	}
+
+	p = xdr_reserve_space(&xdr, 4);
+	if (!p)
+		goto fail;
+	if (!acl) {
+		*p++ = cpu_to_be32(0);
+		return buf.len;
+	}
+	*p++ = cpu_to_be32(acl->a_count);
+
+	richacl_for_each_entry(ace, acl) {
+		p = xdr_reserve_space(&xdr, 4*3);
+		if (!p)
+			goto fail;
+		*p++ = cpu_to_be32(ace->e_type);
+		*p++ = cpu_to_be32(ace->e_flags &
+			~(RICHACE_SPECIAL_WHO | RICHACE_UNMAPPED_WHO));
+		*p++ = cpu_to_be32(ace->e_mask & NFS4_ACE_MASK_ALL);
+		if (nfs4_encode_ace_who(&xdr, server, ace, acl) != 0)
+			goto fail;
+	}
+
+	return buf.len;
+
+fail:
+	return -ENOMEM;
 }
 
-static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t buflen)
+static int __nfs4_proc_set_acl(struct inode *inode, struct richacl *acl)
 {
 	struct nfs_server *server = NFS_SERVER(inode);
-	struct page *pages[NFS4ACL_MAXPAGES];
+	struct page *pages[DIV_ROUND_UP(NFS4ACL_SIZE_MAX, PAGE_SIZE) + 1 /* scratch */] = {};
 	struct nfs_setaclargs arg = {
+		.server		= server,
 		.fh		= NFS_FH(inode),
 		.acl_pages	= pages,
-		.acl_len	= buflen,
 	};
 	struct nfs_setaclres res;
 	struct rpc_message msg = {
@@ -4684,16 +4747,20 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
 		.rpc_argp	= &arg,
 		.rpc_resp	= &res,
 	};
-	unsigned int npages = DIV_ROUND_UP(buflen, PAGE_SIZE);
 	int ret, i;
 
-	if (!nfs4_server_supports_acls(server))
-		return -EOPNOTSUPP;
-	if (npages > ARRAY_SIZE(pages))
-		return -ERANGE;
-	i = buf_to_pages_noslab(buf, buflen, arg.acl_pages);
-	if (i < 0)
-		return i;
+	ret = richacl_supported(server, acl);
+	if (ret)
+		return ret;
+
+	ret = nfs4_encode_acl(pages, NFS4ACL_SIZE_MAX, acl, server);
+	if (ret < 0) {
+		for (i = 0; i < ARRAY_SIZE(pages) && pages[i]; i++)
+			put_page(pages[i]);
+		return ret;
+	}
+	arg.acl_len = ret;
+
 	nfs4_inode_return_delegation(inode);
 	ret = nfs4_call_sync(server->client, server, &msg, &arg.seq_args, &res.seq_res, 1);
 
@@ -4701,8 +4768,8 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
 	 * Free each page after tx, so the only ref left is
 	 * held by the network stack
 	 */
-	for (; i > 0; i--)
-		put_page(pages[i-1]);
+	for (i = 0; i < ARRAY_SIZE(pages) && pages[i]; i++)
+		put_page(pages[i]);
 
 	/*
 	 * Acl update can result in inode attribute update.
@@ -4716,12 +4783,12 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
 	return ret;
 }
 
-static int nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t buflen)
+static int nfs4_proc_set_acl(struct inode *inode, struct richacl *acl)
 {
 	struct nfs4_exception exception = { };
 	int err;
 	do {
-		err = __nfs4_proc_set_acl(inode, buf, buflen);
+		err = __nfs4_proc_set_acl(inode, acl);
 		trace_nfs4_set_acl(inode, err);
 		err = nfs4_handle_exception(NFS_SERVER(inode), err,
 				&exception);
@@ -6198,34 +6265,283 @@ nfs4_release_lockowner(struct nfs_server *server, struct nfs4_lock_state *lsp)
 	rpc_call_async(server->client, &msg, 0, &nfs4_release_lockowner_ops, data);
 }
 
+static int nfs4_xattr_set_richacl(struct dentry *dentry, const char *key,
+				  const void *buf, size_t buflen,
+				  int flags, int handler_flags)
+{
+	struct inode *inode = d_inode(dentry);
+	struct richacl *acl;
+	int error;
+
+	if (strcmp(key, "") != 0)
+		return -EINVAL;
+
+	if (buf) {
+		acl = richacl_from_xattr(&init_user_ns, buf, buflen);
+		if (IS_ERR(acl))
+			return PTR_ERR(acl);
+		error = richacl_apply_masks(&acl, inode->i_uid);
+	} else {
+		/*
+		 * "Remove the acl"; only permissions granted by the mode
+		 * remain.  We are using the cached mode here which could be
+		 * outdated; should we do a GETATTR first to narrow down the
+		 * race window?
+		 */
+		acl = richacl_from_mode(inode->i_mode);
+		error = 0;
+	}
+
+	if (!error)
+		error = nfs4_proc_set_acl(inode, acl);
+	richacl_put(acl);
+	return error;
+}
+
+static int nfs4_xattr_get_richacl(struct dentry *dentry, const char *key,
+				  void *buf, size_t buflen, int handler_flags)
+{
+	struct inode *inode = d_inode(dentry);
+	struct richacl *acl;
+	int error;
+	mode_t mode = inode->i_mode & S_IFMT;
+
+	if (strcmp(key, "") != 0)
+		return -EINVAL;
+
+	acl = nfs4_proc_get_acl(inode);
+	if (IS_ERR(acl))
+		return PTR_ERR(acl);
+	if (acl == NULL)
+		return -ENODATA;
+	error = -ENODATA;
+	if (richacl_equiv_mode(acl, &mode) == 0 &&
+	    ((mode ^ inode->i_mode) & S_IRWXUGO) == 0)
+		goto out;
+	error = richacl_to_xattr(&init_user_ns, acl, buf, buflen);
+out:
+	richacl_put(acl);
+	return error;
+}
+
+static size_t nfs4_xattr_list_richacl(struct dentry *dentry, char *list,
+				      size_t list_len, const char *name,
+				      size_t name_len, int handler_flags)
+{
+	struct nfs_server *server = NFS_SERVER(d_inode(dentry));
+	size_t len = sizeof(XATTR_NAME_RICHACL);
+
+	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
+		return 0;
+
+	if (list && len <= list_len)
+		memcpy(list, XATTR_NAME_RICHACL, len);
+	return len;
+}
+
 #define XATTR_NAME_NFSV4_ACL "system.nfs4_acl"
 
+static int richacl_to_nfs4_acl(struct nfs_server *server,
+			       const struct richacl *acl,
+			       void *buf, size_t buflen)
+{
+	const struct richace *ace;
+	__be32 *p = buf;
+	size_t size = 0;
+
+	size += sizeof(*p);
+	if (buflen >= size)
+		*p++ = cpu_to_be32(acl->a_count);
+
+	richacl_for_each_entry(ace, acl) {
+		char who_buf[IDMAP_NAMESZ];
+		const char *who = who_buf;
+		int who_len;
+
+		size += 3 * sizeof(*p);
+		if (buflen >= size) {
+			*p++ = cpu_to_be32(ace->e_type);
+			*p++ = cpu_to_be32(ace->e_flags &
+					   ~(RICHACE_INHERITED_ACE |
+					     RICHACE_UNMAPPED_WHO |
+					     RICHACE_SPECIAL_WHO));
+			*p++ = cpu_to_be32(ace->e_mask);
+		}
+
+		if (richace_is_unix_user(ace)) {
+			who_len = nfs_map_uid_to_name(server, ace->e_id.uid,
+						      who_buf, sizeof(who_buf));
+			if (who_len < 0)
+				return -EIO;
+		} else if (richace_is_unix_group(ace)) {
+			who_len = nfs_map_gid_to_group(server, ace->e_id.gid,
+						       who_buf, sizeof(who_buf));
+			if (who_len < 0)
+				return -EIO;
+		} else if (ace->e_flags & RICHACE_SPECIAL_WHO) {
+			if (!nfs4acl_special_id_to_who(ace->e_id.special,
+						       &who, &who_len))
+				return -EIO;
+		} else {
+			who = richace_unmapped_identifier(ace, acl);
+			if (who)
+				who_len = strlen(who);
+			else
+				return -EIO;
+		}
+
+		size += sizeof(*p) + ALIGN(who_len, sizeof(*p));
+		if (buflen >= size) {
+			unsigned int padding = -who_len & (sizeof(*p) - 1);
+
+			*p++ = cpu_to_be32(who_len);
+			memcpy(p, who, who_len);
+			memset((char *)p + who_len, 0, padding);
+			p += DIV_ROUND_UP(who_len, sizeof(*p));
+		}
+	}
+	if (buflen && buflen < size)
+		return -ERANGE;
+	return size;
+}
+
+static struct richacl *richacl_from_nfs4_acl(struct nfs_server *server,
+					     const void *buf, size_t buflen)
+{
+	struct richacl *acl = NULL;
+	struct richace *ace;
+	const __be32 *p = buf;
+	int count, err;
+
+	if (buflen < sizeof(*p))
+		return ERR_PTR(-EINVAL);
+	count = be32_to_cpu(*p++);
+	if (count > RICHACL_XATTR_MAX_COUNT)
+		return ERR_PTR(-EINVAL);
+	buflen -= sizeof(*p);
+	acl = richacl_alloc(count, GFP_NOFS);
+	if (!acl)
+		return ERR_PTR(-ENOMEM);
+	richacl_for_each_entry(ace, acl) {
+		u32 who_len, size;
+		int special_id;
+		char *who;
+
+		err = -EINVAL;
+		if (buflen < 4 * sizeof(*p))
+			goto out;
+		ace->e_type = be32_to_cpu(*p++);
+		ace->e_flags = be32_to_cpu(*p++);
+		if (ace->e_flags & (RICHACE_SPECIAL_WHO | RICHACE_UNMAPPED_WHO))
+			goto out;
+		ace->e_mask = be32_to_cpu(*p++);
+		who_len = be32_to_cpu(*p++);
+		buflen -= 4 * sizeof(*p);
+		size = ALIGN(who_len, 4);
+		if (buflen < size || size == 0)
+			goto out;
+		who = (char *)p;
+		special_id = nfs4acl_who_to_special_id(who, who_len);
+		if (special_id >= 0) {
+			ace->e_flags |= RICHACE_SPECIAL_WHO;
+			ace->e_id.special = special_id;
+		} else {
+			bool unmappable;
+
+			if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
+				err = nfs_map_group_to_gid(server, who, who_len,
+							   &ace->e_id.gid);
+				if (err) {
+					dprintk("%s: nfs_map_group_to_gid "
+						"failed!\n", __func__);
+					goto out;
+				}
+				/* FIXME: nfsidmap doesn't distinguish between
+					  group nobody and unmappable groups! */
+				unmappable = gid_eq(ace->e_id.gid,
+					make_kgid(&init_user_ns, 99));
+			} else {
+				err = nfs_map_name_to_uid(server, who, who_len,
+							  &ace->e_id.uid);
+				if (err) {
+					dprintk("%s: nfs_map_name_to_gid "
+						"failed!\n", __func__);
+					goto out;
+				}
+				/* FIXME: nfsidmap doesn't distinguish between
+					  user nobody and unmappable users! */
+				unmappable = uid_eq(ace->e_id.uid,
+					make_kuid(&init_user_ns, 99));
+			}
+			if (unmappable) {
+				err = -ENOMEM;
+				if (richacl_add_unmapped_identifier(&acl, &ace,
+					who, who_len, GFP_NOFS))
+					goto out;
+			}
+		}
+		p += size / sizeof(*p);
+		buflen -= size;
+	}
+	err = -EINVAL;
+	if (buflen != 0)
+		goto out;
+	err = 0;
+
+out:
+	if (err) {
+		richacl_put(acl);
+		acl = ERR_PTR(err);
+	}
+	return acl;
+}
+
 static int nfs4_xattr_set_nfs4_acl(struct dentry *dentry, const char *key,
 				   const void *buf, size_t buflen,
 				   int flags, int type)
 {
-	if (strcmp(key, "") != 0)
+	struct inode *inode = d_inode(dentry);
+	struct richacl *acl;
+	int error;
+
+	if (!buf || strcmp(key, "") != 0)
 		return -EINVAL;
 
-	return nfs4_proc_set_acl(d_inode(dentry), buf, buflen);
+	acl = richacl_from_nfs4_acl(NFS_SERVER(inode), (void *)buf, buflen);
+	if (IS_ERR(acl))
+		return PTR_ERR(acl);
+	error = nfs4_proc_set_acl(inode, acl);
+	richacl_put(acl);
+	return error;
 }
 
 static int nfs4_xattr_get_nfs4_acl(struct dentry *dentry, const char *key,
 				   void *buf, size_t buflen, int type)
 {
+	struct inode *inode = d_inode(dentry);
+	struct richacl *acl;
+	int error;
+
 	if (strcmp(key, "") != 0)
 		return -EINVAL;
-
-	return nfs4_proc_get_acl(d_inode(dentry), buf, buflen);
+	acl = nfs4_proc_get_acl(inode);
+	if (IS_ERR(acl))
+		return PTR_ERR(acl);
+	if (acl == NULL)
+		return -ENODATA;
+	error = richacl_to_nfs4_acl(NFS_SERVER(inode), acl, buf, buflen);
+	richacl_put(acl);
+	return error;
 }
 
 static size_t nfs4_xattr_list_nfs4_acl(struct dentry *dentry, char *list,
 				       size_t list_len, const char *name,
 				       size_t name_len, int type)
 {
+	struct nfs_server *server = NFS_SERVER(d_inode(dentry));
 	size_t len = sizeof(XATTR_NAME_NFSV4_ACL);
 
-	if (!nfs4_server_supports_acls(NFS_SERVER(d_inode(dentry))))
+	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
 		return 0;
 
 	if (list && len <= list_len)
@@ -8757,6 +9073,13 @@ const struct nfs_rpc_ops nfs_v4_clientops = {
 	.clone_server	= nfs_clone_server,
 };
 
+static const struct xattr_handler nfs4_xattr_richacl_handler = {
+	.prefix	= XATTR_NAME_RICHACL,
+	.list	= nfs4_xattr_list_richacl,
+	.get	= nfs4_xattr_get_richacl,
+	.set	= nfs4_xattr_set_richacl,
+};
+
 static const struct xattr_handler nfs4_xattr_nfs4_acl_handler = {
 	.prefix	= XATTR_NAME_NFSV4_ACL,
 	.list	= nfs4_xattr_list_nfs4_acl,
@@ -8765,6 +9088,7 @@ static const struct xattr_handler nfs4_xattr_nfs4_acl_handler = {
 };
 
 const struct xattr_handler *nfs4_xattr_handlers[] = {
+	&nfs4_xattr_richacl_handler,
 	&nfs4_xattr_nfs4_acl_handler,
 #ifdef CONFIG_NFS_V4_SECURITY_LABEL
 	&nfs4_xattr_nfs4_label_handler,
diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
index b2a243b..78a9fbd 100644
--- a/fs/nfs/nfs4xdr.c
+++ b/fs/nfs/nfs4xdr.c
@@ -52,6 +52,10 @@
 #include <linux/nfs.h>
 #include <linux/nfs4.h>
 #include <linux/nfs_fs.h>
+#include <linux/nfs_idmap.h>
+#include <linux/richacl.h>
+#include <linux/richacl_xattr.h>  /* for RICHACL_XATTR_MAX_COUNT */
+#include <linux/nfs4acl.h>
 
 #include "nfs4_fs.h"
 #include "internal.h"
@@ -1637,16 +1641,24 @@ encode_restorefh(struct xdr_stream *xdr, struct compound_hdr *hdr)
 static void
 encode_setacl(struct xdr_stream *xdr, struct nfs_setaclargs *arg, struct compound_hdr *hdr)
 {
-	__be32 *p;
+	int attrlen_offset;
+	__be32 attrlen, *p;
 
 	encode_op_hdr(xdr, OP_SETATTR, decode_setacl_maxsz, hdr);
 	encode_nfs4_stateid(xdr, &zero_stateid);
+
+	/* Encode attribute bitmap. */
 	p = reserve_space(xdr, 2*4);
 	*p++ = cpu_to_be32(1);
 	*p = cpu_to_be32(FATTR4_WORD0_ACL);
-	p = reserve_space(xdr, 4);
-	*p = cpu_to_be32(arg->acl_len);
+
+	attrlen_offset = xdr->buf->len;
+	xdr_reserve_space(xdr, 4);  /* to be backfilled later */
+
 	xdr_write_pages(xdr, arg->acl_pages, 0, arg->acl_len);
+
+	attrlen = htonl(xdr->buf->len - attrlen_offset - 4);
+	write_bytes_to_xdr_buf(xdr->buf, attrlen_offset, &attrlen, 4);
 }
 
 static void
@@ -2475,7 +2487,7 @@ static void nfs4_xdr_enc_getacl(struct rpc_rqst *req, struct xdr_stream *xdr,
 	encode_sequence(xdr, &args->seq_args, &hdr);
 	encode_putfh(xdr, args->fh, &hdr);
 	replen = hdr.replen + op_decode_hdr_maxsz + 1;
-	encode_getattr_two(xdr, FATTR4_WORD0_ACL, 0, &hdr);
+	encode_getattr_two(xdr, FATTR4_WORD0_ACL, FATTR4_WORD1_MODE, &hdr);
 
 	xdr_inline_pages(&req->rq_rcv_buf, replen << 2,
 		args->acl_pages, 0, args->acl_len);
@@ -5227,24 +5239,135 @@ decode_restorefh(struct xdr_stream *xdr)
 	return decode_op_hdr(xdr, OP_RESTOREFH);
 }
 
+static int
+nfs4_decode_ace_who(struct richace *ace,
+		    const char **unmapped, unsigned int *unmapped_len,
+		    const struct nfs_server *server,
+		    struct xdr_stream *xdr)
+{
+	char *who;
+	u32 len;
+	int special_id;
+	__be32 *p;
+	int error;
+
+	p = xdr_inline_decode(xdr, 4);
+	if (!p)
+		return -ENOMEM;  /* acl truncated */
+	len = be32_to_cpup(p++);
+	if (len >= XDR_MAX_NETOBJ) {
+		dprintk("%s: name too long (%u)!\n",
+			__func__, len);
+		return -EIO;
+	}
+	who = (char *)xdr_inline_decode(xdr, len);
+	if (!who)
+		return -ENOMEM;  /* acl truncated */
+
+	special_id = nfs4acl_who_to_special_id(who, len);
+	if (special_id >= 0) {
+		ace->e_flags |= RICHACE_SPECIAL_WHO;
+		ace->e_flags &= ~RICHACE_IDENTIFIER_GROUP;
+		ace->e_id.special = special_id;
+		return 0;
+	}
+	if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
+		error = nfs_map_group_to_gid(server, who, len, &ace->e_id.gid);
+		if (error) {
+			dprintk("%s: nfs_map_group_to_gid failed!\n",
+					__func__);
+			return error;
+		}
+		/* FIXME: nfsidmap doesn't distinguish between group nobody and
+			  unmappable groups! */
+		if (gid_eq(ace->e_id.gid, make_kgid(&init_user_ns, 99))) {
+			*unmapped = who;
+			*unmapped_len = len;
+		}
+	} else {
+		error = nfs_map_name_to_uid(server, who, len, &ace->e_id.uid);
+		if (error) {
+			dprintk("%s: nfs_map_name_to_uid failed!\n",
+					__func__);
+			return error;
+		}
+		/* FIXME: nfsidmap doesn't distinguish between user nobody and
+			  unmappable users! */
+		if (uid_eq(ace->e_id.uid, make_kuid(&init_user_ns, 99))) {
+			*unmapped = who;
+			*unmapped_len = len;
+		}
+	}
+	return 0;
+}
+
+static struct richacl *
+decode_acl_entries(struct xdr_stream *xdr, const struct nfs_server *server)
+{
+	struct richacl *acl;
+	struct richace *ace;
+	uint32_t count;
+	__be32 *p;
+	int status;
+
+	p = xdr_inline_decode(xdr, 4);
+	if (unlikely(!p))
+		return ERR_PTR(-ENOMEM);  /* acl truncated */
+	count = be32_to_cpup(p);
+	if (count > RICHACL_XATTR_MAX_COUNT)
+		return ERR_PTR(-EIO);
+	acl = richacl_alloc(count, GFP_NOFS);
+	if (!acl)
+		return ERR_PTR(-ENOMEM);
+	richacl_for_each_entry(ace, acl) {
+		const char *unmapped = NULL;
+		unsigned int unmapped_len;
+
+		p = xdr_inline_decode(xdr, 4*3);
+		status = -ENOMEM;
+		if (unlikely(!p))
+			goto out;  /* acl truncated */
+		ace->e_type = be32_to_cpup(p++);
+		ace->e_flags = be32_to_cpup(p++);
+		status = -EIO;
+		if (ace->e_flags &
+		    (RICHACE_SPECIAL_WHO | RICHACE_UNMAPPED_WHO))
+			goto out;
+		ace->e_mask = be32_to_cpup(p++);
+		status = nfs4_decode_ace_who(ace, &unmapped,
+					     &unmapped_len, server,
+					     xdr);
+		if (status)
+			goto out;
+		if (unmapped) {
+			status = -ENOMEM;
+			if (richacl_add_unmapped_identifier(&acl, &ace,
+					unmapped, unmapped_len,
+					GFP_NOFS))
+				goto out;
+		}
+	}
+	status = 0;
+
+out:
+	if (status) {
+		richacl_put(acl);
+		acl = ERR_PTR(status);
+	}
+	return acl;
+}
+
 static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req,
 			 struct nfs_getaclres *res)
 {
 	unsigned int savep;
 	uint32_t attrlen,
 		 bitmap[3] = {0};
+	struct richacl *acl = NULL;
 	int status;
-	unsigned int pg_offset;
 
-	res->acl_len = 0;
 	if ((status = decode_op_hdr(xdr, OP_GETATTR)) != 0)
 		goto out;
-
-	xdr_enter_page(xdr, xdr->buf->page_len);
-
-	/* Calculate the offset of the page data */
-	pg_offset = xdr->buf->head[0].iov_len;
-
 	if ((status = decode_attr_bitmap(xdr, bitmap)) != 0)
 		goto out;
 	if ((status = decode_attr_length(xdr, &attrlen, &savep)) != 0)
@@ -5253,24 +5376,28 @@ static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req,
 	if (unlikely(bitmap[0] & (FATTR4_WORD0_ACL - 1U)))
 		return -EIO;
 	if (likely(bitmap[0] & FATTR4_WORD0_ACL)) {
-
-		/* The bitmap (xdr len + bitmaps) and the attr xdr len words
-		 * are stored with the acl data to handle the problem of
-		 * variable length bitmaps.*/
-		res->acl_data_offset = xdr_stream_pos(xdr) - pg_offset;
-		res->acl_len = attrlen;
-
-		/* Check for receive buffer overflow */
-		if (res->acl_len > (xdr->nwords << 2) ||
-		    res->acl_len + res->acl_data_offset > xdr->buf->page_len) {
-			res->acl_flags |= NFS4_ACL_TRUNC;
-			dprintk("NFS: acl reply: attrlen %u > page_len %u\n",
-					attrlen, xdr->nwords << 2);
-		}
+		acl = decode_acl_entries(xdr, res->server);
+		status = PTR_ERR(acl);
+		if (IS_ERR(acl))
+			goto out;
+		bitmap[0] &= ~FATTR4_WORD0_ACL;
 	} else
 		status = -EOPNOTSUPP;
 
+	status = -EIO;
+	if (unlikely(bitmap[0]))
+		goto out;
+
+	status = decode_attr_mode(xdr, bitmap, &res->mode);
+	if (status < 0)
+		goto out;
+	status = 0;
+
 out:
+	if (status == 0)
+		res->acl = acl;
+	else
+		richacl_put(acl);
 	return status;
 }
 
diff --git a/fs/nfs/super.c b/fs/nfs/super.c
index aa62004..fbbcac9 100644
--- a/fs/nfs/super.c
+++ b/fs/nfs/super.c
@@ -2316,7 +2316,7 @@ void nfs_fill_super(struct super_block *sb, struct nfs_mount_info *mount_info)
 		/* The VFS shouldn't apply the umask to mode bits. We will do
 		 * so ourselves when necessary.
 		 */
-		sb->s_flags |= MS_POSIXACL;
+		sb->s_flags |= MS_RICHACL;
 		sb->s_time_gran = 1;
 	}
 
@@ -2343,7 +2343,7 @@ void nfs_clone_super(struct super_block *sb, struct nfs_mount_info *mount_info)
 		/* The VFS shouldn't apply the umask to mode bits. We will do
 		 * so ourselves when necessary.
 		 */
-		sb->s_flags |= MS_POSIXACL;
+		sb->s_flags |= MS_RICHACL;
 	}
 
  	nfs_initialise_sb(sb);
diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h
index 874b772..eb923c6 100644
--- a/include/linux/nfs_fs.h
+++ b/include/linux/nfs_fs.h
@@ -176,7 +176,6 @@ struct nfs_inode {
 	wait_queue_head_t	waitqueue;
 
 #if IS_ENABLED(CONFIG_NFS_V4)
-	struct nfs4_cached_acl	*nfs4_acl;
         /* NFSv4 state */
 	struct list_head	open_states;
 	struct nfs_delegation __rcu *delegation;
diff --git a/include/linux/nfs_fs_sb.h b/include/linux/nfs_fs_sb.h
index 20bc8e5..f128a49 100644
--- a/include/linux/nfs_fs_sb.h
+++ b/include/linux/nfs_fs_sb.h
@@ -238,5 +238,7 @@ struct nfs_server {
 #define NFS_CAP_ALLOCATE	(1U << 20)
 #define NFS_CAP_DEALLOCATE	(1U << 21)
 #define NFS_CAP_LAYOUTSTATS	(1U << 22)
+#define NFS_CAP_ALLOW_ACLS	(1U << 23)
+#define NFS_CAP_DENY_ACLS	(1U << 24)
 
 #endif
diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
index f10bbac..f1c8f93 100644
--- a/include/linux/nfs_xdr.h
+++ b/include/linux/nfs_xdr.h
@@ -683,9 +683,10 @@ struct nfs_setattrargs {
 
 struct nfs_setaclargs {
 	struct nfs4_sequence_args	seq_args;
+	const struct nfs_server *	server;
 	struct nfs_fh *			fh;
-	size_t				acl_len;
 	struct page **			acl_pages;
+	size_t				acl_len;
 };
 
 struct nfs_setaclres {
@@ -703,9 +704,9 @@ struct nfs_getaclargs {
 #define NFS4_ACL_TRUNC		0x0001	/* ACL was truncated */
 struct nfs_getaclres {
 	struct nfs4_sequence_res	seq_res;
-	size_t				acl_len;
-	size_t				acl_data_offset;
-	int				acl_flags;
+	const struct nfs_server *	server;
+	struct richacl *		acl;
+	umode_t				mode;
 	struct page *			acl_scratch;
 };
 
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 40/41] nfs: Add support for the v4.1 dacl attribute
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

The dacl attribute includes Automatic Inheritance flags not supported by
the acl attribute.  it is only supported in NFS version 4.1 and higher.
On systems where NFS version 4.0 is still the default, an additional
mount option is needed:

    mount -t nfs4 -o vers=4.1 [...]

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 fs/nfs/nfs4proc.c       |  2 +-
 fs/nfs/nfs4xdr.c        | 55 ++++++++++++++++++++++++++++++++++++++++++-------
 include/linux/nfs_xdr.h |  2 +-
 3 files changed, 50 insertions(+), 9 deletions(-)

diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index f680364..b17e7c9 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -4482,7 +4482,7 @@ static struct richacl *__nfs4_get_acl_uncached(struct inode *inode)
 	struct nfs_server *server = NFS_SERVER(inode);
 	struct page *pages[DIV_ROUND_UP(NFS4ACL_SIZE_MAX, PAGE_SIZE)] = {};
 	struct nfs_getaclargs args = {
-		.fh = NFS_FH(inode),
+		.inode = inode,
 		.acl_pages = pages,
 		.acl_len = ARRAY_SIZE(pages) * PAGE_SIZE,
 	};
diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
index 78a9fbd..bfb4d49 100644
--- a/fs/nfs/nfs4xdr.c
+++ b/fs/nfs/nfs4xdr.c
@@ -1648,9 +1648,16 @@ encode_setacl(struct xdr_stream *xdr, struct nfs_setaclargs *arg, struct compoun
 	encode_nfs4_stateid(xdr, &zero_stateid);
 
 	/* Encode attribute bitmap. */
-	p = reserve_space(xdr, 2*4);
-	*p++ = cpu_to_be32(1);
-	*p = cpu_to_be32(FATTR4_WORD0_ACL);
+	if (arg->server->attr_bitmask[1] & FATTR4_WORD1_DACL) {
+		p = reserve_space(xdr, 3*4);
+		*p++ = cpu_to_be32(2);
+		*p++ = 0;
+		*p = cpu_to_be32(FATTR4_WORD1_DACL);
+	} else {
+		p = reserve_space(xdr, 2*4);
+		*p++ = cpu_to_be32(1);
+		*p = cpu_to_be32(FATTR4_WORD0_ACL);
+	}
 
 	attrlen_offset = xdr->buf->len;
 	xdr_reserve_space(xdr, 4);  /* to be backfilled later */
@@ -2485,9 +2492,12 @@ static void nfs4_xdr_enc_getacl(struct rpc_rqst *req, struct xdr_stream *xdr,
 
 	encode_compound_hdr(xdr, req, &hdr);
 	encode_sequence(xdr, &args->seq_args, &hdr);
-	encode_putfh(xdr, args->fh, &hdr);
+	encode_putfh(xdr, NFS_FH(args->inode), &hdr);
 	replen = hdr.replen + op_decode_hdr_maxsz + 1;
-	encode_getattr_two(xdr, FATTR4_WORD0_ACL, FATTR4_WORD1_MODE, &hdr);
+	if (NFS_SERVER(args->inode)->attr_bitmask[1] & FATTR4_WORD1_DACL)
+		encode_getattr_two(xdr, 0, FATTR4_WORD1_MODE | FATTR4_WORD1_DACL, &hdr);
+	else
+		encode_getattr_two(xdr, FATTR4_WORD0_ACL, FATTR4_WORD1_MODE, &hdr);
 
 	xdr_inline_pages(&req->rq_rcv_buf, replen << 2,
 		args->acl_pages, 0, args->acl_len);
@@ -5375,14 +5385,28 @@ static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req,
 
 	if (unlikely(bitmap[0] & (FATTR4_WORD0_ACL - 1U)))
 		return -EIO;
-	if (likely(bitmap[0] & FATTR4_WORD0_ACL)) {
+
+	if (bitmap[0] & FATTR4_WORD0_ACL) {
+		struct richace *ace;
+
+		if (bitmap[1] & FATTR4_WORD1_DACL)
+			return -EIO;
+
 		acl = decode_acl_entries(xdr, res->server);
 		status = PTR_ERR(acl);
 		if (IS_ERR(acl))
 			goto out;
+
+		status = -EIO;
+		richacl_for_each_entry(ace, acl) {
+			if (ace->e_flags & RICHACE_INHERITED_ACE)
+				goto out;
+		}
 		bitmap[0] &= ~FATTR4_WORD0_ACL;
-	} else
+	} else if (!(bitmap[1] & FATTR4_WORD1_DACL)) {
 		status = -EOPNOTSUPP;
+		goto out;
+	}
 
 	status = -EIO;
 	if (unlikely(bitmap[0]))
@@ -5391,6 +5415,23 @@ static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req,
 	status = decode_attr_mode(xdr, bitmap, &res->mode);
 	if (status < 0)
 		goto out;
+	if (bitmap[1] & FATTR4_WORD1_DACL) {
+		unsigned int flags;
+		__be32 *p;
+
+		p = xdr_inline_decode(xdr, 4);
+		status = -EIO;
+		if (unlikely(!p))
+			goto out;
+		flags = be32_to_cpup(p);
+
+		acl = decode_acl_entries(xdr, res->server);
+		status = PTR_ERR(acl);
+		if (IS_ERR(acl))
+			goto out;
+		acl->a_flags = flags;
+		bitmap[1] &= ~FATTR4_WORD1_DACL;
+	}
 	status = 0;
 
 out:
diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
index f1c8f93..abe0b66 100644
--- a/include/linux/nfs_xdr.h
+++ b/include/linux/nfs_xdr.h
@@ -695,7 +695,7 @@ struct nfs_setaclres {
 
 struct nfs_getaclargs {
 	struct nfs4_sequence_args 	seq_args;
-	struct nfs_fh *			fh;
+	struct inode *			inode;
 	size_t				acl_len;
 	struct page **			acl_pages;
 };
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 40/41] nfs: Add support for the v4.1 dacl attribute
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

The dacl attribute includes Automatic Inheritance flags not supported by
the acl attribute.  it is only supported in NFS version 4.1 and higher.
On systems where NFS version 4.0 is still the default, an additional
mount option is needed:

    mount -t nfs4 -o vers=4.1 [...]

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/nfs/nfs4proc.c       |  2 +-
 fs/nfs/nfs4xdr.c        | 55 ++++++++++++++++++++++++++++++++++++++++++-------
 include/linux/nfs_xdr.h |  2 +-
 3 files changed, 50 insertions(+), 9 deletions(-)

diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index f680364..b17e7c9 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -4482,7 +4482,7 @@ static struct richacl *__nfs4_get_acl_uncached(struct inode *inode)
 	struct nfs_server *server = NFS_SERVER(inode);
 	struct page *pages[DIV_ROUND_UP(NFS4ACL_SIZE_MAX, PAGE_SIZE)] = {};
 	struct nfs_getaclargs args = {
-		.fh = NFS_FH(inode),
+		.inode = inode,
 		.acl_pages = pages,
 		.acl_len = ARRAY_SIZE(pages) * PAGE_SIZE,
 	};
diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
index 78a9fbd..bfb4d49 100644
--- a/fs/nfs/nfs4xdr.c
+++ b/fs/nfs/nfs4xdr.c
@@ -1648,9 +1648,16 @@ encode_setacl(struct xdr_stream *xdr, struct nfs_setaclargs *arg, struct compoun
 	encode_nfs4_stateid(xdr, &zero_stateid);
 
 	/* Encode attribute bitmap. */
-	p = reserve_space(xdr, 2*4);
-	*p++ = cpu_to_be32(1);
-	*p = cpu_to_be32(FATTR4_WORD0_ACL);
+	if (arg->server->attr_bitmask[1] & FATTR4_WORD1_DACL) {
+		p = reserve_space(xdr, 3*4);
+		*p++ = cpu_to_be32(2);
+		*p++ = 0;
+		*p = cpu_to_be32(FATTR4_WORD1_DACL);
+	} else {
+		p = reserve_space(xdr, 2*4);
+		*p++ = cpu_to_be32(1);
+		*p = cpu_to_be32(FATTR4_WORD0_ACL);
+	}
 
 	attrlen_offset = xdr->buf->len;
 	xdr_reserve_space(xdr, 4);  /* to be backfilled later */
@@ -2485,9 +2492,12 @@ static void nfs4_xdr_enc_getacl(struct rpc_rqst *req, struct xdr_stream *xdr,
 
 	encode_compound_hdr(xdr, req, &hdr);
 	encode_sequence(xdr, &args->seq_args, &hdr);
-	encode_putfh(xdr, args->fh, &hdr);
+	encode_putfh(xdr, NFS_FH(args->inode), &hdr);
 	replen = hdr.replen + op_decode_hdr_maxsz + 1;
-	encode_getattr_two(xdr, FATTR4_WORD0_ACL, FATTR4_WORD1_MODE, &hdr);
+	if (NFS_SERVER(args->inode)->attr_bitmask[1] & FATTR4_WORD1_DACL)
+		encode_getattr_two(xdr, 0, FATTR4_WORD1_MODE | FATTR4_WORD1_DACL, &hdr);
+	else
+		encode_getattr_two(xdr, FATTR4_WORD0_ACL, FATTR4_WORD1_MODE, &hdr);
 
 	xdr_inline_pages(&req->rq_rcv_buf, replen << 2,
 		args->acl_pages, 0, args->acl_len);
@@ -5375,14 +5385,28 @@ static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req,
 
 	if (unlikely(bitmap[0] & (FATTR4_WORD0_ACL - 1U)))
 		return -EIO;
-	if (likely(bitmap[0] & FATTR4_WORD0_ACL)) {
+
+	if (bitmap[0] & FATTR4_WORD0_ACL) {
+		struct richace *ace;
+
+		if (bitmap[1] & FATTR4_WORD1_DACL)
+			return -EIO;
+
 		acl = decode_acl_entries(xdr, res->server);
 		status = PTR_ERR(acl);
 		if (IS_ERR(acl))
 			goto out;
+
+		status = -EIO;
+		richacl_for_each_entry(ace, acl) {
+			if (ace->e_flags & RICHACE_INHERITED_ACE)
+				goto out;
+		}
 		bitmap[0] &= ~FATTR4_WORD0_ACL;
-	} else
+	} else if (!(bitmap[1] & FATTR4_WORD1_DACL)) {
 		status = -EOPNOTSUPP;
+		goto out;
+	}
 
 	status = -EIO;
 	if (unlikely(bitmap[0]))
@@ -5391,6 +5415,23 @@ static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req,
 	status = decode_attr_mode(xdr, bitmap, &res->mode);
 	if (status < 0)
 		goto out;
+	if (bitmap[1] & FATTR4_WORD1_DACL) {
+		unsigned int flags;
+		__be32 *p;
+
+		p = xdr_inline_decode(xdr, 4);
+		status = -EIO;
+		if (unlikely(!p))
+			goto out;
+		flags = be32_to_cpup(p);
+
+		acl = decode_acl_entries(xdr, res->server);
+		status = PTR_ERR(acl);
+		if (IS_ERR(acl))
+			goto out;
+		acl->a_flags = flags;
+		bitmap[1] &= ~FATTR4_WORD1_DACL;
+	}
 	status = 0;
 
 out:
diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
index f1c8f93..abe0b66 100644
--- a/include/linux/nfs_xdr.h
+++ b/include/linux/nfs_xdr.h
@@ -695,7 +695,7 @@ struct nfs_setaclres {
 
 struct nfs_getaclargs {
 	struct nfs4_sequence_args 	seq_args;
-	struct nfs_fh *			fh;
+	struct inode *			inode;
 	size_t				acl_len;
 	struct page **			acl_pages;
 };
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 41/41] richacl: uapi header split
  2015-09-05 10:26 ` Andreas Gruenbacher
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
 include/linux/richacl.h            | 119 +++++--------------------------------
 include/linux/richacl_xattr.h      |  17 +-----
 include/uapi/linux/Kbuild          |   2 +
 include/uapi/linux/richacl.h       | 111 ++++++++++++++++++++++++++++++++++
 include/uapi/linux/richacl_xattr.h |  43 ++++++++++++++
 5 files changed, 173 insertions(+), 119 deletions(-)
 create mode 100644 include/uapi/linux/richacl.h
 create mode 100644 include/uapi/linux/richacl_xattr.h

diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index 1822666..8875941 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -17,9 +17,7 @@
 #ifndef __RICHACL_H
 #define __RICHACL_H
 
-#define RICHACE_OWNER_SPECIAL_ID	0
-#define RICHACE_GROUP_SPECIAL_ID	1
-#define RICHACE_EVERYONE_SPECIAL_ID	2
+#include <uapi/linux/richacl.h>
 
 struct richace {
 	unsigned short	e_type;
@@ -44,43 +42,12 @@ struct richacl {
 	struct richace	a_entries[0];
 };
 
-#define richacl_for_each_entry(_ace, _acl) \
-	for (_ace = (_acl)->a_entries; \
-	     _ace != (_acl)->a_entries + (_acl)->a_count; \
-	     _ace++)
-
-#define richacl_for_each_entry_reverse(_ace, _acl) \
-	for (_ace = (_acl)->a_entries + (_acl)->a_count - 1; \
-	     _ace != (_acl)->a_entries - 1; \
-	     _ace--)
-
-/* a_flags values */
-#define RICHACL_AUTO_INHERIT			0x01
-#define RICHACL_PROTECTED			0x02
-#define RICHACL_DEFAULTED			0x04
-#define RICHACL_WRITE_THROUGH			0x40
-#define RICHACL_MASKED				0x80
-
 #define RICHACL_VALID_FLAGS (					\
-		RICHACL_AUTO_INHERIT |				\
-		RICHACL_PROTECTED |				\
-		RICHACL_DEFAULTED |				\
-		RICHACL_WRITE_THROUGH |				\
-		RICHACL_MASKED)
-
-/* e_type values */
-#define RICHACE_ACCESS_ALLOWED_ACE_TYPE		0x0000
-#define RICHACE_ACCESS_DENIED_ACE_TYPE		0x0001
-
-/* e_flags bitflags */
-#define RICHACE_FILE_INHERIT_ACE		0x0001
-#define RICHACE_DIRECTORY_INHERIT_ACE		0x0002
-#define RICHACE_NO_PROPAGATE_INHERIT_ACE	0x0004
-#define RICHACE_INHERIT_ONLY_ACE		0x0008
-#define RICHACE_IDENTIFIER_GROUP		0x0040
-#define RICHACE_INHERITED_ACE			0x0080
-#define RICHACE_UNMAPPED_WHO			0x2000
-#define RICHACE_SPECIAL_WHO			0x4000
+	RICHACL_AUTO_INHERIT |					\
+	RICHACL_PROTECTED |					\
+	RICHACL_DEFAULTED |					\
+	RICHACL_WRITE_THROUGH |					\
+	RICHACL_MASKED)
 
 #define RICHACE_VALID_FLAGS (					\
 	RICHACE_FILE_INHERIT_ACE |				\
@@ -99,27 +66,6 @@ struct richacl {
 	RICHACE_INHERIT_ONLY_ACE |				\
 	RICHACE_INHERITED_ACE )
 
-/* e_mask bitflags */
-#define RICHACE_READ_DATA			0x00000001
-#define RICHACE_LIST_DIRECTORY			0x00000001
-#define RICHACE_WRITE_DATA			0x00000002
-#define RICHACE_ADD_FILE			0x00000002
-#define RICHACE_APPEND_DATA			0x00000004
-#define RICHACE_ADD_SUBDIRECTORY		0x00000004
-#define RICHACE_READ_NAMED_ATTRS		0x00000008
-#define RICHACE_WRITE_NAMED_ATTRS		0x00000010
-#define RICHACE_EXECUTE				0x00000020
-#define RICHACE_DELETE_CHILD			0x00000040
-#define RICHACE_READ_ATTRIBUTES			0x00000080
-#define RICHACE_WRITE_ATTRIBUTES		0x00000100
-#define RICHACE_WRITE_RETENTION			0x00000200
-#define RICHACE_WRITE_RETENTION_HOLD		0x00000400
-#define RICHACE_DELETE				0x00010000
-#define RICHACE_READ_ACL			0x00020000
-#define RICHACE_WRITE_ACL			0x00040000
-#define RICHACE_WRITE_OWNER			0x00080000
-#define RICHACE_SYNCHRONIZE			0x00100000
-
 /* Valid RICHACE_* flags for directories and non-directories */
 #define RICHACE_VALID_MASK (					\
 	RICHACE_READ_DATA | RICHACE_LIST_DIRECTORY |		\
@@ -139,49 +85,16 @@ struct richacl {
 	RICHACE_WRITE_OWNER |					\
 	RICHACE_SYNCHRONIZE)
 
-/*
- * The POSIX permissions are supersets of the following NFSv4 permissions:
- *
- *  - MAY_READ maps to READ_DATA or LIST_DIRECTORY, depending on the type
- *    of the file system object.
- *
- *  - MAY_WRITE maps to WRITE_DATA or RICHACE_APPEND_DATA for files, and to
- *    ADD_FILE, RICHACE_ADD_SUBDIRECTORY, or RICHACE_DELETE_CHILD for directories.
- *
- *  - MAY_EXECUTE maps to RICHACE_EXECUTE.
- *
- *  (Some of these NFSv4 permissions have the same bit values.)
- */
-#define RICHACE_POSIX_MODE_READ (			\
-		RICHACE_READ_DATA |		\
-		RICHACE_LIST_DIRECTORY)
-#define RICHACE_POSIX_MODE_WRITE (			\
-		RICHACE_WRITE_DATA |		\
-		RICHACE_ADD_FILE |			\
-		RICHACE_APPEND_DATA |		\
-		RICHACE_ADD_SUBDIRECTORY |		\
-		RICHACE_DELETE_CHILD)
-#define RICHACE_POSIX_MODE_EXEC RICHACE_EXECUTE
-#define RICHACE_POSIX_MODE_ALL (			\
-		RICHACE_POSIX_MODE_READ |		\
-		RICHACE_POSIX_MODE_WRITE |		\
-		RICHACE_POSIX_MODE_EXEC)
-/*
- * These permissions are always allowed
- * no matter what the acl says.
- */
-#define RICHACE_POSIX_ALWAYS_ALLOWED (	\
-		RICHACE_SYNCHRONIZE |	\
-		RICHACE_READ_ATTRIBUTES |	\
-		RICHACE_READ_ACL)
-/*
- * The owner is implicitly granted
- * these permissions under POSIX.
- */
-#define RICHACE_POSIX_OWNER_ALLOWED (		\
-		RICHACE_WRITE_ATTRIBUTES |		\
-		RICHACE_WRITE_OWNER |		\
-		RICHACE_WRITE_ACL)
+#define richacl_for_each_entry(_ace, _acl) \
+	for (_ace = (_acl)->a_entries; \
+	     _ace != (_acl)->a_entries + (_acl)->a_count; \
+	     _ace++)
+
+#define richacl_for_each_entry_reverse(_ace, _acl) \
+	for (_ace = (_acl)->a_entries + (_acl)->a_count - 1; \
+	     _ace != (_acl)->a_entries - 1; \
+	     _ace--)
+
 /**
  * richacl_get  -  grab another reference to a richacl handle
  */
diff --git a/include/linux/richacl_xattr.h b/include/linux/richacl_xattr.h
index f84cc21..eff36a3 100644
--- a/include/linux/richacl_xattr.h
+++ b/include/linux/richacl_xattr.h
@@ -17,24 +17,9 @@
 #ifndef __RICHACL_XATTR_H
 #define __RICHACL_XATTR_H
 
+#include <uapi/linux/richacl_xattr.h>
 #include <linux/richacl.h>
 
-struct richace_xattr {
-	__le16		e_type;
-	__le16		e_flags;
-	__le32		e_mask;
-	__le32		e_id;
-};
-
-struct richacl_xattr {
-	unsigned char	a_version;
-	unsigned char	a_flags;
-	__le16		a_count;
-	__le32		a_owner_mask;
-	__le32		a_group_mask;
-	__le32		a_other_mask;
-};
-
 #define RICHACL_XATTR_VERSION	0
 #define RICHACL_XATTR_MAX_COUNT \
 	((XATTR_SIZE_MAX - sizeof(struct richacl_xattr)) / \
diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild
index 1ff9942..3f278fa 100644
--- a/include/uapi/linux/Kbuild
+++ b/include/uapi/linux/Kbuild
@@ -346,6 +346,8 @@ header-y += reboot.h
 header-y += reiserfs_fs.h
 header-y += reiserfs_xattr.h
 header-y += resource.h
+header-y += richacl.h
+header-y += richacl_xattr.h
 header-y += rfkill.h
 header-y += romfs_fs.h
 header-y += rose.h
diff --git a/include/uapi/linux/richacl.h b/include/uapi/linux/richacl.h
new file mode 100644
index 0000000..6887f88
--- /dev/null
+++ b/include/uapi/linux/richacl.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2006, 2010  Novell, Inc.
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Written by Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
+ *
+ * This program 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, or (at your option) any
+ * later version.
+ *
+ * This program 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.
+ */
+
+#ifndef __UAPI_RICHACL_H
+#define __UAPI_RICHACL_H
+
+/* a_flags values */
+#define RICHACL_AUTO_INHERIT			0x01
+#define RICHACL_PROTECTED			0x02
+#define RICHACL_DEFAULTED			0x04
+#define RICHACL_WRITE_THROUGH			0x40
+#define RICHACL_MASKED				0x80
+
+/* e_type values */
+#define RICHACE_ACCESS_ALLOWED_ACE_TYPE		0x0000
+#define RICHACE_ACCESS_DENIED_ACE_TYPE		0x0001
+
+/* e_flags bitflags */
+#define RICHACE_FILE_INHERIT_ACE		0x0001
+#define RICHACE_DIRECTORY_INHERIT_ACE		0x0002
+#define RICHACE_NO_PROPAGATE_INHERIT_ACE	0x0004
+#define RICHACE_INHERIT_ONLY_ACE		0x0008
+#define RICHACE_IDENTIFIER_GROUP		0x0040
+#define RICHACE_INHERITED_ACE			0x0080
+#define RICHACE_UNMAPPED_WHO			0x2000
+#define RICHACE_SPECIAL_WHO			0x4000
+
+/* e_mask bitflags */
+#define RICHACE_READ_DATA			0x00000001
+#define RICHACE_LIST_DIRECTORY			0x00000001
+#define RICHACE_WRITE_DATA			0x00000002
+#define RICHACE_ADD_FILE			0x00000002
+#define RICHACE_APPEND_DATA			0x00000004
+#define RICHACE_ADD_SUBDIRECTORY		0x00000004
+#define RICHACE_READ_NAMED_ATTRS		0x00000008
+#define RICHACE_WRITE_NAMED_ATTRS		0x00000010
+#define RICHACE_EXECUTE				0x00000020
+#define RICHACE_DELETE_CHILD			0x00000040
+#define RICHACE_READ_ATTRIBUTES			0x00000080
+#define RICHACE_WRITE_ATTRIBUTES		0x00000100
+#define RICHACE_WRITE_RETENTION			0x00000200
+#define RICHACE_WRITE_RETENTION_HOLD		0x00000400
+#define RICHACE_DELETE				0x00010000
+#define RICHACE_READ_ACL			0x00020000
+#define RICHACE_WRITE_ACL			0x00040000
+#define RICHACE_WRITE_OWNER			0x00080000
+#define RICHACE_SYNCHRONIZE			0x00100000
+
+/* e_id values */
+#define RICHACE_OWNER_SPECIAL_ID		0
+#define RICHACE_GROUP_SPECIAL_ID		1
+#define RICHACE_EVERYONE_SPECIAL_ID		2
+
+/*
+ * The POSIX permissions are supersets of the following richacl permissions:
+ *
+ *  - MAY_READ maps to READ_DATA or LIST_DIRECTORY, depending on the type
+ *    of the file system object.
+ *
+ *  - MAY_WRITE maps to WRITE_DATA or RICHACE_APPEND_DATA for files, and to
+ *    ADD_FILE, RICHACE_ADD_SUBDIRECTORY, or RICHACE_DELETE_CHILD for directories.
+ *
+ *  - MAY_EXECUTE maps to RICHACE_EXECUTE.
+ *
+ *  (Some of these richacl permissions have the same bit values.)
+ */
+#define RICHACE_POSIX_MODE_READ (			\
+		RICHACE_READ_DATA |			\
+		RICHACE_LIST_DIRECTORY)
+#define RICHACE_POSIX_MODE_WRITE (			\
+		RICHACE_WRITE_DATA |			\
+		RICHACE_ADD_FILE |			\
+		RICHACE_APPEND_DATA |			\
+		RICHACE_ADD_SUBDIRECTORY |		\
+		RICHACE_DELETE_CHILD)
+#define RICHACE_POSIX_MODE_EXEC RICHACE_EXECUTE
+#define RICHACE_POSIX_MODE_ALL (			\
+		RICHACE_POSIX_MODE_READ |		\
+		RICHACE_POSIX_MODE_WRITE |		\
+		RICHACE_POSIX_MODE_EXEC)
+
+/*
+ * These permissions are always allowed no matter what the acl says.
+ */
+#define RICHACE_POSIX_ALWAYS_ALLOWED (			\
+		RICHACE_SYNCHRONIZE |			\
+		RICHACE_READ_ATTRIBUTES |		\
+		RICHACE_READ_ACL)
+
+/*
+ * The owner is implicitly granted these permissions under POSIX.
+ */
+#define RICHACE_POSIX_OWNER_ALLOWED (			\
+		RICHACE_WRITE_ATTRIBUTES |		\
+		RICHACE_WRITE_OWNER |			\
+		RICHACE_WRITE_ACL)
+
+#endif /* __UAPI_RICHACL_H */
diff --git a/include/uapi/linux/richacl_xattr.h b/include/uapi/linux/richacl_xattr.h
new file mode 100644
index 0000000..6f96bc0
--- /dev/null
+++ b/include/uapi/linux/richacl_xattr.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2006, 2010  Novell, Inc.
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Written by Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
+ *
+ * This program 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, or (at your option) any
+ * later version.
+ *
+ * This program 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.
+ */
+
+#ifndef __UAPI_RICHACL_XATTR_H
+#define __UAPI_RICHACL_XATTR_H
+
+#include <linux/types.h>
+#include <linux/xattr.h>
+
+struct richace_xattr {
+	__le16		e_type;
+	__le16		e_flags;
+	__le32		e_mask;
+	__le32		e_id;
+};
+
+struct richacl_xattr {
+	unsigned char	a_version;
+	unsigned char	a_flags;
+	__le16		a_count;
+	__le32		a_owner_mask;
+	__le32		a_group_mask;
+	__le32		a_other_mask;
+};
+
+#define RICHACL_XATTR_MAX_COUNT \
+	((XATTR_SIZE_MAX - sizeof(struct richacl_xattr)) / \
+	 sizeof(struct richace_xattr))
+
+#endif  /* __UAPI_RICHACL_XATTR_H */
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [RFC v7 41/41] richacl: uapi header split
@ 2015-09-05 10:27     ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-05 10:27 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 include/linux/richacl.h            | 119 +++++--------------------------------
 include/linux/richacl_xattr.h      |  17 +-----
 include/uapi/linux/Kbuild          |   2 +
 include/uapi/linux/richacl.h       | 111 ++++++++++++++++++++++++++++++++++
 include/uapi/linux/richacl_xattr.h |  43 ++++++++++++++
 5 files changed, 173 insertions(+), 119 deletions(-)
 create mode 100644 include/uapi/linux/richacl.h
 create mode 100644 include/uapi/linux/richacl_xattr.h

diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index 1822666..8875941 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -17,9 +17,7 @@
 #ifndef __RICHACL_H
 #define __RICHACL_H
 
-#define RICHACE_OWNER_SPECIAL_ID	0
-#define RICHACE_GROUP_SPECIAL_ID	1
-#define RICHACE_EVERYONE_SPECIAL_ID	2
+#include <uapi/linux/richacl.h>
 
 struct richace {
 	unsigned short	e_type;
@@ -44,43 +42,12 @@ struct richacl {
 	struct richace	a_entries[0];
 };
 
-#define richacl_for_each_entry(_ace, _acl) \
-	for (_ace = (_acl)->a_entries; \
-	     _ace != (_acl)->a_entries + (_acl)->a_count; \
-	     _ace++)
-
-#define richacl_for_each_entry_reverse(_ace, _acl) \
-	for (_ace = (_acl)->a_entries + (_acl)->a_count - 1; \
-	     _ace != (_acl)->a_entries - 1; \
-	     _ace--)
-
-/* a_flags values */
-#define RICHACL_AUTO_INHERIT			0x01
-#define RICHACL_PROTECTED			0x02
-#define RICHACL_DEFAULTED			0x04
-#define RICHACL_WRITE_THROUGH			0x40
-#define RICHACL_MASKED				0x80
-
 #define RICHACL_VALID_FLAGS (					\
-		RICHACL_AUTO_INHERIT |				\
-		RICHACL_PROTECTED |				\
-		RICHACL_DEFAULTED |				\
-		RICHACL_WRITE_THROUGH |				\
-		RICHACL_MASKED)
-
-/* e_type values */
-#define RICHACE_ACCESS_ALLOWED_ACE_TYPE		0x0000
-#define RICHACE_ACCESS_DENIED_ACE_TYPE		0x0001
-
-/* e_flags bitflags */
-#define RICHACE_FILE_INHERIT_ACE		0x0001
-#define RICHACE_DIRECTORY_INHERIT_ACE		0x0002
-#define RICHACE_NO_PROPAGATE_INHERIT_ACE	0x0004
-#define RICHACE_INHERIT_ONLY_ACE		0x0008
-#define RICHACE_IDENTIFIER_GROUP		0x0040
-#define RICHACE_INHERITED_ACE			0x0080
-#define RICHACE_UNMAPPED_WHO			0x2000
-#define RICHACE_SPECIAL_WHO			0x4000
+	RICHACL_AUTO_INHERIT |					\
+	RICHACL_PROTECTED |					\
+	RICHACL_DEFAULTED |					\
+	RICHACL_WRITE_THROUGH |					\
+	RICHACL_MASKED)
 
 #define RICHACE_VALID_FLAGS (					\
 	RICHACE_FILE_INHERIT_ACE |				\
@@ -99,27 +66,6 @@ struct richacl {
 	RICHACE_INHERIT_ONLY_ACE |				\
 	RICHACE_INHERITED_ACE )
 
-/* e_mask bitflags */
-#define RICHACE_READ_DATA			0x00000001
-#define RICHACE_LIST_DIRECTORY			0x00000001
-#define RICHACE_WRITE_DATA			0x00000002
-#define RICHACE_ADD_FILE			0x00000002
-#define RICHACE_APPEND_DATA			0x00000004
-#define RICHACE_ADD_SUBDIRECTORY		0x00000004
-#define RICHACE_READ_NAMED_ATTRS		0x00000008
-#define RICHACE_WRITE_NAMED_ATTRS		0x00000010
-#define RICHACE_EXECUTE				0x00000020
-#define RICHACE_DELETE_CHILD			0x00000040
-#define RICHACE_READ_ATTRIBUTES			0x00000080
-#define RICHACE_WRITE_ATTRIBUTES		0x00000100
-#define RICHACE_WRITE_RETENTION			0x00000200
-#define RICHACE_WRITE_RETENTION_HOLD		0x00000400
-#define RICHACE_DELETE				0x00010000
-#define RICHACE_READ_ACL			0x00020000
-#define RICHACE_WRITE_ACL			0x00040000
-#define RICHACE_WRITE_OWNER			0x00080000
-#define RICHACE_SYNCHRONIZE			0x00100000
-
 /* Valid RICHACE_* flags for directories and non-directories */
 #define RICHACE_VALID_MASK (					\
 	RICHACE_READ_DATA | RICHACE_LIST_DIRECTORY |		\
@@ -139,49 +85,16 @@ struct richacl {
 	RICHACE_WRITE_OWNER |					\
 	RICHACE_SYNCHRONIZE)
 
-/*
- * The POSIX permissions are supersets of the following NFSv4 permissions:
- *
- *  - MAY_READ maps to READ_DATA or LIST_DIRECTORY, depending on the type
- *    of the file system object.
- *
- *  - MAY_WRITE maps to WRITE_DATA or RICHACE_APPEND_DATA for files, and to
- *    ADD_FILE, RICHACE_ADD_SUBDIRECTORY, or RICHACE_DELETE_CHILD for directories.
- *
- *  - MAY_EXECUTE maps to RICHACE_EXECUTE.
- *
- *  (Some of these NFSv4 permissions have the same bit values.)
- */
-#define RICHACE_POSIX_MODE_READ (			\
-		RICHACE_READ_DATA |		\
-		RICHACE_LIST_DIRECTORY)
-#define RICHACE_POSIX_MODE_WRITE (			\
-		RICHACE_WRITE_DATA |		\
-		RICHACE_ADD_FILE |			\
-		RICHACE_APPEND_DATA |		\
-		RICHACE_ADD_SUBDIRECTORY |		\
-		RICHACE_DELETE_CHILD)
-#define RICHACE_POSIX_MODE_EXEC RICHACE_EXECUTE
-#define RICHACE_POSIX_MODE_ALL (			\
-		RICHACE_POSIX_MODE_READ |		\
-		RICHACE_POSIX_MODE_WRITE |		\
-		RICHACE_POSIX_MODE_EXEC)
-/*
- * These permissions are always allowed
- * no matter what the acl says.
- */
-#define RICHACE_POSIX_ALWAYS_ALLOWED (	\
-		RICHACE_SYNCHRONIZE |	\
-		RICHACE_READ_ATTRIBUTES |	\
-		RICHACE_READ_ACL)
-/*
- * The owner is implicitly granted
- * these permissions under POSIX.
- */
-#define RICHACE_POSIX_OWNER_ALLOWED (		\
-		RICHACE_WRITE_ATTRIBUTES |		\
-		RICHACE_WRITE_OWNER |		\
-		RICHACE_WRITE_ACL)
+#define richacl_for_each_entry(_ace, _acl) \
+	for (_ace = (_acl)->a_entries; \
+	     _ace != (_acl)->a_entries + (_acl)->a_count; \
+	     _ace++)
+
+#define richacl_for_each_entry_reverse(_ace, _acl) \
+	for (_ace = (_acl)->a_entries + (_acl)->a_count - 1; \
+	     _ace != (_acl)->a_entries - 1; \
+	     _ace--)
+
 /**
  * richacl_get  -  grab another reference to a richacl handle
  */
diff --git a/include/linux/richacl_xattr.h b/include/linux/richacl_xattr.h
index f84cc21..eff36a3 100644
--- a/include/linux/richacl_xattr.h
+++ b/include/linux/richacl_xattr.h
@@ -17,24 +17,9 @@
 #ifndef __RICHACL_XATTR_H
 #define __RICHACL_XATTR_H
 
+#include <uapi/linux/richacl_xattr.h>
 #include <linux/richacl.h>
 
-struct richace_xattr {
-	__le16		e_type;
-	__le16		e_flags;
-	__le32		e_mask;
-	__le32		e_id;
-};
-
-struct richacl_xattr {
-	unsigned char	a_version;
-	unsigned char	a_flags;
-	__le16		a_count;
-	__le32		a_owner_mask;
-	__le32		a_group_mask;
-	__le32		a_other_mask;
-};
-
 #define RICHACL_XATTR_VERSION	0
 #define RICHACL_XATTR_MAX_COUNT \
 	((XATTR_SIZE_MAX - sizeof(struct richacl_xattr)) / \
diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild
index 1ff9942..3f278fa 100644
--- a/include/uapi/linux/Kbuild
+++ b/include/uapi/linux/Kbuild
@@ -346,6 +346,8 @@ header-y += reboot.h
 header-y += reiserfs_fs.h
 header-y += reiserfs_xattr.h
 header-y += resource.h
+header-y += richacl.h
+header-y += richacl_xattr.h
 header-y += rfkill.h
 header-y += romfs_fs.h
 header-y += rose.h
diff --git a/include/uapi/linux/richacl.h b/include/uapi/linux/richacl.h
new file mode 100644
index 0000000..6887f88
--- /dev/null
+++ b/include/uapi/linux/richacl.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2006, 2010  Novell, Inc.
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Written by Andreas Gruenbacher <agruen@kernel.org>
+ *
+ * This program 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, or (at your option) any
+ * later version.
+ *
+ * This program 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.
+ */
+
+#ifndef __UAPI_RICHACL_H
+#define __UAPI_RICHACL_H
+
+/* a_flags values */
+#define RICHACL_AUTO_INHERIT			0x01
+#define RICHACL_PROTECTED			0x02
+#define RICHACL_DEFAULTED			0x04
+#define RICHACL_WRITE_THROUGH			0x40
+#define RICHACL_MASKED				0x80
+
+/* e_type values */
+#define RICHACE_ACCESS_ALLOWED_ACE_TYPE		0x0000
+#define RICHACE_ACCESS_DENIED_ACE_TYPE		0x0001
+
+/* e_flags bitflags */
+#define RICHACE_FILE_INHERIT_ACE		0x0001
+#define RICHACE_DIRECTORY_INHERIT_ACE		0x0002
+#define RICHACE_NO_PROPAGATE_INHERIT_ACE	0x0004
+#define RICHACE_INHERIT_ONLY_ACE		0x0008
+#define RICHACE_IDENTIFIER_GROUP		0x0040
+#define RICHACE_INHERITED_ACE			0x0080
+#define RICHACE_UNMAPPED_WHO			0x2000
+#define RICHACE_SPECIAL_WHO			0x4000
+
+/* e_mask bitflags */
+#define RICHACE_READ_DATA			0x00000001
+#define RICHACE_LIST_DIRECTORY			0x00000001
+#define RICHACE_WRITE_DATA			0x00000002
+#define RICHACE_ADD_FILE			0x00000002
+#define RICHACE_APPEND_DATA			0x00000004
+#define RICHACE_ADD_SUBDIRECTORY		0x00000004
+#define RICHACE_READ_NAMED_ATTRS		0x00000008
+#define RICHACE_WRITE_NAMED_ATTRS		0x00000010
+#define RICHACE_EXECUTE				0x00000020
+#define RICHACE_DELETE_CHILD			0x00000040
+#define RICHACE_READ_ATTRIBUTES			0x00000080
+#define RICHACE_WRITE_ATTRIBUTES		0x00000100
+#define RICHACE_WRITE_RETENTION			0x00000200
+#define RICHACE_WRITE_RETENTION_HOLD		0x00000400
+#define RICHACE_DELETE				0x00010000
+#define RICHACE_READ_ACL			0x00020000
+#define RICHACE_WRITE_ACL			0x00040000
+#define RICHACE_WRITE_OWNER			0x00080000
+#define RICHACE_SYNCHRONIZE			0x00100000
+
+/* e_id values */
+#define RICHACE_OWNER_SPECIAL_ID		0
+#define RICHACE_GROUP_SPECIAL_ID		1
+#define RICHACE_EVERYONE_SPECIAL_ID		2
+
+/*
+ * The POSIX permissions are supersets of the following richacl permissions:
+ *
+ *  - MAY_READ maps to READ_DATA or LIST_DIRECTORY, depending on the type
+ *    of the file system object.
+ *
+ *  - MAY_WRITE maps to WRITE_DATA or RICHACE_APPEND_DATA for files, and to
+ *    ADD_FILE, RICHACE_ADD_SUBDIRECTORY, or RICHACE_DELETE_CHILD for directories.
+ *
+ *  - MAY_EXECUTE maps to RICHACE_EXECUTE.
+ *
+ *  (Some of these richacl permissions have the same bit values.)
+ */
+#define RICHACE_POSIX_MODE_READ (			\
+		RICHACE_READ_DATA |			\
+		RICHACE_LIST_DIRECTORY)
+#define RICHACE_POSIX_MODE_WRITE (			\
+		RICHACE_WRITE_DATA |			\
+		RICHACE_ADD_FILE |			\
+		RICHACE_APPEND_DATA |			\
+		RICHACE_ADD_SUBDIRECTORY |		\
+		RICHACE_DELETE_CHILD)
+#define RICHACE_POSIX_MODE_EXEC RICHACE_EXECUTE
+#define RICHACE_POSIX_MODE_ALL (			\
+		RICHACE_POSIX_MODE_READ |		\
+		RICHACE_POSIX_MODE_WRITE |		\
+		RICHACE_POSIX_MODE_EXEC)
+
+/*
+ * These permissions are always allowed no matter what the acl says.
+ */
+#define RICHACE_POSIX_ALWAYS_ALLOWED (			\
+		RICHACE_SYNCHRONIZE |			\
+		RICHACE_READ_ATTRIBUTES |		\
+		RICHACE_READ_ACL)
+
+/*
+ * The owner is implicitly granted these permissions under POSIX.
+ */
+#define RICHACE_POSIX_OWNER_ALLOWED (			\
+		RICHACE_WRITE_ATTRIBUTES |		\
+		RICHACE_WRITE_OWNER |			\
+		RICHACE_WRITE_ACL)
+
+#endif /* __UAPI_RICHACL_H */
diff --git a/include/uapi/linux/richacl_xattr.h b/include/uapi/linux/richacl_xattr.h
new file mode 100644
index 0000000..6f96bc0
--- /dev/null
+++ b/include/uapi/linux/richacl_xattr.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2006, 2010  Novell, Inc.
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Written by Andreas Gruenbacher <agruen@kernel.org>
+ *
+ * This program 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, or (at your option) any
+ * later version.
+ *
+ * This program 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.
+ */
+
+#ifndef __UAPI_RICHACL_XATTR_H
+#define __UAPI_RICHACL_XATTR_H
+
+#include <linux/types.h>
+#include <linux/xattr.h>
+
+struct richace_xattr {
+	__le16		e_type;
+	__le16		e_flags;
+	__le32		e_mask;
+	__le32		e_id;
+};
+
+struct richacl_xattr {
+	unsigned char	a_version;
+	unsigned char	a_flags;
+	__le16		a_count;
+	__le32		a_owner_mask;
+	__le32		a_group_mask;
+	__le32		a_other_mask;
+};
+
+#define RICHACL_XATTR_MAX_COUNT \
+	((XATTR_SIZE_MAX - sizeof(struct richacl_xattr)) / \
+	 sizeof(struct richace_xattr))
+
+#endif  /* __UAPI_RICHACL_XATTR_H */
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* [PATCH] vfs: Add MAY_DELETE_SELF and MAY_DELETE_CHILD permission flags
  2015-09-05 10:26     ` Andreas Gruenbacher
  (?)
@ 2015-09-06  8:14     ` Andreas Gruenbacher
       [not found]       ` <1441527246-18189-1-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
  -1 siblings, 1 reply; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-06  8:14 UTC (permalink / raw)
  To: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

[This replaces v7 of this patch; MAY_DELETE_SELF now properly overrides the
sticky check.]

Normally, deleting a file requires MAY_WRITE access to the parent
directory.  With richacls, a file may be deleted with MAY_DELETE_CHILD access
to the parent directory or with MAY_DELETE_SELF access to the file.

To support that, pass the MAY_DELETE_CHILD mask flag to inode_permission()
when checking for delete access inside a directory, and MAY_DELETE_SELF
when checking for delete access to a file itelf.

The MAY_DELETE_SELF permission overrides the sticky directory check.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/namei.c         | 21 ++++++++++++---------
 include/linux/fs.h |  2 ++
 2 files changed, 14 insertions(+), 9 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index 428b4a6..1a3f0d8 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -453,9 +453,9 @@ static int sb_permission(struct super_block *sb, struct inode *inode, int mask)
  * this, letting us set arbitrary permissions for filesystem access without
  * changing the "normal" UIDs which are used for other things.
  *
- * MAY_WRITE must be set in @mask whenever MAY_APPEND, MAY_CREATE_FILE, or
- * MAY_CREATE_DIR are set.  That way, file systems that don't support these
- * permissions will check for MAY_WRITE instead.
+ * MAY_WRITE must be set in @mask whenever MAY_APPEND, MAY_CREATE_FILE,
+ * MAY_CREATE_DIR, or MAY_DELETE_CHILD are set.  That way, file systems that
+ * don't support these permissions will check for MAY_WRITE instead.
  */
 int inode_permission(struct inode *inode, int mask)
 {
@@ -2528,7 +2528,7 @@ static int may_delete(struct inode *dir, struct dentry *victim,
 		      bool isdir, bool replace)
 {
 	struct inode *inode = d_backing_inode(victim);
-	int error, mask = MAY_WRITE | MAY_EXEC;
+	int error, mask = MAY_EXEC;
 
 	if (d_is_negative(victim))
 		return -ENOENT;
@@ -2538,15 +2538,18 @@ static int may_delete(struct inode *dir, struct dentry *victim,
 	audit_inode_child(dir, victim, AUDIT_TYPE_CHILD_DELETE);
 
 	if (replace)
-		mask |= isdir ? MAY_CREATE_DIR : MAY_CREATE_FILE;
-	error = inode_permission(dir, mask);
+		mask |= MAY_WRITE | (isdir ? MAY_CREATE_DIR : MAY_CREATE_FILE);
+	error = inode_permission(dir, mask | MAY_WRITE | MAY_DELETE_CHILD);
+	if (!error && check_sticky(dir, inode))
+		error = -EPERM;
+	if (error && IS_RICHACL(inode) &&
+	    inode_permission(inode, MAY_DELETE_SELF) == 0)
+		error = 0;
 	if (error)
 		return error;
 	if (IS_APPEND(dir))
 		return -EPERM;
-
-	if (check_sticky(dir, inode) || IS_APPEND(inode) ||
-	    IS_IMMUTABLE(inode) || IS_SWAPFILE(inode))
+	if (IS_APPEND(inode) || IS_IMMUTABLE(inode) || IS_SWAPFILE(inode))
 		return -EPERM;
 	if (isdir) {
 		if (!d_is_dir(victim))
diff --git a/include/linux/fs.h b/include/linux/fs.h
index b2f1b1b..99c95d1 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -84,6 +84,8 @@ typedef void (dax_iodone_t)(struct buffer_head *bh_map, int uptodate);
 #define MAY_NOT_BLOCK		0x00000080
 #define MAY_CREATE_FILE		0x00000100
 #define MAY_CREATE_DIR		0x00000200
+#define MAY_DELETE_CHILD	0x00000400
+#define MAY_DELETE_SELF		0x00000800
 
 /*
  * flags in file.f_mode.  Note that FMODE_READ and FMODE_WRITE must correspond
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* Re: [PATCH] vfs: Add MAY_DELETE_SELF and MAY_DELETE_CHILD permission flags
  2015-09-06  8:14     ` [PATCH] " Andreas Gruenbacher
@ 2015-09-11 20:30           ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-11 20:30 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

On Sun, Sep 06, 2015 at 10:14:06AM +0200, Andreas Gruenbacher wrote:
> [This replaces v7 of this patch; MAY_DELETE_SELF now properly overrides the
> sticky check.]
> 
> Normally, deleting a file requires MAY_WRITE access to the parent
> directory.  With richacls, a file may be deleted with MAY_DELETE_CHILD access
> to the parent directory or with MAY_DELETE_SELF access to the file.
> 
> To support that, pass the MAY_DELETE_CHILD mask flag to inode_permission()
> when checking for delete access inside a directory, and MAY_DELETE_SELF
> when checking for delete access to a file itelf.
> 
> The MAY_DELETE_SELF permission overrides the sticky directory check.
> 

Reviewed-by: J. Bruce Fields <bfields-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>

> Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> ---
>  fs/namei.c         | 21 ++++++++++++---------
>  include/linux/fs.h |  2 ++
>  2 files changed, 14 insertions(+), 9 deletions(-)
> 
> diff --git a/fs/namei.c b/fs/namei.c
> index 428b4a6..1a3f0d8 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -453,9 +453,9 @@ static int sb_permission(struct super_block *sb, struct inode *inode, int mask)
>   * this, letting us set arbitrary permissions for filesystem access without
>   * changing the "normal" UIDs which are used for other things.
>   *
> - * MAY_WRITE must be set in @mask whenever MAY_APPEND, MAY_CREATE_FILE, or
> - * MAY_CREATE_DIR are set.  That way, file systems that don't support these
> - * permissions will check for MAY_WRITE instead.
> + * MAY_WRITE must be set in @mask whenever MAY_APPEND, MAY_CREATE_FILE,
> + * MAY_CREATE_DIR, or MAY_DELETE_CHILD are set.  That way, file systems that
> + * don't support these permissions will check for MAY_WRITE instead.
>   */
>  int inode_permission(struct inode *inode, int mask)
>  {
> @@ -2528,7 +2528,7 @@ static int may_delete(struct inode *dir, struct dentry *victim,
>  		      bool isdir, bool replace)
>  {
>  	struct inode *inode = d_backing_inode(victim);
> -	int error, mask = MAY_WRITE | MAY_EXEC;
> +	int error, mask = MAY_EXEC;
>  
>  	if (d_is_negative(victim))
>  		return -ENOENT;
> @@ -2538,15 +2538,18 @@ static int may_delete(struct inode *dir, struct dentry *victim,
>  	audit_inode_child(dir, victim, AUDIT_TYPE_CHILD_DELETE);
>  
>  	if (replace)
> -		mask |= isdir ? MAY_CREATE_DIR : MAY_CREATE_FILE;
> -	error = inode_permission(dir, mask);
> +		mask |= MAY_WRITE | (isdir ? MAY_CREATE_DIR : MAY_CREATE_FILE);
> +	error = inode_permission(dir, mask | MAY_WRITE | MAY_DELETE_CHILD);
> +	if (!error && check_sticky(dir, inode))
> +		error = -EPERM;
> +	if (error && IS_RICHACL(inode) &&
> +	    inode_permission(inode, MAY_DELETE_SELF) == 0)
> +		error = 0;
>  	if (error)
>  		return error;
>  	if (IS_APPEND(dir))
>  		return -EPERM;
> -
> -	if (check_sticky(dir, inode) || IS_APPEND(inode) ||
> -	    IS_IMMUTABLE(inode) || IS_SWAPFILE(inode))
> +	if (IS_APPEND(inode) || IS_IMMUTABLE(inode) || IS_SWAPFILE(inode))
>  		return -EPERM;
>  	if (isdir) {
>  		if (!d_is_dir(victim))
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index b2f1b1b..99c95d1 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -84,6 +84,8 @@ typedef void (dax_iodone_t)(struct buffer_head *bh_map, int uptodate);
>  #define MAY_NOT_BLOCK		0x00000080
>  #define MAY_CREATE_FILE		0x00000100
>  #define MAY_CREATE_DIR		0x00000200
> +#define MAY_DELETE_CHILD	0x00000400
> +#define MAY_DELETE_SELF		0x00000800
>  
>  /*
>   * flags in file.f_mode.  Note that FMODE_READ and FMODE_WRITE must correspond
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [PATCH] vfs: Add MAY_DELETE_SELF and MAY_DELETE_CHILD permission flags
@ 2015-09-11 20:30           ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-11 20:30 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Sun, Sep 06, 2015 at 10:14:06AM +0200, Andreas Gruenbacher wrote:
> [This replaces v7 of this patch; MAY_DELETE_SELF now properly overrides the
> sticky check.]
> 
> Normally, deleting a file requires MAY_WRITE access to the parent
> directory.  With richacls, a file may be deleted with MAY_DELETE_CHILD access
> to the parent directory or with MAY_DELETE_SELF access to the file.
> 
> To support that, pass the MAY_DELETE_CHILD mask flag to inode_permission()
> when checking for delete access inside a directory, and MAY_DELETE_SELF
> when checking for delete access to a file itelf.
> 
> The MAY_DELETE_SELF permission overrides the sticky directory check.
> 

Reviewed-by: J. Bruce Fields <bfields@redhat.com>

> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> ---
>  fs/namei.c         | 21 ++++++++++++---------
>  include/linux/fs.h |  2 ++
>  2 files changed, 14 insertions(+), 9 deletions(-)
> 
> diff --git a/fs/namei.c b/fs/namei.c
> index 428b4a6..1a3f0d8 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -453,9 +453,9 @@ static int sb_permission(struct super_block *sb, struct inode *inode, int mask)
>   * this, letting us set arbitrary permissions for filesystem access without
>   * changing the "normal" UIDs which are used for other things.
>   *
> - * MAY_WRITE must be set in @mask whenever MAY_APPEND, MAY_CREATE_FILE, or
> - * MAY_CREATE_DIR are set.  That way, file systems that don't support these
> - * permissions will check for MAY_WRITE instead.
> + * MAY_WRITE must be set in @mask whenever MAY_APPEND, MAY_CREATE_FILE,
> + * MAY_CREATE_DIR, or MAY_DELETE_CHILD are set.  That way, file systems that
> + * don't support these permissions will check for MAY_WRITE instead.
>   */
>  int inode_permission(struct inode *inode, int mask)
>  {
> @@ -2528,7 +2528,7 @@ static int may_delete(struct inode *dir, struct dentry *victim,
>  		      bool isdir, bool replace)
>  {
>  	struct inode *inode = d_backing_inode(victim);
> -	int error, mask = MAY_WRITE | MAY_EXEC;
> +	int error, mask = MAY_EXEC;
>  
>  	if (d_is_negative(victim))
>  		return -ENOENT;
> @@ -2538,15 +2538,18 @@ static int may_delete(struct inode *dir, struct dentry *victim,
>  	audit_inode_child(dir, victim, AUDIT_TYPE_CHILD_DELETE);
>  
>  	if (replace)
> -		mask |= isdir ? MAY_CREATE_DIR : MAY_CREATE_FILE;
> -	error = inode_permission(dir, mask);
> +		mask |= MAY_WRITE | (isdir ? MAY_CREATE_DIR : MAY_CREATE_FILE);
> +	error = inode_permission(dir, mask | MAY_WRITE | MAY_DELETE_CHILD);
> +	if (!error && check_sticky(dir, inode))
> +		error = -EPERM;
> +	if (error && IS_RICHACL(inode) &&
> +	    inode_permission(inode, MAY_DELETE_SELF) == 0)
> +		error = 0;
>  	if (error)
>  		return error;
>  	if (IS_APPEND(dir))
>  		return -EPERM;
> -
> -	if (check_sticky(dir, inode) || IS_APPEND(inode) ||
> -	    IS_IMMUTABLE(inode) || IS_SWAPFILE(inode))
> +	if (IS_APPEND(inode) || IS_IMMUTABLE(inode) || IS_SWAPFILE(inode))
>  		return -EPERM;
>  	if (isdir) {
>  		if (!d_is_dir(victim))
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index b2f1b1b..99c95d1 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -84,6 +84,8 @@ typedef void (dax_iodone_t)(struct buffer_head *bh_map, int uptodate);
>  #define MAY_NOT_BLOCK		0x00000080
>  #define MAY_CREATE_FILE		0x00000100
>  #define MAY_CREATE_DIR		0x00000200
> +#define MAY_DELETE_CHILD	0x00000400
> +#define MAY_DELETE_SELF		0x00000800
>  
>  /*
>   * flags in file.f_mode.  Note that FMODE_READ and FMODE_WRITE must correspond
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 09/41] richacl: Update the file masks in chmod()
  2015-09-05 10:27     ` Andreas Gruenbacher
@ 2015-09-11 20:35         ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-11 20:35 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

On Sat, Sep 05, 2015 at 12:27:04PM +0200, Andreas Gruenbacher wrote:
> Doing a chmod() sets the file mode, which includes the file permission
> bits.  When a file has a richacl, the permissions that the richacl
> grants need to be limited to what the new file permission bits allow.
> 
> This is done by setting the file masks in the richacl to what the file
> permission bits map to.  The richacl access check algorithm takes the
> file masks into account, which ensures that the richacl cannot grant too
> many permissions.
> 
> It is possible to explicitly add permissions to the file masks which go
> beyond what the file permission bits can grant (like the
> RICHACE_WRITE_ACL permission).  The POSIX.1 standard calls this an
> alternate file access control mechanism.  A subsequent chmod() would
> ensure that those permissions are disabled again.
> 

Reviewed-by: J. Bruce Fields <bfields-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>

> Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> ---
>  fs/richacl_base.c       | 40 ++++++++++++++++++++++++++++++++++++++++
>  include/linux/richacl.h |  1 +
>  2 files changed, 41 insertions(+)
> 
> diff --git a/fs/richacl_base.c b/fs/richacl_base.c
> index fc544f7..6c69234 100644
> --- a/fs/richacl_base.c
> +++ b/fs/richacl_base.c
> @@ -339,3 +339,43 @@ restart:
>  	acl->a_flags &= ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED);
>  }
>  EXPORT_SYMBOL_GPL(richacl_compute_max_masks);
> +
> +/**
> + * richacl_chmod  -  update the file masks to reflect the new mode
> + * @mode:	new file permission bits including the file type
> + *
> + * Return a copy of @acl where the file masks have been replaced by the file
> + * masks corresponding to the file permission bits in @mode, or returns @acl
> + * itself if the file masks are already up to date.  Takes over a reference
> + * to @acl.
> + */
> +struct richacl *
> +richacl_chmod(struct richacl *acl, mode_t mode)
> +{
> +	unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> +	unsigned int owner_mask, group_mask, other_mask;
> +	struct richacl *clone;
> +
> +	owner_mask = richacl_mode_to_mask(mode >> 6) & ~x;
> +	group_mask = richacl_mode_to_mask(mode >> 3) & ~x;
> +	other_mask = richacl_mode_to_mask(mode)      & ~x;
> +
> +	if (acl->a_owner_mask == owner_mask &&
> +	    acl->a_group_mask == group_mask &&
> +	    acl->a_other_mask == other_mask &&
> +	    (acl->a_flags & (RICHACL_WRITE_THROUGH | RICHACL_MASKED)))
> +		return acl;
> +
> +	clone = richacl_clone(acl, GFP_KERNEL);
> +	richacl_put(acl);
> +	if (!clone)
> +		return ERR_PTR(-ENOMEM);
> +
> +	clone->a_flags |= (RICHACL_WRITE_THROUGH | RICHACL_MASKED);
> +	clone->a_owner_mask = owner_mask;
> +	clone->a_group_mask = group_mask;
> +	clone->a_other_mask = other_mask;
> +
> +	return clone;
> +}
> +EXPORT_SYMBOL_GPL(richacl_chmod);
> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> index e81144a..e00f313 100644
> --- a/include/linux/richacl.h
> +++ b/include/linux/richacl.h
> @@ -298,5 +298,6 @@ extern int richacl_masks_to_mode(const struct richacl *);
>  extern unsigned int richacl_mode_to_mask(mode_t);
>  extern unsigned int richacl_want_to_mask(unsigned int);
>  extern void richacl_compute_max_masks(struct richacl *);
> +extern struct richacl *richacl_chmod(struct richacl *, mode_t);
>  
>  #endif /* __RICHACL_H */
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 09/41] richacl: Update the file masks in chmod()
@ 2015-09-11 20:35         ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-11 20:35 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Sat, Sep 05, 2015 at 12:27:04PM +0200, Andreas Gruenbacher wrote:
> Doing a chmod() sets the file mode, which includes the file permission
> bits.  When a file has a richacl, the permissions that the richacl
> grants need to be limited to what the new file permission bits allow.
> 
> This is done by setting the file masks in the richacl to what the file
> permission bits map to.  The richacl access check algorithm takes the
> file masks into account, which ensures that the richacl cannot grant too
> many permissions.
> 
> It is possible to explicitly add permissions to the file masks which go
> beyond what the file permission bits can grant (like the
> RICHACE_WRITE_ACL permission).  The POSIX.1 standard calls this an
> alternate file access control mechanism.  A subsequent chmod() would
> ensure that those permissions are disabled again.
> 

Reviewed-by: J. Bruce Fields <bfields@redhat.com>

> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> ---
>  fs/richacl_base.c       | 40 ++++++++++++++++++++++++++++++++++++++++
>  include/linux/richacl.h |  1 +
>  2 files changed, 41 insertions(+)
> 
> diff --git a/fs/richacl_base.c b/fs/richacl_base.c
> index fc544f7..6c69234 100644
> --- a/fs/richacl_base.c
> +++ b/fs/richacl_base.c
> @@ -339,3 +339,43 @@ restart:
>  	acl->a_flags &= ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED);
>  }
>  EXPORT_SYMBOL_GPL(richacl_compute_max_masks);
> +
> +/**
> + * richacl_chmod  -  update the file masks to reflect the new mode
> + * @mode:	new file permission bits including the file type
> + *
> + * Return a copy of @acl where the file masks have been replaced by the file
> + * masks corresponding to the file permission bits in @mode, or returns @acl
> + * itself if the file masks are already up to date.  Takes over a reference
> + * to @acl.
> + */
> +struct richacl *
> +richacl_chmod(struct richacl *acl, mode_t mode)
> +{
> +	unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> +	unsigned int owner_mask, group_mask, other_mask;
> +	struct richacl *clone;
> +
> +	owner_mask = richacl_mode_to_mask(mode >> 6) & ~x;
> +	group_mask = richacl_mode_to_mask(mode >> 3) & ~x;
> +	other_mask = richacl_mode_to_mask(mode)      & ~x;
> +
> +	if (acl->a_owner_mask == owner_mask &&
> +	    acl->a_group_mask == group_mask &&
> +	    acl->a_other_mask == other_mask &&
> +	    (acl->a_flags & (RICHACL_WRITE_THROUGH | RICHACL_MASKED)))
> +		return acl;
> +
> +	clone = richacl_clone(acl, GFP_KERNEL);
> +	richacl_put(acl);
> +	if (!clone)
> +		return ERR_PTR(-ENOMEM);
> +
> +	clone->a_flags |= (RICHACL_WRITE_THROUGH | RICHACL_MASKED);
> +	clone->a_owner_mask = owner_mask;
> +	clone->a_group_mask = group_mask;
> +	clone->a_other_mask = other_mask;
> +
> +	return clone;
> +}
> +EXPORT_SYMBOL_GPL(richacl_chmod);
> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> index e81144a..e00f313 100644
> --- a/include/linux/richacl.h
> +++ b/include/linux/richacl.h
> @@ -298,5 +298,6 @@ extern int richacl_masks_to_mode(const struct richacl *);
>  extern unsigned int richacl_mode_to_mask(mode_t);
>  extern unsigned int richacl_want_to_mask(unsigned int);
>  extern void richacl_compute_max_masks(struct richacl *);
> +extern struct richacl *richacl_chmod(struct richacl *, mode_t);
>  
>  #endif /* __RICHACL_H */
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 10/41] richacl: Permission check algorithm
  2015-09-05 10:27     ` Andreas Gruenbacher
@ 2015-09-11 21:16         ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-11 21:16 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

On Sat, Sep 05, 2015 at 12:27:05PM +0200, Andreas Gruenbacher wrote:
> A richacl roughly grants a requested access if the NFSv4 acl in the
> richacl grants the requested permissions according to the NFSv4
> permission check algorithm and the file mask that applies to the process
> includes the requested permissions.
> 
> Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> Acked-by: "J. Bruce Fields" <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
> ---
>  fs/Makefile             |   2 +-
>  fs/richacl_inode.c      | 147 ++++++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/richacl.h |   3 +
>  3 files changed, 151 insertions(+), 1 deletion(-)
>  create mode 100644 fs/richacl_inode.c
> 
> diff --git a/fs/Makefile b/fs/Makefile
> index ddc43d8..1305047 100644
> --- a/fs/Makefile
> +++ b/fs/Makefile
> @@ -48,7 +48,7 @@ obj-$(CONFIG_SYSCTL)		+= drop_caches.o
>  
>  obj-$(CONFIG_FHANDLE)		+= fhandle.o
>  obj-$(CONFIG_FS_RICHACL)	+= richacl.o
> -richacl-y			:= richacl_base.o
> +richacl-y			:= richacl_base.o richacl_inode.o
>  
>  obj-y				+= quota/
>  
> diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c
> new file mode 100644
> index 0000000..ba05993
> --- /dev/null
> +++ b/fs/richacl_inode.c
> @@ -0,0 +1,147 @@
> +/*
> + * Copyright (C) 2010  Novell, Inc.
> + * Copyright (C) 2015  Red Hat, Inc.
> + * Written by Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> + *
> + * This program 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, or (at your option) any
> + * later version.
> + *
> + * This program 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.
> + */
> +
> +#include <linux/sched.h>
> +#include <linux/module.h>
> +#include <linux/fs.h>
> +#include <linux/slab.h>
> +#include <linux/richacl.h>
> +
> +/**
> + * richacl_permission  -  richacl permission check algorithm
> + * @inode:	inode to check
> + * @acl:	rich acl of the inode
> + * @want:	requested access (MAY_* flags)
> + *
> + * Checks if the current process is granted @mask flags in @acl.
> + */
> +int
> +richacl_permission(struct inode *inode, const struct richacl *acl,
> +		   int want)
> +{
> +	const struct richace *ace;
> +	unsigned int mask = richacl_want_to_mask(want);
> +	unsigned int requested = mask, denied = 0;
> +	int in_owning_group = in_group_p(inode->i_gid);
> +	int in_owner_or_group_class = in_owning_group;
> +
> +	/*
> +	 * A process is
> +	 *   - in the owner file class if it owns the file,
> +	 *   - in the group file class if it is in the file's owning group or
> +	 *     it matches any of the user or group entries, and
> +	 *   - in the other file class otherwise.
> +	 * The file class is only relevant for determining which file mask to
> +	 * apply, which only happens for masked acls.
> +	 */
> +	if (acl->a_flags & RICHACL_MASKED) {
> +		if ((acl->a_flags & RICHACL_WRITE_THROUGH) &&
> +		    uid_eq(current_fsuid(), inode->i_uid)) {
> +			denied = requested & ~acl->a_owner_mask;
> +			goto out;
> +		}
> +	} else {
> +		/*
> +		 * When the acl is not masked, there is no need to determine if
> +		 * the process is in the group class and we can break out
> +		 * earlier of the loop below.
> +		 */
> +		in_owner_or_group_class = 1;
> +	}
> +
> +	/*
> +	 * Check if the acl grants the requested access and determine which
> +	 * file class the process is in.
> +	 */
> +	richacl_for_each_entry(ace, acl) {
> +		unsigned int ace_mask = ace->e_mask;
> +
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_owner(ace)) {
> +			if (!uid_eq(current_fsuid(), inode->i_uid))
> +				continue;
> +			goto entry_matches_owner;
> +		} else if (richace_is_group(ace)) {
> +			if (!in_owning_group)
> +				continue;
> +		} else if (richace_is_unix_user(ace)) {
> +			if (!uid_eq(current_fsuid(), ace->e_id.uid))
> +				continue;
> +		} else if (richace_is_unix_group(ace)) {
> +			if (!in_group_p(ace->e_id.gid))
> +				continue;
> +		} else
> +			goto entry_matches_everyone;
> +
> +		/*
> +		 * Apply the group file mask to entries other than owner@ and
> +		 * everyone@ or user entries matching the owner.  This ensures
> +		 * that we grant the same permissions as the acl computed by
> +		 * richacl_apply_masks().
> +		 *
> +		 * Without this restriction, the following richacl would grant
> +		 * rw access to processes which are both the owner and in the
> +		 * owning group, but not to other users in the owning group,
> +		 * which could not be represented without masks:
> +		 *
> +		 *  owner:rw::mask
> +		 *  group@:rw::allow
> +		 */
> +		if ((acl->a_flags & RICHACL_MASKED) && richace_is_allow(ace))
> +			ace_mask &= acl->a_group_mask;

I'm having trouble understanding this.  I think the problem is that I
don't really understand the notation in your example.  Is a_group_mask
zero in that example?  I think it must be, in which case, OK I think I
get it.

(Though I still have to think about it a little more to convince myself
that richacl_apply_masks() always gets the same result.)

--b.

> +
> +entry_matches_owner:
> +		/* The process is in the owner or group file class. */
> +		in_owner_or_group_class = 1;
> +
> +entry_matches_everyone:
> +		/* Check which mask flags the ACE allows or denies. */
> +		if (richace_is_deny(ace))
> +			denied |= ace_mask & mask;
> +		mask &= ~ace_mask;
> +
> +		/*
> +		 * Keep going until we know which file class
> +		 * the process is in.
> +		 */
> +		if (!mask && in_owner_or_group_class)
> +			break;
> +	}
> +	denied |= mask;
> +
> +	if (acl->a_flags & RICHACL_MASKED) {
> +		/*
> +		 * The file class a process is in determines which file mask
> +		 * applies.  Check if that file mask also grants the requested
> +		 * access.
> +		 */
> +		if (uid_eq(current_fsuid(), inode->i_uid))
> +			denied |= requested & ~acl->a_owner_mask;
> +		else if (in_owner_or_group_class)
> +			denied |= requested & ~acl->a_group_mask;
> +		else {
> +			if (acl->a_flags & RICHACL_WRITE_THROUGH)
> +				denied = requested & ~acl->a_other_mask;
> +			else
> +				denied |= requested & ~acl->a_other_mask;
> +		}
> +	}
> +
> +out:
> +	return denied ? -EACCES : 0;
> +}
> +EXPORT_SYMBOL_GPL(richacl_permission);
> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> index e00f313..9768eeb 100644
> --- a/include/linux/richacl.h
> +++ b/include/linux/richacl.h
> @@ -300,4 +300,7 @@ extern unsigned int richacl_want_to_mask(unsigned int);
>  extern void richacl_compute_max_masks(struct richacl *);
>  extern struct richacl *richacl_chmod(struct richacl *, mode_t);
>  
> +/* richacl_inode.c */
> +extern int richacl_permission(struct inode *, const struct richacl *, int);
> +
>  #endif /* __RICHACL_H */
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 10/41] richacl: Permission check algorithm
@ 2015-09-11 21:16         ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-11 21:16 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Sat, Sep 05, 2015 at 12:27:05PM +0200, Andreas Gruenbacher wrote:
> A richacl roughly grants a requested access if the NFSv4 acl in the
> richacl grants the requested permissions according to the NFSv4
> permission check algorithm and the file mask that applies to the process
> includes the requested permissions.
> 
> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> Acked-by: "J. Bruce Fields" <bfields@fieldses.org>
> ---
>  fs/Makefile             |   2 +-
>  fs/richacl_inode.c      | 147 ++++++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/richacl.h |   3 +
>  3 files changed, 151 insertions(+), 1 deletion(-)
>  create mode 100644 fs/richacl_inode.c
> 
> diff --git a/fs/Makefile b/fs/Makefile
> index ddc43d8..1305047 100644
> --- a/fs/Makefile
> +++ b/fs/Makefile
> @@ -48,7 +48,7 @@ obj-$(CONFIG_SYSCTL)		+= drop_caches.o
>  
>  obj-$(CONFIG_FHANDLE)		+= fhandle.o
>  obj-$(CONFIG_FS_RICHACL)	+= richacl.o
> -richacl-y			:= richacl_base.o
> +richacl-y			:= richacl_base.o richacl_inode.o
>  
>  obj-y				+= quota/
>  
> diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c
> new file mode 100644
> index 0000000..ba05993
> --- /dev/null
> +++ b/fs/richacl_inode.c
> @@ -0,0 +1,147 @@
> +/*
> + * Copyright (C) 2010  Novell, Inc.
> + * Copyright (C) 2015  Red Hat, Inc.
> + * Written by Andreas Gruenbacher <agruen@kernel.org>
> + *
> + * This program 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, or (at your option) any
> + * later version.
> + *
> + * This program 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.
> + */
> +
> +#include <linux/sched.h>
> +#include <linux/module.h>
> +#include <linux/fs.h>
> +#include <linux/slab.h>
> +#include <linux/richacl.h>
> +
> +/**
> + * richacl_permission  -  richacl permission check algorithm
> + * @inode:	inode to check
> + * @acl:	rich acl of the inode
> + * @want:	requested access (MAY_* flags)
> + *
> + * Checks if the current process is granted @mask flags in @acl.
> + */
> +int
> +richacl_permission(struct inode *inode, const struct richacl *acl,
> +		   int want)
> +{
> +	const struct richace *ace;
> +	unsigned int mask = richacl_want_to_mask(want);
> +	unsigned int requested = mask, denied = 0;
> +	int in_owning_group = in_group_p(inode->i_gid);
> +	int in_owner_or_group_class = in_owning_group;
> +
> +	/*
> +	 * A process is
> +	 *   - in the owner file class if it owns the file,
> +	 *   - in the group file class if it is in the file's owning group or
> +	 *     it matches any of the user or group entries, and
> +	 *   - in the other file class otherwise.
> +	 * The file class is only relevant for determining which file mask to
> +	 * apply, which only happens for masked acls.
> +	 */
> +	if (acl->a_flags & RICHACL_MASKED) {
> +		if ((acl->a_flags & RICHACL_WRITE_THROUGH) &&
> +		    uid_eq(current_fsuid(), inode->i_uid)) {
> +			denied = requested & ~acl->a_owner_mask;
> +			goto out;
> +		}
> +	} else {
> +		/*
> +		 * When the acl is not masked, there is no need to determine if
> +		 * the process is in the group class and we can break out
> +		 * earlier of the loop below.
> +		 */
> +		in_owner_or_group_class = 1;
> +	}
> +
> +	/*
> +	 * Check if the acl grants the requested access and determine which
> +	 * file class the process is in.
> +	 */
> +	richacl_for_each_entry(ace, acl) {
> +		unsigned int ace_mask = ace->e_mask;
> +
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_owner(ace)) {
> +			if (!uid_eq(current_fsuid(), inode->i_uid))
> +				continue;
> +			goto entry_matches_owner;
> +		} else if (richace_is_group(ace)) {
> +			if (!in_owning_group)
> +				continue;
> +		} else if (richace_is_unix_user(ace)) {
> +			if (!uid_eq(current_fsuid(), ace->e_id.uid))
> +				continue;
> +		} else if (richace_is_unix_group(ace)) {
> +			if (!in_group_p(ace->e_id.gid))
> +				continue;
> +		} else
> +			goto entry_matches_everyone;
> +
> +		/*
> +		 * Apply the group file mask to entries other than owner@ and
> +		 * everyone@ or user entries matching the owner.  This ensures
> +		 * that we grant the same permissions as the acl computed by
> +		 * richacl_apply_masks().
> +		 *
> +		 * Without this restriction, the following richacl would grant
> +		 * rw access to processes which are both the owner and in the
> +		 * owning group, but not to other users in the owning group,
> +		 * which could not be represented without masks:
> +		 *
> +		 *  owner:rw::mask
> +		 *  group@:rw::allow
> +		 */
> +		if ((acl->a_flags & RICHACL_MASKED) && richace_is_allow(ace))
> +			ace_mask &= acl->a_group_mask;

I'm having trouble understanding this.  I think the problem is that I
don't really understand the notation in your example.  Is a_group_mask
zero in that example?  I think it must be, in which case, OK I think I
get it.

(Though I still have to think about it a little more to convince myself
that richacl_apply_masks() always gets the same result.)

--b.

> +
> +entry_matches_owner:
> +		/* The process is in the owner or group file class. */
> +		in_owner_or_group_class = 1;
> +
> +entry_matches_everyone:
> +		/* Check which mask flags the ACE allows or denies. */
> +		if (richace_is_deny(ace))
> +			denied |= ace_mask & mask;
> +		mask &= ~ace_mask;
> +
> +		/*
> +		 * Keep going until we know which file class
> +		 * the process is in.
> +		 */
> +		if (!mask && in_owner_or_group_class)
> +			break;
> +	}
> +	denied |= mask;
> +
> +	if (acl->a_flags & RICHACL_MASKED) {
> +		/*
> +		 * The file class a process is in determines which file mask
> +		 * applies.  Check if that file mask also grants the requested
> +		 * access.
> +		 */
> +		if (uid_eq(current_fsuid(), inode->i_uid))
> +			denied |= requested & ~acl->a_owner_mask;
> +		else if (in_owner_or_group_class)
> +			denied |= requested & ~acl->a_group_mask;
> +		else {
> +			if (acl->a_flags & RICHACL_WRITE_THROUGH)
> +				denied = requested & ~acl->a_other_mask;
> +			else
> +				denied |= requested & ~acl->a_other_mask;
> +		}
> +	}
> +
> +out:
> +	return denied ? -EACCES : 0;
> +}
> +EXPORT_SYMBOL_GPL(richacl_permission);
> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> index e00f313..9768eeb 100644
> --- a/include/linux/richacl.h
> +++ b/include/linux/richacl.h
> @@ -300,4 +300,7 @@ extern unsigned int richacl_want_to_mask(unsigned int);
>  extern void richacl_compute_max_masks(struct richacl *);
>  extern struct richacl *richacl_chmod(struct richacl *, mode_t);
>  
> +/* richacl_inode.c */
> +extern int richacl_permission(struct inode *, const struct richacl *, int);
> +
>  #endif /* __RICHACL_H */
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 10/41] richacl: Permission check algorithm
  2015-09-11 21:16         ` J. Bruce Fields
@ 2015-09-11 22:12             ` Andreas Grünbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Grünbacher @ 2015-09-11 22:12 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: Andreas Gruenbacher, Linux Kernel Mailing List,
	Linux FS-devel Mailing List, Linux NFS Mailing List,
	Linux API Mailing List, linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	LSM List

2015-09-11 23:16 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> On Sat, Sep 05, 2015 at 12:27:05PM +0200, Andreas Gruenbacher wrote:
>> +             /*
>> +              * Apply the group file mask to entries other than owner@ and
>> +              * everyone@ or user entries matching the owner.  This ensures
>> +              * that we grant the same permissions as the acl computed by
>> +              * richacl_apply_masks().
>> +              *
>> +              * Without this restriction, the following richacl would grant
>> +              * rw access to processes which are both the owner and in the
>> +              * owning group, but not to other users in the owning group,
>> +              * which could not be represented without masks:
>> +              *
>> +              *  owner:rw::mask
>> +              *  group@:rw::allow
>> +              */
>> +             if ((acl->a_flags & RICHACL_MASKED) && richace_is_allow(ace))
>> +                     ace_mask &= acl->a_group_mask;
>
> I'm having trouble understanding this.  I think the problem is that I
> don't really understand the notation in your example.  Is a_group_mask
> zero in that example?  I think it must be, in which case, OK I think I
> get it.

Yes. I'm not sure if the example becomes easier to understand when the
empty group mask and perhaps also the other mask is included.

> (Though I still have to think about it a little more to convince myself
> that richacl_apply_masks() always gets the same result.)

I have tried to break the algorithm into digestible pieces. Do you see
another way to make things easier to understand?

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 10/41] richacl: Permission check algorithm
@ 2015-09-11 22:12             ` Andreas Grünbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Grünbacher @ 2015-09-11 22:12 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: Andreas Gruenbacher, Linux Kernel Mailing List,
	Linux FS-devel Mailing List, Linux NFS Mailing List,
	Linux API Mailing List, linux-cifs, LSM List

2015-09-11 23:16 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> On Sat, Sep 05, 2015 at 12:27:05PM +0200, Andreas Gruenbacher wrote:
>> +             /*
>> +              * Apply the group file mask to entries other than owner@ and
>> +              * everyone@ or user entries matching the owner.  This ensures
>> +              * that we grant the same permissions as the acl computed by
>> +              * richacl_apply_masks().
>> +              *
>> +              * Without this restriction, the following richacl would grant
>> +              * rw access to processes which are both the owner and in the
>> +              * owning group, but not to other users in the owning group,
>> +              * which could not be represented without masks:
>> +              *
>> +              *  owner:rw::mask
>> +              *  group@:rw::allow
>> +              */
>> +             if ((acl->a_flags & RICHACL_MASKED) && richace_is_allow(ace))
>> +                     ace_mask &= acl->a_group_mask;
>
> I'm having trouble understanding this.  I think the problem is that I
> don't really understand the notation in your example.  Is a_group_mask
> zero in that example?  I think it must be, in which case, OK I think I
> get it.

Yes. I'm not sure if the example becomes easier to understand when the
empty group mask and perhaps also the other mask is included.

> (Though I still have to think about it a little more to convince myself
> that richacl_apply_masks() always gets the same result.)

I have tried to break the algorithm into digestible pieces. Do you see
another way to make things easier to understand?

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 10/41] richacl: Permission check algorithm
  2015-09-11 22:12             ` Andreas Grünbacher
  (?)
@ 2015-09-17 17:30             ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-17 17:30 UTC (permalink / raw)
  To: Andreas Grünbacher
  Cc: Andreas Gruenbacher, Linux Kernel Mailing List,
	Linux FS-devel Mailing List, Linux NFS Mailing List,
	Linux API Mailing List, linux-cifs, LSM List

On Sat, Sep 12, 2015 at 12:12:16AM +0200, Andreas Grünbacher wrote:
> 2015-09-11 23:16 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> > On Sat, Sep 05, 2015 at 12:27:05PM +0200, Andreas Gruenbacher wrote:
> >> +             /*
> >> +              * Apply the group file mask to entries other than owner@ and
> >> +              * everyone@ or user entries matching the owner.  This ensures
> >> +              * that we grant the same permissions as the acl computed by
> >> +              * richacl_apply_masks().
> >> +              *
> >> +              * Without this restriction, the following richacl would grant
> >> +              * rw access to processes which are both the owner and in the
> >> +              * owning group, but not to other users in the owning group,
> >> +              * which could not be represented without masks:
> >> +              *
> >> +              *  owner:rw::mask
> >> +              *  group@:rw::allow
> >> +              */
> >> +             if ((acl->a_flags & RICHACL_MASKED) && richace_is_allow(ace))
> >> +                     ace_mask &= acl->a_group_mask;
> >
> > I'm having trouble understanding this.  I think the problem is that I
> > don't really understand the notation in your example.  Is a_group_mask
> > zero in that example?  I think it must be, in which case, OK I think I
> > get it.
> 
> Yes. I'm not sure if the example becomes easier to understand when the
> empty group mask and perhaps also the other mask is included.

I think it would have been for me.

In general I find it confusing to present the mask bits as additional
ACEs--they're really pretty different.

> > (Though I still have to think about it a little more to convince myself
> > that richacl_apply_masks() always gets the same result.)
> 
> I have tried to break the algorithm into digestible pieces. Do you see
> another way to make things easier to understand?

I just haven't reread those carefully enough yet, working on it....

--b.

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 13/41] richacl: Check if an acl is equivalent to a file mode
  2015-09-05 10:27     ` Andreas Gruenbacher
  (?)
@ 2015-09-17 18:22     ` J. Bruce Fields
       [not found]       ` <20150917182219.GB13825-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
  2015-09-21 23:20       ` Andreas Gruenbacher
  -1 siblings, 2 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-17 18:22 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Sat, Sep 05, 2015 at 12:27:08PM +0200, Andreas Gruenbacher wrote:
> ACLs are considered equivalent to file modes if they only consist of
> owner@, group@, and everyone@ entries, the owner@ permissions do not
> depend on whether the owner is a member in the owning group, and no
> inheritance flags are set.  This test is used to avoid storing richacls
> if the acl can be computed from the file permission bits.

We're assuming here that it's OK for us to silently rearrange an ACL as
long as the result is still equivalent (in the sense that the permission
algorithm would always produce the same result).

I guess that's OK by me, but it might violate user expectations in some
simple common cases, so may be worth mentioning in documentation
someplace if we don't already.

--b.

> 
> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> ---
>  fs/richacl_base.c       | 104 ++++++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/richacl.h |   1 +
>  2 files changed, 105 insertions(+)
> 
> diff --git a/fs/richacl_base.c b/fs/richacl_base.c
> index 3163152..106e988 100644
> --- a/fs/richacl_base.c
> +++ b/fs/richacl_base.c
> @@ -379,3 +379,107 @@ richacl_chmod(struct richacl *acl, mode_t mode)
>  	return clone;
>  }
>  EXPORT_SYMBOL_GPL(richacl_chmod);
> +
> +/**
> + * richacl_equiv_mode  -  compute the mode equivalent of @acl
> + *
> + * An acl is considered equivalent to a file mode if it only consists of
> + * owner@, group@, and everyone@ entries and the owner@ permissions do not
> + * depend on whether the owner is a member in the owning group.
> + */
> +int
> +richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
> +{
> +	mode_t mode = *mode_p;
> +
> +	/*
> +	 * The RICHACE_DELETE_CHILD flag is meaningless for non-directories, so
> +	 * we ignore it.
> +	 */
> +	unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> +	struct {
> +		unsigned int allowed;
> +		unsigned int defined;  /* allowed or denied */
> +	} owner = {
> +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED |
> +			   RICHACE_POSIX_OWNER_ALLOWED  | x,
> +	}, group = {
> +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
> +	}, everyone = {
> +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
> +	};
> +	const struct richace *ace;
> +
> +	if (acl->a_flags & ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED))
> +		return -1;
> +
> +	richacl_for_each_entry(ace, acl) {
> +		if (ace->e_flags & ~RICHACE_SPECIAL_WHO)
> +			return -1;
> +
> +		if (richace_is_owner(ace) || richace_is_everyone(ace)) {
> +			x = ace->e_mask & ~owner.defined;
> +			if (richace_is_allow(ace)) {
> +				unsigned int group_denied =
> +					group.defined & ~group.allowed;
> +
> +				if (x & group_denied)
> +					return -1;
> +				owner.allowed |= x;
> +			} else /* if (richace_is_deny(ace)) */ {
> +				if (x & group.allowed)
> +					return -1;
> +			}
> +			owner.defined |= x;
> +
> +			if (richace_is_everyone(ace)) {
> +				x = ace->e_mask;
> +				if (richace_is_allow(ace)) {
> +					group.allowed |=
> +						x & ~group.defined;
> +					everyone.allowed |=
> +						x & ~everyone.defined;
> +				}
> +				group.defined |= x;
> +				everyone.defined |= x;
> +			}
> +		} else if (richace_is_group(ace)) {
> +			x = ace->e_mask & ~group.defined;
> +			if (richace_is_allow(ace))
> +				group.allowed |= x;
> +			group.defined |= x;
> +		} else
> +			return -1;
> +	}
> +
> +	if (group.allowed & ~owner.defined)
> +		return -1;
> +
> +	if (acl->a_flags & RICHACL_MASKED) {
> +		if (acl->a_flags & RICHACL_WRITE_THROUGH) {
> +			owner.allowed = acl->a_owner_mask;
> +			everyone.allowed = acl->a_other_mask;
> +		} else {
> +			owner.allowed &= acl->a_owner_mask;
> +			everyone.allowed &= acl->a_other_mask;
> +		}
> +		group.allowed &= acl->a_group_mask;
> +	}
> +
> +	mode = (mode & ~S_IRWXUGO) |
> +	       (richacl_mask_to_mode(owner.allowed) << 6) |
> +	       (richacl_mask_to_mode(group.allowed) << 3) |
> +		richacl_mask_to_mode(everyone.allowed);
> +
> +	/* Mask flags we can ignore */
> +	x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> +
> +	if (((richacl_mode_to_mask(mode >> 6) ^ owner.allowed)    & ~x) ||
> +	    ((richacl_mode_to_mask(mode >> 3) ^ group.allowed)    & ~x) ||
> +	    ((richacl_mode_to_mask(mode)      ^ everyone.allowed) & ~x))
> +		return -1;
> +
> +	*mode_p = mode;
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(richacl_equiv_mode);
> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> index d4a576c..6535ce5 100644
> --- a/include/linux/richacl.h
> +++ b/include/linux/richacl.h
> @@ -304,6 +304,7 @@ extern unsigned int richacl_mode_to_mask(mode_t);
>  extern unsigned int richacl_want_to_mask(unsigned int);
>  extern void richacl_compute_max_masks(struct richacl *);
>  extern struct richacl *richacl_chmod(struct richacl *, mode_t);
> +extern int richacl_equiv_mode(const struct richacl *, mode_t *);
>  
>  /* richacl_inode.c */
>  extern int richacl_permission(struct inode *, const struct richacl *, int);
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 13/41] richacl: Check if an acl is equivalent to a file mode
  2015-09-05 10:27     ` Andreas Gruenbacher
  (?)
  (?)
@ 2015-09-17 18:37     ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-17 18:37 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Sat, Sep 05, 2015 at 12:27:08PM +0200, Andreas Gruenbacher wrote:
> ACLs are considered equivalent to file modes if they only consist of
> owner@, group@, and everyone@ entries, the owner@ permissions do not
> depend on whether the owner is a member in the owning group, and no
> inheritance flags are set.  This test is used to avoid storing richacls
> if the acl can be computed from the file permission bits.
> 
> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> ---
>  fs/richacl_base.c       | 104 ++++++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/richacl.h |   1 +
>  2 files changed, 105 insertions(+)
> 
> diff --git a/fs/richacl_base.c b/fs/richacl_base.c
> index 3163152..106e988 100644
> --- a/fs/richacl_base.c
> +++ b/fs/richacl_base.c
> @@ -379,3 +379,107 @@ richacl_chmod(struct richacl *acl, mode_t mode)
>  	return clone;
>  }
>  EXPORT_SYMBOL_GPL(richacl_chmod);
> +
> +/**
> + * richacl_equiv_mode  -  compute the mode equivalent of @acl
> + *
> + * An acl is considered equivalent to a file mode if it only consists of
> + * owner@, group@, and everyone@ entries and the owner@ permissions do not
> + * depend on whether the owner is a member in the owning group.
> + */
> +int
> +richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
> +{
> +	mode_t mode = *mode_p;
> +
> +	/*
> +	 * The RICHACE_DELETE_CHILD flag is meaningless for non-directories, so
> +	 * we ignore it.
> +	 */
> +	unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> +	struct {
> +		unsigned int allowed;
> +		unsigned int defined;  /* allowed or denied */
> +	} owner = {
> +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED |
> +			   RICHACE_POSIX_OWNER_ALLOWED  | x,
> +	}, group = {
> +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
> +	}, everyone = {
> +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
> +	};
> +	const struct richace *ace;
> +
> +	if (acl->a_flags & ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED))
> +		return -1;
> +
> +	richacl_for_each_entry(ace, acl) {
> +		if (ace->e_flags & ~RICHACE_SPECIAL_WHO)
> +			return -1;

Couldn't this just be

		if (ace->e_flags != RICHACE_SPECIAL_WHO)
			return -1

I guess the only difference is that you're letting the named-user case
through to get caught by the final "else" clause below.... Still, the !=
test seems possibly simpler to me.

--b.

> +
> +		if (richace_is_owner(ace) || richace_is_everyone(ace)) {
> +			x = ace->e_mask & ~owner.defined;
> +			if (richace_is_allow(ace)) {
> +				unsigned int group_denied =
> +					group.defined & ~group.allowed;
> +
> +				if (x & group_denied)
> +					return -1;
> +				owner.allowed |= x;
> +			} else /* if (richace_is_deny(ace)) */ {
> +				if (x & group.allowed)
> +					return -1;
> +			}
> +			owner.defined |= x;
> +
> +			if (richace_is_everyone(ace)) {
> +				x = ace->e_mask;
> +				if (richace_is_allow(ace)) {
> +					group.allowed |=
> +						x & ~group.defined;
> +					everyone.allowed |=
> +						x & ~everyone.defined;
> +				}
> +				group.defined |= x;
> +				everyone.defined |= x;
> +			}
> +		} else if (richace_is_group(ace)) {
> +			x = ace->e_mask & ~group.defined;
> +			if (richace_is_allow(ace))
> +				group.allowed |= x;
> +			group.defined |= x;
> +		} else
> +			return -1;
> +	}
> +
> +	if (group.allowed & ~owner.defined)
> +		return -1;
> +
> +	if (acl->a_flags & RICHACL_MASKED) {
> +		if (acl->a_flags & RICHACL_WRITE_THROUGH) {
> +			owner.allowed = acl->a_owner_mask;
> +			everyone.allowed = acl->a_other_mask;
> +		} else {
> +			owner.allowed &= acl->a_owner_mask;
> +			everyone.allowed &= acl->a_other_mask;
> +		}
> +		group.allowed &= acl->a_group_mask;
> +	}
> +
> +	mode = (mode & ~S_IRWXUGO) |
> +	       (richacl_mask_to_mode(owner.allowed) << 6) |
> +	       (richacl_mask_to_mode(group.allowed) << 3) |
> +		richacl_mask_to_mode(everyone.allowed);
> +
> +	/* Mask flags we can ignore */
> +	x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> +
> +	if (((richacl_mode_to_mask(mode >> 6) ^ owner.allowed)    & ~x) ||
> +	    ((richacl_mode_to_mask(mode >> 3) ^ group.allowed)    & ~x) ||
> +	    ((richacl_mode_to_mask(mode)      ^ everyone.allowed) & ~x))
> +		return -1;
> +
> +	*mode_p = mode;
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(richacl_equiv_mode);
> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> index d4a576c..6535ce5 100644
> --- a/include/linux/richacl.h
> +++ b/include/linux/richacl.h
> @@ -304,6 +304,7 @@ extern unsigned int richacl_mode_to_mask(mode_t);
>  extern unsigned int richacl_want_to_mask(unsigned int);
>  extern void richacl_compute_max_masks(struct richacl *);
>  extern struct richacl *richacl_chmod(struct richacl *, mode_t);
> +extern int richacl_equiv_mode(const struct richacl *, mode_t *);
>  
>  /* richacl_inode.c */
>  extern int richacl_permission(struct inode *, const struct richacl *, int);
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 13/41] richacl: Check if an acl is equivalent to a file mode
  2015-09-17 18:22     ` J. Bruce Fields
@ 2015-09-18  0:56           ` J. Bruce Fields
  2015-09-21 23:20       ` Andreas Gruenbacher
  1 sibling, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-18  0:56 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

On Thu, Sep 17, 2015 at 02:22:19PM -0400, bfields wrote:
> On Sat, Sep 05, 2015 at 12:27:08PM +0200, Andreas Gruenbacher wrote:
> > ACLs are considered equivalent to file modes if they only consist of
> > owner@, group@, and everyone@ entries, the owner@ permissions do not
> > depend on whether the owner is a member in the owning group, and no
> > inheritance flags are set.  This test is used to avoid storing richacls
> > if the acl can be computed from the file permission bits.
> 
> We're assuming here that it's OK for us to silently rearrange an ACL as
> long as the result is still equivalent (in the sense that the permission
> algorithm would always produce the same result).
> 
> I guess that's OK by me, but it might violate user expectations in some
> simple common cases, so may be worth mentioning in documentation
> someplace if we don't already.

Also your notion of mode-equivalence here is interesting, it's actually
a strict subset of the ACLs that produce the same permission results as
a mode.  (For example, everyone:rwx,bfields:rwx is equivalent to 0777
but won't be considered mode-equivalent by this algorithm.)

I think the choices you've made probably make the most sense, they just
wouldn't have been obvious to me.  Anyway, so, OK by me:

	Reviewed-by: J. Bruce Fields <bfields-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>

--b.

> 
> --b.
> 
> > 
> > Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> > ---
> >  fs/richacl_base.c       | 104 ++++++++++++++++++++++++++++++++++++++++++++++++
> >  include/linux/richacl.h |   1 +
> >  2 files changed, 105 insertions(+)
> > 
> > diff --git a/fs/richacl_base.c b/fs/richacl_base.c
> > index 3163152..106e988 100644
> > --- a/fs/richacl_base.c
> > +++ b/fs/richacl_base.c
> > @@ -379,3 +379,107 @@ richacl_chmod(struct richacl *acl, mode_t mode)
> >  	return clone;
> >  }
> >  EXPORT_SYMBOL_GPL(richacl_chmod);
> > +
> > +/**
> > + * richacl_equiv_mode  -  compute the mode equivalent of @acl
> > + *
> > + * An acl is considered equivalent to a file mode if it only consists of
> > + * owner@, group@, and everyone@ entries and the owner@ permissions do not
> > + * depend on whether the owner is a member in the owning group.
> > + */
> > +int
> > +richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
> > +{
> > +	mode_t mode = *mode_p;
> > +
> > +	/*
> > +	 * The RICHACE_DELETE_CHILD flag is meaningless for non-directories, so
> > +	 * we ignore it.
> > +	 */
> > +	unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> > +	struct {
> > +		unsigned int allowed;
> > +		unsigned int defined;  /* allowed or denied */
> > +	} owner = {
> > +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED |
> > +			   RICHACE_POSIX_OWNER_ALLOWED  | x,
> > +	}, group = {
> > +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
> > +	}, everyone = {
> > +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
> > +	};
> > +	const struct richace *ace;
> > +
> > +	if (acl->a_flags & ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED))
> > +		return -1;
> > +
> > +	richacl_for_each_entry(ace, acl) {
> > +		if (ace->e_flags & ~RICHACE_SPECIAL_WHO)
> > +			return -1;
> > +
> > +		if (richace_is_owner(ace) || richace_is_everyone(ace)) {
> > +			x = ace->e_mask & ~owner.defined;
> > +			if (richace_is_allow(ace)) {
> > +				unsigned int group_denied =
> > +					group.defined & ~group.allowed;
> > +
> > +				if (x & group_denied)
> > +					return -1;
> > +				owner.allowed |= x;
> > +			} else /* if (richace_is_deny(ace)) */ {
> > +				if (x & group.allowed)
> > +					return -1;
> > +			}
> > +			owner.defined |= x;
> > +
> > +			if (richace_is_everyone(ace)) {
> > +				x = ace->e_mask;
> > +				if (richace_is_allow(ace)) {
> > +					group.allowed |=
> > +						x & ~group.defined;
> > +					everyone.allowed |=
> > +						x & ~everyone.defined;
> > +				}
> > +				group.defined |= x;
> > +				everyone.defined |= x;
> > +			}
> > +		} else if (richace_is_group(ace)) {
> > +			x = ace->e_mask & ~group.defined;
> > +			if (richace_is_allow(ace))
> > +				group.allowed |= x;
> > +			group.defined |= x;
> > +		} else
> > +			return -1;
> > +	}
> > +
> > +	if (group.allowed & ~owner.defined)
> > +		return -1;
> > +
> > +	if (acl->a_flags & RICHACL_MASKED) {
> > +		if (acl->a_flags & RICHACL_WRITE_THROUGH) {
> > +			owner.allowed = acl->a_owner_mask;
> > +			everyone.allowed = acl->a_other_mask;
> > +		} else {
> > +			owner.allowed &= acl->a_owner_mask;
> > +			everyone.allowed &= acl->a_other_mask;
> > +		}
> > +		group.allowed &= acl->a_group_mask;
> > +	}
> > +
> > +	mode = (mode & ~S_IRWXUGO) |
> > +	       (richacl_mask_to_mode(owner.allowed) << 6) |
> > +	       (richacl_mask_to_mode(group.allowed) << 3) |
> > +		richacl_mask_to_mode(everyone.allowed);
> > +
> > +	/* Mask flags we can ignore */
> > +	x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> > +
> > +	if (((richacl_mode_to_mask(mode >> 6) ^ owner.allowed)    & ~x) ||
> > +	    ((richacl_mode_to_mask(mode >> 3) ^ group.allowed)    & ~x) ||
> > +	    ((richacl_mode_to_mask(mode)      ^ everyone.allowed) & ~x))
> > +		return -1;
> > +
> > +	*mode_p = mode;
> > +	return 0;
> > +}
> > +EXPORT_SYMBOL_GPL(richacl_equiv_mode);
> > diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> > index d4a576c..6535ce5 100644
> > --- a/include/linux/richacl.h
> > +++ b/include/linux/richacl.h
> > @@ -304,6 +304,7 @@ extern unsigned int richacl_mode_to_mask(mode_t);
> >  extern unsigned int richacl_want_to_mask(unsigned int);
> >  extern void richacl_compute_max_masks(struct richacl *);
> >  extern struct richacl *richacl_chmod(struct richacl *, mode_t);
> > +extern int richacl_equiv_mode(const struct richacl *, mode_t *);
> >  
> >  /* richacl_inode.c */
> >  extern int richacl_permission(struct inode *, const struct richacl *, int);
> > -- 
> > 2.4.3
> > 
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> > the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 13/41] richacl: Check if an acl is equivalent to a file mode
@ 2015-09-18  0:56           ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-18  0:56 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Thu, Sep 17, 2015 at 02:22:19PM -0400, bfields wrote:
> On Sat, Sep 05, 2015 at 12:27:08PM +0200, Andreas Gruenbacher wrote:
> > ACLs are considered equivalent to file modes if they only consist of
> > owner@, group@, and everyone@ entries, the owner@ permissions do not
> > depend on whether the owner is a member in the owning group, and no
> > inheritance flags are set.  This test is used to avoid storing richacls
> > if the acl can be computed from the file permission bits.
> 
> We're assuming here that it's OK for us to silently rearrange an ACL as
> long as the result is still equivalent (in the sense that the permission
> algorithm would always produce the same result).
> 
> I guess that's OK by me, but it might violate user expectations in some
> simple common cases, so may be worth mentioning in documentation
> someplace if we don't already.

Also your notion of mode-equivalence here is interesting, it's actually
a strict subset of the ACLs that produce the same permission results as
a mode.  (For example, everyone:rwx,bfields:rwx is equivalent to 0777
but won't be considered mode-equivalent by this algorithm.)

I think the choices you've made probably make the most sense, they just
wouldn't have been obvious to me.  Anyway, so, OK by me:

	Reviewed-by: J. Bruce Fields <bfields@redhat.com>

--b.

> 
> --b.
> 
> > 
> > Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> > ---
> >  fs/richacl_base.c       | 104 ++++++++++++++++++++++++++++++++++++++++++++++++
> >  include/linux/richacl.h |   1 +
> >  2 files changed, 105 insertions(+)
> > 
> > diff --git a/fs/richacl_base.c b/fs/richacl_base.c
> > index 3163152..106e988 100644
> > --- a/fs/richacl_base.c
> > +++ b/fs/richacl_base.c
> > @@ -379,3 +379,107 @@ richacl_chmod(struct richacl *acl, mode_t mode)
> >  	return clone;
> >  }
> >  EXPORT_SYMBOL_GPL(richacl_chmod);
> > +
> > +/**
> > + * richacl_equiv_mode  -  compute the mode equivalent of @acl
> > + *
> > + * An acl is considered equivalent to a file mode if it only consists of
> > + * owner@, group@, and everyone@ entries and the owner@ permissions do not
> > + * depend on whether the owner is a member in the owning group.
> > + */
> > +int
> > +richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
> > +{
> > +	mode_t mode = *mode_p;
> > +
> > +	/*
> > +	 * The RICHACE_DELETE_CHILD flag is meaningless for non-directories, so
> > +	 * we ignore it.
> > +	 */
> > +	unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> > +	struct {
> > +		unsigned int allowed;
> > +		unsigned int defined;  /* allowed or denied */
> > +	} owner = {
> > +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED |
> > +			   RICHACE_POSIX_OWNER_ALLOWED  | x,
> > +	}, group = {
> > +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
> > +	}, everyone = {
> > +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
> > +	};
> > +	const struct richace *ace;
> > +
> > +	if (acl->a_flags & ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED))
> > +		return -1;
> > +
> > +	richacl_for_each_entry(ace, acl) {
> > +		if (ace->e_flags & ~RICHACE_SPECIAL_WHO)
> > +			return -1;
> > +
> > +		if (richace_is_owner(ace) || richace_is_everyone(ace)) {
> > +			x = ace->e_mask & ~owner.defined;
> > +			if (richace_is_allow(ace)) {
> > +				unsigned int group_denied =
> > +					group.defined & ~group.allowed;
> > +
> > +				if (x & group_denied)
> > +					return -1;
> > +				owner.allowed |= x;
> > +			} else /* if (richace_is_deny(ace)) */ {
> > +				if (x & group.allowed)
> > +					return -1;
> > +			}
> > +			owner.defined |= x;
> > +
> > +			if (richace_is_everyone(ace)) {
> > +				x = ace->e_mask;
> > +				if (richace_is_allow(ace)) {
> > +					group.allowed |=
> > +						x & ~group.defined;
> > +					everyone.allowed |=
> > +						x & ~everyone.defined;
> > +				}
> > +				group.defined |= x;
> > +				everyone.defined |= x;
> > +			}
> > +		} else if (richace_is_group(ace)) {
> > +			x = ace->e_mask & ~group.defined;
> > +			if (richace_is_allow(ace))
> > +				group.allowed |= x;
> > +			group.defined |= x;
> > +		} else
> > +			return -1;
> > +	}
> > +
> > +	if (group.allowed & ~owner.defined)
> > +		return -1;
> > +
> > +	if (acl->a_flags & RICHACL_MASKED) {
> > +		if (acl->a_flags & RICHACL_WRITE_THROUGH) {
> > +			owner.allowed = acl->a_owner_mask;
> > +			everyone.allowed = acl->a_other_mask;
> > +		} else {
> > +			owner.allowed &= acl->a_owner_mask;
> > +			everyone.allowed &= acl->a_other_mask;
> > +		}
> > +		group.allowed &= acl->a_group_mask;
> > +	}
> > +
> > +	mode = (mode & ~S_IRWXUGO) |
> > +	       (richacl_mask_to_mode(owner.allowed) << 6) |
> > +	       (richacl_mask_to_mode(group.allowed) << 3) |
> > +		richacl_mask_to_mode(everyone.allowed);
> > +
> > +	/* Mask flags we can ignore */
> > +	x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> > +
> > +	if (((richacl_mode_to_mask(mode >> 6) ^ owner.allowed)    & ~x) ||
> > +	    ((richacl_mode_to_mask(mode >> 3) ^ group.allowed)    & ~x) ||
> > +	    ((richacl_mode_to_mask(mode)      ^ everyone.allowed) & ~x))
> > +		return -1;
> > +
> > +	*mode_p = mode;
> > +	return 0;
> > +}
> > +EXPORT_SYMBOL_GPL(richacl_equiv_mode);
> > diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> > index d4a576c..6535ce5 100644
> > --- a/include/linux/richacl.h
> > +++ b/include/linux/richacl.h
> > @@ -304,6 +304,7 @@ extern unsigned int richacl_mode_to_mask(mode_t);
> >  extern unsigned int richacl_want_to_mask(unsigned int);
> >  extern void richacl_compute_max_masks(struct richacl *);
> >  extern struct richacl *richacl_chmod(struct richacl *, mode_t);
> > +extern int richacl_equiv_mode(const struct richacl *, mode_t *);
> >  
> >  /* richacl_inode.c */
> >  extern int richacl_permission(struct inode *, const struct richacl *, int);
> > -- 
> > 2.4.3
> > 
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 14/41] richacl: Create-time inheritance
  2015-09-05 10:27     ` Andreas Gruenbacher
@ 2015-09-18 17:58         ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-18 17:58 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

On Sat, Sep 05, 2015 at 12:27:09PM +0200, Andreas Gruenbacher wrote:
> When a new file is created, it can inherit an acl from its parent
> directory; this is similar to how default acls work in POSIX (draft)
> ACLs.
> 
> As with POSIX ACLs, if a file inherits an acl from its parent directory,
> the intersection between the create mode and the permissions granted by
> the inherited acl determines the file masks and file permission bits,
> and the umask is ignored.
> 
> Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> ---
>  fs/richacl_base.c       | 69 +++++++++++++++++++++++++++++++++++++++++++++++++
>  fs/richacl_inode.c      | 65 ++++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/richacl.h |  2 ++
>  3 files changed, 136 insertions(+)
> 
> diff --git a/fs/richacl_base.c b/fs/richacl_base.c
> index 106e988..fda407d 100644
> --- a/fs/richacl_base.c
> +++ b/fs/richacl_base.c
> @@ -483,3 +483,72 @@ richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
>  	return 0;
>  }
>  EXPORT_SYMBOL_GPL(richacl_equiv_mode);
> +
> +/**
> + * richacl_inherit  -  compute the inherited acl of a new file
> + * @dir_acl:	acl of the containing directory
> + * @isdir:	inherit by a directory or non-directory?
> + *
> + * A directory can have acl entries which files and/or directories created
> + * inside the directory will inherit.  This function computes the acl for such
> + * a new file.  If there is no inheritable acl, it will return %NULL.
> + */
> +struct richacl *
> +richacl_inherit(const struct richacl *dir_acl, int isdir)
> +{
> +	const struct richace *dir_ace;
> +	struct richacl *acl = NULL;
> +	struct richace *ace;
> +	int count = 0;
> +
> +	if (isdir) {
> +		richacl_for_each_entry(dir_ace, dir_acl) {
> +			if (!richace_is_inheritable(dir_ace))
> +				continue;
> +			count++;
> +		}
> +		if (!count)
> +			return NULL;
> +		acl = richacl_alloc(count, GFP_KERNEL);
> +		if (!acl)
> +			return ERR_PTR(-ENOMEM);
> +		ace = acl->a_entries;
> +		richacl_for_each_entry(dir_ace, dir_acl) {
> +			if (!richace_is_inheritable(dir_ace))
> +				continue;
> +			richace_copy(ace, dir_ace);
> +			if (dir_ace->e_flags & RICHACE_NO_PROPAGATE_INHERIT_ACE)
> +				ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
> +			else if ((dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE) &&
> +				 !(dir_ace->e_flags & RICHACE_DIRECTORY_INHERIT_ACE))

The FILE_INHERIT_ACE check there is redundant since we already know
dir_ace is inheritable.

(So, OK, it isn't wrong to check it again but let's not make this
condition any more complicated than necessary.)

> +				ace->e_flags |= RICHACE_INHERIT_ONLY_ACE;
> +			ace++;
> +		}
> +	} else {
> +		richacl_for_each_entry(dir_ace, dir_acl) {
> +			if (!(dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE))
> +				continue;
> +			count++;
> +		}
> +		if (!count)
> +			return NULL;
> +		acl = richacl_alloc(count, GFP_KERNEL);
> +		if (!acl)
> +			return ERR_PTR(-ENOMEM);
> +		ace = acl->a_entries;
> +		richacl_for_each_entry(dir_ace, dir_acl) {
> +			if (!(dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE))
> +				continue;
> +			richace_copy(ace, dir_ace);
> +			ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
> +			/*
> +			 * RICHACE_DELETE_CHILD is meaningless for
> +			 * non-directories, so clear it.
> +			 */
> +			ace->e_mask &= ~RICHACE_DELETE_CHILD;
> +			ace++;
> +		}
> +	}
> +
> +	return acl;
> +}
> diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c
> index dc2a69f..f3f1f84 100644
> --- a/fs/richacl_inode.c
> +++ b/fs/richacl_inode.c
> @@ -220,3 +220,68 @@ out:
>  	return denied ? -EACCES : 0;
>  }
>  EXPORT_SYMBOL_GPL(richacl_permission);
> +
> +/**
> + * richacl_inherit_inode  -  compute inherited acl and file mode
> + * @dir_acl:	acl of the containing directory
> + * @inode:	inode of the new file (create mode in i_mode)
> + *
> + * The file permission bits in inode->i_mode must be set to the create mode by
> + * the caller.
> + *
> + * If there is an inheritable acl, the maximum permissions that the acl grants
> + * will be computed and permissions not granted by the acl will be removed from
> + * inode->i_mode.  If there is no inheritable acl, the umask will be applied
> + * instead.
> + */
> +static struct richacl *
> +richacl_inherit_inode(const struct richacl *dir_acl, struct inode *inode)
> +{
> +	struct richacl *acl;
> +	mode_t mask;
> +
> +	acl = richacl_inherit(dir_acl, S_ISDIR(inode->i_mode));
> +	if (acl) {
> +		mask = inode->i_mode;
> +		if (richacl_equiv_mode(acl, &mask) == 0) {
> +			richacl_put(acl);
> +			acl = NULL;

Why is it correct to ignore entirely the inherited acl in this case?

Oh, I see, I'm forgetting that richacl_equiv_mode is setting the mask,
which will get applied at the end of this function.  In my defense,
maybe it's easy to overlook a side effect in an if condition.... But I
don't have a better idea.  OK.

So, nits aside:

	Reviewed-by: J. Bruce Fields <bfields-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>

--b.

> +		} else {
> +			richacl_compute_max_masks(acl);
> +			/*
> +			 * Ensure that the acl will not grant any permissions
> +			 * beyond the create mode.
> +			 */
> +			acl->a_flags |= RICHACL_MASKED;
> +			acl->a_owner_mask &=
> +				richacl_mode_to_mask(inode->i_mode >> 6);
> +			acl->a_group_mask &=
> +				richacl_mode_to_mask(inode->i_mode >> 3);
> +			acl->a_other_mask &=
> +				richacl_mode_to_mask(inode->i_mode);
> +			mask = ~S_IRWXUGO | richacl_masks_to_mode(acl);
> +		}
> +	} else
> +		mask = ~current_umask();
> +
> +	inode->i_mode &= mask;
> +	return acl;
> +}
> +
> +struct richacl *richacl_create(struct inode *inode, struct inode *dir)
> +{
> +	struct richacl *dir_acl, *acl = NULL;
> +
> +	if (S_ISLNK(inode->i_mode))
> +		return NULL;
> +	dir_acl = get_richacl(dir);
> +	if (dir_acl) {
> +		if (IS_ERR(dir_acl))
> +			return dir_acl;
> +		acl = richacl_inherit_inode(dir_acl, inode);
> +		richacl_put(dir_acl);
> +	} else
> +		inode->i_mode &= ~current_umask();
> +	return acl;
> +}
> +EXPORT_SYMBOL_GPL(richacl_create);
> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> index 6535ce5..9bf95c2 100644
> --- a/include/linux/richacl.h
> +++ b/include/linux/richacl.h
> @@ -305,8 +305,10 @@ extern unsigned int richacl_want_to_mask(unsigned int);
>  extern void richacl_compute_max_masks(struct richacl *);
>  extern struct richacl *richacl_chmod(struct richacl *, mode_t);
>  extern int richacl_equiv_mode(const struct richacl *, mode_t *);
> +extern struct richacl *richacl_inherit(const struct richacl *, int);
>  
>  /* richacl_inode.c */
>  extern int richacl_permission(struct inode *, const struct richacl *, int);
> +extern struct richacl *richacl_create(struct inode *, struct inode *);
>  
>  #endif /* __RICHACL_H */
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 14/41] richacl: Create-time inheritance
@ 2015-09-18 17:58         ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-18 17:58 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Sat, Sep 05, 2015 at 12:27:09PM +0200, Andreas Gruenbacher wrote:
> When a new file is created, it can inherit an acl from its parent
> directory; this is similar to how default acls work in POSIX (draft)
> ACLs.
> 
> As with POSIX ACLs, if a file inherits an acl from its parent directory,
> the intersection between the create mode and the permissions granted by
> the inherited acl determines the file masks and file permission bits,
> and the umask is ignored.
> 
> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> ---
>  fs/richacl_base.c       | 69 +++++++++++++++++++++++++++++++++++++++++++++++++
>  fs/richacl_inode.c      | 65 ++++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/richacl.h |  2 ++
>  3 files changed, 136 insertions(+)
> 
> diff --git a/fs/richacl_base.c b/fs/richacl_base.c
> index 106e988..fda407d 100644
> --- a/fs/richacl_base.c
> +++ b/fs/richacl_base.c
> @@ -483,3 +483,72 @@ richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
>  	return 0;
>  }
>  EXPORT_SYMBOL_GPL(richacl_equiv_mode);
> +
> +/**
> + * richacl_inherit  -  compute the inherited acl of a new file
> + * @dir_acl:	acl of the containing directory
> + * @isdir:	inherit by a directory or non-directory?
> + *
> + * A directory can have acl entries which files and/or directories created
> + * inside the directory will inherit.  This function computes the acl for such
> + * a new file.  If there is no inheritable acl, it will return %NULL.
> + */
> +struct richacl *
> +richacl_inherit(const struct richacl *dir_acl, int isdir)
> +{
> +	const struct richace *dir_ace;
> +	struct richacl *acl = NULL;
> +	struct richace *ace;
> +	int count = 0;
> +
> +	if (isdir) {
> +		richacl_for_each_entry(dir_ace, dir_acl) {
> +			if (!richace_is_inheritable(dir_ace))
> +				continue;
> +			count++;
> +		}
> +		if (!count)
> +			return NULL;
> +		acl = richacl_alloc(count, GFP_KERNEL);
> +		if (!acl)
> +			return ERR_PTR(-ENOMEM);
> +		ace = acl->a_entries;
> +		richacl_for_each_entry(dir_ace, dir_acl) {
> +			if (!richace_is_inheritable(dir_ace))
> +				continue;
> +			richace_copy(ace, dir_ace);
> +			if (dir_ace->e_flags & RICHACE_NO_PROPAGATE_INHERIT_ACE)
> +				ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
> +			else if ((dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE) &&
> +				 !(dir_ace->e_flags & RICHACE_DIRECTORY_INHERIT_ACE))

The FILE_INHERIT_ACE check there is redundant since we already know
dir_ace is inheritable.

(So, OK, it isn't wrong to check it again but let's not make this
condition any more complicated than necessary.)

> +				ace->e_flags |= RICHACE_INHERIT_ONLY_ACE;
> +			ace++;
> +		}
> +	} else {
> +		richacl_for_each_entry(dir_ace, dir_acl) {
> +			if (!(dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE))
> +				continue;
> +			count++;
> +		}
> +		if (!count)
> +			return NULL;
> +		acl = richacl_alloc(count, GFP_KERNEL);
> +		if (!acl)
> +			return ERR_PTR(-ENOMEM);
> +		ace = acl->a_entries;
> +		richacl_for_each_entry(dir_ace, dir_acl) {
> +			if (!(dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE))
> +				continue;
> +			richace_copy(ace, dir_ace);
> +			ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
> +			/*
> +			 * RICHACE_DELETE_CHILD is meaningless for
> +			 * non-directories, so clear it.
> +			 */
> +			ace->e_mask &= ~RICHACE_DELETE_CHILD;
> +			ace++;
> +		}
> +	}
> +
> +	return acl;
> +}
> diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c
> index dc2a69f..f3f1f84 100644
> --- a/fs/richacl_inode.c
> +++ b/fs/richacl_inode.c
> @@ -220,3 +220,68 @@ out:
>  	return denied ? -EACCES : 0;
>  }
>  EXPORT_SYMBOL_GPL(richacl_permission);
> +
> +/**
> + * richacl_inherit_inode  -  compute inherited acl and file mode
> + * @dir_acl:	acl of the containing directory
> + * @inode:	inode of the new file (create mode in i_mode)
> + *
> + * The file permission bits in inode->i_mode must be set to the create mode by
> + * the caller.
> + *
> + * If there is an inheritable acl, the maximum permissions that the acl grants
> + * will be computed and permissions not granted by the acl will be removed from
> + * inode->i_mode.  If there is no inheritable acl, the umask will be applied
> + * instead.
> + */
> +static struct richacl *
> +richacl_inherit_inode(const struct richacl *dir_acl, struct inode *inode)
> +{
> +	struct richacl *acl;
> +	mode_t mask;
> +
> +	acl = richacl_inherit(dir_acl, S_ISDIR(inode->i_mode));
> +	if (acl) {
> +		mask = inode->i_mode;
> +		if (richacl_equiv_mode(acl, &mask) == 0) {
> +			richacl_put(acl);
> +			acl = NULL;

Why is it correct to ignore entirely the inherited acl in this case?

Oh, I see, I'm forgetting that richacl_equiv_mode is setting the mask,
which will get applied at the end of this function.  In my defense,
maybe it's easy to overlook a side effect in an if condition.... But I
don't have a better idea.  OK.

So, nits aside:

	Reviewed-by: J. Bruce Fields <bfields@redhat.com>

--b.

> +		} else {
> +			richacl_compute_max_masks(acl);
> +			/*
> +			 * Ensure that the acl will not grant any permissions
> +			 * beyond the create mode.
> +			 */
> +			acl->a_flags |= RICHACL_MASKED;
> +			acl->a_owner_mask &=
> +				richacl_mode_to_mask(inode->i_mode >> 6);
> +			acl->a_group_mask &=
> +				richacl_mode_to_mask(inode->i_mode >> 3);
> +			acl->a_other_mask &=
> +				richacl_mode_to_mask(inode->i_mode);
> +			mask = ~S_IRWXUGO | richacl_masks_to_mode(acl);
> +		}
> +	} else
> +		mask = ~current_umask();
> +
> +	inode->i_mode &= mask;
> +	return acl;
> +}
> +
> +struct richacl *richacl_create(struct inode *inode, struct inode *dir)
> +{
> +	struct richacl *dir_acl, *acl = NULL;
> +
> +	if (S_ISLNK(inode->i_mode))
> +		return NULL;
> +	dir_acl = get_richacl(dir);
> +	if (dir_acl) {
> +		if (IS_ERR(dir_acl))
> +			return dir_acl;
> +		acl = richacl_inherit_inode(dir_acl, inode);
> +		richacl_put(dir_acl);
> +	} else
> +		inode->i_mode &= ~current_umask();
> +	return acl;
> +}
> +EXPORT_SYMBOL_GPL(richacl_create);
> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> index 6535ce5..9bf95c2 100644
> --- a/include/linux/richacl.h
> +++ b/include/linux/richacl.h
> @@ -305,8 +305,10 @@ extern unsigned int richacl_want_to_mask(unsigned int);
>  extern void richacl_compute_max_masks(struct richacl *);
>  extern struct richacl *richacl_chmod(struct richacl *, mode_t);
>  extern int richacl_equiv_mode(const struct richacl *, mode_t *);
> +extern struct richacl *richacl_inherit(const struct richacl *, int);
>  
>  /* richacl_inode.c */
>  extern int richacl_permission(struct inode *, const struct richacl *, int);
> +extern struct richacl *richacl_create(struct inode *, struct inode *);
>  
>  #endif /* __RICHACL_H */
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 15/41] richacl: Automatic Inheritance
  2015-09-05 10:27 ` [RFC v7 15/41] richacl: Automatic Inheritance Andreas Gruenbacher
@ 2015-09-18 18:40   ` J. Bruce Fields
  2015-09-21 21:19     ` Andreas Gruenbacher
  0 siblings, 1 reply; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-18 18:40 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Sat, Sep 05, 2015 at 12:27:10PM +0200, Andreas Gruenbacher wrote:
> Automatic Inheritance (AI) allows changes to the acl of a directory to
> propagate down to children.
> 
> This is mostly implemented in user space: when a process changes the
> permissions of a directory and Automatic Inheritance is enabled for that
> directory, the process must propagate those changes to all children,
> recursively.
> 
> The kernel enables this by keeping track of which permissions have been
> inherited at file create time.  In addition, it makes sure that
> permission propagation is turned off when the permissions of a file are
> set explicitly (for example, upon create or chmod).
> 
> Automatic Inheritance works as follows:
> 
>  - When the RICHACL_AUTO_INHERIT flag in the acl of a file is not set,
>    the file is not affected by AI.
> 
>  - When the RICHACL_AUTO_INHERIT flag in the acl of a directory is set
>    and a file or subdirectory is created in that directory, the
>    inherited acl will have the RICHACL_AUTO_INHERIT flag set, and all
>    inherited aces will have the RICHACE_INHERITED_ACE flag set.  This
>    allows user space to distinguish between aces which have been
>    inherited and aces which have been explicitly added.
> 
>  - When the RICHACL_PROTECTED acl flag in the acl of a file is set, AI
>    will not modify the acl of the file.  This does not affect
>    propagation of permissions from the file to its children (if the file
>    is a directory).

In the above "file" sometimes means "any object" and somethings "a
non-directory".  I can sort it out, but more consistent terminology
would help.

> Linux does not have a way of creating files without setting the file
> permission bits, so all files created inside a directory with
> RICHACL_AUTO_INHERIT set will have the RICHACL_PROTECTED flag set.  This
> effectively disables Automatic Inheritance.
> 
> Protocols which support creating files without specifying permissions
> can explicitly clear the RICHACL_PROTECTED flag after creating a file
> and reset the file masks to "undo" applying the create mode; see
> richacl_compute_max_masks().  They should set the RICHACL_DEFAULTED
> flag.  This is a workaround; a mechanism that would allow a process to
> indicate to the kernel to ignore the create mode when there are
> inherited permissions would fix this problem.

Also, as you know: current nfsd has no way to create files without
setting permissions.  And if we were to implement that it's unclear how
many clients would actually use it (Windows clients are rare).  And of
course Samba doesn't have the interfaces it would need.

I think we should just drop this for now.  The rest of the richacl stuff
is still useful without it.

--b.

> 
> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> ---
>  fs/richacl_base.c       | 10 +++++++++-
>  fs/richacl_inode.c      |  7 +++++++
>  include/linux/richacl.h | 23 ++++++++++++++++++++++-
>  3 files changed, 38 insertions(+), 2 deletions(-)
> 
> diff --git a/fs/richacl_base.c b/fs/richacl_base.c
> index fda407d..a85b7a3 100644
> --- a/fs/richacl_base.c
> +++ b/fs/richacl_base.c
> @@ -363,7 +363,8 @@ richacl_chmod(struct richacl *acl, mode_t mode)
>  	if (acl->a_owner_mask == owner_mask &&
>  	    acl->a_group_mask == group_mask &&
>  	    acl->a_other_mask == other_mask &&
> -	    (acl->a_flags & (RICHACL_WRITE_THROUGH | RICHACL_MASKED)))
> +	    (acl->a_flags & (RICHACL_WRITE_THROUGH | RICHACL_MASKED)) &&
> +	    (!richacl_is_auto_inherit(acl) || richacl_is_protected(acl)))
>  		return acl;
>  
>  	clone = richacl_clone(acl, GFP_KERNEL);
> @@ -375,6 +376,8 @@ richacl_chmod(struct richacl *acl, mode_t mode)
>  	clone->a_owner_mask = owner_mask;
>  	clone->a_group_mask = group_mask;
>  	clone->a_other_mask = other_mask;
> +	if (richacl_is_auto_inherit(clone))
> +		clone->a_flags |= RICHACL_PROTECTED;
>  
>  	return clone;
>  }
> @@ -549,6 +552,11 @@ richacl_inherit(const struct richacl *dir_acl, int isdir)
>  			ace++;
>  		}
>  	}
> +	if (richacl_is_auto_inherit(dir_acl)) {
> +		acl->a_flags = RICHACL_AUTO_INHERIT;
> +		richacl_for_each_entry(ace, acl)
> +			ace->e_flags |= RICHACE_INHERITED_ACE;
> +	}
>  
>  	return acl;
>  }
> diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c
> index f3f1f84..df175c1 100644
> --- a/fs/richacl_inode.c
> +++ b/fs/richacl_inode.c
> @@ -247,6 +247,13 @@ richacl_inherit_inode(const struct richacl *dir_acl, struct inode *inode)
>  			richacl_put(acl);
>  			acl = NULL;
>  		} else {
> +			/*
> +			 * We need to set RICHACL_PROTECTED because we are
> +			 * doing an implicit chmod
> +			 */
> +			if (richacl_is_auto_inherit(acl))
> +				acl->a_flags |= RICHACL_PROTECTED;
> +
>  			richacl_compute_max_masks(acl);
>  			/*
>  			 * Ensure that the acl will not grant any permissions
> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> index 9bf95c2..832b06c 100644
> --- a/include/linux/richacl.h
> +++ b/include/linux/richacl.h
> @@ -53,10 +53,16 @@ struct richacl {
>  	     _ace--)
>  
>  /* a_flags values */
> +#define RICHACL_AUTO_INHERIT			0x01
> +#define RICHACL_PROTECTED			0x02
> +#define RICHACL_DEFAULTED			0x04
>  #define RICHACL_WRITE_THROUGH			0x40
>  #define RICHACL_MASKED				0x80
>  
>  #define RICHACL_VALID_FLAGS (					\
> +		RICHACL_AUTO_INHERIT |				\
> +		RICHACL_PROTECTED |				\
> +		RICHACL_DEFAULTED |				\
>  		RICHACL_WRITE_THROUGH |				\
>  		RICHACL_MASKED)
>  
> @@ -70,6 +76,7 @@ struct richacl {
>  #define RICHACE_NO_PROPAGATE_INHERIT_ACE	0x0004
>  #define RICHACE_INHERIT_ONLY_ACE		0x0008
>  #define RICHACE_IDENTIFIER_GROUP		0x0040
> +#define RICHACE_INHERITED_ACE			0x0080
>  #define RICHACE_SPECIAL_WHO			0x4000
>  
>  #define RICHACE_VALID_FLAGS (					\
> @@ -78,13 +85,15 @@ struct richacl {
>  	RICHACE_NO_PROPAGATE_INHERIT_ACE |			\
>  	RICHACE_INHERIT_ONLY_ACE |				\
>  	RICHACE_IDENTIFIER_GROUP |				\
> +	RICHACE_INHERITED_ACE |					\
>  	RICHACE_SPECIAL_WHO)
>  
>  #define RICHACE_INHERITANCE_FLAGS (				\
>  	RICHACE_FILE_INHERIT_ACE |				\
>  	RICHACE_DIRECTORY_INHERIT_ACE |				\
>  	RICHACE_NO_PROPAGATE_INHERIT_ACE |			\
> -	RICHACE_INHERIT_ONLY_ACE )
> +	RICHACE_INHERIT_ONLY_ACE |				\
> +	RICHACE_INHERITED_ACE )
>  
>  /* e_mask bitflags */
>  #define RICHACE_READ_DATA			0x00000001
> @@ -195,6 +204,18 @@ extern void set_cached_richacl(struct inode *, struct richacl *);
>  extern void forget_cached_richacl(struct inode *);
>  extern struct richacl *get_richacl(struct inode *);
>  
> +static inline int
> +richacl_is_auto_inherit(const struct richacl *acl)
> +{
> +	return acl->a_flags & RICHACL_AUTO_INHERIT;
> +}
> +
> +static inline int
> +richacl_is_protected(const struct richacl *acl)
> +{
> +	return acl->a_flags & RICHACL_PROTECTED;
> +}
> +
>  /**
>   * richace_is_owner  -  check if @ace is an OWNER@ entry
>   */
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 20/41] richacl: acl editing helper functions
  2015-09-05 10:27     ` Andreas Gruenbacher
@ 2015-09-18 18:54         ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-18 18:54 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

On Sat, Sep 05, 2015 at 12:27:15PM +0200, Andreas Gruenbacher wrote:
> The file masks in richacls make chmod and creating new files more
> efficient than having to apply file permission bits to the acl directly.
> They also allow us to regain permissions from an acl even after a
> restrictive chmod, because the permissions in the acl itself are not
> being destroyed.  In POSIX ACLs, the mask entry has a similar function.
> 
> Protocols like nfsv4 do not understand file masks.  For those protocols,
> we need to compute nfs4 acls which represent the effective permissions
> granted by a richacl: we need to "apply" the file masks to the acl.
> 
> This is the first in a series of richacl transformation patches; it
> implements basic richacl editing functions.  The following patches
> implement algorithms for transforming a richacl so that it can be
> evaluated as a plain nfs4 acl, with identical permission check results.

Reviewed-by: J. Bruce Fields <bfields-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>

Looks nicely done.--b.

> 
> Signed-off-by: Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> ---
>  fs/Makefile                    |   3 +-
>  fs/richacl_compat.c            | 155 +++++++++++++++++++++++++++++++++++++++++
>  include/linux/richacl_compat.h |  40 +++++++++++
>  3 files changed, 197 insertions(+), 1 deletion(-)
>  create mode 100644 fs/richacl_compat.c
>  create mode 100644 include/linux/richacl_compat.h
> 
> diff --git a/fs/Makefile b/fs/Makefile
> index baf385a..2d08c70 100644
> --- a/fs/Makefile
> +++ b/fs/Makefile
> @@ -48,7 +48,8 @@ obj-$(CONFIG_SYSCTL)		+= drop_caches.o
>  
>  obj-$(CONFIG_FHANDLE)		+= fhandle.o
>  obj-$(CONFIG_FS_RICHACL)	+= richacl.o
> -richacl-y			:= richacl_base.o richacl_inode.o richacl_xattr.o
> +richacl-y			:= richacl_base.o richacl_inode.o \
> +				   richacl_xattr.o richacl_compat.o
>  
>  obj-y				+= quota/
>  
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> new file mode 100644
> index 0000000..341e429
> --- /dev/null
> +++ b/fs/richacl_compat.c
> @@ -0,0 +1,155 @@
> +/*
> + * Copyright (C) 2006, 2010  Novell, Inc.
> + * Copyright (C) 2015  Red Hat, Inc.
> + * Written by Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> + *
> + * This program 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, or (at your option) any
> + * later version.
> + *
> + * This program 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.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/fs.h>
> +#include <linux/slab.h>
> +#include <linux/richacl_compat.h>
> +
> +/**
> + * richacl_prepare  -  allocate richacl being constructed
> + *
> + * Allocate a richacl which can hold @count entries but which is initially
> + * empty.
> + */
> +struct richacl *richacl_prepare(struct richacl_alloc *alloc, unsigned int count)
> +{
> +	alloc->acl = richacl_alloc(count, GFP_KERNEL);
> +	if (!alloc->acl)
> +		return NULL;
> +	alloc->acl->a_count = 0;
> +	alloc->count = count;
> +	return alloc->acl;
> +}
> +EXPORT_SYMBOL_GPL(richacl_prepare);
> +
> +/**
> + * richacl_delete_entry  -  delete an entry in an acl
> + * @alloc:	acl and number of allocated entries
> + * @ace:	an entry in @alloc->acl
> + *
> + * Updates @ace so that it points to the entry before the deleted entry
> + * on return. (When deleting the first entry, @ace will point to the
> + * (non-existent) entry before the first entry). This behavior is the
> + * expected behavior when deleting entries while forward iterating over
> + * an acl.
> + */
> +void
> +richacl_delete_entry(struct richacl_alloc *alloc, struct richace **ace)
> +{
> +	void *end = alloc->acl->a_entries + alloc->acl->a_count;
> +
> +	memmove(*ace, *ace + 1, end - (void *)(*ace + 1));
> +	(*ace)--;
> +	alloc->acl->a_count--;
> +}
> +EXPORT_SYMBOL_GPL(richacl_delete_entry);
> +
> +/**
> + * richacl_insert_entry  -  insert an entry in an acl
> + * @alloc:	acl and number of allocated entries
> + * @ace:	entry before which the new entry shall be inserted
> + *
> + * Insert a new entry in @alloc->acl at position @ace and zero-initialize
> + * it.  This may require reallocating @alloc->acl.
> + */
> +int
> +richacl_insert_entry(struct richacl_alloc *alloc, struct richace **ace)
> +{
> +	struct richacl *acl = alloc->acl;
> +	unsigned int index = *ace - acl->a_entries;
> +	size_t tail_size = (acl->a_count - index) * sizeof(struct richace);
> +
> +	if (alloc->count == acl->a_count) {
> +		size_t new_size = sizeof(struct richacl) +
> +			(acl->a_count + 1) * sizeof(struct richace);
> +
> +		acl = krealloc(acl, new_size, GFP_KERNEL);
> +		if (!acl)
> +			return -1;
> +		*ace = acl->a_entries + index;
> +		alloc->acl = acl;
> +		alloc->count++;
> +	}
> +
> +	memmove(*ace + 1, *ace, tail_size);
> +	memset(*ace, 0, sizeof(**ace));
> +	acl->a_count++;
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(richacl_insert_entry);
> +
> +/**
> + * richacl_append_entry  -  append an entry to an acl
> + * @alloc:		acl and number of allocated entries
> + *
> + * This may require reallocating @alloc->acl.
> + */
> +struct richace *richacl_append_entry(struct richacl_alloc *alloc)
> +{
> +	struct richacl *acl = alloc->acl;
> +	struct richace *ace = acl->a_entries + acl->a_count;
> +
> +	if (alloc->count > alloc->acl->a_count) {
> +		acl->a_count++;
> +		return ace;
> +	}
> +	return richacl_insert_entry(alloc, &ace) ? NULL : ace;
> +}
> +EXPORT_SYMBOL_GPL(richacl_append_entry);
> +
> +/**
> + * richace_change_mask  -  set the mask of @ace to @mask
> + * @alloc:	acl and number of allocated entries
> + * @ace:	entry to modify
> + * @mask:	new mask for @ace
> + *
> + * If @ace is inheritable, a inherit-only ace is inserted before @ace which
> + * includes the inheritable permissions of @ace and the inheritance flags of
> + * @ace are cleared before changing the mask.
> + *
> + * If @mask is 0, the original ace is turned into an inherit-only entry if
> + * there are any inheritable permissions, and removed otherwise.
> + *
> + * The returned @ace points to the modified or inserted effective-only acl
> + * entry if that entry exists, to the entry that has become inheritable-only,
> + * or else to the previous entry in the acl.
> + */
> +static int
> +richace_change_mask(struct richacl_alloc *alloc, struct richace **ace,
> +			   unsigned int mask)
> +{
> +	if (mask && (*ace)->e_mask == mask)
> +		(*ace)->e_flags &= ~RICHACE_INHERIT_ONLY_ACE;
> +	else if (mask & ~RICHACE_POSIX_ALWAYS_ALLOWED) {
> +		if (richace_is_inheritable(*ace)) {
> +			if (richacl_insert_entry(alloc, ace))
> +				return -1;
> +			richace_copy(*ace, *ace + 1);
> +			(*ace)->e_flags |= RICHACE_INHERIT_ONLY_ACE;
> +			(*ace)++;
> +			(*ace)->e_flags &= ~RICHACE_INHERITANCE_FLAGS |
> +					   RICHACE_INHERITED_ACE;
> +		}
> +		(*ace)->e_mask = mask;
> +	} else {
> +		if (richace_is_inheritable(*ace))
> +			(*ace)->e_flags |= RICHACE_INHERIT_ONLY_ACE;
> +		else
> +			richacl_delete_entry(alloc, ace);
> +	}
> +	return 0;
> +}
> diff --git a/include/linux/richacl_compat.h b/include/linux/richacl_compat.h
> new file mode 100644
> index 0000000..a9ff630
> --- /dev/null
> +++ b/include/linux/richacl_compat.h
> @@ -0,0 +1,40 @@
> +/*
> + * Copyright (C) 2015  Red Hat, Inc.
> + * Written by Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> + *
> + * This program 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, or (at your option) any
> + * later version.
> + *
> + * This program 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.
> + */
> +
> +#ifndef __RICHACL_COMPAT_H
> +#define __RICHACL_COMPAT_H
> +
> +#include <linux/richacl.h>
> +
> +/**
> + * struct richacl_alloc  -  remember how many entries are actually allocated
> + * @acl:	acl with a_count <= @count
> + * @count:	the actual number of entries allocated in @acl
> + *
> + * We pass around this structure while modifying an acl so that we do
> + * not have to reallocate when we remove existing entries followed by
> + * adding new entries.
> + */
> +struct richacl_alloc {
> +	struct richacl *acl;
> +	unsigned int count;
> +};
> +
> +struct richacl *richacl_prepare(struct richacl_alloc *, unsigned int);
> +struct richace *richacl_append_entry(struct richacl_alloc *);
> +int richacl_insert_entry(struct richacl_alloc *, struct richace **);
> +void richacl_delete_entry(struct richacl_alloc *, struct richace **);
> +
> +#endif  /* __RICHACL_COMPAT_H */
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 20/41] richacl: acl editing helper functions
@ 2015-09-18 18:54         ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-18 18:54 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

On Sat, Sep 05, 2015 at 12:27:15PM +0200, Andreas Gruenbacher wrote:
> The file masks in richacls make chmod and creating new files more
> efficient than having to apply file permission bits to the acl directly.
> They also allow us to regain permissions from an acl even after a
> restrictive chmod, because the permissions in the acl itself are not
> being destroyed.  In POSIX ACLs, the mask entry has a similar function.
> 
> Protocols like nfsv4 do not understand file masks.  For those protocols,
> we need to compute nfs4 acls which represent the effective permissions
> granted by a richacl: we need to "apply" the file masks to the acl.
> 
> This is the first in a series of richacl transformation patches; it
> implements basic richacl editing functions.  The following patches
> implement algorithms for transforming a richacl so that it can be
> evaluated as a plain nfs4 acl, with identical permission check results.

Reviewed-by: J. Bruce Fields <bfields@redhat.com>

Looks nicely done.--b.

> 
> Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
> ---
>  fs/Makefile                    |   3 +-
>  fs/richacl_compat.c            | 155 +++++++++++++++++++++++++++++++++++++++++
>  include/linux/richacl_compat.h |  40 +++++++++++
>  3 files changed, 197 insertions(+), 1 deletion(-)
>  create mode 100644 fs/richacl_compat.c
>  create mode 100644 include/linux/richacl_compat.h
> 
> diff --git a/fs/Makefile b/fs/Makefile
> index baf385a..2d08c70 100644
> --- a/fs/Makefile
> +++ b/fs/Makefile
> @@ -48,7 +48,8 @@ obj-$(CONFIG_SYSCTL)		+= drop_caches.o
>  
>  obj-$(CONFIG_FHANDLE)		+= fhandle.o
>  obj-$(CONFIG_FS_RICHACL)	+= richacl.o
> -richacl-y			:= richacl_base.o richacl_inode.o richacl_xattr.o
> +richacl-y			:= richacl_base.o richacl_inode.o \
> +				   richacl_xattr.o richacl_compat.o
>  
>  obj-y				+= quota/
>  
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> new file mode 100644
> index 0000000..341e429
> --- /dev/null
> +++ b/fs/richacl_compat.c
> @@ -0,0 +1,155 @@
> +/*
> + * Copyright (C) 2006, 2010  Novell, Inc.
> + * Copyright (C) 2015  Red Hat, Inc.
> + * Written by Andreas Gruenbacher <agruen@kernel.org>
> + *
> + * This program 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, or (at your option) any
> + * later version.
> + *
> + * This program 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.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/fs.h>
> +#include <linux/slab.h>
> +#include <linux/richacl_compat.h>
> +
> +/**
> + * richacl_prepare  -  allocate richacl being constructed
> + *
> + * Allocate a richacl which can hold @count entries but which is initially
> + * empty.
> + */
> +struct richacl *richacl_prepare(struct richacl_alloc *alloc, unsigned int count)
> +{
> +	alloc->acl = richacl_alloc(count, GFP_KERNEL);
> +	if (!alloc->acl)
> +		return NULL;
> +	alloc->acl->a_count = 0;
> +	alloc->count = count;
> +	return alloc->acl;
> +}
> +EXPORT_SYMBOL_GPL(richacl_prepare);
> +
> +/**
> + * richacl_delete_entry  -  delete an entry in an acl
> + * @alloc:	acl and number of allocated entries
> + * @ace:	an entry in @alloc->acl
> + *
> + * Updates @ace so that it points to the entry before the deleted entry
> + * on return. (When deleting the first entry, @ace will point to the
> + * (non-existent) entry before the first entry). This behavior is the
> + * expected behavior when deleting entries while forward iterating over
> + * an acl.
> + */
> +void
> +richacl_delete_entry(struct richacl_alloc *alloc, struct richace **ace)
> +{
> +	void *end = alloc->acl->a_entries + alloc->acl->a_count;
> +
> +	memmove(*ace, *ace + 1, end - (void *)(*ace + 1));
> +	(*ace)--;
> +	alloc->acl->a_count--;
> +}
> +EXPORT_SYMBOL_GPL(richacl_delete_entry);
> +
> +/**
> + * richacl_insert_entry  -  insert an entry in an acl
> + * @alloc:	acl and number of allocated entries
> + * @ace:	entry before which the new entry shall be inserted
> + *
> + * Insert a new entry in @alloc->acl at position @ace and zero-initialize
> + * it.  This may require reallocating @alloc->acl.
> + */
> +int
> +richacl_insert_entry(struct richacl_alloc *alloc, struct richace **ace)
> +{
> +	struct richacl *acl = alloc->acl;
> +	unsigned int index = *ace - acl->a_entries;
> +	size_t tail_size = (acl->a_count - index) * sizeof(struct richace);
> +
> +	if (alloc->count == acl->a_count) {
> +		size_t new_size = sizeof(struct richacl) +
> +			(acl->a_count + 1) * sizeof(struct richace);
> +
> +		acl = krealloc(acl, new_size, GFP_KERNEL);
> +		if (!acl)
> +			return -1;
> +		*ace = acl->a_entries + index;
> +		alloc->acl = acl;
> +		alloc->count++;
> +	}
> +
> +	memmove(*ace + 1, *ace, tail_size);
> +	memset(*ace, 0, sizeof(**ace));
> +	acl->a_count++;
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(richacl_insert_entry);
> +
> +/**
> + * richacl_append_entry  -  append an entry to an acl
> + * @alloc:		acl and number of allocated entries
> + *
> + * This may require reallocating @alloc->acl.
> + */
> +struct richace *richacl_append_entry(struct richacl_alloc *alloc)
> +{
> +	struct richacl *acl = alloc->acl;
> +	struct richace *ace = acl->a_entries + acl->a_count;
> +
> +	if (alloc->count > alloc->acl->a_count) {
> +		acl->a_count++;
> +		return ace;
> +	}
> +	return richacl_insert_entry(alloc, &ace) ? NULL : ace;
> +}
> +EXPORT_SYMBOL_GPL(richacl_append_entry);
> +
> +/**
> + * richace_change_mask  -  set the mask of @ace to @mask
> + * @alloc:	acl and number of allocated entries
> + * @ace:	entry to modify
> + * @mask:	new mask for @ace
> + *
> + * If @ace is inheritable, a inherit-only ace is inserted before @ace which
> + * includes the inheritable permissions of @ace and the inheritance flags of
> + * @ace are cleared before changing the mask.
> + *
> + * If @mask is 0, the original ace is turned into an inherit-only entry if
> + * there are any inheritable permissions, and removed otherwise.
> + *
> + * The returned @ace points to the modified or inserted effective-only acl
> + * entry if that entry exists, to the entry that has become inheritable-only,
> + * or else to the previous entry in the acl.
> + */
> +static int
> +richace_change_mask(struct richacl_alloc *alloc, struct richace **ace,
> +			   unsigned int mask)
> +{
> +	if (mask && (*ace)->e_mask == mask)
> +		(*ace)->e_flags &= ~RICHACE_INHERIT_ONLY_ACE;
> +	else if (mask & ~RICHACE_POSIX_ALWAYS_ALLOWED) {
> +		if (richace_is_inheritable(*ace)) {
> +			if (richacl_insert_entry(alloc, ace))
> +				return -1;
> +			richace_copy(*ace, *ace + 1);
> +			(*ace)->e_flags |= RICHACE_INHERIT_ONLY_ACE;
> +			(*ace)++;
> +			(*ace)->e_flags &= ~RICHACE_INHERITANCE_FLAGS |
> +					   RICHACE_INHERITED_ACE;
> +		}
> +		(*ace)->e_mask = mask;
> +	} else {
> +		if (richace_is_inheritable(*ace))
> +			(*ace)->e_flags |= RICHACE_INHERIT_ONLY_ACE;
> +		else
> +			richacl_delete_entry(alloc, ace);
> +	}
> +	return 0;
> +}
> diff --git a/include/linux/richacl_compat.h b/include/linux/richacl_compat.h
> new file mode 100644
> index 0000000..a9ff630
> --- /dev/null
> +++ b/include/linux/richacl_compat.h
> @@ -0,0 +1,40 @@
> +/*
> + * Copyright (C) 2015  Red Hat, Inc.
> + * Written by Andreas Gruenbacher <agruenba@redhat.com>
> + *
> + * This program 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, or (at your option) any
> + * later version.
> + *
> + * This program 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.
> + */
> +
> +#ifndef __RICHACL_COMPAT_H
> +#define __RICHACL_COMPAT_H
> +
> +#include <linux/richacl.h>
> +
> +/**
> + * struct richacl_alloc  -  remember how many entries are actually allocated
> + * @acl:	acl with a_count <= @count
> + * @count:	the actual number of entries allocated in @acl
> + *
> + * We pass around this structure while modifying an acl so that we do
> + * not have to reallocate when we remove existing entries followed by
> + * adding new entries.
> + */
> +struct richacl_alloc {
> +	struct richacl *acl;
> +	unsigned int count;
> +};
> +
> +struct richacl *richacl_prepare(struct richacl_alloc *, unsigned int);
> +struct richace *richacl_append_entry(struct richacl_alloc *);
> +int richacl_insert_entry(struct richacl_alloc *, struct richace **);
> +void richacl_delete_entry(struct richacl_alloc *, struct richace **);
> +
> +#endif  /* __RICHACL_COMPAT_H */
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 21/41] richacl: Move everyone@ aces down the acl
  2015-09-05 10:27 ` [RFC v7 21/41] richacl: Move everyone@ aces down the acl Andreas Gruenbacher
@ 2015-09-18 19:35   ` J. Bruce Fields
       [not found]     ` <20150918193524.GA22671-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
  0 siblings, 1 reply; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-18 19:35 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

On Sat, Sep 05, 2015 at 12:27:16PM +0200, Andreas Gruenbacher wrote:
> The POSIX standard puts processes which are not the owner or a member in
> the owning group or which match any ace other then everyone@ on the
> other file class.  We only know if a process is in the other class after
> processing the entire acl.
> 
> Move all everyone@ aces in the acl down in the acl so that at most a
> single everyone@ allow ace remains at the end.  Permissions which are
> not explicitly allowed are implicitly denied, so an everyone@ deny ace
> is unneeded.
> 
> The everyone@ aces can be moved down the acl without changing the
> permissions that the acl grants.  This transformation simplifies the
> following algorithms, and eventually allows us to turn the final
> everyone@ allow ace into an entry for the other class.
> 
> Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
> ---
>  fs/richacl_compat.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 65 insertions(+)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 341e429..4f0acf5 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -153,3 +153,68 @@ richace_change_mask(struct richacl_alloc *alloc, struct richace **ace,
>  	}
>  	return 0;
>  }
> +
> +/**
> + * richacl_move_everyone_aces_down  -  move everyone@ aces to the end of the acl
> + * @alloc:	acl and number of allocated entries
> + *
> + * Move all everyone aces to the end of the acl so that only a single everyone@
> + * allow ace remains at the end, and update the mask fields of all aces on the
> + * way.  The last ace of the resulting acl will be an everyone@ allow ace only
> + * if @acl grants any permissions to @everyone.  No @everyone deny aces will
> + * remain.
> + *
> + * This transformation does not alter the permissions that the acl grants.
> + * Having at most one everyone@ allow ace at the end of the acl helps us in the
> + * following algorithms.
> + */
> +static int
> +richacl_move_everyone_aces_down(struct richacl_alloc *alloc)
> +{
> +	struct richace *ace;
> +	unsigned int allowed = 0, denied = 0;
> +
> +	richacl_for_each_entry(ace, alloc->acl) {
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_everyone(ace)) {
> +			if (richace_is_allow(ace))
> +				allowed |= (ace->e_mask & ~denied);
> +			else if (richace_is_deny(ace))
> +				denied |= (ace->e_mask & ~allowed);
> +			else
> +				continue;
> +			if (richace_change_mask(alloc, &ace, 0))
> +				return -1;
> +		} else {
> +			if (richace_is_allow(ace)) {
> +				if (richace_change_mask(alloc, &ace, allowed |
> +						(ace->e_mask & ~denied)))
> +					return -1;
> +			} else if (richace_is_deny(ace)) {
> +				if (richace_change_mask(alloc, &ace, denied |
> +						(ace->e_mask & ~allowed)))
> +					return -1;
> +			}
> +		}
> +	}
> +	if (allowed & ~RICHACE_POSIX_ALWAYS_ALLOWED) {
> +		struct richace *last_ace = ace - 1;
> +
> +		if (alloc->acl->a_entries &&
> +		    richace_is_everyone(last_ace) &&
> +		    richace_is_allow(last_ace) &&
> +		    richace_is_inherit_only(last_ace) &&
> +		    last_ace->e_mask == allowed)
> +			last_ace->e_flags &= ~RICHACE_INHERIT_ONLY_ACE;

That's a funny special case!  Is it even worth it, or could we just live
with an extra uninheritable EVERYONE ace in this case?

Anyway, again I like the way you've set this all up with the little
acl-editing helpers, it makes this easier to follow than it otherwise
would be....

	Reviewed-by: J. Bruce Fields <bfields@redhat.com>

--b.

> +		else {
> +			if (richacl_insert_entry(alloc, &ace))
> +				return -1;
> +			ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +			ace->e_flags = RICHACE_SPECIAL_WHO;
> +			ace->e_mask = allowed;
> +			ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
> +		}
> +	}
> +	return 0;
> +}
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 22/41] richacl: Propagate everyone@ permissions to other aces
  2015-09-05 10:27     ` Andreas Gruenbacher
@ 2015-09-18 21:36         ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-18 21:36 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

On Sat, Sep 05, 2015 at 12:27:17PM +0200, Andreas Gruenbacher wrote:
> The trailing everyone@ allow ace can grant permissions to all file
> classes including the owner and group class.  Before we can apply the
> other mask to this entry to turn it into an "other class" entry, we need
> to ensure that members of the owner or group class will not lose any
> permissions from that ace.
> 
> Conceptually, we do this by inserting additional <who>:<allow>::allow
> entries before the trailing everyone@ allow ace with the same
> permissions as the trailing everyone@ allow ace for owner@, group@, and
> all explicitly mentioned users and groups.  (In practice, we will rarely
> need to insert any additional aces in this step.)
> 
> Signed-off-by: Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> ---
>  fs/richacl_compat.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 195 insertions(+)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 4f0acf5..9b76fc0 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -218,3 +218,198 @@ richacl_move_everyone_aces_down(struct richacl_alloc *alloc)
>  	}
>  	return 0;
>  }
> +
> +/**
> + * __richacl_propagate_everyone  -  propagate everyone@ permissions up for @who
> + * @alloc:	acl and number of allocated entries
> + * @who:	identifier to propagate permissions for
> + * @allow:	permissions to propagate up
> + *
> + * Propagate the permissions in @allow up from the end of the acl to the start
> + * for the specified principal @who.
> + *
> + * The simplest possible approach to achieve this would be to insert a
> + * "<who>:<allow>::allow" ace before the final everyone@ allow ace.  Since this
> + * would often result in aces which are not needed or which could be merged
> + * with an existing ace, we make the following optimizations:
> + *
> + *   - We go through the acl and determine which permissions are already
> + *     allowed or denied to @who, and we remove those permissions from
> + *     @allow.
> + *
> + *   - If the acl contains an allow ace for @who and no aces after this entry
> + *     deny permissions in @allow, we add the permissions in @allow to this
> + *     ace.  (Propagating permissions across a deny ace which can match the
> + *     process can elevate permissions.)
> + *
> + * This transformation does not alter the permissions that the acl grants.
> + */
> +static int
> +__richacl_propagate_everyone(struct richacl_alloc *alloc, struct richace *who,
> +			     unsigned int allow)
> +{
> +	struct richace *allow_last = NULL, *ace;
> +	struct richacl *acl = alloc->acl;
> +
> +	/*
> +	 * Remove the permissions from allow that are already determined for
> +	 * this who value, and figure out if there is an allow entry for
> +	 * this who value that is "reachable" from the trailing everyone@
> +	 * allow ace.
> +	 */
> +	richacl_for_each_entry(ace, acl) {
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_allow(ace)) {
> +			if (richace_is_same_identifier(ace, who)) {
> +				allow &= ~ace->e_mask;
> +				allow_last = ace;
> +			}
> +		} else if (richace_is_deny(ace)) {
> +			if (richace_is_same_identifier(ace, who))
> +				allow &= ~ace->e_mask;
> +			else if (allow & ace->e_mask)
> +				allow_last = NULL;
> +		}
> +	}
> +	ace--;
> +
> +	/*
> +	 * If for group class entries, all the remaining permissions will
> +	 * remain granted by the trailing everyone@ ace, no additional entry is
> +	 * needed.
> +	 */
> +	if (!richace_is_owner(who) &&
> +	    richace_is_everyone(ace) && richace_is_allow(ace) &&

That richace_is_allow(ace) check is redundant at this point, isn't it?

> +	    !(allow & ~(ace->e_mask & acl->a_other_mask)))

Uh, I wish C had a subset-of operator, that construct took me longer to
work out than I should admit.

> +		allow = 0;
> +
> +	if (allow) {
> +		if (allow_last)
> +			return richace_change_mask(alloc, &allow_last,
> +						   allow_last->e_mask | allow);
> +		else {
> +			struct richace who_copy;
> +
> +			richace_copy(&who_copy, who);
> +			ace = acl->a_entries + acl->a_count - 1;

Isn't ace already set to the last ace?

--b.

> +			if (richacl_insert_entry(alloc, &ace))
> +				return -1;
> +			richace_copy(ace, &who_copy);
> +			ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +			ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
> +			ace->e_mask = allow;
> +		}
> +	}
> +	return 0;
> +}
> +
> +/**
> + * richacl_propagate_everyone  -  propagate everyone@ permissions up the acl
> + * @alloc:	acl and number of allocated entries
> + *
> + * Make sure that group@ and all other users and groups mentioned in the acl
> + * will not lose any permissions when finally applying the other mask to the
> + * everyone@ allow ace at the end of the acl.  We modify the permissions of
> + * existing entries or add new entries before the final everyone@ allow ace to
> + * achieve that.
> + *
> + * For example, the following acl implicitly grants everyone rwpx access:
> + *
> + *    joe:r::allow
> + *    everyone@:rwpx::allow
> + *
> + * When applying mode 0660 to this acl, group@ would lose rwp access, and joe
> + * would lose wp access even though the mode does not exclude those
> + * permissions.  After propagating the everyone@ permissions, the result for
> + * applying mode 0660 becomes:
> + *
> + *    owner@:rwp::allow
> + *    joe:rwp::allow
> + *    group@:rwp::allow
> + *
> + * Deny aces complicate the matter.  For example, the following acl grants
> + * everyone but joe write access:
> + *
> + *    joe:wp::deny
> + *    everyone@:rwpx::allow
> + *
> + * When applying mode 0660 to this acl, group@ would lose rwp access, and joe
> + * would lose r access.  After propagating the everyone@ permissions, the
> + * result for applying mode 0660 becomes:
> + *
> + *    owner@:rwp::allow
> + *    joe:w::deny
> + *    group@:rwp::allow
> + *    joe:r::allow
> + */
> +static int
> +richacl_propagate_everyone(struct richacl_alloc *alloc)
> +{
> +	struct richace who = { .e_flags = RICHACE_SPECIAL_WHO };
> +	struct richacl *acl = alloc->acl;
> +	struct richace *ace;
> +	unsigned int owner_allow, group_allow;
> +
> +	/*
> +	 * If the owner mask contains permissions which are not in the group
> +	 * mask, the group mask contains permissions which are not in the other
> +	 * mask, or the owner class contains permissions which are not in the
> +	 * other mask, we may need to propagate permissions up from the
> +	 * everyone@ allow ace.  The third condition is implied by the first
> +	 * two.
> +	 */
> +	if (!((acl->a_owner_mask & ~acl->a_group_mask) ||
> +	      (acl->a_group_mask & ~acl->a_other_mask)))
> +		return 0;
> +	if (!acl->a_count)
> +		return 0;
> +	ace = acl->a_entries + acl->a_count - 1;
> +	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
> +		return 0;
> +
> +	owner_allow = ace->e_mask & acl->a_owner_mask;
> +	group_allow = ace->e_mask & acl->a_group_mask;
> +
> +	if (owner_allow & ~(acl->a_group_mask & acl->a_other_mask)) {
> +		/* Propagate everyone@ permissions through to owner@. */
> +		who.e_id.special = RICHACE_OWNER_SPECIAL_ID;
> +		if (__richacl_propagate_everyone(alloc, &who, owner_allow))
> +			return -1;
> +		acl = alloc->acl;
> +	}
> +
> +	if (group_allow & ~acl->a_other_mask) {
> +		int n;
> +
> +		/* Propagate everyone@ permissions through to group@. */
> +		who.e_id.special = RICHACE_GROUP_SPECIAL_ID;
> +		if (__richacl_propagate_everyone(alloc, &who, group_allow))
> +			return -1;
> +		acl = alloc->acl;
> +
> +		/*
> +		 * Start from the entry before the trailing everyone@ allow
> +		 * entry. We will not hit everyone@ entries in the loop.
> +		 */
> +		for (n = acl->a_count - 2; n != -1; n--) {
> +			ace = acl->a_entries + n;
> +
> +			if (richace_is_inherit_only(ace) ||
> +			    richace_is_owner(ace) ||
> +			    richace_is_group(ace))
> +				continue;
> +			if (richace_is_allow(ace) || richace_is_deny(ace)) {
> +				/*
> +				 * Any inserted entry will end up below the
> +				 * current entry
> +				 */
> +				if (__richacl_propagate_everyone(alloc, ace,
> +								 group_allow))
> +					return -1;
> +				acl = alloc->acl;
> +			}
> +		}
> +	}
> +	return 0;
> +}
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 22/41] richacl: Propagate everyone@ permissions to other aces
@ 2015-09-18 21:36         ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-18 21:36 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

On Sat, Sep 05, 2015 at 12:27:17PM +0200, Andreas Gruenbacher wrote:
> The trailing everyone@ allow ace can grant permissions to all file
> classes including the owner and group class.  Before we can apply the
> other mask to this entry to turn it into an "other class" entry, we need
> to ensure that members of the owner or group class will not lose any
> permissions from that ace.
> 
> Conceptually, we do this by inserting additional <who>:<allow>::allow
> entries before the trailing everyone@ allow ace with the same
> permissions as the trailing everyone@ allow ace for owner@, group@, and
> all explicitly mentioned users and groups.  (In practice, we will rarely
> need to insert any additional aces in this step.)
> 
> Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
> ---
>  fs/richacl_compat.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 195 insertions(+)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 4f0acf5..9b76fc0 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -218,3 +218,198 @@ richacl_move_everyone_aces_down(struct richacl_alloc *alloc)
>  	}
>  	return 0;
>  }
> +
> +/**
> + * __richacl_propagate_everyone  -  propagate everyone@ permissions up for @who
> + * @alloc:	acl and number of allocated entries
> + * @who:	identifier to propagate permissions for
> + * @allow:	permissions to propagate up
> + *
> + * Propagate the permissions in @allow up from the end of the acl to the start
> + * for the specified principal @who.
> + *
> + * The simplest possible approach to achieve this would be to insert a
> + * "<who>:<allow>::allow" ace before the final everyone@ allow ace.  Since this
> + * would often result in aces which are not needed or which could be merged
> + * with an existing ace, we make the following optimizations:
> + *
> + *   - We go through the acl and determine which permissions are already
> + *     allowed or denied to @who, and we remove those permissions from
> + *     @allow.
> + *
> + *   - If the acl contains an allow ace for @who and no aces after this entry
> + *     deny permissions in @allow, we add the permissions in @allow to this
> + *     ace.  (Propagating permissions across a deny ace which can match the
> + *     process can elevate permissions.)
> + *
> + * This transformation does not alter the permissions that the acl grants.
> + */
> +static int
> +__richacl_propagate_everyone(struct richacl_alloc *alloc, struct richace *who,
> +			     unsigned int allow)
> +{
> +	struct richace *allow_last = NULL, *ace;
> +	struct richacl *acl = alloc->acl;
> +
> +	/*
> +	 * Remove the permissions from allow that are already determined for
> +	 * this who value, and figure out if there is an allow entry for
> +	 * this who value that is "reachable" from the trailing everyone@
> +	 * allow ace.
> +	 */
> +	richacl_for_each_entry(ace, acl) {
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_allow(ace)) {
> +			if (richace_is_same_identifier(ace, who)) {
> +				allow &= ~ace->e_mask;
> +				allow_last = ace;
> +			}
> +		} else if (richace_is_deny(ace)) {
> +			if (richace_is_same_identifier(ace, who))
> +				allow &= ~ace->e_mask;
> +			else if (allow & ace->e_mask)
> +				allow_last = NULL;
> +		}
> +	}
> +	ace--;
> +
> +	/*
> +	 * If for group class entries, all the remaining permissions will
> +	 * remain granted by the trailing everyone@ ace, no additional entry is
> +	 * needed.
> +	 */
> +	if (!richace_is_owner(who) &&
> +	    richace_is_everyone(ace) && richace_is_allow(ace) &&

That richace_is_allow(ace) check is redundant at this point, isn't it?

> +	    !(allow & ~(ace->e_mask & acl->a_other_mask)))

Uh, I wish C had a subset-of operator, that construct took me longer to
work out than I should admit.

> +		allow = 0;
> +
> +	if (allow) {
> +		if (allow_last)
> +			return richace_change_mask(alloc, &allow_last,
> +						   allow_last->e_mask | allow);
> +		else {
> +			struct richace who_copy;
> +
> +			richace_copy(&who_copy, who);
> +			ace = acl->a_entries + acl->a_count - 1;

Isn't ace already set to the last ace?

--b.

> +			if (richacl_insert_entry(alloc, &ace))
> +				return -1;
> +			richace_copy(ace, &who_copy);
> +			ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +			ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
> +			ace->e_mask = allow;
> +		}
> +	}
> +	return 0;
> +}
> +
> +/**
> + * richacl_propagate_everyone  -  propagate everyone@ permissions up the acl
> + * @alloc:	acl and number of allocated entries
> + *
> + * Make sure that group@ and all other users and groups mentioned in the acl
> + * will not lose any permissions when finally applying the other mask to the
> + * everyone@ allow ace at the end of the acl.  We modify the permissions of
> + * existing entries or add new entries before the final everyone@ allow ace to
> + * achieve that.
> + *
> + * For example, the following acl implicitly grants everyone rwpx access:
> + *
> + *    joe:r::allow
> + *    everyone@:rwpx::allow
> + *
> + * When applying mode 0660 to this acl, group@ would lose rwp access, and joe
> + * would lose wp access even though the mode does not exclude those
> + * permissions.  After propagating the everyone@ permissions, the result for
> + * applying mode 0660 becomes:
> + *
> + *    owner@:rwp::allow
> + *    joe:rwp::allow
> + *    group@:rwp::allow
> + *
> + * Deny aces complicate the matter.  For example, the following acl grants
> + * everyone but joe write access:
> + *
> + *    joe:wp::deny
> + *    everyone@:rwpx::allow
> + *
> + * When applying mode 0660 to this acl, group@ would lose rwp access, and joe
> + * would lose r access.  After propagating the everyone@ permissions, the
> + * result for applying mode 0660 becomes:
> + *
> + *    owner@:rwp::allow
> + *    joe:w::deny
> + *    group@:rwp::allow
> + *    joe:r::allow
> + */
> +static int
> +richacl_propagate_everyone(struct richacl_alloc *alloc)
> +{
> +	struct richace who = { .e_flags = RICHACE_SPECIAL_WHO };
> +	struct richacl *acl = alloc->acl;
> +	struct richace *ace;
> +	unsigned int owner_allow, group_allow;
> +
> +	/*
> +	 * If the owner mask contains permissions which are not in the group
> +	 * mask, the group mask contains permissions which are not in the other
> +	 * mask, or the owner class contains permissions which are not in the
> +	 * other mask, we may need to propagate permissions up from the
> +	 * everyone@ allow ace.  The third condition is implied by the first
> +	 * two.
> +	 */
> +	if (!((acl->a_owner_mask & ~acl->a_group_mask) ||
> +	      (acl->a_group_mask & ~acl->a_other_mask)))
> +		return 0;
> +	if (!acl->a_count)
> +		return 0;
> +	ace = acl->a_entries + acl->a_count - 1;
> +	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
> +		return 0;
> +
> +	owner_allow = ace->e_mask & acl->a_owner_mask;
> +	group_allow = ace->e_mask & acl->a_group_mask;
> +
> +	if (owner_allow & ~(acl->a_group_mask & acl->a_other_mask)) {
> +		/* Propagate everyone@ permissions through to owner@. */
> +		who.e_id.special = RICHACE_OWNER_SPECIAL_ID;
> +		if (__richacl_propagate_everyone(alloc, &who, owner_allow))
> +			return -1;
> +		acl = alloc->acl;
> +	}
> +
> +	if (group_allow & ~acl->a_other_mask) {
> +		int n;
> +
> +		/* Propagate everyone@ permissions through to group@. */
> +		who.e_id.special = RICHACE_GROUP_SPECIAL_ID;
> +		if (__richacl_propagate_everyone(alloc, &who, group_allow))
> +			return -1;
> +		acl = alloc->acl;
> +
> +		/*
> +		 * Start from the entry before the trailing everyone@ allow
> +		 * entry. We will not hit everyone@ entries in the loop.
> +		 */
> +		for (n = acl->a_count - 2; n != -1; n--) {
> +			ace = acl->a_entries + n;
> +
> +			if (richace_is_inherit_only(ace) ||
> +			    richace_is_owner(ace) ||
> +			    richace_is_group(ace))
> +				continue;
> +			if (richace_is_allow(ace) || richace_is_deny(ace)) {
> +				/*
> +				 * Any inserted entry will end up below the
> +				 * current entry
> +				 */
> +				if (__richacl_propagate_everyone(alloc, ace,
> +								 group_allow))
> +					return -1;
> +				acl = alloc->acl;
> +			}
> +		}
> +	}
> +	return 0;
> +}
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 22/41] richacl: Propagate everyone@ permissions to other aces
  2015-09-05 10:27     ` Andreas Gruenbacher
@ 2015-09-18 21:56         ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-18 21:56 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

On Sat, Sep 05, 2015 at 12:27:17PM +0200, Andreas Gruenbacher wrote:
> The trailing everyone@ allow ace can grant permissions to all file
> classes including the owner and group class.  Before we can apply the
> other mask to this entry to turn it into an "other class" entry, we need
> to ensure that members of the owner or group class will not lose any
> permissions from that ace.
> 
> Conceptually, we do this by inserting additional <who>:<allow>::allow
> entries before the trailing everyone@ allow ace with the same
> permissions as the trailing everyone@ allow ace for owner@, group@, and
> all explicitly mentioned users and groups.  (In practice, we will rarely
> need to insert any additional aces in this step.)
> 
> Signed-off-by: Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> ---
>  fs/richacl_compat.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 195 insertions(+)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 4f0acf5..9b76fc0 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -218,3 +218,198 @@ richacl_move_everyone_aces_down(struct richacl_alloc *alloc)
>  	}
>  	return 0;
>  }
> +
> +/**
> + * __richacl_propagate_everyone  -  propagate everyone@ permissions up for @who
> + * @alloc:	acl and number of allocated entries
> + * @who:	identifier to propagate permissions for
> + * @allow:	permissions to propagate up
> + *
> + * Propagate the permissions in @allow up from the end of the acl to the start
> + * for the specified principal @who.
> + *
> + * The simplest possible approach to achieve this would be to insert a
> + * "<who>:<allow>::allow" ace before the final everyone@ allow ace.  Since this
> + * would often result in aces which are not needed or which could be merged
> + * with an existing ace, we make the following optimizations:
> + *
> + *   - We go through the acl and determine which permissions are already
> + *     allowed or denied to @who, and we remove those permissions from
> + *     @allow.
> + *
> + *   - If the acl contains an allow ace for @who and no aces after this entry
> + *     deny permissions in @allow, we add the permissions in @allow to this
> + *     ace.  (Propagating permissions across a deny ace which can match the
> + *     process can elevate permissions.)
> + *
> + * This transformation does not alter the permissions that the acl grants.
> + */
> +static int
> +__richacl_propagate_everyone(struct richacl_alloc *alloc, struct richace *who,
> +			     unsigned int allow)
> +{
> +	struct richace *allow_last = NULL, *ace;
> +	struct richacl *acl = alloc->acl;
> +
> +	/*
> +	 * Remove the permissions from allow that are already determined for
> +	 * this who value, and figure out if there is an allow entry for
> +	 * this who value that is "reachable" from the trailing everyone@
> +	 * allow ace.
> +	 */
> +	richacl_for_each_entry(ace, acl) {
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_allow(ace)) {
> +			if (richace_is_same_identifier(ace, who)) {
> +				allow &= ~ace->e_mask;
> +				allow_last = ace;
> +			}
> +		} else if (richace_is_deny(ace)) {
> +			if (richace_is_same_identifier(ace, who))
> +				allow &= ~ace->e_mask;
> +			else if (allow & ace->e_mask)
> +				allow_last = NULL;
> +		}
> +	}
> +	ace--;
> +
> +	/*
> +	 * If for group class entries, all the remaining permissions will
> +	 * remain granted by the trailing everyone@ ace, no additional entry is
> +	 * needed.
> +	 */
> +	if (!richace_is_owner(who) &&
> +	    richace_is_everyone(ace) && richace_is_allow(ace) &&
> +	    !(allow & ~(ace->e_mask & acl->a_other_mask)))
> +		allow = 0;
> +
> +	if (allow) {
> +		if (allow_last)
> +			return richace_change_mask(alloc, &allow_last,
> +						   allow_last->e_mask | allow);
> +		else {
> +			struct richace who_copy;
> +
> +			richace_copy(&who_copy, who);
> +			ace = acl->a_entries + acl->a_count - 1;
> +			if (richacl_insert_entry(alloc, &ace))
> +				return -1;
> +			richace_copy(ace, &who_copy);
> +			ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +			ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
> +			ace->e_mask = allow;
> +		}
> +	}
> +	return 0;
> +}
> +
> +/**
> + * richacl_propagate_everyone  -  propagate everyone@ permissions up the acl
> + * @alloc:	acl and number of allocated entries
> + *
> + * Make sure that group@ and all other users and groups mentioned in the acl
> + * will not lose any permissions when finally applying the other mask to the
> + * everyone@ allow ace at the end of the acl.  We modify the permissions of
> + * existing entries or add new entries before the final everyone@ allow ace to
> + * achieve that.
> + *
> + * For example, the following acl implicitly grants everyone rwpx access:
> + *
> + *    joe:r::allow
> + *    everyone@:rwpx::allow
> + *
> + * When applying mode 0660 to this acl, group@ would lose rwp access, and joe
> + * would lose wp access even though the mode does not exclude those
> + * permissions.  After propagating the everyone@ permissions, the result for
> + * applying mode 0660 becomes:
> + *
> + *    owner@:rwp::allow
> + *    joe:rwp::allow
> + *    group@:rwp::allow
> + *
> + * Deny aces complicate the matter.  For example, the following acl grants
> + * everyone but joe write access:
> + *
> + *    joe:wp::deny
> + *    everyone@:rwpx::allow
> + *
> + * When applying mode 0660 to this acl, group@ would lose rwp access, and joe
> + * would lose r access.  After propagating the everyone@ permissions, the
> + * result for applying mode 0660 becomes:
> + *
> + *    owner@:rwp::allow
> + *    joe:w::deny
> + *    group@:rwp::allow
> + *    joe:r::allow
> + */
> +static int
> +richacl_propagate_everyone(struct richacl_alloc *alloc)
> +{
> +	struct richace who = { .e_flags = RICHACE_SPECIAL_WHO };
> +	struct richacl *acl = alloc->acl;
> +	struct richace *ace;
> +	unsigned int owner_allow, group_allow;
> +
> +	/*
> +	 * If the owner mask contains permissions which are not in the group
> +	 * mask, the group mask contains permissions which are not in the other
> +	 * mask, or the owner class contains permissions which are not in the

s/owner class/owner mask?

> +	 * other mask, we may need to propagate permissions up from the
> +	 * everyone@ allow ace.  The third condition is implied by the first
> +	 * two.
> +	 */
> +	if (!((acl->a_owner_mask & ~acl->a_group_mask) ||
> +	      (acl->a_group_mask & ~acl->a_other_mask)))
> +		return 0;

The code looks right, but I don't understand the preceding comment.

For example, 

	owner mask: rw
	group mask:  wx
	other mask: rw

satisfies the first two conditions, but not the third.

Also, I don't understand why the first condition would imply that we
might need to propagate permissions.

--b.

> +	if (!acl->a_count)
> +		return 0;
> +	ace = acl->a_entries + acl->a_count - 1;
> +	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
> +		return 0;
> +
> +	owner_allow = ace->e_mask & acl->a_owner_mask;
> +	group_allow = ace->e_mask & acl->a_group_mask;
> +
> +	if (owner_allow & ~(acl->a_group_mask & acl->a_other_mask)) {
> +		/* Propagate everyone@ permissions through to owner@. */
> +		who.e_id.special = RICHACE_OWNER_SPECIAL_ID;
> +		if (__richacl_propagate_everyone(alloc, &who, owner_allow))
> +			return -1;
> +		acl = alloc->acl;
> +	}
> +
> +	if (group_allow & ~acl->a_other_mask) {
> +		int n;
> +
> +		/* Propagate everyone@ permissions through to group@. */
> +		who.e_id.special = RICHACE_GROUP_SPECIAL_ID;
> +		if (__richacl_propagate_everyone(alloc, &who, group_allow))
> +			return -1;
> +		acl = alloc->acl;
> +
> +		/*
> +		 * Start from the entry before the trailing everyone@ allow
> +		 * entry. We will not hit everyone@ entries in the loop.
> +		 */
> +		for (n = acl->a_count - 2; n != -1; n--) {
> +			ace = acl->a_entries + n;
> +
> +			if (richace_is_inherit_only(ace) ||
> +			    richace_is_owner(ace) ||
> +			    richace_is_group(ace))
> +				continue;
> +			if (richace_is_allow(ace) || richace_is_deny(ace)) {
> +				/*
> +				 * Any inserted entry will end up below the
> +				 * current entry
> +				 */
> +				if (__richacl_propagate_everyone(alloc, ace,
> +								 group_allow))
> +					return -1;
> +				acl = alloc->acl;
> +			}
> +		}
> +	}
> +	return 0;
> +}
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 22/41] richacl: Propagate everyone@ permissions to other aces
@ 2015-09-18 21:56         ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-18 21:56 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

On Sat, Sep 05, 2015 at 12:27:17PM +0200, Andreas Gruenbacher wrote:
> The trailing everyone@ allow ace can grant permissions to all file
> classes including the owner and group class.  Before we can apply the
> other mask to this entry to turn it into an "other class" entry, we need
> to ensure that members of the owner or group class will not lose any
> permissions from that ace.
> 
> Conceptually, we do this by inserting additional <who>:<allow>::allow
> entries before the trailing everyone@ allow ace with the same
> permissions as the trailing everyone@ allow ace for owner@, group@, and
> all explicitly mentioned users and groups.  (In practice, we will rarely
> need to insert any additional aces in this step.)
> 
> Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
> ---
>  fs/richacl_compat.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 195 insertions(+)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 4f0acf5..9b76fc0 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -218,3 +218,198 @@ richacl_move_everyone_aces_down(struct richacl_alloc *alloc)
>  	}
>  	return 0;
>  }
> +
> +/**
> + * __richacl_propagate_everyone  -  propagate everyone@ permissions up for @who
> + * @alloc:	acl and number of allocated entries
> + * @who:	identifier to propagate permissions for
> + * @allow:	permissions to propagate up
> + *
> + * Propagate the permissions in @allow up from the end of the acl to the start
> + * for the specified principal @who.
> + *
> + * The simplest possible approach to achieve this would be to insert a
> + * "<who>:<allow>::allow" ace before the final everyone@ allow ace.  Since this
> + * would often result in aces which are not needed or which could be merged
> + * with an existing ace, we make the following optimizations:
> + *
> + *   - We go through the acl and determine which permissions are already
> + *     allowed or denied to @who, and we remove those permissions from
> + *     @allow.
> + *
> + *   - If the acl contains an allow ace for @who and no aces after this entry
> + *     deny permissions in @allow, we add the permissions in @allow to this
> + *     ace.  (Propagating permissions across a deny ace which can match the
> + *     process can elevate permissions.)
> + *
> + * This transformation does not alter the permissions that the acl grants.
> + */
> +static int
> +__richacl_propagate_everyone(struct richacl_alloc *alloc, struct richace *who,
> +			     unsigned int allow)
> +{
> +	struct richace *allow_last = NULL, *ace;
> +	struct richacl *acl = alloc->acl;
> +
> +	/*
> +	 * Remove the permissions from allow that are already determined for
> +	 * this who value, and figure out if there is an allow entry for
> +	 * this who value that is "reachable" from the trailing everyone@
> +	 * allow ace.
> +	 */
> +	richacl_for_each_entry(ace, acl) {
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_allow(ace)) {
> +			if (richace_is_same_identifier(ace, who)) {
> +				allow &= ~ace->e_mask;
> +				allow_last = ace;
> +			}
> +		} else if (richace_is_deny(ace)) {
> +			if (richace_is_same_identifier(ace, who))
> +				allow &= ~ace->e_mask;
> +			else if (allow & ace->e_mask)
> +				allow_last = NULL;
> +		}
> +	}
> +	ace--;
> +
> +	/*
> +	 * If for group class entries, all the remaining permissions will
> +	 * remain granted by the trailing everyone@ ace, no additional entry is
> +	 * needed.
> +	 */
> +	if (!richace_is_owner(who) &&
> +	    richace_is_everyone(ace) && richace_is_allow(ace) &&
> +	    !(allow & ~(ace->e_mask & acl->a_other_mask)))
> +		allow = 0;
> +
> +	if (allow) {
> +		if (allow_last)
> +			return richace_change_mask(alloc, &allow_last,
> +						   allow_last->e_mask | allow);
> +		else {
> +			struct richace who_copy;
> +
> +			richace_copy(&who_copy, who);
> +			ace = acl->a_entries + acl->a_count - 1;
> +			if (richacl_insert_entry(alloc, &ace))
> +				return -1;
> +			richace_copy(ace, &who_copy);
> +			ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +			ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
> +			ace->e_mask = allow;
> +		}
> +	}
> +	return 0;
> +}
> +
> +/**
> + * richacl_propagate_everyone  -  propagate everyone@ permissions up the acl
> + * @alloc:	acl and number of allocated entries
> + *
> + * Make sure that group@ and all other users and groups mentioned in the acl
> + * will not lose any permissions when finally applying the other mask to the
> + * everyone@ allow ace at the end of the acl.  We modify the permissions of
> + * existing entries or add new entries before the final everyone@ allow ace to
> + * achieve that.
> + *
> + * For example, the following acl implicitly grants everyone rwpx access:
> + *
> + *    joe:r::allow
> + *    everyone@:rwpx::allow
> + *
> + * When applying mode 0660 to this acl, group@ would lose rwp access, and joe
> + * would lose wp access even though the mode does not exclude those
> + * permissions.  After propagating the everyone@ permissions, the result for
> + * applying mode 0660 becomes:
> + *
> + *    owner@:rwp::allow
> + *    joe:rwp::allow
> + *    group@:rwp::allow
> + *
> + * Deny aces complicate the matter.  For example, the following acl grants
> + * everyone but joe write access:
> + *
> + *    joe:wp::deny
> + *    everyone@:rwpx::allow
> + *
> + * When applying mode 0660 to this acl, group@ would lose rwp access, and joe
> + * would lose r access.  After propagating the everyone@ permissions, the
> + * result for applying mode 0660 becomes:
> + *
> + *    owner@:rwp::allow
> + *    joe:w::deny
> + *    group@:rwp::allow
> + *    joe:r::allow
> + */
> +static int
> +richacl_propagate_everyone(struct richacl_alloc *alloc)
> +{
> +	struct richace who = { .e_flags = RICHACE_SPECIAL_WHO };
> +	struct richacl *acl = alloc->acl;
> +	struct richace *ace;
> +	unsigned int owner_allow, group_allow;
> +
> +	/*
> +	 * If the owner mask contains permissions which are not in the group
> +	 * mask, the group mask contains permissions which are not in the other
> +	 * mask, or the owner class contains permissions which are not in the

s/owner class/owner mask?

> +	 * other mask, we may need to propagate permissions up from the
> +	 * everyone@ allow ace.  The third condition is implied by the first
> +	 * two.
> +	 */
> +	if (!((acl->a_owner_mask & ~acl->a_group_mask) ||
> +	      (acl->a_group_mask & ~acl->a_other_mask)))
> +		return 0;

The code looks right, but I don't understand the preceding comment.

For example, 

	owner mask: rw
	group mask:  wx
	other mask: rw

satisfies the first two conditions, but not the third.

Also, I don't understand why the first condition would imply that we
might need to propagate permissions.

--b.

> +	if (!acl->a_count)
> +		return 0;
> +	ace = acl->a_entries + acl->a_count - 1;
> +	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
> +		return 0;
> +
> +	owner_allow = ace->e_mask & acl->a_owner_mask;
> +	group_allow = ace->e_mask & acl->a_group_mask;
> +
> +	if (owner_allow & ~(acl->a_group_mask & acl->a_other_mask)) {
> +		/* Propagate everyone@ permissions through to owner@. */
> +		who.e_id.special = RICHACE_OWNER_SPECIAL_ID;
> +		if (__richacl_propagate_everyone(alloc, &who, owner_allow))
> +			return -1;
> +		acl = alloc->acl;
> +	}
> +
> +	if (group_allow & ~acl->a_other_mask) {
> +		int n;
> +
> +		/* Propagate everyone@ permissions through to group@. */
> +		who.e_id.special = RICHACE_GROUP_SPECIAL_ID;
> +		if (__richacl_propagate_everyone(alloc, &who, group_allow))
> +			return -1;
> +		acl = alloc->acl;
> +
> +		/*
> +		 * Start from the entry before the trailing everyone@ allow
> +		 * entry. We will not hit everyone@ entries in the loop.
> +		 */
> +		for (n = acl->a_count - 2; n != -1; n--) {
> +			ace = acl->a_entries + n;
> +
> +			if (richace_is_inherit_only(ace) ||
> +			    richace_is_owner(ace) ||
> +			    richace_is_group(ace))
> +				continue;
> +			if (richace_is_allow(ace) || richace_is_deny(ace)) {
> +				/*
> +				 * Any inserted entry will end up below the
> +				 * current entry
> +				 */
> +				if (__richacl_propagate_everyone(alloc, ace,
> +								 group_allow))
> +					return -1;
> +				acl = alloc->acl;
> +			}
> +		}
> +	}
> +	return 0;
> +}
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 13/41] richacl: Check if an acl is equivalent to a file mode
  2015-09-18  0:56           ` J. Bruce Fields
@ 2015-09-21 13:59               ` Austin S Hemmelgarn
  -1 siblings, 0 replies; 188+ messages in thread
From: Austin S Hemmelgarn @ 2015-09-21 13:59 UTC (permalink / raw)
  To: J. Bruce Fields, Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

[-- Attachment #1: Type: text/plain, Size: 7247 bytes --]

On 2015-09-17 20:56, J. Bruce Fields wrote:
> On Thu, Sep 17, 2015 at 02:22:19PM -0400, bfields wrote:
>> On Sat, Sep 05, 2015 at 12:27:08PM +0200, Andreas Gruenbacher wrote:
>>> ACLs are considered equivalent to file modes if they only consist of
>>> owner@, group@, and everyone@ entries, the owner@ permissions do not
>>> depend on whether the owner is a member in the owning group, and no
>>> inheritance flags are set.  This test is used to avoid storing richacls
>>> if the acl can be computed from the file permission bits.
>>
>> We're assuming here that it's OK for us to silently rearrange an ACL as
>> long as the result is still equivalent (in the sense that the permission
>> algorithm would always produce the same result).
>>
>> I guess that's OK by me, but it might violate user expectations in some
>> simple common cases, so may be worth mentioning in documentation
>> someplace if we don't already.
>
> Also your notion of mode-equivalence here is interesting, it's actually
> a strict subset of the ACLs that produce the same permission results as
> a mode.  (For example, everyone:rwx,bfields:rwx is equivalent to 0777
> but won't be considered mode-equivalent by this algorithm.)
Although it could also be equivalent to 0707, or (if bfields is the 
group name also) 0077, or even (if bfields isn't the group or owner of 
the file) 0007.  Mode equivalence get's even trickier when you throw in 
permissions just beyond rwx (for example, by Windows standards, the 
usage of the execute bit on directories is weird (they have a separate 
permission in their ACE's for directory listing), or by VMS standards, 
write permission on a directory doesn't mean that you can delete things 
in it (VMS actually had a separate bit for the delete permission, and 
even had separate permissions for system access)).
>
> I think the choices you've made probably make the most sense, they just
> wouldn't have been obvious to me.  Anyway, so, OK by me:
>
> 	Reviewed-by: J. Bruce Fields <bfields-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
>
> --b.
>
>>
>> --b.
>>
>>>
>>> Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
>>> ---
>>>   fs/richacl_base.c       | 104 ++++++++++++++++++++++++++++++++++++++++++++++++
>>>   include/linux/richacl.h |   1 +
>>>   2 files changed, 105 insertions(+)
>>>
>>> diff --git a/fs/richacl_base.c b/fs/richacl_base.c
>>> index 3163152..106e988 100644
>>> --- a/fs/richacl_base.c
>>> +++ b/fs/richacl_base.c
>>> @@ -379,3 +379,107 @@ richacl_chmod(struct richacl *acl, mode_t mode)
>>>   	return clone;
>>>   }
>>>   EXPORT_SYMBOL_GPL(richacl_chmod);
>>> +
>>> +/**
>>> + * richacl_equiv_mode  -  compute the mode equivalent of @acl
>>> + *
>>> + * An acl is considered equivalent to a file mode if it only consists of
>>> + * owner@, group@, and everyone@ entries and the owner@ permissions do not
>>> + * depend on whether the owner is a member in the owning group.
>>> + */
>>> +int
>>> +richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
>>> +{
>>> +	mode_t mode = *mode_p;
>>> +
>>> +	/*
>>> +	 * The RICHACE_DELETE_CHILD flag is meaningless for non-directories, so
>>> +	 * we ignore it.
>>> +	 */
>>> +	unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
>>> +	struct {
>>> +		unsigned int allowed;
>>> +		unsigned int defined;  /* allowed or denied */
>>> +	} owner = {
>>> +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED |
>>> +			   RICHACE_POSIX_OWNER_ALLOWED  | x,
>>> +	}, group = {
>>> +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
>>> +	}, everyone = {
>>> +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
>>> +	};
>>> +	const struct richace *ace;
>>> +
>>> +	if (acl->a_flags & ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED))
>>> +		return -1;
>>> +
>>> +	richacl_for_each_entry(ace, acl) {
>>> +		if (ace->e_flags & ~RICHACE_SPECIAL_WHO)
>>> +			return -1;
>>> +
>>> +		if (richace_is_owner(ace) || richace_is_everyone(ace)) {
>>> +			x = ace->e_mask & ~owner.defined;
>>> +			if (richace_is_allow(ace)) {
>>> +				unsigned int group_denied =
>>> +					group.defined & ~group.allowed;
>>> +
>>> +				if (x & group_denied)
>>> +					return -1;
>>> +				owner.allowed |= x;
>>> +			} else /* if (richace_is_deny(ace)) */ {
>>> +				if (x & group.allowed)
>>> +					return -1;
>>> +			}
>>> +			owner.defined |= x;
>>> +
>>> +			if (richace_is_everyone(ace)) {
>>> +				x = ace->e_mask;
>>> +				if (richace_is_allow(ace)) {
>>> +					group.allowed |=
>>> +						x & ~group.defined;
>>> +					everyone.allowed |=
>>> +						x & ~everyone.defined;
>>> +				}
>>> +				group.defined |= x;
>>> +				everyone.defined |= x;
>>> +			}
>>> +		} else if (richace_is_group(ace)) {
>>> +			x = ace->e_mask & ~group.defined;
>>> +			if (richace_is_allow(ace))
>>> +				group.allowed |= x;
>>> +			group.defined |= x;
>>> +		} else
>>> +			return -1;
>>> +	}
>>> +
>>> +	if (group.allowed & ~owner.defined)
>>> +		return -1;
>>> +
>>> +	if (acl->a_flags & RICHACL_MASKED) {
>>> +		if (acl->a_flags & RICHACL_WRITE_THROUGH) {
>>> +			owner.allowed = acl->a_owner_mask;
>>> +			everyone.allowed = acl->a_other_mask;
>>> +		} else {
>>> +			owner.allowed &= acl->a_owner_mask;
>>> +			everyone.allowed &= acl->a_other_mask;
>>> +		}
>>> +		group.allowed &= acl->a_group_mask;
>>> +	}
>>> +
>>> +	mode = (mode & ~S_IRWXUGO) |
>>> +	       (richacl_mask_to_mode(owner.allowed) << 6) |
>>> +	       (richacl_mask_to_mode(group.allowed) << 3) |
>>> +		richacl_mask_to_mode(everyone.allowed);
>>> +
>>> +	/* Mask flags we can ignore */
>>> +	x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
>>> +
>>> +	if (((richacl_mode_to_mask(mode >> 6) ^ owner.allowed)    & ~x) ||
>>> +	    ((richacl_mode_to_mask(mode >> 3) ^ group.allowed)    & ~x) ||
>>> +	    ((richacl_mode_to_mask(mode)      ^ everyone.allowed) & ~x))
>>> +		return -1;
>>> +
>>> +	*mode_p = mode;
>>> +	return 0;
>>> +}
>>> +EXPORT_SYMBOL_GPL(richacl_equiv_mode);
>>> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
>>> index d4a576c..6535ce5 100644
>>> --- a/include/linux/richacl.h
>>> +++ b/include/linux/richacl.h
>>> @@ -304,6 +304,7 @@ extern unsigned int richacl_mode_to_mask(mode_t);
>>>   extern unsigned int richacl_want_to_mask(unsigned int);
>>>   extern void richacl_compute_max_masks(struct richacl *);
>>>   extern struct richacl *richacl_chmod(struct richacl *, mode_t);
>>> +extern int richacl_equiv_mode(const struct richacl *, mode_t *);
>>>
>>>   /* richacl_inode.c */
>>>   extern int richacl_permission(struct inode *, const struct richacl *, int);
>>> --
>>> 2.4.3
>>>
>>> --
>>> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
>>> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
>>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/
>



[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 3019 bytes --]

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 13/41] richacl: Check if an acl is equivalent to a file mode
@ 2015-09-21 13:59               ` Austin S Hemmelgarn
  0 siblings, 0 replies; 188+ messages in thread
From: Austin S Hemmelgarn @ 2015-09-21 13:59 UTC (permalink / raw)
  To: J. Bruce Fields, Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

[-- Attachment #1: Type: text/plain, Size: 7141 bytes --]

On 2015-09-17 20:56, J. Bruce Fields wrote:
> On Thu, Sep 17, 2015 at 02:22:19PM -0400, bfields wrote:
>> On Sat, Sep 05, 2015 at 12:27:08PM +0200, Andreas Gruenbacher wrote:
>>> ACLs are considered equivalent to file modes if they only consist of
>>> owner@, group@, and everyone@ entries, the owner@ permissions do not
>>> depend on whether the owner is a member in the owning group, and no
>>> inheritance flags are set.  This test is used to avoid storing richacls
>>> if the acl can be computed from the file permission bits.
>>
>> We're assuming here that it's OK for us to silently rearrange an ACL as
>> long as the result is still equivalent (in the sense that the permission
>> algorithm would always produce the same result).
>>
>> I guess that's OK by me, but it might violate user expectations in some
>> simple common cases, so may be worth mentioning in documentation
>> someplace if we don't already.
>
> Also your notion of mode-equivalence here is interesting, it's actually
> a strict subset of the ACLs that produce the same permission results as
> a mode.  (For example, everyone:rwx,bfields:rwx is equivalent to 0777
> but won't be considered mode-equivalent by this algorithm.)
Although it could also be equivalent to 0707, or (if bfields is the 
group name also) 0077, or even (if bfields isn't the group or owner of 
the file) 0007.  Mode equivalence get's even trickier when you throw in 
permissions just beyond rwx (for example, by Windows standards, the 
usage of the execute bit on directories is weird (they have a separate 
permission in their ACE's for directory listing), or by VMS standards, 
write permission on a directory doesn't mean that you can delete things 
in it (VMS actually had a separate bit for the delete permission, and 
even had separate permissions for system access)).
>
> I think the choices you've made probably make the most sense, they just
> wouldn't have been obvious to me.  Anyway, so, OK by me:
>
> 	Reviewed-by: J. Bruce Fields <bfields@redhat.com>
>
> --b.
>
>>
>> --b.
>>
>>>
>>> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
>>> ---
>>>   fs/richacl_base.c       | 104 ++++++++++++++++++++++++++++++++++++++++++++++++
>>>   include/linux/richacl.h |   1 +
>>>   2 files changed, 105 insertions(+)
>>>
>>> diff --git a/fs/richacl_base.c b/fs/richacl_base.c
>>> index 3163152..106e988 100644
>>> --- a/fs/richacl_base.c
>>> +++ b/fs/richacl_base.c
>>> @@ -379,3 +379,107 @@ richacl_chmod(struct richacl *acl, mode_t mode)
>>>   	return clone;
>>>   }
>>>   EXPORT_SYMBOL_GPL(richacl_chmod);
>>> +
>>> +/**
>>> + * richacl_equiv_mode  -  compute the mode equivalent of @acl
>>> + *
>>> + * An acl is considered equivalent to a file mode if it only consists of
>>> + * owner@, group@, and everyone@ entries and the owner@ permissions do not
>>> + * depend on whether the owner is a member in the owning group.
>>> + */
>>> +int
>>> +richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
>>> +{
>>> +	mode_t mode = *mode_p;
>>> +
>>> +	/*
>>> +	 * The RICHACE_DELETE_CHILD flag is meaningless for non-directories, so
>>> +	 * we ignore it.
>>> +	 */
>>> +	unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
>>> +	struct {
>>> +		unsigned int allowed;
>>> +		unsigned int defined;  /* allowed or denied */
>>> +	} owner = {
>>> +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED |
>>> +			   RICHACE_POSIX_OWNER_ALLOWED  | x,
>>> +	}, group = {
>>> +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
>>> +	}, everyone = {
>>> +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
>>> +	};
>>> +	const struct richace *ace;
>>> +
>>> +	if (acl->a_flags & ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED))
>>> +		return -1;
>>> +
>>> +	richacl_for_each_entry(ace, acl) {
>>> +		if (ace->e_flags & ~RICHACE_SPECIAL_WHO)
>>> +			return -1;
>>> +
>>> +		if (richace_is_owner(ace) || richace_is_everyone(ace)) {
>>> +			x = ace->e_mask & ~owner.defined;
>>> +			if (richace_is_allow(ace)) {
>>> +				unsigned int group_denied =
>>> +					group.defined & ~group.allowed;
>>> +
>>> +				if (x & group_denied)
>>> +					return -1;
>>> +				owner.allowed |= x;
>>> +			} else /* if (richace_is_deny(ace)) */ {
>>> +				if (x & group.allowed)
>>> +					return -1;
>>> +			}
>>> +			owner.defined |= x;
>>> +
>>> +			if (richace_is_everyone(ace)) {
>>> +				x = ace->e_mask;
>>> +				if (richace_is_allow(ace)) {
>>> +					group.allowed |=
>>> +						x & ~group.defined;
>>> +					everyone.allowed |=
>>> +						x & ~everyone.defined;
>>> +				}
>>> +				group.defined |= x;
>>> +				everyone.defined |= x;
>>> +			}
>>> +		} else if (richace_is_group(ace)) {
>>> +			x = ace->e_mask & ~group.defined;
>>> +			if (richace_is_allow(ace))
>>> +				group.allowed |= x;
>>> +			group.defined |= x;
>>> +		} else
>>> +			return -1;
>>> +	}
>>> +
>>> +	if (group.allowed & ~owner.defined)
>>> +		return -1;
>>> +
>>> +	if (acl->a_flags & RICHACL_MASKED) {
>>> +		if (acl->a_flags & RICHACL_WRITE_THROUGH) {
>>> +			owner.allowed = acl->a_owner_mask;
>>> +			everyone.allowed = acl->a_other_mask;
>>> +		} else {
>>> +			owner.allowed &= acl->a_owner_mask;
>>> +			everyone.allowed &= acl->a_other_mask;
>>> +		}
>>> +		group.allowed &= acl->a_group_mask;
>>> +	}
>>> +
>>> +	mode = (mode & ~S_IRWXUGO) |
>>> +	       (richacl_mask_to_mode(owner.allowed) << 6) |
>>> +	       (richacl_mask_to_mode(group.allowed) << 3) |
>>> +		richacl_mask_to_mode(everyone.allowed);
>>> +
>>> +	/* Mask flags we can ignore */
>>> +	x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
>>> +
>>> +	if (((richacl_mode_to_mask(mode >> 6) ^ owner.allowed)    & ~x) ||
>>> +	    ((richacl_mode_to_mask(mode >> 3) ^ group.allowed)    & ~x) ||
>>> +	    ((richacl_mode_to_mask(mode)      ^ everyone.allowed) & ~x))
>>> +		return -1;
>>> +
>>> +	*mode_p = mode;
>>> +	return 0;
>>> +}
>>> +EXPORT_SYMBOL_GPL(richacl_equiv_mode);
>>> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
>>> index d4a576c..6535ce5 100644
>>> --- a/include/linux/richacl.h
>>> +++ b/include/linux/richacl.h
>>> @@ -304,6 +304,7 @@ extern unsigned int richacl_mode_to_mask(mode_t);
>>>   extern unsigned int richacl_want_to_mask(unsigned int);
>>>   extern void richacl_compute_max_masks(struct richacl *);
>>>   extern struct richacl *richacl_chmod(struct richacl *, mode_t);
>>> +extern int richacl_equiv_mode(const struct richacl *, mode_t *);
>>>
>>>   /* richacl_inode.c */
>>>   extern int richacl_permission(struct inode *, const struct richacl *, int);
>>> --
>>> 2.4.3
>>>
>>> --
>>> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
>>> the body of a message to majordomo@vger.kernel.org
>>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/
>



[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 3019 bytes --]

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 13/41] richacl: Check if an acl is equivalent to a file mode
  2015-09-21 13:59               ` Austin S Hemmelgarn
@ 2015-09-21 14:38                   ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-21 14:38 UTC (permalink / raw)
  To: Austin S Hemmelgarn
  Cc: Andreas Gruenbacher, linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

On Mon, Sep 21, 2015 at 09:59:07AM -0400, Austin S Hemmelgarn wrote:
> On 2015-09-17 20:56, J. Bruce Fields wrote:
> >On Thu, Sep 17, 2015 at 02:22:19PM -0400, bfields wrote:
> >>On Sat, Sep 05, 2015 at 12:27:08PM +0200, Andreas Gruenbacher wrote:
> >>>ACLs are considered equivalent to file modes if they only consist of
> >>>owner@, group@, and everyone@ entries, the owner@ permissions do not
> >>>depend on whether the owner is a member in the owning group, and no
> >>>inheritance flags are set.  This test is used to avoid storing richacls
> >>>if the acl can be computed from the file permission bits.
> >>
> >>We're assuming here that it's OK for us to silently rearrange an ACL as
> >>long as the result is still equivalent (in the sense that the permission
> >>algorithm would always produce the same result).
> >>
> >>I guess that's OK by me, but it might violate user expectations in some
> >>simple common cases, so may be worth mentioning in documentation
> >>someplace if we don't already.
> >
> >Also your notion of mode-equivalence here is interesting, it's actually
> >a strict subset of the ACLs that produce the same permission results as
> >a mode.  (For example, everyone:rwx,bfields:rwx is equivalent to 0777
> >but won't be considered mode-equivalent by this algorithm.)
> Although it could also be equivalent to 0707, or (if bfields is the
> group name also) 0077, or even (if bfields isn't the group or owner
> of the file) 0007.

I disagree.  I think you've misread my example ACL (may be my sloppy
notation, sorry) or misunderstood the ACL evalutation algorithm.

> Mode equivalence get's even trickier when you
> throw in permissions just beyond rwx (for example, by Windows
> standards, the usage of the execute bit on directories is weird
> (they have a separate permission in their ACE's for directory
> listing), or by VMS standards, write permission on a directory
> doesn't mean that you can delete things in it (VMS actually had a
> separate bit for the delete permission, and even had separate
> permissions for system access)).

I believe these patches handle all of those details correctly; if you
see anything to the contrary, please do speak up.

Note that Windows also has a DELETE bit, though it is ORed with the
directory permission not ANDed (so it is sufficient for the directory to
allow MAY_DELETE_CHILD *or* for the file to allow DELETE).

(But I believe you're correct that VMS required both permissions, if
e.g. http://www.djesys.com/vms/freevms/mentor/vms_prot.html is correct).

--b.

> >
> >I think the choices you've made probably make the most sense, they just
> >wouldn't have been obvious to me.  Anyway, so, OK by me:
> >
> >	Reviewed-by: J. Bruce Fields <bfields-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> >
> >--b.
> >
> >>
> >>--b.
> >>
> >>>
> >>>Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> >>>---
> >>>  fs/richacl_base.c       | 104 ++++++++++++++++++++++++++++++++++++++++++++++++
> >>>  include/linux/richacl.h |   1 +
> >>>  2 files changed, 105 insertions(+)
> >>>
> >>>diff --git a/fs/richacl_base.c b/fs/richacl_base.c
> >>>index 3163152..106e988 100644
> >>>--- a/fs/richacl_base.c
> >>>+++ b/fs/richacl_base.c
> >>>@@ -379,3 +379,107 @@ richacl_chmod(struct richacl *acl, mode_t mode)
> >>>  	return clone;
> >>>  }
> >>>  EXPORT_SYMBOL_GPL(richacl_chmod);
> >>>+
> >>>+/**
> >>>+ * richacl_equiv_mode  -  compute the mode equivalent of @acl
> >>>+ *
> >>>+ * An acl is considered equivalent to a file mode if it only consists of
> >>>+ * owner@, group@, and everyone@ entries and the owner@ permissions do not
> >>>+ * depend on whether the owner is a member in the owning group.
> >>>+ */
> >>>+int
> >>>+richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
> >>>+{
> >>>+	mode_t mode = *mode_p;
> >>>+
> >>>+	/*
> >>>+	 * The RICHACE_DELETE_CHILD flag is meaningless for non-directories, so
> >>>+	 * we ignore it.
> >>>+	 */
> >>>+	unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> >>>+	struct {
> >>>+		unsigned int allowed;
> >>>+		unsigned int defined;  /* allowed or denied */
> >>>+	} owner = {
> >>>+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED |
> >>>+			   RICHACE_POSIX_OWNER_ALLOWED  | x,
> >>>+	}, group = {
> >>>+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
> >>>+	}, everyone = {
> >>>+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
> >>>+	};
> >>>+	const struct richace *ace;
> >>>+
> >>>+	if (acl->a_flags & ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED))
> >>>+		return -1;
> >>>+
> >>>+	richacl_for_each_entry(ace, acl) {
> >>>+		if (ace->e_flags & ~RICHACE_SPECIAL_WHO)
> >>>+			return -1;
> >>>+
> >>>+		if (richace_is_owner(ace) || richace_is_everyone(ace)) {
> >>>+			x = ace->e_mask & ~owner.defined;
> >>>+			if (richace_is_allow(ace)) {
> >>>+				unsigned int group_denied =
> >>>+					group.defined & ~group.allowed;
> >>>+
> >>>+				if (x & group_denied)
> >>>+					return -1;
> >>>+				owner.allowed |= x;
> >>>+			} else /* if (richace_is_deny(ace)) */ {
> >>>+				if (x & group.allowed)
> >>>+					return -1;
> >>>+			}
> >>>+			owner.defined |= x;
> >>>+
> >>>+			if (richace_is_everyone(ace)) {
> >>>+				x = ace->e_mask;
> >>>+				if (richace_is_allow(ace)) {
> >>>+					group.allowed |=
> >>>+						x & ~group.defined;
> >>>+					everyone.allowed |=
> >>>+						x & ~everyone.defined;
> >>>+				}
> >>>+				group.defined |= x;
> >>>+				everyone.defined |= x;
> >>>+			}
> >>>+		} else if (richace_is_group(ace)) {
> >>>+			x = ace->e_mask & ~group.defined;
> >>>+			if (richace_is_allow(ace))
> >>>+				group.allowed |= x;
> >>>+			group.defined |= x;
> >>>+		} else
> >>>+			return -1;
> >>>+	}
> >>>+
> >>>+	if (group.allowed & ~owner.defined)
> >>>+		return -1;
> >>>+
> >>>+	if (acl->a_flags & RICHACL_MASKED) {
> >>>+		if (acl->a_flags & RICHACL_WRITE_THROUGH) {
> >>>+			owner.allowed = acl->a_owner_mask;
> >>>+			everyone.allowed = acl->a_other_mask;
> >>>+		} else {
> >>>+			owner.allowed &= acl->a_owner_mask;
> >>>+			everyone.allowed &= acl->a_other_mask;
> >>>+		}
> >>>+		group.allowed &= acl->a_group_mask;
> >>>+	}
> >>>+
> >>>+	mode = (mode & ~S_IRWXUGO) |
> >>>+	       (richacl_mask_to_mode(owner.allowed) << 6) |
> >>>+	       (richacl_mask_to_mode(group.allowed) << 3) |
> >>>+		richacl_mask_to_mode(everyone.allowed);
> >>>+
> >>>+	/* Mask flags we can ignore */
> >>>+	x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> >>>+
> >>>+	if (((richacl_mode_to_mask(mode >> 6) ^ owner.allowed)    & ~x) ||
> >>>+	    ((richacl_mode_to_mask(mode >> 3) ^ group.allowed)    & ~x) ||
> >>>+	    ((richacl_mode_to_mask(mode)      ^ everyone.allowed) & ~x))
> >>>+		return -1;
> >>>+
> >>>+	*mode_p = mode;
> >>>+	return 0;
> >>>+}
> >>>+EXPORT_SYMBOL_GPL(richacl_equiv_mode);
> >>>diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> >>>index d4a576c..6535ce5 100644
> >>>--- a/include/linux/richacl.h
> >>>+++ b/include/linux/richacl.h
> >>>@@ -304,6 +304,7 @@ extern unsigned int richacl_mode_to_mask(mode_t);
> >>>  extern unsigned int richacl_want_to_mask(unsigned int);
> >>>  extern void richacl_compute_max_masks(struct richacl *);
> >>>  extern struct richacl *richacl_chmod(struct richacl *, mode_t);
> >>>+extern int richacl_equiv_mode(const struct richacl *, mode_t *);
> >>>
> >>>  /* richacl_inode.c */
> >>>  extern int richacl_permission(struct inode *, const struct richacl *, int);
> >>>--
> >>>2.4.3
> >>>
> >>>--
> >>>To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> >>>the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> >>>More majordomo info at  http://vger.kernel.org/majordomo-info.html
> >--
> >To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> >the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> >More majordomo info at  http://vger.kernel.org/majordomo-info.html
> >Please read the FAQ at  http://www.tux.org/lkml/
> >
> 
> 

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 13/41] richacl: Check if an acl is equivalent to a file mode
@ 2015-09-21 14:38                   ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-21 14:38 UTC (permalink / raw)
  To: Austin S Hemmelgarn
  Cc: Andreas Gruenbacher, linux-kernel, linux-fsdevel, linux-nfs,
	linux-api, linux-cifs, linux-security-module

On Mon, Sep 21, 2015 at 09:59:07AM -0400, Austin S Hemmelgarn wrote:
> On 2015-09-17 20:56, J. Bruce Fields wrote:
> >On Thu, Sep 17, 2015 at 02:22:19PM -0400, bfields wrote:
> >>On Sat, Sep 05, 2015 at 12:27:08PM +0200, Andreas Gruenbacher wrote:
> >>>ACLs are considered equivalent to file modes if they only consist of
> >>>owner@, group@, and everyone@ entries, the owner@ permissions do not
> >>>depend on whether the owner is a member in the owning group, and no
> >>>inheritance flags are set.  This test is used to avoid storing richacls
> >>>if the acl can be computed from the file permission bits.
> >>
> >>We're assuming here that it's OK for us to silently rearrange an ACL as
> >>long as the result is still equivalent (in the sense that the permission
> >>algorithm would always produce the same result).
> >>
> >>I guess that's OK by me, but it might violate user expectations in some
> >>simple common cases, so may be worth mentioning in documentation
> >>someplace if we don't already.
> >
> >Also your notion of mode-equivalence here is interesting, it's actually
> >a strict subset of the ACLs that produce the same permission results as
> >a mode.  (For example, everyone:rwx,bfields:rwx is equivalent to 0777
> >but won't be considered mode-equivalent by this algorithm.)
> Although it could also be equivalent to 0707, or (if bfields is the
> group name also) 0077, or even (if bfields isn't the group or owner
> of the file) 0007.

I disagree.  I think you've misread my example ACL (may be my sloppy
notation, sorry) or misunderstood the ACL evalutation algorithm.

> Mode equivalence get's even trickier when you
> throw in permissions just beyond rwx (for example, by Windows
> standards, the usage of the execute bit on directories is weird
> (they have a separate permission in their ACE's for directory
> listing), or by VMS standards, write permission on a directory
> doesn't mean that you can delete things in it (VMS actually had a
> separate bit for the delete permission, and even had separate
> permissions for system access)).

I believe these patches handle all of those details correctly; if you
see anything to the contrary, please do speak up.

Note that Windows also has a DELETE bit, though it is ORed with the
directory permission not ANDed (so it is sufficient for the directory to
allow MAY_DELETE_CHILD *or* for the file to allow DELETE).

(But I believe you're correct that VMS required both permissions, if
e.g. http://www.djesys.com/vms/freevms/mentor/vms_prot.html is correct).

--b.

> >
> >I think the choices you've made probably make the most sense, they just
> >wouldn't have been obvious to me.  Anyway, so, OK by me:
> >
> >	Reviewed-by: J. Bruce Fields <bfields@redhat.com>
> >
> >--b.
> >
> >>
> >>--b.
> >>
> >>>
> >>>Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> >>>---
> >>>  fs/richacl_base.c       | 104 ++++++++++++++++++++++++++++++++++++++++++++++++
> >>>  include/linux/richacl.h |   1 +
> >>>  2 files changed, 105 insertions(+)
> >>>
> >>>diff --git a/fs/richacl_base.c b/fs/richacl_base.c
> >>>index 3163152..106e988 100644
> >>>--- a/fs/richacl_base.c
> >>>+++ b/fs/richacl_base.c
> >>>@@ -379,3 +379,107 @@ richacl_chmod(struct richacl *acl, mode_t mode)
> >>>  	return clone;
> >>>  }
> >>>  EXPORT_SYMBOL_GPL(richacl_chmod);
> >>>+
> >>>+/**
> >>>+ * richacl_equiv_mode  -  compute the mode equivalent of @acl
> >>>+ *
> >>>+ * An acl is considered equivalent to a file mode if it only consists of
> >>>+ * owner@, group@, and everyone@ entries and the owner@ permissions do not
> >>>+ * depend on whether the owner is a member in the owning group.
> >>>+ */
> >>>+int
> >>>+richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
> >>>+{
> >>>+	mode_t mode = *mode_p;
> >>>+
> >>>+	/*
> >>>+	 * The RICHACE_DELETE_CHILD flag is meaningless for non-directories, so
> >>>+	 * we ignore it.
> >>>+	 */
> >>>+	unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> >>>+	struct {
> >>>+		unsigned int allowed;
> >>>+		unsigned int defined;  /* allowed or denied */
> >>>+	} owner = {
> >>>+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED |
> >>>+			   RICHACE_POSIX_OWNER_ALLOWED  | x,
> >>>+	}, group = {
> >>>+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
> >>>+	}, everyone = {
> >>>+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
> >>>+	};
> >>>+	const struct richace *ace;
> >>>+
> >>>+	if (acl->a_flags & ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED))
> >>>+		return -1;
> >>>+
> >>>+	richacl_for_each_entry(ace, acl) {
> >>>+		if (ace->e_flags & ~RICHACE_SPECIAL_WHO)
> >>>+			return -1;
> >>>+
> >>>+		if (richace_is_owner(ace) || richace_is_everyone(ace)) {
> >>>+			x = ace->e_mask & ~owner.defined;
> >>>+			if (richace_is_allow(ace)) {
> >>>+				unsigned int group_denied =
> >>>+					group.defined & ~group.allowed;
> >>>+
> >>>+				if (x & group_denied)
> >>>+					return -1;
> >>>+				owner.allowed |= x;
> >>>+			} else /* if (richace_is_deny(ace)) */ {
> >>>+				if (x & group.allowed)
> >>>+					return -1;
> >>>+			}
> >>>+			owner.defined |= x;
> >>>+
> >>>+			if (richace_is_everyone(ace)) {
> >>>+				x = ace->e_mask;
> >>>+				if (richace_is_allow(ace)) {
> >>>+					group.allowed |=
> >>>+						x & ~group.defined;
> >>>+					everyone.allowed |=
> >>>+						x & ~everyone.defined;
> >>>+				}
> >>>+				group.defined |= x;
> >>>+				everyone.defined |= x;
> >>>+			}
> >>>+		} else if (richace_is_group(ace)) {
> >>>+			x = ace->e_mask & ~group.defined;
> >>>+			if (richace_is_allow(ace))
> >>>+				group.allowed |= x;
> >>>+			group.defined |= x;
> >>>+		} else
> >>>+			return -1;
> >>>+	}
> >>>+
> >>>+	if (group.allowed & ~owner.defined)
> >>>+		return -1;
> >>>+
> >>>+	if (acl->a_flags & RICHACL_MASKED) {
> >>>+		if (acl->a_flags & RICHACL_WRITE_THROUGH) {
> >>>+			owner.allowed = acl->a_owner_mask;
> >>>+			everyone.allowed = acl->a_other_mask;
> >>>+		} else {
> >>>+			owner.allowed &= acl->a_owner_mask;
> >>>+			everyone.allowed &= acl->a_other_mask;
> >>>+		}
> >>>+		group.allowed &= acl->a_group_mask;
> >>>+	}
> >>>+
> >>>+	mode = (mode & ~S_IRWXUGO) |
> >>>+	       (richacl_mask_to_mode(owner.allowed) << 6) |
> >>>+	       (richacl_mask_to_mode(group.allowed) << 3) |
> >>>+		richacl_mask_to_mode(everyone.allowed);
> >>>+
> >>>+	/* Mask flags we can ignore */
> >>>+	x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> >>>+
> >>>+	if (((richacl_mode_to_mask(mode >> 6) ^ owner.allowed)    & ~x) ||
> >>>+	    ((richacl_mode_to_mask(mode >> 3) ^ group.allowed)    & ~x) ||
> >>>+	    ((richacl_mode_to_mask(mode)      ^ everyone.allowed) & ~x))
> >>>+		return -1;
> >>>+
> >>>+	*mode_p = mode;
> >>>+	return 0;
> >>>+}
> >>>+EXPORT_SYMBOL_GPL(richacl_equiv_mode);
> >>>diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> >>>index d4a576c..6535ce5 100644
> >>>--- a/include/linux/richacl.h
> >>>+++ b/include/linux/richacl.h
> >>>@@ -304,6 +304,7 @@ extern unsigned int richacl_mode_to_mask(mode_t);
> >>>  extern unsigned int richacl_want_to_mask(unsigned int);
> >>>  extern void richacl_compute_max_masks(struct richacl *);
> >>>  extern struct richacl *richacl_chmod(struct richacl *, mode_t);
> >>>+extern int richacl_equiv_mode(const struct richacl *, mode_t *);
> >>>
> >>>  /* richacl_inode.c */
> >>>  extern int richacl_permission(struct inode *, const struct richacl *, int);
> >>>--
> >>>2.4.3
> >>>
> >>>--
> >>>To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> >>>the body of a message to majordomo@vger.kernel.org
> >>>More majordomo info at  http://vger.kernel.org/majordomo-info.html
> >--
> >To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> >the body of a message to majordomo@vger.kernel.org
> >More majordomo info at  http://vger.kernel.org/majordomo-info.html
> >Please read the FAQ at  http://www.tux.org/lkml/
> >
> 
> 



^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 13/41] richacl: Check if an acl is equivalent to a file mode
  2015-09-21 13:59               ` Austin S Hemmelgarn
@ 2015-09-21 15:31                   ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-21 15:31 UTC (permalink / raw)
  To: Austin S Hemmelgarn
  Cc: Andreas Gruenbacher, linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

On Mon, Sep 21, 2015 at 09:59:07AM -0400, Austin S Hemmelgarn wrote:
> Mode equivalence get's even trickier when you throw in permissions
> just beyond rwx

Note by the way that the major technical achievement here is the
reconciliation of two completely different evaluation algorithms.
(Windows/v4-like ACLs are evaluated top-to-bottom and stop as soon as
the requested permissions are allowed, or a remaining requested
permission is denied. Mode bits match the requester first and then check
the permissions associated with that requester.)

(You're correct that reconciling the permission bits is tricky (and
requires some judgement calls), but that's conceptually simpler.)

And then the handling of ID's looks like the major remaining problem.

--b.

> (for example, by Windows
> standards, the usage of the execute bit on directories is weird
> (they have a separate permission in their ACE's for directory
> listing), or by VMS standards, write permission on a directory
> doesn't mean that you can delete things in it (VMS actually had a
> separate bit for the delete permission, and even had separate
> permissions for system access)).
> >
> >I think the choices you've made probably make the most sense, they just
> >wouldn't have been obvious to me.  Anyway, so, OK by me:
> >
> >	Reviewed-by: J. Bruce Fields <bfields-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> >
> >--b.
> >
> >>
> >>--b.
> >>
> >>>
> >>>Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> >>>---
> >>>  fs/richacl_base.c       | 104 ++++++++++++++++++++++++++++++++++++++++++++++++
> >>>  include/linux/richacl.h |   1 +
> >>>  2 files changed, 105 insertions(+)
> >>>
> >>>diff --git a/fs/richacl_base.c b/fs/richacl_base.c
> >>>index 3163152..106e988 100644
> >>>--- a/fs/richacl_base.c
> >>>+++ b/fs/richacl_base.c
> >>>@@ -379,3 +379,107 @@ richacl_chmod(struct richacl *acl, mode_t mode)
> >>>  	return clone;
> >>>  }
> >>>  EXPORT_SYMBOL_GPL(richacl_chmod);
> >>>+
> >>>+/**
> >>>+ * richacl_equiv_mode  -  compute the mode equivalent of @acl
> >>>+ *
> >>>+ * An acl is considered equivalent to a file mode if it only consists of
> >>>+ * owner@, group@, and everyone@ entries and the owner@ permissions do not
> >>>+ * depend on whether the owner is a member in the owning group.
> >>>+ */
> >>>+int
> >>>+richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
> >>>+{
> >>>+	mode_t mode = *mode_p;
> >>>+
> >>>+	/*
> >>>+	 * The RICHACE_DELETE_CHILD flag is meaningless for non-directories, so
> >>>+	 * we ignore it.
> >>>+	 */
> >>>+	unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> >>>+	struct {
> >>>+		unsigned int allowed;
> >>>+		unsigned int defined;  /* allowed or denied */
> >>>+	} owner = {
> >>>+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED |
> >>>+			   RICHACE_POSIX_OWNER_ALLOWED  | x,
> >>>+	}, group = {
> >>>+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
> >>>+	}, everyone = {
> >>>+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
> >>>+	};
> >>>+	const struct richace *ace;
> >>>+
> >>>+	if (acl->a_flags & ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED))
> >>>+		return -1;
> >>>+
> >>>+	richacl_for_each_entry(ace, acl) {
> >>>+		if (ace->e_flags & ~RICHACE_SPECIAL_WHO)
> >>>+			return -1;
> >>>+
> >>>+		if (richace_is_owner(ace) || richace_is_everyone(ace)) {
> >>>+			x = ace->e_mask & ~owner.defined;
> >>>+			if (richace_is_allow(ace)) {
> >>>+				unsigned int group_denied =
> >>>+					group.defined & ~group.allowed;
> >>>+
> >>>+				if (x & group_denied)
> >>>+					return -1;
> >>>+				owner.allowed |= x;
> >>>+			} else /* if (richace_is_deny(ace)) */ {
> >>>+				if (x & group.allowed)
> >>>+					return -1;
> >>>+			}
> >>>+			owner.defined |= x;
> >>>+
> >>>+			if (richace_is_everyone(ace)) {
> >>>+				x = ace->e_mask;
> >>>+				if (richace_is_allow(ace)) {
> >>>+					group.allowed |=
> >>>+						x & ~group.defined;
> >>>+					everyone.allowed |=
> >>>+						x & ~everyone.defined;
> >>>+				}
> >>>+				group.defined |= x;
> >>>+				everyone.defined |= x;
> >>>+			}
> >>>+		} else if (richace_is_group(ace)) {
> >>>+			x = ace->e_mask & ~group.defined;
> >>>+			if (richace_is_allow(ace))
> >>>+				group.allowed |= x;
> >>>+			group.defined |= x;
> >>>+		} else
> >>>+			return -1;
> >>>+	}
> >>>+
> >>>+	if (group.allowed & ~owner.defined)
> >>>+		return -1;
> >>>+
> >>>+	if (acl->a_flags & RICHACL_MASKED) {
> >>>+		if (acl->a_flags & RICHACL_WRITE_THROUGH) {
> >>>+			owner.allowed = acl->a_owner_mask;
> >>>+			everyone.allowed = acl->a_other_mask;
> >>>+		} else {
> >>>+			owner.allowed &= acl->a_owner_mask;
> >>>+			everyone.allowed &= acl->a_other_mask;
> >>>+		}
> >>>+		group.allowed &= acl->a_group_mask;
> >>>+	}
> >>>+
> >>>+	mode = (mode & ~S_IRWXUGO) |
> >>>+	       (richacl_mask_to_mode(owner.allowed) << 6) |
> >>>+	       (richacl_mask_to_mode(group.allowed) << 3) |
> >>>+		richacl_mask_to_mode(everyone.allowed);
> >>>+
> >>>+	/* Mask flags we can ignore */
> >>>+	x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> >>>+
> >>>+	if (((richacl_mode_to_mask(mode >> 6) ^ owner.allowed)    & ~x) ||
> >>>+	    ((richacl_mode_to_mask(mode >> 3) ^ group.allowed)    & ~x) ||
> >>>+	    ((richacl_mode_to_mask(mode)      ^ everyone.allowed) & ~x))
> >>>+		return -1;
> >>>+
> >>>+	*mode_p = mode;
> >>>+	return 0;
> >>>+}
> >>>+EXPORT_SYMBOL_GPL(richacl_equiv_mode);
> >>>diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> >>>index d4a576c..6535ce5 100644
> >>>--- a/include/linux/richacl.h
> >>>+++ b/include/linux/richacl.h
> >>>@@ -304,6 +304,7 @@ extern unsigned int richacl_mode_to_mask(mode_t);
> >>>  extern unsigned int richacl_want_to_mask(unsigned int);
> >>>  extern void richacl_compute_max_masks(struct richacl *);
> >>>  extern struct richacl *richacl_chmod(struct richacl *, mode_t);
> >>>+extern int richacl_equiv_mode(const struct richacl *, mode_t *);
> >>>
> >>>  /* richacl_inode.c */
> >>>  extern int richacl_permission(struct inode *, const struct richacl *, int);
> >>>--
> >>>2.4.3
> >>>
> >>>--
> >>>To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> >>>the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> >>>More majordomo info at  http://vger.kernel.org/majordomo-info.html
> >--
> >To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> >the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> >More majordomo info at  http://vger.kernel.org/majordomo-info.html
> >Please read the FAQ at  http://www.tux.org/lkml/
> >
> 
> 

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 13/41] richacl: Check if an acl is equivalent to a file mode
@ 2015-09-21 15:31                   ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-21 15:31 UTC (permalink / raw)
  To: Austin S Hemmelgarn
  Cc: Andreas Gruenbacher, linux-kernel, linux-fsdevel, linux-nfs,
	linux-api, linux-cifs, linux-security-module

On Mon, Sep 21, 2015 at 09:59:07AM -0400, Austin S Hemmelgarn wrote:
> Mode equivalence get's even trickier when you throw in permissions
> just beyond rwx

Note by the way that the major technical achievement here is the
reconciliation of two completely different evaluation algorithms.
(Windows/v4-like ACLs are evaluated top-to-bottom and stop as soon as
the requested permissions are allowed, or a remaining requested
permission is denied. Mode bits match the requester first and then check
the permissions associated with that requester.)

(You're correct that reconciling the permission bits is tricky (and
requires some judgement calls), but that's conceptually simpler.)

And then the handling of ID's looks like the major remaining problem.

--b.

> (for example, by Windows
> standards, the usage of the execute bit on directories is weird
> (they have a separate permission in their ACE's for directory
> listing), or by VMS standards, write permission on a directory
> doesn't mean that you can delete things in it (VMS actually had a
> separate bit for the delete permission, and even had separate
> permissions for system access)).
> >
> >I think the choices you've made probably make the most sense, they just
> >wouldn't have been obvious to me.  Anyway, so, OK by me:
> >
> >	Reviewed-by: J. Bruce Fields <bfields@redhat.com>
> >
> >--b.
> >
> >>
> >>--b.
> >>
> >>>
> >>>Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> >>>---
> >>>  fs/richacl_base.c       | 104 ++++++++++++++++++++++++++++++++++++++++++++++++
> >>>  include/linux/richacl.h |   1 +
> >>>  2 files changed, 105 insertions(+)
> >>>
> >>>diff --git a/fs/richacl_base.c b/fs/richacl_base.c
> >>>index 3163152..106e988 100644
> >>>--- a/fs/richacl_base.c
> >>>+++ b/fs/richacl_base.c
> >>>@@ -379,3 +379,107 @@ richacl_chmod(struct richacl *acl, mode_t mode)
> >>>  	return clone;
> >>>  }
> >>>  EXPORT_SYMBOL_GPL(richacl_chmod);
> >>>+
> >>>+/**
> >>>+ * richacl_equiv_mode  -  compute the mode equivalent of @acl
> >>>+ *
> >>>+ * An acl is considered equivalent to a file mode if it only consists of
> >>>+ * owner@, group@, and everyone@ entries and the owner@ permissions do not
> >>>+ * depend on whether the owner is a member in the owning group.
> >>>+ */
> >>>+int
> >>>+richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
> >>>+{
> >>>+	mode_t mode = *mode_p;
> >>>+
> >>>+	/*
> >>>+	 * The RICHACE_DELETE_CHILD flag is meaningless for non-directories, so
> >>>+	 * we ignore it.
> >>>+	 */
> >>>+	unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> >>>+	struct {
> >>>+		unsigned int allowed;
> >>>+		unsigned int defined;  /* allowed or denied */
> >>>+	} owner = {
> >>>+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED |
> >>>+			   RICHACE_POSIX_OWNER_ALLOWED  | x,
> >>>+	}, group = {
> >>>+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
> >>>+	}, everyone = {
> >>>+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
> >>>+	};
> >>>+	const struct richace *ace;
> >>>+
> >>>+	if (acl->a_flags & ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED))
> >>>+		return -1;
> >>>+
> >>>+	richacl_for_each_entry(ace, acl) {
> >>>+		if (ace->e_flags & ~RICHACE_SPECIAL_WHO)
> >>>+			return -1;
> >>>+
> >>>+		if (richace_is_owner(ace) || richace_is_everyone(ace)) {
> >>>+			x = ace->e_mask & ~owner.defined;
> >>>+			if (richace_is_allow(ace)) {
> >>>+				unsigned int group_denied =
> >>>+					group.defined & ~group.allowed;
> >>>+
> >>>+				if (x & group_denied)
> >>>+					return -1;
> >>>+				owner.allowed |= x;
> >>>+			} else /* if (richace_is_deny(ace)) */ {
> >>>+				if (x & group.allowed)
> >>>+					return -1;
> >>>+			}
> >>>+			owner.defined |= x;
> >>>+
> >>>+			if (richace_is_everyone(ace)) {
> >>>+				x = ace->e_mask;
> >>>+				if (richace_is_allow(ace)) {
> >>>+					group.allowed |=
> >>>+						x & ~group.defined;
> >>>+					everyone.allowed |=
> >>>+						x & ~everyone.defined;
> >>>+				}
> >>>+				group.defined |= x;
> >>>+				everyone.defined |= x;
> >>>+			}
> >>>+		} else if (richace_is_group(ace)) {
> >>>+			x = ace->e_mask & ~group.defined;
> >>>+			if (richace_is_allow(ace))
> >>>+				group.allowed |= x;
> >>>+			group.defined |= x;
> >>>+		} else
> >>>+			return -1;
> >>>+	}
> >>>+
> >>>+	if (group.allowed & ~owner.defined)
> >>>+		return -1;
> >>>+
> >>>+	if (acl->a_flags & RICHACL_MASKED) {
> >>>+		if (acl->a_flags & RICHACL_WRITE_THROUGH) {
> >>>+			owner.allowed = acl->a_owner_mask;
> >>>+			everyone.allowed = acl->a_other_mask;
> >>>+		} else {
> >>>+			owner.allowed &= acl->a_owner_mask;
> >>>+			everyone.allowed &= acl->a_other_mask;
> >>>+		}
> >>>+		group.allowed &= acl->a_group_mask;
> >>>+	}
> >>>+
> >>>+	mode = (mode & ~S_IRWXUGO) |
> >>>+	       (richacl_mask_to_mode(owner.allowed) << 6) |
> >>>+	       (richacl_mask_to_mode(group.allowed) << 3) |
> >>>+		richacl_mask_to_mode(everyone.allowed);
> >>>+
> >>>+	/* Mask flags we can ignore */
> >>>+	x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> >>>+
> >>>+	if (((richacl_mode_to_mask(mode >> 6) ^ owner.allowed)    & ~x) ||
> >>>+	    ((richacl_mode_to_mask(mode >> 3) ^ group.allowed)    & ~x) ||
> >>>+	    ((richacl_mode_to_mask(mode)      ^ everyone.allowed) & ~x))
> >>>+		return -1;
> >>>+
> >>>+	*mode_p = mode;
> >>>+	return 0;
> >>>+}
> >>>+EXPORT_SYMBOL_GPL(richacl_equiv_mode);
> >>>diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> >>>index d4a576c..6535ce5 100644
> >>>--- a/include/linux/richacl.h
> >>>+++ b/include/linux/richacl.h
> >>>@@ -304,6 +304,7 @@ extern unsigned int richacl_mode_to_mask(mode_t);
> >>>  extern unsigned int richacl_want_to_mask(unsigned int);
> >>>  extern void richacl_compute_max_masks(struct richacl *);
> >>>  extern struct richacl *richacl_chmod(struct richacl *, mode_t);
> >>>+extern int richacl_equiv_mode(const struct richacl *, mode_t *);
> >>>
> >>>  /* richacl_inode.c */
> >>>  extern int richacl_permission(struct inode *, const struct richacl *, int);
> >>>--
> >>>2.4.3
> >>>
> >>>--
> >>>To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> >>>the body of a message to majordomo@vger.kernel.org
> >>>More majordomo info at  http://vger.kernel.org/majordomo-info.html
> >--
> >To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> >the body of a message to majordomo@vger.kernel.org
> >More majordomo info at  http://vger.kernel.org/majordomo-info.html
> >Please read the FAQ at  http://www.tux.org/lkml/
> >
> 
> 



^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 13/41] richacl: Check if an acl is equivalent to a file mode
  2015-09-21 14:38                   ` J. Bruce Fields
@ 2015-09-21 17:00                       ` Austin S Hemmelgarn
  -1 siblings, 0 replies; 188+ messages in thread
From: Austin S Hemmelgarn @ 2015-09-21 17:00 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: Andreas Gruenbacher, linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

[-- Attachment #1: Type: text/plain, Size: 9424 bytes --]

On 2015-09-21 10:38, J. Bruce Fields wrote:
> On Mon, Sep 21, 2015 at 09:59:07AM -0400, Austin S Hemmelgarn wrote:
>> On 2015-09-17 20:56, J. Bruce Fields wrote:
>>> On Thu, Sep 17, 2015 at 02:22:19PM -0400, bfields wrote:
>>>> On Sat, Sep 05, 2015 at 12:27:08PM +0200, Andreas Gruenbacher wrote:
>>>>> ACLs are considered equivalent to file modes if they only consist of
>>>>> owner@, group@, and everyone@ entries, the owner@ permissions do not
>>>>> depend on whether the owner is a member in the owning group, and no
>>>>> inheritance flags are set.  This test is used to avoid storing richacls
>>>>> if the acl can be computed from the file permission bits.
>>>>
>>>> We're assuming here that it's OK for us to silently rearrange an ACL as
>>>> long as the result is still equivalent (in the sense that the permission
>>>> algorithm would always produce the same result).
>>>>
>>>> I guess that's OK by me, but it might violate user expectations in some
>>>> simple common cases, so may be worth mentioning in documentation
>>>> someplace if we don't already.
>>>
>>> Also your notion of mode-equivalence here is interesting, it's actually
>>> a strict subset of the ACLs that produce the same permission results as
>>> a mode.  (For example, everyone:rwx,bfields:rwx is equivalent to 0777
>>> but won't be considered mode-equivalent by this algorithm.)
>> Although it could also be equivalent to 0707, or (if bfields is the
>> group name also) 0077, or even (if bfields isn't the group or owner
>> of the file) 0007.
>
> I disagree.  I think you've misread my example ACL (may be my sloppy
> notation, sorry) or misunderstood the ACL evalutation algorithm.
I'm just saying in general that there isn't enough information without 
knowing not only the ACL, but also the ownership information, to 
determine exact mode-equivalence.  I didn't phrase it well to convey 
this though.

I can kind of understand the more I think about it why an ACL only 
covering the owner and everyone permissions would not be considered 
equivalent to 0777 (strictly speaking it's 0707, even though (I think) 
this behaves like 0777).
>
>> Mode equivalence get's even trickier when you
>> throw in permissions just beyond rwx (for example, by Windows
>> standards, the usage of the execute bit on directories is weird
>> (they have a separate permission in their ACE's for directory
>> listing), or by VMS standards, write permission on a directory
>> doesn't mean that you can delete things in it (VMS actually had a
>> separate bit for the delete permission, and even had separate
>> permissions for system access)).
>
> I believe these patches handle all of those details correctly; if you
> see anything to the contrary, please do speak up.
I see absolutely nothing wrong with them, I was just trying to point out 
that when you consider all the permissions allowed under the proposed 
system, mode-equivalence get's really tricky.
> Note that Windows also has a DELETE bit, though it is ORed with the
> directory permission not ANDed (so it is sufficient for the directory to
> allow MAY_DELETE_CHILD *or* for the file to allow DELETE).
Yeah, which has ironically caused issues for a number of people I know 
before.  There's all kinds of weird things you can do with Windows 
ACE's, for example, I'm pretty sure it's possible to let someone read 
just the file contents, but nothing else about it, or make it so that 
someone can modify the timestamps on it but not do anything else to it.
>
> (But I believe you're correct that VMS required both permissions, if
> e.g. http://www.djesys.com/vms/freevms/mentor/vms_prot.html is correct).
>
> --b.
>
>>>
>>> I think the choices you've made probably make the most sense, they just
>>> wouldn't have been obvious to me.  Anyway, so, OK by me:
>>>
>>> 	Reviewed-by: J. Bruce Fields <bfields-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
>>>
>>> --b.
>>>
>>>>
>>>> --b.
>>>>
>>>>>
>>>>> Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
>>>>> ---
>>>>>   fs/richacl_base.c       | 104 ++++++++++++++++++++++++++++++++++++++++++++++++
>>>>>   include/linux/richacl.h |   1 +
>>>>>   2 files changed, 105 insertions(+)
>>>>>
>>>>> diff --git a/fs/richacl_base.c b/fs/richacl_base.c
>>>>> index 3163152..106e988 100644
>>>>> --- a/fs/richacl_base.c
>>>>> +++ b/fs/richacl_base.c
>>>>> @@ -379,3 +379,107 @@ richacl_chmod(struct richacl *acl, mode_t mode)
>>>>>   	return clone;
>>>>>   }
>>>>>   EXPORT_SYMBOL_GPL(richacl_chmod);
>>>>> +
>>>>> +/**
>>>>> + * richacl_equiv_mode  -  compute the mode equivalent of @acl
>>>>> + *
>>>>> + * An acl is considered equivalent to a file mode if it only consists of
>>>>> + * owner@, group@, and everyone@ entries and the owner@ permissions do not
>>>>> + * depend on whether the owner is a member in the owning group.
>>>>> + */
>>>>> +int
>>>>> +richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
>>>>> +{
>>>>> +	mode_t mode = *mode_p;
>>>>> +
>>>>> +	/*
>>>>> +	 * The RICHACE_DELETE_CHILD flag is meaningless for non-directories, so
>>>>> +	 * we ignore it.
>>>>> +	 */
>>>>> +	unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
>>>>> +	struct {
>>>>> +		unsigned int allowed;
>>>>> +		unsigned int defined;  /* allowed or denied */
>>>>> +	} owner = {
>>>>> +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED |
>>>>> +			   RICHACE_POSIX_OWNER_ALLOWED  | x,
>>>>> +	}, group = {
>>>>> +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
>>>>> +	}, everyone = {
>>>>> +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
>>>>> +	};
>>>>> +	const struct richace *ace;
>>>>> +
>>>>> +	if (acl->a_flags & ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED))
>>>>> +		return -1;
>>>>> +
>>>>> +	richacl_for_each_entry(ace, acl) {
>>>>> +		if (ace->e_flags & ~RICHACE_SPECIAL_WHO)
>>>>> +			return -1;
>>>>> +
>>>>> +		if (richace_is_owner(ace) || richace_is_everyone(ace)) {
>>>>> +			x = ace->e_mask & ~owner.defined;
>>>>> +			if (richace_is_allow(ace)) {
>>>>> +				unsigned int group_denied =
>>>>> +					group.defined & ~group.allowed;
>>>>> +
>>>>> +				if (x & group_denied)
>>>>> +					return -1;
>>>>> +				owner.allowed |= x;
>>>>> +			} else /* if (richace_is_deny(ace)) */ {
>>>>> +				if (x & group.allowed)
>>>>> +					return -1;
>>>>> +			}
>>>>> +			owner.defined |= x;
>>>>> +
>>>>> +			if (richace_is_everyone(ace)) {
>>>>> +				x = ace->e_mask;
>>>>> +				if (richace_is_allow(ace)) {
>>>>> +					group.allowed |=
>>>>> +						x & ~group.defined;
>>>>> +					everyone.allowed |=
>>>>> +						x & ~everyone.defined;
>>>>> +				}
>>>>> +				group.defined |= x;
>>>>> +				everyone.defined |= x;
>>>>> +			}
>>>>> +		} else if (richace_is_group(ace)) {
>>>>> +			x = ace->e_mask & ~group.defined;
>>>>> +			if (richace_is_allow(ace))
>>>>> +				group.allowed |= x;
>>>>> +			group.defined |= x;
>>>>> +		} else
>>>>> +			return -1;
>>>>> +	}
>>>>> +
>>>>> +	if (group.allowed & ~owner.defined)
>>>>> +		return -1;
>>>>> +
>>>>> +	if (acl->a_flags & RICHACL_MASKED) {
>>>>> +		if (acl->a_flags & RICHACL_WRITE_THROUGH) {
>>>>> +			owner.allowed = acl->a_owner_mask;
>>>>> +			everyone.allowed = acl->a_other_mask;
>>>>> +		} else {
>>>>> +			owner.allowed &= acl->a_owner_mask;
>>>>> +			everyone.allowed &= acl->a_other_mask;
>>>>> +		}
>>>>> +		group.allowed &= acl->a_group_mask;
>>>>> +	}
>>>>> +
>>>>> +	mode = (mode & ~S_IRWXUGO) |
>>>>> +	       (richacl_mask_to_mode(owner.allowed) << 6) |
>>>>> +	       (richacl_mask_to_mode(group.allowed) << 3) |
>>>>> +		richacl_mask_to_mode(everyone.allowed);
>>>>> +
>>>>> +	/* Mask flags we can ignore */
>>>>> +	x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
>>>>> +
>>>>> +	if (((richacl_mode_to_mask(mode >> 6) ^ owner.allowed)    & ~x) ||
>>>>> +	    ((richacl_mode_to_mask(mode >> 3) ^ group.allowed)    & ~x) ||
>>>>> +	    ((richacl_mode_to_mask(mode)      ^ everyone.allowed) & ~x))
>>>>> +		return -1;
>>>>> +
>>>>> +	*mode_p = mode;
>>>>> +	return 0;
>>>>> +}
>>>>> +EXPORT_SYMBOL_GPL(richacl_equiv_mode);
>>>>> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
>>>>> index d4a576c..6535ce5 100644
>>>>> --- a/include/linux/richacl.h
>>>>> +++ b/include/linux/richacl.h
>>>>> @@ -304,6 +304,7 @@ extern unsigned int richacl_mode_to_mask(mode_t);
>>>>>   extern unsigned int richacl_want_to_mask(unsigned int);
>>>>>   extern void richacl_compute_max_masks(struct richacl *);
>>>>>   extern struct richacl *richacl_chmod(struct richacl *, mode_t);
>>>>> +extern int richacl_equiv_mode(const struct richacl *, mode_t *);
>>>>>
>>>>>   /* richacl_inode.c */
>>>>>   extern int richacl_permission(struct inode *, const struct richacl *, int);
>>>>> --
>>>>> 2.4.3
>>>>>
>>>>> --
>>>>> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
>>>>> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
>>>>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>>> --
>>> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
>>> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
>>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>>> Please read the FAQ at  http://www.tux.org/lkml/
>>>
>>
>>
>
>



[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 3019 bytes --]

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 13/41] richacl: Check if an acl is equivalent to a file mode
@ 2015-09-21 17:00                       ` Austin S Hemmelgarn
  0 siblings, 0 replies; 188+ messages in thread
From: Austin S Hemmelgarn @ 2015-09-21 17:00 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: Andreas Gruenbacher, linux-kernel, linux-fsdevel, linux-nfs,
	linux-api, linux-cifs, linux-security-module

[-- Attachment #1: Type: text/plain, Size: 9318 bytes --]

On 2015-09-21 10:38, J. Bruce Fields wrote:
> On Mon, Sep 21, 2015 at 09:59:07AM -0400, Austin S Hemmelgarn wrote:
>> On 2015-09-17 20:56, J. Bruce Fields wrote:
>>> On Thu, Sep 17, 2015 at 02:22:19PM -0400, bfields wrote:
>>>> On Sat, Sep 05, 2015 at 12:27:08PM +0200, Andreas Gruenbacher wrote:
>>>>> ACLs are considered equivalent to file modes if they only consist of
>>>>> owner@, group@, and everyone@ entries, the owner@ permissions do not
>>>>> depend on whether the owner is a member in the owning group, and no
>>>>> inheritance flags are set.  This test is used to avoid storing richacls
>>>>> if the acl can be computed from the file permission bits.
>>>>
>>>> We're assuming here that it's OK for us to silently rearrange an ACL as
>>>> long as the result is still equivalent (in the sense that the permission
>>>> algorithm would always produce the same result).
>>>>
>>>> I guess that's OK by me, but it might violate user expectations in some
>>>> simple common cases, so may be worth mentioning in documentation
>>>> someplace if we don't already.
>>>
>>> Also your notion of mode-equivalence here is interesting, it's actually
>>> a strict subset of the ACLs that produce the same permission results as
>>> a mode.  (For example, everyone:rwx,bfields:rwx is equivalent to 0777
>>> but won't be considered mode-equivalent by this algorithm.)
>> Although it could also be equivalent to 0707, or (if bfields is the
>> group name also) 0077, or even (if bfields isn't the group or owner
>> of the file) 0007.
>
> I disagree.  I think you've misread my example ACL (may be my sloppy
> notation, sorry) or misunderstood the ACL evalutation algorithm.
I'm just saying in general that there isn't enough information without 
knowing not only the ACL, but also the ownership information, to 
determine exact mode-equivalence.  I didn't phrase it well to convey 
this though.

I can kind of understand the more I think about it why an ACL only 
covering the owner and everyone permissions would not be considered 
equivalent to 0777 (strictly speaking it's 0707, even though (I think) 
this behaves like 0777).
>
>> Mode equivalence get's even trickier when you
>> throw in permissions just beyond rwx (for example, by Windows
>> standards, the usage of the execute bit on directories is weird
>> (they have a separate permission in their ACE's for directory
>> listing), or by VMS standards, write permission on a directory
>> doesn't mean that you can delete things in it (VMS actually had a
>> separate bit for the delete permission, and even had separate
>> permissions for system access)).
>
> I believe these patches handle all of those details correctly; if you
> see anything to the contrary, please do speak up.
I see absolutely nothing wrong with them, I was just trying to point out 
that when you consider all the permissions allowed under the proposed 
system, mode-equivalence get's really tricky.
> Note that Windows also has a DELETE bit, though it is ORed with the
> directory permission not ANDed (so it is sufficient for the directory to
> allow MAY_DELETE_CHILD *or* for the file to allow DELETE).
Yeah, which has ironically caused issues for a number of people I know 
before.  There's all kinds of weird things you can do with Windows 
ACE's, for example, I'm pretty sure it's possible to let someone read 
just the file contents, but nothing else about it, or make it so that 
someone can modify the timestamps on it but not do anything else to it.
>
> (But I believe you're correct that VMS required both permissions, if
> e.g. http://www.djesys.com/vms/freevms/mentor/vms_prot.html is correct).
>
> --b.
>
>>>
>>> I think the choices you've made probably make the most sense, they just
>>> wouldn't have been obvious to me.  Anyway, so, OK by me:
>>>
>>> 	Reviewed-by: J. Bruce Fields <bfields@redhat.com>
>>>
>>> --b.
>>>
>>>>
>>>> --b.
>>>>
>>>>>
>>>>> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
>>>>> ---
>>>>>   fs/richacl_base.c       | 104 ++++++++++++++++++++++++++++++++++++++++++++++++
>>>>>   include/linux/richacl.h |   1 +
>>>>>   2 files changed, 105 insertions(+)
>>>>>
>>>>> diff --git a/fs/richacl_base.c b/fs/richacl_base.c
>>>>> index 3163152..106e988 100644
>>>>> --- a/fs/richacl_base.c
>>>>> +++ b/fs/richacl_base.c
>>>>> @@ -379,3 +379,107 @@ richacl_chmod(struct richacl *acl, mode_t mode)
>>>>>   	return clone;
>>>>>   }
>>>>>   EXPORT_SYMBOL_GPL(richacl_chmod);
>>>>> +
>>>>> +/**
>>>>> + * richacl_equiv_mode  -  compute the mode equivalent of @acl
>>>>> + *
>>>>> + * An acl is considered equivalent to a file mode if it only consists of
>>>>> + * owner@, group@, and everyone@ entries and the owner@ permissions do not
>>>>> + * depend on whether the owner is a member in the owning group.
>>>>> + */
>>>>> +int
>>>>> +richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
>>>>> +{
>>>>> +	mode_t mode = *mode_p;
>>>>> +
>>>>> +	/*
>>>>> +	 * The RICHACE_DELETE_CHILD flag is meaningless for non-directories, so
>>>>> +	 * we ignore it.
>>>>> +	 */
>>>>> +	unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
>>>>> +	struct {
>>>>> +		unsigned int allowed;
>>>>> +		unsigned int defined;  /* allowed or denied */
>>>>> +	} owner = {
>>>>> +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED |
>>>>> +			   RICHACE_POSIX_OWNER_ALLOWED  | x,
>>>>> +	}, group = {
>>>>> +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
>>>>> +	}, everyone = {
>>>>> +		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
>>>>> +	};
>>>>> +	const struct richace *ace;
>>>>> +
>>>>> +	if (acl->a_flags & ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED))
>>>>> +		return -1;
>>>>> +
>>>>> +	richacl_for_each_entry(ace, acl) {
>>>>> +		if (ace->e_flags & ~RICHACE_SPECIAL_WHO)
>>>>> +			return -1;
>>>>> +
>>>>> +		if (richace_is_owner(ace) || richace_is_everyone(ace)) {
>>>>> +			x = ace->e_mask & ~owner.defined;
>>>>> +			if (richace_is_allow(ace)) {
>>>>> +				unsigned int group_denied =
>>>>> +					group.defined & ~group.allowed;
>>>>> +
>>>>> +				if (x & group_denied)
>>>>> +					return -1;
>>>>> +				owner.allowed |= x;
>>>>> +			} else /* if (richace_is_deny(ace)) */ {
>>>>> +				if (x & group.allowed)
>>>>> +					return -1;
>>>>> +			}
>>>>> +			owner.defined |= x;
>>>>> +
>>>>> +			if (richace_is_everyone(ace)) {
>>>>> +				x = ace->e_mask;
>>>>> +				if (richace_is_allow(ace)) {
>>>>> +					group.allowed |=
>>>>> +						x & ~group.defined;
>>>>> +					everyone.allowed |=
>>>>> +						x & ~everyone.defined;
>>>>> +				}
>>>>> +				group.defined |= x;
>>>>> +				everyone.defined |= x;
>>>>> +			}
>>>>> +		} else if (richace_is_group(ace)) {
>>>>> +			x = ace->e_mask & ~group.defined;
>>>>> +			if (richace_is_allow(ace))
>>>>> +				group.allowed |= x;
>>>>> +			group.defined |= x;
>>>>> +		} else
>>>>> +			return -1;
>>>>> +	}
>>>>> +
>>>>> +	if (group.allowed & ~owner.defined)
>>>>> +		return -1;
>>>>> +
>>>>> +	if (acl->a_flags & RICHACL_MASKED) {
>>>>> +		if (acl->a_flags & RICHACL_WRITE_THROUGH) {
>>>>> +			owner.allowed = acl->a_owner_mask;
>>>>> +			everyone.allowed = acl->a_other_mask;
>>>>> +		} else {
>>>>> +			owner.allowed &= acl->a_owner_mask;
>>>>> +			everyone.allowed &= acl->a_other_mask;
>>>>> +		}
>>>>> +		group.allowed &= acl->a_group_mask;
>>>>> +	}
>>>>> +
>>>>> +	mode = (mode & ~S_IRWXUGO) |
>>>>> +	       (richacl_mask_to_mode(owner.allowed) << 6) |
>>>>> +	       (richacl_mask_to_mode(group.allowed) << 3) |
>>>>> +		richacl_mask_to_mode(everyone.allowed);
>>>>> +
>>>>> +	/* Mask flags we can ignore */
>>>>> +	x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
>>>>> +
>>>>> +	if (((richacl_mode_to_mask(mode >> 6) ^ owner.allowed)    & ~x) ||
>>>>> +	    ((richacl_mode_to_mask(mode >> 3) ^ group.allowed)    & ~x) ||
>>>>> +	    ((richacl_mode_to_mask(mode)      ^ everyone.allowed) & ~x))
>>>>> +		return -1;
>>>>> +
>>>>> +	*mode_p = mode;
>>>>> +	return 0;
>>>>> +}
>>>>> +EXPORT_SYMBOL_GPL(richacl_equiv_mode);
>>>>> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
>>>>> index d4a576c..6535ce5 100644
>>>>> --- a/include/linux/richacl.h
>>>>> +++ b/include/linux/richacl.h
>>>>> @@ -304,6 +304,7 @@ extern unsigned int richacl_mode_to_mask(mode_t);
>>>>>   extern unsigned int richacl_want_to_mask(unsigned int);
>>>>>   extern void richacl_compute_max_masks(struct richacl *);
>>>>>   extern struct richacl *richacl_chmod(struct richacl *, mode_t);
>>>>> +extern int richacl_equiv_mode(const struct richacl *, mode_t *);
>>>>>
>>>>>   /* richacl_inode.c */
>>>>>   extern int richacl_permission(struct inode *, const struct richacl *, int);
>>>>> --
>>>>> 2.4.3
>>>>>
>>>>> --
>>>>> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
>>>>> the body of a message to majordomo@vger.kernel.org
>>>>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>>> --
>>> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
>>> the body of a message to majordomo@vger.kernel.org
>>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>>> Please read the FAQ at  http://www.tux.org/lkml/
>>>
>>
>>
>
>



[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 3019 bytes --]

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 13/41] richacl: Check if an acl is equivalent to a file mode
  2015-09-21 17:00                       ` Austin S Hemmelgarn
  (?)
@ 2015-09-21 17:48                       ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-21 17:48 UTC (permalink / raw)
  To: Austin S Hemmelgarn
  Cc: Andreas Gruenbacher, linux-kernel, linux-fsdevel, linux-nfs,
	linux-api, linux-cifs, linux-security-module

On Mon, Sep 21, 2015 at 01:00:40PM -0400, Austin S Hemmelgarn wrote:
> On 2015-09-21 10:38, J. Bruce Fields wrote:
> >On Mon, Sep 21, 2015 at 09:59:07AM -0400, Austin S Hemmelgarn wrote:
> >>On 2015-09-17 20:56, J. Bruce Fields wrote:
> >>>On Thu, Sep 17, 2015 at 02:22:19PM -0400, bfields wrote:
> >>>>On Sat, Sep 05, 2015 at 12:27:08PM +0200, Andreas Gruenbacher wrote:
> >>>>>ACLs are considered equivalent to file modes if they only consist of
> >>>>>owner@, group@, and everyone@ entries, the owner@ permissions do not
> >>>>>depend on whether the owner is a member in the owning group, and no
> >>>>>inheritance flags are set.  This test is used to avoid storing richacls
> >>>>>if the acl can be computed from the file permission bits.
> >>>>
> >>>>We're assuming here that it's OK for us to silently rearrange an ACL as
> >>>>long as the result is still equivalent (in the sense that the permission
> >>>>algorithm would always produce the same result).
> >>>>
> >>>>I guess that's OK by me, but it might violate user expectations in some
> >>>>simple common cases, so may be worth mentioning in documentation
> >>>>someplace if we don't already.
> >>>
> >>>Also your notion of mode-equivalence here is interesting, it's actually
> >>>a strict subset of the ACLs that produce the same permission results as
> >>>a mode.  (For example, everyone:rwx,bfields:rwx is equivalent to 0777
> >>>but won't be considered mode-equivalent by this algorithm.)
> >>Although it could also be equivalent to 0707, or (if bfields is the
> >>group name also) 0077, or even (if bfields isn't the group or owner
> >>of the file) 0007.
> >
> >I disagree.  I think you've misread my example ACL (may be my sloppy
> >notation, sorry) or misunderstood the ACL evalutation algorithm.
> I'm just saying in general that there isn't enough information
> without knowing not only the ACL, but also the ownership
> information, to determine exact mode-equivalence.

Note the first ACE in that ACL gives everyone all permissions, the
second ACE is never even consulted.  There's no doubt that 0777 is the
only mode we could consider that equivalent to.

--b.

> I didn't phrase it well to convey this though.
> 
> I can kind of understand the more I think about it why an ACL only
> covering the owner and everyone permissions would not be considered
> equivalent to 0777 (strictly speaking it's 0707, even though (I
> think) this behaves like 0777).
> >
> >>Mode equivalence get's even trickier when you
> >>throw in permissions just beyond rwx (for example, by Windows
> >>standards, the usage of the execute bit on directories is weird
> >>(they have a separate permission in their ACE's for directory
> >>listing), or by VMS standards, write permission on a directory
> >>doesn't mean that you can delete things in it (VMS actually had a
> >>separate bit for the delete permission, and even had separate
> >>permissions for system access)).
> >
> >I believe these patches handle all of those details correctly; if you
> >see anything to the contrary, please do speak up.
> I see absolutely nothing wrong with them, I was just trying to point
> out that when you consider all the permissions allowed under the
> proposed system, mode-equivalence get's really tricky.
> >Note that Windows also has a DELETE bit, though it is ORed with the
> >directory permission not ANDed (so it is sufficient for the directory to
> >allow MAY_DELETE_CHILD *or* for the file to allow DELETE).
> Yeah, which has ironically caused issues for a number of people I
> know before.  There's all kinds of weird things you can do with
> Windows ACE's, for example, I'm pretty sure it's possible to let
> someone read just the file contents, but nothing else about it, or
> make it so that someone can modify the timestamps on it but not do
> anything else to it.
> >
> >(But I believe you're correct that VMS required both permissions, if
> >e.g. http://www.djesys.com/vms/freevms/mentor/vms_prot.html is correct).
> >
> >--b.
> >
> >>>
> >>>I think the choices you've made probably make the most sense, they just
> >>>wouldn't have been obvious to me.  Anyway, so, OK by me:
> >>>
> >>>	Reviewed-by: J. Bruce Fields <bfields@redhat.com>
> >>>
> >>>--b.
> >>>
> >>>>
> >>>>--b.
> >>>>
> >>>>>
> >>>>>Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> >>>>>---
> >>>>>  fs/richacl_base.c       | 104 ++++++++++++++++++++++++++++++++++++++++++++++++
> >>>>>  include/linux/richacl.h |   1 +
> >>>>>  2 files changed, 105 insertions(+)
> >>>>>
> >>>>>diff --git a/fs/richacl_base.c b/fs/richacl_base.c
> >>>>>index 3163152..106e988 100644
> >>>>>--- a/fs/richacl_base.c
> >>>>>+++ b/fs/richacl_base.c
> >>>>>@@ -379,3 +379,107 @@ richacl_chmod(struct richacl *acl, mode_t mode)
> >>>>>  	return clone;
> >>>>>  }
> >>>>>  EXPORT_SYMBOL_GPL(richacl_chmod);
> >>>>>+
> >>>>>+/**
> >>>>>+ * richacl_equiv_mode  -  compute the mode equivalent of @acl
> >>>>>+ *
> >>>>>+ * An acl is considered equivalent to a file mode if it only consists of
> >>>>>+ * owner@, group@, and everyone@ entries and the owner@ permissions do not
> >>>>>+ * depend on whether the owner is a member in the owning group.
> >>>>>+ */
> >>>>>+int
> >>>>>+richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
> >>>>>+{
> >>>>>+	mode_t mode = *mode_p;
> >>>>>+
> >>>>>+	/*
> >>>>>+	 * The RICHACE_DELETE_CHILD flag is meaningless for non-directories, so
> >>>>>+	 * we ignore it.
> >>>>>+	 */
> >>>>>+	unsigned int x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> >>>>>+	struct {
> >>>>>+		unsigned int allowed;
> >>>>>+		unsigned int defined;  /* allowed or denied */
> >>>>>+	} owner = {
> >>>>>+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED |
> >>>>>+			   RICHACE_POSIX_OWNER_ALLOWED  | x,
> >>>>>+	}, group = {
> >>>>>+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
> >>>>>+	}, everyone = {
> >>>>>+		.defined = RICHACE_POSIX_ALWAYS_ALLOWED | x,
> >>>>>+	};
> >>>>>+	const struct richace *ace;
> >>>>>+
> >>>>>+	if (acl->a_flags & ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED))
> >>>>>+		return -1;
> >>>>>+
> >>>>>+	richacl_for_each_entry(ace, acl) {
> >>>>>+		if (ace->e_flags & ~RICHACE_SPECIAL_WHO)
> >>>>>+			return -1;
> >>>>>+
> >>>>>+		if (richace_is_owner(ace) || richace_is_everyone(ace)) {
> >>>>>+			x = ace->e_mask & ~owner.defined;
> >>>>>+			if (richace_is_allow(ace)) {
> >>>>>+				unsigned int group_denied =
> >>>>>+					group.defined & ~group.allowed;
> >>>>>+
> >>>>>+				if (x & group_denied)
> >>>>>+					return -1;
> >>>>>+				owner.allowed |= x;
> >>>>>+			} else /* if (richace_is_deny(ace)) */ {
> >>>>>+				if (x & group.allowed)
> >>>>>+					return -1;
> >>>>>+			}
> >>>>>+			owner.defined |= x;
> >>>>>+
> >>>>>+			if (richace_is_everyone(ace)) {
> >>>>>+				x = ace->e_mask;
> >>>>>+				if (richace_is_allow(ace)) {
> >>>>>+					group.allowed |=
> >>>>>+						x & ~group.defined;
> >>>>>+					everyone.allowed |=
> >>>>>+						x & ~everyone.defined;
> >>>>>+				}
> >>>>>+				group.defined |= x;
> >>>>>+				everyone.defined |= x;
> >>>>>+			}
> >>>>>+		} else if (richace_is_group(ace)) {
> >>>>>+			x = ace->e_mask & ~group.defined;
> >>>>>+			if (richace_is_allow(ace))
> >>>>>+				group.allowed |= x;
> >>>>>+			group.defined |= x;
> >>>>>+		} else
> >>>>>+			return -1;
> >>>>>+	}
> >>>>>+
> >>>>>+	if (group.allowed & ~owner.defined)
> >>>>>+		return -1;
> >>>>>+
> >>>>>+	if (acl->a_flags & RICHACL_MASKED) {
> >>>>>+		if (acl->a_flags & RICHACL_WRITE_THROUGH) {
> >>>>>+			owner.allowed = acl->a_owner_mask;
> >>>>>+			everyone.allowed = acl->a_other_mask;
> >>>>>+		} else {
> >>>>>+			owner.allowed &= acl->a_owner_mask;
> >>>>>+			everyone.allowed &= acl->a_other_mask;
> >>>>>+		}
> >>>>>+		group.allowed &= acl->a_group_mask;
> >>>>>+	}
> >>>>>+
> >>>>>+	mode = (mode & ~S_IRWXUGO) |
> >>>>>+	       (richacl_mask_to_mode(owner.allowed) << 6) |
> >>>>>+	       (richacl_mask_to_mode(group.allowed) << 3) |
> >>>>>+		richacl_mask_to_mode(everyone.allowed);
> >>>>>+
> >>>>>+	/* Mask flags we can ignore */
> >>>>>+	x = S_ISDIR(mode) ? 0 : RICHACE_DELETE_CHILD;
> >>>>>+
> >>>>>+	if (((richacl_mode_to_mask(mode >> 6) ^ owner.allowed)    & ~x) ||
> >>>>>+	    ((richacl_mode_to_mask(mode >> 3) ^ group.allowed)    & ~x) ||
> >>>>>+	    ((richacl_mode_to_mask(mode)      ^ everyone.allowed) & ~x))
> >>>>>+		return -1;
> >>>>>+
> >>>>>+	*mode_p = mode;
> >>>>>+	return 0;
> >>>>>+}
> >>>>>+EXPORT_SYMBOL_GPL(richacl_equiv_mode);
> >>>>>diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> >>>>>index d4a576c..6535ce5 100644
> >>>>>--- a/include/linux/richacl.h
> >>>>>+++ b/include/linux/richacl.h
> >>>>>@@ -304,6 +304,7 @@ extern unsigned int richacl_mode_to_mask(mode_t);
> >>>>>  extern unsigned int richacl_want_to_mask(unsigned int);
> >>>>>  extern void richacl_compute_max_masks(struct richacl *);
> >>>>>  extern struct richacl *richacl_chmod(struct richacl *, mode_t);
> >>>>>+extern int richacl_equiv_mode(const struct richacl *, mode_t *);
> >>>>>
> >>>>>  /* richacl_inode.c */
> >>>>>  extern int richacl_permission(struct inode *, const struct richacl *, int);
> >>>>>--
> >>>>>2.4.3
> >>>>>
> >>>>>--
> >>>>>To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> >>>>>the body of a message to majordomo@vger.kernel.org
> >>>>>More majordomo info at  http://vger.kernel.org/majordomo-info.html
> >>>--
> >>>To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> >>>the body of a message to majordomo@vger.kernel.org
> >>>More majordomo info at  http://vger.kernel.org/majordomo-info.html
> >>>Please read the FAQ at  http://www.tux.org/lkml/
> >>>
> >>
> >>
> >
> >
> 
> 

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 22/41] richacl: Propagate everyone@ permissions to other aces
  2015-09-18 21:56         ` J. Bruce Fields
@ 2015-09-21 19:24             ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-21 19:24 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

On Fri, Sep 18, 2015 at 05:56:11PM -0400, bfields wrote:
> On Sat, Sep 05, 2015 at 12:27:17PM +0200, Andreas Gruenbacher wrote:
> > The trailing everyone@ allow ace can grant permissions to all file
> > classes including the owner and group class.  Before we can apply the
> > other mask to this entry to turn it into an "other class" entry, we need
> > to ensure that members of the owner or group class will not lose any
> > permissions from that ace.
> > 
> > Conceptually, we do this by inserting additional <who>:<allow>::allow
> > entries before the trailing everyone@ allow ace with the same
> > permissions as the trailing everyone@ allow ace for owner@, group@, and
> > all explicitly mentioned users and groups.  (In practice, we will rarely
> > need to insert any additional aces in this step.)
> > 
> > Signed-off-by: Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> > ---
> >  fs/richacl_compat.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++++
> >  1 file changed, 195 insertions(+)
> > 
> > diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> > index 4f0acf5..9b76fc0 100644
> > --- a/fs/richacl_compat.c
> > +++ b/fs/richacl_compat.c
> > @@ -218,3 +218,198 @@ richacl_move_everyone_aces_down(struct richacl_alloc *alloc)
> >  	}
> >  	return 0;
> >  }
> > +
> > +/**
> > + * __richacl_propagate_everyone  -  propagate everyone@ permissions up for @who
> > + * @alloc:	acl and number of allocated entries
> > + * @who:	identifier to propagate permissions for
> > + * @allow:	permissions to propagate up
> > + *
> > + * Propagate the permissions in @allow up from the end of the acl to the start
> > + * for the specified principal @who.
> > + *
> > + * The simplest possible approach to achieve this would be to insert a
> > + * "<who>:<allow>::allow" ace before the final everyone@ allow ace.  Since this
> > + * would often result in aces which are not needed or which could be merged
> > + * with an existing ace, we make the following optimizations:
> > + *
> > + *   - We go through the acl and determine which permissions are already
> > + *     allowed or denied to @who, and we remove those permissions from
> > + *     @allow.
> > + *
> > + *   - If the acl contains an allow ace for @who and no aces after this entry
> > + *     deny permissions in @allow, we add the permissions in @allow to this
> > + *     ace.  (Propagating permissions across a deny ace which can match the
> > + *     process can elevate permissions.)
> > + *
> > + * This transformation does not alter the permissions that the acl grants.
> > + */
> > +static int
> > +__richacl_propagate_everyone(struct richacl_alloc *alloc, struct richace *who,
> > +			     unsigned int allow)
> > +{
> > +	struct richace *allow_last = NULL, *ace;
> > +	struct richacl *acl = alloc->acl;
> > +
> > +	/*
> > +	 * Remove the permissions from allow that are already determined for
> > +	 * this who value, and figure out if there is an allow entry for
> > +	 * this who value that is "reachable" from the trailing everyone@
> > +	 * allow ace.
> > +	 */
> > +	richacl_for_each_entry(ace, acl) {
> > +		if (richace_is_inherit_only(ace))
> > +			continue;
> > +		if (richace_is_allow(ace)) {
> > +			if (richace_is_same_identifier(ace, who)) {
> > +				allow &= ~ace->e_mask;
> > +				allow_last = ace;
> > +			}
> > +		} else if (richace_is_deny(ace)) {
> > +			if (richace_is_same_identifier(ace, who))
> > +				allow &= ~ace->e_mask;
> > +			else if (allow & ace->e_mask)
> > +				allow_last = NULL;
> > +		}
> > +	}
> > +	ace--;
> > +
> > +	/*
> > +	 * If for group class entries, all the remaining permissions will
> > +	 * remain granted by the trailing everyone@ ace, no additional entry is
> > +	 * needed.
> > +	 */
> > +	if (!richace_is_owner(who) &&
> > +	    richace_is_everyone(ace) && richace_is_allow(ace) &&
> > +	    !(allow & ~(ace->e_mask & acl->a_other_mask)))
> > +		allow = 0;
> > +
> > +	if (allow) {
> > +		if (allow_last)
> > +			return richace_change_mask(alloc, &allow_last,
> > +						   allow_last->e_mask | allow);
> > +		else {
> > +			struct richace who_copy;
> > +
> > +			richace_copy(&who_copy, who);
> > +			ace = acl->a_entries + acl->a_count - 1;
> > +			if (richacl_insert_entry(alloc, &ace))
> > +				return -1;
> > +			richace_copy(ace, &who_copy);
> > +			ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> > +			ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
> > +			ace->e_mask = allow;
> > +		}
> > +	}
> > +	return 0;
> > +}
> > +
> > +/**
> > + * richacl_propagate_everyone  -  propagate everyone@ permissions up the acl
> > + * @alloc:	acl and number of allocated entries
> > + *
> > + * Make sure that group@ and all other users and groups mentioned in the acl
> > + * will not lose any permissions when finally applying the other mask to the
> > + * everyone@ allow ace at the end of the acl.  We modify the permissions of
> > + * existing entries or add new entries before the final everyone@ allow ace to
> > + * achieve that.
> > + *
> > + * For example, the following acl implicitly grants everyone rwpx access:
> > + *
> > + *    joe:r::allow
> > + *    everyone@:rwpx::allow
> > + *
> > + * When applying mode 0660 to this acl, group@ would lose rwp access, and joe
> > + * would lose wp access even though the mode does not exclude those
> > + * permissions.  After propagating the everyone@ permissions, the result for
> > + * applying mode 0660 becomes:
> > + *
> > + *    owner@:rwp::allow
> > + *    joe:rwp::allow
> > + *    group@:rwp::allow
> > + *
> > + * Deny aces complicate the matter.  For example, the following acl grants
> > + * everyone but joe write access:
> > + *
> > + *    joe:wp::deny
> > + *    everyone@:rwpx::allow
> > + *
> > + * When applying mode 0660 to this acl, group@ would lose rwp access, and joe
> > + * would lose r access.  After propagating the everyone@ permissions, the
> > + * result for applying mode 0660 becomes:
> > + *
> > + *    owner@:rwp::allow
> > + *    joe:w::deny
> > + *    group@:rwp::allow
> > + *    joe:r::allow
> > + */
> > +static int
> > +richacl_propagate_everyone(struct richacl_alloc *alloc)
> > +{
> > +	struct richace who = { .e_flags = RICHACE_SPECIAL_WHO };
> > +	struct richacl *acl = alloc->acl;
> > +	struct richace *ace;
> > +	unsigned int owner_allow, group_allow;
> > +
> > +	/*
> > +	 * If the owner mask contains permissions which are not in the group
> > +	 * mask, the group mask contains permissions which are not in the other
> > +	 * mask, or the owner class contains permissions which are not in the
> 
> s/owner class/owner mask?
> 
> > +	 * other mask, we may need to propagate permissions up from the
> > +	 * everyone@ allow ace.  The third condition is implied by the first
> > +	 * two.
> > +	 */
> > +	if (!((acl->a_owner_mask & ~acl->a_group_mask) ||
> > +	      (acl->a_group_mask & ~acl->a_other_mask)))
> > +		return 0;
> 
> The code looks right, but I don't understand the preceding comment.
> 
> For example, 
> 
> 	owner mask: rw
> 	group mask:  wx
> 	other mask: rw
> 
> satisfies the first two conditions, but not the third.
> 
> Also, I don't understand why the first condition would imply that we
> might need to propagate permissions.

OK, maybe I get the part about the owner mask containing permissions
not in the group mask: we'll need to insert a deny ace for the bits in
the other mask but not in the group mask, and then we'll need an allow
ace for the owner to get those bits back.  I think?

> > +			if (richace_is_allow(ace) || richace_is_deny(ace)) {

The v4 spec allows aces other than allow and deny aces (audit and
alarm), but I didn't think you were implementing those.

--b.

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 22/41] richacl: Propagate everyone@ permissions to other aces
@ 2015-09-21 19:24             ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-21 19:24 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

On Fri, Sep 18, 2015 at 05:56:11PM -0400, bfields wrote:
> On Sat, Sep 05, 2015 at 12:27:17PM +0200, Andreas Gruenbacher wrote:
> > The trailing everyone@ allow ace can grant permissions to all file
> > classes including the owner and group class.  Before we can apply the
> > other mask to this entry to turn it into an "other class" entry, we need
> > to ensure that members of the owner or group class will not lose any
> > permissions from that ace.
> > 
> > Conceptually, we do this by inserting additional <who>:<allow>::allow
> > entries before the trailing everyone@ allow ace with the same
> > permissions as the trailing everyone@ allow ace for owner@, group@, and
> > all explicitly mentioned users and groups.  (In practice, we will rarely
> > need to insert any additional aces in this step.)
> > 
> > Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
> > ---
> >  fs/richacl_compat.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++++
> >  1 file changed, 195 insertions(+)
> > 
> > diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> > index 4f0acf5..9b76fc0 100644
> > --- a/fs/richacl_compat.c
> > +++ b/fs/richacl_compat.c
> > @@ -218,3 +218,198 @@ richacl_move_everyone_aces_down(struct richacl_alloc *alloc)
> >  	}
> >  	return 0;
> >  }
> > +
> > +/**
> > + * __richacl_propagate_everyone  -  propagate everyone@ permissions up for @who
> > + * @alloc:	acl and number of allocated entries
> > + * @who:	identifier to propagate permissions for
> > + * @allow:	permissions to propagate up
> > + *
> > + * Propagate the permissions in @allow up from the end of the acl to the start
> > + * for the specified principal @who.
> > + *
> > + * The simplest possible approach to achieve this would be to insert a
> > + * "<who>:<allow>::allow" ace before the final everyone@ allow ace.  Since this
> > + * would often result in aces which are not needed or which could be merged
> > + * with an existing ace, we make the following optimizations:
> > + *
> > + *   - We go through the acl and determine which permissions are already
> > + *     allowed or denied to @who, and we remove those permissions from
> > + *     @allow.
> > + *
> > + *   - If the acl contains an allow ace for @who and no aces after this entry
> > + *     deny permissions in @allow, we add the permissions in @allow to this
> > + *     ace.  (Propagating permissions across a deny ace which can match the
> > + *     process can elevate permissions.)
> > + *
> > + * This transformation does not alter the permissions that the acl grants.
> > + */
> > +static int
> > +__richacl_propagate_everyone(struct richacl_alloc *alloc, struct richace *who,
> > +			     unsigned int allow)
> > +{
> > +	struct richace *allow_last = NULL, *ace;
> > +	struct richacl *acl = alloc->acl;
> > +
> > +	/*
> > +	 * Remove the permissions from allow that are already determined for
> > +	 * this who value, and figure out if there is an allow entry for
> > +	 * this who value that is "reachable" from the trailing everyone@
> > +	 * allow ace.
> > +	 */
> > +	richacl_for_each_entry(ace, acl) {
> > +		if (richace_is_inherit_only(ace))
> > +			continue;
> > +		if (richace_is_allow(ace)) {
> > +			if (richace_is_same_identifier(ace, who)) {
> > +				allow &= ~ace->e_mask;
> > +				allow_last = ace;
> > +			}
> > +		} else if (richace_is_deny(ace)) {
> > +			if (richace_is_same_identifier(ace, who))
> > +				allow &= ~ace->e_mask;
> > +			else if (allow & ace->e_mask)
> > +				allow_last = NULL;
> > +		}
> > +	}
> > +	ace--;
> > +
> > +	/*
> > +	 * If for group class entries, all the remaining permissions will
> > +	 * remain granted by the trailing everyone@ ace, no additional entry is
> > +	 * needed.
> > +	 */
> > +	if (!richace_is_owner(who) &&
> > +	    richace_is_everyone(ace) && richace_is_allow(ace) &&
> > +	    !(allow & ~(ace->e_mask & acl->a_other_mask)))
> > +		allow = 0;
> > +
> > +	if (allow) {
> > +		if (allow_last)
> > +			return richace_change_mask(alloc, &allow_last,
> > +						   allow_last->e_mask | allow);
> > +		else {
> > +			struct richace who_copy;
> > +
> > +			richace_copy(&who_copy, who);
> > +			ace = acl->a_entries + acl->a_count - 1;
> > +			if (richacl_insert_entry(alloc, &ace))
> > +				return -1;
> > +			richace_copy(ace, &who_copy);
> > +			ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> > +			ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
> > +			ace->e_mask = allow;
> > +		}
> > +	}
> > +	return 0;
> > +}
> > +
> > +/**
> > + * richacl_propagate_everyone  -  propagate everyone@ permissions up the acl
> > + * @alloc:	acl and number of allocated entries
> > + *
> > + * Make sure that group@ and all other users and groups mentioned in the acl
> > + * will not lose any permissions when finally applying the other mask to the
> > + * everyone@ allow ace at the end of the acl.  We modify the permissions of
> > + * existing entries or add new entries before the final everyone@ allow ace to
> > + * achieve that.
> > + *
> > + * For example, the following acl implicitly grants everyone rwpx access:
> > + *
> > + *    joe:r::allow
> > + *    everyone@:rwpx::allow
> > + *
> > + * When applying mode 0660 to this acl, group@ would lose rwp access, and joe
> > + * would lose wp access even though the mode does not exclude those
> > + * permissions.  After propagating the everyone@ permissions, the result for
> > + * applying mode 0660 becomes:
> > + *
> > + *    owner@:rwp::allow
> > + *    joe:rwp::allow
> > + *    group@:rwp::allow
> > + *
> > + * Deny aces complicate the matter.  For example, the following acl grants
> > + * everyone but joe write access:
> > + *
> > + *    joe:wp::deny
> > + *    everyone@:rwpx::allow
> > + *
> > + * When applying mode 0660 to this acl, group@ would lose rwp access, and joe
> > + * would lose r access.  After propagating the everyone@ permissions, the
> > + * result for applying mode 0660 becomes:
> > + *
> > + *    owner@:rwp::allow
> > + *    joe:w::deny
> > + *    group@:rwp::allow
> > + *    joe:r::allow
> > + */
> > +static int
> > +richacl_propagate_everyone(struct richacl_alloc *alloc)
> > +{
> > +	struct richace who = { .e_flags = RICHACE_SPECIAL_WHO };
> > +	struct richacl *acl = alloc->acl;
> > +	struct richace *ace;
> > +	unsigned int owner_allow, group_allow;
> > +
> > +	/*
> > +	 * If the owner mask contains permissions which are not in the group
> > +	 * mask, the group mask contains permissions which are not in the other
> > +	 * mask, or the owner class contains permissions which are not in the
> 
> s/owner class/owner mask?
> 
> > +	 * other mask, we may need to propagate permissions up from the
> > +	 * everyone@ allow ace.  The third condition is implied by the first
> > +	 * two.
> > +	 */
> > +	if (!((acl->a_owner_mask & ~acl->a_group_mask) ||
> > +	      (acl->a_group_mask & ~acl->a_other_mask)))
> > +		return 0;
> 
> The code looks right, but I don't understand the preceding comment.
> 
> For example, 
> 
> 	owner mask: rw
> 	group mask:  wx
> 	other mask: rw
> 
> satisfies the first two conditions, but not the third.
> 
> Also, I don't understand why the first condition would imply that we
> might need to propagate permissions.

OK, maybe I get the part about the owner mask containing permissions
not in the group mask: we'll need to insert a deny ace for the bits in
the other mask but not in the group mask, and then we'll need an allow
ace for the owner to get those bits back.  I think?

> > +			if (richace_is_allow(ace) || richace_is_deny(ace)) {

The v4 spec allows aces other than allow and deny aces (audit and
alarm), but I didn't think you were implementing those.

--b.

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 14/41] richacl: Create-time inheritance
  2015-09-18 17:58         ` J. Bruce Fields
@ 2015-09-21 20:37             ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-21 20:37 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-fsdevel,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

2015-09-18 19:58 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> On Sat, Sep 05, 2015 at 12:27:09PM +0200, Andreas Gruenbacher wrote:
>> +                     if (dir_ace->e_flags & RICHACE_NO_PROPAGATE_INHERIT_ACE)
>> +                             ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
>> +                     else if ((dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE) &&
>> +                              !(dir_ace->e_flags & RICHACE_DIRECTORY_INHERIT_ACE))
>
> The FILE_INHERIT_ACE check there is redundant since we already know
> dir_ace is inheritable.
>
> (So, OK, it isn't wrong to check it again but let's not make this
> condition any more complicated than necessary.)

Indeed, we can turn it into:

        if (dir_ace->e_flags & RICHACE_NO_PROPAGATE_INHERIT_ACE)
                ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
        else if (!(dir_ace->e_flags & RICHACE_DIRECTORY_INHERIT_ACE))
                ace->e_flags |= RICHACE_INHERIT_ONLY_ACE;

>> +static struct richacl *
>> +richacl_inherit_inode(const struct richacl *dir_acl, struct inode *inode)
>> +{
>> +     struct richacl *acl;
>> +     mode_t mask;
>> +
>> +     acl = richacl_inherit(dir_acl, S_ISDIR(inode->i_mode));
>> +     if (acl) {
>> +             mask = inode->i_mode;
>> +             if (richacl_equiv_mode(acl, &mask) == 0) {
>> +                     richacl_put(acl);
>> +                     acl = NULL;
>
> Why is it correct to ignore entirely the inherited acl in this case?
>
> Oh, I see, I'm forgetting that richacl_equiv_mode is setting the mask,
> which will get applied at the end of this function.  In my defense,
> maybe it's easy to overlook a side effect in an if condition.... But I
> don't have a better idea.  OK.

Yes. I'll leave that as it is.

> So, nits aside:
>
>         Reviewed-by: J. Bruce Fields <bfields-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 14/41] richacl: Create-time inheritance
@ 2015-09-21 20:37             ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-21 20:37 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

2015-09-18 19:58 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> On Sat, Sep 05, 2015 at 12:27:09PM +0200, Andreas Gruenbacher wrote:
>> +                     if (dir_ace->e_flags & RICHACE_NO_PROPAGATE_INHERIT_ACE)
>> +                             ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
>> +                     else if ((dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE) &&
>> +                              !(dir_ace->e_flags & RICHACE_DIRECTORY_INHERIT_ACE))
>
> The FILE_INHERIT_ACE check there is redundant since we already know
> dir_ace is inheritable.
>
> (So, OK, it isn't wrong to check it again but let's not make this
> condition any more complicated than necessary.)

Indeed, we can turn it into:

        if (dir_ace->e_flags & RICHACE_NO_PROPAGATE_INHERIT_ACE)
                ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
        else if (!(dir_ace->e_flags & RICHACE_DIRECTORY_INHERIT_ACE))
                ace->e_flags |= RICHACE_INHERIT_ONLY_ACE;

>> +static struct richacl *
>> +richacl_inherit_inode(const struct richacl *dir_acl, struct inode *inode)
>> +{
>> +     struct richacl *acl;
>> +     mode_t mask;
>> +
>> +     acl = richacl_inherit(dir_acl, S_ISDIR(inode->i_mode));
>> +     if (acl) {
>> +             mask = inode->i_mode;
>> +             if (richacl_equiv_mode(acl, &mask) == 0) {
>> +                     richacl_put(acl);
>> +                     acl = NULL;
>
> Why is it correct to ignore entirely the inherited acl in this case?
>
> Oh, I see, I'm forgetting that richacl_equiv_mode is setting the mask,
> which will get applied at the end of this function.  In my defense,
> maybe it's easy to overlook a side effect in an if condition.... But I
> don't have a better idea.  OK.

Yes. I'll leave that as it is.

> So, nits aside:
>
>         Reviewed-by: J. Bruce Fields <bfields@redhat.com>

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 23/41] richacl: Set the owner permissions to the owner mask
  2015-09-05 10:27 ` [RFC v7 23/41] richacl: Set the owner permissions to the owner mask Andreas Gruenbacher
@ 2015-09-21 21:00   ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-21 21:00 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Sat, Sep 05, 2015 at 12:27:18PM +0200, Andreas Gruenbacher wrote:
> Change the acl so that owner@ is granted the permissions set in the
> owner mask.
> 
> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> ---
>  fs/richacl_compat.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 46 insertions(+)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 9b76fc0..14a1b4b 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -413,3 +413,49 @@ richacl_propagate_everyone(struct richacl_alloc *alloc)
>  	}
>  	return 0;
>  }
> +
> +/**
> + * richacl_set_owner_permissions  -  set the owner permissions to the owner mask
> + *
> + * Change the acl so that owner@ is granted the permissions set in the owner
> + * mask.  This leaves at most one efective owner@ allow entry at the beginning
> + * of the acl.

Perhaps also worth nothing here that it's a no-op in the !MASKED or
!WRITE_THROUGH cases and that this does not change the permissions that
the ACL grants.  (The latter wasn't obvious to me at first....  But
this only modifies OWNER@ aces, and in the MASKED & WRITE_THROUGH case
modification of OWNER@ aces doesn't affect permissions.)

Anyway, code looks right to me:

	Reviewed-by: J. Bruce Fields <bfields@redhat.com>

--b.

> + */
> +static int
> +richacl_set_owner_permissions(struct richacl_alloc *alloc)
> +{
> +	unsigned int x = RICHACE_POSIX_ALWAYS_ALLOWED;
> +	unsigned int owner_mask = alloc->acl->a_owner_mask & ~x;
> +	unsigned int denied = 0;
> +	struct richace *ace;
> +
> +	if (!((alloc->acl->a_flags & RICHACL_WRITE_THROUGH) &&
> +	      (alloc->acl->a_flags & RICHACL_MASKED)))
> +		return 0;
> +
> +	richacl_for_each_entry(ace, alloc->acl) {
> +		if (richace_is_owner(ace)) {
> +			if (richace_is_allow(ace) && !(owner_mask & denied)) {
> +				richace_change_mask(alloc, &ace, owner_mask);
> +				owner_mask = 0;
> +			} else
> +				richace_change_mask(alloc, &ace, 0);
> +		} else {
> +			if (richace_is_deny(ace))
> +				denied |= ace->e_mask;
> +		}
> +	}
> +
> +	if (owner_mask & (denied |
> +			  ~alloc->acl->a_other_mask |
> +			  ~alloc->acl->a_group_mask)) {
> +		ace = alloc->acl->a_entries;
> +		if (richacl_insert_entry(alloc, &ace))
> +			return -1;
> +		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +		ace->e_flags = RICHACE_SPECIAL_WHO;
> +		ace->e_mask = owner_mask;
> +		ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
> +	}
> +	return 0;
> +}
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 15/41] richacl: Automatic Inheritance
  2015-09-18 18:40   ` J. Bruce Fields
@ 2015-09-21 21:19     ` Andreas Gruenbacher
  2015-09-22  1:51       ` J. Bruce Fields
  0 siblings, 1 reply; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-21 21:19 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

2015-09-18 20:40 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> On Sat, Sep 05, 2015 at 12:27:10PM +0200, Andreas Gruenbacher wrote:
>> Automatic Inheritance (AI) allows changes to the acl of a directory to
> In the above "file" sometimes means "any object" and somethings "a
> non-directory".  I can sort it out, but more consistent terminology
> would help.

Okay, I'll fix it.

>> Linux does not have a way of creating files without setting the file
>> permission bits, so all files created inside a directory with
>> RICHACL_AUTO_INHERIT set will have the RICHACL_PROTECTED flag set.  This
>> effectively disables Automatic Inheritance.
>>
>> Protocols which support creating files without specifying permissions
>> can explicitly clear the RICHACL_PROTECTED flag after creating a file
>> and reset the file masks to "undo" applying the create mode; see
>> richacl_compute_max_masks().  They should set the RICHACL_DEFAULTED
>> flag.  This is a workaround; a mechanism that would allow a process to
>> indicate to the kernel to ignore the create mode when there are
>> inherited permissions would fix this problem.
>
> Also, as you know: current nfsd has no way to create files without
> setting permissions.  And if we were to implement that it's unclear how
> many clients would actually use it (Windows clients are rare).  And of
> course Samba doesn't have the interfaces it would need.
>
> I think we should just drop this for now.  The rest of the richacl stuff
> is still useful without it.

Samba will hack around it and adjust the ACL after the create; that's
still better than not having Automatic Inheritance. Windows uses AI
all the time so AI is more important for Samba than for NFSv4.

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 21/41] richacl: Move everyone@ aces down the acl
  2015-09-18 19:35   ` J. Bruce Fields
@ 2015-09-21 21:43         ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-21 21:43 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-fsdevel,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

2015-09-18 21:35 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> On Sat, Sep 05, 2015 at 12:27:16PM +0200, Andreas Gruenbacher wrote:
>> The POSIX standard puts processes which are not the owner or a member in
>> the owning group or which match any ace other then everyone@ on the
>> other file class.  We only know if a process is in the other class after
>> processing the entire acl.
>>
>> Move all everyone@ aces in the acl down in the acl so that at most a
>> single everyone@ allow ace remains at the end.  Permissions which are
>> not explicitly allowed are implicitly denied, so an everyone@ deny ace
>> is unneeded.
>>
>> The everyone@ aces can be moved down the acl without changing the
>> permissions that the acl grants.  This transformation simplifies the
>> following algorithms, and eventually allows us to turn the final
>> everyone@ allow ace into an entry for the other class.
>>
>> Signed-off-by: Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
>> ---
>>  fs/richacl_compat.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++
>>  1 file changed, 65 insertions(+)
>>
>> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
>> index 341e429..4f0acf5 100644
>> --- a/fs/richacl_compat.c
>> +++ b/fs/richacl_compat.c
>> @@ -153,3 +153,68 @@ richace_change_mask(struct richacl_alloc *alloc, struct richace **ace,
>>       }
>>       return 0;
>>  }
>> +
>> +/**
>> + * richacl_move_everyone_aces_down  -  move everyone@ aces to the end of the acl
>> + * @alloc:   acl and number of allocated entries
>> + *
>> + * Move all everyone aces to the end of the acl so that only a single everyone@
>> + * allow ace remains at the end, and update the mask fields of all aces on the
>> + * way.  The last ace of the resulting acl will be an everyone@ allow ace only
>> + * if @acl grants any permissions to @everyone.  No @everyone deny aces will
>> + * remain.
>> + *
>> + * This transformation does not alter the permissions that the acl grants.
>> + * Having at most one everyone@ allow ace at the end of the acl helps us in the
>> + * following algorithms.
>> + */
>> +static int
>> +richacl_move_everyone_aces_down(struct richacl_alloc *alloc)
>> +{
>> +     struct richace *ace;
>> +     unsigned int allowed = 0, denied = 0;
>> +
>> +     richacl_for_each_entry(ace, alloc->acl) {
>> +             if (richace_is_inherit_only(ace))
>> +                     continue;
>> +             if (richace_is_everyone(ace)) {
>> +                     if (richace_is_allow(ace))
>> +                             allowed |= (ace->e_mask & ~denied);
>> +                     else if (richace_is_deny(ace))
>> +                             denied |= (ace->e_mask & ~allowed);
>> +                     else
>> +                             continue;
>> +                     if (richace_change_mask(alloc, &ace, 0))
>> +                             return -1;
>> +             } else {
>> +                     if (richace_is_allow(ace)) {
>> +                             if (richace_change_mask(alloc, &ace, allowed |
>> +                                             (ace->e_mask & ~denied)))
>> +                                     return -1;
>> +                     } else if (richace_is_deny(ace)) {
>> +                             if (richace_change_mask(alloc, &ace, denied |
>> +                                             (ace->e_mask & ~allowed)))
>> +                                     return -1;
>> +                     }
>> +             }
>> +     }
>> +     if (allowed & ~RICHACE_POSIX_ALWAYS_ALLOWED) {
>> +             struct richace *last_ace = ace - 1;
>> +
>> +             if (alloc->acl->a_entries &&
>> +                 richace_is_everyone(last_ace) &&
>> +                 richace_is_allow(last_ace) &&
>> +                 richace_is_inherit_only(last_ace) &&
>> +                 last_ace->e_mask == allowed)
>> +                     last_ace->e_flags &= ~RICHACE_INHERIT_ONLY_ACE;
>
> That's a funny special case!  Is it even worth it, or could we just live
> with an extra uninheritable EVERYONE ace in this case?

Inheritable everyone@ allow entries at the end of the ACL are not
uncommon. This special case prevents the algorithm from splitting such
entries into inherit-only and non-inheritable parts.

> Anyway, again I like the way you've set this all up with the little
> acl-editing helpers, it makes this easier to follow than it otherwise
> would be....

Great to hear that :)

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 21/41] richacl: Move everyone@ aces down the acl
@ 2015-09-21 21:43         ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-21 21:43 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

2015-09-18 21:35 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> On Sat, Sep 05, 2015 at 12:27:16PM +0200, Andreas Gruenbacher wrote:
>> The POSIX standard puts processes which are not the owner or a member in
>> the owning group or which match any ace other then everyone@ on the
>> other file class.  We only know if a process is in the other class after
>> processing the entire acl.
>>
>> Move all everyone@ aces in the acl down in the acl so that at most a
>> single everyone@ allow ace remains at the end.  Permissions which are
>> not explicitly allowed are implicitly denied, so an everyone@ deny ace
>> is unneeded.
>>
>> The everyone@ aces can be moved down the acl without changing the
>> permissions that the acl grants.  This transformation simplifies the
>> following algorithms, and eventually allows us to turn the final
>> everyone@ allow ace into an entry for the other class.
>>
>> Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
>> ---
>>  fs/richacl_compat.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++
>>  1 file changed, 65 insertions(+)
>>
>> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
>> index 341e429..4f0acf5 100644
>> --- a/fs/richacl_compat.c
>> +++ b/fs/richacl_compat.c
>> @@ -153,3 +153,68 @@ richace_change_mask(struct richacl_alloc *alloc, struct richace **ace,
>>       }
>>       return 0;
>>  }
>> +
>> +/**
>> + * richacl_move_everyone_aces_down  -  move everyone@ aces to the end of the acl
>> + * @alloc:   acl and number of allocated entries
>> + *
>> + * Move all everyone aces to the end of the acl so that only a single everyone@
>> + * allow ace remains at the end, and update the mask fields of all aces on the
>> + * way.  The last ace of the resulting acl will be an everyone@ allow ace only
>> + * if @acl grants any permissions to @everyone.  No @everyone deny aces will
>> + * remain.
>> + *
>> + * This transformation does not alter the permissions that the acl grants.
>> + * Having at most one everyone@ allow ace at the end of the acl helps us in the
>> + * following algorithms.
>> + */
>> +static int
>> +richacl_move_everyone_aces_down(struct richacl_alloc *alloc)
>> +{
>> +     struct richace *ace;
>> +     unsigned int allowed = 0, denied = 0;
>> +
>> +     richacl_for_each_entry(ace, alloc->acl) {
>> +             if (richace_is_inherit_only(ace))
>> +                     continue;
>> +             if (richace_is_everyone(ace)) {
>> +                     if (richace_is_allow(ace))
>> +                             allowed |= (ace->e_mask & ~denied);
>> +                     else if (richace_is_deny(ace))
>> +                             denied |= (ace->e_mask & ~allowed);
>> +                     else
>> +                             continue;
>> +                     if (richace_change_mask(alloc, &ace, 0))
>> +                             return -1;
>> +             } else {
>> +                     if (richace_is_allow(ace)) {
>> +                             if (richace_change_mask(alloc, &ace, allowed |
>> +                                             (ace->e_mask & ~denied)))
>> +                                     return -1;
>> +                     } else if (richace_is_deny(ace)) {
>> +                             if (richace_change_mask(alloc, &ace, denied |
>> +                                             (ace->e_mask & ~allowed)))
>> +                                     return -1;
>> +                     }
>> +             }
>> +     }
>> +     if (allowed & ~RICHACE_POSIX_ALWAYS_ALLOWED) {
>> +             struct richace *last_ace = ace - 1;
>> +
>> +             if (alloc->acl->a_entries &&
>> +                 richace_is_everyone(last_ace) &&
>> +                 richace_is_allow(last_ace) &&
>> +                 richace_is_inherit_only(last_ace) &&
>> +                 last_ace->e_mask == allowed)
>> +                     last_ace->e_flags &= ~RICHACE_INHERIT_ONLY_ACE;
>
> That's a funny special case!  Is it even worth it, or could we just live
> with an extra uninheritable EVERYONE ace in this case?

Inheritable everyone@ allow entries at the end of the ACL are not
uncommon. This special case prevents the algorithm from splitting such
entries into inherit-only and non-inheritable parts.

> Anyway, again I like the way you've set this all up with the little
> acl-editing helpers, it makes this easier to follow than it otherwise
> would be....

Great to hear that :)

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 13/41] richacl: Check if an acl is equivalent to a file mode
  2015-09-17 18:22     ` J. Bruce Fields
       [not found]       ` <20150917182219.GB13825-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
@ 2015-09-21 23:20       ` Andreas Gruenbacher
  1 sibling, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-21 23:20 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

2015-09-17 20:22 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> On Sat, Sep 05, 2015 at 12:27:08PM +0200, Andreas Gruenbacher wrote:
>> ACLs are considered equivalent to file modes if they only consist of
>> owner@, group@, and everyone@ entries, the owner@ permissions do not
>> depend on whether the owner is a member in the owning group, and no
>> inheritance flags are set.  This test is used to avoid storing richacls
>> if the acl can be computed from the file permission bits.
>
> We're assuming here that it's OK for us to silently rearrange an ACL as
> long as the result is still equivalent (in the sense that the permission
> algorithm would always produce the same result).
>
> I guess that's OK by me, but it might violate user expectations in some
> simple common cases, so may be worth mentioning in documentation
> someplace if we don't already.

I've tried to be clear about that in the man pages.

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 13/41] richacl: Check if an acl is equivalent to a file mode
  2015-09-18  0:56           ` J. Bruce Fields
@ 2015-09-21 23:26               ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-21 23:26 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-fsdevel,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

2015-09-18 2:56 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> On Thu, Sep 17, 2015 at 02:22:19PM -0400, bfields wrote:
>> On Sat, Sep 05, 2015 at 12:27:08PM +0200, Andreas Gruenbacher wrote:
>> > ACLs are considered equivalent to file modes if they only consist of
>> > owner@, group@, and everyone@ entries, the owner@ permissions do not
>> > depend on whether the owner is a member in the owning group, and no
>> > inheritance flags are set.  This test is used to avoid storing richacls
>> > if the acl can be computed from the file permission bits.
>>
>> We're assuming here that it's OK for us to silently rearrange an ACL as
>> long as the result is still equivalent (in the sense that the permission
>> algorithm would always produce the same result).
>>
>> I guess that's OK by me, but it might violate user expectations in some
>> simple common cases, so may be worth mentioning in documentation
>> someplace if we don't already.
>
> Also your notion of mode-equivalence here is interesting, it's actually
> a strict subset of the ACLs that produce the same permission results as
> a mode.  (For example, everyone:rwx,bfields:rwx is equivalent to 0777
> but won't be considered mode-equivalent by this algorithm.)

Yes, the algorithm should better not surprise the user by being too clever.

> I think the choices you've made probably make the most sense, they just
> wouldn't have been obvious to me.  Anyway, so, OK by me:
>
>         Reviewed-by: J. Bruce Fields <bfields-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 13/41] richacl: Check if an acl is equivalent to a file mode
@ 2015-09-21 23:26               ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-21 23:26 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

2015-09-18 2:56 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> On Thu, Sep 17, 2015 at 02:22:19PM -0400, bfields wrote:
>> On Sat, Sep 05, 2015 at 12:27:08PM +0200, Andreas Gruenbacher wrote:
>> > ACLs are considered equivalent to file modes if they only consist of
>> > owner@, group@, and everyone@ entries, the owner@ permissions do not
>> > depend on whether the owner is a member in the owning group, and no
>> > inheritance flags are set.  This test is used to avoid storing richacls
>> > if the acl can be computed from the file permission bits.
>>
>> We're assuming here that it's OK for us to silently rearrange an ACL as
>> long as the result is still equivalent (in the sense that the permission
>> algorithm would always produce the same result).
>>
>> I guess that's OK by me, but it might violate user expectations in some
>> simple common cases, so may be worth mentioning in documentation
>> someplace if we don't already.
>
> Also your notion of mode-equivalence here is interesting, it's actually
> a strict subset of the ACLs that produce the same permission results as
> a mode.  (For example, everyone:rwx,bfields:rwx is equivalent to 0777
> but won't be considered mode-equivalent by this algorithm.)

Yes, the algorithm should better not surprise the user by being too clever.

> I think the choices you've made probably make the most sense, they just
> wouldn't have been obvious to me.  Anyway, so, OK by me:
>
>         Reviewed-by: J. Bruce Fields <bfields@redhat.com>

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 22/41] richacl: Propagate everyone@ permissions to other aces
  2015-09-18 21:36         ` J. Bruce Fields
@ 2015-09-21 23:44             ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-21 23:44 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-fsdevel,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

2015-09-18 23:36 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> On Sat, Sep 05, 2015 at 12:27:17PM +0200, Andreas Gruenbacher wrote:
>> +     if (!richace_is_owner(who) &&
>> +         richace_is_everyone(ace) && richace_is_allow(ace) &&
>
> That richace_is_allow(ace) check is redundant at this point, isn't it?

Yes, I'll change that.

>> +         !(allow & ~(ace->e_mask & acl->a_other_mask)))
>
> Uh, I wish C had a subset-of operator, that construct took me longer to
> work out than I should admit.
>
>> +             allow = 0;
>> +
>> +     if (allow) {
>> +             if (allow_last)
>> +                     return richace_change_mask(alloc, &allow_last,
>> +                                                allow_last->e_mask | allow);
>> +             else {
>> +                     struct richace who_copy;
>> +
>> +                     richace_copy(&who_copy, who);
>> +                     ace = acl->a_entries + acl->a_count - 1;
>
> Isn't ace already set to the last ace?

Yes indeed, that line can also go.

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 22/41] richacl: Propagate everyone@ permissions to other aces
@ 2015-09-21 23:44             ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-21 23:44 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

2015-09-18 23:36 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> On Sat, Sep 05, 2015 at 12:27:17PM +0200, Andreas Gruenbacher wrote:
>> +     if (!richace_is_owner(who) &&
>> +         richace_is_everyone(ace) && richace_is_allow(ace) &&
>
> That richace_is_allow(ace) check is redundant at this point, isn't it?

Yes, I'll change that.

>> +         !(allow & ~(ace->e_mask & acl->a_other_mask)))
>
> Uh, I wish C had a subset-of operator, that construct took me longer to
> work out than I should admit.
>
>> +             allow = 0;
>> +
>> +     if (allow) {
>> +             if (allow_last)
>> +                     return richace_change_mask(alloc, &allow_last,
>> +                                                allow_last->e_mask | allow);
>> +             else {
>> +                     struct richace who_copy;
>> +
>> +                     richace_copy(&who_copy, who);
>> +                     ace = acl->a_entries + acl->a_count - 1;
>
> Isn't ace already set to the last ace?

Yes indeed, that line can also go.

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 15/41] richacl: Automatic Inheritance
  2015-09-21 21:19     ` Andreas Gruenbacher
@ 2015-09-22  1:51       ` J. Bruce Fields
       [not found]         ` <20150922015146.GA15960-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
  0 siblings, 1 reply; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-22  1:51 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Mon, Sep 21, 2015 at 11:19:59PM +0200, Andreas Gruenbacher wrote:
> 2015-09-18 20:40 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> > On Sat, Sep 05, 2015 at 12:27:10PM +0200, Andreas Gruenbacher wrote:
> >> Automatic Inheritance (AI) allows changes to the acl of a directory to
> > In the above "file" sometimes means "any object" and somethings "a
> > non-directory".  I can sort it out, but more consistent terminology
> > would help.
> 
> Okay, I'll fix it.
> 
> >> Linux does not have a way of creating files without setting the file
> >> permission bits, so all files created inside a directory with
> >> RICHACL_AUTO_INHERIT set will have the RICHACL_PROTECTED flag set.  This
> >> effectively disables Automatic Inheritance.
> >>
> >> Protocols which support creating files without specifying permissions
> >> can explicitly clear the RICHACL_PROTECTED flag after creating a file
> >> and reset the file masks to "undo" applying the create mode; see
> >> richacl_compute_max_masks().  They should set the RICHACL_DEFAULTED
> >> flag.  This is a workaround; a mechanism that would allow a process to
> >> indicate to the kernel to ignore the create mode when there are
> >> inherited permissions would fix this problem.
> >
> > Also, as you know: current nfsd has no way to create files without
> > setting permissions.  And if we were to implement that it's unclear how
> > many clients would actually use it (Windows clients are rare).  And of
> > course Samba doesn't have the interfaces it would need.
> >
> > I think we should just drop this for now.  The rest of the richacl stuff
> > is still useful without it.
> 
> Samba will hack around it and adjust the ACL after the create; that's
> still better than not having Automatic Inheritance. Windows uses AI
> all the time so AI is more important for Samba than for NFSv4.

Oh, OK, that makes sense.  Even just giving them a place to store the
bits would be better than nothing.  So, ignore my objection there....

--b.

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 21/41] richacl: Move everyone@ aces down the acl
  2015-09-21 21:43         ` Andreas Gruenbacher
@ 2015-09-22  1:52             ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-22  1:52 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-fsdevel,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

On Mon, Sep 21, 2015 at 11:43:16PM +0200, Andreas Gruenbacher wrote:
> 2015-09-18 21:35 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> > On Sat, Sep 05, 2015 at 12:27:16PM +0200, Andreas Gruenbacher wrote:
> >> The POSIX standard puts processes which are not the owner or a member in
> >> the owning group or which match any ace other then everyone@ on the
> >> other file class.  We only know if a process is in the other class after
> >> processing the entire acl.
> >>
> >> Move all everyone@ aces in the acl down in the acl so that at most a
> >> single everyone@ allow ace remains at the end.  Permissions which are
> >> not explicitly allowed are implicitly denied, so an everyone@ deny ace
> >> is unneeded.
> >>
> >> The everyone@ aces can be moved down the acl without changing the
> >> permissions that the acl grants.  This transformation simplifies the
> >> following algorithms, and eventually allows us to turn the final
> >> everyone@ allow ace into an entry for the other class.
> >>
> >> Signed-off-by: Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> >> ---
> >>  fs/richacl_compat.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++
> >>  1 file changed, 65 insertions(+)
> >>
> >> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> >> index 341e429..4f0acf5 100644
> >> --- a/fs/richacl_compat.c
> >> +++ b/fs/richacl_compat.c
> >> @@ -153,3 +153,68 @@ richace_change_mask(struct richacl_alloc *alloc, struct richace **ace,
> >>       }
> >>       return 0;
> >>  }
> >> +
> >> +/**
> >> + * richacl_move_everyone_aces_down  -  move everyone@ aces to the end of the acl
> >> + * @alloc:   acl and number of allocated entries
> >> + *
> >> + * Move all everyone aces to the end of the acl so that only a single everyone@
> >> + * allow ace remains at the end, and update the mask fields of all aces on the
> >> + * way.  The last ace of the resulting acl will be an everyone@ allow ace only
> >> + * if @acl grants any permissions to @everyone.  No @everyone deny aces will
> >> + * remain.
> >> + *
> >> + * This transformation does not alter the permissions that the acl grants.
> >> + * Having at most one everyone@ allow ace at the end of the acl helps us in the
> >> + * following algorithms.
> >> + */
> >> +static int
> >> +richacl_move_everyone_aces_down(struct richacl_alloc *alloc)
> >> +{
> >> +     struct richace *ace;
> >> +     unsigned int allowed = 0, denied = 0;
> >> +
> >> +     richacl_for_each_entry(ace, alloc->acl) {
> >> +             if (richace_is_inherit_only(ace))
> >> +                     continue;
> >> +             if (richace_is_everyone(ace)) {
> >> +                     if (richace_is_allow(ace))
> >> +                             allowed |= (ace->e_mask & ~denied);
> >> +                     else if (richace_is_deny(ace))
> >> +                             denied |= (ace->e_mask & ~allowed);
> >> +                     else
> >> +                             continue;
> >> +                     if (richace_change_mask(alloc, &ace, 0))
> >> +                             return -1;
> >> +             } else {
> >> +                     if (richace_is_allow(ace)) {
> >> +                             if (richace_change_mask(alloc, &ace, allowed |
> >> +                                             (ace->e_mask & ~denied)))
> >> +                                     return -1;
> >> +                     } else if (richace_is_deny(ace)) {
> >> +                             if (richace_change_mask(alloc, &ace, denied |
> >> +                                             (ace->e_mask & ~allowed)))
> >> +                                     return -1;
> >> +                     }
> >> +             }
> >> +     }
> >> +     if (allowed & ~RICHACE_POSIX_ALWAYS_ALLOWED) {
> >> +             struct richace *last_ace = ace - 1;
> >> +
> >> +             if (alloc->acl->a_entries &&
> >> +                 richace_is_everyone(last_ace) &&
> >> +                 richace_is_allow(last_ace) &&
> >> +                 richace_is_inherit_only(last_ace) &&
> >> +                 last_ace->e_mask == allowed)
> >> +                     last_ace->e_flags &= ~RICHACE_INHERIT_ONLY_ACE;
> >
> > That's a funny special case!  Is it even worth it, or could we just live
> > with an extra uninheritable EVERYONE ace in this case?
> 
> Inheritable everyone@ allow entries at the end of the ACL are not
> uncommon. This special case prevents the algorithm from splitting such
> entries into inherit-only and non-inheritable parts.

OK.--b.

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 21/41] richacl: Move everyone@ aces down the acl
@ 2015-09-22  1:52             ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-22  1:52 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

On Mon, Sep 21, 2015 at 11:43:16PM +0200, Andreas Gruenbacher wrote:
> 2015-09-18 21:35 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> > On Sat, Sep 05, 2015 at 12:27:16PM +0200, Andreas Gruenbacher wrote:
> >> The POSIX standard puts processes which are not the owner or a member in
> >> the owning group or which match any ace other then everyone@ on the
> >> other file class.  We only know if a process is in the other class after
> >> processing the entire acl.
> >>
> >> Move all everyone@ aces in the acl down in the acl so that at most a
> >> single everyone@ allow ace remains at the end.  Permissions which are
> >> not explicitly allowed are implicitly denied, so an everyone@ deny ace
> >> is unneeded.
> >>
> >> The everyone@ aces can be moved down the acl without changing the
> >> permissions that the acl grants.  This transformation simplifies the
> >> following algorithms, and eventually allows us to turn the final
> >> everyone@ allow ace into an entry for the other class.
> >>
> >> Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
> >> ---
> >>  fs/richacl_compat.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++
> >>  1 file changed, 65 insertions(+)
> >>
> >> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> >> index 341e429..4f0acf5 100644
> >> --- a/fs/richacl_compat.c
> >> +++ b/fs/richacl_compat.c
> >> @@ -153,3 +153,68 @@ richace_change_mask(struct richacl_alloc *alloc, struct richace **ace,
> >>       }
> >>       return 0;
> >>  }
> >> +
> >> +/**
> >> + * richacl_move_everyone_aces_down  -  move everyone@ aces to the end of the acl
> >> + * @alloc:   acl and number of allocated entries
> >> + *
> >> + * Move all everyone aces to the end of the acl so that only a single everyone@
> >> + * allow ace remains at the end, and update the mask fields of all aces on the
> >> + * way.  The last ace of the resulting acl will be an everyone@ allow ace only
> >> + * if @acl grants any permissions to @everyone.  No @everyone deny aces will
> >> + * remain.
> >> + *
> >> + * This transformation does not alter the permissions that the acl grants.
> >> + * Having at most one everyone@ allow ace at the end of the acl helps us in the
> >> + * following algorithms.
> >> + */
> >> +static int
> >> +richacl_move_everyone_aces_down(struct richacl_alloc *alloc)
> >> +{
> >> +     struct richace *ace;
> >> +     unsigned int allowed = 0, denied = 0;
> >> +
> >> +     richacl_for_each_entry(ace, alloc->acl) {
> >> +             if (richace_is_inherit_only(ace))
> >> +                     continue;
> >> +             if (richace_is_everyone(ace)) {
> >> +                     if (richace_is_allow(ace))
> >> +                             allowed |= (ace->e_mask & ~denied);
> >> +                     else if (richace_is_deny(ace))
> >> +                             denied |= (ace->e_mask & ~allowed);
> >> +                     else
> >> +                             continue;
> >> +                     if (richace_change_mask(alloc, &ace, 0))
> >> +                             return -1;
> >> +             } else {
> >> +                     if (richace_is_allow(ace)) {
> >> +                             if (richace_change_mask(alloc, &ace, allowed |
> >> +                                             (ace->e_mask & ~denied)))
> >> +                                     return -1;
> >> +                     } else if (richace_is_deny(ace)) {
> >> +                             if (richace_change_mask(alloc, &ace, denied |
> >> +                                             (ace->e_mask & ~allowed)))
> >> +                                     return -1;
> >> +                     }
> >> +             }
> >> +     }
> >> +     if (allowed & ~RICHACE_POSIX_ALWAYS_ALLOWED) {
> >> +             struct richace *last_ace = ace - 1;
> >> +
> >> +             if (alloc->acl->a_entries &&
> >> +                 richace_is_everyone(last_ace) &&
> >> +                 richace_is_allow(last_ace) &&
> >> +                 richace_is_inherit_only(last_ace) &&
> >> +                 last_ace->e_mask == allowed)
> >> +                     last_ace->e_flags &= ~RICHACE_INHERIT_ONLY_ACE;
> >
> > That's a funny special case!  Is it even worth it, or could we just live
> > with an extra uninheritable EVERYONE ace in this case?
> 
> Inheritable everyone@ allow entries at the end of the ACL are not
> uncommon. This special case prevents the algorithm from splitting such
> entries into inherit-only and non-inheritable parts.

OK.--b.

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 25/41] richacl: Isolate the owner and group classes
  2015-09-05 10:27     ` Andreas Gruenbacher
@ 2015-09-22 16:06         ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-22 16:06 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

On Sat, Sep 05, 2015 at 12:27:20PM +0200, Andreas Gruenbacher wrote:
> When applying the file masks to an acl, we need to ensure that no
> process gets more permissions than allowed by its file mask.
> 
> This may require inserting an owner@ deny ace to ensure this if the
> owner mask contains fewer permissions than the group or other mask.  For
> example, when applying mode 0466 to the following acl:
> 
>    everyone@:rw::allow
> 
> A deny ace needs to be inserted so that the owner won't get elevated
> write access:
> 
>    owner@:w::deny
>    everyone@:rw::allow
> 
> Likewise, we may need to insert group class deny aces if the group mask
> contains fewer permissions than the other mask.  For example, when
> applying mode 0646 to the following acl:
> 
>    owner@:rw::allow
>    everyone@:rw::allow
> 
> A deny ace needs to be inserted so that the owning group won't get
> elevated write access:
> 
>    owner@:rw::allow
>    group@:w::deny
>    everyone@:rw::allow
> 
> Signed-off-by: Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> ---
>  fs/richacl_compat.c | 236 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 236 insertions(+)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 30bdc95..412844c 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -494,3 +494,239 @@ richacl_set_other_permissions(struct richacl_alloc *alloc)
>  		richace_change_mask(alloc, &ace, other_mask);
>  	return 0;
>  }
> +
> +/**
> + * richacl_max_allowed  -  maximum permissions that anybody is allowed
> + */
> +static unsigned int
> +richacl_max_allowed(struct richacl *acl)
> +{
> +	struct richace *ace;
> +	unsigned int allowed = 0;
> +
> +	richacl_for_each_entry_reverse(ace, acl) {
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_allow(ace))
> +			allowed |= ace->e_mask;
> +		else if (richace_is_deny(ace)) {
> +			if (richace_is_everyone(ace))
> +				allowed &= ~ace->e_mask;
> +		}
> +	}
> +	return allowed;
> +}
> +
> +/**
> + * richacl_isolate_owner_class  -  limit the owner class to the owner file mask
> + * @alloc:	acl and number of allocated entries
> + *
> + * POSIX requires that after a chmod, the owner class is granted no more
> + * permissions than the owner file permission bits.  For richacls, this
> + * means that the owner class must not be granted any permissions that the
> + * owner mask does not include.
> + *
> + * When we apply file masks to an acl which grant more permissions to the group
> + * or other class than to the owner class, we may end up in a situation where
> + * the owner is granted additional permissions from other aces.  For example,
> + * given this acl:
> + *
> + *    everyone:rwx::allow
> + *
> + * when file masks corresponding to mode 0466 are applied, after
> + * richacl_propagate_everyone() and __richacl_apply_masks(), we end up with:
> + *
> + *    owner@:r::allow
> + *    everyone@:rw::allow

Are you sure?  I didn't think richacl_apply_masks actually creates an
owner@ entry in this case.  Which is OK, just delete the owner@ ace from
here and the following example and it still makes sense, I think.

(But: thanks in general for the examples in these comments, they're
extremely helpful.)

> + *
> + * This acl still grants the owner rw access through the everyone@ allow ace.
> + * To fix this, we must deny the owner w access:
> + *
> + *    owner@:w::deny
> + *    owner@:r::allow
> + *    everyone@:rw::allow
> + */
> +static int
> +richacl_isolate_owner_class(struct richacl_alloc *alloc)
> +{
> +	struct richace *ace;
> +	unsigned int allowed = 0;
> +
> +	allowed = richacl_max_allowed(alloc->acl);
> +	if (allowed & ~alloc->acl->a_owner_mask) {
> +		/*
> +		 * Figure out if we can update an existig OWNER@ DENY entry.
> +		 */
> +		richacl_for_each_entry(ace, alloc->acl) {
> +			if (richace_is_inherit_only(ace))
> +				continue;
> +			if (richace_is_deny(ace)) {
> +				if (richace_is_owner(ace))
> +					break;
> +			} else if (richace_is_allow(ace)) {
> +				ace = alloc->acl->a_entries +
> +				      alloc->acl->a_count;
> +				break;
> +			}
> +		}
> +		if (ace != alloc->acl->a_entries + alloc->acl->a_count) {
> +			if (richace_change_mask(alloc, &ace, ace->e_mask |
> +					(allowed & ~alloc->acl->a_owner_mask)))
> +				return -1;
> +		} else {
> +			/* Insert an owner@ deny entry at the front. */
> +			ace = alloc->acl->a_entries;
> +			if (richacl_insert_entry(alloc, &ace))
> +				return -1;
> +			ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +			ace->e_flags = RICHACE_SPECIAL_WHO;
> +			ace->e_mask = allowed & ~alloc->acl->a_owner_mask;
> +			ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
> +		}
> +	}
> +	return 0;

Makes sense, though personally I'd find it simpler to follow without the
a_entries + a_count condition, maybe something like this (untested):

		richacl_for_each_entry(ace, alloc->acl) {
			if (richace_is_inherit_only(ace))
				continue;
			if (richace_is_allow(ace))
				break;
			if (richace_is_owner(ace))
				goto found;
		}
		/* Insert an owner@ deny entry at the front. */
		ace = alloc->acl->a_entries;
		if (richacl_insert_entry(alloc, &ace))
			return -1;
		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
		ace->e_flags = RICHACE_SPECIAL_WHO;
		ace->e_mask = allowed & ~alloc->acl->a_owner_mask;
		ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
		return 0;
	found:
		if (richace_change_mask(alloc, &ace, ace->e_mask |
					(allowed & ~alloc->acl->a_owner_mask)))
			return -1;
		return 0;

--b.

> +}
> +
> +/**
> + * __richacl_isolate_who  -  isolate entry from everyone@ allow entry
> + * @alloc:	acl and number of allocated entries
> + * @who:	identifier to isolate
> + * @deny:	permissions this identifier should not be allowed
> + *
> + * See richacl_isolate_group_class().
> + */
> +static int
> +__richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
> +		      unsigned int deny)
> +{
> +	struct richacl *acl = alloc->acl;
> +	struct richace *ace;
> +	int n;
> +	/*
> +	 * Compute the permissions already denied to @who.
> +	 */
> +	richacl_for_each_entry(ace, acl) {
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_same_identifier(ace, who) &&
> +		    richace_is_deny(ace))
> +			deny &= ~ace->e_mask;
> +	}
> +	if (!deny)
> +		return 0;
> +
> +	/*
> +	 * Figure out if we can update an existig deny entry.  Start from the
> +	 * entry before the trailing everyone@ allow entry. We will not hit
> +	 * everyone@ entries in the loop.
> +	 */
> +	for (n = acl->a_count - 2; n != -1; n--) {
> +		ace = acl->a_entries + n;
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_deny(ace)) {
> +			if (richace_is_same_identifier(ace, who))
> +				break;
> +		} else if (richace_is_allow(ace) &&
> +			   (ace->e_mask & deny)) {
> +			n = -1;
> +			break;
> +		}
> +	}
> +	if (n != -1) {
> +		if (richace_change_mask(alloc, &ace, ace->e_mask | deny))
> +			return -1;
> +	} else {
> +		/*
> +		 * Insert a new entry before the trailing everyone@ deny entry.
> +		 */
> +		struct richace who_copy;
> +
> +		richace_copy(&who_copy, who);
> +		ace = acl->a_entries + acl->a_count - 1;
> +		if (richacl_insert_entry(alloc, &ace))
> +			return -1;
> +		richace_copy(ace, &who_copy);
> +		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +		ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
> +		ace->e_mask = deny;
> +	}
> +	return 0;
> +}
> +
> +/**
> + * richacl_isolate_group_class  -  limit the group class to the group file mask
> + * @alloc:	acl and number of allocated entries
> + *
> + * POSIX requires that after a chmod, the group class is granted no more
> + * permissions than the group file permission bits.  For richacls, this
> + * means that the group class must not be granted any permissions that the
> + * group mask does not include.
> + *
> + * When we apply file masks to an acl which grant more permissions to the other
> + * class than to the group class, we may end up in a situation where processes
> + * in the group class are granted additional permission from other aces.  For
> + * example, given this acl:
> + *
> + *    joe:rwx::allow
> + *    everyone:rwx::allow
> + *
> + * when file masks corresponding to mode 0646 are applied, after
> + * richacl_propagate_everyone() and __richacl_apply_masks(), we end up with:
> + *
> + *    joe:r::allow
> + *    owner@:rw::allow
> + *    group@:r::allow
> + *    everyone@:rw::allow
> + *
> + * This acl still grants joe and group@ rw access through the everyone@ allow
> + * ace.  To fix this, we must deny w access to group class aces before the
> + * everyone@ allow ace at the end of the acl:
> + *
> + *    joe:r::allow
> + *    owner@:rw::allow
> + *    group@:r::allow
> + *    joe:w::deny
> + *    group@:w::deny
> + *    everyone@:rw::allow
> + */
> +static int
> +richacl_isolate_group_class(struct richacl_alloc *alloc)
> +{
> +	struct richace who = {
> +		.e_flags = RICHACE_SPECIAL_WHO,
> +		.e_id.special = RICHACE_GROUP_SPECIAL_ID,
> +	};
> +	struct richace *ace;
> +	unsigned int deny;
> +
> +	if (!alloc->acl->a_count)
> +		return 0;
> +	ace = alloc->acl->a_entries + alloc->acl->a_count - 1;
> +	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
> +		return 0;
> +	deny = ace->e_mask & ~alloc->acl->a_group_mask;
> +
> +	if (deny) {
> +		unsigned int n;
> +
> +		if (__richacl_isolate_who(alloc, &who, deny))
> +			return -1;
> +		/*
> +		 * Start from the entry before the trailing everyone@ allow
> +		 * entry.  We will not hit everyone@ entries in the loop.
> +		 */
> +		for (n = alloc->acl->a_count - 2; n != -1; n--) {
> +			ace = alloc->acl->a_entries + n;
> +
> +			if (richace_is_inherit_only(ace) ||
> +			    richace_is_owner(ace) ||
> +			    richace_is_group(ace) ||
> +			    richace_is_everyone(ace))
> +				continue;
> +			if (__richacl_isolate_who(alloc, ace, deny))
> +				return -1;
> +		}
> +	}
> +	return 0;
> +}
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 25/41] richacl: Isolate the owner and group classes
@ 2015-09-22 16:06         ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-22 16:06 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

On Sat, Sep 05, 2015 at 12:27:20PM +0200, Andreas Gruenbacher wrote:
> When applying the file masks to an acl, we need to ensure that no
> process gets more permissions than allowed by its file mask.
> 
> This may require inserting an owner@ deny ace to ensure this if the
> owner mask contains fewer permissions than the group or other mask.  For
> example, when applying mode 0466 to the following acl:
> 
>    everyone@:rw::allow
> 
> A deny ace needs to be inserted so that the owner won't get elevated
> write access:
> 
>    owner@:w::deny
>    everyone@:rw::allow
> 
> Likewise, we may need to insert group class deny aces if the group mask
> contains fewer permissions than the other mask.  For example, when
> applying mode 0646 to the following acl:
> 
>    owner@:rw::allow
>    everyone@:rw::allow
> 
> A deny ace needs to be inserted so that the owning group won't get
> elevated write access:
> 
>    owner@:rw::allow
>    group@:w::deny
>    everyone@:rw::allow
> 
> Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
> ---
>  fs/richacl_compat.c | 236 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 236 insertions(+)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 30bdc95..412844c 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -494,3 +494,239 @@ richacl_set_other_permissions(struct richacl_alloc *alloc)
>  		richace_change_mask(alloc, &ace, other_mask);
>  	return 0;
>  }
> +
> +/**
> + * richacl_max_allowed  -  maximum permissions that anybody is allowed
> + */
> +static unsigned int
> +richacl_max_allowed(struct richacl *acl)
> +{
> +	struct richace *ace;
> +	unsigned int allowed = 0;
> +
> +	richacl_for_each_entry_reverse(ace, acl) {
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_allow(ace))
> +			allowed |= ace->e_mask;
> +		else if (richace_is_deny(ace)) {
> +			if (richace_is_everyone(ace))
> +				allowed &= ~ace->e_mask;
> +		}
> +	}
> +	return allowed;
> +}
> +
> +/**
> + * richacl_isolate_owner_class  -  limit the owner class to the owner file mask
> + * @alloc:	acl and number of allocated entries
> + *
> + * POSIX requires that after a chmod, the owner class is granted no more
> + * permissions than the owner file permission bits.  For richacls, this
> + * means that the owner class must not be granted any permissions that the
> + * owner mask does not include.
> + *
> + * When we apply file masks to an acl which grant more permissions to the group
> + * or other class than to the owner class, we may end up in a situation where
> + * the owner is granted additional permissions from other aces.  For example,
> + * given this acl:
> + *
> + *    everyone:rwx::allow
> + *
> + * when file masks corresponding to mode 0466 are applied, after
> + * richacl_propagate_everyone() and __richacl_apply_masks(), we end up with:
> + *
> + *    owner@:r::allow
> + *    everyone@:rw::allow

Are you sure?  I didn't think richacl_apply_masks actually creates an
owner@ entry in this case.  Which is OK, just delete the owner@ ace from
here and the following example and it still makes sense, I think.

(But: thanks in general for the examples in these comments, they're
extremely helpful.)

> + *
> + * This acl still grants the owner rw access through the everyone@ allow ace.
> + * To fix this, we must deny the owner w access:
> + *
> + *    owner@:w::deny
> + *    owner@:r::allow
> + *    everyone@:rw::allow
> + */
> +static int
> +richacl_isolate_owner_class(struct richacl_alloc *alloc)
> +{
> +	struct richace *ace;
> +	unsigned int allowed = 0;
> +
> +	allowed = richacl_max_allowed(alloc->acl);
> +	if (allowed & ~alloc->acl->a_owner_mask) {
> +		/*
> +		 * Figure out if we can update an existig OWNER@ DENY entry.
> +		 */
> +		richacl_for_each_entry(ace, alloc->acl) {
> +			if (richace_is_inherit_only(ace))
> +				continue;
> +			if (richace_is_deny(ace)) {
> +				if (richace_is_owner(ace))
> +					break;
> +			} else if (richace_is_allow(ace)) {
> +				ace = alloc->acl->a_entries +
> +				      alloc->acl->a_count;
> +				break;
> +			}
> +		}
> +		if (ace != alloc->acl->a_entries + alloc->acl->a_count) {
> +			if (richace_change_mask(alloc, &ace, ace->e_mask |
> +					(allowed & ~alloc->acl->a_owner_mask)))
> +				return -1;
> +		} else {
> +			/* Insert an owner@ deny entry at the front. */
> +			ace = alloc->acl->a_entries;
> +			if (richacl_insert_entry(alloc, &ace))
> +				return -1;
> +			ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +			ace->e_flags = RICHACE_SPECIAL_WHO;
> +			ace->e_mask = allowed & ~alloc->acl->a_owner_mask;
> +			ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
> +		}
> +	}
> +	return 0;

Makes sense, though personally I'd find it simpler to follow without the
a_entries + a_count condition, maybe something like this (untested):

		richacl_for_each_entry(ace, alloc->acl) {
			if (richace_is_inherit_only(ace))
				continue;
			if (richace_is_allow(ace))
				break;
			if (richace_is_owner(ace))
				goto found;
		}
		/* Insert an owner@ deny entry at the front. */
		ace = alloc->acl->a_entries;
		if (richacl_insert_entry(alloc, &ace))
			return -1;
		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
		ace->e_flags = RICHACE_SPECIAL_WHO;
		ace->e_mask = allowed & ~alloc->acl->a_owner_mask;
		ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
		return 0;
	found:
		if (richace_change_mask(alloc, &ace, ace->e_mask |
					(allowed & ~alloc->acl->a_owner_mask)))
			return -1;
		return 0;

--b.

> +}
> +
> +/**
> + * __richacl_isolate_who  -  isolate entry from everyone@ allow entry
> + * @alloc:	acl and number of allocated entries
> + * @who:	identifier to isolate
> + * @deny:	permissions this identifier should not be allowed
> + *
> + * See richacl_isolate_group_class().
> + */
> +static int
> +__richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
> +		      unsigned int deny)
> +{
> +	struct richacl *acl = alloc->acl;
> +	struct richace *ace;
> +	int n;
> +	/*
> +	 * Compute the permissions already denied to @who.
> +	 */
> +	richacl_for_each_entry(ace, acl) {
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_same_identifier(ace, who) &&
> +		    richace_is_deny(ace))
> +			deny &= ~ace->e_mask;
> +	}
> +	if (!deny)
> +		return 0;
> +
> +	/*
> +	 * Figure out if we can update an existig deny entry.  Start from the
> +	 * entry before the trailing everyone@ allow entry. We will not hit
> +	 * everyone@ entries in the loop.
> +	 */
> +	for (n = acl->a_count - 2; n != -1; n--) {
> +		ace = acl->a_entries + n;
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_deny(ace)) {
> +			if (richace_is_same_identifier(ace, who))
> +				break;
> +		} else if (richace_is_allow(ace) &&
> +			   (ace->e_mask & deny)) {
> +			n = -1;
> +			break;
> +		}
> +	}
> +	if (n != -1) {
> +		if (richace_change_mask(alloc, &ace, ace->e_mask | deny))
> +			return -1;
> +	} else {
> +		/*
> +		 * Insert a new entry before the trailing everyone@ deny entry.
> +		 */
> +		struct richace who_copy;
> +
> +		richace_copy(&who_copy, who);
> +		ace = acl->a_entries + acl->a_count - 1;
> +		if (richacl_insert_entry(alloc, &ace))
> +			return -1;
> +		richace_copy(ace, &who_copy);
> +		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +		ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
> +		ace->e_mask = deny;
> +	}
> +	return 0;
> +}
> +
> +/**
> + * richacl_isolate_group_class  -  limit the group class to the group file mask
> + * @alloc:	acl and number of allocated entries
> + *
> + * POSIX requires that after a chmod, the group class is granted no more
> + * permissions than the group file permission bits.  For richacls, this
> + * means that the group class must not be granted any permissions that the
> + * group mask does not include.
> + *
> + * When we apply file masks to an acl which grant more permissions to the other
> + * class than to the group class, we may end up in a situation where processes
> + * in the group class are granted additional permission from other aces.  For
> + * example, given this acl:
> + *
> + *    joe:rwx::allow
> + *    everyone:rwx::allow
> + *
> + * when file masks corresponding to mode 0646 are applied, after
> + * richacl_propagate_everyone() and __richacl_apply_masks(), we end up with:
> + *
> + *    joe:r::allow
> + *    owner@:rw::allow
> + *    group@:r::allow
> + *    everyone@:rw::allow
> + *
> + * This acl still grants joe and group@ rw access through the everyone@ allow
> + * ace.  To fix this, we must deny w access to group class aces before the
> + * everyone@ allow ace at the end of the acl:
> + *
> + *    joe:r::allow
> + *    owner@:rw::allow
> + *    group@:r::allow
> + *    joe:w::deny
> + *    group@:w::deny
> + *    everyone@:rw::allow
> + */
> +static int
> +richacl_isolate_group_class(struct richacl_alloc *alloc)
> +{
> +	struct richace who = {
> +		.e_flags = RICHACE_SPECIAL_WHO,
> +		.e_id.special = RICHACE_GROUP_SPECIAL_ID,
> +	};
> +	struct richace *ace;
> +	unsigned int deny;
> +
> +	if (!alloc->acl->a_count)
> +		return 0;
> +	ace = alloc->acl->a_entries + alloc->acl->a_count - 1;
> +	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
> +		return 0;
> +	deny = ace->e_mask & ~alloc->acl->a_group_mask;
> +
> +	if (deny) {
> +		unsigned int n;
> +
> +		if (__richacl_isolate_who(alloc, &who, deny))
> +			return -1;
> +		/*
> +		 * Start from the entry before the trailing everyone@ allow
> +		 * entry.  We will not hit everyone@ entries in the loop.
> +		 */
> +		for (n = alloc->acl->a_count - 2; n != -1; n--) {
> +			ace = alloc->acl->a_entries + n;
> +
> +			if (richace_is_inherit_only(ace) ||
> +			    richace_is_owner(ace) ||
> +			    richace_is_group(ace) ||
> +			    richace_is_everyone(ace))
> +				continue;
> +			if (__richacl_isolate_who(alloc, ace, deny))
> +				return -1;
> +		}
> +	}
> +	return 0;
> +}
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 25/41] richacl: Isolate the owner and group classes
  2015-09-05 10:27     ` Andreas Gruenbacher
  (?)
  (?)
@ 2015-09-22 19:02     ` J. Bruce Fields
       [not found]       ` <20150922190224.GA19127-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
  -1 siblings, 1 reply; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-22 19:02 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

On Sat, Sep 05, 2015 at 12:27:20PM +0200, Andreas Gruenbacher wrote:
> When applying the file masks to an acl, we need to ensure that no
> process gets more permissions than allowed by its file mask.
> 
> This may require inserting an owner@ deny ace to ensure this if the
> owner mask contains fewer permissions than the group or other mask.  For
> example, when applying mode 0466 to the following acl:
> 
>    everyone@:rw::allow
> 
> A deny ace needs to be inserted so that the owner won't get elevated
> write access:
> 
>    owner@:w::deny
>    everyone@:rw::allow
> 
> Likewise, we may need to insert group class deny aces if the group mask
> contains fewer permissions than the other mask.  For example, when
> applying mode 0646 to the following acl:
> 
>    owner@:rw::allow
>    everyone@:rw::allow
> 
> A deny ace needs to be inserted so that the owning group won't get
> elevated write access:
> 
>    owner@:rw::allow
>    group@:w::deny
>    everyone@:rw::allow
> 
> Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
> ---
>  fs/richacl_compat.c | 236 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 236 insertions(+)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 30bdc95..412844c 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -494,3 +494,239 @@ richacl_set_other_permissions(struct richacl_alloc *alloc)
>  		richace_change_mask(alloc, &ace, other_mask);
>  	return 0;
>  }
> +
> +/**
> + * richacl_max_allowed  -  maximum permissions that anybody is allowed
> + */
> +static unsigned int
> +richacl_max_allowed(struct richacl *acl)
> +{
> +	struct richace *ace;
> +	unsigned int allowed = 0;
> +
> +	richacl_for_each_entry_reverse(ace, acl) {
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_allow(ace))
> +			allowed |= ace->e_mask;
> +		else if (richace_is_deny(ace)) {
> +			if (richace_is_everyone(ace))
> +				allowed &= ~ace->e_mask;
> +		}
> +	}
> +	return allowed;
> +}
> +
> +/**
> + * richacl_isolate_owner_class  -  limit the owner class to the owner file mask
> + * @alloc:	acl and number of allocated entries
> + *
> + * POSIX requires that after a chmod, the owner class is granted no more
> + * permissions than the owner file permission bits.  For richacls, this
> + * means that the owner class must not be granted any permissions that the
> + * owner mask does not include.
> + *
> + * When we apply file masks to an acl which grant more permissions to the group
> + * or other class than to the owner class, we may end up in a situation where
> + * the owner is granted additional permissions from other aces.  For example,
> + * given this acl:
> + *
> + *    everyone:rwx::allow
> + *
> + * when file masks corresponding to mode 0466 are applied, after
> + * richacl_propagate_everyone() and __richacl_apply_masks(), we end up with:
> + *
> + *    owner@:r::allow
> + *    everyone@:rw::allow
> + *
> + * This acl still grants the owner rw access through the everyone@ allow ace.
> + * To fix this, we must deny the owner w access:
> + *
> + *    owner@:w::deny
> + *    owner@:r::allow
> + *    everyone@:rw::allow
> + */
> +static int
> +richacl_isolate_owner_class(struct richacl_alloc *alloc)
> +{
> +	struct richace *ace;
> +	unsigned int allowed = 0;
> +
> +	allowed = richacl_max_allowed(alloc->acl);
> +	if (allowed & ~alloc->acl->a_owner_mask) {
> +		/*
> +		 * Figure out if we can update an existig OWNER@ DENY entry.
> +		 */
> +		richacl_for_each_entry(ace, alloc->acl) {
> +			if (richace_is_inherit_only(ace))
> +				continue;
> +			if (richace_is_deny(ace)) {
> +				if (richace_is_owner(ace))
> +					break;
> +			} else if (richace_is_allow(ace)) {
> +				ace = alloc->acl->a_entries +
> +				      alloc->acl->a_count;
> +				break;
> +			}
> +		}
> +		if (ace != alloc->acl->a_entries + alloc->acl->a_count) {
> +			if (richace_change_mask(alloc, &ace, ace->e_mask |
> +					(allowed & ~alloc->acl->a_owner_mask)))
> +				return -1;
> +		} else {
> +			/* Insert an owner@ deny entry at the front. */
> +			ace = alloc->acl->a_entries;
> +			if (richacl_insert_entry(alloc, &ace))
> +				return -1;
> +			ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +			ace->e_flags = RICHACE_SPECIAL_WHO;
> +			ace->e_mask = allowed & ~alloc->acl->a_owner_mask;
> +			ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
> +		}
> +	}
> +	return 0;
> +}
> +
> +/**
> + * __richacl_isolate_who  -  isolate entry from everyone@ allow entry
> + * @alloc:	acl and number of allocated entries
> + * @who:	identifier to isolate
> + * @deny:	permissions this identifier should not be allowed
> + *
> + * See richacl_isolate_group_class().
> + */
> +static int
> +__richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
> +		      unsigned int deny)
> +{
> +	struct richacl *acl = alloc->acl;
> +	struct richace *ace;
> +	int n;
> +	/*
> +	 * Compute the permissions already denied to @who.

I'm not sure, but may be worth commenting on the lack of everyone denies
here as you do in a couple places below.

> +	 */
> +	richacl_for_each_entry(ace, acl) {
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_same_identifier(ace, who) &&
> +		    richace_is_deny(ace))
> +			deny &= ~ace->e_mask;
> +	}
> +	if (!deny)
> +		return 0;
> +
> +	/*
> +	 * Figure out if we can update an existig deny entry.  Start from the
> +	 * entry before the trailing everyone@ allow entry. We will not hit
> +	 * everyone@ entries in the loop.
> +	 */
> +	for (n = acl->a_count - 2; n != -1; n--) {
> +		ace = acl->a_entries + n;
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_deny(ace)) {
> +			if (richace_is_same_identifier(ace, who))
> +				break;
> +		} else if (richace_is_allow(ace) &&
> +			   (ace->e_mask & deny)) {
> +			n = -1;
> +			break;
> +		}
> +	}
> +	if (n != -1) {
> +		if (richace_change_mask(alloc, &ace, ace->e_mask | deny))
> +			return -1;
> +	} else {
> +		/*
> +		 * Insert a new entry before the trailing everyone@ deny entry.
> +		 */
> +		struct richace who_copy;
> +
> +		richace_copy(&who_copy, who);
> +		ace = acl->a_entries + acl->a_count - 1;
> +		if (richacl_insert_entry(alloc, &ace))
> +			return -1;
> +		richace_copy(ace, &who_copy);
> +		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +		ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
> +		ace->e_mask = deny;
> +	}

As in the owner case, I think a goto would simplify the logic a little.

> +	return 0;
> +}
> +
> +/**
> + * richacl_isolate_group_class  -  limit the group class to the group file mask
> + * @alloc:	acl and number of allocated entries
> + *
> + * POSIX requires that after a chmod, the group class is granted no more
> + * permissions than the group file permission bits.  For richacls, this
> + * means that the group class must not be granted any permissions that the
> + * group mask does not include.
> + *
> + * When we apply file masks to an acl which grant more permissions to the other
> + * class than to the group class, we may end up in a situation where processes
> + * in the group class are granted additional permission from other aces.  For
> + * example, given this acl:
> + *
> + *    joe:rwx::allow
> + *    everyone:rwx::allow
> + *
> + * when file masks corresponding to mode 0646 are applied, after
> + * richacl_propagate_everyone() and __richacl_apply_masks(), we end up with:
> + *
> + *    joe:r::allow
> + *    owner@:rw::allow
> + *    group@:r::allow
> + *    everyone@:rw::allow
> + *
> + * This acl still grants joe and group@ rw access through the everyone@ allow
> + * ace.  To fix this, we must deny w access to group class aces before the
> + * everyone@ allow ace at the end of the acl:
> + *
> + *    joe:r::allow
> + *    owner@:rw::allow
> + *    group@:r::allow
> + *    joe:w::deny
> + *    group@:w::deny
> + *    everyone@:rw::allow
> + */
> +static int
> +richacl_isolate_group_class(struct richacl_alloc *alloc)
> +{
> +	struct richace who = {
> +		.e_flags = RICHACE_SPECIAL_WHO,
> +		.e_id.special = RICHACE_GROUP_SPECIAL_ID,
> +	};
> +	struct richace *ace;
> +	unsigned int deny;
> +
> +	if (!alloc->acl->a_count)
> +		return 0;
> +	ace = alloc->acl->a_entries + alloc->acl->a_count - 1;
> +	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
> +		return 0;
> +	deny = ace->e_mask & ~alloc->acl->a_group_mask;
> +
> +	if (deny) {
> +		unsigned int n;
> +
> +		if (__richacl_isolate_who(alloc, &who, deny))
> +			return -1;
> +		/*
> +		 * Start from the entry before the trailing everyone@ allow
> +		 * entry.  We will not hit everyone@ entries in the loop.

May as well skip the check below, in that case?

--b.

> +		 */
> +		for (n = alloc->acl->a_count - 2; n != -1; n--) {
> +			ace = alloc->acl->a_entries + n;
> +
> +			if (richace_is_inherit_only(ace) ||
> +			    richace_is_owner(ace) ||
> +			    richace_is_group(ace) ||
> +			    richace_is_everyone(ace))
> +				continue;
> +			if (__richacl_isolate_who(alloc, ace, deny))
> +				return -1;
> +		}
> +	}
> +	return 0;
> +}
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 25/41] richacl: Isolate the owner and group classes
  2015-09-05 10:27     ` Andreas Gruenbacher
                       ` (2 preceding siblings ...)
  (?)
@ 2015-09-22 19:02     ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-22 19:02 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

Oh, and my only comments were nits, this looks good to me:

	Reviewed-by: J. Bruce Fields <bfields@redhat.com>

--b.

On Sat, Sep 05, 2015 at 12:27:20PM +0200, Andreas Gruenbacher wrote:
> When applying the file masks to an acl, we need to ensure that no
> process gets more permissions than allowed by its file mask.
> 
> This may require inserting an owner@ deny ace to ensure this if the
> owner mask contains fewer permissions than the group or other mask.  For
> example, when applying mode 0466 to the following acl:
> 
>    everyone@:rw::allow
> 
> A deny ace needs to be inserted so that the owner won't get elevated
> write access:
> 
>    owner@:w::deny
>    everyone@:rw::allow
> 
> Likewise, we may need to insert group class deny aces if the group mask
> contains fewer permissions than the other mask.  For example, when
> applying mode 0646 to the following acl:
> 
>    owner@:rw::allow
>    everyone@:rw::allow
> 
> A deny ace needs to be inserted so that the owning group won't get
> elevated write access:
> 
>    owner@:rw::allow
>    group@:w::deny
>    everyone@:rw::allow
> 
> Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
> ---
>  fs/richacl_compat.c | 236 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 236 insertions(+)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 30bdc95..412844c 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -494,3 +494,239 @@ richacl_set_other_permissions(struct richacl_alloc *alloc)
>  		richace_change_mask(alloc, &ace, other_mask);
>  	return 0;
>  }
> +
> +/**
> + * richacl_max_allowed  -  maximum permissions that anybody is allowed
> + */
> +static unsigned int
> +richacl_max_allowed(struct richacl *acl)
> +{
> +	struct richace *ace;
> +	unsigned int allowed = 0;
> +
> +	richacl_for_each_entry_reverse(ace, acl) {
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_allow(ace))
> +			allowed |= ace->e_mask;
> +		else if (richace_is_deny(ace)) {
> +			if (richace_is_everyone(ace))
> +				allowed &= ~ace->e_mask;
> +		}
> +	}
> +	return allowed;
> +}
> +
> +/**
> + * richacl_isolate_owner_class  -  limit the owner class to the owner file mask
> + * @alloc:	acl and number of allocated entries
> + *
> + * POSIX requires that after a chmod, the owner class is granted no more
> + * permissions than the owner file permission bits.  For richacls, this
> + * means that the owner class must not be granted any permissions that the
> + * owner mask does not include.
> + *
> + * When we apply file masks to an acl which grant more permissions to the group
> + * or other class than to the owner class, we may end up in a situation where
> + * the owner is granted additional permissions from other aces.  For example,
> + * given this acl:
> + *
> + *    everyone:rwx::allow
> + *
> + * when file masks corresponding to mode 0466 are applied, after
> + * richacl_propagate_everyone() and __richacl_apply_masks(), we end up with:
> + *
> + *    owner@:r::allow
> + *    everyone@:rw::allow
> + *
> + * This acl still grants the owner rw access through the everyone@ allow ace.
> + * To fix this, we must deny the owner w access:
> + *
> + *    owner@:w::deny
> + *    owner@:r::allow
> + *    everyone@:rw::allow
> + */
> +static int
> +richacl_isolate_owner_class(struct richacl_alloc *alloc)
> +{
> +	struct richace *ace;
> +	unsigned int allowed = 0;
> +
> +	allowed = richacl_max_allowed(alloc->acl);
> +	if (allowed & ~alloc->acl->a_owner_mask) {
> +		/*
> +		 * Figure out if we can update an existig OWNER@ DENY entry.
> +		 */
> +		richacl_for_each_entry(ace, alloc->acl) {
> +			if (richace_is_inherit_only(ace))
> +				continue;
> +			if (richace_is_deny(ace)) {
> +				if (richace_is_owner(ace))
> +					break;
> +			} else if (richace_is_allow(ace)) {
> +				ace = alloc->acl->a_entries +
> +				      alloc->acl->a_count;
> +				break;
> +			}
> +		}
> +		if (ace != alloc->acl->a_entries + alloc->acl->a_count) {
> +			if (richace_change_mask(alloc, &ace, ace->e_mask |
> +					(allowed & ~alloc->acl->a_owner_mask)))
> +				return -1;
> +		} else {
> +			/* Insert an owner@ deny entry at the front. */
> +			ace = alloc->acl->a_entries;
> +			if (richacl_insert_entry(alloc, &ace))
> +				return -1;
> +			ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +			ace->e_flags = RICHACE_SPECIAL_WHO;
> +			ace->e_mask = allowed & ~alloc->acl->a_owner_mask;
> +			ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
> +		}
> +	}
> +	return 0;
> +}
> +
> +/**
> + * __richacl_isolate_who  -  isolate entry from everyone@ allow entry
> + * @alloc:	acl and number of allocated entries
> + * @who:	identifier to isolate
> + * @deny:	permissions this identifier should not be allowed
> + *
> + * See richacl_isolate_group_class().
> + */
> +static int
> +__richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
> +		      unsigned int deny)
> +{
> +	struct richacl *acl = alloc->acl;
> +	struct richace *ace;
> +	int n;
> +	/*
> +	 * Compute the permissions already denied to @who.
> +	 */
> +	richacl_for_each_entry(ace, acl) {
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_same_identifier(ace, who) &&
> +		    richace_is_deny(ace))
> +			deny &= ~ace->e_mask;
> +	}
> +	if (!deny)
> +		return 0;
> +
> +	/*
> +	 * Figure out if we can update an existig deny entry.  Start from the
> +	 * entry before the trailing everyone@ allow entry. We will not hit
> +	 * everyone@ entries in the loop.
> +	 */
> +	for (n = acl->a_count - 2; n != -1; n--) {
> +		ace = acl->a_entries + n;
> +		if (richace_is_inherit_only(ace))
> +			continue;
> +		if (richace_is_deny(ace)) {
> +			if (richace_is_same_identifier(ace, who))
> +				break;
> +		} else if (richace_is_allow(ace) &&
> +			   (ace->e_mask & deny)) {
> +			n = -1;
> +			break;
> +		}
> +	}
> +	if (n != -1) {
> +		if (richace_change_mask(alloc, &ace, ace->e_mask | deny))
> +			return -1;
> +	} else {
> +		/*
> +		 * Insert a new entry before the trailing everyone@ deny entry.
> +		 */
> +		struct richace who_copy;
> +
> +		richace_copy(&who_copy, who);
> +		ace = acl->a_entries + acl->a_count - 1;
> +		if (richacl_insert_entry(alloc, &ace))
> +			return -1;
> +		richace_copy(ace, &who_copy);
> +		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +		ace->e_flags &= ~RICHACE_INHERITANCE_FLAGS;
> +		ace->e_mask = deny;
> +	}
> +	return 0;
> +}
> +
> +/**
> + * richacl_isolate_group_class  -  limit the group class to the group file mask
> + * @alloc:	acl and number of allocated entries
> + *
> + * POSIX requires that after a chmod, the group class is granted no more
> + * permissions than the group file permission bits.  For richacls, this
> + * means that the group class must not be granted any permissions that the
> + * group mask does not include.
> + *
> + * When we apply file masks to an acl which grant more permissions to the other
> + * class than to the group class, we may end up in a situation where processes
> + * in the group class are granted additional permission from other aces.  For
> + * example, given this acl:
> + *
> + *    joe:rwx::allow
> + *    everyone:rwx::allow
> + *
> + * when file masks corresponding to mode 0646 are applied, after
> + * richacl_propagate_everyone() and __richacl_apply_masks(), we end up with:
> + *
> + *    joe:r::allow
> + *    owner@:rw::allow
> + *    group@:r::allow
> + *    everyone@:rw::allow
> + *
> + * This acl still grants joe and group@ rw access through the everyone@ allow
> + * ace.  To fix this, we must deny w access to group class aces before the
> + * everyone@ allow ace at the end of the acl:
> + *
> + *    joe:r::allow
> + *    owner@:rw::allow
> + *    group@:r::allow
> + *    joe:w::deny
> + *    group@:w::deny
> + *    everyone@:rw::allow
> + */
> +static int
> +richacl_isolate_group_class(struct richacl_alloc *alloc)
> +{
> +	struct richace who = {
> +		.e_flags = RICHACE_SPECIAL_WHO,
> +		.e_id.special = RICHACE_GROUP_SPECIAL_ID,
> +	};
> +	struct richace *ace;
> +	unsigned int deny;
> +
> +	if (!alloc->acl->a_count)
> +		return 0;
> +	ace = alloc->acl->a_entries + alloc->acl->a_count - 1;
> +	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
> +		return 0;
> +	deny = ace->e_mask & ~alloc->acl->a_group_mask;
> +
> +	if (deny) {
> +		unsigned int n;
> +
> +		if (__richacl_isolate_who(alloc, &who, deny))
> +			return -1;
> +		/*
> +		 * Start from the entry before the trailing everyone@ allow
> +		 * entry.  We will not hit everyone@ entries in the loop.
> +		 */
> +		for (n = alloc->acl->a_count - 2; n != -1; n--) {
> +			ace = alloc->acl->a_entries + n;
> +
> +			if (richace_is_inherit_only(ace) ||
> +			    richace_is_owner(ace) ||
> +			    richace_is_group(ace) ||
> +			    richace_is_everyone(ace))
> +				continue;
> +			if (__richacl_isolate_who(alloc, ace, deny))
> +				return -1;
> +		}
> +	}
> +	return 0;
> +}
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 26/41] richacl: Apply the file masks to a richacl
  2015-09-05 10:27     ` Andreas Gruenbacher
  (?)
@ 2015-09-22 19:11     ` J. Bruce Fields
       [not found]       ` <20150922191108.GC19127-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
  -1 siblings, 1 reply; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-22 19:11 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

On Sat, Sep 05, 2015 at 12:27:21PM +0200, Andreas Gruenbacher wrote:
> Put all the pieces of the acl transformation puzzle together for
> computing a richacl which has the file masks "applied" so that the
> standard nfsv4 access check algorithm can be used on the richacl.
> 
> Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
> ---
>  fs/richacl_compat.c     | 110 ++++++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/richacl.h |   3 ++
>  2 files changed, 113 insertions(+)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 412844c..9681efe 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -730,3 +730,113 @@ richacl_isolate_group_class(struct richacl_alloc *alloc)
>  	}
>  	return 0;
>  }
> +
> +/**
> + * __richacl_apply_masks  -  apply the file masks to all aces
> + * @alloc:	acl and number of allocated entries
> + *
> + * Apply the owner mask to owner@ aces, the other mask to
> + * everyone@ aces, and the group mask to all other aces.
> + *
> + * The previous transformations have brought the acl into a
> + * form in which applying the masks will not lead to the
> + * accidental loss of permissions anymore.
> + */
> +static int
> +__richacl_apply_masks(struct richacl_alloc *alloc, kuid_t owner)
> +{
> +	struct richace *ace;
> +
> +	richacl_for_each_entry(ace, alloc->acl) {
> +		unsigned int mask;
> +
> +		if (richace_is_inherit_only(ace) || !richace_is_allow(ace))
> +			continue;
> +		if (richace_is_owner(ace) ||
> +		    (richace_is_unix_user(ace) && uid_eq(owner, ace->e_id.uid)))
> +			mask = alloc->acl->a_owner_mask;

Is treating matching user aces like owner aces what you intended to do,
and if so, why?

--b.

> +		else if (richace_is_everyone(ace))
> +			mask = alloc->acl->a_other_mask;
> +		else
> +			mask = alloc->acl->a_group_mask;
> +		if (richace_change_mask(alloc, &ace, ace->e_mask & mask))
> +			return -1;
> +	}
> +	return 0;
> +}
> +
> +/**
> + * richacl_apply_masks  -  apply the masks to the acl
> + *
> + * Transform @acl so that the standard NFSv4 permission check algorithm (which
> + * is not aware of file masks) will compute the same access decisions as the
> + * richacl permission check algorithm (which looks at the acl and the file
> + * masks).
> + *
> + * This algorithm is split into several steps:
> + *
> + *   - Move everyone@ aces to the end of the acl.  This simplifies the other
> + *     transformations, and allows the everyone@ allow ace at the end of the
> + *     acl to eventually allow permissions to the other class only.
> + *
> + *   - Propagate everyone@ permissions up the acl.  This transformation makes
> + *     sure that the owner and group class aces won't lose any permissions when
> + *     we apply the other mask to the everyone@ allow ace at the end of the acl.
> + *
> + *   - Apply the file masks to all aces.
> + *
> + *   - Make sure the owner is granted the owner mask permissions.
> + *
> + *   - Make sure everyone is granted the other mask permissions.
> + *
> + *   - Make sure that the owner is not granted any permissions beyond the owner
> + *     mask from group class aces or from everyone@.
> + *
> + *   - Make sure that the group class is not granted any permissions from
> + *     everyone@.
> + *
> + * The algorithm is exact except for richacls which cannot be represented as an
> + * acl alone: for example, given this acl:
> + *
> + *    group@:rw::allow
> + *
> + * when file masks corresponding to mode 0600 are applied, the owner would only
> + * get rw access if he is a member of the owning group.  This algorithm would
> + * produce an empty acl in this case.  We fix this case by modifying
> + * richacl_permission() so that the group mask is always applied to group class
> + * aces.  With this fix, the owner would not have any access (beyond the
> + * implicit permissions always granted to owners).
> + *
> + * NOTE: Depending on the acl and file masks, this algorithm can increase the
> + * number of aces by almost a factor of three in the worst case. This may make
> + * the acl too large for some purposes.
> + */
> +int
> +richacl_apply_masks(struct richacl **acl, kuid_t owner)
> +{
> +	if ((*acl)->a_flags & RICHACL_MASKED) {
> +		struct richacl_alloc alloc = {
> +			.acl = richacl_clone(*acl, GFP_KERNEL),
> +			.count = (*acl)->a_count,
> +		};
> +		if (!alloc.acl)
> +			return -ENOMEM;
> +
> +		if (richacl_move_everyone_aces_down(&alloc) ||
> +		    richacl_propagate_everyone(&alloc) ||
> +		    __richacl_apply_masks(&alloc, owner) ||
> +		    richacl_set_owner_permissions(&alloc) ||
> +		    richacl_set_other_permissions(&alloc) ||
> +		    richacl_isolate_owner_class(&alloc) ||
> +		    richacl_isolate_group_class(&alloc)) {
> +			richacl_put(alloc.acl);
> +			return -ENOMEM;
> +		}
> +
> +		alloc.acl->a_flags &= ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED);
> +		richacl_put(*acl);
> +		*acl = alloc.acl;
> +	}
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(richacl_apply_masks);
> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> index 832b06c..a945f3c 100644
> --- a/include/linux/richacl.h
> +++ b/include/linux/richacl.h
> @@ -332,4 +332,7 @@ extern struct richacl *richacl_inherit(const struct richacl *, int);
>  extern int richacl_permission(struct inode *, const struct richacl *, int);
>  extern struct richacl *richacl_create(struct inode *, struct inode *);
>  
> +/* richacl_compat.c */
> +extern int richacl_apply_masks(struct richacl **, kuid_t);
> +
>  #endif /* __RICHACL_H */
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 26/41] richacl: Apply the file masks to a richacl
  2015-09-05 10:27     ` Andreas Gruenbacher
@ 2015-09-22 20:50         ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-22 20:50 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

On Sat, Sep 05, 2015 at 12:27:21PM +0200, Andreas Gruenbacher wrote:
> Put all the pieces of the acl transformation puzzle together for
> computing a richacl which has the file masks "applied" so that the
> standard nfsv4 access check algorithm can be used on the richacl.
> 
> Signed-off-by: Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> ---
>  fs/richacl_compat.c     | 110 ++++++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/richacl.h |   3 ++
>  2 files changed, 113 insertions(+)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 412844c..9681efe 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -730,3 +730,113 @@ richacl_isolate_group_class(struct richacl_alloc *alloc)
>  	}
>  	return 0;
>  }
> +
> +/**
> + * __richacl_apply_masks  -  apply the file masks to all aces
> + * @alloc:	acl and number of allocated entries
> + *
> + * Apply the owner mask to owner@ aces, the other mask to
> + * everyone@ aces, and the group mask to all other aces.
> + *
> + * The previous transformations have brought the acl into a
> + * form in which applying the masks will not lead to the
> + * accidental loss of permissions anymore.
> + */
> +static int
> +__richacl_apply_masks(struct richacl_alloc *alloc, kuid_t owner)
> +{
> +	struct richace *ace;
> +
> +	richacl_for_each_entry(ace, alloc->acl) {
> +		unsigned int mask;
> +
> +		if (richace_is_inherit_only(ace) || !richace_is_allow(ace))
> +			continue;
> +		if (richace_is_owner(ace) ||
> +		    (richace_is_unix_user(ace) && uid_eq(owner, ace->e_id.uid)))
> +			mask = alloc->acl->a_owner_mask;
> +		else if (richace_is_everyone(ace))
> +			mask = alloc->acl->a_other_mask;
> +		else
> +			mask = alloc->acl->a_group_mask;
> +		if (richace_change_mask(alloc, &ace, ace->e_mask & mask))
> +			return -1;
> +	}
> +	return 0;
> +}
> +
> +/**
> + * richacl_apply_masks  -  apply the masks to the acl
> + *
> + * Transform @acl so that the standard NFSv4 permission check algorithm (which
> + * is not aware of file masks) will compute the same access decisions as the
> + * richacl permission check algorithm (which looks at the acl and the file
> + * masks).
> + *
> + * This algorithm is split into several steps:
> + *
> + *   - Move everyone@ aces to the end of the acl.  This simplifies the other
> + *     transformations, and allows the everyone@ allow ace at the end of the
> + *     acl to eventually allow permissions to the other class only.
> + *
> + *   - Propagate everyone@ permissions up the acl.  This transformation makes
> + *     sure that the owner and group class aces won't lose any permissions when
> + *     we apply the other mask to the everyone@ allow ace at the end of the acl.
> + *
> + *   - Apply the file masks to all aces.
> + *
> + *   - Make sure the owner is granted the owner mask permissions.
> + *
> + *   - Make sure everyone is granted the other mask permissions.
> + *
> + *   - Make sure that the owner is not granted any permissions beyond the owner
> + *     mask from group class aces or from everyone@.
> + *
> + *   - Make sure that the group class is not granted any permissions from
> + *     everyone@.
> + *
> + * The algorithm is exact except for richacls which cannot be represented as an
> + * acl alone: for example, given this acl:
> + *
> + *    group@:rw::allow
> + *
> + * when file masks corresponding to mode 0600 are applied, the owner would only
> + * get rw access if he is a member of the owning group.  This algorithm would
> + * produce an empty acl in this case.  We fix this case by modifying
> + * richacl_permission() so that the group mask is always applied to group class
> + * aces.  With this fix, the owner would not have any access (beyond the
> + * implicit permissions always granted to owners).

That's a confusing way to put it: you say the algorithm isn't exact,
then say, actually, no, it *is* exact, because of a change we made to
richacls.

Better to just say from the start "the algorithm is exact", and then
explain why.  (Actually, the term "exact" isn't really defined here:
maybe say what you mean, something like "the resulting ACL will always
grant the same permissions that the original ACL would".)

> + *
> + * NOTE: Depending on the acl and file masks, this algorithm can increase the
> + * number of aces by almost a factor of three in the worst case. This may make
> + * the acl too large for some purposes.

We should maybe give some guidelines (e.g., "this won't happen if you
always use modes that grant more to group than user, and more to other
than group (if that's true)).

--b.

> + */
> +int
> +richacl_apply_masks(struct richacl **acl, kuid_t owner)
> +{
> +	if ((*acl)->a_flags & RICHACL_MASKED) {
> +		struct richacl_alloc alloc = {
> +			.acl = richacl_clone(*acl, GFP_KERNEL),
> +			.count = (*acl)->a_count,
> +		};
> +		if (!alloc.acl)
> +			return -ENOMEM;
> +
> +		if (richacl_move_everyone_aces_down(&alloc) ||
> +		    richacl_propagate_everyone(&alloc) ||
> +		    __richacl_apply_masks(&alloc, owner) ||
> +		    richacl_set_owner_permissions(&alloc) ||
> +		    richacl_set_other_permissions(&alloc) ||
> +		    richacl_isolate_owner_class(&alloc) ||
> +		    richacl_isolate_group_class(&alloc)) {
> +			richacl_put(alloc.acl);
> +			return -ENOMEM;
> +		}
> +
> +		alloc.acl->a_flags &= ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED);
> +		richacl_put(*acl);
> +		*acl = alloc.acl;
> +	}
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(richacl_apply_masks);
> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> index 832b06c..a945f3c 100644
> --- a/include/linux/richacl.h
> +++ b/include/linux/richacl.h
> @@ -332,4 +332,7 @@ extern struct richacl *richacl_inherit(const struct richacl *, int);
>  extern int richacl_permission(struct inode *, const struct richacl *, int);
>  extern struct richacl *richacl_create(struct inode *, struct inode *);
>  
> +/* richacl_compat.c */
> +extern int richacl_apply_masks(struct richacl **, kuid_t);
> +
>  #endif /* __RICHACL_H */
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 26/41] richacl: Apply the file masks to a richacl
@ 2015-09-22 20:50         ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-22 20:50 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

On Sat, Sep 05, 2015 at 12:27:21PM +0200, Andreas Gruenbacher wrote:
> Put all the pieces of the acl transformation puzzle together for
> computing a richacl which has the file masks "applied" so that the
> standard nfsv4 access check algorithm can be used on the richacl.
> 
> Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
> ---
>  fs/richacl_compat.c     | 110 ++++++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/richacl.h |   3 ++
>  2 files changed, 113 insertions(+)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 412844c..9681efe 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -730,3 +730,113 @@ richacl_isolate_group_class(struct richacl_alloc *alloc)
>  	}
>  	return 0;
>  }
> +
> +/**
> + * __richacl_apply_masks  -  apply the file masks to all aces
> + * @alloc:	acl and number of allocated entries
> + *
> + * Apply the owner mask to owner@ aces, the other mask to
> + * everyone@ aces, and the group mask to all other aces.
> + *
> + * The previous transformations have brought the acl into a
> + * form in which applying the masks will not lead to the
> + * accidental loss of permissions anymore.
> + */
> +static int
> +__richacl_apply_masks(struct richacl_alloc *alloc, kuid_t owner)
> +{
> +	struct richace *ace;
> +
> +	richacl_for_each_entry(ace, alloc->acl) {
> +		unsigned int mask;
> +
> +		if (richace_is_inherit_only(ace) || !richace_is_allow(ace))
> +			continue;
> +		if (richace_is_owner(ace) ||
> +		    (richace_is_unix_user(ace) && uid_eq(owner, ace->e_id.uid)))
> +			mask = alloc->acl->a_owner_mask;
> +		else if (richace_is_everyone(ace))
> +			mask = alloc->acl->a_other_mask;
> +		else
> +			mask = alloc->acl->a_group_mask;
> +		if (richace_change_mask(alloc, &ace, ace->e_mask & mask))
> +			return -1;
> +	}
> +	return 0;
> +}
> +
> +/**
> + * richacl_apply_masks  -  apply the masks to the acl
> + *
> + * Transform @acl so that the standard NFSv4 permission check algorithm (which
> + * is not aware of file masks) will compute the same access decisions as the
> + * richacl permission check algorithm (which looks at the acl and the file
> + * masks).
> + *
> + * This algorithm is split into several steps:
> + *
> + *   - Move everyone@ aces to the end of the acl.  This simplifies the other
> + *     transformations, and allows the everyone@ allow ace at the end of the
> + *     acl to eventually allow permissions to the other class only.
> + *
> + *   - Propagate everyone@ permissions up the acl.  This transformation makes
> + *     sure that the owner and group class aces won't lose any permissions when
> + *     we apply the other mask to the everyone@ allow ace at the end of the acl.
> + *
> + *   - Apply the file masks to all aces.
> + *
> + *   - Make sure the owner is granted the owner mask permissions.
> + *
> + *   - Make sure everyone is granted the other mask permissions.
> + *
> + *   - Make sure that the owner is not granted any permissions beyond the owner
> + *     mask from group class aces or from everyone@.
> + *
> + *   - Make sure that the group class is not granted any permissions from
> + *     everyone@.
> + *
> + * The algorithm is exact except for richacls which cannot be represented as an
> + * acl alone: for example, given this acl:
> + *
> + *    group@:rw::allow
> + *
> + * when file masks corresponding to mode 0600 are applied, the owner would only
> + * get rw access if he is a member of the owning group.  This algorithm would
> + * produce an empty acl in this case.  We fix this case by modifying
> + * richacl_permission() so that the group mask is always applied to group class
> + * aces.  With this fix, the owner would not have any access (beyond the
> + * implicit permissions always granted to owners).

That's a confusing way to put it: you say the algorithm isn't exact,
then say, actually, no, it *is* exact, because of a change we made to
richacls.

Better to just say from the start "the algorithm is exact", and then
explain why.  (Actually, the term "exact" isn't really defined here:
maybe say what you mean, something like "the resulting ACL will always
grant the same permissions that the original ACL would".)

> + *
> + * NOTE: Depending on the acl and file masks, this algorithm can increase the
> + * number of aces by almost a factor of three in the worst case. This may make
> + * the acl too large for some purposes.

We should maybe give some guidelines (e.g., "this won't happen if you
always use modes that grant more to group than user, and more to other
than group (if that's true)).

--b.

> + */
> +int
> +richacl_apply_masks(struct richacl **acl, kuid_t owner)
> +{
> +	if ((*acl)->a_flags & RICHACL_MASKED) {
> +		struct richacl_alloc alloc = {
> +			.acl = richacl_clone(*acl, GFP_KERNEL),
> +			.count = (*acl)->a_count,
> +		};
> +		if (!alloc.acl)
> +			return -ENOMEM;
> +
> +		if (richacl_move_everyone_aces_down(&alloc) ||
> +		    richacl_propagate_everyone(&alloc) ||
> +		    __richacl_apply_masks(&alloc, owner) ||
> +		    richacl_set_owner_permissions(&alloc) ||
> +		    richacl_set_other_permissions(&alloc) ||
> +		    richacl_isolate_owner_class(&alloc) ||
> +		    richacl_isolate_group_class(&alloc)) {
> +			richacl_put(alloc.acl);
> +			return -ENOMEM;
> +		}
> +
> +		alloc.acl->a_flags &= ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED);
> +		richacl_put(*acl);
> +		*acl = alloc.acl;
> +	}
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(richacl_apply_masks);
> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> index 832b06c..a945f3c 100644
> --- a/include/linux/richacl.h
> +++ b/include/linux/richacl.h
> @@ -332,4 +332,7 @@ extern struct richacl *richacl_inherit(const struct richacl *, int);
>  extern int richacl_permission(struct inode *, const struct richacl *, int);
>  extern struct richacl *richacl_create(struct inode *, struct inode *);
>  
> +/* richacl_compat.c */
> +extern int richacl_apply_masks(struct richacl **, kuid_t);
> +
>  #endif /* __RICHACL_H */
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 22/41] richacl: Propagate everyone@ permissions to other aces
  2015-09-21 19:24             ` J. Bruce Fields
@ 2015-09-23  1:24                 ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-23  1:24 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: LKML, linux-fsdevel, linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

2015-09-21 21:24 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> On Fri, Sep 18, 2015 at 05:56:11PM -0400, bfields wrote:
>> On Sat, Sep 05, 2015 at 12:27:17PM +0200, Andreas Gruenbacher wrote:
>> > +   /*
>> > +    * If the owner mask contains permissions which are not in the group
>> > +    * mask, the group mask contains permissions which are not in the other
>> > +    * mask, or the owner class contains permissions which are not in the
>>
>> s/owner class/owner mask?
>>
>> > +    * other mask, we may need to propagate permissions up from the
>> > +    * everyone@ allow ace.  The third condition is implied by the first
>> > +    * two.
>> > +    */
>> > +   if (!((acl->a_owner_mask & ~acl->a_group_mask) ||
>> > +         (acl->a_group_mask & ~acl->a_other_mask)))
>> > +           return 0;
>>
>> The code looks right, but I don't understand the preceding comment.
>>
>> For example,
>>
>>       owner mask: rw
>>       group mask:  wx
>>       other mask: rw
>>
>> satisfies the first two conditions, but not the third.
>>
>> Also, I don't understand why the first condition would imply that we
>> might need to propagate permissions.
>
> OK, maybe I get the part about the owner mask containing permissions
> not in the group mask: we'll need to insert a deny ace for the bits in
> the other mask but not in the group mask, and then we'll need an allow
> ace for the owner to get those bits back.  I think?

That is indeed the reason, and it also seems clear that this wasn't
documented well enough. Let me remove the offending comment and tiny
optimization, and add better comments instead.

>> > +                   if (richace_is_allow(ace) || richace_is_deny(ace)) {
>
> The v4 spec allows aces other than allow and deny aces (audit and
> alarm), but I didn't think you were implementing those.

Right, I don't see that happening. I'll remove that as well.

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 22/41] richacl: Propagate everyone@ permissions to other aces
@ 2015-09-23  1:24                 ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-23  1:24 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: LKML, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

2015-09-21 21:24 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> On Fri, Sep 18, 2015 at 05:56:11PM -0400, bfields wrote:
>> On Sat, Sep 05, 2015 at 12:27:17PM +0200, Andreas Gruenbacher wrote:
>> > +   /*
>> > +    * If the owner mask contains permissions which are not in the group
>> > +    * mask, the group mask contains permissions which are not in the other
>> > +    * mask, or the owner class contains permissions which are not in the
>>
>> s/owner class/owner mask?
>>
>> > +    * other mask, we may need to propagate permissions up from the
>> > +    * everyone@ allow ace.  The third condition is implied by the first
>> > +    * two.
>> > +    */
>> > +   if (!((acl->a_owner_mask & ~acl->a_group_mask) ||
>> > +         (acl->a_group_mask & ~acl->a_other_mask)))
>> > +           return 0;
>>
>> The code looks right, but I don't understand the preceding comment.
>>
>> For example,
>>
>>       owner mask: rw
>>       group mask:  wx
>>       other mask: rw
>>
>> satisfies the first two conditions, but not the third.
>>
>> Also, I don't understand why the first condition would imply that we
>> might need to propagate permissions.
>
> OK, maybe I get the part about the owner mask containing permissions
> not in the group mask: we'll need to insert a deny ace for the bits in
> the other mask but not in the group mask, and then we'll need an allow
> ace for the owner to get those bits back.  I think?

That is indeed the reason, and it also seems clear that this wasn't
documented well enough. Let me remove the offending comment and tiny
optimization, and add better comments instead.

>> > +                   if (richace_is_allow(ace) || richace_is_deny(ace)) {
>
> The v4 spec allows aces other than allow and deny aces (audit and
> alarm), but I didn't think you were implementing those.

Right, I don't see that happening. I'll remove that as well.

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 22/41] richacl: Propagate everyone@ permissions to other aces
  2015-09-21 19:24             ` J. Bruce Fields
@ 2015-09-23  1:39                 ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-23  1:39 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

Here are my improvements; hope that helps ...

Thanks,
Andreas

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 9b76fc0..21af9a0 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -351,26 +351,26 @@ richacl_propagate_everyone(struct richacl_alloc *alloc)
 	struct richace *ace;
 	unsigned int owner_allow, group_allow;
 
-	/*
-	 * If the owner mask contains permissions which are not in the group
-	 * mask, the group mask contains permissions which are not in the other
-	 * mask, or the owner class contains permissions which are not in the
-	 * other mask, we may need to propagate permissions up from the
-	 * everyone@ allow ace.  The third condition is implied by the first
-	 * two.
-	 */
-	if (!((acl->a_owner_mask & ~acl->a_group_mask) ||
-	      (acl->a_group_mask & ~acl->a_other_mask)))
-		return 0;
 	if (!acl->a_count)
 		return 0;
 	ace = acl->a_entries + acl->a_count - 1;
 	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
 		return 0;
 
+	/*
+	 * Permissions the owner and group class are granted through the
+	 * trailing everyone@ allow ace.
+	 */
 	owner_allow = ace->e_mask & acl->a_owner_mask;
 	group_allow = ace->e_mask & acl->a_group_mask;
 
+	/*
+	 * If the group or other masks hide permissions which the owner should
+	 * be allowed, we need to propagate those permissions up.  Otherwise,
+	 * those permissions may be lost when applying the other mask to the
+	 * trailing everyone@ allow ace, or when isolating the group class from
+	 * the other class through additional deny aces.
+	 */
 	if (owner_allow & ~(acl->a_group_mask & acl->a_other_mask)) {
 		/* Propagate everyone@ permissions through to owner@. */
 		who.e_id.special = RICHACE_OWNER_SPECIAL_ID;
@@ -379,6 +379,11 @@ richacl_propagate_everyone(struct richacl_alloc *alloc)
 		acl = alloc->acl;
 	}
 
+	/*
+	 * If the other mask hides permissions which the group class should be
+	 * allowed, we need to propagate those permissions up to the owning
+	 * group and to all other members in the group class.
+	 */
 	if (group_allow & ~acl->a_other_mask) {
 		int n;
 
@@ -399,16 +404,15 @@ richacl_propagate_everyone(struct richacl_alloc *alloc)
 			    richace_is_owner(ace) ||
 			    richace_is_group(ace))
 				continue;
-			if (richace_is_allow(ace) || richace_is_deny(ace)) {
-				/*
-				 * Any inserted entry will end up below the
-				 * current entry
-				 */
-				if (__richacl_propagate_everyone(alloc, ace,
-								 group_allow))
-					return -1;
-				acl = alloc->acl;
-			}
+
+			/*
+			 * Any inserted entry will end up below the current
+			 * entry.
+			 */
+			if (__richacl_propagate_everyone(alloc, ace,
+							 group_allow))
+				return -1;
+			acl = alloc->acl;
 		}
 	}
 	return 0;
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* Re: [RFC v7 22/41] richacl: Propagate everyone@ permissions to other aces
@ 2015-09-23  1:39                 ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-23  1:39 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

Here are my improvements; hope that helps ...

Thanks,
Andreas

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 9b76fc0..21af9a0 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -351,26 +351,26 @@ richacl_propagate_everyone(struct richacl_alloc *alloc)
 	struct richace *ace;
 	unsigned int owner_allow, group_allow;
 
-	/*
-	 * If the owner mask contains permissions which are not in the group
-	 * mask, the group mask contains permissions which are not in the other
-	 * mask, or the owner class contains permissions which are not in the
-	 * other mask, we may need to propagate permissions up from the
-	 * everyone@ allow ace.  The third condition is implied by the first
-	 * two.
-	 */
-	if (!((acl->a_owner_mask & ~acl->a_group_mask) ||
-	      (acl->a_group_mask & ~acl->a_other_mask)))
-		return 0;
 	if (!acl->a_count)
 		return 0;
 	ace = acl->a_entries + acl->a_count - 1;
 	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
 		return 0;
 
+	/*
+	 * Permissions the owner and group class are granted through the
+	 * trailing everyone@ allow ace.
+	 */
 	owner_allow = ace->e_mask & acl->a_owner_mask;
 	group_allow = ace->e_mask & acl->a_group_mask;
 
+	/*
+	 * If the group or other masks hide permissions which the owner should
+	 * be allowed, we need to propagate those permissions up.  Otherwise,
+	 * those permissions may be lost when applying the other mask to the
+	 * trailing everyone@ allow ace, or when isolating the group class from
+	 * the other class through additional deny aces.
+	 */
 	if (owner_allow & ~(acl->a_group_mask & acl->a_other_mask)) {
 		/* Propagate everyone@ permissions through to owner@. */
 		who.e_id.special = RICHACE_OWNER_SPECIAL_ID;
@@ -379,6 +379,11 @@ richacl_propagate_everyone(struct richacl_alloc *alloc)
 		acl = alloc->acl;
 	}
 
+	/*
+	 * If the other mask hides permissions which the group class should be
+	 * allowed, we need to propagate those permissions up to the owning
+	 * group and to all other members in the group class.
+	 */
 	if (group_allow & ~acl->a_other_mask) {
 		int n;
 
@@ -399,16 +404,15 @@ richacl_propagate_everyone(struct richacl_alloc *alloc)
 			    richace_is_owner(ace) ||
 			    richace_is_group(ace))
 				continue;
-			if (richace_is_allow(ace) || richace_is_deny(ace)) {
-				/*
-				 * Any inserted entry will end up below the
-				 * current entry
-				 */
-				if (__richacl_propagate_everyone(alloc, ace,
-								 group_allow))
-					return -1;
-				acl = alloc->acl;
-			}
+
+			/*
+			 * Any inserted entry will end up below the current
+			 * entry.
+			 */
+			if (__richacl_propagate_everyone(alloc, ace,
+							 group_allow))
+				return -1;
+			acl = alloc->acl;
 		}
 	}
 	return 0;
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* Re: [RFC v7 22/41] richacl: Propagate everyone@ permissions to other aces
  2015-09-23  1:39                 ` Andreas Gruenbacher
@ 2015-09-23  1:46                     ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-23  1:46 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

On Wed, Sep 23, 2015 at 03:39:44AM +0200, Andreas Gruenbacher wrote:
> Here are my improvements; hope that helps ...

Yes, looks good, thanks!--b.

> 
> Thanks,
> Andreas
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 9b76fc0..21af9a0 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -351,26 +351,26 @@ richacl_propagate_everyone(struct richacl_alloc *alloc)
>  	struct richace *ace;
>  	unsigned int owner_allow, group_allow;
>  
> -	/*
> -	 * If the owner mask contains permissions which are not in the group
> -	 * mask, the group mask contains permissions which are not in the other
> -	 * mask, or the owner class contains permissions which are not in the
> -	 * other mask, we may need to propagate permissions up from the
> -	 * everyone@ allow ace.  The third condition is implied by the first
> -	 * two.
> -	 */
> -	if (!((acl->a_owner_mask & ~acl->a_group_mask) ||
> -	      (acl->a_group_mask & ~acl->a_other_mask)))
> -		return 0;
>  	if (!acl->a_count)
>  		return 0;
>  	ace = acl->a_entries + acl->a_count - 1;
>  	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
>  		return 0;
>  
> +	/*
> +	 * Permissions the owner and group class are granted through the
> +	 * trailing everyone@ allow ace.
> +	 */
>  	owner_allow = ace->e_mask & acl->a_owner_mask;
>  	group_allow = ace->e_mask & acl->a_group_mask;
>  
> +	/*
> +	 * If the group or other masks hide permissions which the owner should
> +	 * be allowed, we need to propagate those permissions up.  Otherwise,
> +	 * those permissions may be lost when applying the other mask to the
> +	 * trailing everyone@ allow ace, or when isolating the group class from
> +	 * the other class through additional deny aces.
> +	 */
>  	if (owner_allow & ~(acl->a_group_mask & acl->a_other_mask)) {
>  		/* Propagate everyone@ permissions through to owner@. */
>  		who.e_id.special = RICHACE_OWNER_SPECIAL_ID;
> @@ -379,6 +379,11 @@ richacl_propagate_everyone(struct richacl_alloc *alloc)
>  		acl = alloc->acl;
>  	}
>  
> +	/*
> +	 * If the other mask hides permissions which the group class should be
> +	 * allowed, we need to propagate those permissions up to the owning
> +	 * group and to all other members in the group class.
> +	 */
>  	if (group_allow & ~acl->a_other_mask) {
>  		int n;
>  
> @@ -399,16 +404,15 @@ richacl_propagate_everyone(struct richacl_alloc *alloc)
>  			    richace_is_owner(ace) ||
>  			    richace_is_group(ace))
>  				continue;
> -			if (richace_is_allow(ace) || richace_is_deny(ace)) {
> -				/*
> -				 * Any inserted entry will end up below the
> -				 * current entry
> -				 */
> -				if (__richacl_propagate_everyone(alloc, ace,
> -								 group_allow))
> -					return -1;
> -				acl = alloc->acl;
> -			}
> +
> +			/*
> +			 * Any inserted entry will end up below the current
> +			 * entry.
> +			 */
> +			if (__richacl_propagate_everyone(alloc, ace,
> +							 group_allow))
> +				return -1;
> +			acl = alloc->acl;
>  		}
>  	}
>  	return 0;
> -- 
> 2.4.3
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 22/41] richacl: Propagate everyone@ permissions to other aces
@ 2015-09-23  1:46                     ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-23  1:46 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Wed, Sep 23, 2015 at 03:39:44AM +0200, Andreas Gruenbacher wrote:
> Here are my improvements; hope that helps ...

Yes, looks good, thanks!--b.

> 
> Thanks,
> Andreas
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 9b76fc0..21af9a0 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -351,26 +351,26 @@ richacl_propagate_everyone(struct richacl_alloc *alloc)
>  	struct richace *ace;
>  	unsigned int owner_allow, group_allow;
>  
> -	/*
> -	 * If the owner mask contains permissions which are not in the group
> -	 * mask, the group mask contains permissions which are not in the other
> -	 * mask, or the owner class contains permissions which are not in the
> -	 * other mask, we may need to propagate permissions up from the
> -	 * everyone@ allow ace.  The third condition is implied by the first
> -	 * two.
> -	 */
> -	if (!((acl->a_owner_mask & ~acl->a_group_mask) ||
> -	      (acl->a_group_mask & ~acl->a_other_mask)))
> -		return 0;
>  	if (!acl->a_count)
>  		return 0;
>  	ace = acl->a_entries + acl->a_count - 1;
>  	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
>  		return 0;
>  
> +	/*
> +	 * Permissions the owner and group class are granted through the
> +	 * trailing everyone@ allow ace.
> +	 */
>  	owner_allow = ace->e_mask & acl->a_owner_mask;
>  	group_allow = ace->e_mask & acl->a_group_mask;
>  
> +	/*
> +	 * If the group or other masks hide permissions which the owner should
> +	 * be allowed, we need to propagate those permissions up.  Otherwise,
> +	 * those permissions may be lost when applying the other mask to the
> +	 * trailing everyone@ allow ace, or when isolating the group class from
> +	 * the other class through additional deny aces.
> +	 */
>  	if (owner_allow & ~(acl->a_group_mask & acl->a_other_mask)) {
>  		/* Propagate everyone@ permissions through to owner@. */
>  		who.e_id.special = RICHACE_OWNER_SPECIAL_ID;
> @@ -379,6 +379,11 @@ richacl_propagate_everyone(struct richacl_alloc *alloc)
>  		acl = alloc->acl;
>  	}
>  
> +	/*
> +	 * If the other mask hides permissions which the group class should be
> +	 * allowed, we need to propagate those permissions up to the owning
> +	 * group and to all other members in the group class.
> +	 */
>  	if (group_allow & ~acl->a_other_mask) {
>  		int n;
>  
> @@ -399,16 +404,15 @@ richacl_propagate_everyone(struct richacl_alloc *alloc)
>  			    richace_is_owner(ace) ||
>  			    richace_is_group(ace))
>  				continue;
> -			if (richace_is_allow(ace) || richace_is_deny(ace)) {
> -				/*
> -				 * Any inserted entry will end up below the
> -				 * current entry
> -				 */
> -				if (__richacl_propagate_everyone(alloc, ace,
> -								 group_allow))
> -					return -1;
> -				acl = alloc->acl;
> -			}
> +
> +			/*
> +			 * Any inserted entry will end up below the current
> +			 * entry.
> +			 */
> +			if (__richacl_propagate_everyone(alloc, ace,
> +							 group_allow))
> +				return -1;
> +			acl = alloc->acl;
>  		}
>  	}
>  	return 0;
> -- 
> 2.4.3

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 18/41] ext4: Add richacl support
  2015-09-05 10:27 ` [RFC v7 18/41] ext4: Add richacl support Andreas Gruenbacher
@ 2015-09-23  2:30       ` Aneesh Kumar K.V
  0 siblings, 0 replies; 188+ messages in thread
From: Aneesh Kumar K.V @ 2015-09-23  2:30 UTC (permalink / raw)
  To: Andreas Gruenbacher, linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org> writes:

> From: "Aneesh Kumar K.V" <aneesh.kumar-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8@public.gmane.org>
>
> Support the richacl permission model in ext4.  The richacls are stored
> in "system.richacl" xattrs.  Richacls need to be enabled by tune2fs or
> at file system create time.
>

 Signed-off-by:  Aneesh Kumar K.V <aneesh.kumar-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8@public.gmane.org>

> Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> ---
>  fs/ext4/Kconfig   |  15 ++++
>  fs/ext4/Makefile  |   1 +
>  fs/ext4/acl.c     |   6 +-
>  fs/ext4/acl.h     |  12 +--
>  fs/ext4/file.c    |   6 +-
>  fs/ext4/ialloc.c  |   7 +-
>  fs/ext4/inode.c   |  10 ++-
>  fs/ext4/namei.c   |  11 ++-
>  fs/ext4/richacl.c | 213 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  fs/ext4/richacl.h |  47 ++++++++++++
>  fs/ext4/xattr.c   |   6 ++
>  fs/ext4/xattr.h   |   1 +
>  12 files changed, 316 insertions(+), 19 deletions(-)
>  create mode 100644 fs/ext4/richacl.c
>  create mode 100644 fs/ext4/richacl.h
>
> diff --git a/fs/ext4/Kconfig b/fs/ext4/Kconfig
> index bf8bc8a..77a386d 100644
> --- a/fs/ext4/Kconfig
> +++ b/fs/ext4/Kconfig
> @@ -96,3 +96,18 @@ config EXT4_DEBUG
>  	  If you select Y here, then you will be able to turn on debugging
>  	  with a command such as:
>  		echo 1 > /sys/module/ext4/parameters/mballoc_debug
> +
> +config EXT4_FS_RICHACL
> +	bool "Ext4 Rich Access Control Lists (EXPERIMENTAL)"
> +	depends on EXT4_FS
> +	select FS_RICHACL
> +	help
> +	  Rich ACLs are an implementation of NFSv4 ACLs, extended by file masks
> +	  to fit into the standard POSIX file permission model.  They are
> +	  designed to work seamlessly locally as well as across the NFSv4 and
> +	  CIFS/SMB2 network file system protocols.
> +
> +	  To learn more about Rich ACL, visit
> +	  http://acl.bestbits.at/richacl/
> +
> +	  If you don't know what Rich ACLs are, say N
> diff --git a/fs/ext4/Makefile b/fs/ext4/Makefile
> index 75285ea..ea0d539 100644
> --- a/fs/ext4/Makefile
> +++ b/fs/ext4/Makefile
> @@ -14,3 +14,4 @@ ext4-$(CONFIG_EXT4_FS_POSIX_ACL)	+= acl.o
>  ext4-$(CONFIG_EXT4_FS_SECURITY)		+= xattr_security.o
>  ext4-$(CONFIG_EXT4_FS_ENCRYPTION)	+= crypto_policy.o crypto.o \
>  		crypto_key.o crypto_fname.o
> +ext4-$(CONFIG_EXT4_FS_RICHACL) 		+= richacl.o
> diff --git a/fs/ext4/acl.c b/fs/ext4/acl.c
> index 69b1e73..d965fa6 100644
> --- a/fs/ext4/acl.c
> +++ b/fs/ext4/acl.c
> @@ -140,7 +140,7 @@ fail:
>   * inode->i_mutex: don't care
>   */
>  struct posix_acl *
> -ext4_get_acl(struct inode *inode, int type)
> +ext4_get_posix_acl(struct inode *inode, int type)
>  {
>  	int name_index;
>  	char *value = NULL;
> @@ -234,7 +234,7 @@ __ext4_set_acl(handle_t *handle, struct inode *inode, int type,
>  }
>
>  int
> -ext4_set_acl(struct inode *inode, struct posix_acl *acl, int type)
> +ext4_set_posix_acl(struct inode *inode, struct posix_acl *acl, int type)
>  {
>  	handle_t *handle;
>  	int error, retries = 0;
> @@ -259,7 +259,7 @@ retry:
>   * inode->i_mutex: up (access to inode is still exclusive)
>   */
>  int
> -ext4_init_acl(handle_t *handle, struct inode *inode, struct inode *dir)
> +ext4_init_posix_acl(handle_t *handle, struct inode *inode, struct inode *dir)
>  {
>  	struct posix_acl *default_acl, *acl;
>  	int error;
> diff --git a/fs/ext4/acl.h b/fs/ext4/acl.h
> index da2c795..450b4d1 100644
> --- a/fs/ext4/acl.h
> +++ b/fs/ext4/acl.h
> @@ -54,17 +54,17 @@ static inline int ext4_acl_count(size_t size)
>  #ifdef CONFIG_EXT4_FS_POSIX_ACL
>
>  /* acl.c */
> -struct posix_acl *ext4_get_acl(struct inode *inode, int type);
> -int ext4_set_acl(struct inode *inode, struct posix_acl *acl, int type);
> -extern int ext4_init_acl(handle_t *, struct inode *, struct inode *);
> +struct posix_acl *ext4_get_posix_acl(struct inode *inode, int type);
> +int ext4_set_posix_acl(struct inode *inode, struct posix_acl *acl, int type);
> +extern int ext4_init_posix_acl(handle_t *, struct inode *, struct inode *);
>
>  #else  /* CONFIG_EXT4_FS_POSIX_ACL */
>  #include <linux/sched.h>
> -#define ext4_get_acl NULL
> -#define ext4_set_acl NULL
> +#define ext4_get_posix_acl NULL
> +#define ext4_set_posix_acl NULL
>
>  static inline int
> -ext4_init_acl(handle_t *handle, struct inode *inode, struct inode *dir)
> +ext4_init_posix_acl(handle_t *handle, struct inode *inode, struct inode *dir)
>  {
>  	return 0;
>  }
> diff --git a/fs/ext4/file.c b/fs/ext4/file.c
> index bc313ac..3d3fcc8 100644
> --- a/fs/ext4/file.c
> +++ b/fs/ext4/file.c
> @@ -29,6 +29,7 @@
>  #include "ext4_jbd2.h"
>  #include "xattr.h"
>  #include "acl.h"
> +#include "richacl.h"
>
>  /*
>   * Called when an inode is released. Note that this is different
> @@ -659,8 +660,9 @@ const struct inode_operations ext4_file_inode_operations = {
>  	.getxattr	= generic_getxattr,
>  	.listxattr	= ext4_listxattr,
>  	.removexattr	= generic_removexattr,
> -	.get_acl	= ext4_get_acl,
> -	.set_acl	= ext4_set_acl,
> +	.get_acl	= ext4_get_posix_acl,
> +	.set_acl	= ext4_set_posix_acl,
> +	.get_richacl	= ext4_get_richacl,
>  	.fiemap		= ext4_fiemap,
>  };
>
> diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c
> index 173c1ae..aa5d1e1 100644
> --- a/fs/ext4/ialloc.c
> +++ b/fs/ext4/ialloc.c
> @@ -27,6 +27,7 @@
>  #include "ext4_jbd2.h"
>  #include "xattr.h"
>  #include "acl.h"
> +#include "richacl.h"
>
>  #include <trace/events/ext4.h>
>
> @@ -1050,7 +1051,11 @@ got:
>  	if (err)
>  		goto fail_drop;
>
> -	err = ext4_init_acl(handle, inode, dir);
> +	if (EXT4_IS_RICHACL(dir))
> +		err = ext4_init_richacl(handle, inode, dir);
> +	else
> +		err = ext4_init_posix_acl(handle, inode, dir);
> +
>  	if (err)
>  		goto fail_free_drop;
>
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index cecf9aa..7f6bbe8 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -41,6 +41,7 @@
>  #include "xattr.h"
>  #include "acl.h"
>  #include "truncate.h"
> +#include "richacl.h"
>
>  #include <trace/events/ext4.h>
>
> @@ -4782,9 +4783,12 @@ int ext4_setattr(struct dentry *dentry, struct iattr *attr)
>  	if (orphan && inode->i_nlink)
>  		ext4_orphan_del(NULL, inode);
>
> -	if (!rc && (ia_valid & ATTR_MODE))
> -		rc = posix_acl_chmod(inode, inode->i_mode);
> -
> +	if (!rc && (ia_valid & ATTR_MODE)) {
> +		if (EXT4_IS_RICHACL(inode))
> +			rc = ext4_richacl_chmod(inode);
> +		else
> +			rc = posix_acl_chmod(inode, inode->i_mode);
> +	}
>  err_out:
>  	ext4_std_error(inode->i_sb, error);
>  	if (!error)
> diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
> index 011dcfb..9be6a8a 100644
> --- a/fs/ext4/namei.c
> +++ b/fs/ext4/namei.c
> @@ -38,6 +38,7 @@
>
>  #include "xattr.h"
>  #include "acl.h"
> +#include "richacl.h"
>
>  #include <trace/events/ext4.h>
>  /*
> @@ -3821,8 +3822,9 @@ const struct inode_operations ext4_dir_inode_operations = {
>  	.getxattr	= generic_getxattr,
>  	.listxattr	= ext4_listxattr,
>  	.removexattr	= generic_removexattr,
> -	.get_acl	= ext4_get_acl,
> -	.set_acl	= ext4_set_acl,
> +	.get_acl	= ext4_get_posix_acl,
> +	.set_acl	= ext4_set_posix_acl,
> +	.get_richacl	= ext4_get_richacl,
>  	.fiemap         = ext4_fiemap,
>  };
>
> @@ -3832,6 +3834,7 @@ const struct inode_operations ext4_special_inode_operations = {
>  	.getxattr	= generic_getxattr,
>  	.listxattr	= ext4_listxattr,
>  	.removexattr	= generic_removexattr,
> -	.get_acl	= ext4_get_acl,
> -	.set_acl	= ext4_set_acl,
> +	.get_acl	= ext4_get_posix_acl,
> +	.set_acl	= ext4_set_posix_acl,
> +	.get_richacl	= ext4_get_richacl,
>  };
> diff --git a/fs/ext4/richacl.c b/fs/ext4/richacl.c
> new file mode 100644
> index 0000000..6758def
> --- /dev/null
> +++ b/fs/ext4/richacl.c
> @@ -0,0 +1,213 @@
> +/*
> + * Copyright IBM Corporation, 2010
> + * Copyright (C) 2015  Red Hat, Inc.
> + * Author Aneesh Kumar K.V <aneesh.kumar-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8@public.gmane.org>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of version 2.1 of the GNU Lesser General Public License
> + * as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it would be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> + *
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/fs.h>
> +#include <linux/richacl_xattr.h>
> +
> +#include "ext4.h"
> +#include "ext4_jbd2.h"
> +#include "xattr.h"
> +#include "acl.h"
> +#include "richacl.h"
> +
> +struct richacl *
> +ext4_get_richacl(struct inode *inode)
> +{
> +	const int name_index = EXT4_XATTR_INDEX_RICHACL;
> +	void *value = NULL;
> +	struct richacl *acl;
> +	int retval;
> +
> +	if (!IS_RICHACL(inode))
> +		return ERR_PTR(-EOPNOTSUPP);
> +	acl = get_cached_richacl(inode);
> +	if (acl != ACL_NOT_CACHED)
> +		return acl;
> +	retval = ext4_xattr_get(inode, name_index, "", NULL, 0);
> +	if (retval > 0) {
> +		value = kmalloc(retval, GFP_NOFS);
> +		if (!value)
> +			return ERR_PTR(-ENOMEM);
> +		retval = ext4_xattr_get(inode, name_index, "", value, retval);
> +	}
> +	if (retval > 0) {
> +		acl = richacl_from_xattr(&init_user_ns, value, retval);
> +		if (acl == ERR_PTR(-EINVAL))
> +			acl = ERR_PTR(-EIO);
> +	} else if (retval == -ENODATA || retval == -ENOSYS)
> +		acl = NULL;
> +	else
> +		acl = ERR_PTR(retval);
> +	kfree(value);
> +
> +	if (!IS_ERR(acl))
> +		set_cached_richacl(inode, acl);
> +
> +	return acl;
> +}
> +
> +static int
> +ext4_set_richacl(handle_t *handle, struct inode *inode, struct richacl *acl)
> +{
> +	const int name_index = EXT4_XATTR_INDEX_RICHACL;
> +	size_t size = 0;
> +	void *value = NULL;
> +	int retval;
> +
> +	if (acl) {
> +		mode_t mode = inode->i_mode;
> +
> +		if (richacl_equiv_mode(acl, &mode) == 0) {
> +			inode->i_mode = mode;
> +			ext4_mark_inode_dirty(handle, inode);
> +			acl = NULL;
> +		}
> +	}
> +	if (acl) {
> +		size = richacl_xattr_size(acl);
> +		value = kmalloc(size, GFP_NOFS);
> +		if (!value)
> +			return -ENOMEM;
> +		richacl_to_xattr(&init_user_ns, acl, value, size);
> +	}
> +	if (handle)
> +		retval = ext4_xattr_set_handle(handle, inode, name_index, "",
> +					       value, size, 0);
> +	else
> +		retval = ext4_xattr_set(inode, name_index, "", value, size, 0);
> +	kfree(value);
> +	if (!retval)
> +		set_cached_richacl(inode, acl);
> +
> +	return retval;
> +}
> +
> +int
> +ext4_init_richacl(handle_t *handle, struct inode *inode, struct inode *dir)
> +{
> +	struct richacl *acl = richacl_create(inode, dir);
> +	int error;
> +
> +	error = PTR_ERR(acl);
> +	if (IS_ERR(acl))
> +		return error;
> +	if (acl) {
> +		error = ext4_set_richacl(handle, inode, acl);
> +		richacl_put(acl);
> +	}
> +	return error;
> +}
> +
> +int
> +ext4_richacl_chmod(struct inode *inode)
> +{
> +	struct richacl *acl;
> +	int retval;
> +
> +	if (S_ISLNK(inode->i_mode))
> +		return -EOPNOTSUPP;
> +	acl = ext4_get_richacl(inode);
> +	if (IS_ERR_OR_NULL(acl))
> +		return PTR_ERR(acl);
> +	acl = richacl_chmod(acl, inode->i_mode);
> +	if (IS_ERR(acl))
> +		return PTR_ERR(acl);
> +	retval = ext4_set_richacl(NULL, inode, acl);
> +	richacl_put(acl);
> +
> +	return retval;
> +}
> +
> +static size_t
> +ext4_xattr_list_richacl(struct dentry *dentry, char *list, size_t list_len,
> +			const char *name, size_t name_len, int type)
> +{
> +	const size_t size = sizeof(XATTR_NAME_RICHACL);
> +
> +	if (!IS_RICHACL(d_inode(dentry)))
> +		return 0;
> +	if (list && size <= list_len)
> +		memcpy(list, XATTR_NAME_RICHACL, size);
> +	return size;
> +}
> +
> +static int
> +ext4_xattr_get_richacl(struct dentry *dentry, const char *name, void *buffer,
> +		size_t buffer_size, int type)
> +{
> +	struct richacl *acl;
> +	int error;
> +
> +	if (strcmp(name, "") != 0)
> +		return -EINVAL;
> +	acl = ext4_get_richacl(d_inode(dentry));
> +	if (IS_ERR(acl))
> +		return PTR_ERR(acl);
> +	if (acl == NULL)
> +		return -ENODATA;
> +
> +	error = richacl_to_xattr(&init_user_ns, acl, buffer, buffer_size);
> +	richacl_put(acl);
> +	return error;
> +}
> +
> +static int
> +ext4_xattr_set_richacl(struct dentry *dentry, const char *name,
> +		const void *value, size_t size, int flags, int type)
> +{
> +	handle_t *handle;
> +	struct richacl *acl = NULL;
> +	int retval, retries = 0;
> +	struct inode *inode = d_inode(dentry);
> +
> +	if (!IS_RICHACL(d_inode(dentry)))
> +		return -EOPNOTSUPP;
> +	if (S_ISLNK(inode->i_mode))
> +		return -EOPNOTSUPP;
> +	if (strcmp(name, "") != 0)
> +		return -EINVAL;
> +	if (!uid_eq(current_fsuid(), inode->i_uid) &&
> +	    inode_permission(inode, MAY_CHMOD) &&
> +	    !capable(CAP_FOWNER))
> +		return -EPERM;
> +	if (value) {
> +		acl = richacl_from_xattr(&init_user_ns, value, size);
> +		if (IS_ERR(acl))
> +			return PTR_ERR(acl);
> +
> +		inode->i_mode &= ~S_IRWXUGO;
> +		inode->i_mode |= richacl_masks_to_mode(acl);
> +	}
> +
> +retry:
> +	handle = ext4_journal_start(inode, EXT4_HT_XATTR,
> +				    EXT4_DATA_TRANS_BLOCKS(inode->i_sb));
> +	if (IS_ERR(handle))
> +		return PTR_ERR(handle);
> +	retval = ext4_set_richacl(handle, inode, acl);
> +	ext4_journal_stop(handle);
> +	if (retval == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries))
> +		goto retry;
> +	richacl_put(acl);
> +	return retval;
> +}
> +
> +const struct xattr_handler ext4_richacl_xattr_handler = {
> +	.prefix	= XATTR_NAME_RICHACL,
> +	.list	= ext4_xattr_list_richacl,
> +	.get	= ext4_xattr_get_richacl,
> +	.set	= ext4_xattr_set_richacl,
> +};
> diff --git a/fs/ext4/richacl.h b/fs/ext4/richacl.h
> new file mode 100644
> index 0000000..09a5cad
> --- /dev/null
> +++ b/fs/ext4/richacl.h
> @@ -0,0 +1,47 @@
> +/*
> + * Copyright IBM Corporation, 2010
> + * Copyright (C)  2015 Red Hat, Inc.
> + * Author Aneesh Kumar K.V <aneesh.kumar-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8@public.gmane.org>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of version 2.1 of the GNU Lesser General Public License
> + * as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it would be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> + *
> + */
> +
> +#ifndef __FS_EXT4_RICHACL_H
> +#define __FS_EXT4_RICHACL_H
> +
> +#include <linux/richacl.h>
> +
> +#ifdef CONFIG_EXT4_FS_RICHACL
> +
> +#define EXT4_IS_RICHACL(inode) IS_RICHACL(inode)
> +
> +extern struct richacl *ext4_get_richacl(struct inode *);
> +extern int ext4_init_richacl(handle_t *, struct inode *, struct inode *);
> +extern int ext4_richacl_chmod(struct inode *);
> +
> +#else  /* CONFIG_FS_EXT4_RICHACL */
> +
> +#define EXT4_IS_RICHACL(inode) (0)
> +#define ext4_get_richacl   NULL
> +
> +static inline int
> +ext4_init_richacl(handle_t *handle, struct inode *inode, struct inode *dir)
> +{
> +	return 0;
> +}
> +
> +static inline int
> +ext4_richacl_chmod(struct inode *inode)
> +{
> +	return 0;
> +}
> +
> +#endif  /* CONFIG_FS_EXT4_RICHACL */
> +#endif  /* __FS_EXT4_RICHACL_H */
> diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c
> index 16e28c0..c1dee9b 100644
> --- a/fs/ext4/xattr.c
> +++ b/fs/ext4/xattr.c
> @@ -99,6 +99,9 @@ static const struct xattr_handler *ext4_xattr_handler_map[] = {
>  #ifdef CONFIG_EXT4_FS_SECURITY
>  	[EXT4_XATTR_INDEX_SECURITY]	     = &ext4_xattr_security_handler,
>  #endif
> +#ifdef CONFIG_EXT4_FS_RICHACL
> +	[EXT4_XATTR_INDEX_RICHACL]           = &ext4_richacl_xattr_handler,
> +#endif
>  };
>
>  const struct xattr_handler *ext4_xattr_handlers[] = {
> @@ -111,6 +114,9 @@ const struct xattr_handler *ext4_xattr_handlers[] = {
>  #ifdef CONFIG_EXT4_FS_SECURITY
>  	&ext4_xattr_security_handler,
>  #endif
> +#ifdef CONFIG_EXT4_FS_RICHACL
> +	&ext4_richacl_xattr_handler,
> +#endif
>  	NULL
>  };
>
> diff --git a/fs/ext4/xattr.h b/fs/ext4/xattr.h
> index ddc0957..f315493 100644
> --- a/fs/ext4/xattr.h
> +++ b/fs/ext4/xattr.h
> @@ -98,6 +98,7 @@ struct ext4_xattr_ibody_find {
>  extern const struct xattr_handler ext4_xattr_user_handler;
>  extern const struct xattr_handler ext4_xattr_trusted_handler;
>  extern const struct xattr_handler ext4_xattr_security_handler;
> +extern const struct xattr_handler ext4_richacl_xattr_handler;
>
>  #define EXT4_XATTR_NAME_ENCRYPTION_CONTEXT "c"
>
> -- 
> 2.4.3

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 18/41] ext4: Add richacl support
@ 2015-09-23  2:30       ` Aneesh Kumar K.V
  0 siblings, 0 replies; 188+ messages in thread
From: Aneesh Kumar K.V @ 2015-09-23  2:30 UTC (permalink / raw)
  To: Andreas Gruenbacher, linux-kernel, linux-fsdevel, linux-nfs,
	linux-api, linux-cifs, linux-security-module

Andreas Gruenbacher <agruenba@redhat.com> writes:

> From: "Aneesh Kumar K.V" <aneesh.kumar@linux.vnet.ibm.com>
>
> Support the richacl permission model in ext4.  The richacls are stored
> in "system.richacl" xattrs.  Richacls need to be enabled by tune2fs or
> at file system create time.
>

 Signed-off-by:  Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>

> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> ---
>  fs/ext4/Kconfig   |  15 ++++
>  fs/ext4/Makefile  |   1 +
>  fs/ext4/acl.c     |   6 +-
>  fs/ext4/acl.h     |  12 +--
>  fs/ext4/file.c    |   6 +-
>  fs/ext4/ialloc.c  |   7 +-
>  fs/ext4/inode.c   |  10 ++-
>  fs/ext4/namei.c   |  11 ++-
>  fs/ext4/richacl.c | 213 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  fs/ext4/richacl.h |  47 ++++++++++++
>  fs/ext4/xattr.c   |   6 ++
>  fs/ext4/xattr.h   |   1 +
>  12 files changed, 316 insertions(+), 19 deletions(-)
>  create mode 100644 fs/ext4/richacl.c
>  create mode 100644 fs/ext4/richacl.h
>
> diff --git a/fs/ext4/Kconfig b/fs/ext4/Kconfig
> index bf8bc8a..77a386d 100644
> --- a/fs/ext4/Kconfig
> +++ b/fs/ext4/Kconfig
> @@ -96,3 +96,18 @@ config EXT4_DEBUG
>  	  If you select Y here, then you will be able to turn on debugging
>  	  with a command such as:
>  		echo 1 > /sys/module/ext4/parameters/mballoc_debug
> +
> +config EXT4_FS_RICHACL
> +	bool "Ext4 Rich Access Control Lists (EXPERIMENTAL)"
> +	depends on EXT4_FS
> +	select FS_RICHACL
> +	help
> +	  Rich ACLs are an implementation of NFSv4 ACLs, extended by file masks
> +	  to fit into the standard POSIX file permission model.  They are
> +	  designed to work seamlessly locally as well as across the NFSv4 and
> +	  CIFS/SMB2 network file system protocols.
> +
> +	  To learn more about Rich ACL, visit
> +	  http://acl.bestbits.at/richacl/
> +
> +	  If you don't know what Rich ACLs are, say N
> diff --git a/fs/ext4/Makefile b/fs/ext4/Makefile
> index 75285ea..ea0d539 100644
> --- a/fs/ext4/Makefile
> +++ b/fs/ext4/Makefile
> @@ -14,3 +14,4 @@ ext4-$(CONFIG_EXT4_FS_POSIX_ACL)	+= acl.o
>  ext4-$(CONFIG_EXT4_FS_SECURITY)		+= xattr_security.o
>  ext4-$(CONFIG_EXT4_FS_ENCRYPTION)	+= crypto_policy.o crypto.o \
>  		crypto_key.o crypto_fname.o
> +ext4-$(CONFIG_EXT4_FS_RICHACL) 		+= richacl.o
> diff --git a/fs/ext4/acl.c b/fs/ext4/acl.c
> index 69b1e73..d965fa6 100644
> --- a/fs/ext4/acl.c
> +++ b/fs/ext4/acl.c
> @@ -140,7 +140,7 @@ fail:
>   * inode->i_mutex: don't care
>   */
>  struct posix_acl *
> -ext4_get_acl(struct inode *inode, int type)
> +ext4_get_posix_acl(struct inode *inode, int type)
>  {
>  	int name_index;
>  	char *value = NULL;
> @@ -234,7 +234,7 @@ __ext4_set_acl(handle_t *handle, struct inode *inode, int type,
>  }
>
>  int
> -ext4_set_acl(struct inode *inode, struct posix_acl *acl, int type)
> +ext4_set_posix_acl(struct inode *inode, struct posix_acl *acl, int type)
>  {
>  	handle_t *handle;
>  	int error, retries = 0;
> @@ -259,7 +259,7 @@ retry:
>   * inode->i_mutex: up (access to inode is still exclusive)
>   */
>  int
> -ext4_init_acl(handle_t *handle, struct inode *inode, struct inode *dir)
> +ext4_init_posix_acl(handle_t *handle, struct inode *inode, struct inode *dir)
>  {
>  	struct posix_acl *default_acl, *acl;
>  	int error;
> diff --git a/fs/ext4/acl.h b/fs/ext4/acl.h
> index da2c795..450b4d1 100644
> --- a/fs/ext4/acl.h
> +++ b/fs/ext4/acl.h
> @@ -54,17 +54,17 @@ static inline int ext4_acl_count(size_t size)
>  #ifdef CONFIG_EXT4_FS_POSIX_ACL
>
>  /* acl.c */
> -struct posix_acl *ext4_get_acl(struct inode *inode, int type);
> -int ext4_set_acl(struct inode *inode, struct posix_acl *acl, int type);
> -extern int ext4_init_acl(handle_t *, struct inode *, struct inode *);
> +struct posix_acl *ext4_get_posix_acl(struct inode *inode, int type);
> +int ext4_set_posix_acl(struct inode *inode, struct posix_acl *acl, int type);
> +extern int ext4_init_posix_acl(handle_t *, struct inode *, struct inode *);
>
>  #else  /* CONFIG_EXT4_FS_POSIX_ACL */
>  #include <linux/sched.h>
> -#define ext4_get_acl NULL
> -#define ext4_set_acl NULL
> +#define ext4_get_posix_acl NULL
> +#define ext4_set_posix_acl NULL
>
>  static inline int
> -ext4_init_acl(handle_t *handle, struct inode *inode, struct inode *dir)
> +ext4_init_posix_acl(handle_t *handle, struct inode *inode, struct inode *dir)
>  {
>  	return 0;
>  }
> diff --git a/fs/ext4/file.c b/fs/ext4/file.c
> index bc313ac..3d3fcc8 100644
> --- a/fs/ext4/file.c
> +++ b/fs/ext4/file.c
> @@ -29,6 +29,7 @@
>  #include "ext4_jbd2.h"
>  #include "xattr.h"
>  #include "acl.h"
> +#include "richacl.h"
>
>  /*
>   * Called when an inode is released. Note that this is different
> @@ -659,8 +660,9 @@ const struct inode_operations ext4_file_inode_operations = {
>  	.getxattr	= generic_getxattr,
>  	.listxattr	= ext4_listxattr,
>  	.removexattr	= generic_removexattr,
> -	.get_acl	= ext4_get_acl,
> -	.set_acl	= ext4_set_acl,
> +	.get_acl	= ext4_get_posix_acl,
> +	.set_acl	= ext4_set_posix_acl,
> +	.get_richacl	= ext4_get_richacl,
>  	.fiemap		= ext4_fiemap,
>  };
>
> diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c
> index 173c1ae..aa5d1e1 100644
> --- a/fs/ext4/ialloc.c
> +++ b/fs/ext4/ialloc.c
> @@ -27,6 +27,7 @@
>  #include "ext4_jbd2.h"
>  #include "xattr.h"
>  #include "acl.h"
> +#include "richacl.h"
>
>  #include <trace/events/ext4.h>
>
> @@ -1050,7 +1051,11 @@ got:
>  	if (err)
>  		goto fail_drop;
>
> -	err = ext4_init_acl(handle, inode, dir);
> +	if (EXT4_IS_RICHACL(dir))
> +		err = ext4_init_richacl(handle, inode, dir);
> +	else
> +		err = ext4_init_posix_acl(handle, inode, dir);
> +
>  	if (err)
>  		goto fail_free_drop;
>
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index cecf9aa..7f6bbe8 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -41,6 +41,7 @@
>  #include "xattr.h"
>  #include "acl.h"
>  #include "truncate.h"
> +#include "richacl.h"
>
>  #include <trace/events/ext4.h>
>
> @@ -4782,9 +4783,12 @@ int ext4_setattr(struct dentry *dentry, struct iattr *attr)
>  	if (orphan && inode->i_nlink)
>  		ext4_orphan_del(NULL, inode);
>
> -	if (!rc && (ia_valid & ATTR_MODE))
> -		rc = posix_acl_chmod(inode, inode->i_mode);
> -
> +	if (!rc && (ia_valid & ATTR_MODE)) {
> +		if (EXT4_IS_RICHACL(inode))
> +			rc = ext4_richacl_chmod(inode);
> +		else
> +			rc = posix_acl_chmod(inode, inode->i_mode);
> +	}
>  err_out:
>  	ext4_std_error(inode->i_sb, error);
>  	if (!error)
> diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
> index 011dcfb..9be6a8a 100644
> --- a/fs/ext4/namei.c
> +++ b/fs/ext4/namei.c
> @@ -38,6 +38,7 @@
>
>  #include "xattr.h"
>  #include "acl.h"
> +#include "richacl.h"
>
>  #include <trace/events/ext4.h>
>  /*
> @@ -3821,8 +3822,9 @@ const struct inode_operations ext4_dir_inode_operations = {
>  	.getxattr	= generic_getxattr,
>  	.listxattr	= ext4_listxattr,
>  	.removexattr	= generic_removexattr,
> -	.get_acl	= ext4_get_acl,
> -	.set_acl	= ext4_set_acl,
> +	.get_acl	= ext4_get_posix_acl,
> +	.set_acl	= ext4_set_posix_acl,
> +	.get_richacl	= ext4_get_richacl,
>  	.fiemap         = ext4_fiemap,
>  };
>
> @@ -3832,6 +3834,7 @@ const struct inode_operations ext4_special_inode_operations = {
>  	.getxattr	= generic_getxattr,
>  	.listxattr	= ext4_listxattr,
>  	.removexattr	= generic_removexattr,
> -	.get_acl	= ext4_get_acl,
> -	.set_acl	= ext4_set_acl,
> +	.get_acl	= ext4_get_posix_acl,
> +	.set_acl	= ext4_set_posix_acl,
> +	.get_richacl	= ext4_get_richacl,
>  };
> diff --git a/fs/ext4/richacl.c b/fs/ext4/richacl.c
> new file mode 100644
> index 0000000..6758def
> --- /dev/null
> +++ b/fs/ext4/richacl.c
> @@ -0,0 +1,213 @@
> +/*
> + * Copyright IBM Corporation, 2010
> + * Copyright (C) 2015  Red Hat, Inc.
> + * Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of version 2.1 of the GNU Lesser General Public License
> + * as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it would be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> + *
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/fs.h>
> +#include <linux/richacl_xattr.h>
> +
> +#include "ext4.h"
> +#include "ext4_jbd2.h"
> +#include "xattr.h"
> +#include "acl.h"
> +#include "richacl.h"
> +
> +struct richacl *
> +ext4_get_richacl(struct inode *inode)
> +{
> +	const int name_index = EXT4_XATTR_INDEX_RICHACL;
> +	void *value = NULL;
> +	struct richacl *acl;
> +	int retval;
> +
> +	if (!IS_RICHACL(inode))
> +		return ERR_PTR(-EOPNOTSUPP);
> +	acl = get_cached_richacl(inode);
> +	if (acl != ACL_NOT_CACHED)
> +		return acl;
> +	retval = ext4_xattr_get(inode, name_index, "", NULL, 0);
> +	if (retval > 0) {
> +		value = kmalloc(retval, GFP_NOFS);
> +		if (!value)
> +			return ERR_PTR(-ENOMEM);
> +		retval = ext4_xattr_get(inode, name_index, "", value, retval);
> +	}
> +	if (retval > 0) {
> +		acl = richacl_from_xattr(&init_user_ns, value, retval);
> +		if (acl == ERR_PTR(-EINVAL))
> +			acl = ERR_PTR(-EIO);
> +	} else if (retval == -ENODATA || retval == -ENOSYS)
> +		acl = NULL;
> +	else
> +		acl = ERR_PTR(retval);
> +	kfree(value);
> +
> +	if (!IS_ERR(acl))
> +		set_cached_richacl(inode, acl);
> +
> +	return acl;
> +}
> +
> +static int
> +ext4_set_richacl(handle_t *handle, struct inode *inode, struct richacl *acl)
> +{
> +	const int name_index = EXT4_XATTR_INDEX_RICHACL;
> +	size_t size = 0;
> +	void *value = NULL;
> +	int retval;
> +
> +	if (acl) {
> +		mode_t mode = inode->i_mode;
> +
> +		if (richacl_equiv_mode(acl, &mode) == 0) {
> +			inode->i_mode = mode;
> +			ext4_mark_inode_dirty(handle, inode);
> +			acl = NULL;
> +		}
> +	}
> +	if (acl) {
> +		size = richacl_xattr_size(acl);
> +		value = kmalloc(size, GFP_NOFS);
> +		if (!value)
> +			return -ENOMEM;
> +		richacl_to_xattr(&init_user_ns, acl, value, size);
> +	}
> +	if (handle)
> +		retval = ext4_xattr_set_handle(handle, inode, name_index, "",
> +					       value, size, 0);
> +	else
> +		retval = ext4_xattr_set(inode, name_index, "", value, size, 0);
> +	kfree(value);
> +	if (!retval)
> +		set_cached_richacl(inode, acl);
> +
> +	return retval;
> +}
> +
> +int
> +ext4_init_richacl(handle_t *handle, struct inode *inode, struct inode *dir)
> +{
> +	struct richacl *acl = richacl_create(inode, dir);
> +	int error;
> +
> +	error = PTR_ERR(acl);
> +	if (IS_ERR(acl))
> +		return error;
> +	if (acl) {
> +		error = ext4_set_richacl(handle, inode, acl);
> +		richacl_put(acl);
> +	}
> +	return error;
> +}
> +
> +int
> +ext4_richacl_chmod(struct inode *inode)
> +{
> +	struct richacl *acl;
> +	int retval;
> +
> +	if (S_ISLNK(inode->i_mode))
> +		return -EOPNOTSUPP;
> +	acl = ext4_get_richacl(inode);
> +	if (IS_ERR_OR_NULL(acl))
> +		return PTR_ERR(acl);
> +	acl = richacl_chmod(acl, inode->i_mode);
> +	if (IS_ERR(acl))
> +		return PTR_ERR(acl);
> +	retval = ext4_set_richacl(NULL, inode, acl);
> +	richacl_put(acl);
> +
> +	return retval;
> +}
> +
> +static size_t
> +ext4_xattr_list_richacl(struct dentry *dentry, char *list, size_t list_len,
> +			const char *name, size_t name_len, int type)
> +{
> +	const size_t size = sizeof(XATTR_NAME_RICHACL);
> +
> +	if (!IS_RICHACL(d_inode(dentry)))
> +		return 0;
> +	if (list && size <= list_len)
> +		memcpy(list, XATTR_NAME_RICHACL, size);
> +	return size;
> +}
> +
> +static int
> +ext4_xattr_get_richacl(struct dentry *dentry, const char *name, void *buffer,
> +		size_t buffer_size, int type)
> +{
> +	struct richacl *acl;
> +	int error;
> +
> +	if (strcmp(name, "") != 0)
> +		return -EINVAL;
> +	acl = ext4_get_richacl(d_inode(dentry));
> +	if (IS_ERR(acl))
> +		return PTR_ERR(acl);
> +	if (acl == NULL)
> +		return -ENODATA;
> +
> +	error = richacl_to_xattr(&init_user_ns, acl, buffer, buffer_size);
> +	richacl_put(acl);
> +	return error;
> +}
> +
> +static int
> +ext4_xattr_set_richacl(struct dentry *dentry, const char *name,
> +		const void *value, size_t size, int flags, int type)
> +{
> +	handle_t *handle;
> +	struct richacl *acl = NULL;
> +	int retval, retries = 0;
> +	struct inode *inode = d_inode(dentry);
> +
> +	if (!IS_RICHACL(d_inode(dentry)))
> +		return -EOPNOTSUPP;
> +	if (S_ISLNK(inode->i_mode))
> +		return -EOPNOTSUPP;
> +	if (strcmp(name, "") != 0)
> +		return -EINVAL;
> +	if (!uid_eq(current_fsuid(), inode->i_uid) &&
> +	    inode_permission(inode, MAY_CHMOD) &&
> +	    !capable(CAP_FOWNER))
> +		return -EPERM;
> +	if (value) {
> +		acl = richacl_from_xattr(&init_user_ns, value, size);
> +		if (IS_ERR(acl))
> +			return PTR_ERR(acl);
> +
> +		inode->i_mode &= ~S_IRWXUGO;
> +		inode->i_mode |= richacl_masks_to_mode(acl);
> +	}
> +
> +retry:
> +	handle = ext4_journal_start(inode, EXT4_HT_XATTR,
> +				    EXT4_DATA_TRANS_BLOCKS(inode->i_sb));
> +	if (IS_ERR(handle))
> +		return PTR_ERR(handle);
> +	retval = ext4_set_richacl(handle, inode, acl);
> +	ext4_journal_stop(handle);
> +	if (retval == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries))
> +		goto retry;
> +	richacl_put(acl);
> +	return retval;
> +}
> +
> +const struct xattr_handler ext4_richacl_xattr_handler = {
> +	.prefix	= XATTR_NAME_RICHACL,
> +	.list	= ext4_xattr_list_richacl,
> +	.get	= ext4_xattr_get_richacl,
> +	.set	= ext4_xattr_set_richacl,
> +};
> diff --git a/fs/ext4/richacl.h b/fs/ext4/richacl.h
> new file mode 100644
> index 0000000..09a5cad
> --- /dev/null
> +++ b/fs/ext4/richacl.h
> @@ -0,0 +1,47 @@
> +/*
> + * Copyright IBM Corporation, 2010
> + * Copyright (C)  2015 Red Hat, Inc.
> + * Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of version 2.1 of the GNU Lesser General Public License
> + * as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it would be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> + *
> + */
> +
> +#ifndef __FS_EXT4_RICHACL_H
> +#define __FS_EXT4_RICHACL_H
> +
> +#include <linux/richacl.h>
> +
> +#ifdef CONFIG_EXT4_FS_RICHACL
> +
> +#define EXT4_IS_RICHACL(inode) IS_RICHACL(inode)
> +
> +extern struct richacl *ext4_get_richacl(struct inode *);
> +extern int ext4_init_richacl(handle_t *, struct inode *, struct inode *);
> +extern int ext4_richacl_chmod(struct inode *);
> +
> +#else  /* CONFIG_FS_EXT4_RICHACL */
> +
> +#define EXT4_IS_RICHACL(inode) (0)
> +#define ext4_get_richacl   NULL
> +
> +static inline int
> +ext4_init_richacl(handle_t *handle, struct inode *inode, struct inode *dir)
> +{
> +	return 0;
> +}
> +
> +static inline int
> +ext4_richacl_chmod(struct inode *inode)
> +{
> +	return 0;
> +}
> +
> +#endif  /* CONFIG_FS_EXT4_RICHACL */
> +#endif  /* __FS_EXT4_RICHACL_H */
> diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c
> index 16e28c0..c1dee9b 100644
> --- a/fs/ext4/xattr.c
> +++ b/fs/ext4/xattr.c
> @@ -99,6 +99,9 @@ static const struct xattr_handler *ext4_xattr_handler_map[] = {
>  #ifdef CONFIG_EXT4_FS_SECURITY
>  	[EXT4_XATTR_INDEX_SECURITY]	     = &ext4_xattr_security_handler,
>  #endif
> +#ifdef CONFIG_EXT4_FS_RICHACL
> +	[EXT4_XATTR_INDEX_RICHACL]           = &ext4_richacl_xattr_handler,
> +#endif
>  };
>
>  const struct xattr_handler *ext4_xattr_handlers[] = {
> @@ -111,6 +114,9 @@ const struct xattr_handler *ext4_xattr_handlers[] = {
>  #ifdef CONFIG_EXT4_FS_SECURITY
>  	&ext4_xattr_security_handler,
>  #endif
> +#ifdef CONFIG_EXT4_FS_RICHACL
> +	&ext4_richacl_xattr_handler,
> +#endif
>  	NULL
>  };
>
> diff --git a/fs/ext4/xattr.h b/fs/ext4/xattr.h
> index ddc0957..f315493 100644
> --- a/fs/ext4/xattr.h
> +++ b/fs/ext4/xattr.h
> @@ -98,6 +98,7 @@ struct ext4_xattr_ibody_find {
>  extern const struct xattr_handler ext4_xattr_user_handler;
>  extern const struct xattr_handler ext4_xattr_trusted_handler;
>  extern const struct xattr_handler ext4_xattr_security_handler;
> +extern const struct xattr_handler ext4_richacl_xattr_handler;
>
>  #define EXT4_XATTR_NAME_ENCRYPTION_CONTEXT "c"
>
> -- 
> 2.4.3


^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 19/41] ext4: Add richacl feature flag
  2015-09-05 10:27     ` Andreas Gruenbacher
  (?)
@ 2015-09-23  2:31     ` Aneesh Kumar K.V
  -1 siblings, 0 replies; 188+ messages in thread
From: Aneesh Kumar K.V @ 2015-09-23  2:31 UTC (permalink / raw)
  To: Andreas Gruenbacher, linux-kernel, linux-fsdevel, linux-nfs,
	linux-api, linux-cifs, linux-security-module

Andreas Gruenbacher <agruenba@redhat.com> writes:

> From: "Aneesh Kumar K.V" <aneesh.kumar@linux.vnet.ibm.com>
>
> This feature flag selects richacl instead of posix acl support on the
> file system. In addition, the "acl" mount option is needed for enabling
> either of the two kinds of acls.
>
> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>

 Signed-off-by: Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>

> ---
>  fs/ext4/ext4.h  |  6 ++++--
>  fs/ext4/super.c | 42 +++++++++++++++++++++++++++++++++---------
>  2 files changed, 37 insertions(+), 11 deletions(-)
>
> diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
> index f5e9f04..e69c8ea 100644
> --- a/fs/ext4/ext4.h
> +++ b/fs/ext4/ext4.h
> @@ -991,7 +991,7 @@ struct ext4_inode_info {
>  #define EXT4_MOUNT_UPDATE_JOURNAL	0x01000	/* Update the journal format */
>  #define EXT4_MOUNT_NO_UID32		0x02000  /* Disable 32-bit UIDs */
>  #define EXT4_MOUNT_XATTR_USER		0x04000	/* Extended user attributes */
> -#define EXT4_MOUNT_POSIX_ACL		0x08000	/* POSIX Access Control Lists */
> +#define EXT4_MOUNT_ACL			0x08000	/* Access Control Lists */
>  #define EXT4_MOUNT_NO_AUTO_DA_ALLOC	0x10000	/* No auto delalloc mapping */
>  #define EXT4_MOUNT_BARRIER		0x20000 /* Use block barriers */
>  #define EXT4_MOUNT_QUOTA		0x80000 /* Some quota option set */
> @@ -1582,6 +1582,7 @@ static inline int ext4_encrypted_inode(struct inode *inode)
>  #define EXT4_FEATURE_INCOMPAT_LARGEDIR		0x4000 /* >2GB or 3-lvl htree */
>  #define EXT4_FEATURE_INCOMPAT_INLINE_DATA	0x8000 /* data in inode */
>  #define EXT4_FEATURE_INCOMPAT_ENCRYPT		0x10000
> +#define EXT4_FEATURE_INCOMPAT_RICHACL		0x20000
>
>  #define EXT2_FEATURE_COMPAT_SUPP	EXT4_FEATURE_COMPAT_EXT_ATTR
>  #define EXT2_FEATURE_INCOMPAT_SUPP	(EXT4_FEATURE_INCOMPAT_FILETYPE| \
> @@ -1607,7 +1608,8 @@ static inline int ext4_encrypted_inode(struct inode *inode)
>  					 EXT4_FEATURE_INCOMPAT_FLEX_BG| \
>  					 EXT4_FEATURE_INCOMPAT_MMP | \
>  					 EXT4_FEATURE_INCOMPAT_INLINE_DATA | \
> -					 EXT4_FEATURE_INCOMPAT_ENCRYPT)
> +					 EXT4_FEATURE_INCOMPAT_ENCRYPT | \
> +					 EXT4_FEATURE_INCOMPAT_RICHACL)
>  #define EXT4_FEATURE_RO_COMPAT_SUPP	(EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER| \
>  					 EXT4_FEATURE_RO_COMPAT_LARGE_FILE| \
>  					 EXT4_FEATURE_RO_COMPAT_GDT_CSUM| \
> diff --git a/fs/ext4/super.c b/fs/ext4/super.c
> index 58987b5..05d6537 100644
> --- a/fs/ext4/super.c
> +++ b/fs/ext4/super.c
> @@ -1257,6 +1257,28 @@ static ext4_fsblk_t get_sb_block(void **data)
>  	return sb_block;
>  }
>
> +static int enable_acl(struct super_block *sb)
> +{
> +	sb->s_flags &= ~(MS_POSIXACL | MS_RICHACL);
> +	if (test_opt(sb, ACL)) {
> +		if (EXT4_HAS_INCOMPAT_FEATURE(sb,
> +					      EXT4_FEATURE_INCOMPAT_RICHACL)) {
> +#ifdef CONFIG_EXT4_FS_RICHACL
> +			sb->s_flags |= MS_RICHACL;
> +#else
> +			return -EOPNOTSUPP;
> +#endif
> +		} else {
> +#ifdef CONFIG_EXT4_FS_POSIX_ACL
> +			sb->s_flags |= MS_POSIXACL;
> +#else
> +			return -EOPNOTSUPP;
> +#endif
> +		}
> +	}
> +	return 0;
> +}
> +
>  #define DEFAULT_JOURNAL_IOPRIO (IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 3))
>  static char deprecated_msg[] = "Mount option \"%s\" will be removed by %s\n"
>  	"Contact linux-ext4@vger.kernel.org if you think we should keep it.\n";
> @@ -1403,9 +1425,9 @@ static const struct mount_opts {
>  	 MOPT_NO_EXT2 | MOPT_DATAJ},
>  	{Opt_user_xattr, EXT4_MOUNT_XATTR_USER, MOPT_SET},
>  	{Opt_nouser_xattr, EXT4_MOUNT_XATTR_USER, MOPT_CLEAR},
> -#ifdef CONFIG_EXT4_FS_POSIX_ACL
> -	{Opt_acl, EXT4_MOUNT_POSIX_ACL, MOPT_SET},
> -	{Opt_noacl, EXT4_MOUNT_POSIX_ACL, MOPT_CLEAR},
> +#if defined(CONFIG_EXT4_FS_POSIX_ACL) || defined(CONFIG_EXT4_FS_RICHACL)
> +	{Opt_acl, EXT4_MOUNT_ACL, MOPT_SET},
> +	{Opt_noacl, EXT4_MOUNT_ACL, MOPT_CLEAR},
>  #else
>  	{Opt_acl, 0, MOPT_NOSUPPORT},
>  	{Opt_noacl, 0, MOPT_NOSUPPORT},
> @@ -3563,8 +3585,8 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
>  		set_opt(sb, NO_UID32);
>  	/* xattr user namespace & acls are now defaulted on */
>  	set_opt(sb, XATTR_USER);
> -#ifdef CONFIG_EXT4_FS_POSIX_ACL
> -	set_opt(sb, POSIX_ACL);
> +#if defined(CONFIG_EXT4_FS_POSIX_ACL) || defined(CONFIG_EXT4_FS_RICHACL)
> +	set_opt(sb, ACL);
>  #endif
>  	/* don't forget to enable journal_csum when metadata_csum is enabled. */
>  	if (ext4_has_metadata_csum(sb))
> @@ -3645,8 +3667,9 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
>  			clear_opt(sb, DELALLOC);
>  	}
>
> -	sb->s_flags = (sb->s_flags & ~MS_POSIXACL) |
> -		(test_opt(sb, POSIX_ACL) ? MS_POSIXACL : 0);
> +	err = enable_acl(sb);
> +	if (err)
> +		goto failed_mount;
>
>  	if (le32_to_cpu(es->s_rev_level) == EXT4_GOOD_OLD_REV &&
>  	    (EXT4_HAS_COMPAT_FEATURE(sb, ~0U) ||
> @@ -4960,8 +4983,9 @@ static int ext4_remount(struct super_block *sb, int *flags, char *data)
>  	if (sbi->s_mount_flags & EXT4_MF_FS_ABORTED)
>  		ext4_abort(sb, "Abort forced by user");
>
> -	sb->s_flags = (sb->s_flags & ~MS_POSIXACL) |
> -		(test_opt(sb, POSIX_ACL) ? MS_POSIXACL : 0);
> +	err = enable_acl(sb);
> +	if (err)
> +		goto restore_opts;
>
>  	es = sbi->s_es;
>
> -- 
> 2.4.3

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 25/41] richacl: Isolate the owner and group classes
  2015-09-22 16:06         ` J. Bruce Fields
@ 2015-09-23 13:11             ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-23 13:11 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: LKML, linux-fsdevel, linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

2015-09-22 18:06 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> On Sat, Sep 05, 2015 at 12:27:20PM +0200, Andreas Gruenbacher wrote:
>> When applying the file masks to an acl, we need to ensure that no
>> process gets more permissions than allowed by its file mask.
>>
>> This may require inserting an owner@ deny ace to ensure this if the
>> owner mask contains fewer permissions than the group or other mask.  For
>> example, when applying mode 0466 to the following acl:
>>
>>    everyone@:rw::allow
>>
>> A deny ace needs to be inserted so that the owner won't get elevated
>> write access:
>>
>>    owner@:w::deny
>>    everyone@:rw::allow
>>
>> Likewise, we may need to insert group class deny aces if the group mask
>> contains fewer permissions than the other mask.  For example, when
>> applying mode 0646 to the following acl:
>>
>>    owner@:rw::allow
>>    everyone@:rw::allow
>>
>> A deny ace needs to be inserted so that the owning group won't get
>> elevated write access:
>>
>>    owner@:rw::allow
>>    group@:w::deny
>>    everyone@:rw::allow
>>
>> Signed-off-by: Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
>> ---
>>  fs/richacl_compat.c | 236 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>>  1 file changed, 236 insertions(+)
>>
>> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
>> index 30bdc95..412844c 100644
>> --- a/fs/richacl_compat.c
>> +++ b/fs/richacl_compat.c
>> @@ -494,3 +494,239 @@ richacl_set_other_permissions(struct richacl_alloc *alloc)
>>               richace_change_mask(alloc, &ace, other_mask);
>>       return 0;
>>  }
>> +
>> +/**
>> + * richacl_max_allowed  -  maximum permissions that anybody is allowed
>> + */
>> +static unsigned int
>> +richacl_max_allowed(struct richacl *acl)
>> +{
>> +     struct richace *ace;
>> +     unsigned int allowed = 0;
>> +
>> +     richacl_for_each_entry_reverse(ace, acl) {
>> +             if (richace_is_inherit_only(ace))
>> +                     continue;
>> +             if (richace_is_allow(ace))
>> +                     allowed |= ace->e_mask;
>> +             else if (richace_is_deny(ace)) {
>> +                     if (richace_is_everyone(ace))
>> +                             allowed &= ~ace->e_mask;
>> +             }
>> +     }
>> +     return allowed;
>> +}
>> +
>> +/**
>> + * richacl_isolate_owner_class  -  limit the owner class to the owner file mask
>> + * @alloc:   acl and number of allocated entries
>> + *
>> + * POSIX requires that after a chmod, the owner class is granted no more
>> + * permissions than the owner file permission bits.  For richacls, this
>> + * means that the owner class must not be granted any permissions that the
>> + * owner mask does not include.
>> + *
>> + * When we apply file masks to an acl which grant more permissions to the group
>> + * or other class than to the owner class, we may end up in a situation where
>> + * the owner is granted additional permissions from other aces.  For example,
>> + * given this acl:
>> + *
>> + *    everyone:rwx::allow
>> + *
>> + * when file masks corresponding to mode 0466 are applied, after
>> + * richacl_propagate_everyone() and __richacl_apply_masks(), we end up with:
>> + *
>> + *    owner@:r::allow
>> + *    everyone@:rw::allow
>
> Are you sure?  I didn't think richacl_apply_masks actually creates an
> owner@ entry in this case.  Which is OK, just delete the owner@ ace from
> here and the following example and it still makes sense, I think.

Hmm, the example can be fixed by applying more 0406 here instead of 0466.

> (But: thanks in general for the examples in these comments, they're
> extremely helpful.)

Yes, I think without them, the code cannot be reviewed properly.

> I'd find it simpler to follow without the  a_entries + a_count condition,
> maybe something like this (untested):
>
> [...]

Great, let me further simplify this to:

static int
richacl_isolate_owner_class(struct richacl_alloc *alloc)
{
        struct richacl *acl = alloc->acl;
        unsigned int deny = richacl_max_allowed(acl) & ~acl->a_owner_mask;

        if (deny) {
                struct richace *ace;

                /*
                 * Figure out if we can update an existig OWNER@ DENY entry.
                 */
                richacl_for_each_entry(ace, acl) {
                        if (richace_is_inherit_only(ace))
                                continue;
                        if (richace_is_allow(ace))
                                break;
                        if (richace_is_owner(ace)) {
                                return richace_change_mask(alloc, &ace,
                                                           ace->e_mask | deny);
                        }
                }

                /* Insert an owner@ deny entry at the front. */
                ace = acl->a_entries;
                if (richacl_insert_entry(alloc, &ace))
                        return -1;
                ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
                ace->e_flags = RICHACE_SPECIAL_WHO;
                ace->e_mask = deny;
                ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
        }
        return 0;
}

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 25/41] richacl: Isolate the owner and group classes
@ 2015-09-23 13:11             ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-23 13:11 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: LKML, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

2015-09-22 18:06 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> On Sat, Sep 05, 2015 at 12:27:20PM +0200, Andreas Gruenbacher wrote:
>> When applying the file masks to an acl, we need to ensure that no
>> process gets more permissions than allowed by its file mask.
>>
>> This may require inserting an owner@ deny ace to ensure this if the
>> owner mask contains fewer permissions than the group or other mask.  For
>> example, when applying mode 0466 to the following acl:
>>
>>    everyone@:rw::allow
>>
>> A deny ace needs to be inserted so that the owner won't get elevated
>> write access:
>>
>>    owner@:w::deny
>>    everyone@:rw::allow
>>
>> Likewise, we may need to insert group class deny aces if the group mask
>> contains fewer permissions than the other mask.  For example, when
>> applying mode 0646 to the following acl:
>>
>>    owner@:rw::allow
>>    everyone@:rw::allow
>>
>> A deny ace needs to be inserted so that the owning group won't get
>> elevated write access:
>>
>>    owner@:rw::allow
>>    group@:w::deny
>>    everyone@:rw::allow
>>
>> Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
>> ---
>>  fs/richacl_compat.c | 236 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>>  1 file changed, 236 insertions(+)
>>
>> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
>> index 30bdc95..412844c 100644
>> --- a/fs/richacl_compat.c
>> +++ b/fs/richacl_compat.c
>> @@ -494,3 +494,239 @@ richacl_set_other_permissions(struct richacl_alloc *alloc)
>>               richace_change_mask(alloc, &ace, other_mask);
>>       return 0;
>>  }
>> +
>> +/**
>> + * richacl_max_allowed  -  maximum permissions that anybody is allowed
>> + */
>> +static unsigned int
>> +richacl_max_allowed(struct richacl *acl)
>> +{
>> +     struct richace *ace;
>> +     unsigned int allowed = 0;
>> +
>> +     richacl_for_each_entry_reverse(ace, acl) {
>> +             if (richace_is_inherit_only(ace))
>> +                     continue;
>> +             if (richace_is_allow(ace))
>> +                     allowed |= ace->e_mask;
>> +             else if (richace_is_deny(ace)) {
>> +                     if (richace_is_everyone(ace))
>> +                             allowed &= ~ace->e_mask;
>> +             }
>> +     }
>> +     return allowed;
>> +}
>> +
>> +/**
>> + * richacl_isolate_owner_class  -  limit the owner class to the owner file mask
>> + * @alloc:   acl and number of allocated entries
>> + *
>> + * POSIX requires that after a chmod, the owner class is granted no more
>> + * permissions than the owner file permission bits.  For richacls, this
>> + * means that the owner class must not be granted any permissions that the
>> + * owner mask does not include.
>> + *
>> + * When we apply file masks to an acl which grant more permissions to the group
>> + * or other class than to the owner class, we may end up in a situation where
>> + * the owner is granted additional permissions from other aces.  For example,
>> + * given this acl:
>> + *
>> + *    everyone:rwx::allow
>> + *
>> + * when file masks corresponding to mode 0466 are applied, after
>> + * richacl_propagate_everyone() and __richacl_apply_masks(), we end up with:
>> + *
>> + *    owner@:r::allow
>> + *    everyone@:rw::allow
>
> Are you sure?  I didn't think richacl_apply_masks actually creates an
> owner@ entry in this case.  Which is OK, just delete the owner@ ace from
> here and the following example and it still makes sense, I think.

Hmm, the example can be fixed by applying more 0406 here instead of 0466.

> (But: thanks in general for the examples in these comments, they're
> extremely helpful.)

Yes, I think without them, the code cannot be reviewed properly.

> I'd find it simpler to follow without the  a_entries + a_count condition,
> maybe something like this (untested):
>
> [...]

Great, let me further simplify this to:

static int
richacl_isolate_owner_class(struct richacl_alloc *alloc)
{
        struct richacl *acl = alloc->acl;
        unsigned int deny = richacl_max_allowed(acl) & ~acl->a_owner_mask;

        if (deny) {
                struct richace *ace;

                /*
                 * Figure out if we can update an existig OWNER@ DENY entry.
                 */
                richacl_for_each_entry(ace, acl) {
                        if (richace_is_inherit_only(ace))
                                continue;
                        if (richace_is_allow(ace))
                                break;
                        if (richace_is_owner(ace)) {
                                return richace_change_mask(alloc, &ace,
                                                           ace->e_mask | deny);
                        }
                }

                /* Insert an owner@ deny entry at the front. */
                ace = acl->a_entries;
                if (richacl_insert_entry(alloc, &ace))
                        return -1;
                ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
                ace->e_flags = RICHACE_SPECIAL_WHO;
                ace->e_mask = deny;
                ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
        }
        return 0;
}

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 25/41] richacl: Isolate the owner and group classes
  2015-09-23 13:11             ` Andreas Gruenbacher
@ 2015-09-23 13:15                 ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-23 13:15 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: LKML, linux-fsdevel, linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

On Wed, Sep 23, 2015 at 03:11:45PM +0200, Andreas Gruenbacher wrote:
> 2015-09-22 18:06 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> > On Sat, Sep 05, 2015 at 12:27:20PM +0200, Andreas Gruenbacher wrote:
> >> When applying the file masks to an acl, we need to ensure that no
> >> process gets more permissions than allowed by its file mask.
> >>
> >> This may require inserting an owner@ deny ace to ensure this if the
> >> owner mask contains fewer permissions than the group or other mask.  For
> >> example, when applying mode 0466 to the following acl:
> >>
> >>    everyone@:rw::allow
> >>
> >> A deny ace needs to be inserted so that the owner won't get elevated
> >> write access:
> >>
> >>    owner@:w::deny
> >>    everyone@:rw::allow
> >>
> >> Likewise, we may need to insert group class deny aces if the group mask
> >> contains fewer permissions than the other mask.  For example, when
> >> applying mode 0646 to the following acl:
> >>
> >>    owner@:rw::allow
> >>    everyone@:rw::allow
> >>
> >> A deny ace needs to be inserted so that the owning group won't get
> >> elevated write access:
> >>
> >>    owner@:rw::allow
> >>    group@:w::deny
> >>    everyone@:rw::allow
> >>
> >> Signed-off-by: Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> >> ---
> >>  fs/richacl_compat.c | 236 ++++++++++++++++++++++++++++++++++++++++++++++++++++
> >>  1 file changed, 236 insertions(+)
> >>
> >> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> >> index 30bdc95..412844c 100644
> >> --- a/fs/richacl_compat.c
> >> +++ b/fs/richacl_compat.c
> >> @@ -494,3 +494,239 @@ richacl_set_other_permissions(struct richacl_alloc *alloc)
> >>               richace_change_mask(alloc, &ace, other_mask);
> >>       return 0;
> >>  }
> >> +
> >> +/**
> >> + * richacl_max_allowed  -  maximum permissions that anybody is allowed
> >> + */
> >> +static unsigned int
> >> +richacl_max_allowed(struct richacl *acl)
> >> +{
> >> +     struct richace *ace;
> >> +     unsigned int allowed = 0;
> >> +
> >> +     richacl_for_each_entry_reverse(ace, acl) {
> >> +             if (richace_is_inherit_only(ace))
> >> +                     continue;
> >> +             if (richace_is_allow(ace))
> >> +                     allowed |= ace->e_mask;
> >> +             else if (richace_is_deny(ace)) {
> >> +                     if (richace_is_everyone(ace))
> >> +                             allowed &= ~ace->e_mask;
> >> +             }
> >> +     }
> >> +     return allowed;
> >> +}
> >> +
> >> +/**
> >> + * richacl_isolate_owner_class  -  limit the owner class to the owner file mask
> >> + * @alloc:   acl and number of allocated entries
> >> + *
> >> + * POSIX requires that after a chmod, the owner class is granted no more
> >> + * permissions than the owner file permission bits.  For richacls, this
> >> + * means that the owner class must not be granted any permissions that the
> >> + * owner mask does not include.
> >> + *
> >> + * When we apply file masks to an acl which grant more permissions to the group
> >> + * or other class than to the owner class, we may end up in a situation where
> >> + * the owner is granted additional permissions from other aces.  For example,
> >> + * given this acl:
> >> + *
> >> + *    everyone:rwx::allow
> >> + *
> >> + * when file masks corresponding to mode 0466 are applied, after
> >> + * richacl_propagate_everyone() and __richacl_apply_masks(), we end up with:
> >> + *
> >> + *    owner@:r::allow
> >> + *    everyone@:rw::allow
> >
> > Are you sure?  I didn't think richacl_apply_masks actually creates an
> > owner@ entry in this case.  Which is OK, just delete the owner@ ace from
> > here and the following example and it still makes sense, I think.
> 
> Hmm, the example can be fixed by applying more 0406 here instead of 0466.
> 
> > (But: thanks in general for the examples in these comments, they're
> > extremely helpful.)
> 
> Yes, I think without them, the code cannot be reviewed properly.
> 
> > I'd find it simpler to follow without the  a_entries + a_count condition,
> > maybe something like this (untested):
> >
> > [...]
> 
> Great, let me further simplify this to:

Works for me!  (And feel free to add a Reviewed-by:.)

--b.

> 
> static int
> richacl_isolate_owner_class(struct richacl_alloc *alloc)
> {
>         struct richacl *acl = alloc->acl;
>         unsigned int deny = richacl_max_allowed(acl) & ~acl->a_owner_mask;
> 
>         if (deny) {
>                 struct richace *ace;
> 
>                 /*
>                  * Figure out if we can update an existig OWNER@ DENY entry.
>                  */
>                 richacl_for_each_entry(ace, acl) {
>                         if (richace_is_inherit_only(ace))
>                                 continue;
>                         if (richace_is_allow(ace))
>                                 break;
>                         if (richace_is_owner(ace)) {
>                                 return richace_change_mask(alloc, &ace,
>                                                            ace->e_mask | deny);
>                         }
>                 }
> 
>                 /* Insert an owner@ deny entry at the front. */
>                 ace = acl->a_entries;
>                 if (richacl_insert_entry(alloc, &ace))
>                         return -1;
>                 ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
>                 ace->e_flags = RICHACE_SPECIAL_WHO;
>                 ace->e_mask = deny;
>                 ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
>         }
>         return 0;
> }
> 
> Thanks,
> Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 25/41] richacl: Isolate the owner and group classes
@ 2015-09-23 13:15                 ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-23 13:15 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: LKML, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

On Wed, Sep 23, 2015 at 03:11:45PM +0200, Andreas Gruenbacher wrote:
> 2015-09-22 18:06 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> > On Sat, Sep 05, 2015 at 12:27:20PM +0200, Andreas Gruenbacher wrote:
> >> When applying the file masks to an acl, we need to ensure that no
> >> process gets more permissions than allowed by its file mask.
> >>
> >> This may require inserting an owner@ deny ace to ensure this if the
> >> owner mask contains fewer permissions than the group or other mask.  For
> >> example, when applying mode 0466 to the following acl:
> >>
> >>    everyone@:rw::allow
> >>
> >> A deny ace needs to be inserted so that the owner won't get elevated
> >> write access:
> >>
> >>    owner@:w::deny
> >>    everyone@:rw::allow
> >>
> >> Likewise, we may need to insert group class deny aces if the group mask
> >> contains fewer permissions than the other mask.  For example, when
> >> applying mode 0646 to the following acl:
> >>
> >>    owner@:rw::allow
> >>    everyone@:rw::allow
> >>
> >> A deny ace needs to be inserted so that the owning group won't get
> >> elevated write access:
> >>
> >>    owner@:rw::allow
> >>    group@:w::deny
> >>    everyone@:rw::allow
> >>
> >> Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
> >> ---
> >>  fs/richacl_compat.c | 236 ++++++++++++++++++++++++++++++++++++++++++++++++++++
> >>  1 file changed, 236 insertions(+)
> >>
> >> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> >> index 30bdc95..412844c 100644
> >> --- a/fs/richacl_compat.c
> >> +++ b/fs/richacl_compat.c
> >> @@ -494,3 +494,239 @@ richacl_set_other_permissions(struct richacl_alloc *alloc)
> >>               richace_change_mask(alloc, &ace, other_mask);
> >>       return 0;
> >>  }
> >> +
> >> +/**
> >> + * richacl_max_allowed  -  maximum permissions that anybody is allowed
> >> + */
> >> +static unsigned int
> >> +richacl_max_allowed(struct richacl *acl)
> >> +{
> >> +     struct richace *ace;
> >> +     unsigned int allowed = 0;
> >> +
> >> +     richacl_for_each_entry_reverse(ace, acl) {
> >> +             if (richace_is_inherit_only(ace))
> >> +                     continue;
> >> +             if (richace_is_allow(ace))
> >> +                     allowed |= ace->e_mask;
> >> +             else if (richace_is_deny(ace)) {
> >> +                     if (richace_is_everyone(ace))
> >> +                             allowed &= ~ace->e_mask;
> >> +             }
> >> +     }
> >> +     return allowed;
> >> +}
> >> +
> >> +/**
> >> + * richacl_isolate_owner_class  -  limit the owner class to the owner file mask
> >> + * @alloc:   acl and number of allocated entries
> >> + *
> >> + * POSIX requires that after a chmod, the owner class is granted no more
> >> + * permissions than the owner file permission bits.  For richacls, this
> >> + * means that the owner class must not be granted any permissions that the
> >> + * owner mask does not include.
> >> + *
> >> + * When we apply file masks to an acl which grant more permissions to the group
> >> + * or other class than to the owner class, we may end up in a situation where
> >> + * the owner is granted additional permissions from other aces.  For example,
> >> + * given this acl:
> >> + *
> >> + *    everyone:rwx::allow
> >> + *
> >> + * when file masks corresponding to mode 0466 are applied, after
> >> + * richacl_propagate_everyone() and __richacl_apply_masks(), we end up with:
> >> + *
> >> + *    owner@:r::allow
> >> + *    everyone@:rw::allow
> >
> > Are you sure?  I didn't think richacl_apply_masks actually creates an
> > owner@ entry in this case.  Which is OK, just delete the owner@ ace from
> > here and the following example and it still makes sense, I think.
> 
> Hmm, the example can be fixed by applying more 0406 here instead of 0466.
> 
> > (But: thanks in general for the examples in these comments, they're
> > extremely helpful.)
> 
> Yes, I think without them, the code cannot be reviewed properly.
> 
> > I'd find it simpler to follow without the  a_entries + a_count condition,
> > maybe something like this (untested):
> >
> > [...]
> 
> Great, let me further simplify this to:

Works for me!  (And feel free to add a Reviewed-by:.)

--b.

> 
> static int
> richacl_isolate_owner_class(struct richacl_alloc *alloc)
> {
>         struct richacl *acl = alloc->acl;
>         unsigned int deny = richacl_max_allowed(acl) & ~acl->a_owner_mask;
> 
>         if (deny) {
>                 struct richace *ace;
> 
>                 /*
>                  * Figure out if we can update an existig OWNER@ DENY entry.
>                  */
>                 richacl_for_each_entry(ace, acl) {
>                         if (richace_is_inherit_only(ace))
>                                 continue;
>                         if (richace_is_allow(ace))
>                                 break;
>                         if (richace_is_owner(ace)) {
>                                 return richace_change_mask(alloc, &ace,
>                                                            ace->e_mask | deny);
>                         }
>                 }
> 
>                 /* Insert an owner@ deny entry at the front. */
>                 ace = acl->a_entries;
>                 if (richacl_insert_entry(alloc, &ace))
>                         return -1;
>                 ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
>                 ace->e_flags = RICHACE_SPECIAL_WHO;
>                 ace->e_mask = deny;
>                 ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
>         }
>         return 0;
> }
> 
> Thanks,
> Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 25/41] richacl: Isolate the owner and group classes
  2015-09-22 19:02     ` J. Bruce Fields
@ 2015-09-23 13:33           ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-23 13:33 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: LKML, linux-fsdevel, linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

2015-09-22 21:02 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> On Sat, Sep 05, 2015 at 12:27:20PM +0200, Andreas Gruenbacher wrote:
>> +      * Compute the permissions already denied to @who.
>
> I'm not sure, but may be worth commenting on the lack of everyone denies
> here as you do in a couple places below.

Okay.

> As in the owner case, I think a goto would simplify the logic a little.

Yep, I'll clean that up.

>> +             /*
>> +              * Start from the entry before the trailing everyone@ allow
>> +              * entry.  We will not hit everyone@ entries in the loop.
>
> May as well skip the check below, in that case?

Indeed. You can almost tell from those few leftovers how the code has
evolved over time :)

Thanks,
Andreas
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 25/41] richacl: Isolate the owner and group classes
@ 2015-09-23 13:33           ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-23 13:33 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: LKML, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

2015-09-22 21:02 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> On Sat, Sep 05, 2015 at 12:27:20PM +0200, Andreas Gruenbacher wrote:
>> +      * Compute the permissions already denied to @who.
>
> I'm not sure, but may be worth commenting on the lack of everyone denies
> here as you do in a couple places below.

Okay.

> As in the owner case, I think a goto would simplify the logic a little.

Yep, I'll clean that up.

>> +             /*
>> +              * Start from the entry before the trailing everyone@ allow
>> +              * entry.  We will not hit everyone@ entries in the loop.
>
> May as well skip the check below, in that case?

Indeed. You can almost tell from those few leftovers how the code has
evolved over time :)

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 15/41] richacl: Automatic Inheritance
  2015-09-22  1:51       ` J. Bruce Fields
@ 2015-09-23 13:55             ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-23 13:55 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-fsdevel,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

On Mon, Sep 21, 2015 at 09:51:46PM -0400, J. Bruce Fields wrote:
> On Mon, Sep 21, 2015 at 11:19:59PM +0200, Andreas Gruenbacher wrote:
> > 2015-09-18 20:40 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> > > On Sat, Sep 05, 2015 at 12:27:10PM +0200, Andreas Gruenbacher wrote:
> > >> Automatic Inheritance (AI) allows changes to the acl of a directory to
> > > In the above "file" sometimes means "any object" and somethings "a
> > > non-directory".  I can sort it out, but more consistent terminology
> > > would help.
> > 
> > Okay, I'll fix it.
> > 
> > >> Linux does not have a way of creating files without setting the file
> > >> permission bits, so all files created inside a directory with
> > >> RICHACL_AUTO_INHERIT set will have the RICHACL_PROTECTED flag set.  This
> > >> effectively disables Automatic Inheritance.
> > >>
> > >> Protocols which support creating files without specifying permissions
> > >> can explicitly clear the RICHACL_PROTECTED flag after creating a file
> > >> and reset the file masks to "undo" applying the create mode; see
> > >> richacl_compute_max_masks().  They should set the RICHACL_DEFAULTED
> > >> flag.  This is a workaround; a mechanism that would allow a process to
> > >> indicate to the kernel to ignore the create mode when there are
> > >> inherited permissions would fix this problem.
> > >
> > > Also, as you know: current nfsd has no way to create files without
> > > setting permissions.  And if we were to implement that it's unclear how
> > > many clients would actually use it (Windows clients are rare).  And of
> > > course Samba doesn't have the interfaces it would need.
> > >
> > > I think we should just drop this for now.  The rest of the richacl stuff
> > > is still useful without it.
> > 
> > Samba will hack around it and adjust the ACL after the create; that's
> > still better than not having Automatic Inheritance. Windows uses AI
> > all the time so AI is more important for Samba than for NFSv4.
> 
> Oh, OK, that makes sense.  Even just giving them a place to store the
> bits would be better than nothing.  So, ignore my objection there....

Oh, and

	Reviewed-by: J. Bruce Fields <bfields-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>

--b.
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 15/41] richacl: Automatic Inheritance
@ 2015-09-23 13:55             ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-23 13:55 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Mon, Sep 21, 2015 at 09:51:46PM -0400, J. Bruce Fields wrote:
> On Mon, Sep 21, 2015 at 11:19:59PM +0200, Andreas Gruenbacher wrote:
> > 2015-09-18 20:40 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> > > On Sat, Sep 05, 2015 at 12:27:10PM +0200, Andreas Gruenbacher wrote:
> > >> Automatic Inheritance (AI) allows changes to the acl of a directory to
> > > In the above "file" sometimes means "any object" and somethings "a
> > > non-directory".  I can sort it out, but more consistent terminology
> > > would help.
> > 
> > Okay, I'll fix it.
> > 
> > >> Linux does not have a way of creating files without setting the file
> > >> permission bits, so all files created inside a directory with
> > >> RICHACL_AUTO_INHERIT set will have the RICHACL_PROTECTED flag set.  This
> > >> effectively disables Automatic Inheritance.
> > >>
> > >> Protocols which support creating files without specifying permissions
> > >> can explicitly clear the RICHACL_PROTECTED flag after creating a file
> > >> and reset the file masks to "undo" applying the create mode; see
> > >> richacl_compute_max_masks().  They should set the RICHACL_DEFAULTED
> > >> flag.  This is a workaround; a mechanism that would allow a process to
> > >> indicate to the kernel to ignore the create mode when there are
> > >> inherited permissions would fix this problem.
> > >
> > > Also, as you know: current nfsd has no way to create files without
> > > setting permissions.  And if we were to implement that it's unclear how
> > > many clients would actually use it (Windows clients are rare).  And of
> > > course Samba doesn't have the interfaces it would need.
> > >
> > > I think we should just drop this for now.  The rest of the richacl stuff
> > > is still useful without it.
> > 
> > Samba will hack around it and adjust the ACL after the create; that's
> > still better than not having Automatic Inheritance. Windows uses AI
> > all the time so AI is more important for Samba than for NFSv4.
> 
> Oh, OK, that makes sense.  Even just giving them a place to store the
> bits would be better than nothing.  So, ignore my objection there....

Oh, and

	Reviewed-by: J. Bruce Fields <bfields@redhat.com>

--b.

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 24/41] richacl: Set the other permissions to the other mask
  2015-09-05 10:27     ` Andreas Gruenbacher
  (?)
@ 2015-09-23 14:03     ` J. Bruce Fields
       [not found]       ` <20150923140307.GB27083-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
  -1 siblings, 1 reply; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-23 14:03 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Sat, Sep 05, 2015 at 12:27:19PM +0200, Andreas Gruenbacher wrote:
> Change the acl so that everyone@ is granted the permissions set in the
> other mask.
> 
> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> ---
>  fs/richacl_compat.c | 35 +++++++++++++++++++++++++++++++++++
>  1 file changed, 35 insertions(+)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 14a1b4b..30bdc95 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -459,3 +459,38 @@ richacl_set_owner_permissions(struct richacl_alloc *alloc)
>  	}
>  	return 0;
>  }
> +
> +/**
> + * richacl_set_other_permissions  -  set the other permissions to the other mask
> + *
> + * Change the acl so that everyone@ is granted the permissions set in the other
> + * mask.  This leaves at most one efective everyone@ allow entry at the end of
> + * the acl.
> + */
> +static int
> +richacl_set_other_permissions(struct richacl_alloc *alloc)
> +{
> +	struct richacl *acl = alloc->acl;
> +	unsigned int x = RICHACE_POSIX_ALWAYS_ALLOWED;
> +	unsigned int other_mask = acl->a_other_mask & ~x;
> +	struct richace *ace = acl->a_entries + acl->a_count - 1;
> +
> +	if (!(other_mask &&
> +	      (acl->a_flags & RICHACL_WRITE_THROUGH) &&
> +	      (acl->a_flags & RICHACL_MASKED)))
> +		return 0;

By the way, I think this is only called after checking MASKED--so the
MASKED check here could be a WARN_ON, or could just be dropped.  Ditto
for the following patch.

--b.

> +
> +	if (acl->a_count == 0 ||
> +	    !richace_is_everyone(ace) ||
> +	    richace_is_inherit_only(ace)) {
> +		ace++;
> +		if (richacl_insert_entry(alloc, &ace))
> +			return -1;
> +		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +		ace->e_flags = RICHACE_SPECIAL_WHO;
> +		ace->e_mask = other_mask;
> +		ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
> +	} else
> +		richace_change_mask(alloc, &ace, other_mask);
> +	return 0;
> +}
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 24/41] richacl: Set the other permissions to the other mask
  2015-09-23 14:03     ` J. Bruce Fields
@ 2015-09-23 14:12           ` Andreas Grünbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Grünbacher @ 2015-09-23 14:12 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: Andreas Gruenbacher, Linux Kernel Mailing List,
	Linux FS-devel Mailing List, Linux NFS Mailing List,
	Linux API Mailing List, linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	LSM List

2015-09-23 16:03 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> On Sat, Sep 05, 2015 at 12:27:19PM +0200, Andreas Gruenbacher wrote:
>> +static int
>> +richacl_set_other_permissions(struct richacl_alloc *alloc)
>> +{
>> +     struct richacl *acl = alloc->acl;
>> +     unsigned int x = RICHACE_POSIX_ALWAYS_ALLOWED;
>> +     unsigned int other_mask = acl->a_other_mask & ~x;
>> +     struct richace *ace = acl->a_entries + acl->a_count - 1;
>> +
>> +     if (!(other_mask &&
>> +           (acl->a_flags & RICHACL_WRITE_THROUGH) &&
>> +           (acl->a_flags & RICHACL_MASKED)))
>> +             return 0;
>
> By the way, I think this is only called after checking MASKED--so the
> MASKED check here could be a WARN_ON, or could just be dropped.  Ditto
> for the following patch.

Hmm, yes. I'll drop that.

Thanks,
Andreas
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 24/41] richacl: Set the other permissions to the other mask
@ 2015-09-23 14:12           ` Andreas Grünbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Grünbacher @ 2015-09-23 14:12 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: Andreas Gruenbacher, Linux Kernel Mailing List,
	Linux FS-devel Mailing List, Linux NFS Mailing List,
	Linux API Mailing List, linux-cifs, LSM List

2015-09-23 16:03 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> On Sat, Sep 05, 2015 at 12:27:19PM +0200, Andreas Gruenbacher wrote:
>> +static int
>> +richacl_set_other_permissions(struct richacl_alloc *alloc)
>> +{
>> +     struct richacl *acl = alloc->acl;
>> +     unsigned int x = RICHACE_POSIX_ALWAYS_ALLOWED;
>> +     unsigned int other_mask = acl->a_other_mask & ~x;
>> +     struct richace *ace = acl->a_entries + acl->a_count - 1;
>> +
>> +     if (!(other_mask &&
>> +           (acl->a_flags & RICHACL_WRITE_THROUGH) &&
>> +           (acl->a_flags & RICHACL_MASKED)))
>> +             return 0;
>
> By the way, I think this is only called after checking MASKED--so the
> MASKED check here could be a WARN_ON, or could just be dropped.  Ditto
> for the following patch.

Hmm, yes. I'll drop that.

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 26/41] richacl: Apply the file masks to a richacl
  2015-09-22 19:11     ` J. Bruce Fields
@ 2015-09-23 19:18           ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-23 19:18 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

On Tue, Sep 22, 2015 at 03:11:08PM -0400, bfields wrote:
> On Sat, Sep 05, 2015 at 12:27:21PM +0200, Andreas Gruenbacher wrote:
> > Put all the pieces of the acl transformation puzzle together for
> > computing a richacl which has the file masks "applied" so that the
> > standard nfsv4 access check algorithm can be used on the richacl.
> > 
> > Signed-off-by: Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> > ---
> >  fs/richacl_compat.c     | 110 ++++++++++++++++++++++++++++++++++++++++++++++++
> >  include/linux/richacl.h |   3 ++
> >  2 files changed, 113 insertions(+)
> > 
> > diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> > index 412844c..9681efe 100644
> > --- a/fs/richacl_compat.c
> > +++ b/fs/richacl_compat.c
> > @@ -730,3 +730,113 @@ richacl_isolate_group_class(struct richacl_alloc *alloc)
> >  	}
> >  	return 0;
> >  }
> > +
> > +/**
> > + * __richacl_apply_masks  -  apply the file masks to all aces
> > + * @alloc:	acl and number of allocated entries
> > + *
> > + * Apply the owner mask to owner@ aces, the other mask to
> > + * everyone@ aces, and the group mask to all other aces.
> > + *
> > + * The previous transformations have brought the acl into a
> > + * form in which applying the masks will not lead to the
> > + * accidental loss of permissions anymore.
> > + */
> > +static int
> > +__richacl_apply_masks(struct richacl_alloc *alloc, kuid_t owner)
> > +{
> > +	struct richace *ace;
> > +
> > +	richacl_for_each_entry(ace, alloc->acl) {
> > +		unsigned int mask;
> > +
> > +		if (richace_is_inherit_only(ace) || !richace_is_allow(ace))
> > +			continue;
> > +		if (richace_is_owner(ace) ||
> > +		    (richace_is_unix_user(ace) && uid_eq(owner, ace->e_id.uid)))
> > +			mask = alloc->acl->a_owner_mask;
> 
> Is treating matching user aces like owner aces what you intended to do,
> and if so, why?

That does look wrong to me; in an example like:

	file owner bfields
	mask 0700, not WRITE_THROUGH
	bfields:rwx::allow

The permission algorithm grants nothing to anyone, but it looks to me
like richacl_apply_masks just leaves this as

	bfields:rwx::allow

but it would give the right result (an empty/deny-all ACL) if it weren't
for this odd case here.

--b.

> 
> --b.
> 
> > +		else if (richace_is_everyone(ace))
> > +			mask = alloc->acl->a_other_mask;
> > +		else
> > +			mask = alloc->acl->a_group_mask;
> > +		if (richace_change_mask(alloc, &ace, ace->e_mask & mask))
> > +			return -1;
> > +	}
> > +	return 0;
> > +}
> > +
> > +/**
> > + * richacl_apply_masks  -  apply the masks to the acl
> > + *
> > + * Transform @acl so that the standard NFSv4 permission check algorithm (which
> > + * is not aware of file masks) will compute the same access decisions as the
> > + * richacl permission check algorithm (which looks at the acl and the file
> > + * masks).
> > + *
> > + * This algorithm is split into several steps:
> > + *
> > + *   - Move everyone@ aces to the end of the acl.  This simplifies the other
> > + *     transformations, and allows the everyone@ allow ace at the end of the
> > + *     acl to eventually allow permissions to the other class only.
> > + *
> > + *   - Propagate everyone@ permissions up the acl.  This transformation makes
> > + *     sure that the owner and group class aces won't lose any permissions when
> > + *     we apply the other mask to the everyone@ allow ace at the end of the acl.
> > + *
> > + *   - Apply the file masks to all aces.
> > + *
> > + *   - Make sure the owner is granted the owner mask permissions.
> > + *
> > + *   - Make sure everyone is granted the other mask permissions.
> > + *
> > + *   - Make sure that the owner is not granted any permissions beyond the owner
> > + *     mask from group class aces or from everyone@.
> > + *
> > + *   - Make sure that the group class is not granted any permissions from
> > + *     everyone@.
> > + *
> > + * The algorithm is exact except for richacls which cannot be represented as an
> > + * acl alone: for example, given this acl:
> > + *
> > + *    group@:rw::allow
> > + *
> > + * when file masks corresponding to mode 0600 are applied, the owner would only
> > + * get rw access if he is a member of the owning group.  This algorithm would
> > + * produce an empty acl in this case.  We fix this case by modifying
> > + * richacl_permission() so that the group mask is always applied to group class
> > + * aces.  With this fix, the owner would not have any access (beyond the
> > + * implicit permissions always granted to owners).
> > + *
> > + * NOTE: Depending on the acl and file masks, this algorithm can increase the
> > + * number of aces by almost a factor of three in the worst case. This may make
> > + * the acl too large for some purposes.
> > + */
> > +int
> > +richacl_apply_masks(struct richacl **acl, kuid_t owner)
> > +{
> > +	if ((*acl)->a_flags & RICHACL_MASKED) {
> > +		struct richacl_alloc alloc = {
> > +			.acl = richacl_clone(*acl, GFP_KERNEL),
> > +			.count = (*acl)->a_count,
> > +		};
> > +		if (!alloc.acl)
> > +			return -ENOMEM;
> > +
> > +		if (richacl_move_everyone_aces_down(&alloc) ||
> > +		    richacl_propagate_everyone(&alloc) ||
> > +		    __richacl_apply_masks(&alloc, owner) ||
> > +		    richacl_set_owner_permissions(&alloc) ||
> > +		    richacl_set_other_permissions(&alloc) ||
> > +		    richacl_isolate_owner_class(&alloc) ||
> > +		    richacl_isolate_group_class(&alloc)) {
> > +			richacl_put(alloc.acl);
> > +			return -ENOMEM;
> > +		}
> > +
> > +		alloc.acl->a_flags &= ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED);
> > +		richacl_put(*acl);
> > +		*acl = alloc.acl;
> > +	}
> > +	return 0;
> > +}
> > +EXPORT_SYMBOL_GPL(richacl_apply_masks);
> > diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> > index 832b06c..a945f3c 100644
> > --- a/include/linux/richacl.h
> > +++ b/include/linux/richacl.h
> > @@ -332,4 +332,7 @@ extern struct richacl *richacl_inherit(const struct richacl *, int);
> >  extern int richacl_permission(struct inode *, const struct richacl *, int);
> >  extern struct richacl *richacl_create(struct inode *, struct inode *);
> >  
> > +/* richacl_compat.c */
> > +extern int richacl_apply_masks(struct richacl **, kuid_t);
> > +
> >  #endif /* __RICHACL_H */
> > -- 
> > 2.4.3
> > 
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> > the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 26/41] richacl: Apply the file masks to a richacl
@ 2015-09-23 19:18           ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-23 19:18 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

On Tue, Sep 22, 2015 at 03:11:08PM -0400, bfields wrote:
> On Sat, Sep 05, 2015 at 12:27:21PM +0200, Andreas Gruenbacher wrote:
> > Put all the pieces of the acl transformation puzzle together for
> > computing a richacl which has the file masks "applied" so that the
> > standard nfsv4 access check algorithm can be used on the richacl.
> > 
> > Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
> > ---
> >  fs/richacl_compat.c     | 110 ++++++++++++++++++++++++++++++++++++++++++++++++
> >  include/linux/richacl.h |   3 ++
> >  2 files changed, 113 insertions(+)
> > 
> > diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> > index 412844c..9681efe 100644
> > --- a/fs/richacl_compat.c
> > +++ b/fs/richacl_compat.c
> > @@ -730,3 +730,113 @@ richacl_isolate_group_class(struct richacl_alloc *alloc)
> >  	}
> >  	return 0;
> >  }
> > +
> > +/**
> > + * __richacl_apply_masks  -  apply the file masks to all aces
> > + * @alloc:	acl and number of allocated entries
> > + *
> > + * Apply the owner mask to owner@ aces, the other mask to
> > + * everyone@ aces, and the group mask to all other aces.
> > + *
> > + * The previous transformations have brought the acl into a
> > + * form in which applying the masks will not lead to the
> > + * accidental loss of permissions anymore.
> > + */
> > +static int
> > +__richacl_apply_masks(struct richacl_alloc *alloc, kuid_t owner)
> > +{
> > +	struct richace *ace;
> > +
> > +	richacl_for_each_entry(ace, alloc->acl) {
> > +		unsigned int mask;
> > +
> > +		if (richace_is_inherit_only(ace) || !richace_is_allow(ace))
> > +			continue;
> > +		if (richace_is_owner(ace) ||
> > +		    (richace_is_unix_user(ace) && uid_eq(owner, ace->e_id.uid)))
> > +			mask = alloc->acl->a_owner_mask;
> 
> Is treating matching user aces like owner aces what you intended to do,
> and if so, why?

That does look wrong to me; in an example like:

	file owner bfields
	mask 0700, not WRITE_THROUGH
	bfields:rwx::allow

The permission algorithm grants nothing to anyone, but it looks to me
like richacl_apply_masks just leaves this as

	bfields:rwx::allow

but it would give the right result (an empty/deny-all ACL) if it weren't
for this odd case here.

--b.

> 
> --b.
> 
> > +		else if (richace_is_everyone(ace))
> > +			mask = alloc->acl->a_other_mask;
> > +		else
> > +			mask = alloc->acl->a_group_mask;
> > +		if (richace_change_mask(alloc, &ace, ace->e_mask & mask))
> > +			return -1;
> > +	}
> > +	return 0;
> > +}
> > +
> > +/**
> > + * richacl_apply_masks  -  apply the masks to the acl
> > + *
> > + * Transform @acl so that the standard NFSv4 permission check algorithm (which
> > + * is not aware of file masks) will compute the same access decisions as the
> > + * richacl permission check algorithm (which looks at the acl and the file
> > + * masks).
> > + *
> > + * This algorithm is split into several steps:
> > + *
> > + *   - Move everyone@ aces to the end of the acl.  This simplifies the other
> > + *     transformations, and allows the everyone@ allow ace at the end of the
> > + *     acl to eventually allow permissions to the other class only.
> > + *
> > + *   - Propagate everyone@ permissions up the acl.  This transformation makes
> > + *     sure that the owner and group class aces won't lose any permissions when
> > + *     we apply the other mask to the everyone@ allow ace at the end of the acl.
> > + *
> > + *   - Apply the file masks to all aces.
> > + *
> > + *   - Make sure the owner is granted the owner mask permissions.
> > + *
> > + *   - Make sure everyone is granted the other mask permissions.
> > + *
> > + *   - Make sure that the owner is not granted any permissions beyond the owner
> > + *     mask from group class aces or from everyone@.
> > + *
> > + *   - Make sure that the group class is not granted any permissions from
> > + *     everyone@.
> > + *
> > + * The algorithm is exact except for richacls which cannot be represented as an
> > + * acl alone: for example, given this acl:
> > + *
> > + *    group@:rw::allow
> > + *
> > + * when file masks corresponding to mode 0600 are applied, the owner would only
> > + * get rw access if he is a member of the owning group.  This algorithm would
> > + * produce an empty acl in this case.  We fix this case by modifying
> > + * richacl_permission() so that the group mask is always applied to group class
> > + * aces.  With this fix, the owner would not have any access (beyond the
> > + * implicit permissions always granted to owners).
> > + *
> > + * NOTE: Depending on the acl and file masks, this algorithm can increase the
> > + * number of aces by almost a factor of three in the worst case. This may make
> > + * the acl too large for some purposes.
> > + */
> > +int
> > +richacl_apply_masks(struct richacl **acl, kuid_t owner)
> > +{
> > +	if ((*acl)->a_flags & RICHACL_MASKED) {
> > +		struct richacl_alloc alloc = {
> > +			.acl = richacl_clone(*acl, GFP_KERNEL),
> > +			.count = (*acl)->a_count,
> > +		};
> > +		if (!alloc.acl)
> > +			return -ENOMEM;
> > +
> > +		if (richacl_move_everyone_aces_down(&alloc) ||
> > +		    richacl_propagate_everyone(&alloc) ||
> > +		    __richacl_apply_masks(&alloc, owner) ||
> > +		    richacl_set_owner_permissions(&alloc) ||
> > +		    richacl_set_other_permissions(&alloc) ||
> > +		    richacl_isolate_owner_class(&alloc) ||
> > +		    richacl_isolate_group_class(&alloc)) {
> > +			richacl_put(alloc.acl);
> > +			return -ENOMEM;
> > +		}
> > +
> > +		alloc.acl->a_flags &= ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED);
> > +		richacl_put(*acl);
> > +		*acl = alloc.acl;
> > +	}
> > +	return 0;
> > +}
> > +EXPORT_SYMBOL_GPL(richacl_apply_masks);
> > diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> > index 832b06c..a945f3c 100644
> > --- a/include/linux/richacl.h
> > +++ b/include/linux/richacl.h
> > @@ -332,4 +332,7 @@ extern struct richacl *richacl_inherit(const struct richacl *, int);
> >  extern int richacl_permission(struct inode *, const struct richacl *, int);
> >  extern struct richacl *richacl_create(struct inode *, struct inode *);
> >  
> > +/* richacl_compat.c */
> > +extern int richacl_apply_masks(struct richacl **, kuid_t);
> > +
> >  #endif /* __RICHACL_H */
> > -- 
> > 2.4.3
> > 
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 27/41] richacl: Create richacl from mode values
  2015-09-05 10:27     ` Andreas Gruenbacher
@ 2015-09-23 20:11         ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-23 20:11 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

On Sat, Sep 05, 2015 at 12:27:22PM +0200, Andreas Gruenbacher wrote:
> A file can have "no acl" in the sense that only the file mode permission
> bits determine access.  In that case, the getxattr system call fails with
> errno == ENODATA (No such attribute).
> 
> Over the NFSv4 protocol, a file always has an acl, and we convert the file
> mode permission bits into an equivalent acl with richacl_from_mode.  Such
> "trivial" acls can be converted back to a file mode with
> richacl_equiv_mode.

Reviewed-by: J. Bruce Fields <bfields-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>

> 
> Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> ---
>  fs/richacl_compat.c     | 88 +++++++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/richacl.h |  1 +
>  2 files changed, 89 insertions(+)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 9681efe..632409f 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -840,3 +840,91 @@ richacl_apply_masks(struct richacl **acl, kuid_t owner)
>  	return 0;
>  }
>  EXPORT_SYMBOL_GPL(richacl_apply_masks);
> +
> +/**
> + * richacl_from_mode  -  create an acl which corresponds to @mode
> + *
> + * The resulting acl doesn't have the RICHACL_MASKED flag set.
> + *
> + * @mode:	file mode including the file type
> + */
> +struct richacl *
> +richacl_from_mode(mode_t mode)
> +{
> +	unsigned int owner_mask = richacl_mode_to_mask(mode >> 6);
> +	unsigned int group_mask = richacl_mode_to_mask(mode >> 3);
> +	unsigned int other_mask = richacl_mode_to_mask(mode);
> +	unsigned int denied;
> +	unsigned int entries = 0;
> +	struct richacl *acl;
> +	struct richace *ace;
> +
> +	/* RICHACE_DELETE_CHILD is meaningless for non-directories. */
> +	if (!S_ISDIR(mode)) {
> +		owner_mask &= ~RICHACE_DELETE_CHILD;
> +		group_mask &= ~RICHACE_DELETE_CHILD;
> +		other_mask &= ~RICHACE_DELETE_CHILD;
> +	}
> +
> +	denied = ~owner_mask & (group_mask | other_mask);
> +	if (denied)
> +		entries++;  /* owner@ deny entry needed */
> +	if (owner_mask & ~(group_mask & other_mask))
> +		entries++;  /* owner@ allow entry needed */
> +	denied = ~group_mask & other_mask;
> +	if (denied)
> +		entries++;  /* group@ deny entry needed */
> +	if (group_mask & ~other_mask)
> +		entries++;  /* group@ allow entry needed */
> +	if (other_mask)
> +		entries++;  /* everyone@ allow entry needed */
> +
> +	acl = richacl_alloc(entries, GFP_KERNEL);
> +	if (!acl)
> +		return NULL;
> +	acl->a_owner_mask = owner_mask;
> +	acl->a_group_mask = group_mask;
> +	acl->a_other_mask = other_mask;
> +	ace = acl->a_entries;
> +
> +	denied = ~owner_mask & (group_mask | other_mask);
> +	if (denied) {
> +		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +		ace->e_flags = RICHACE_SPECIAL_WHO;
> +		ace->e_mask = denied;
> +		ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
> +		ace++;
> +	}
> +	if (owner_mask & ~(group_mask & other_mask)) {
> +		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +		ace->e_flags = RICHACE_SPECIAL_WHO;
> +		ace->e_mask = owner_mask;
> +		ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
> +		ace++;
> +	}
> +	denied = ~group_mask & other_mask;
> +	if (denied) {
> +		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +		ace->e_flags = RICHACE_SPECIAL_WHO;
> +		ace->e_mask = denied;
> +		ace->e_id.special = RICHACE_GROUP_SPECIAL_ID;
> +		ace++;
> +	}
> +	if (group_mask & ~other_mask) {
> +		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +		ace->e_flags = RICHACE_SPECIAL_WHO;
> +		ace->e_mask = group_mask;
> +		ace->e_id.special = RICHACE_GROUP_SPECIAL_ID;
> +		ace++;
> +	}
> +	if (other_mask) {
> +		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +		ace->e_flags = RICHACE_SPECIAL_WHO;
> +		ace->e_mask = other_mask;
> +		ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
> +		ace++;
> +	}
> +
> +	return acl;
> +}
> +EXPORT_SYMBOL_GPL(richacl_from_mode);
> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> index a945f3c..f48c04e 100644
> --- a/include/linux/richacl.h
> +++ b/include/linux/richacl.h
> @@ -334,5 +334,6 @@ extern struct richacl *richacl_create(struct inode *, struct inode *);
>  
>  /* richacl_compat.c */
>  extern int richacl_apply_masks(struct richacl **, kuid_t);
> +extern struct richacl *richacl_from_mode(mode_t);
>  
>  #endif /* __RICHACL_H */
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 27/41] richacl: Create richacl from mode values
@ 2015-09-23 20:11         ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-23 20:11 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Sat, Sep 05, 2015 at 12:27:22PM +0200, Andreas Gruenbacher wrote:
> A file can have "no acl" in the sense that only the file mode permission
> bits determine access.  In that case, the getxattr system call fails with
> errno == ENODATA (No such attribute).
> 
> Over the NFSv4 protocol, a file always has an acl, and we convert the file
> mode permission bits into an equivalent acl with richacl_from_mode.  Such
> "trivial" acls can be converted back to a file mode with
> richacl_equiv_mode.

Reviewed-by: J. Bruce Fields <bfields@redhat.com>

> 
> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> ---
>  fs/richacl_compat.c     | 88 +++++++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/richacl.h |  1 +
>  2 files changed, 89 insertions(+)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 9681efe..632409f 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -840,3 +840,91 @@ richacl_apply_masks(struct richacl **acl, kuid_t owner)
>  	return 0;
>  }
>  EXPORT_SYMBOL_GPL(richacl_apply_masks);
> +
> +/**
> + * richacl_from_mode  -  create an acl which corresponds to @mode
> + *
> + * The resulting acl doesn't have the RICHACL_MASKED flag set.
> + *
> + * @mode:	file mode including the file type
> + */
> +struct richacl *
> +richacl_from_mode(mode_t mode)
> +{
> +	unsigned int owner_mask = richacl_mode_to_mask(mode >> 6);
> +	unsigned int group_mask = richacl_mode_to_mask(mode >> 3);
> +	unsigned int other_mask = richacl_mode_to_mask(mode);
> +	unsigned int denied;
> +	unsigned int entries = 0;
> +	struct richacl *acl;
> +	struct richace *ace;
> +
> +	/* RICHACE_DELETE_CHILD is meaningless for non-directories. */
> +	if (!S_ISDIR(mode)) {
> +		owner_mask &= ~RICHACE_DELETE_CHILD;
> +		group_mask &= ~RICHACE_DELETE_CHILD;
> +		other_mask &= ~RICHACE_DELETE_CHILD;
> +	}
> +
> +	denied = ~owner_mask & (group_mask | other_mask);
> +	if (denied)
> +		entries++;  /* owner@ deny entry needed */
> +	if (owner_mask & ~(group_mask & other_mask))
> +		entries++;  /* owner@ allow entry needed */
> +	denied = ~group_mask & other_mask;
> +	if (denied)
> +		entries++;  /* group@ deny entry needed */
> +	if (group_mask & ~other_mask)
> +		entries++;  /* group@ allow entry needed */
> +	if (other_mask)
> +		entries++;  /* everyone@ allow entry needed */
> +
> +	acl = richacl_alloc(entries, GFP_KERNEL);
> +	if (!acl)
> +		return NULL;
> +	acl->a_owner_mask = owner_mask;
> +	acl->a_group_mask = group_mask;
> +	acl->a_other_mask = other_mask;
> +	ace = acl->a_entries;
> +
> +	denied = ~owner_mask & (group_mask | other_mask);
> +	if (denied) {
> +		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +		ace->e_flags = RICHACE_SPECIAL_WHO;
> +		ace->e_mask = denied;
> +		ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
> +		ace++;
> +	}
> +	if (owner_mask & ~(group_mask & other_mask)) {
> +		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +		ace->e_flags = RICHACE_SPECIAL_WHO;
> +		ace->e_mask = owner_mask;
> +		ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
> +		ace++;
> +	}
> +	denied = ~group_mask & other_mask;
> +	if (denied) {
> +		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +		ace->e_flags = RICHACE_SPECIAL_WHO;
> +		ace->e_mask = denied;
> +		ace->e_id.special = RICHACE_GROUP_SPECIAL_ID;
> +		ace++;
> +	}
> +	if (group_mask & ~other_mask) {
> +		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +		ace->e_flags = RICHACE_SPECIAL_WHO;
> +		ace->e_mask = group_mask;
> +		ace->e_id.special = RICHACE_GROUP_SPECIAL_ID;
> +		ace++;
> +	}
> +	if (other_mask) {
> +		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +		ace->e_flags = RICHACE_SPECIAL_WHO;
> +		ace->e_mask = other_mask;
> +		ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
> +		ace++;
> +	}
> +
> +	return acl;
> +}
> +EXPORT_SYMBOL_GPL(richacl_from_mode);
> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> index a945f3c..f48c04e 100644
> --- a/include/linux/richacl.h
> +++ b/include/linux/richacl.h
> @@ -334,5 +334,6 @@ extern struct richacl *richacl_create(struct inode *, struct inode *);
>  
>  /* richacl_compat.c */
>  extern int richacl_apply_masks(struct richacl **, kuid_t);
> +extern struct richacl *richacl_from_mode(mode_t);
>  
>  #endif /* __RICHACL_H */
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 28/41] nfsd: Keep list of acls to dispose of in compoundargs
  2015-09-05 10:27     ` Andreas Gruenbacher
@ 2015-09-23 20:28         ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-23 20:28 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

On Sat, Sep 05, 2015 at 12:27:23PM +0200, Andreas Gruenbacher wrote:
> We will decode acls in requests into richacls.  Even if unlikely, there
> can be more than one acl in a single request; those richacls need to be
> richacl_put() at the end of the request instead of kfree()d, so keep a
> list of acls in compoundargs for that.

Looks fine, ACK.--b.

> 
> Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> ---
>  fs/nfsd/nfs4xdr.c | 27 +++++++++++++++++++++++++++
>  fs/nfsd/xdr4.h    |  6 ++++++
>  2 files changed, 33 insertions(+)
> 
> diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
> index 75e0563..0768251 100644
> --- a/fs/nfsd/nfs4xdr.c
> +++ b/fs/nfsd/nfs4xdr.c
> @@ -40,6 +40,7 @@
>  #include <linux/utsname.h>
>  #include <linux/pagemap.h>
>  #include <linux/sunrpc/svcauth_gss.h>
> +#include <linux/richacl.h>
>  
>  #include "idmap.h"
>  #include "acl.h"
> @@ -196,6 +197,24 @@ svcxdr_tmpalloc(struct nfsd4_compoundargs *argp, u32 len)
>  	return tb->buf;
>  }
>  
> +static struct richacl *
> +svcxdr_alloc_richacl(struct nfsd4_compoundargs *argp, u32 nace)
> +{
> +	struct svcxdr_richacl *acls;
> +
> +	acls = kmalloc(sizeof(*acls), GFP_KERNEL);
> +	if (!acls)
> +		return NULL;
> +	acls->acl = richacl_alloc(nace, GFP_KERNEL);
> +	if (!acls->acl) {
> +		kfree(acls);
> +		return NULL;
> +	}
> +	acls->next = argp->acls;
> +	argp->acls = acls;
> +	return acls->acl;
> +}
> +
>  /*
>   * For xdr strings that need to be passed to other kernel api's
>   * as null-terminated strings.
> @@ -4395,6 +4414,13 @@ int nfsd4_release_compoundargs(void *rq, __be32 *p, void *resp)
>  		args->to_free = tb->next;
>  		kfree(tb);
>  	}
> +	while (args->acls) {
> +		struct svcxdr_richacl *acls = args->acls;
> +
> +		args->acls = acls->next;
> +		richacl_put(acls->acl);
> +		kfree(acls);
> +	}
>  	return 1;
>  }
>  
> @@ -4413,6 +4439,7 @@ nfs4svc_decode_compoundargs(struct svc_rqst *rqstp, __be32 *p, struct nfsd4_comp
>  	args->pagelen = rqstp->rq_arg.page_len;
>  	args->tmpp = NULL;
>  	args->to_free = NULL;
> +	args->acls = NULL;
>  	args->ops = args->iops;
>  	args->rqstp = rqstp;
>  
> diff --git a/fs/nfsd/xdr4.h b/fs/nfsd/xdr4.h
> index 9f99100..b698585 100644
> --- a/fs/nfsd/xdr4.h
> +++ b/fs/nfsd/xdr4.h
> @@ -570,6 +570,11 @@ struct svcxdr_tmpbuf {
>  	char buf[];
>  };
>  
> +struct svcxdr_richacl {
> +	struct svcxdr_richacl *next;
> +	struct richacl *acl;
> +};
> +
>  struct nfsd4_compoundargs {
>  	/* scratch variables for XDR decode */
>  	__be32 *			p;
> @@ -579,6 +584,7 @@ struct nfsd4_compoundargs {
>  	__be32				tmp[8];
>  	__be32 *			tmpp;
>  	struct svcxdr_tmpbuf		*to_free;
> +	struct svcxdr_richacl		*acls;
>  
>  	struct svc_rqst			*rqstp;
>  
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 28/41] nfsd: Keep list of acls to dispose of in compoundargs
@ 2015-09-23 20:28         ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-23 20:28 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Sat, Sep 05, 2015 at 12:27:23PM +0200, Andreas Gruenbacher wrote:
> We will decode acls in requests into richacls.  Even if unlikely, there
> can be more than one acl in a single request; those richacls need to be
> richacl_put() at the end of the request instead of kfree()d, so keep a
> list of acls in compoundargs for that.

Looks fine, ACK.--b.

> 
> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> ---
>  fs/nfsd/nfs4xdr.c | 27 +++++++++++++++++++++++++++
>  fs/nfsd/xdr4.h    |  6 ++++++
>  2 files changed, 33 insertions(+)
> 
> diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
> index 75e0563..0768251 100644
> --- a/fs/nfsd/nfs4xdr.c
> +++ b/fs/nfsd/nfs4xdr.c
> @@ -40,6 +40,7 @@
>  #include <linux/utsname.h>
>  #include <linux/pagemap.h>
>  #include <linux/sunrpc/svcauth_gss.h>
> +#include <linux/richacl.h>
>  
>  #include "idmap.h"
>  #include "acl.h"
> @@ -196,6 +197,24 @@ svcxdr_tmpalloc(struct nfsd4_compoundargs *argp, u32 len)
>  	return tb->buf;
>  }
>  
> +static struct richacl *
> +svcxdr_alloc_richacl(struct nfsd4_compoundargs *argp, u32 nace)
> +{
> +	struct svcxdr_richacl *acls;
> +
> +	acls = kmalloc(sizeof(*acls), GFP_KERNEL);
> +	if (!acls)
> +		return NULL;
> +	acls->acl = richacl_alloc(nace, GFP_KERNEL);
> +	if (!acls->acl) {
> +		kfree(acls);
> +		return NULL;
> +	}
> +	acls->next = argp->acls;
> +	argp->acls = acls;
> +	return acls->acl;
> +}
> +
>  /*
>   * For xdr strings that need to be passed to other kernel api's
>   * as null-terminated strings.
> @@ -4395,6 +4414,13 @@ int nfsd4_release_compoundargs(void *rq, __be32 *p, void *resp)
>  		args->to_free = tb->next;
>  		kfree(tb);
>  	}
> +	while (args->acls) {
> +		struct svcxdr_richacl *acls = args->acls;
> +
> +		args->acls = acls->next;
> +		richacl_put(acls->acl);
> +		kfree(acls);
> +	}
>  	return 1;
>  }
>  
> @@ -4413,6 +4439,7 @@ nfs4svc_decode_compoundargs(struct svc_rqst *rqstp, __be32 *p, struct nfsd4_comp
>  	args->pagelen = rqstp->rq_arg.page_len;
>  	args->tmpp = NULL;
>  	args->to_free = NULL;
> +	args->acls = NULL;
>  	args->ops = args->iops;
>  	args->rqstp = rqstp;
>  
> diff --git a/fs/nfsd/xdr4.h b/fs/nfsd/xdr4.h
> index 9f99100..b698585 100644
> --- a/fs/nfsd/xdr4.h
> +++ b/fs/nfsd/xdr4.h
> @@ -570,6 +570,11 @@ struct svcxdr_tmpbuf {
>  	char buf[];
>  };
>  
> +struct svcxdr_richacl {
> +	struct svcxdr_richacl *next;
> +	struct richacl *acl;
> +};
> +
>  struct nfsd4_compoundargs {
>  	/* scratch variables for XDR decode */
>  	__be32 *			p;
> @@ -579,6 +584,7 @@ struct nfsd4_compoundargs {
>  	__be32				tmp[8];
>  	__be32 *			tmpp;
>  	struct svcxdr_tmpbuf		*to_free;
> +	struct svcxdr_richacl		*acls;
>  
>  	struct svc_rqst			*rqstp;
>  
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 26/41] richacl: Apply the file masks to a richacl
  2015-09-23 19:18           ` J. Bruce Fields
@ 2015-09-23 20:29               ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-23 20:29 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: LKML, linux-fsdevel, linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

2015-09-23 21:18 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> On Tue, Sep 22, 2015 at 03:11:08PM -0400, bfields wrote:
>> user aces like owner aces what you intended to do,
>> and if so, why?
>
> That does look wrong to me; in an example like:
>
>         file owner bfields
>         mask 0700, not WRITE_THROUGH
>         bfields:rwx::allow
>
> The permission algorithm grants nothing to anyone, but it looks to me
> like richacl_apply_masks just leaves this as
>
>         bfields:rwx::allow
>
> but it would give the right result (an empty/deny-all ACL) if it weren't
> for this odd case here.

In POSIX ACLs, only the entry that best matches the process determines
the access permissions. For the file owner, this would always be the
"user::" entry, and such an entry always exists.

In richacls, permissions from multiple entries do accumulate; the
permission check algorithm does not pick a "best match". When bfields
owns a file and a "bfields:rwx::allow" entry exists, denying rwx
access to bfields would be very surprising. It makes more sense to put
user entries that match the current owner into the owner class, and
apply the owner mask instead of the group mask. This was working in an
earlier version but apparently broke at some point.

So the result that richacl_apply_masks computes here is correct, and
the permission check algorithm needs a little fix.

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 26/41] richacl: Apply the file masks to a richacl
@ 2015-09-23 20:29               ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-23 20:29 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: LKML, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

2015-09-23 21:18 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> On Tue, Sep 22, 2015 at 03:11:08PM -0400, bfields wrote:
>> user aces like owner aces what you intended to do,
>> and if so, why?
>
> That does look wrong to me; in an example like:
>
>         file owner bfields
>         mask 0700, not WRITE_THROUGH
>         bfields:rwx::allow
>
> The permission algorithm grants nothing to anyone, but it looks to me
> like richacl_apply_masks just leaves this as
>
>         bfields:rwx::allow
>
> but it would give the right result (an empty/deny-all ACL) if it weren't
> for this odd case here.

In POSIX ACLs, only the entry that best matches the process determines
the access permissions. For the file owner, this would always be the
"user::" entry, and such an entry always exists.

In richacls, permissions from multiple entries do accumulate; the
permission check algorithm does not pick a "best match". When bfields
owns a file and a "bfields:rwx::allow" entry exists, denying rwx
access to bfields would be very surprising. It makes more sense to put
user entries that match the current owner into the owner class, and
apply the owner mask instead of the group mask. This was working in an
earlier version but apparently broke at some point.

So the result that richacl_apply_masks computes here is correct, and
the permission check algorithm needs a little fix.

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 26/41] richacl: Apply the file masks to a richacl
  2015-09-23 20:29               ` Andreas Gruenbacher
@ 2015-09-23 20:33                   ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-23 20:33 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: LKML, linux-fsdevel, linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

On Wed, Sep 23, 2015 at 10:29:40PM +0200, Andreas Gruenbacher wrote:
> 2015-09-23 21:18 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> > On Tue, Sep 22, 2015 at 03:11:08PM -0400, bfields wrote:
> >> user aces like owner aces what you intended to do,
> >> and if so, why?
> >
> > That does look wrong to me; in an example like:
> >
> >         file owner bfields
> >         mask 0700, not WRITE_THROUGH
> >         bfields:rwx::allow
> >
> > The permission algorithm grants nothing to anyone, but it looks to me
> > like richacl_apply_masks just leaves this as
> >
> >         bfields:rwx::allow
> >
> > but it would give the right result (an empty/deny-all ACL) if it weren't
> > for this odd case here.
> 
> In POSIX ACLs, only the entry that best matches the process determines
> the access permissions. For the file owner, this would always be the
> "user::" entry, and such an entry always exists.
> 
> In richacls, permissions from multiple entries do accumulate; the
> permission check algorithm does not pick a "best match". When bfields
> owns a file and a "bfields:rwx::allow" entry exists, denying rwx
> access to bfields would be very surprising.

The same could be said if there's a group-i-belong-to:rwx::allow entry,
do we make that exception too?

--b.

> It makes more sense to put
> user entries that match the current owner into the owner class, and
> apply the owner mask instead of the group mask. This was working in an
> earlier version but apparently broke at some point.
> 
> So the result that richacl_apply_masks computes here is correct, and
> the permission check algorithm needs a little fix.
> 
> Thanks,
> Andreas
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 26/41] richacl: Apply the file masks to a richacl
@ 2015-09-23 20:33                   ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-23 20:33 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: LKML, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

On Wed, Sep 23, 2015 at 10:29:40PM +0200, Andreas Gruenbacher wrote:
> 2015-09-23 21:18 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> > On Tue, Sep 22, 2015 at 03:11:08PM -0400, bfields wrote:
> >> user aces like owner aces what you intended to do,
> >> and if so, why?
> >
> > That does look wrong to me; in an example like:
> >
> >         file owner bfields
> >         mask 0700, not WRITE_THROUGH
> >         bfields:rwx::allow
> >
> > The permission algorithm grants nothing to anyone, but it looks to me
> > like richacl_apply_masks just leaves this as
> >
> >         bfields:rwx::allow
> >
> > but it would give the right result (an empty/deny-all ACL) if it weren't
> > for this odd case here.
> 
> In POSIX ACLs, only the entry that best matches the process determines
> the access permissions. For the file owner, this would always be the
> "user::" entry, and such an entry always exists.
> 
> In richacls, permissions from multiple entries do accumulate; the
> permission check algorithm does not pick a "best match". When bfields
> owns a file and a "bfields:rwx::allow" entry exists, denying rwx
> access to bfields would be very surprising.

The same could be said if there's a group-i-belong-to:rwx::allow entry,
do we make that exception too?

--b.

> It makes more sense to put
> user entries that match the current owner into the owner class, and
> apply the owner mask instead of the group mask. This was working in an
> earlier version but apparently broke at some point.
> 
> So the result that richacl_apply_masks computes here is correct, and
> the permission check algorithm needs a little fix.
> 
> Thanks,
> Andreas
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 26/41] richacl: Apply the file masks to a richacl
  2015-09-23 20:33                   ` J. Bruce Fields
@ 2015-09-23 20:40                       ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-23 20:40 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: LKML, linux-fsdevel, linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

2015-09-23 22:33 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> The same could be said if there's a group-i-belong-to:rwx::allow entry,
> do we make that exception too?

We cannot because that would be incorrect for all other group members.

Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 26/41] richacl: Apply the file masks to a richacl
@ 2015-09-23 20:40                       ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-23 20:40 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: LKML, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

2015-09-23 22:33 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> The same could be said if there's a group-i-belong-to:rwx::allow entry,
> do we make that exception too?

We cannot because that would be incorrect for all other group members.

Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 26/41] richacl: Apply the file masks to a richacl
  2015-09-23 20:40                       ` Andreas Gruenbacher
  (?)
@ 2015-09-23 21:05                       ` J. Bruce Fields
       [not found]                         ` <20150923210531.GC29349-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
  -1 siblings, 1 reply; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-23 21:05 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: LKML, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

On Wed, Sep 23, 2015 at 10:40:18PM +0200, Andreas Gruenbacher wrote:
> 2015-09-23 22:33 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> > The same could be said if there's a group-i-belong-to:rwx::allow entry,
> > do we make that exception too?
> 
> We cannot because that would be incorrect for all other group members.

OK.  So people have to learn how the group mask works anyway, and now
they have to learn a special exception to that rule.

I don't like having this exception.  Or making the richacl->v4acl
translation dependent on the owner.

But I admit it's surprising to that an 0700 mask with
"bfields:rwx::allow" ACL denies access to a bfields-owned file.

--b.

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 26/41] richacl: Apply the file masks to a richacl
  2015-09-23 21:05                       ` J. Bruce Fields
@ 2015-09-23 22:14                             ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-23 22:14 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: LKML, linux-fsdevel, linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

2015-09-23 23:05 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> On Wed, Sep 23, 2015 at 10:40:18PM +0200, Andreas Gruenbacher wrote:
>> 2015-09-23 22:33 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
>> > The same could be said if there's a group-i-belong-to:rwx::allow entry,
>> > do we make that exception too?
>>
>> We cannot because that would be incorrect for all other group members.
>
> OK.  So people have to learn how the group mask works anyway, and now
> they have to learn a special exception to that rule.
>
> I don't like having this exception.  Or making the richacl->v4acl
> translation dependent on the owner.
>
> But I admit it's surprising to that an 0700 mask with
> "bfields:rwx::allow" ACL denies access to a bfields-owned file.

I fully understand your point. This kind of acl is one of the the
first things people will try, and nobody is going to accept when
access is denied in this case though.

Things are made worse by the fact that Windows has the concept of
owner@ or group@ entries for inheritable permissions but not for
effective ones; it will always produce and expect "bfields:rwx::allow"
type entries instead of "owner@:rwx::allow" type entries. I'm not sure
if Samba could bridge that gap.

The fact that we cannot handle entries for groups the owner is in in a
similar way is not a big deal; it's not surprising that changing the
group file mode permission bits affects group entries.

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 26/41] richacl: Apply the file masks to a richacl
@ 2015-09-23 22:14                             ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-23 22:14 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: LKML, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

2015-09-23 23:05 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> On Wed, Sep 23, 2015 at 10:40:18PM +0200, Andreas Gruenbacher wrote:
>> 2015-09-23 22:33 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
>> > The same could be said if there's a group-i-belong-to:rwx::allow entry,
>> > do we make that exception too?
>>
>> We cannot because that would be incorrect for all other group members.
>
> OK.  So people have to learn how the group mask works anyway, and now
> they have to learn a special exception to that rule.
>
> I don't like having this exception.  Or making the richacl->v4acl
> translation dependent on the owner.
>
> But I admit it's surprising to that an 0700 mask with
> "bfields:rwx::allow" ACL denies access to a bfields-owned file.

I fully understand your point. This kind of acl is one of the the
first things people will try, and nobody is going to accept when
access is denied in this case though.

Things are made worse by the fact that Windows has the concept of
owner@ or group@ entries for inheritable permissions but not for
effective ones; it will always produce and expect "bfields:rwx::allow"
type entries instead of "owner@:rwx::allow" type entries. I'm not sure
if Samba could bridge that gap.

The fact that we cannot handle entries for groups the owner is in in a
similar way is not a big deal; it's not surprising that changing the
group file mode permission bits affects group entries.

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 26/41] richacl: Apply the file masks to a richacl
  2015-09-23 22:14                             ` Andreas Gruenbacher
  (?)
@ 2015-09-24 15:28                             ` J. Bruce Fields
       [not found]                               ` <20150924152851.GC3823-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
  -1 siblings, 1 reply; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-24 15:28 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: LKML, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

On Thu, Sep 24, 2015 at 12:14:40AM +0200, Andreas Gruenbacher wrote:
> 2015-09-23 23:05 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> > On Wed, Sep 23, 2015 at 10:40:18PM +0200, Andreas Gruenbacher wrote:
> >> 2015-09-23 22:33 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> >> > The same could be said if there's a group-i-belong-to:rwx::allow entry,
> >> > do we make that exception too?
> >>
> >> We cannot because that would be incorrect for all other group members.
> >
> > OK.  So people have to learn how the group mask works anyway, and now
> > they have to learn a special exception to that rule.
> >
> > I don't like having this exception.  Or making the richacl->v4acl
> > translation dependent on the owner.
> >
> > But I admit it's surprising to that an 0700 mask with
> > "bfields:rwx::allow" ACL denies access to a bfields-owned file.
> 
> I fully understand your point. This kind of acl is one of the the
> first things people will try, and nobody is going to accept when
> access is denied in this case though.
> 
> Things are made worse by the fact that Windows has the concept of
> owner@ or group@ entries for inheritable permissions but not for
> effective ones; it will always produce and expect "bfields:rwx::allow"
> type entries instead of "owner@:rwx::allow" type entries. I'm not sure
> if Samba could bridge that gap.

I guess Samba's only choice on reading an ACL will be to split OWNER@
ACEs into inheritable and effective parts and then replace the "who" on
the latter by the current owner.


On writing do you think it should try to translate ACEs for users
matching the current owner to OWNER@ ACEs, or are you assuming it should
leave those untouched?

Sambas needs here seem most likely to be the determining factor, so I
just want to make sure I understand.

--b.

> The fact that we cannot handle entries for groups the owner is in in a
> similar way is not a big deal; it's not surprising that changing the
> group file mode permission bits affects group entries.
> 
> Thanks,
> Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 26/41] richacl: Apply the file masks to a richacl
  2015-09-24 15:28                             ` J. Bruce Fields
@ 2015-09-24 15:48                                   ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-24 15:48 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: LKML, linux-fsdevel, linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

2015-09-24 17:28 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> I guess Samba's only choice on reading an ACL will be to split OWNER@
> ACEs into inheritable and effective parts and then replace the "who" on
> the latter by the current owner.

Right, that's when translating from richacls to Windows ACLs.

> On writing do you think it should try to translate ACEs for users
> matching the current owner to OWNER@ ACEs, or are you assuming it should
> leave those untouched?

In the other direction, from Windows ACLs to richacls, Samba at least
shouldn't have to do that mapping. There may still be cases where it
wants to do that mapping though.

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 26/41] richacl: Apply the file masks to a richacl
@ 2015-09-24 15:48                                   ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-24 15:48 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: LKML, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

2015-09-24 17:28 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> I guess Samba's only choice on reading an ACL will be to split OWNER@
> ACEs into inheritable and effective parts and then replace the "who" on
> the latter by the current owner.

Right, that's when translating from richacls to Windows ACLs.

> On writing do you think it should try to translate ACEs for users
> matching the current owner to OWNER@ ACEs, or are you assuming it should
> leave those untouched?

In the other direction, from Windows ACLs to richacls, Samba at least
shouldn't have to do that mapping. There may still be cases where it
wants to do that mapping though.

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 26/41] richacl: Apply the file masks to a richacl
  2015-09-05 10:27     ` Andreas Gruenbacher
@ 2015-09-24 18:33         ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-24 18:33 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA,
	Andreas Gruenbacher

On Sat, Sep 05, 2015 at 12:27:21PM +0200, Andreas Gruenbacher wrote:
> Put all the pieces of the acl transformation puzzle together for
> computing a richacl which has the file masks "applied" so that the
> standard nfsv4 access check algorithm can be used on the richacl.
> 
> Signed-off-by: Andreas Gruenbacher <agruen-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
> ---
>  fs/richacl_compat.c     | 110 ++++++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/richacl.h |   3 ++
>  2 files changed, 113 insertions(+)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 412844c..9681efe 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -730,3 +730,113 @@ richacl_isolate_group_class(struct richacl_alloc *alloc)
>  	}
>  	return 0;
>  }
> +
> +/**
> + * __richacl_apply_masks  -  apply the file masks to all aces
> + * @alloc:	acl and number of allocated entries
> + *
> + * Apply the owner mask to owner@ aces, the other mask to
> + * everyone@ aces, and the group mask to all other aces.
> + *
> + * The previous transformations have brought the acl into a
> + * form in which applying the masks will not lead to the
> + * accidental loss of permissions anymore.
> + */
> +static int
> +__richacl_apply_masks(struct richacl_alloc *alloc, kuid_t owner)
> +{
> +	struct richace *ace;
> +
> +	richacl_for_each_entry(ace, alloc->acl) {
> +		unsigned int mask;
> +
> +		if (richace_is_inherit_only(ace) || !richace_is_allow(ace))
> +			continue;
> +		if (richace_is_owner(ace) ||
> +		    (richace_is_unix_user(ace) && uid_eq(owner, ace->e_id.uid)))
> +			mask = alloc->acl->a_owner_mask;
> +		else if (richace_is_everyone(ace))
> +			mask = alloc->acl->a_other_mask;
> +		else
> +			mask = alloc->acl->a_group_mask;
> +		if (richace_change_mask(alloc, &ace, ace->e_mask & mask))
> +			return -1;
> +	}
> +	return 0;
> +}
> +
> +/**
> + * richacl_apply_masks  -  apply the masks to the acl
> + *
> + * Transform @acl so that the standard NFSv4 permission check algorithm (which
> + * is not aware of file masks) will compute the same access decisions as the
> + * richacl permission check algorithm (which looks at the acl and the file
> + * masks).
> + *
> + * This algorithm is split into several steps:
> + *
> + *   - Move everyone@ aces to the end of the acl.  This simplifies the other
> + *     transformations, and allows the everyone@ allow ace at the end of the
> + *     acl to eventually allow permissions to the other class only.
> + *
> + *   - Propagate everyone@ permissions up the acl.  This transformation makes
> + *     sure that the owner and group class aces won't lose any permissions when
> + *     we apply the other mask to the everyone@ allow ace at the end of the acl.
> + *
> + *   - Apply the file masks to all aces.
> + *
> + *   - Make sure the owner is granted the owner mask permissions.
> + *
> + *   - Make sure everyone is granted the other mask permissions.
> + *
> + *   - Make sure that the owner is not granted any permissions beyond the owner
> + *     mask from group class aces or from everyone@.
> + *
> + *   - Make sure that the group class is not granted any permissions from
> + *     everyone@.
> + *
> + * The algorithm is exact except for richacls which cannot be represented as an
> + * acl alone: for example, given this acl:
> + *
> + *    group@:rw::allow
> + *
> + * when file masks corresponding to mode 0600 are applied, the owner would only
> + * get rw access if he is a member of the owning group.  This algorithm would
> + * produce an empty acl in this case.  We fix this case by modifying
> + * richacl_permission() so that the group mask is always applied to group class
> + * aces.  With this fix, the owner would not have any access (beyond the
> + * implicit permissions always granted to owners).
> + *
> + * NOTE: Depending on the acl and file masks, this algorithm can increase the
> + * number of aces by almost a factor of three in the worst case. This may make
> + * the acl too large for some purposes.
> + */
> +int
> +richacl_apply_masks(struct richacl **acl, kuid_t owner)
> +{
> +	if ((*acl)->a_flags & RICHACL_MASKED) {
> +		struct richacl_alloc alloc = {
> +			.acl = richacl_clone(*acl, GFP_KERNEL),
> +			.count = (*acl)->a_count,
> +		};
> +		if (!alloc.acl)
> +			return -ENOMEM;
> +
> +		if (richacl_move_everyone_aces_down(&alloc) ||
> +		    richacl_propagate_everyone(&alloc) ||
> +		    __richacl_apply_masks(&alloc, owner) ||
> +		    richacl_set_owner_permissions(&alloc) ||
> +		    richacl_set_other_permissions(&alloc) ||

Hm, I'm a little unsure about this step: it seems like the one step that
can actually change the permissions (making them more permissive, in
this case), whereas the others are neutral.

The following two steps should fix that, but I'm not sure they do.

E.g. if I have an ACL like

	mask 0777, WRITE_THROUGH
	GROUP@:r--::allow

I think it should result in

	OWNER@:   rwx::allow
	GROUP@:   -wx::deny
	GROUP@:   r--::allow
	EVERYONE@:rwx::allow

But does the GROUP@ deny actually get in there?  It looks to me like the
denies inserted by richacl_isolate_group_class only take into account
the group mask, not the actual permissions granted to any group class
user.

I may be mising something.

--b.

> +		    richacl_isolate_owner_class(&alloc) ||
> +		    richacl_isolate_group_class(&alloc)) {
> +			richacl_put(alloc.acl);
> +			return -ENOMEM;
> +		}
> +
> +		alloc.acl->a_flags &= ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED);
> +		richacl_put(*acl);
> +		*acl = alloc.acl;
> +	}
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(richacl_apply_masks);
> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> index 832b06c..a945f3c 100644
> --- a/include/linux/richacl.h
> +++ b/include/linux/richacl.h
> @@ -332,4 +332,7 @@ extern struct richacl *richacl_inherit(const struct richacl *, int);
>  extern int richacl_permission(struct inode *, const struct richacl *, int);
>  extern struct richacl *richacl_create(struct inode *, struct inode *);
>  
> +/* richacl_compat.c */
> +extern int richacl_apply_masks(struct richacl **, kuid_t);
> +
>  #endif /* __RICHACL_H */
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 26/41] richacl: Apply the file masks to a richacl
@ 2015-09-24 18:33         ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-24 18:33 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module, Andreas Gruenbacher

On Sat, Sep 05, 2015 at 12:27:21PM +0200, Andreas Gruenbacher wrote:
> Put all the pieces of the acl transformation puzzle together for
> computing a richacl which has the file masks "applied" so that the
> standard nfsv4 access check algorithm can be used on the richacl.
> 
> Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
> ---
>  fs/richacl_compat.c     | 110 ++++++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/richacl.h |   3 ++
>  2 files changed, 113 insertions(+)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 412844c..9681efe 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -730,3 +730,113 @@ richacl_isolate_group_class(struct richacl_alloc *alloc)
>  	}
>  	return 0;
>  }
> +
> +/**
> + * __richacl_apply_masks  -  apply the file masks to all aces
> + * @alloc:	acl and number of allocated entries
> + *
> + * Apply the owner mask to owner@ aces, the other mask to
> + * everyone@ aces, and the group mask to all other aces.
> + *
> + * The previous transformations have brought the acl into a
> + * form in which applying the masks will not lead to the
> + * accidental loss of permissions anymore.
> + */
> +static int
> +__richacl_apply_masks(struct richacl_alloc *alloc, kuid_t owner)
> +{
> +	struct richace *ace;
> +
> +	richacl_for_each_entry(ace, alloc->acl) {
> +		unsigned int mask;
> +
> +		if (richace_is_inherit_only(ace) || !richace_is_allow(ace))
> +			continue;
> +		if (richace_is_owner(ace) ||
> +		    (richace_is_unix_user(ace) && uid_eq(owner, ace->e_id.uid)))
> +			mask = alloc->acl->a_owner_mask;
> +		else if (richace_is_everyone(ace))
> +			mask = alloc->acl->a_other_mask;
> +		else
> +			mask = alloc->acl->a_group_mask;
> +		if (richace_change_mask(alloc, &ace, ace->e_mask & mask))
> +			return -1;
> +	}
> +	return 0;
> +}
> +
> +/**
> + * richacl_apply_masks  -  apply the masks to the acl
> + *
> + * Transform @acl so that the standard NFSv4 permission check algorithm (which
> + * is not aware of file masks) will compute the same access decisions as the
> + * richacl permission check algorithm (which looks at the acl and the file
> + * masks).
> + *
> + * This algorithm is split into several steps:
> + *
> + *   - Move everyone@ aces to the end of the acl.  This simplifies the other
> + *     transformations, and allows the everyone@ allow ace at the end of the
> + *     acl to eventually allow permissions to the other class only.
> + *
> + *   - Propagate everyone@ permissions up the acl.  This transformation makes
> + *     sure that the owner and group class aces won't lose any permissions when
> + *     we apply the other mask to the everyone@ allow ace at the end of the acl.
> + *
> + *   - Apply the file masks to all aces.
> + *
> + *   - Make sure the owner is granted the owner mask permissions.
> + *
> + *   - Make sure everyone is granted the other mask permissions.
> + *
> + *   - Make sure that the owner is not granted any permissions beyond the owner
> + *     mask from group class aces or from everyone@.
> + *
> + *   - Make sure that the group class is not granted any permissions from
> + *     everyone@.
> + *
> + * The algorithm is exact except for richacls which cannot be represented as an
> + * acl alone: for example, given this acl:
> + *
> + *    group@:rw::allow
> + *
> + * when file masks corresponding to mode 0600 are applied, the owner would only
> + * get rw access if he is a member of the owning group.  This algorithm would
> + * produce an empty acl in this case.  We fix this case by modifying
> + * richacl_permission() so that the group mask is always applied to group class
> + * aces.  With this fix, the owner would not have any access (beyond the
> + * implicit permissions always granted to owners).
> + *
> + * NOTE: Depending on the acl and file masks, this algorithm can increase the
> + * number of aces by almost a factor of three in the worst case. This may make
> + * the acl too large for some purposes.
> + */
> +int
> +richacl_apply_masks(struct richacl **acl, kuid_t owner)
> +{
> +	if ((*acl)->a_flags & RICHACL_MASKED) {
> +		struct richacl_alloc alloc = {
> +			.acl = richacl_clone(*acl, GFP_KERNEL),
> +			.count = (*acl)->a_count,
> +		};
> +		if (!alloc.acl)
> +			return -ENOMEM;
> +
> +		if (richacl_move_everyone_aces_down(&alloc) ||
> +		    richacl_propagate_everyone(&alloc) ||
> +		    __richacl_apply_masks(&alloc, owner) ||
> +		    richacl_set_owner_permissions(&alloc) ||
> +		    richacl_set_other_permissions(&alloc) ||

Hm, I'm a little unsure about this step: it seems like the one step that
can actually change the permissions (making them more permissive, in
this case), whereas the others are neutral.

The following two steps should fix that, but I'm not sure they do.

E.g. if I have an ACL like

	mask 0777, WRITE_THROUGH
	GROUP@:r--::allow

I think it should result in

	OWNER@:   rwx::allow
	GROUP@:   -wx::deny
	GROUP@:   r--::allow
	EVERYONE@:rwx::allow

But does the GROUP@ deny actually get in there?  It looks to me like the
denies inserted by richacl_isolate_group_class only take into account
the group mask, not the actual permissions granted to any group class
user.

I may be mising something.

--b.

> +		    richacl_isolate_owner_class(&alloc) ||
> +		    richacl_isolate_group_class(&alloc)) {
> +			richacl_put(alloc.acl);
> +			return -ENOMEM;
> +		}
> +
> +		alloc.acl->a_flags &= ~(RICHACL_WRITE_THROUGH | RICHACL_MASKED);
> +		richacl_put(*acl);
> +		*acl = alloc.acl;
> +	}
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(richacl_apply_masks);
> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> index 832b06c..a945f3c 100644
> --- a/include/linux/richacl.h
> +++ b/include/linux/richacl.h
> @@ -332,4 +332,7 @@ extern struct richacl *richacl_inherit(const struct richacl *, int);
>  extern int richacl_permission(struct inode *, const struct richacl *, int);
>  extern struct richacl *richacl_create(struct inode *, struct inode *);
>  
> +/* richacl_compat.c */
> +extern int richacl_apply_masks(struct richacl **, kuid_t);
> +
>  #endif /* __RICHACL_H */
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 29/41] nfsd: Use richacls as internal acl representation
  2015-09-05 10:27     ` Andreas Gruenbacher
@ 2015-09-24 19:29         ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-24 19:29 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

On Sat, Sep 05, 2015 at 12:27:24PM +0200, Andreas Gruenbacher wrote:
> When converting from NFSv4 ACLs to POSIX ACLs, nfsd so far was using
> struct nfs4_acl as its internal representation. This representation is a
> subset of richacls, so get rid of struct nfs4_acl. Richacls even have a
> more compact in-memory representation, so a few more ACL entries can
> easily be supported.

Looks good to me, ACK.

--b.

> 
> Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> ---
>  fs/Kconfig              |   6 +
>  fs/nfs_common/Makefile  |   1 +
>  fs/nfs_common/nfs4acl.c |  44 ++++++
>  fs/nfsd/Kconfig         |   1 +
>  fs/nfsd/acl.h           |  24 ++--
>  fs/nfsd/nfs4acl.c       | 367 ++++++++++++++++++++++--------------------------
>  fs/nfsd/nfs4proc.c      |  15 +-
>  fs/nfsd/nfs4xdr.c       |  64 +++------
>  fs/nfsd/xdr4.h          |   6 +-
>  include/linux/nfs4.h    |  23 ---
>  include/linux/nfs4acl.h |   7 +
>  11 files changed, 273 insertions(+), 285 deletions(-)
>  create mode 100644 fs/nfs_common/nfs4acl.c
>  create mode 100644 include/linux/nfs4acl.h
> 
> diff --git a/fs/Kconfig b/fs/Kconfig
> index 3e09c06..dd3f2d6 100644
> --- a/fs/Kconfig
> +++ b/fs/Kconfig
> @@ -268,6 +268,12 @@ config NFS_COMMON
>  	depends on NFSD || NFS_FS || LOCKD
>  	default y
>  
> +config NFS_RICHACL
> +	bool
> +	depends on NFSD_V4 || NFS_V4
> +	select FS_RICHACL
> +	default y
> +
>  source "net/sunrpc/Kconfig"
>  source "fs/ceph/Kconfig"
>  source "fs/cifs/Kconfig"
> diff --git a/fs/nfs_common/Makefile b/fs/nfs_common/Makefile
> index d153ca3..e055139 100644
> --- a/fs/nfs_common/Makefile
> +++ b/fs/nfs_common/Makefile
> @@ -4,5 +4,6 @@
>  
>  obj-$(CONFIG_NFS_ACL_SUPPORT) += nfs_acl.o
>  nfs_acl-objs := nfsacl.o
> +obj-$(CONFIG_NFS_RICHACL) += nfs4acl.o
>  
>  obj-$(CONFIG_GRACE_PERIOD) += grace.o
> diff --git a/fs/nfs_common/nfs4acl.c b/fs/nfs_common/nfs4acl.c
> new file mode 100644
> index 0000000..02df064
> --- /dev/null
> +++ b/fs/nfs_common/nfs4acl.c
> @@ -0,0 +1,44 @@
> +#include <linux/fs.h>
> +#include <linux/richacl.h>
> +#include <linux/nfs4acl.h>
> +
> +static struct special_id {
> +	char *who;
> +	int   len;
> +} special_who_map[] = {
> +	[RICHACE_OWNER_SPECIAL_ID] = {
> +		.who = "OWNER@",
> +		.len = sizeof("OWNER@") - 1 },
> +	[RICHACE_GROUP_SPECIAL_ID] = {
> +		.who = "GROUP@",
> +		.len = sizeof("GROUP@") - 1 },
> +	[RICHACE_EVERYONE_SPECIAL_ID] = {
> +		.who = "EVERYONE@",
> +		.len = sizeof("EVERYONE@") - 1 }
> +};
> +
> +int nfs4acl_who_to_special_id(const char *who, u32 len)
> +{
> +	int n;
> +
> +	for (n = 0; n < ARRAY_SIZE(special_who_map); n++) {
> +		if (len == special_who_map[n].len &&
> +		    !memcmp(who, special_who_map[n].who, len))
> +			return n;
> +	}
> +	return -1;
> +}
> +EXPORT_SYMBOL(nfs4acl_who_to_special_id);
> +
> +bool nfs4acl_special_id_to_who(unsigned int special_who,
> +			       const char **who, unsigned int *len)
> +{
> +	struct special_id *special = &special_who_map[special_who];
> +
> +	if (special_who > ARRAY_SIZE(special_who_map) || !special->len)
> +		return false;
> +	*who = special->who;
> +	*len = special->len;
> +	return true;
> +}
> +EXPORT_SYMBOL(nfs4acl_special_id_to_who);
> diff --git a/fs/nfsd/Kconfig b/fs/nfsd/Kconfig
> index a0b77fc..811379a 100644
> --- a/fs/nfsd/Kconfig
> +++ b/fs/nfsd/Kconfig
> @@ -70,6 +70,7 @@ config NFSD_V4
>  	depends on NFSD && PROC_FS
>  	select NFSD_V3
>  	select FS_POSIX_ACL
> +	select FS_RICHACL
>  	select SUNRPC_GSS
>  	select CRYPTO
>  	select GRACE_PERIOD
> diff --git a/fs/nfsd/acl.h b/fs/nfsd/acl.h
> index 4cd7c69..1c5deb5 100644
> --- a/fs/nfsd/acl.h
> +++ b/fs/nfsd/acl.h
> @@ -35,25 +35,27 @@
>  #ifndef LINUX_NFS4_ACL_H
>  #define LINUX_NFS4_ACL_H
>  
> -struct nfs4_acl;
> +struct richacl;
> +struct richace;
>  struct svc_fh;
>  struct svc_rqst;
>  
>  /*
>   * Maximum ACL we'll accept from a client; chosen (somewhat
>   * arbitrarily) so that kmalloc'ing the ACL shouldn't require a
> - * high-order allocation.  This allows 204 ACEs on x86_64:
> + * high-order allocation.  This allows 339 ACEs on x86_64:
>   */
> -#define NFS4_ACL_MAX ((PAGE_SIZE - sizeof(struct nfs4_acl)) \
> -			/ sizeof(struct nfs4_ace))
> +#define NFSD4_ACL_MAX ((PAGE_SIZE - sizeof(struct richacl)) \
> +			/ sizeof(struct richace))
>  
> -int nfs4_acl_bytes(int entries);
> -int nfs4_acl_get_whotype(char *, u32);
> -__be32 nfs4_acl_write_who(struct xdr_stream *xdr, int who);
> +__be32 nfsd4_decode_ace_who(struct richace *ace, struct svc_rqst *rqstp,
> +			    char *who, u32 len);
> +__be32 nfsd4_encode_ace_who(struct xdr_stream *xdr, struct svc_rqst *rqstp,
> +			    struct richace *ace);
>  
> -int nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
> -		struct nfs4_acl **acl);
> -__be32 nfsd4_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
> -		struct nfs4_acl *acl);
> +int nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
> +		  struct richacl **acl);
> +__be32 nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
> +		     struct richacl *acl);
>  
>  #endif /* LINUX_NFS4_ACL_H */
> diff --git a/fs/nfsd/nfs4acl.c b/fs/nfsd/nfs4acl.c
> index eb5accf..582f772 100644
> --- a/fs/nfsd/nfs4acl.c
> +++ b/fs/nfsd/nfs4acl.c
> @@ -36,45 +36,48 @@
>  
>  #include <linux/slab.h>
>  #include <linux/nfs_fs.h>
> +#include <linux/richacl_compat.h>
> +#include <linux/nfs4acl.h>
>  #include "nfsfh.h"
>  #include "nfsd.h"
> +#include "idmap.h"
>  #include "acl.h"
>  #include "vfs.h"
>  
> -#define NFS4_ACL_TYPE_DEFAULT	0x01
> -#define NFS4_ACL_DIR		0x02
> -#define NFS4_ACL_OWNER		0x04
> +#define FLAG_DEFAULT_ACL	0x01
> +#define FLAG_DIRECTORY		0x02
> +#define FLAG_OWNER		0x04
>  
>  /* mode bit translations: */
> -#define NFS4_READ_MODE (NFS4_ACE_READ_DATA)
> -#define NFS4_WRITE_MODE (NFS4_ACE_WRITE_DATA | NFS4_ACE_APPEND_DATA)
> -#define NFS4_EXECUTE_MODE NFS4_ACE_EXECUTE
> -#define NFS4_ANYONE_MODE (NFS4_ACE_READ_ATTRIBUTES | NFS4_ACE_READ_ACL | NFS4_ACE_SYNCHRONIZE)
> -#define NFS4_OWNER_MODE (NFS4_ACE_WRITE_ATTRIBUTES | NFS4_ACE_WRITE_ACL)
> +#define RICHACE_READ_MODE (RICHACE_READ_DATA)
> +#define RICHACE_WRITE_MODE (RICHACE_WRITE_DATA | RICHACE_APPEND_DATA)
> +#define RICHACE_EXECUTE_MODE RICHACE_EXECUTE
> +#define RICHACE_ANYONE_MODE (RICHACE_READ_ATTRIBUTES | RICHACE_READ_ACL | RICHACE_SYNCHRONIZE)
> +#define RICHACE_OWNER_MODE (RICHACE_WRITE_ATTRIBUTES | RICHACE_WRITE_ACL)
>  
>  /* flags used to simulate posix default ACLs */
> -#define NFS4_INHERITANCE_FLAGS (NFS4_ACE_FILE_INHERIT_ACE \
> -		| NFS4_ACE_DIRECTORY_INHERIT_ACE)
> -
> -#define NFS4_SUPPORTED_FLAGS (NFS4_INHERITANCE_FLAGS \
> -		| NFS4_ACE_INHERIT_ONLY_ACE \
> -		| NFS4_ACE_IDENTIFIER_GROUP)
> +#define RICHACE_SUPPORTED_FLAGS (		\
> +	RICHACE_FILE_INHERIT_ACE |		\
> +	RICHACE_DIRECTORY_INHERIT_ACE |		\
> +	RICHACE_INHERIT_ONLY_ACE |		\
> +	RICHACE_IDENTIFIER_GROUP |		\
> +	RICHACE_SPECIAL_WHO)
>  
>  static u32
>  mask_from_posix(unsigned short perm, unsigned int flags)
>  {
> -	int mask = NFS4_ANYONE_MODE;
> +	int mask = RICHACE_ANYONE_MODE;
>  
> -	if (flags & NFS4_ACL_OWNER)
> -		mask |= NFS4_OWNER_MODE;
> +	if (flags & FLAG_OWNER)
> +		mask |= RICHACE_OWNER_MODE;
>  	if (perm & ACL_READ)
> -		mask |= NFS4_READ_MODE;
> +		mask |= RICHACE_READ_MODE;
>  	if (perm & ACL_WRITE)
> -		mask |= NFS4_WRITE_MODE;
> -	if ((perm & ACL_WRITE) && (flags & NFS4_ACL_DIR))
> -		mask |= NFS4_ACE_DELETE_CHILD;
> +		mask |= RICHACE_WRITE_MODE;
> +	if ((perm & ACL_WRITE) && (flags & FLAG_DIRECTORY))
> +		mask |= RICHACE_DELETE_CHILD;
>  	if (perm & ACL_EXECUTE)
> -		mask |= NFS4_EXECUTE_MODE;
> +		mask |= RICHACE_EXECUTE_MODE;
>  	return mask;
>  }
>  
> @@ -84,13 +87,13 @@ deny_mask_from_posix(unsigned short perm, u32 flags)
>  	u32 mask = 0;
>  
>  	if (perm & ACL_READ)
> -		mask |= NFS4_READ_MODE;
> +		mask |= RICHACE_READ_MODE;
>  	if (perm & ACL_WRITE)
> -		mask |= NFS4_WRITE_MODE;
> -	if ((perm & ACL_WRITE) && (flags & NFS4_ACL_DIR))
> -		mask |= NFS4_ACE_DELETE_CHILD;
> +		mask |= RICHACE_WRITE_MODE;
> +	if ((perm & ACL_WRITE) && (flags & FLAG_DIRECTORY))
> +		mask |= RICHACE_DELETE_CHILD;
>  	if (perm & ACL_EXECUTE)
> -		mask |= NFS4_EXECUTE_MODE;
> +		mask |= RICHACE_EXECUTE_MODE;
>  	return mask;
>  }
>  
> @@ -106,32 +109,33 @@ deny_mask_from_posix(unsigned short perm, u32 flags)
>  static void
>  low_mode_from_nfs4(u32 perm, unsigned short *mode, unsigned int flags)
>  {
> -	u32 write_mode = NFS4_WRITE_MODE;
> +	u32 write_mode = RICHACE_WRITE_MODE;
>  
> -	if (flags & NFS4_ACL_DIR)
> -		write_mode |= NFS4_ACE_DELETE_CHILD;
> +	if (flags & FLAG_DIRECTORY)
> +		write_mode |= RICHACE_DELETE_CHILD;
>  	*mode = 0;
> -	if ((perm & NFS4_READ_MODE) == NFS4_READ_MODE)
> +	if ((perm & RICHACE_READ_MODE) == RICHACE_READ_MODE)
>  		*mode |= ACL_READ;
>  	if ((perm & write_mode) == write_mode)
>  		*mode |= ACL_WRITE;
> -	if ((perm & NFS4_EXECUTE_MODE) == NFS4_EXECUTE_MODE)
> +	if ((perm & RICHACE_EXECUTE_MODE) == RICHACE_EXECUTE_MODE)
>  		*mode |= ACL_EXECUTE;
>  }
>  
> -static short ace2type(struct nfs4_ace *);
> -static void _posix_to_nfsv4_one(struct posix_acl *, struct nfs4_acl *,
> +static short ace2type(struct richace *);
> +static void _posix_to_richacl_one(struct posix_acl *, struct richacl_alloc *,
>  				unsigned int);
>  
>  int
> -nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
> -		struct nfs4_acl **acl)
> +nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
> +	      struct richacl **acl)
>  {
>  	struct inode *inode = d_inode(dentry);
>  	int error = 0;
>  	struct posix_acl *pacl = NULL, *dpacl = NULL;
> +	struct richacl_alloc alloc;
>  	unsigned int flags = 0;
> -	int size = 0;
> +	int count;
>  
>  	pacl = get_acl(inode, ACL_TYPE_ACCESS);
>  	if (!pacl)
> @@ -141,10 +145,10 @@ nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
>  		return PTR_ERR(pacl);
>  
>  	/* allocate for worst case: one (deny, allow) pair each: */
> -	size += 2 * pacl->a_count;
> +	count = 2 * pacl->a_count;
>  
>  	if (S_ISDIR(inode->i_mode)) {
> -		flags = NFS4_ACL_DIR;
> +		flags = FLAG_DIRECTORY;
>  		dpacl = get_acl(inode, ACL_TYPE_DEFAULT);
>  		if (IS_ERR(dpacl)) {
>  			error = PTR_ERR(dpacl);
> @@ -152,20 +156,20 @@ nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
>  		}
>  
>  		if (dpacl)
> -			size += 2 * dpacl->a_count;
> +			count += 2 * dpacl->a_count;
>  	}
>  
> -	*acl = kmalloc(nfs4_acl_bytes(size), GFP_KERNEL);
> -	if (*acl == NULL) {
> +	if (!richacl_prepare(&alloc, count)) {
>  		error = -ENOMEM;
>  		goto out;
>  	}
> -	(*acl)->naces = 0;
>  
> -	_posix_to_nfsv4_one(pacl, *acl, flags & ~NFS4_ACL_TYPE_DEFAULT);
> +	_posix_to_richacl_one(pacl, &alloc, flags);
>  
>  	if (dpacl)
> -		_posix_to_nfsv4_one(dpacl, *acl, flags | NFS4_ACL_TYPE_DEFAULT);
> +		_posix_to_richacl_one(dpacl, &alloc, flags | FLAG_DEFAULT_ACL);
> +
> +	*acl = alloc.acl;
>  
>  out:
>  	posix_acl_release(dpacl);
> @@ -228,21 +232,22 @@ summarize_posix_acl(struct posix_acl *acl, struct posix_acl_summary *pas)
>  
>  /* We assume the acl has been verified with posix_acl_valid. */
>  static void
> -_posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
> -						unsigned int flags)
> +_posix_to_richacl_one(struct posix_acl *pacl, struct richacl_alloc *alloc,
> +		unsigned int flags)
>  {
>  	struct posix_acl_entry *pa, *group_owner_entry;
> -	struct nfs4_ace *ace;
> +	struct richace *ace;
>  	struct posix_acl_summary pas;
>  	unsigned short deny;
> -	int eflag = ((flags & NFS4_ACL_TYPE_DEFAULT) ?
> -		NFS4_INHERITANCE_FLAGS | NFS4_ACE_INHERIT_ONLY_ACE : 0);
> +	int e_flags = ((flags & FLAG_DEFAULT_ACL) ?
> +		       (RICHACE_FILE_INHERIT_ACE |
> +		        RICHACE_DIRECTORY_INHERIT_ACE |
> +		        RICHACE_INHERIT_ONLY_ACE) : 0);
>  
>  	BUG_ON(pacl->a_count < 3);
>  	summarize_posix_acl(pacl, &pas);
>  
>  	pa = pacl->a_entries;
> -	ace = acl->aces + acl->naces;
>  
>  	/* We could deny everything not granted by the owner: */
>  	deny = ~pas.owner;
> @@ -252,42 +257,35 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
>  	 */
>  	deny &= pas.users | pas.group | pas.groups | pas.other;
>  	if (deny) {
> -		ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
> -		ace->flag = eflag;
> -		ace->access_mask = deny_mask_from_posix(deny, flags);
> -		ace->whotype = NFS4_ACL_WHO_OWNER;
> -		ace++;
> -		acl->naces++;
> +		ace = richacl_append_entry(alloc);
> +		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +		ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
> +		ace->e_mask = deny_mask_from_posix(deny, flags);
> +		ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
>  	}
>  
> -	ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
> -	ace->flag = eflag;
> -	ace->access_mask = mask_from_posix(pa->e_perm, flags | NFS4_ACL_OWNER);
> -	ace->whotype = NFS4_ACL_WHO_OWNER;
> -	ace++;
> -	acl->naces++;
> +	ace = richacl_append_entry(alloc);
> +	ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +	ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
> +	ace->e_mask = mask_from_posix(pa->e_perm, flags | FLAG_OWNER);
> +	ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
>  	pa++;
>  
>  	while (pa->e_tag == ACL_USER) {
>  		deny = ~(pa->e_perm & pas.mask);
>  		deny &= pas.groups | pas.group | pas.other;
>  		if (deny) {
> -			ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
> -			ace->flag = eflag;
> -			ace->access_mask = deny_mask_from_posix(deny, flags);
> -			ace->whotype = NFS4_ACL_WHO_NAMED;
> -			ace->who_uid = pa->e_uid;
> -			ace++;
> -			acl->naces++;
> +			ace = richacl_append_entry(alloc);
> +			ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +			ace->e_flags = e_flags;
> +			ace->e_mask = deny_mask_from_posix(deny, flags);
> +			ace->e_id.uid = pa->e_uid;
>  		}
> -		ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
> -		ace->flag = eflag;
> -		ace->access_mask = mask_from_posix(pa->e_perm & pas.mask,
> -						   flags);
> -		ace->whotype = NFS4_ACL_WHO_NAMED;
> -		ace->who_uid = pa->e_uid;
> -		ace++;
> -		acl->naces++;
> +		ace = richacl_append_entry(alloc);
> +		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +		ace->e_flags = e_flags;
> +		ace->e_mask = mask_from_posix(pa->e_perm & pas.mask, flags);
> +		ace->e_id.uid = pa->e_uid;
>  		pa++;
>  	}
>  
> @@ -298,23 +296,19 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
>  
>  	group_owner_entry = pa;
>  
> -	ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
> -	ace->flag = eflag;
> -	ace->access_mask = mask_from_posix(pas.group, flags);
> -	ace->whotype = NFS4_ACL_WHO_GROUP;
> -	ace++;
> -	acl->naces++;
> +	ace = richacl_append_entry(alloc);
> +	ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +	ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
> +	ace->e_mask = mask_from_posix(pas.group, flags);
> +	ace->e_id.special = RICHACE_GROUP_SPECIAL_ID;
>  	pa++;
>  
>  	while (pa->e_tag == ACL_GROUP) {
> -		ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
> -		ace->flag = eflag | NFS4_ACE_IDENTIFIER_GROUP;
> -		ace->access_mask = mask_from_posix(pa->e_perm & pas.mask,
> -						   flags);
> -		ace->whotype = NFS4_ACL_WHO_NAMED;
> -		ace->who_gid = pa->e_gid;
> -		ace++;
> -		acl->naces++;
> +		ace = richacl_append_entry(alloc);
> +		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +		ace->e_flags = e_flags | RICHACE_IDENTIFIER_GROUP;
> +		ace->e_mask = mask_from_posix(pa->e_perm & pas.mask, flags);
> +		ace->e_id.gid = pa->e_gid;
>  		pa++;
>  	}
>  
> @@ -324,12 +318,11 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
>  
>  	deny = ~pas.group & pas.other;
>  	if (deny) {
> -		ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
> -		ace->flag = eflag;
> -		ace->access_mask = deny_mask_from_posix(deny, flags);
> -		ace->whotype = NFS4_ACL_WHO_GROUP;
> -		ace++;
> -		acl->naces++;
> +		ace = richacl_append_entry(alloc);
> +		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +		ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
> +		ace->e_mask = deny_mask_from_posix(deny, flags);
> +		ace->e_id.special = RICHACE_GROUP_SPECIAL_ID;
>  	}
>  	pa++;
>  
> @@ -337,24 +330,22 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
>  		deny = ~(pa->e_perm & pas.mask);
>  		deny &= pas.other;
>  		if (deny) {
> -			ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
> -			ace->flag = eflag | NFS4_ACE_IDENTIFIER_GROUP;
> -			ace->access_mask = deny_mask_from_posix(deny, flags);
> -			ace->whotype = NFS4_ACL_WHO_NAMED;
> -			ace->who_gid = pa->e_gid;
> -			ace++;
> -			acl->naces++;
> +			ace = richacl_append_entry(alloc);
> +			ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +			ace->e_flags = e_flags | RICHACE_IDENTIFIER_GROUP;
> +			ace->e_mask = deny_mask_from_posix(deny, flags);
> +			ace->e_id.gid = pa->e_gid;
>  		}
>  		pa++;
>  	}
>  
>  	if (pa->e_tag == ACL_MASK)
>  		pa++;
> -	ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
> -	ace->flag = eflag;
> -	ace->access_mask = mask_from_posix(pa->e_perm, flags);
> -	ace->whotype = NFS4_ACL_WHO_EVERYONE;
> -	acl->naces++;
> +	ace = richacl_append_entry(alloc);
> +	ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +	ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
> +	ace->e_mask = mask_from_posix(pa->e_perm, flags);
> +	ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
>  }
>  
>  static bool
> @@ -498,7 +489,7 @@ posix_state_to_acl(struct posix_acl_state *state, unsigned int flags)
>  	 * and effective cases: when there are no inheritable ACEs,
>  	 * calls ->set_acl with a NULL ACL structure.
>  	 */
> -	if (state->empty && (flags & NFS4_ACL_TYPE_DEFAULT))
> +	if (state->empty && (flags & FLAG_DEFAULT_ACL))
>  		return NULL;
>  
>  	/*
> @@ -617,24 +608,24 @@ static void allow_bits_array(struct posix_ace_state_array *a, u32 mask)
>  }
>  
>  static void process_one_v4_ace(struct posix_acl_state *state,
> -				struct nfs4_ace *ace)
> +				struct richace *ace)
>  {
> -	u32 mask = ace->access_mask;
> +	u32 mask = ace->e_mask;
>  	int i;
>  
>  	state->empty = 0;
>  
>  	switch (ace2type(ace)) {
>  	case ACL_USER_OBJ:
> -		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
> +		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
>  			allow_bits(&state->owner, mask);
>  		} else {
>  			deny_bits(&state->owner, mask);
>  		}
>  		break;
>  	case ACL_USER:
> -		i = find_uid(state, ace->who_uid);
> -		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
> +		i = find_uid(state, ace->e_id.uid);
> +		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
>  			allow_bits(&state->users->aces[i].perms, mask);
>  		} else {
>  			deny_bits(&state->users->aces[i].perms, mask);
> @@ -643,7 +634,7 @@ static void process_one_v4_ace(struct posix_acl_state *state,
>  		}
>  		break;
>  	case ACL_GROUP_OBJ:
> -		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
> +		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
>  			allow_bits(&state->group, mask);
>  		} else {
>  			deny_bits(&state->group, mask);
> @@ -655,8 +646,8 @@ static void process_one_v4_ace(struct posix_acl_state *state,
>  		}
>  		break;
>  	case ACL_GROUP:
> -		i = find_gid(state, ace->who_gid);
> -		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
> +		i = find_gid(state, ace->e_id.gid);
> +		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
>  			allow_bits(&state->groups->aces[i].perms, mask);
>  		} else {
>  			deny_bits(&state->groups->aces[i].perms, mask);
> @@ -669,7 +660,7 @@ static void process_one_v4_ace(struct posix_acl_state *state,
>  		}
>  		break;
>  	case ACL_OTHER:
> -		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
> +		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
>  			allow_bits(&state->owner, mask);
>  			allow_bits(&state->group, mask);
>  			allow_bits(&state->other, mask);
> @@ -687,32 +678,33 @@ static void process_one_v4_ace(struct posix_acl_state *state,
>  	}
>  }
>  
> -static int nfs4_acl_nfsv4_to_posix(struct nfs4_acl *acl,
> +static int nfs4_richacl_to_posix(struct richacl *acl,
>  		struct posix_acl **pacl, struct posix_acl **dpacl,
>  		unsigned int flags)
>  {
>  	struct posix_acl_state effective_acl_state, default_acl_state;
> -	struct nfs4_ace *ace;
> +	struct richace *ace;
>  	int ret;
>  
> -	ret = init_state(&effective_acl_state, acl->naces);
> +	ret = init_state(&effective_acl_state, acl->a_count);
>  	if (ret)
>  		return ret;
> -	ret = init_state(&default_acl_state, acl->naces);
> +	ret = init_state(&default_acl_state, acl->a_count);
>  	if (ret)
>  		goto out_estate;
>  	ret = -EINVAL;
> -	for (ace = acl->aces; ace < acl->aces + acl->naces; ace++) {
> -		if (ace->type != NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE &&
> -		    ace->type != NFS4_ACE_ACCESS_DENIED_ACE_TYPE)
> +	richacl_for_each_entry(ace, acl) {
> +		if (ace->e_type != RICHACE_ACCESS_ALLOWED_ACE_TYPE &&
> +		    ace->e_type != RICHACE_ACCESS_DENIED_ACE_TYPE)
>  			goto out_dstate;
> -		if (ace->flag & ~NFS4_SUPPORTED_FLAGS)
> +		if (ace->e_flags & ~RICHACE_SUPPORTED_FLAGS)
>  			goto out_dstate;
> -		if ((ace->flag & NFS4_INHERITANCE_FLAGS) == 0) {
> +		if ((ace->e_flags & (RICHACE_FILE_INHERIT_ACE |
> +				     RICHACE_DIRECTORY_INHERIT_ACE)) == 0) {
>  			process_one_v4_ace(&effective_acl_state, ace);
>  			continue;
>  		}
> -		if (!(flags & NFS4_ACL_DIR))
> +		if (!(flags & FLAG_DIRECTORY))
>  			goto out_dstate;
>  		/*
>  		 * Note that when only one of FILE_INHERIT or DIRECTORY_INHERIT
> @@ -721,7 +713,7 @@ static int nfs4_acl_nfsv4_to_posix(struct nfs4_acl *acl,
>  		 */
>  		process_one_v4_ace(&default_acl_state, ace);
>  
> -		if (!(ace->flag & NFS4_ACE_INHERIT_ONLY_ACE))
> +		if (!(ace->e_flags & RICHACE_INHERIT_ONLY_ACE))
>  			process_one_v4_ace(&effective_acl_state, ace);
>  	}
>  	*pacl = posix_state_to_acl(&effective_acl_state, flags);
> @@ -731,7 +723,7 @@ static int nfs4_acl_nfsv4_to_posix(struct nfs4_acl *acl,
>  		goto out_dstate;
>  	}
>  	*dpacl = posix_state_to_acl(&default_acl_state,
> -						flags | NFS4_ACL_TYPE_DEFAULT);
> +						flags | FLAG_DEFAULT_ACL);
>  	if (IS_ERR(*dpacl)) {
>  		ret = PTR_ERR(*dpacl);
>  		*dpacl = NULL;
> @@ -750,8 +742,7 @@ out_estate:
>  }
>  
>  __be32
> -nfsd4_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
> -		struct nfs4_acl *acl)
> +nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp, struct richacl *acl)
>  {
>  	__be32 error;
>  	int host_error;
> @@ -772,9 +763,9 @@ nfsd4_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
>  		return nfserr_attrnotsupp;
>  
>  	if (S_ISDIR(inode->i_mode))
> -		flags = NFS4_ACL_DIR;
> +		flags = FLAG_DIRECTORY;
>  
> -	host_error = nfs4_acl_nfsv4_to_posix(acl, &pacl, &dpacl, flags);
> +	host_error = nfs4_richacl_to_posix(acl, &pacl, &dpacl, flags);
>  	if (host_error == -EINVAL)
>  		return nfserr_attrnotsupp;
>  	if (host_error < 0)
> @@ -801,82 +792,62 @@ out_nfserr:
>  
>  
>  static short
> -ace2type(struct nfs4_ace *ace)
> +ace2type(struct richace *ace)
>  {
> -	switch (ace->whotype) {
> -		case NFS4_ACL_WHO_NAMED:
> -			return (ace->flag & NFS4_ACE_IDENTIFIER_GROUP ?
> -					ACL_GROUP : ACL_USER);
> -		case NFS4_ACL_WHO_OWNER:
> +	if (ace->e_flags & RICHACE_SPECIAL_WHO) {
> +		switch (ace->e_id.special) {
> +		case RICHACE_OWNER_SPECIAL_ID:
>  			return ACL_USER_OBJ;
> -		case NFS4_ACL_WHO_GROUP:
> +		case RICHACE_GROUP_SPECIAL_ID:
>  			return ACL_GROUP_OBJ;
> -		case NFS4_ACL_WHO_EVERYONE:
> +		case RICHACE_EVERYONE_SPECIAL_ID:
>  			return ACL_OTHER;
> +		default:
> +			BUG();
> +		}
>  	}
> -	BUG();
> -	return -1;
> -}
> -
> -/*
> - * return the size of the struct nfs4_acl required to represent an acl
> - * with @entries entries.
> - */
> -int nfs4_acl_bytes(int entries)
> -{
> -	return sizeof(struct nfs4_acl) + entries * sizeof(struct nfs4_ace);
> +	return ace->e_flags & RICHACE_IDENTIFIER_GROUP ? ACL_GROUP : ACL_USER;
>  }
>  
> -static struct {
> -	char *string;
> -	int   stringlen;
> -	int type;
> -} s2t_map[] = {
> -	{
> -		.string    = "OWNER@",
> -		.stringlen = sizeof("OWNER@") - 1,
> -		.type      = NFS4_ACL_WHO_OWNER,
> -	},
> -	{
> -		.string    = "GROUP@",
> -		.stringlen = sizeof("GROUP@") - 1,
> -		.type      = NFS4_ACL_WHO_GROUP,
> -	},
> -	{
> -		.string    = "EVERYONE@",
> -		.stringlen = sizeof("EVERYONE@") - 1,
> -		.type      = NFS4_ACL_WHO_EVERYONE,
> -	},
> -};
> -
> -int
> -nfs4_acl_get_whotype(char *p, u32 len)
> +__be32 nfsd4_decode_ace_who(struct richace *ace, struct svc_rqst *rqstp,
> +			    char *who, u32 len)
>  {
> -	int i;
> -
> -	for (i = 0; i < ARRAY_SIZE(s2t_map); i++) {
> -		if (s2t_map[i].stringlen == len &&
> -				0 == memcmp(s2t_map[i].string, p, len))
> -			return s2t_map[i].type;
> +	int special_id;
> +
> +	special_id = nfs4acl_who_to_special_id(who, len);
> +	if (special_id >= 0) {
> +		ace->e_flags |= RICHACE_SPECIAL_WHO;
> +		ace->e_flags &= ~RICHACE_IDENTIFIER_GROUP;
> +		ace->e_id.special = special_id;
> +		return nfs_ok;
>  	}
> -	return NFS4_ACL_WHO_NAMED;
> +	if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
> +		return nfsd_map_name_to_gid(rqstp, who, len, &ace->e_id.gid);
> +	else
> +		return nfsd_map_name_to_uid(rqstp, who, len, &ace->e_id.uid);
>  }
>  
> -__be32 nfs4_acl_write_who(struct xdr_stream *xdr, int who)
> +__be32 nfsd4_encode_ace_who(struct xdr_stream *xdr, struct svc_rqst *rqstp,
> +			    struct richace *ace)
>  {
> -	__be32 *p;
> -	int i;
> -
> -	for (i = 0; i < ARRAY_SIZE(s2t_map); i++) {
> -		if (s2t_map[i].type != who)
> -			continue;
> -		p = xdr_reserve_space(xdr, s2t_map[i].stringlen + 4);
> +	if (ace->e_flags & RICHACE_SPECIAL_WHO) {
> +		unsigned int special_id = ace->e_id.special;
> +		const char *who;
> +		unsigned int len;
> +		__be32 *p;
> +
> +		if (!nfs4acl_special_id_to_who(special_id, &who, &len)) {
> +			WARN_ON_ONCE(1);
> +			return nfserr_serverfault;
> +		}
> +		p = xdr_reserve_space(xdr, len + 4);
>  		if (!p)
>  			return nfserr_resource;
> -		p = xdr_encode_opaque(p, s2t_map[i].string,
> -					s2t_map[i].stringlen);
> +		p = xdr_encode_opaque(p, who, len);
>  		return 0;
>  	}
> -	WARN_ON_ONCE(1);
> -	return nfserr_serverfault;
> +	if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
> +		return nfsd4_encode_group(xdr, rqstp, ace->e_id.gid);
> +	else
> +		return nfsd4_encode_user(xdr, rqstp, ace->e_id.uid);
>  }
> diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
> index 90cfda7..8c2cb16 100644
> --- a/fs/nfsd/nfs4proc.c
> +++ b/fs/nfsd/nfs4proc.c
> @@ -159,12 +159,12 @@ is_create_with_attrs(struct nfsd4_open *open)
>   * in the returned attr bitmap.
>   */
>  static void
> -do_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
> -		struct nfs4_acl *acl, u32 *bmval)
> +do_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp, struct richacl *acl,
> +	   u32 *bmval)
>  {
>  	__be32 status;
>  
> -	status = nfsd4_set_nfs4_acl(rqstp, fhp, acl);
> +	status = nfsd4_set_acl(rqstp, fhp, acl);
>  	if (status)
>  		/*
>  		 * We should probably fail the whole open at this point,
> @@ -299,7 +299,7 @@ do_open_lookup(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, stru
>  		goto out;
>  
>  	if (is_create_with_attrs(open) && open->op_acl != NULL)
> -		do_set_nfs4_acl(rqstp, *resfh, open->op_acl, open->op_bmval);
> +		do_set_acl(rqstp, *resfh, open->op_acl, open->op_bmval);
>  
>  	nfsd4_set_open_owner_reply_cache(cstate, open, *resfh);
>  	accmode = NFSD_MAY_NOP;
> @@ -674,8 +674,7 @@ nfsd4_create(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
>  		nfsd4_security_inode_setsecctx(&resfh, &create->cr_label, create->cr_bmval);
>  
>  	if (create->cr_acl != NULL)
> -		do_set_nfs4_acl(rqstp, &resfh, create->cr_acl,
> -				create->cr_bmval);
> +		do_set_acl(rqstp, &resfh, create->cr_acl, create->cr_bmval);
>  
>  	fh_unlock(&cstate->current_fh);
>  	set_change_info(&create->cr_cinfo, &cstate->current_fh);
> @@ -940,8 +939,8 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
>  		goto out;
>  
>  	if (setattr->sa_acl != NULL)
> -		status = nfsd4_set_nfs4_acl(rqstp, &cstate->current_fh,
> -					    setattr->sa_acl);
> +		status = nfsd4_set_acl(rqstp, &cstate->current_fh,
> +				       setattr->sa_acl);
>  	if (status)
>  		goto out;
>  	if (setattr->sa_label.len)
> diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
> index 0768251..465f82a 100644
> --- a/fs/nfsd/nfs4xdr.c
> +++ b/fs/nfsd/nfs4xdr.c
> @@ -303,7 +303,7 @@ nfsd4_decode_bitmap(struct nfsd4_compoundargs *argp, u32 *bmval)
>  
>  static __be32
>  nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
> -		   struct iattr *iattr, struct nfs4_acl **acl,
> +		   struct iattr *iattr, struct richacl **acl,
>  		   struct xdr_netobj *label)
>  {
>  	int expected_len, len = 0;
> @@ -326,38 +326,31 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
>  	}
>  	if (bmval[0] & FATTR4_WORD0_ACL) {
>  		u32 nace;
> -		struct nfs4_ace *ace;
> +		struct richace *ace;
>  
>  		READ_BUF(4); len += 4;
>  		nace = be32_to_cpup(p++);
>  
> -		if (nace > NFS4_ACL_MAX)
> +		if (nace > NFSD4_ACL_MAX)
>  			return nfserr_fbig;
>  
> -		*acl = svcxdr_tmpalloc(argp, nfs4_acl_bytes(nace));
> +		*acl = svcxdr_alloc_richacl(argp, nace);
>  		if (*acl == NULL)
>  			return nfserr_jukebox;
>  
> -		(*acl)->naces = nace;
> -		for (ace = (*acl)->aces; ace < (*acl)->aces + nace; ace++) {
> +		richacl_for_each_entry(ace, *acl) {
>  			READ_BUF(16); len += 16;
> -			ace->type = be32_to_cpup(p++);
> -			ace->flag = be32_to_cpup(p++);
> -			ace->access_mask = be32_to_cpup(p++);
> +			ace->e_type = be32_to_cpup(p++);
> +			ace->e_flags = be32_to_cpup(p++);
> +			ace->e_mask = be32_to_cpup(p++);
> +			if (ace->e_flags & RICHACE_SPECIAL_WHO)
> +				return nfserr_inval;
>  			dummy32 = be32_to_cpup(p++);
>  			READ_BUF(dummy32);
>  			len += XDR_QUADLEN(dummy32) << 2;
>  			READMEM(buf, dummy32);
> -			ace->whotype = nfs4_acl_get_whotype(buf, dummy32);
> -			status = nfs_ok;
> -			if (ace->whotype != NFS4_ACL_WHO_NAMED)
> -				;
> -			else if (ace->flag & NFS4_ACE_IDENTIFIER_GROUP)
> -				status = nfsd_map_name_to_gid(argp->rqstp,
> -						buf, dummy32, &ace->who_gid);
> -			else
> -				status = nfsd_map_name_to_uid(argp->rqstp,
> -						buf, dummy32, &ace->who_uid);
> +			status = nfsd4_decode_ace_who(ace, argp->rqstp,
> +						      buf, dummy32);
>  			if (status)
>  				return status;
>  		}
> @@ -2147,18 +2140,6 @@ static u32 nfs4_file_type(umode_t mode)
>  	};
>  }
>  
> -static inline __be32
> -nfsd4_encode_aclname(struct xdr_stream *xdr, struct svc_rqst *rqstp,
> -		     struct nfs4_ace *ace)
> -{
> -	if (ace->whotype != NFS4_ACL_WHO_NAMED)
> -		return nfs4_acl_write_who(xdr, ace->whotype);
> -	else if (ace->flag & NFS4_ACE_IDENTIFIER_GROUP)
> -		return nfsd4_encode_group(xdr, rqstp, ace->who_gid);
> -	else
> -		return nfsd4_encode_user(xdr, rqstp, ace->who_uid);
> -}
> -
>  #define WORD0_ABSENT_FS_ATTRS (FATTR4_WORD0_FS_LOCATIONS | FATTR4_WORD0_FSID | \
>  			      FATTR4_WORD0_RDATTR_ERROR)
>  #define WORD1_ABSENT_FS_ATTRS FATTR4_WORD1_MOUNTED_ON_FILEID
> @@ -2249,7 +2230,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
>  	u32 rdattr_err = 0;
>  	__be32 status;
>  	int err;
> -	struct nfs4_acl *acl = NULL;
> +	struct richacl *acl = NULL;
>  	void *context = NULL;
>  	int contextlen;
>  	bool contextsupport = false;
> @@ -2295,7 +2276,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
>  		fhp = tempfh;
>  	}
>  	if (bmval0 & FATTR4_WORD0_ACL) {
> -		err = nfsd4_get_nfs4_acl(rqstp, dentry, &acl);
> +		err = nfsd4_get_acl(rqstp, dentry, &acl);
>  		if (err == -EOPNOTSUPP)
>  			bmval0 &= ~FATTR4_WORD0_ACL;
>  		else if (err == -EINVAL) {
> @@ -2469,7 +2450,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
>  		*p++ = cpu_to_be32(rdattr_err);
>  	}
>  	if (bmval0 & FATTR4_WORD0_ACL) {
> -		struct nfs4_ace *ace;
> +		struct richace *ace;
>  
>  		if (acl == NULL) {
>  			p = xdr_reserve_space(xdr, 4);
> @@ -2482,17 +2463,16 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
>  		p = xdr_reserve_space(xdr, 4);
>  		if (!p)
>  			goto out_resource;
> -		*p++ = cpu_to_be32(acl->naces);
> +		*p++ = cpu_to_be32(acl->a_count);
>  
> -		for (ace = acl->aces; ace < acl->aces + acl->naces; ace++) {
> +		richacl_for_each_entry(ace, acl) {
>  			p = xdr_reserve_space(xdr, 4*3);
>  			if (!p)
>  				goto out_resource;
> -			*p++ = cpu_to_be32(ace->type);
> -			*p++ = cpu_to_be32(ace->flag);
> -			*p++ = cpu_to_be32(ace->access_mask &
> -							NFS4_ACE_MASK_ALL);
> -			status = nfsd4_encode_aclname(xdr, rqstp, ace);
> +			*p++ = cpu_to_be32(ace->e_type);
> +			*p++ = cpu_to_be32(ace->e_flags & ~RICHACE_SPECIAL_WHO);
> +			*p++ = cpu_to_be32(ace->e_mask & NFS4_ACE_MASK_ALL);
> +			status = nfsd4_encode_ace_who(xdr, rqstp, ace);
>  			if (status)
>  				goto out;
>  		}
> @@ -2755,7 +2735,7 @@ out:
>  	if (context)
>  		security_release_secctx(context, contextlen);
>  #endif /* CONFIG_NFSD_V4_SECURITY_LABEL */
> -	kfree(acl);
> +	richacl_put(acl);
>  	if (tempfh) {
>  		fh_put(tempfh);
>  		kfree(tempfh);
> diff --git a/fs/nfsd/xdr4.h b/fs/nfsd/xdr4.h
> index b698585..c311066 100644
> --- a/fs/nfsd/xdr4.h
> +++ b/fs/nfsd/xdr4.h
> @@ -118,7 +118,7 @@ struct nfsd4_create {
>  	u32		cr_bmval[3];        /* request */
>  	struct iattr	cr_iattr;           /* request */
>  	struct nfsd4_change_info  cr_cinfo; /* response */
> -	struct nfs4_acl *cr_acl;
> +	struct richacl *cr_acl;
>  	struct xdr_netobj cr_label;
>  };
>  #define cr_datalen	u.link.datalen
> @@ -248,7 +248,7 @@ struct nfsd4_open {
>  	struct nfs4_file *op_file;          /* used during processing */
>  	struct nfs4_ol_stateid *op_stp;	    /* used during processing */
>  	struct nfs4_clnt_odstate *op_odstate; /* used during processing */
> -	struct nfs4_acl *op_acl;
> +	struct richacl *op_acl;
>  	struct xdr_netobj op_label;
>  };
>  
> @@ -332,7 +332,7 @@ struct nfsd4_setattr {
>  	stateid_t	sa_stateid;         /* request */
>  	u32		sa_bmval[3];        /* request */
>  	struct iattr	sa_iattr;           /* request */
> -	struct nfs4_acl *sa_acl;
> +	struct richacl *sa_acl;
>  	struct xdr_netobj sa_label;
>  };
>  
> diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h
> index b8e72aa..992ddc4 100644
> --- a/include/linux/nfs4.h
> +++ b/include/linux/nfs4.h
> @@ -16,29 +16,6 @@
>  #include <linux/uidgid.h>
>  #include <uapi/linux/nfs4.h>
>  
> -enum nfs4_acl_whotype {
> -	NFS4_ACL_WHO_NAMED = 0,
> -	NFS4_ACL_WHO_OWNER,
> -	NFS4_ACL_WHO_GROUP,
> -	NFS4_ACL_WHO_EVERYONE,
> -};
> -
> -struct nfs4_ace {
> -	uint32_t	type;
> -	uint32_t	flag;
> -	uint32_t	access_mask;
> -	int		whotype;
> -	union {
> -		kuid_t	who_uid;
> -		kgid_t	who_gid;
> -	};
> -};
> -
> -struct nfs4_acl {
> -	uint32_t	naces;
> -	struct nfs4_ace	aces[0];
> -};
> -
>  #define NFS4_MAXLABELLEN	2048
>  
>  struct nfs4_label {
> diff --git a/include/linux/nfs4acl.h b/include/linux/nfs4acl.h
> new file mode 100644
> index 0000000..db9f9a6
> --- /dev/null
> +++ b/include/linux/nfs4acl.h
> @@ -0,0 +1,7 @@
> +#ifndef __LINUX_NFS4ACL_H
> +#define __LINUX_NFS4ACL_H
> +
> +int nfs4acl_who_to_special_id(const char *, u32);
> +bool nfs4acl_special_id_to_who(unsigned int, const char **, unsigned int *);
> +
> +#endif
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 29/41] nfsd: Use richacls as internal acl representation
@ 2015-09-24 19:29         ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-24 19:29 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Sat, Sep 05, 2015 at 12:27:24PM +0200, Andreas Gruenbacher wrote:
> When converting from NFSv4 ACLs to POSIX ACLs, nfsd so far was using
> struct nfs4_acl as its internal representation. This representation is a
> subset of richacls, so get rid of struct nfs4_acl. Richacls even have a
> more compact in-memory representation, so a few more ACL entries can
> easily be supported.

Looks good to me, ACK.

--b.

> 
> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> ---
>  fs/Kconfig              |   6 +
>  fs/nfs_common/Makefile  |   1 +
>  fs/nfs_common/nfs4acl.c |  44 ++++++
>  fs/nfsd/Kconfig         |   1 +
>  fs/nfsd/acl.h           |  24 ++--
>  fs/nfsd/nfs4acl.c       | 367 ++++++++++++++++++++++--------------------------
>  fs/nfsd/nfs4proc.c      |  15 +-
>  fs/nfsd/nfs4xdr.c       |  64 +++------
>  fs/nfsd/xdr4.h          |   6 +-
>  include/linux/nfs4.h    |  23 ---
>  include/linux/nfs4acl.h |   7 +
>  11 files changed, 273 insertions(+), 285 deletions(-)
>  create mode 100644 fs/nfs_common/nfs4acl.c
>  create mode 100644 include/linux/nfs4acl.h
> 
> diff --git a/fs/Kconfig b/fs/Kconfig
> index 3e09c06..dd3f2d6 100644
> --- a/fs/Kconfig
> +++ b/fs/Kconfig
> @@ -268,6 +268,12 @@ config NFS_COMMON
>  	depends on NFSD || NFS_FS || LOCKD
>  	default y
>  
> +config NFS_RICHACL
> +	bool
> +	depends on NFSD_V4 || NFS_V4
> +	select FS_RICHACL
> +	default y
> +
>  source "net/sunrpc/Kconfig"
>  source "fs/ceph/Kconfig"
>  source "fs/cifs/Kconfig"
> diff --git a/fs/nfs_common/Makefile b/fs/nfs_common/Makefile
> index d153ca3..e055139 100644
> --- a/fs/nfs_common/Makefile
> +++ b/fs/nfs_common/Makefile
> @@ -4,5 +4,6 @@
>  
>  obj-$(CONFIG_NFS_ACL_SUPPORT) += nfs_acl.o
>  nfs_acl-objs := nfsacl.o
> +obj-$(CONFIG_NFS_RICHACL) += nfs4acl.o
>  
>  obj-$(CONFIG_GRACE_PERIOD) += grace.o
> diff --git a/fs/nfs_common/nfs4acl.c b/fs/nfs_common/nfs4acl.c
> new file mode 100644
> index 0000000..02df064
> --- /dev/null
> +++ b/fs/nfs_common/nfs4acl.c
> @@ -0,0 +1,44 @@
> +#include <linux/fs.h>
> +#include <linux/richacl.h>
> +#include <linux/nfs4acl.h>
> +
> +static struct special_id {
> +	char *who;
> +	int   len;
> +} special_who_map[] = {
> +	[RICHACE_OWNER_SPECIAL_ID] = {
> +		.who = "OWNER@",
> +		.len = sizeof("OWNER@") - 1 },
> +	[RICHACE_GROUP_SPECIAL_ID] = {
> +		.who = "GROUP@",
> +		.len = sizeof("GROUP@") - 1 },
> +	[RICHACE_EVERYONE_SPECIAL_ID] = {
> +		.who = "EVERYONE@",
> +		.len = sizeof("EVERYONE@") - 1 }
> +};
> +
> +int nfs4acl_who_to_special_id(const char *who, u32 len)
> +{
> +	int n;
> +
> +	for (n = 0; n < ARRAY_SIZE(special_who_map); n++) {
> +		if (len == special_who_map[n].len &&
> +		    !memcmp(who, special_who_map[n].who, len))
> +			return n;
> +	}
> +	return -1;
> +}
> +EXPORT_SYMBOL(nfs4acl_who_to_special_id);
> +
> +bool nfs4acl_special_id_to_who(unsigned int special_who,
> +			       const char **who, unsigned int *len)
> +{
> +	struct special_id *special = &special_who_map[special_who];
> +
> +	if (special_who > ARRAY_SIZE(special_who_map) || !special->len)
> +		return false;
> +	*who = special->who;
> +	*len = special->len;
> +	return true;
> +}
> +EXPORT_SYMBOL(nfs4acl_special_id_to_who);
> diff --git a/fs/nfsd/Kconfig b/fs/nfsd/Kconfig
> index a0b77fc..811379a 100644
> --- a/fs/nfsd/Kconfig
> +++ b/fs/nfsd/Kconfig
> @@ -70,6 +70,7 @@ config NFSD_V4
>  	depends on NFSD && PROC_FS
>  	select NFSD_V3
>  	select FS_POSIX_ACL
> +	select FS_RICHACL
>  	select SUNRPC_GSS
>  	select CRYPTO
>  	select GRACE_PERIOD
> diff --git a/fs/nfsd/acl.h b/fs/nfsd/acl.h
> index 4cd7c69..1c5deb5 100644
> --- a/fs/nfsd/acl.h
> +++ b/fs/nfsd/acl.h
> @@ -35,25 +35,27 @@
>  #ifndef LINUX_NFS4_ACL_H
>  #define LINUX_NFS4_ACL_H
>  
> -struct nfs4_acl;
> +struct richacl;
> +struct richace;
>  struct svc_fh;
>  struct svc_rqst;
>  
>  /*
>   * Maximum ACL we'll accept from a client; chosen (somewhat
>   * arbitrarily) so that kmalloc'ing the ACL shouldn't require a
> - * high-order allocation.  This allows 204 ACEs on x86_64:
> + * high-order allocation.  This allows 339 ACEs on x86_64:
>   */
> -#define NFS4_ACL_MAX ((PAGE_SIZE - sizeof(struct nfs4_acl)) \
> -			/ sizeof(struct nfs4_ace))
> +#define NFSD4_ACL_MAX ((PAGE_SIZE - sizeof(struct richacl)) \
> +			/ sizeof(struct richace))
>  
> -int nfs4_acl_bytes(int entries);
> -int nfs4_acl_get_whotype(char *, u32);
> -__be32 nfs4_acl_write_who(struct xdr_stream *xdr, int who);
> +__be32 nfsd4_decode_ace_who(struct richace *ace, struct svc_rqst *rqstp,
> +			    char *who, u32 len);
> +__be32 nfsd4_encode_ace_who(struct xdr_stream *xdr, struct svc_rqst *rqstp,
> +			    struct richace *ace);
>  
> -int nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
> -		struct nfs4_acl **acl);
> -__be32 nfsd4_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
> -		struct nfs4_acl *acl);
> +int nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
> +		  struct richacl **acl);
> +__be32 nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
> +		     struct richacl *acl);
>  
>  #endif /* LINUX_NFS4_ACL_H */
> diff --git a/fs/nfsd/nfs4acl.c b/fs/nfsd/nfs4acl.c
> index eb5accf..582f772 100644
> --- a/fs/nfsd/nfs4acl.c
> +++ b/fs/nfsd/nfs4acl.c
> @@ -36,45 +36,48 @@
>  
>  #include <linux/slab.h>
>  #include <linux/nfs_fs.h>
> +#include <linux/richacl_compat.h>
> +#include <linux/nfs4acl.h>
>  #include "nfsfh.h"
>  #include "nfsd.h"
> +#include "idmap.h"
>  #include "acl.h"
>  #include "vfs.h"
>  
> -#define NFS4_ACL_TYPE_DEFAULT	0x01
> -#define NFS4_ACL_DIR		0x02
> -#define NFS4_ACL_OWNER		0x04
> +#define FLAG_DEFAULT_ACL	0x01
> +#define FLAG_DIRECTORY		0x02
> +#define FLAG_OWNER		0x04
>  
>  /* mode bit translations: */
> -#define NFS4_READ_MODE (NFS4_ACE_READ_DATA)
> -#define NFS4_WRITE_MODE (NFS4_ACE_WRITE_DATA | NFS4_ACE_APPEND_DATA)
> -#define NFS4_EXECUTE_MODE NFS4_ACE_EXECUTE
> -#define NFS4_ANYONE_MODE (NFS4_ACE_READ_ATTRIBUTES | NFS4_ACE_READ_ACL | NFS4_ACE_SYNCHRONIZE)
> -#define NFS4_OWNER_MODE (NFS4_ACE_WRITE_ATTRIBUTES | NFS4_ACE_WRITE_ACL)
> +#define RICHACE_READ_MODE (RICHACE_READ_DATA)
> +#define RICHACE_WRITE_MODE (RICHACE_WRITE_DATA | RICHACE_APPEND_DATA)
> +#define RICHACE_EXECUTE_MODE RICHACE_EXECUTE
> +#define RICHACE_ANYONE_MODE (RICHACE_READ_ATTRIBUTES | RICHACE_READ_ACL | RICHACE_SYNCHRONIZE)
> +#define RICHACE_OWNER_MODE (RICHACE_WRITE_ATTRIBUTES | RICHACE_WRITE_ACL)
>  
>  /* flags used to simulate posix default ACLs */
> -#define NFS4_INHERITANCE_FLAGS (NFS4_ACE_FILE_INHERIT_ACE \
> -		| NFS4_ACE_DIRECTORY_INHERIT_ACE)
> -
> -#define NFS4_SUPPORTED_FLAGS (NFS4_INHERITANCE_FLAGS \
> -		| NFS4_ACE_INHERIT_ONLY_ACE \
> -		| NFS4_ACE_IDENTIFIER_GROUP)
> +#define RICHACE_SUPPORTED_FLAGS (		\
> +	RICHACE_FILE_INHERIT_ACE |		\
> +	RICHACE_DIRECTORY_INHERIT_ACE |		\
> +	RICHACE_INHERIT_ONLY_ACE |		\
> +	RICHACE_IDENTIFIER_GROUP |		\
> +	RICHACE_SPECIAL_WHO)
>  
>  static u32
>  mask_from_posix(unsigned short perm, unsigned int flags)
>  {
> -	int mask = NFS4_ANYONE_MODE;
> +	int mask = RICHACE_ANYONE_MODE;
>  
> -	if (flags & NFS4_ACL_OWNER)
> -		mask |= NFS4_OWNER_MODE;
> +	if (flags & FLAG_OWNER)
> +		mask |= RICHACE_OWNER_MODE;
>  	if (perm & ACL_READ)
> -		mask |= NFS4_READ_MODE;
> +		mask |= RICHACE_READ_MODE;
>  	if (perm & ACL_WRITE)
> -		mask |= NFS4_WRITE_MODE;
> -	if ((perm & ACL_WRITE) && (flags & NFS4_ACL_DIR))
> -		mask |= NFS4_ACE_DELETE_CHILD;
> +		mask |= RICHACE_WRITE_MODE;
> +	if ((perm & ACL_WRITE) && (flags & FLAG_DIRECTORY))
> +		mask |= RICHACE_DELETE_CHILD;
>  	if (perm & ACL_EXECUTE)
> -		mask |= NFS4_EXECUTE_MODE;
> +		mask |= RICHACE_EXECUTE_MODE;
>  	return mask;
>  }
>  
> @@ -84,13 +87,13 @@ deny_mask_from_posix(unsigned short perm, u32 flags)
>  	u32 mask = 0;
>  
>  	if (perm & ACL_READ)
> -		mask |= NFS4_READ_MODE;
> +		mask |= RICHACE_READ_MODE;
>  	if (perm & ACL_WRITE)
> -		mask |= NFS4_WRITE_MODE;
> -	if ((perm & ACL_WRITE) && (flags & NFS4_ACL_DIR))
> -		mask |= NFS4_ACE_DELETE_CHILD;
> +		mask |= RICHACE_WRITE_MODE;
> +	if ((perm & ACL_WRITE) && (flags & FLAG_DIRECTORY))
> +		mask |= RICHACE_DELETE_CHILD;
>  	if (perm & ACL_EXECUTE)
> -		mask |= NFS4_EXECUTE_MODE;
> +		mask |= RICHACE_EXECUTE_MODE;
>  	return mask;
>  }
>  
> @@ -106,32 +109,33 @@ deny_mask_from_posix(unsigned short perm, u32 flags)
>  static void
>  low_mode_from_nfs4(u32 perm, unsigned short *mode, unsigned int flags)
>  {
> -	u32 write_mode = NFS4_WRITE_MODE;
> +	u32 write_mode = RICHACE_WRITE_MODE;
>  
> -	if (flags & NFS4_ACL_DIR)
> -		write_mode |= NFS4_ACE_DELETE_CHILD;
> +	if (flags & FLAG_DIRECTORY)
> +		write_mode |= RICHACE_DELETE_CHILD;
>  	*mode = 0;
> -	if ((perm & NFS4_READ_MODE) == NFS4_READ_MODE)
> +	if ((perm & RICHACE_READ_MODE) == RICHACE_READ_MODE)
>  		*mode |= ACL_READ;
>  	if ((perm & write_mode) == write_mode)
>  		*mode |= ACL_WRITE;
> -	if ((perm & NFS4_EXECUTE_MODE) == NFS4_EXECUTE_MODE)
> +	if ((perm & RICHACE_EXECUTE_MODE) == RICHACE_EXECUTE_MODE)
>  		*mode |= ACL_EXECUTE;
>  }
>  
> -static short ace2type(struct nfs4_ace *);
> -static void _posix_to_nfsv4_one(struct posix_acl *, struct nfs4_acl *,
> +static short ace2type(struct richace *);
> +static void _posix_to_richacl_one(struct posix_acl *, struct richacl_alloc *,
>  				unsigned int);
>  
>  int
> -nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
> -		struct nfs4_acl **acl)
> +nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
> +	      struct richacl **acl)
>  {
>  	struct inode *inode = d_inode(dentry);
>  	int error = 0;
>  	struct posix_acl *pacl = NULL, *dpacl = NULL;
> +	struct richacl_alloc alloc;
>  	unsigned int flags = 0;
> -	int size = 0;
> +	int count;
>  
>  	pacl = get_acl(inode, ACL_TYPE_ACCESS);
>  	if (!pacl)
> @@ -141,10 +145,10 @@ nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
>  		return PTR_ERR(pacl);
>  
>  	/* allocate for worst case: one (deny, allow) pair each: */
> -	size += 2 * pacl->a_count;
> +	count = 2 * pacl->a_count;
>  
>  	if (S_ISDIR(inode->i_mode)) {
> -		flags = NFS4_ACL_DIR;
> +		flags = FLAG_DIRECTORY;
>  		dpacl = get_acl(inode, ACL_TYPE_DEFAULT);
>  		if (IS_ERR(dpacl)) {
>  			error = PTR_ERR(dpacl);
> @@ -152,20 +156,20 @@ nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
>  		}
>  
>  		if (dpacl)
> -			size += 2 * dpacl->a_count;
> +			count += 2 * dpacl->a_count;
>  	}
>  
> -	*acl = kmalloc(nfs4_acl_bytes(size), GFP_KERNEL);
> -	if (*acl == NULL) {
> +	if (!richacl_prepare(&alloc, count)) {
>  		error = -ENOMEM;
>  		goto out;
>  	}
> -	(*acl)->naces = 0;
>  
> -	_posix_to_nfsv4_one(pacl, *acl, flags & ~NFS4_ACL_TYPE_DEFAULT);
> +	_posix_to_richacl_one(pacl, &alloc, flags);
>  
>  	if (dpacl)
> -		_posix_to_nfsv4_one(dpacl, *acl, flags | NFS4_ACL_TYPE_DEFAULT);
> +		_posix_to_richacl_one(dpacl, &alloc, flags | FLAG_DEFAULT_ACL);
> +
> +	*acl = alloc.acl;
>  
>  out:
>  	posix_acl_release(dpacl);
> @@ -228,21 +232,22 @@ summarize_posix_acl(struct posix_acl *acl, struct posix_acl_summary *pas)
>  
>  /* We assume the acl has been verified with posix_acl_valid. */
>  static void
> -_posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
> -						unsigned int flags)
> +_posix_to_richacl_one(struct posix_acl *pacl, struct richacl_alloc *alloc,
> +		unsigned int flags)
>  {
>  	struct posix_acl_entry *pa, *group_owner_entry;
> -	struct nfs4_ace *ace;
> +	struct richace *ace;
>  	struct posix_acl_summary pas;
>  	unsigned short deny;
> -	int eflag = ((flags & NFS4_ACL_TYPE_DEFAULT) ?
> -		NFS4_INHERITANCE_FLAGS | NFS4_ACE_INHERIT_ONLY_ACE : 0);
> +	int e_flags = ((flags & FLAG_DEFAULT_ACL) ?
> +		       (RICHACE_FILE_INHERIT_ACE |
> +		        RICHACE_DIRECTORY_INHERIT_ACE |
> +		        RICHACE_INHERIT_ONLY_ACE) : 0);
>  
>  	BUG_ON(pacl->a_count < 3);
>  	summarize_posix_acl(pacl, &pas);
>  
>  	pa = pacl->a_entries;
> -	ace = acl->aces + acl->naces;
>  
>  	/* We could deny everything not granted by the owner: */
>  	deny = ~pas.owner;
> @@ -252,42 +257,35 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
>  	 */
>  	deny &= pas.users | pas.group | pas.groups | pas.other;
>  	if (deny) {
> -		ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
> -		ace->flag = eflag;
> -		ace->access_mask = deny_mask_from_posix(deny, flags);
> -		ace->whotype = NFS4_ACL_WHO_OWNER;
> -		ace++;
> -		acl->naces++;
> +		ace = richacl_append_entry(alloc);
> +		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +		ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
> +		ace->e_mask = deny_mask_from_posix(deny, flags);
> +		ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
>  	}
>  
> -	ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
> -	ace->flag = eflag;
> -	ace->access_mask = mask_from_posix(pa->e_perm, flags | NFS4_ACL_OWNER);
> -	ace->whotype = NFS4_ACL_WHO_OWNER;
> -	ace++;
> -	acl->naces++;
> +	ace = richacl_append_entry(alloc);
> +	ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +	ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
> +	ace->e_mask = mask_from_posix(pa->e_perm, flags | FLAG_OWNER);
> +	ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
>  	pa++;
>  
>  	while (pa->e_tag == ACL_USER) {
>  		deny = ~(pa->e_perm & pas.mask);
>  		deny &= pas.groups | pas.group | pas.other;
>  		if (deny) {
> -			ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
> -			ace->flag = eflag;
> -			ace->access_mask = deny_mask_from_posix(deny, flags);
> -			ace->whotype = NFS4_ACL_WHO_NAMED;
> -			ace->who_uid = pa->e_uid;
> -			ace++;
> -			acl->naces++;
> +			ace = richacl_append_entry(alloc);
> +			ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +			ace->e_flags = e_flags;
> +			ace->e_mask = deny_mask_from_posix(deny, flags);
> +			ace->e_id.uid = pa->e_uid;
>  		}
> -		ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
> -		ace->flag = eflag;
> -		ace->access_mask = mask_from_posix(pa->e_perm & pas.mask,
> -						   flags);
> -		ace->whotype = NFS4_ACL_WHO_NAMED;
> -		ace->who_uid = pa->e_uid;
> -		ace++;
> -		acl->naces++;
> +		ace = richacl_append_entry(alloc);
> +		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +		ace->e_flags = e_flags;
> +		ace->e_mask = mask_from_posix(pa->e_perm & pas.mask, flags);
> +		ace->e_id.uid = pa->e_uid;
>  		pa++;
>  	}
>  
> @@ -298,23 +296,19 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
>  
>  	group_owner_entry = pa;
>  
> -	ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
> -	ace->flag = eflag;
> -	ace->access_mask = mask_from_posix(pas.group, flags);
> -	ace->whotype = NFS4_ACL_WHO_GROUP;
> -	ace++;
> -	acl->naces++;
> +	ace = richacl_append_entry(alloc);
> +	ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +	ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
> +	ace->e_mask = mask_from_posix(pas.group, flags);
> +	ace->e_id.special = RICHACE_GROUP_SPECIAL_ID;
>  	pa++;
>  
>  	while (pa->e_tag == ACL_GROUP) {
> -		ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
> -		ace->flag = eflag | NFS4_ACE_IDENTIFIER_GROUP;
> -		ace->access_mask = mask_from_posix(pa->e_perm & pas.mask,
> -						   flags);
> -		ace->whotype = NFS4_ACL_WHO_NAMED;
> -		ace->who_gid = pa->e_gid;
> -		ace++;
> -		acl->naces++;
> +		ace = richacl_append_entry(alloc);
> +		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +		ace->e_flags = e_flags | RICHACE_IDENTIFIER_GROUP;
> +		ace->e_mask = mask_from_posix(pa->e_perm & pas.mask, flags);
> +		ace->e_id.gid = pa->e_gid;
>  		pa++;
>  	}
>  
> @@ -324,12 +318,11 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
>  
>  	deny = ~pas.group & pas.other;
>  	if (deny) {
> -		ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
> -		ace->flag = eflag;
> -		ace->access_mask = deny_mask_from_posix(deny, flags);
> -		ace->whotype = NFS4_ACL_WHO_GROUP;
> -		ace++;
> -		acl->naces++;
> +		ace = richacl_append_entry(alloc);
> +		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +		ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
> +		ace->e_mask = deny_mask_from_posix(deny, flags);
> +		ace->e_id.special = RICHACE_GROUP_SPECIAL_ID;
>  	}
>  	pa++;
>  
> @@ -337,24 +330,22 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
>  		deny = ~(pa->e_perm & pas.mask);
>  		deny &= pas.other;
>  		if (deny) {
> -			ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
> -			ace->flag = eflag | NFS4_ACE_IDENTIFIER_GROUP;
> -			ace->access_mask = deny_mask_from_posix(deny, flags);
> -			ace->whotype = NFS4_ACL_WHO_NAMED;
> -			ace->who_gid = pa->e_gid;
> -			ace++;
> -			acl->naces++;
> +			ace = richacl_append_entry(alloc);
> +			ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
> +			ace->e_flags = e_flags | RICHACE_IDENTIFIER_GROUP;
> +			ace->e_mask = deny_mask_from_posix(deny, flags);
> +			ace->e_id.gid = pa->e_gid;
>  		}
>  		pa++;
>  	}
>  
>  	if (pa->e_tag == ACL_MASK)
>  		pa++;
> -	ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
> -	ace->flag = eflag;
> -	ace->access_mask = mask_from_posix(pa->e_perm, flags);
> -	ace->whotype = NFS4_ACL_WHO_EVERYONE;
> -	acl->naces++;
> +	ace = richacl_append_entry(alloc);
> +	ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
> +	ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
> +	ace->e_mask = mask_from_posix(pa->e_perm, flags);
> +	ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
>  }
>  
>  static bool
> @@ -498,7 +489,7 @@ posix_state_to_acl(struct posix_acl_state *state, unsigned int flags)
>  	 * and effective cases: when there are no inheritable ACEs,
>  	 * calls ->set_acl with a NULL ACL structure.
>  	 */
> -	if (state->empty && (flags & NFS4_ACL_TYPE_DEFAULT))
> +	if (state->empty && (flags & FLAG_DEFAULT_ACL))
>  		return NULL;
>  
>  	/*
> @@ -617,24 +608,24 @@ static void allow_bits_array(struct posix_ace_state_array *a, u32 mask)
>  }
>  
>  static void process_one_v4_ace(struct posix_acl_state *state,
> -				struct nfs4_ace *ace)
> +				struct richace *ace)
>  {
> -	u32 mask = ace->access_mask;
> +	u32 mask = ace->e_mask;
>  	int i;
>  
>  	state->empty = 0;
>  
>  	switch (ace2type(ace)) {
>  	case ACL_USER_OBJ:
> -		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
> +		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
>  			allow_bits(&state->owner, mask);
>  		} else {
>  			deny_bits(&state->owner, mask);
>  		}
>  		break;
>  	case ACL_USER:
> -		i = find_uid(state, ace->who_uid);
> -		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
> +		i = find_uid(state, ace->e_id.uid);
> +		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
>  			allow_bits(&state->users->aces[i].perms, mask);
>  		} else {
>  			deny_bits(&state->users->aces[i].perms, mask);
> @@ -643,7 +634,7 @@ static void process_one_v4_ace(struct posix_acl_state *state,
>  		}
>  		break;
>  	case ACL_GROUP_OBJ:
> -		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
> +		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
>  			allow_bits(&state->group, mask);
>  		} else {
>  			deny_bits(&state->group, mask);
> @@ -655,8 +646,8 @@ static void process_one_v4_ace(struct posix_acl_state *state,
>  		}
>  		break;
>  	case ACL_GROUP:
> -		i = find_gid(state, ace->who_gid);
> -		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
> +		i = find_gid(state, ace->e_id.gid);
> +		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
>  			allow_bits(&state->groups->aces[i].perms, mask);
>  		} else {
>  			deny_bits(&state->groups->aces[i].perms, mask);
> @@ -669,7 +660,7 @@ static void process_one_v4_ace(struct posix_acl_state *state,
>  		}
>  		break;
>  	case ACL_OTHER:
> -		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
> +		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
>  			allow_bits(&state->owner, mask);
>  			allow_bits(&state->group, mask);
>  			allow_bits(&state->other, mask);
> @@ -687,32 +678,33 @@ static void process_one_v4_ace(struct posix_acl_state *state,
>  	}
>  }
>  
> -static int nfs4_acl_nfsv4_to_posix(struct nfs4_acl *acl,
> +static int nfs4_richacl_to_posix(struct richacl *acl,
>  		struct posix_acl **pacl, struct posix_acl **dpacl,
>  		unsigned int flags)
>  {
>  	struct posix_acl_state effective_acl_state, default_acl_state;
> -	struct nfs4_ace *ace;
> +	struct richace *ace;
>  	int ret;
>  
> -	ret = init_state(&effective_acl_state, acl->naces);
> +	ret = init_state(&effective_acl_state, acl->a_count);
>  	if (ret)
>  		return ret;
> -	ret = init_state(&default_acl_state, acl->naces);
> +	ret = init_state(&default_acl_state, acl->a_count);
>  	if (ret)
>  		goto out_estate;
>  	ret = -EINVAL;
> -	for (ace = acl->aces; ace < acl->aces + acl->naces; ace++) {
> -		if (ace->type != NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE &&
> -		    ace->type != NFS4_ACE_ACCESS_DENIED_ACE_TYPE)
> +	richacl_for_each_entry(ace, acl) {
> +		if (ace->e_type != RICHACE_ACCESS_ALLOWED_ACE_TYPE &&
> +		    ace->e_type != RICHACE_ACCESS_DENIED_ACE_TYPE)
>  			goto out_dstate;
> -		if (ace->flag & ~NFS4_SUPPORTED_FLAGS)
> +		if (ace->e_flags & ~RICHACE_SUPPORTED_FLAGS)
>  			goto out_dstate;
> -		if ((ace->flag & NFS4_INHERITANCE_FLAGS) == 0) {
> +		if ((ace->e_flags & (RICHACE_FILE_INHERIT_ACE |
> +				     RICHACE_DIRECTORY_INHERIT_ACE)) == 0) {
>  			process_one_v4_ace(&effective_acl_state, ace);
>  			continue;
>  		}
> -		if (!(flags & NFS4_ACL_DIR))
> +		if (!(flags & FLAG_DIRECTORY))
>  			goto out_dstate;
>  		/*
>  		 * Note that when only one of FILE_INHERIT or DIRECTORY_INHERIT
> @@ -721,7 +713,7 @@ static int nfs4_acl_nfsv4_to_posix(struct nfs4_acl *acl,
>  		 */
>  		process_one_v4_ace(&default_acl_state, ace);
>  
> -		if (!(ace->flag & NFS4_ACE_INHERIT_ONLY_ACE))
> +		if (!(ace->e_flags & RICHACE_INHERIT_ONLY_ACE))
>  			process_one_v4_ace(&effective_acl_state, ace);
>  	}
>  	*pacl = posix_state_to_acl(&effective_acl_state, flags);
> @@ -731,7 +723,7 @@ static int nfs4_acl_nfsv4_to_posix(struct nfs4_acl *acl,
>  		goto out_dstate;
>  	}
>  	*dpacl = posix_state_to_acl(&default_acl_state,
> -						flags | NFS4_ACL_TYPE_DEFAULT);
> +						flags | FLAG_DEFAULT_ACL);
>  	if (IS_ERR(*dpacl)) {
>  		ret = PTR_ERR(*dpacl);
>  		*dpacl = NULL;
> @@ -750,8 +742,7 @@ out_estate:
>  }
>  
>  __be32
> -nfsd4_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
> -		struct nfs4_acl *acl)
> +nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp, struct richacl *acl)
>  {
>  	__be32 error;
>  	int host_error;
> @@ -772,9 +763,9 @@ nfsd4_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
>  		return nfserr_attrnotsupp;
>  
>  	if (S_ISDIR(inode->i_mode))
> -		flags = NFS4_ACL_DIR;
> +		flags = FLAG_DIRECTORY;
>  
> -	host_error = nfs4_acl_nfsv4_to_posix(acl, &pacl, &dpacl, flags);
> +	host_error = nfs4_richacl_to_posix(acl, &pacl, &dpacl, flags);
>  	if (host_error == -EINVAL)
>  		return nfserr_attrnotsupp;
>  	if (host_error < 0)
> @@ -801,82 +792,62 @@ out_nfserr:
>  
>  
>  static short
> -ace2type(struct nfs4_ace *ace)
> +ace2type(struct richace *ace)
>  {
> -	switch (ace->whotype) {
> -		case NFS4_ACL_WHO_NAMED:
> -			return (ace->flag & NFS4_ACE_IDENTIFIER_GROUP ?
> -					ACL_GROUP : ACL_USER);
> -		case NFS4_ACL_WHO_OWNER:
> +	if (ace->e_flags & RICHACE_SPECIAL_WHO) {
> +		switch (ace->e_id.special) {
> +		case RICHACE_OWNER_SPECIAL_ID:
>  			return ACL_USER_OBJ;
> -		case NFS4_ACL_WHO_GROUP:
> +		case RICHACE_GROUP_SPECIAL_ID:
>  			return ACL_GROUP_OBJ;
> -		case NFS4_ACL_WHO_EVERYONE:
> +		case RICHACE_EVERYONE_SPECIAL_ID:
>  			return ACL_OTHER;
> +		default:
> +			BUG();
> +		}
>  	}
> -	BUG();
> -	return -1;
> -}
> -
> -/*
> - * return the size of the struct nfs4_acl required to represent an acl
> - * with @entries entries.
> - */
> -int nfs4_acl_bytes(int entries)
> -{
> -	return sizeof(struct nfs4_acl) + entries * sizeof(struct nfs4_ace);
> +	return ace->e_flags & RICHACE_IDENTIFIER_GROUP ? ACL_GROUP : ACL_USER;
>  }
>  
> -static struct {
> -	char *string;
> -	int   stringlen;
> -	int type;
> -} s2t_map[] = {
> -	{
> -		.string    = "OWNER@",
> -		.stringlen = sizeof("OWNER@") - 1,
> -		.type      = NFS4_ACL_WHO_OWNER,
> -	},
> -	{
> -		.string    = "GROUP@",
> -		.stringlen = sizeof("GROUP@") - 1,
> -		.type      = NFS4_ACL_WHO_GROUP,
> -	},
> -	{
> -		.string    = "EVERYONE@",
> -		.stringlen = sizeof("EVERYONE@") - 1,
> -		.type      = NFS4_ACL_WHO_EVERYONE,
> -	},
> -};
> -
> -int
> -nfs4_acl_get_whotype(char *p, u32 len)
> +__be32 nfsd4_decode_ace_who(struct richace *ace, struct svc_rqst *rqstp,
> +			    char *who, u32 len)
>  {
> -	int i;
> -
> -	for (i = 0; i < ARRAY_SIZE(s2t_map); i++) {
> -		if (s2t_map[i].stringlen == len &&
> -				0 == memcmp(s2t_map[i].string, p, len))
> -			return s2t_map[i].type;
> +	int special_id;
> +
> +	special_id = nfs4acl_who_to_special_id(who, len);
> +	if (special_id >= 0) {
> +		ace->e_flags |= RICHACE_SPECIAL_WHO;
> +		ace->e_flags &= ~RICHACE_IDENTIFIER_GROUP;
> +		ace->e_id.special = special_id;
> +		return nfs_ok;
>  	}
> -	return NFS4_ACL_WHO_NAMED;
> +	if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
> +		return nfsd_map_name_to_gid(rqstp, who, len, &ace->e_id.gid);
> +	else
> +		return nfsd_map_name_to_uid(rqstp, who, len, &ace->e_id.uid);
>  }
>  
> -__be32 nfs4_acl_write_who(struct xdr_stream *xdr, int who)
> +__be32 nfsd4_encode_ace_who(struct xdr_stream *xdr, struct svc_rqst *rqstp,
> +			    struct richace *ace)
>  {
> -	__be32 *p;
> -	int i;
> -
> -	for (i = 0; i < ARRAY_SIZE(s2t_map); i++) {
> -		if (s2t_map[i].type != who)
> -			continue;
> -		p = xdr_reserve_space(xdr, s2t_map[i].stringlen + 4);
> +	if (ace->e_flags & RICHACE_SPECIAL_WHO) {
> +		unsigned int special_id = ace->e_id.special;
> +		const char *who;
> +		unsigned int len;
> +		__be32 *p;
> +
> +		if (!nfs4acl_special_id_to_who(special_id, &who, &len)) {
> +			WARN_ON_ONCE(1);
> +			return nfserr_serverfault;
> +		}
> +		p = xdr_reserve_space(xdr, len + 4);
>  		if (!p)
>  			return nfserr_resource;
> -		p = xdr_encode_opaque(p, s2t_map[i].string,
> -					s2t_map[i].stringlen);
> +		p = xdr_encode_opaque(p, who, len);
>  		return 0;
>  	}
> -	WARN_ON_ONCE(1);
> -	return nfserr_serverfault;
> +	if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
> +		return nfsd4_encode_group(xdr, rqstp, ace->e_id.gid);
> +	else
> +		return nfsd4_encode_user(xdr, rqstp, ace->e_id.uid);
>  }
> diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
> index 90cfda7..8c2cb16 100644
> --- a/fs/nfsd/nfs4proc.c
> +++ b/fs/nfsd/nfs4proc.c
> @@ -159,12 +159,12 @@ is_create_with_attrs(struct nfsd4_open *open)
>   * in the returned attr bitmap.
>   */
>  static void
> -do_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
> -		struct nfs4_acl *acl, u32 *bmval)
> +do_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp, struct richacl *acl,
> +	   u32 *bmval)
>  {
>  	__be32 status;
>  
> -	status = nfsd4_set_nfs4_acl(rqstp, fhp, acl);
> +	status = nfsd4_set_acl(rqstp, fhp, acl);
>  	if (status)
>  		/*
>  		 * We should probably fail the whole open at this point,
> @@ -299,7 +299,7 @@ do_open_lookup(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, stru
>  		goto out;
>  
>  	if (is_create_with_attrs(open) && open->op_acl != NULL)
> -		do_set_nfs4_acl(rqstp, *resfh, open->op_acl, open->op_bmval);
> +		do_set_acl(rqstp, *resfh, open->op_acl, open->op_bmval);
>  
>  	nfsd4_set_open_owner_reply_cache(cstate, open, *resfh);
>  	accmode = NFSD_MAY_NOP;
> @@ -674,8 +674,7 @@ nfsd4_create(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
>  		nfsd4_security_inode_setsecctx(&resfh, &create->cr_label, create->cr_bmval);
>  
>  	if (create->cr_acl != NULL)
> -		do_set_nfs4_acl(rqstp, &resfh, create->cr_acl,
> -				create->cr_bmval);
> +		do_set_acl(rqstp, &resfh, create->cr_acl, create->cr_bmval);
>  
>  	fh_unlock(&cstate->current_fh);
>  	set_change_info(&create->cr_cinfo, &cstate->current_fh);
> @@ -940,8 +939,8 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
>  		goto out;
>  
>  	if (setattr->sa_acl != NULL)
> -		status = nfsd4_set_nfs4_acl(rqstp, &cstate->current_fh,
> -					    setattr->sa_acl);
> +		status = nfsd4_set_acl(rqstp, &cstate->current_fh,
> +				       setattr->sa_acl);
>  	if (status)
>  		goto out;
>  	if (setattr->sa_label.len)
> diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
> index 0768251..465f82a 100644
> --- a/fs/nfsd/nfs4xdr.c
> +++ b/fs/nfsd/nfs4xdr.c
> @@ -303,7 +303,7 @@ nfsd4_decode_bitmap(struct nfsd4_compoundargs *argp, u32 *bmval)
>  
>  static __be32
>  nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
> -		   struct iattr *iattr, struct nfs4_acl **acl,
> +		   struct iattr *iattr, struct richacl **acl,
>  		   struct xdr_netobj *label)
>  {
>  	int expected_len, len = 0;
> @@ -326,38 +326,31 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
>  	}
>  	if (bmval[0] & FATTR4_WORD0_ACL) {
>  		u32 nace;
> -		struct nfs4_ace *ace;
> +		struct richace *ace;
>  
>  		READ_BUF(4); len += 4;
>  		nace = be32_to_cpup(p++);
>  
> -		if (nace > NFS4_ACL_MAX)
> +		if (nace > NFSD4_ACL_MAX)
>  			return nfserr_fbig;
>  
> -		*acl = svcxdr_tmpalloc(argp, nfs4_acl_bytes(nace));
> +		*acl = svcxdr_alloc_richacl(argp, nace);
>  		if (*acl == NULL)
>  			return nfserr_jukebox;
>  
> -		(*acl)->naces = nace;
> -		for (ace = (*acl)->aces; ace < (*acl)->aces + nace; ace++) {
> +		richacl_for_each_entry(ace, *acl) {
>  			READ_BUF(16); len += 16;
> -			ace->type = be32_to_cpup(p++);
> -			ace->flag = be32_to_cpup(p++);
> -			ace->access_mask = be32_to_cpup(p++);
> +			ace->e_type = be32_to_cpup(p++);
> +			ace->e_flags = be32_to_cpup(p++);
> +			ace->e_mask = be32_to_cpup(p++);
> +			if (ace->e_flags & RICHACE_SPECIAL_WHO)
> +				return nfserr_inval;
>  			dummy32 = be32_to_cpup(p++);
>  			READ_BUF(dummy32);
>  			len += XDR_QUADLEN(dummy32) << 2;
>  			READMEM(buf, dummy32);
> -			ace->whotype = nfs4_acl_get_whotype(buf, dummy32);
> -			status = nfs_ok;
> -			if (ace->whotype != NFS4_ACL_WHO_NAMED)
> -				;
> -			else if (ace->flag & NFS4_ACE_IDENTIFIER_GROUP)
> -				status = nfsd_map_name_to_gid(argp->rqstp,
> -						buf, dummy32, &ace->who_gid);
> -			else
> -				status = nfsd_map_name_to_uid(argp->rqstp,
> -						buf, dummy32, &ace->who_uid);
> +			status = nfsd4_decode_ace_who(ace, argp->rqstp,
> +						      buf, dummy32);
>  			if (status)
>  				return status;
>  		}
> @@ -2147,18 +2140,6 @@ static u32 nfs4_file_type(umode_t mode)
>  	};
>  }
>  
> -static inline __be32
> -nfsd4_encode_aclname(struct xdr_stream *xdr, struct svc_rqst *rqstp,
> -		     struct nfs4_ace *ace)
> -{
> -	if (ace->whotype != NFS4_ACL_WHO_NAMED)
> -		return nfs4_acl_write_who(xdr, ace->whotype);
> -	else if (ace->flag & NFS4_ACE_IDENTIFIER_GROUP)
> -		return nfsd4_encode_group(xdr, rqstp, ace->who_gid);
> -	else
> -		return nfsd4_encode_user(xdr, rqstp, ace->who_uid);
> -}
> -
>  #define WORD0_ABSENT_FS_ATTRS (FATTR4_WORD0_FS_LOCATIONS | FATTR4_WORD0_FSID | \
>  			      FATTR4_WORD0_RDATTR_ERROR)
>  #define WORD1_ABSENT_FS_ATTRS FATTR4_WORD1_MOUNTED_ON_FILEID
> @@ -2249,7 +2230,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
>  	u32 rdattr_err = 0;
>  	__be32 status;
>  	int err;
> -	struct nfs4_acl *acl = NULL;
> +	struct richacl *acl = NULL;
>  	void *context = NULL;
>  	int contextlen;
>  	bool contextsupport = false;
> @@ -2295,7 +2276,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
>  		fhp = tempfh;
>  	}
>  	if (bmval0 & FATTR4_WORD0_ACL) {
> -		err = nfsd4_get_nfs4_acl(rqstp, dentry, &acl);
> +		err = nfsd4_get_acl(rqstp, dentry, &acl);
>  		if (err == -EOPNOTSUPP)
>  			bmval0 &= ~FATTR4_WORD0_ACL;
>  		else if (err == -EINVAL) {
> @@ -2469,7 +2450,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
>  		*p++ = cpu_to_be32(rdattr_err);
>  	}
>  	if (bmval0 & FATTR4_WORD0_ACL) {
> -		struct nfs4_ace *ace;
> +		struct richace *ace;
>  
>  		if (acl == NULL) {
>  			p = xdr_reserve_space(xdr, 4);
> @@ -2482,17 +2463,16 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
>  		p = xdr_reserve_space(xdr, 4);
>  		if (!p)
>  			goto out_resource;
> -		*p++ = cpu_to_be32(acl->naces);
> +		*p++ = cpu_to_be32(acl->a_count);
>  
> -		for (ace = acl->aces; ace < acl->aces + acl->naces; ace++) {
> +		richacl_for_each_entry(ace, acl) {
>  			p = xdr_reserve_space(xdr, 4*3);
>  			if (!p)
>  				goto out_resource;
> -			*p++ = cpu_to_be32(ace->type);
> -			*p++ = cpu_to_be32(ace->flag);
> -			*p++ = cpu_to_be32(ace->access_mask &
> -							NFS4_ACE_MASK_ALL);
> -			status = nfsd4_encode_aclname(xdr, rqstp, ace);
> +			*p++ = cpu_to_be32(ace->e_type);
> +			*p++ = cpu_to_be32(ace->e_flags & ~RICHACE_SPECIAL_WHO);
> +			*p++ = cpu_to_be32(ace->e_mask & NFS4_ACE_MASK_ALL);
> +			status = nfsd4_encode_ace_who(xdr, rqstp, ace);
>  			if (status)
>  				goto out;
>  		}
> @@ -2755,7 +2735,7 @@ out:
>  	if (context)
>  		security_release_secctx(context, contextlen);
>  #endif /* CONFIG_NFSD_V4_SECURITY_LABEL */
> -	kfree(acl);
> +	richacl_put(acl);
>  	if (tempfh) {
>  		fh_put(tempfh);
>  		kfree(tempfh);
> diff --git a/fs/nfsd/xdr4.h b/fs/nfsd/xdr4.h
> index b698585..c311066 100644
> --- a/fs/nfsd/xdr4.h
> +++ b/fs/nfsd/xdr4.h
> @@ -118,7 +118,7 @@ struct nfsd4_create {
>  	u32		cr_bmval[3];        /* request */
>  	struct iattr	cr_iattr;           /* request */
>  	struct nfsd4_change_info  cr_cinfo; /* response */
> -	struct nfs4_acl *cr_acl;
> +	struct richacl *cr_acl;
>  	struct xdr_netobj cr_label;
>  };
>  #define cr_datalen	u.link.datalen
> @@ -248,7 +248,7 @@ struct nfsd4_open {
>  	struct nfs4_file *op_file;          /* used during processing */
>  	struct nfs4_ol_stateid *op_stp;	    /* used during processing */
>  	struct nfs4_clnt_odstate *op_odstate; /* used during processing */
> -	struct nfs4_acl *op_acl;
> +	struct richacl *op_acl;
>  	struct xdr_netobj op_label;
>  };
>  
> @@ -332,7 +332,7 @@ struct nfsd4_setattr {
>  	stateid_t	sa_stateid;         /* request */
>  	u32		sa_bmval[3];        /* request */
>  	struct iattr	sa_iattr;           /* request */
> -	struct nfs4_acl *sa_acl;
> +	struct richacl *sa_acl;
>  	struct xdr_netobj sa_label;
>  };
>  
> diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h
> index b8e72aa..992ddc4 100644
> --- a/include/linux/nfs4.h
> +++ b/include/linux/nfs4.h
> @@ -16,29 +16,6 @@
>  #include <linux/uidgid.h>
>  #include <uapi/linux/nfs4.h>
>  
> -enum nfs4_acl_whotype {
> -	NFS4_ACL_WHO_NAMED = 0,
> -	NFS4_ACL_WHO_OWNER,
> -	NFS4_ACL_WHO_GROUP,
> -	NFS4_ACL_WHO_EVERYONE,
> -};
> -
> -struct nfs4_ace {
> -	uint32_t	type;
> -	uint32_t	flag;
> -	uint32_t	access_mask;
> -	int		whotype;
> -	union {
> -		kuid_t	who_uid;
> -		kgid_t	who_gid;
> -	};
> -};
> -
> -struct nfs4_acl {
> -	uint32_t	naces;
> -	struct nfs4_ace	aces[0];
> -};
> -
>  #define NFS4_MAXLABELLEN	2048
>  
>  struct nfs4_label {
> diff --git a/include/linux/nfs4acl.h b/include/linux/nfs4acl.h
> new file mode 100644
> index 0000000..db9f9a6
> --- /dev/null
> +++ b/include/linux/nfs4acl.h
> @@ -0,0 +1,7 @@
> +#ifndef __LINUX_NFS4ACL_H
> +#define __LINUX_NFS4ACL_H
> +
> +int nfs4acl_who_to_special_id(const char *, u32);
> +bool nfs4acl_special_id_to_who(unsigned int, const char **, unsigned int *);
> +
> +#endif
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 30/41] nfsd: Add richacl support
  2015-09-05 10:27     ` Andreas Gruenbacher
  (?)
@ 2015-09-24 19:38     ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-24 19:38 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Sat, Sep 05, 2015 at 12:27:25PM +0200, Andreas Gruenbacher wrote:
> On file systems with richacls enabled, get and set richacls directly
> instead of converting from / to posix acls.

ACK.--b.

> 
> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> ---
>  fs/nfsd/acl.h      |   3 +-
>  fs/nfsd/nfs4acl.c  | 124 ++++++++++++++++++++++++++++++++++++++---------------
>  fs/nfsd/nfs4proc.c |   2 +-
>  fs/nfsd/nfs4xdr.c  |  34 +++++++++++----
>  4 files changed, 117 insertions(+), 46 deletions(-)
> 
> diff --git a/fs/nfsd/acl.h b/fs/nfsd/acl.h
> index 1c5deb5..d73c664 100644
> --- a/fs/nfsd/acl.h
> +++ b/fs/nfsd/acl.h
> @@ -53,8 +53,7 @@ __be32 nfsd4_decode_ace_who(struct richace *ace, struct svc_rqst *rqstp,
>  __be32 nfsd4_encode_ace_who(struct xdr_stream *xdr, struct svc_rqst *rqstp,
>  			    struct richace *ace);
>  
> -int nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
> -		  struct richacl **acl);
> +struct richacl *nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry);
>  __be32 nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
>  		     struct richacl *acl);
>  
> diff --git a/fs/nfsd/nfs4acl.c b/fs/nfsd/nfs4acl.c
> index 582f772..a8d596c 100644
> --- a/fs/nfsd/nfs4acl.c
> +++ b/fs/nfsd/nfs4acl.c
> @@ -38,6 +38,8 @@
>  #include <linux/nfs_fs.h>
>  #include <linux/richacl_compat.h>
>  #include <linux/nfs4acl.h>
> +#include <linux/xattr.h>
> +#include <linux/richacl_xattr.h>
>  #include "nfsfh.h"
>  #include "nfsd.h"
>  #include "idmap.h"
> @@ -126,32 +128,28 @@ static short ace2type(struct richace *);
>  static void _posix_to_richacl_one(struct posix_acl *, struct richacl_alloc *,
>  				unsigned int);
>  
> -int
> -nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
> -	      struct richacl **acl)
> +static struct richacl *
> +nfsd4_get_posix_acl(struct svc_rqst *rqstp, struct dentry *dentry)
>  {
>  	struct inode *inode = d_inode(dentry);
> -	int error = 0;
>  	struct posix_acl *pacl = NULL, *dpacl = NULL;
>  	struct richacl_alloc alloc;
>  	unsigned int flags = 0;
>  	int count;
>  
>  	pacl = get_acl(inode, ACL_TYPE_ACCESS);
> -	if (!pacl)
> -		pacl = posix_acl_from_mode(inode->i_mode, GFP_KERNEL);
> -
> -	if (IS_ERR(pacl))
> -		return PTR_ERR(pacl);
> +	if (IS_ERR_OR_NULL(pacl))
> +		return (void *)pacl;
>  
> -	/* allocate for worst case: one (deny, allow) pair each: */
> +	/* Allocate for worst case: one (deny, allow) pair each.  The resulting
> +	   acl will be released shortly and won't be cached. */
>  	count = 2 * pacl->a_count;
>  
>  	if (S_ISDIR(inode->i_mode)) {
>  		flags = FLAG_DIRECTORY;
>  		dpacl = get_acl(inode, ACL_TYPE_DEFAULT);
>  		if (IS_ERR(dpacl)) {
> -			error = PTR_ERR(dpacl);
> +			alloc.acl = (void *)dpacl;
>  			goto rel_pacl;
>  		}
>  
> @@ -160,7 +158,7 @@ nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
>  	}
>  
>  	if (!richacl_prepare(&alloc, count)) {
> -		error = -ENOMEM;
> +		alloc.acl = ERR_PTR(-ENOMEM);
>  		goto out;
>  	}
>  
> @@ -169,13 +167,37 @@ nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
>  	if (dpacl)
>  		_posix_to_richacl_one(dpacl, &alloc, flags | FLAG_DEFAULT_ACL);
>  
> -	*acl = alloc.acl;
> -
>  out:
>  	posix_acl_release(dpacl);
>  rel_pacl:
>  	posix_acl_release(pacl);
> -	return error;
> +	return alloc.acl;
> +}
> +
> +struct richacl *
> +nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry)
> +{
> +	struct inode *inode = d_inode(dentry);
> +	struct richacl *acl;
> +	int error;
> +
> +	if (IS_RICHACL(inode))
> +		acl = get_richacl(inode);
> +	else
> +		acl = nfsd4_get_posix_acl(rqstp, dentry);
> +	if (IS_ERR(acl))
> +		return acl;
> +	else if (acl == NULL) {
> +		acl = richacl_from_mode(inode->i_mode);
> +		if (acl == NULL)
> +			acl = ERR_PTR(-ENOMEM);
> +	}
> +	error = richacl_apply_masks(&acl, inode->i_uid);
> +	if (error) {
> +		richacl_put(acl);
> +		acl = ERR_PTR(error);
> +	}
> +	return acl;
>  }
>  
>  struct posix_acl_summary {
> @@ -741,56 +763,88 @@ out_estate:
>  	return ret;
>  }
>  
> -__be32
> -nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp, struct richacl *acl)
> +static int
> +nfsd4_set_posix_acl(struct svc_rqst *rqstp, struct dentry *dentry,
> +		    struct richacl *acl)
>  {
> -	__be32 error;
>  	int host_error;
> -	struct dentry *dentry;
> -	struct inode *inode;
> +	struct inode *inode = d_inode(dentry);
>  	struct posix_acl *pacl = NULL, *dpacl = NULL;
>  	unsigned int flags = 0;
>  
> -	/* Get inode */
> -	error = fh_verify(rqstp, fhp, 0, NFSD_MAY_SATTR);
> -	if (error)
> -		return error;
> -
> -	dentry = fhp->fh_dentry;
> -	inode = d_inode(dentry);
> -
>  	if (!inode->i_op->set_acl || !IS_POSIXACL(inode))
> -		return nfserr_attrnotsupp;
> +		return -EOPNOTSUPP;
>  
>  	if (S_ISDIR(inode->i_mode))
>  		flags = FLAG_DIRECTORY;
>  
>  	host_error = nfs4_richacl_to_posix(acl, &pacl, &dpacl, flags);
>  	if (host_error == -EINVAL)
> -		return nfserr_attrnotsupp;
> +		return -EOPNOTSUPP;
>  	if (host_error < 0)
> -		goto out_nfserr;
> +		return host_error;
>  
>  	host_error = inode->i_op->set_acl(inode, pacl, ACL_TYPE_ACCESS);
>  	if (host_error < 0)
>  		goto out_release;
>  
> -	if (S_ISDIR(inode->i_mode)) {
> +	if (S_ISDIR(inode->i_mode))
>  		host_error = inode->i_op->set_acl(inode, dpacl,
>  						  ACL_TYPE_DEFAULT);
> -	}
>  
>  out_release:
>  	posix_acl_release(pacl);
>  	posix_acl_release(dpacl);
> -out_nfserr:
> +	return host_error;
> +}
> +
> +static int
> +nfsd4_set_richacl(struct svc_rqst *rqstp, struct dentry *dentry,
> +		  struct richacl *acl)
> +{
> +	int host_error;
> +	struct inode *inode = d_inode(dentry);
> +	size_t size = richacl_xattr_size(acl);
> +	char *buffer;
> +
> +	if (!inode->i_op->setxattr || !IS_RICHACL(inode))
> +		return -EOPNOTSUPP;
> +
> +	richacl_compute_max_masks(acl);
> +
> +	buffer = kmalloc(size, GFP_KERNEL);
> +	if (!buffer)
> +		return -ENOMEM;
> +	richacl_to_xattr(&init_user_ns, acl, buffer, size);
> +	host_error = inode->i_op->setxattr(dentry, XATTR_NAME_RICHACL,
> +					   buffer, size, 0);
> +	kfree(buffer);
> +	return host_error;
> +}
> +
> +__be32
> +nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp, struct richacl *acl)
> +{
> +	struct dentry *dentry;
> +	int host_error;
> +	__be32 error;
> +
> +	error = fh_verify(rqstp, fhp, 0, NFSD_MAY_SATTR);
> +	if (error)
> +		return error;
> +	dentry = fhp->fh_dentry;
> +
> +	if (IS_RICHACL(d_inode(dentry)))
> +		host_error = nfsd4_set_richacl(rqstp, dentry, acl);
> +	else
> +		host_error = nfsd4_set_posix_acl(rqstp, dentry, acl);
> +
>  	if (host_error == -EOPNOTSUPP)
>  		return nfserr_attrnotsupp;
>  	else
>  		return nfserrno(host_error);
>  }
>  
> -
>  static short
>  ace2type(struct richace *ace)
>  {
> diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
> index 8c2cb16..9f8f0a8 100644
> --- a/fs/nfsd/nfs4proc.c
> +++ b/fs/nfsd/nfs4proc.c
> @@ -110,7 +110,7 @@ check_attr_support(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
>  	 * in current environment or not.
>  	 */
>  	if (bmval[0] & FATTR4_WORD0_ACL) {
> -		if (!IS_POSIXACL(d_inode(dentry)))
> +		if (!IS_ACL(d_inode(dentry)))
>  			return nfserr_attrnotsupp;
>  	}
>  
> diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
> index 465f82a..3280c30 100644
> --- a/fs/nfsd/nfs4xdr.c
> +++ b/fs/nfsd/nfs4xdr.c
> @@ -340,11 +340,24 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
>  
>  		richacl_for_each_entry(ace, *acl) {
>  			READ_BUF(16); len += 16;
> -			ace->e_type = be32_to_cpup(p++);
> -			ace->e_flags = be32_to_cpup(p++);
> -			ace->e_mask = be32_to_cpup(p++);
> -			if (ace->e_flags & RICHACE_SPECIAL_WHO)
> +
> +			dummy32 = be32_to_cpup(p++);
> +			if (dummy32 > RICHACE_ACCESS_DENIED_ACE_TYPE)
> +				return nfserr_inval;
> +			ace->e_type = dummy32;
> +
> +			dummy32 = be32_to_cpup(p++);
> +			if (dummy32 & (~RICHACE_VALID_FLAGS |
> +				       RICHACE_INHERITED_ACE |
> +				       RICHACE_SPECIAL_WHO))
>  				return nfserr_inval;
> +			ace->e_flags = dummy32;
> +
> +			dummy32 = be32_to_cpup(p++);
> +			if (dummy32 & ~NFS4_ACE_MASK_ALL)
> +				return nfserr_inval;
> +			ace->e_mask = dummy32;
> +
>  			dummy32 = be32_to_cpup(p++);
>  			READ_BUF(dummy32);
>  			len += XDR_QUADLEN(dummy32) << 2;
> @@ -2276,7 +2289,11 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
>  		fhp = tempfh;
>  	}
>  	if (bmval0 & FATTR4_WORD0_ACL) {
> -		err = nfsd4_get_acl(rqstp, dentry, &acl);
> +		acl = nfsd4_get_acl(rqstp, dentry);
> +		if (IS_ERR(acl)) {
> +			err = PTR_ERR(acl);
> +			acl = NULL;
> +		}
>  		if (err == -EOPNOTSUPP)
>  			bmval0 &= ~FATTR4_WORD0_ACL;
>  		else if (err == -EINVAL) {
> @@ -2335,7 +2352,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
>  		u32 word1 = nfsd_suppattrs1(minorversion);
>  		u32 word2 = nfsd_suppattrs2(minorversion);
>  
> -		if (!IS_POSIXACL(dentry->d_inode))
> +		if (!IS_ACL(d_inode(dentry)))
>  			word0 &= ~FATTR4_WORD0_ACL;
>  		if (!contextsupport)
>  			word2 &= ~FATTR4_WORD2_SECURITY_LABEL;
> @@ -2470,7 +2487,8 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
>  			if (!p)
>  				goto out_resource;
>  			*p++ = cpu_to_be32(ace->e_type);
> -			*p++ = cpu_to_be32(ace->e_flags & ~RICHACE_SPECIAL_WHO);
> +			*p++ = cpu_to_be32(ace->e_flags &
> +				~(RICHACE_SPECIAL_WHO | RICHACE_INHERITED_ACE));
>  			*p++ = cpu_to_be32(ace->e_mask & NFS4_ACE_MASK_ALL);
>  			status = nfsd4_encode_ace_who(xdr, rqstp, ace);
>  			if (status)
> @@ -2482,7 +2500,7 @@ out_acl:
>  		p = xdr_reserve_space(xdr, 4);
>  		if (!p)
>  			goto out_resource;
> -		*p++ = cpu_to_be32(IS_POSIXACL(dentry->d_inode) ?
> +		*p++ = cpu_to_be32(IS_ACL(d_inode(dentry)) ?
>  			ACL4_SUPPORT_ALLOW_ACL|ACL4_SUPPORT_DENY_ACL : 0);
>  	}
>  	if (bmval0 & FATTR4_WORD0_CANSETTIME) {
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 31/41] nfsd: Add support for the v4.1 dacl attribute
  2015-09-05 10:27     ` Andreas Gruenbacher
@ 2015-09-24 19:59         ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-24 19:59 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

On Sat, Sep 05, 2015 at 12:27:26PM +0200, Andreas Gruenbacher wrote:
> Richacls support the Automatic Inheritance permission propagation
> mechanism as specified in NFSv4.1.  Over NFS, this requires support for
> the dacl attribute: compared to the acl attribute, the dacl attribute
> has an additional flags field which indicates when Automatic Inheritance
> is in use.
> 
> The server will only indicate dacl attribute support in protocol version
> 4.1 and later, on file systems with richacl support.
> 
> This commit also adds support for the NFSv4.1 NFS4_ACE_WRITE_RETENTION
> and NFS4_ACE_WRITE_RETENTION_HOLD ACL permissions.

OK, not useful at this point, but I guess it makes sense to go ahead and
allow it.

And looks like when accessed over an insufficient protocol version,
you're zeroing out the extra bits on read and erroring out (with INVAL)
on write.  Looks sensible.

> 
> Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> ---
>  fs/nfsd/nfs4proc.c        |   2 +
>  fs/nfsd/nfs4xdr.c         | 219 ++++++++++++++++++++++++++++++----------------
>  fs/nfsd/nfsd.h            |   6 +-
>  include/linux/nfs4.h      |   1 +
>  include/uapi/linux/nfs4.h |   3 +-
>  5 files changed, 155 insertions(+), 76 deletions(-)
> 
> diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
> index 9f8f0a8..ef9e6cd 100644
> --- a/fs/nfsd/nfs4proc.c
> +++ b/fs/nfsd/nfs4proc.c
> @@ -1804,6 +1804,8 @@ static inline u32 nfsd4_getattr_rsize(struct svc_rqst *rqstp,
>  		ret += NFS4_FHSIZE + 4;
>  		bmap0 &= ~FATTR4_WORD0_FILEHANDLE;
>  	}
> +	if (bmap1 & FATTR4_WORD1_DACL)
> +		return svc_max_payload(rqstp);

I'd probably move this up top with the other ACL case.

But that's a nit, looks fine.  ACK to the patch.

--b.

>  	if (bmap2 & FATTR4_WORD2_SECURITY_LABEL) {
>  		ret += NFS4_MAXLABELLEN + 12;
>  		bmap2 &= ~FATTR4_WORD2_SECURITY_LABEL;
> diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
> index 3280c30..b66063d 100644
> --- a/fs/nfsd/nfs4xdr.c
> +++ b/fs/nfsd/nfs4xdr.c
> @@ -301,6 +301,68 @@ nfsd4_decode_bitmap(struct nfsd4_compoundargs *argp, u32 *bmval)
>  	DECODE_TAIL;
>  }
>  
> +static unsigned int
> +nfsd4_ace_mask(int minorversion)
> +{
> +	return minorversion == 0 ?  NFS40_ACE_MASK_ALL : NFS4_ACE_MASK_ALL;
> +}
> +
> +static __be32
> +nfsd4_decode_acl_entries(struct nfsd4_compoundargs *argp, struct richacl **acl,
> +			 unsigned short flags_mask, unsigned int ace_mask,
> +			 int *plen)
> +{
> +	struct richace *ace;
> +	u32 dummy32;
> +	char *buf;
> +	int len = 0;
> +
> +	DECODE_HEAD;
> +
> +	flags_mask &= RICHACE_VALID_FLAGS & ~RICHACE_SPECIAL_WHO;
> +
> +	READ_BUF(4); len += 4;
> +	dummy32 = be32_to_cpup(p++);
> +
> +	if (dummy32 > NFSD4_ACL_MAX)
> +		return nfserr_fbig;
> +
> +	*acl = svcxdr_alloc_richacl(argp, dummy32);
> +	if (*acl == NULL)
> +		return nfserr_jukebox;
> +
> +	richacl_for_each_entry(ace, *acl) {
> +		READ_BUF(16); len += 16;
> +
> +		dummy32 = be32_to_cpup(p++);
> +		if (dummy32 > RICHACE_ACCESS_DENIED_ACE_TYPE)
> +			return nfserr_inval;
> +		ace->e_type = dummy32;
> +
> +		dummy32 = be32_to_cpup(p++);
> +		if (dummy32 & ~flags_mask)
> +			return nfserr_inval;
> +		ace->e_flags = dummy32;
> +
> +		dummy32 = be32_to_cpup(p++);
> +		if (dummy32 & ~ace_mask)
> +			return nfserr_inval;
> +		ace->e_mask = dummy32;
> +
> +		dummy32 = be32_to_cpup(p++);
> +		READ_BUF(dummy32);
> +		len += XDR_QUADLEN(dummy32) << 2;
> +		READMEM(buf, dummy32);
> +		status = nfsd4_decode_ace_who(ace, argp->rqstp,
> +					      buf, dummy32);
> +		if (status)
> +			return status;
> +	}
> +	*plen += len;
> +
> +	DECODE_TAIL;
> +}
> +
>  static __be32
>  nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
>  		   struct iattr *iattr, struct richacl **acl,
> @@ -312,6 +374,7 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
>  
>  	DECODE_HEAD;
>  	iattr->ia_valid = 0;
> +	*acl = NULL;
>  	if ((status = nfsd4_decode_bitmap(argp, bmval)))
>  		return status;
>  
> @@ -325,50 +388,18 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
>  		iattr->ia_valid |= ATTR_SIZE;
>  	}
>  	if (bmval[0] & FATTR4_WORD0_ACL) {
> -		u32 nace;
> -		struct richace *ace;
> -
> -		READ_BUF(4); len += 4;
> -		nace = be32_to_cpup(p++);
> -
> -		if (nace > NFSD4_ACL_MAX)
> -			return nfserr_fbig;
> +		if (bmval[1] & FATTR4_WORD1_DACL)
> +			return nfserr_inval;
>  
> -		*acl = svcxdr_alloc_richacl(argp, nace);
> -		if (*acl == NULL)
> +		status = nfsd4_decode_acl_entries(argp, acl,
> +				~NFS4_ACE_INHERITED_ACE,
> +				nfsd4_ace_mask(argp->minorversion),
> +				&len);
> +		if (status)
> +			return status;
> +		else if (*acl == NULL)
>  			return nfserr_jukebox;
> -
> -		richacl_for_each_entry(ace, *acl) {
> -			READ_BUF(16); len += 16;
> -
> -			dummy32 = be32_to_cpup(p++);
> -			if (dummy32 > RICHACE_ACCESS_DENIED_ACE_TYPE)
> -				return nfserr_inval;
> -			ace->e_type = dummy32;
> -
> -			dummy32 = be32_to_cpup(p++);
> -			if (dummy32 & (~RICHACE_VALID_FLAGS |
> -				       RICHACE_INHERITED_ACE |
> -				       RICHACE_SPECIAL_WHO))
> -				return nfserr_inval;
> -			ace->e_flags = dummy32;
> -
> -			dummy32 = be32_to_cpup(p++);
> -			if (dummy32 & ~NFS4_ACE_MASK_ALL)
> -				return nfserr_inval;
> -			ace->e_mask = dummy32;
> -
> -			dummy32 = be32_to_cpup(p++);
> -			READ_BUF(dummy32);
> -			len += XDR_QUADLEN(dummy32) << 2;
> -			READMEM(buf, dummy32);
> -			status = nfsd4_decode_ace_who(ace, argp->rqstp,
> -						      buf, dummy32);
> -			if (status)
> -				return status;
> -		}
> -	} else
> -		*acl = NULL;
> +	}
>  	if (bmval[1] & FATTR4_WORD1_MODE) {
>  		READ_BUF(4);
>  		len += 4;
> @@ -436,6 +467,22 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
>  			goto xdr_error;
>  		}
>  	}
> +	if (bmval[1] & FATTR4_WORD1_DACL) {
> +		READ_BUF(4);
> +		len += 4;
> +		dummy32 = be32_to_cpup(p++);
> +		if (dummy32 & (~RICHACL_VALID_FLAGS | RICHACL_MASKED))
> +			return nfserr_inval;
> +		status = nfsd4_decode_acl_entries(argp, acl,
> +				~0,
> +				nfsd4_ace_mask(argp->minorversion),
> +				&len);
> +		if (status)
> +			return status;
> +		else if (*acl == NULL)
> +			return nfserr_jukebox;
> +		(*acl)->a_flags = dummy32;
> +	}
>  
>  	label->len = 0;
>  #ifdef CONFIG_NFSD_V4_SECURITY_LABEL
> @@ -2218,6 +2265,42 @@ static int get_parent_attributes(struct svc_export *exp, struct kstat *stat)
>  	return err;
>  }
>  
> +static __be32 nfsd4_encode_acl_entries(struct xdr_stream *xdr,
> +		struct richacl *acl, struct svc_rqst *rqstp,
> +		unsigned short flags_mask, unsigned int ace_mask)
> +{
> +	__be32 *p;
> +
> +	flags_mask &= ~RICHACE_SPECIAL_WHO;
> +
> +	p = xdr_reserve_space(xdr, 4);
> +	if (!p)
> +		return nfserr_resource;
> +
> +	if (acl == NULL) {
> +		*p++ = cpu_to_be32(0);
> +	} else {
> +		struct richace *ace;
> +
> +		*p++ = cpu_to_be32(acl->a_count);
> +
> +		richacl_for_each_entry(ace, acl) {
> +			__be32 status;
> +
> +			p = xdr_reserve_space(xdr, 4*3);
> +			if (!p)
> +				return nfserr_resource;
> +			*p++ = cpu_to_be32(ace->e_type);
> +			*p++ = cpu_to_be32(ace->e_flags & flags_mask);
> +			*p++ = cpu_to_be32(ace->e_mask & ace_mask);
> +			status = nfsd4_encode_ace_who(xdr, rqstp, ace);
> +			if (status)
> +				return status;
> +		}
> +	}
> +	return 0;
> +}
> +
>  /*
>   * Note: @fhp can be NULL; in this case, we might have to compose the filehandle
>   * ourselves.
> @@ -2288,15 +2371,16 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
>  			goto out;
>  		fhp = tempfh;
>  	}
> -	if (bmval0 & FATTR4_WORD0_ACL) {
> +	if ((bmval0 & FATTR4_WORD0_ACL) || (bmval1 & FATTR4_WORD1_DACL)) {
>  		acl = nfsd4_get_acl(rqstp, dentry);
>  		if (IS_ERR(acl)) {
>  			err = PTR_ERR(acl);
>  			acl = NULL;
>  		}
> -		if (err == -EOPNOTSUPP)
> +		if (err == -EOPNOTSUPP) {
>  			bmval0 &= ~FATTR4_WORD0_ACL;
> -		else if (err == -EINVAL) {
> +			bmval1 &= ~FATTR4_WORD1_DACL;
> +		} else if (err == -EINVAL) {
>  			status = nfserr_attrnotsupp;
>  			goto out;
>  		} else if (err != 0)
> @@ -2354,6 +2438,8 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
>  
>  		if (!IS_ACL(d_inode(dentry)))
>  			word0 &= ~FATTR4_WORD0_ACL;
> +		if (!IS_RICHACL(d_inode(dentry)))
> +			word1 &= ~FATTR4_WORD1_DACL;
>  		if (!contextsupport)
>  			word2 &= ~FATTR4_WORD2_SECURITY_LABEL;
>  		if (!word2) {
> @@ -2467,35 +2553,12 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
>  		*p++ = cpu_to_be32(rdattr_err);
>  	}
>  	if (bmval0 & FATTR4_WORD0_ACL) {
> -		struct richace *ace;
> -
> -		if (acl == NULL) {
> -			p = xdr_reserve_space(xdr, 4);
> -			if (!p)
> -				goto out_resource;
> -
> -			*p++ = cpu_to_be32(0);
> -			goto out_acl;
> -		}
> -		p = xdr_reserve_space(xdr, 4);
> -		if (!p)
> -			goto out_resource;
> -		*p++ = cpu_to_be32(acl->a_count);
> -
> -		richacl_for_each_entry(ace, acl) {
> -			p = xdr_reserve_space(xdr, 4*3);
> -			if (!p)
> -				goto out_resource;
> -			*p++ = cpu_to_be32(ace->e_type);
> -			*p++ = cpu_to_be32(ace->e_flags &
> -				~(RICHACE_SPECIAL_WHO | RICHACE_INHERITED_ACE));
> -			*p++ = cpu_to_be32(ace->e_mask & NFS4_ACE_MASK_ALL);
> -			status = nfsd4_encode_ace_who(xdr, rqstp, ace);
> -			if (status)
> -				goto out;
> -		}
> +		status = nfsd4_encode_acl_entries(xdr, acl, rqstp,
> +				~NFS4_ACE_INHERITED_ACE,
> +				nfsd4_ace_mask(minorversion));
> +		if (status)
> +			goto out;
>  	}
> -out_acl:
>  	if (bmval0 & FATTR4_WORD0_ACLSUPPORT) {
>  		p = xdr_reserve_space(xdr, 4);
>  		if (!p)
> @@ -2704,6 +2767,16 @@ out_acl:
>  			get_parent_attributes(exp, &stat);
>  		p = xdr_encode_hyper(p, stat.ino);
>  	}
> +	if (bmval1 & FATTR4_WORD1_DACL) {
> +		p = xdr_reserve_space(xdr, 4);
> +		if (!p)
> +			goto out_resource;
> +		*p++ = cpu_to_be32(acl->a_flags);
> +		status = nfsd4_encode_acl_entries(xdr, acl, rqstp,
> +				~0, nfsd4_ace_mask(minorversion));
> +		if (status)
> +			goto out;
> +	}
>  #ifdef CONFIG_NFSD_PNFS
>  	if ((bmval1 & FATTR4_WORD1_FS_LAYOUT_TYPES) ||
>  	    (bmval2 & FATTR4_WORD2_LAYOUT_TYPES)) {
> diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h
> index cf98052..cb5c3ed 100644
> --- a/fs/nfsd/nfsd.h
> +++ b/fs/nfsd/nfsd.h
> @@ -339,7 +339,8 @@ void		nfsd_lockd_shutdown(void);
>  	NFSD4_SUPPORTED_ATTRS_WORD0
>  
>  #define NFSD4_1_SUPPORTED_ATTRS_WORD1 \
> -	(NFSD4_SUPPORTED_ATTRS_WORD1	| PNFSD_SUPPORTED_ATTRS_WORD1)
> +	(NFSD4_SUPPORTED_ATTRS_WORD1	| PNFSD_SUPPORTED_ATTRS_WORD1 | \
> +	 FATTR4_WORD1_DACL)
>  
>  #define NFSD4_1_SUPPORTED_ATTRS_WORD2 \
>  	(NFSD4_SUPPORTED_ATTRS_WORD2	| PNFSD_SUPPORTED_ATTRS_WORD2 | \
> @@ -386,7 +387,8 @@ static inline u32 nfsd_suppattrs2(u32 minorversion)
>  	(FATTR4_WORD0_SIZE | FATTR4_WORD0_ACL)
>  #define NFSD_WRITEABLE_ATTRS_WORD1 \
>  	(FATTR4_WORD1_MODE | FATTR4_WORD1_OWNER | FATTR4_WORD1_OWNER_GROUP \
> -	| FATTR4_WORD1_TIME_ACCESS_SET | FATTR4_WORD1_TIME_MODIFY_SET)
> +	| FATTR4_WORD1_TIME_ACCESS_SET | FATTR4_WORD1_TIME_MODIFY_SET \
> +	| FATTR4_WORD1_DACL)
>  #ifdef CONFIG_NFSD_V4_SECURITY_LABEL
>  #define NFSD_WRITEABLE_ATTRS_WORD2 FATTR4_WORD2_SECURITY_LABEL
>  #else
> diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h
> index 992ddc4..1bd4aea 100644
> --- a/include/linux/nfs4.h
> +++ b/include/linux/nfs4.h
> @@ -394,6 +394,7 @@ enum lock_type4 {
>  #define FATTR4_WORD1_TIME_MODIFY        (1UL << 21)
>  #define FATTR4_WORD1_TIME_MODIFY_SET    (1UL << 22)
>  #define FATTR4_WORD1_MOUNTED_ON_FILEID  (1UL << 23)
> +#define FATTR4_WORD1_DACL               (1UL << 26)
>  #define FATTR4_WORD1_FS_LAYOUT_TYPES    (1UL << 30)
>  #define FATTR4_WORD2_LAYOUT_TYPES       (1UL << 0)
>  #define FATTR4_WORD2_LAYOUT_BLKSIZE     (1UL << 1)
> diff --git a/include/uapi/linux/nfs4.h b/include/uapi/linux/nfs4.h
> index 2119c7c..64e4c6c 100644
> --- a/include/uapi/linux/nfs4.h
> +++ b/include/uapi/linux/nfs4.h
> @@ -121,7 +121,8 @@
>  #define NFS4_ACE_GENERIC_READ                 0x00120081
>  #define NFS4_ACE_GENERIC_WRITE                0x00160106
>  #define NFS4_ACE_GENERIC_EXECUTE              0x001200A0
> -#define NFS4_ACE_MASK_ALL                     0x001F01FF
> +#define NFS40_ACE_MASK_ALL                    0x001F01FF
> +#define NFS4_ACE_MASK_ALL                     0x001F07FF
>  
>  #define EXCHGID4_FLAG_SUPP_MOVED_REFER		0x00000001
>  #define EXCHGID4_FLAG_SUPP_MOVED_MIGR		0x00000002
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 31/41] nfsd: Add support for the v4.1 dacl attribute
@ 2015-09-24 19:59         ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-24 19:59 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Sat, Sep 05, 2015 at 12:27:26PM +0200, Andreas Gruenbacher wrote:
> Richacls support the Automatic Inheritance permission propagation
> mechanism as specified in NFSv4.1.  Over NFS, this requires support for
> the dacl attribute: compared to the acl attribute, the dacl attribute
> has an additional flags field which indicates when Automatic Inheritance
> is in use.
> 
> The server will only indicate dacl attribute support in protocol version
> 4.1 and later, on file systems with richacl support.
> 
> This commit also adds support for the NFSv4.1 NFS4_ACE_WRITE_RETENTION
> and NFS4_ACE_WRITE_RETENTION_HOLD ACL permissions.

OK, not useful at this point, but I guess it makes sense to go ahead and
allow it.

And looks like when accessed over an insufficient protocol version,
you're zeroing out the extra bits on read and erroring out (with INVAL)
on write.  Looks sensible.

> 
> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> ---
>  fs/nfsd/nfs4proc.c        |   2 +
>  fs/nfsd/nfs4xdr.c         | 219 ++++++++++++++++++++++++++++++----------------
>  fs/nfsd/nfsd.h            |   6 +-
>  include/linux/nfs4.h      |   1 +
>  include/uapi/linux/nfs4.h |   3 +-
>  5 files changed, 155 insertions(+), 76 deletions(-)
> 
> diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
> index 9f8f0a8..ef9e6cd 100644
> --- a/fs/nfsd/nfs4proc.c
> +++ b/fs/nfsd/nfs4proc.c
> @@ -1804,6 +1804,8 @@ static inline u32 nfsd4_getattr_rsize(struct svc_rqst *rqstp,
>  		ret += NFS4_FHSIZE + 4;
>  		bmap0 &= ~FATTR4_WORD0_FILEHANDLE;
>  	}
> +	if (bmap1 & FATTR4_WORD1_DACL)
> +		return svc_max_payload(rqstp);

I'd probably move this up top with the other ACL case.

But that's a nit, looks fine.  ACK to the patch.

--b.

>  	if (bmap2 & FATTR4_WORD2_SECURITY_LABEL) {
>  		ret += NFS4_MAXLABELLEN + 12;
>  		bmap2 &= ~FATTR4_WORD2_SECURITY_LABEL;
> diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
> index 3280c30..b66063d 100644
> --- a/fs/nfsd/nfs4xdr.c
> +++ b/fs/nfsd/nfs4xdr.c
> @@ -301,6 +301,68 @@ nfsd4_decode_bitmap(struct nfsd4_compoundargs *argp, u32 *bmval)
>  	DECODE_TAIL;
>  }
>  
> +static unsigned int
> +nfsd4_ace_mask(int minorversion)
> +{
> +	return minorversion == 0 ?  NFS40_ACE_MASK_ALL : NFS4_ACE_MASK_ALL;
> +}
> +
> +static __be32
> +nfsd4_decode_acl_entries(struct nfsd4_compoundargs *argp, struct richacl **acl,
> +			 unsigned short flags_mask, unsigned int ace_mask,
> +			 int *plen)
> +{
> +	struct richace *ace;
> +	u32 dummy32;
> +	char *buf;
> +	int len = 0;
> +
> +	DECODE_HEAD;
> +
> +	flags_mask &= RICHACE_VALID_FLAGS & ~RICHACE_SPECIAL_WHO;
> +
> +	READ_BUF(4); len += 4;
> +	dummy32 = be32_to_cpup(p++);
> +
> +	if (dummy32 > NFSD4_ACL_MAX)
> +		return nfserr_fbig;
> +
> +	*acl = svcxdr_alloc_richacl(argp, dummy32);
> +	if (*acl == NULL)
> +		return nfserr_jukebox;
> +
> +	richacl_for_each_entry(ace, *acl) {
> +		READ_BUF(16); len += 16;
> +
> +		dummy32 = be32_to_cpup(p++);
> +		if (dummy32 > RICHACE_ACCESS_DENIED_ACE_TYPE)
> +			return nfserr_inval;
> +		ace->e_type = dummy32;
> +
> +		dummy32 = be32_to_cpup(p++);
> +		if (dummy32 & ~flags_mask)
> +			return nfserr_inval;
> +		ace->e_flags = dummy32;
> +
> +		dummy32 = be32_to_cpup(p++);
> +		if (dummy32 & ~ace_mask)
> +			return nfserr_inval;
> +		ace->e_mask = dummy32;
> +
> +		dummy32 = be32_to_cpup(p++);
> +		READ_BUF(dummy32);
> +		len += XDR_QUADLEN(dummy32) << 2;
> +		READMEM(buf, dummy32);
> +		status = nfsd4_decode_ace_who(ace, argp->rqstp,
> +					      buf, dummy32);
> +		if (status)
> +			return status;
> +	}
> +	*plen += len;
> +
> +	DECODE_TAIL;
> +}
> +
>  static __be32
>  nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
>  		   struct iattr *iattr, struct richacl **acl,
> @@ -312,6 +374,7 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
>  
>  	DECODE_HEAD;
>  	iattr->ia_valid = 0;
> +	*acl = NULL;
>  	if ((status = nfsd4_decode_bitmap(argp, bmval)))
>  		return status;
>  
> @@ -325,50 +388,18 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
>  		iattr->ia_valid |= ATTR_SIZE;
>  	}
>  	if (bmval[0] & FATTR4_WORD0_ACL) {
> -		u32 nace;
> -		struct richace *ace;
> -
> -		READ_BUF(4); len += 4;
> -		nace = be32_to_cpup(p++);
> -
> -		if (nace > NFSD4_ACL_MAX)
> -			return nfserr_fbig;
> +		if (bmval[1] & FATTR4_WORD1_DACL)
> +			return nfserr_inval;
>  
> -		*acl = svcxdr_alloc_richacl(argp, nace);
> -		if (*acl == NULL)
> +		status = nfsd4_decode_acl_entries(argp, acl,
> +				~NFS4_ACE_INHERITED_ACE,
> +				nfsd4_ace_mask(argp->minorversion),
> +				&len);
> +		if (status)
> +			return status;
> +		else if (*acl == NULL)
>  			return nfserr_jukebox;
> -
> -		richacl_for_each_entry(ace, *acl) {
> -			READ_BUF(16); len += 16;
> -
> -			dummy32 = be32_to_cpup(p++);
> -			if (dummy32 > RICHACE_ACCESS_DENIED_ACE_TYPE)
> -				return nfserr_inval;
> -			ace->e_type = dummy32;
> -
> -			dummy32 = be32_to_cpup(p++);
> -			if (dummy32 & (~RICHACE_VALID_FLAGS |
> -				       RICHACE_INHERITED_ACE |
> -				       RICHACE_SPECIAL_WHO))
> -				return nfserr_inval;
> -			ace->e_flags = dummy32;
> -
> -			dummy32 = be32_to_cpup(p++);
> -			if (dummy32 & ~NFS4_ACE_MASK_ALL)
> -				return nfserr_inval;
> -			ace->e_mask = dummy32;
> -
> -			dummy32 = be32_to_cpup(p++);
> -			READ_BUF(dummy32);
> -			len += XDR_QUADLEN(dummy32) << 2;
> -			READMEM(buf, dummy32);
> -			status = nfsd4_decode_ace_who(ace, argp->rqstp,
> -						      buf, dummy32);
> -			if (status)
> -				return status;
> -		}
> -	} else
> -		*acl = NULL;
> +	}
>  	if (bmval[1] & FATTR4_WORD1_MODE) {
>  		READ_BUF(4);
>  		len += 4;
> @@ -436,6 +467,22 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
>  			goto xdr_error;
>  		}
>  	}
> +	if (bmval[1] & FATTR4_WORD1_DACL) {
> +		READ_BUF(4);
> +		len += 4;
> +		dummy32 = be32_to_cpup(p++);
> +		if (dummy32 & (~RICHACL_VALID_FLAGS | RICHACL_MASKED))
> +			return nfserr_inval;
> +		status = nfsd4_decode_acl_entries(argp, acl,
> +				~0,
> +				nfsd4_ace_mask(argp->minorversion),
> +				&len);
> +		if (status)
> +			return status;
> +		else if (*acl == NULL)
> +			return nfserr_jukebox;
> +		(*acl)->a_flags = dummy32;
> +	}
>  
>  	label->len = 0;
>  #ifdef CONFIG_NFSD_V4_SECURITY_LABEL
> @@ -2218,6 +2265,42 @@ static int get_parent_attributes(struct svc_export *exp, struct kstat *stat)
>  	return err;
>  }
>  
> +static __be32 nfsd4_encode_acl_entries(struct xdr_stream *xdr,
> +		struct richacl *acl, struct svc_rqst *rqstp,
> +		unsigned short flags_mask, unsigned int ace_mask)
> +{
> +	__be32 *p;
> +
> +	flags_mask &= ~RICHACE_SPECIAL_WHO;
> +
> +	p = xdr_reserve_space(xdr, 4);
> +	if (!p)
> +		return nfserr_resource;
> +
> +	if (acl == NULL) {
> +		*p++ = cpu_to_be32(0);
> +	} else {
> +		struct richace *ace;
> +
> +		*p++ = cpu_to_be32(acl->a_count);
> +
> +		richacl_for_each_entry(ace, acl) {
> +			__be32 status;
> +
> +			p = xdr_reserve_space(xdr, 4*3);
> +			if (!p)
> +				return nfserr_resource;
> +			*p++ = cpu_to_be32(ace->e_type);
> +			*p++ = cpu_to_be32(ace->e_flags & flags_mask);
> +			*p++ = cpu_to_be32(ace->e_mask & ace_mask);
> +			status = nfsd4_encode_ace_who(xdr, rqstp, ace);
> +			if (status)
> +				return status;
> +		}
> +	}
> +	return 0;
> +}
> +
>  /*
>   * Note: @fhp can be NULL; in this case, we might have to compose the filehandle
>   * ourselves.
> @@ -2288,15 +2371,16 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
>  			goto out;
>  		fhp = tempfh;
>  	}
> -	if (bmval0 & FATTR4_WORD0_ACL) {
> +	if ((bmval0 & FATTR4_WORD0_ACL) || (bmval1 & FATTR4_WORD1_DACL)) {
>  		acl = nfsd4_get_acl(rqstp, dentry);
>  		if (IS_ERR(acl)) {
>  			err = PTR_ERR(acl);
>  			acl = NULL;
>  		}
> -		if (err == -EOPNOTSUPP)
> +		if (err == -EOPNOTSUPP) {
>  			bmval0 &= ~FATTR4_WORD0_ACL;
> -		else if (err == -EINVAL) {
> +			bmval1 &= ~FATTR4_WORD1_DACL;
> +		} else if (err == -EINVAL) {
>  			status = nfserr_attrnotsupp;
>  			goto out;
>  		} else if (err != 0)
> @@ -2354,6 +2438,8 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
>  
>  		if (!IS_ACL(d_inode(dentry)))
>  			word0 &= ~FATTR4_WORD0_ACL;
> +		if (!IS_RICHACL(d_inode(dentry)))
> +			word1 &= ~FATTR4_WORD1_DACL;
>  		if (!contextsupport)
>  			word2 &= ~FATTR4_WORD2_SECURITY_LABEL;
>  		if (!word2) {
> @@ -2467,35 +2553,12 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
>  		*p++ = cpu_to_be32(rdattr_err);
>  	}
>  	if (bmval0 & FATTR4_WORD0_ACL) {
> -		struct richace *ace;
> -
> -		if (acl == NULL) {
> -			p = xdr_reserve_space(xdr, 4);
> -			if (!p)
> -				goto out_resource;
> -
> -			*p++ = cpu_to_be32(0);
> -			goto out_acl;
> -		}
> -		p = xdr_reserve_space(xdr, 4);
> -		if (!p)
> -			goto out_resource;
> -		*p++ = cpu_to_be32(acl->a_count);
> -
> -		richacl_for_each_entry(ace, acl) {
> -			p = xdr_reserve_space(xdr, 4*3);
> -			if (!p)
> -				goto out_resource;
> -			*p++ = cpu_to_be32(ace->e_type);
> -			*p++ = cpu_to_be32(ace->e_flags &
> -				~(RICHACE_SPECIAL_WHO | RICHACE_INHERITED_ACE));
> -			*p++ = cpu_to_be32(ace->e_mask & NFS4_ACE_MASK_ALL);
> -			status = nfsd4_encode_ace_who(xdr, rqstp, ace);
> -			if (status)
> -				goto out;
> -		}
> +		status = nfsd4_encode_acl_entries(xdr, acl, rqstp,
> +				~NFS4_ACE_INHERITED_ACE,
> +				nfsd4_ace_mask(minorversion));
> +		if (status)
> +			goto out;
>  	}
> -out_acl:
>  	if (bmval0 & FATTR4_WORD0_ACLSUPPORT) {
>  		p = xdr_reserve_space(xdr, 4);
>  		if (!p)
> @@ -2704,6 +2767,16 @@ out_acl:
>  			get_parent_attributes(exp, &stat);
>  		p = xdr_encode_hyper(p, stat.ino);
>  	}
> +	if (bmval1 & FATTR4_WORD1_DACL) {
> +		p = xdr_reserve_space(xdr, 4);
> +		if (!p)
> +			goto out_resource;
> +		*p++ = cpu_to_be32(acl->a_flags);
> +		status = nfsd4_encode_acl_entries(xdr, acl, rqstp,
> +				~0, nfsd4_ace_mask(minorversion));
> +		if (status)
> +			goto out;
> +	}
>  #ifdef CONFIG_NFSD_PNFS
>  	if ((bmval1 & FATTR4_WORD1_FS_LAYOUT_TYPES) ||
>  	    (bmval2 & FATTR4_WORD2_LAYOUT_TYPES)) {
> diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h
> index cf98052..cb5c3ed 100644
> --- a/fs/nfsd/nfsd.h
> +++ b/fs/nfsd/nfsd.h
> @@ -339,7 +339,8 @@ void		nfsd_lockd_shutdown(void);
>  	NFSD4_SUPPORTED_ATTRS_WORD0
>  
>  #define NFSD4_1_SUPPORTED_ATTRS_WORD1 \
> -	(NFSD4_SUPPORTED_ATTRS_WORD1	| PNFSD_SUPPORTED_ATTRS_WORD1)
> +	(NFSD4_SUPPORTED_ATTRS_WORD1	| PNFSD_SUPPORTED_ATTRS_WORD1 | \
> +	 FATTR4_WORD1_DACL)
>  
>  #define NFSD4_1_SUPPORTED_ATTRS_WORD2 \
>  	(NFSD4_SUPPORTED_ATTRS_WORD2	| PNFSD_SUPPORTED_ATTRS_WORD2 | \
> @@ -386,7 +387,8 @@ static inline u32 nfsd_suppattrs2(u32 minorversion)
>  	(FATTR4_WORD0_SIZE | FATTR4_WORD0_ACL)
>  #define NFSD_WRITEABLE_ATTRS_WORD1 \
>  	(FATTR4_WORD1_MODE | FATTR4_WORD1_OWNER | FATTR4_WORD1_OWNER_GROUP \
> -	| FATTR4_WORD1_TIME_ACCESS_SET | FATTR4_WORD1_TIME_MODIFY_SET)
> +	| FATTR4_WORD1_TIME_ACCESS_SET | FATTR4_WORD1_TIME_MODIFY_SET \
> +	| FATTR4_WORD1_DACL)
>  #ifdef CONFIG_NFSD_V4_SECURITY_LABEL
>  #define NFSD_WRITEABLE_ATTRS_WORD2 FATTR4_WORD2_SECURITY_LABEL
>  #else
> diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h
> index 992ddc4..1bd4aea 100644
> --- a/include/linux/nfs4.h
> +++ b/include/linux/nfs4.h
> @@ -394,6 +394,7 @@ enum lock_type4 {
>  #define FATTR4_WORD1_TIME_MODIFY        (1UL << 21)
>  #define FATTR4_WORD1_TIME_MODIFY_SET    (1UL << 22)
>  #define FATTR4_WORD1_MOUNTED_ON_FILEID  (1UL << 23)
> +#define FATTR4_WORD1_DACL               (1UL << 26)
>  #define FATTR4_WORD1_FS_LAYOUT_TYPES    (1UL << 30)
>  #define FATTR4_WORD2_LAYOUT_TYPES       (1UL << 0)
>  #define FATTR4_WORD2_LAYOUT_BLKSIZE     (1UL << 1)
> diff --git a/include/uapi/linux/nfs4.h b/include/uapi/linux/nfs4.h
> index 2119c7c..64e4c6c 100644
> --- a/include/uapi/linux/nfs4.h
> +++ b/include/uapi/linux/nfs4.h
> @@ -121,7 +121,8 @@
>  #define NFS4_ACE_GENERIC_READ                 0x00120081
>  #define NFS4_ACE_GENERIC_WRITE                0x00160106
>  #define NFS4_ACE_GENERIC_EXECUTE              0x001200A0
> -#define NFS4_ACE_MASK_ALL                     0x001F01FF
> +#define NFS40_ACE_MASK_ALL                    0x001F01FF
> +#define NFS4_ACE_MASK_ALL                     0x001F07FF
>  
>  #define EXCHGID4_FLAG_SUPP_MOVED_REFER		0x00000001
>  #define EXCHGID4_FLAG_SUPP_MOVED_MIGR		0x00000002
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 32/41] nfsd: Add support for the MAY_CREATE_{FILE,DIR} permissions
  2015-09-05 10:27     ` Andreas Gruenbacher
@ 2015-09-24 20:01         ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-24 20:01 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

On Sat, Sep 05, 2015 at 12:27:27PM +0200, Andreas Gruenbacher wrote:
> For local file systems, the vfs performs the necessary permission checks
> for operations like creating files and directories.  NFSd duplicates
> several of those checks.  The vfs checks have been extended to check for
> additional permissions like MAY_CREATE_FILE and MY_CREATE_DIR; the nfsd
> checks currently lack those extensions.
> 
> Ideally, all duplicate checks should be removed; for now, just fix the
> duplicate checks instead though.

ACK to the patch, but yes this probably worth some more investigation at
some point.

--b.

> 
> Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> ---
>  fs/nfsd/nfs4proc.c |  5 +++--
>  fs/nfsd/nfsfh.c    |  8 ++++----
>  fs/nfsd/vfs.c      | 28 ++++++++++++++++++++--------
>  fs/nfsd/vfs.h      | 17 +++++++++--------
>  4 files changed, 36 insertions(+), 22 deletions(-)
> 
> diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
> index ef9e6cd..5213945 100644
> --- a/fs/nfsd/nfs4proc.c
> +++ b/fs/nfsd/nfs4proc.c
> @@ -601,14 +601,15 @@ static __be32
>  nfsd4_create(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
>  	     struct nfsd4_create *create)
>  {
> +	int access = create->cr_type == NF4DIR ?
> +		NFSD_MAY_CREATE_DIR : NFSD_MAY_CREATE_FILE;
>  	struct svc_fh resfh;
>  	__be32 status;
>  	dev_t rdev;
>  
>  	fh_init(&resfh, NFS4_FHSIZE);
>  
> -	status = fh_verify(rqstp, &cstate->current_fh, S_IFDIR,
> -			   NFSD_MAY_CREATE);
> +	status = fh_verify(rqstp, &cstate->current_fh, S_IFDIR, access);
>  	if (status)
>  		return status;
>  
> diff --git a/fs/nfsd/nfsfh.c b/fs/nfsd/nfsfh.c
> index 350041a..7159316 100644
> --- a/fs/nfsd/nfsfh.c
> +++ b/fs/nfsd/nfsfh.c
> @@ -319,10 +319,10 @@ fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, umode_t type, int access)
>  	/*
>  	 * We still have to do all these permission checks, even when
>  	 * fh_dentry is already set:
> -	 * 	- fh_verify may be called multiple times with different
> -	 * 	  "access" arguments (e.g. nfsd_proc_create calls
> -	 * 	  fh_verify(...,NFSD_MAY_EXEC) first, then later (in
> -	 * 	  nfsd_create) calls fh_verify(...,NFSD_MAY_CREATE).
> +	 *	- fh_verify may be called multiple times with different
> +	 *	  "access" arguments (e.g. nfsd_proc_create calls
> +	 *	  fh_verify(...,NFSD_MAY_EXEC) first, then later (in
> +	 *	  nfsd_create) calls fh_verify(...,NFSD_MAY_CREATE_FILE).
>  	 *	- in the NFSv4 case, the filehandle may have been filled
>  	 *	  in by fh_compose, and given a dentry, but further
>  	 *	  compound operations performed with that filehandle
> diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
> index b5e077a..bd19096 100644
> --- a/fs/nfsd/vfs.c
> +++ b/fs/nfsd/vfs.c
> @@ -1128,6 +1128,8 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
>  	__be32		err;
>  	__be32		err2;
>  	int		host_err;
> +	int access = (type == S_IFDIR) ?
> +		NFSD_MAY_CREATE_DIR : NFSD_MAY_CREATE_FILE;
>  
>  	err = nfserr_perm;
>  	if (!flen)
> @@ -1136,7 +1138,7 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
>  	if (isdotent(fname, flen))
>  		goto out;
>  
> -	err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE);
> +	err = fh_verify(rqstp, fhp, S_IFDIR, access);
>  	if (err)
>  		goto out;
>  
> @@ -1307,7 +1309,7 @@ do_nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
>  
>  	/* If file doesn't exist, check for permissions to create one */
>  	if (d_really_is_negative(dchild)) {
> -		err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE);
> +		err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE_FILE);
>  		if (err)
>  			goto out;
>  	}
> @@ -1491,7 +1493,7 @@ nfsd_symlink(struct svc_rqst *rqstp, struct svc_fh *fhp,
>  	if (isdotent(fname, flen))
>  		goto out;
>  
> -	err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE);
> +	err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE_FILE);
>  	if (err)
>  		goto out;
>  
> @@ -1538,7 +1540,7 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp,
>  	__be32		err;
>  	int		host_err;
>  
> -	err = fh_verify(rqstp, ffhp, S_IFDIR, NFSD_MAY_CREATE);
> +	err = fh_verify(rqstp, ffhp, S_IFDIR, NFSD_MAY_CREATE_FILE);
>  	if (err)
>  		goto out;
>  	err = fh_verify(rqstp, tfhp, 0, NFSD_MAY_NOP);
> @@ -1610,11 +1612,12 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
>  	struct inode	*fdir, *tdir;
>  	__be32		err;
>  	int		host_err;
> +	int		access;
>  
>  	err = fh_verify(rqstp, ffhp, S_IFDIR, NFSD_MAY_REMOVE);
>  	if (err)
>  		goto out;
> -	err = fh_verify(rqstp, tfhp, S_IFDIR, NFSD_MAY_CREATE);
> +	err = fh_verify(rqstp, tfhp, S_IFDIR, NFSD_MAY_NOP);
>  	if (err)
>  		goto out;
>  
> @@ -1653,6 +1656,13 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
>  	if (odentry == trap)
>  		goto out_dput_old;
>  
> +	host_err = 0;
> +	access = S_ISDIR(d_inode(odentry)->i_mode) ?
> +		NFSD_MAY_CREATE_DIR : NFSD_MAY_CREATE_FILE;
> +	err = fh_verify(rqstp, tfhp, S_IFDIR, access);
> +	if (err)
> +		goto out_dput_old;
> +
>  	ndentry = lookup_one_len(tname, tdentry, tlen);
>  	host_err = PTR_ERR(ndentry);
>  	if (IS_ERR(ndentry))
> @@ -1678,7 +1688,8 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
>   out_dput_old:
>  	dput(odentry);
>   out_nfserr:
> -	err = nfserrno(host_err);
> +	if (host_err)
> +		err = nfserrno(host_err);
>  	/*
>  	 * We cannot rely on fh_unlock on the two filehandles,
>  	 * as that would do the wrong thing if the two directories
> @@ -2011,8 +2022,9 @@ nfsd_permission(struct svc_rqst *rqstp, struct svc_export *exp,
>  	    uid_eq(inode->i_uid, current_fsuid()))
>  		return 0;
>  
> -	/* This assumes  NFSD_MAY_{READ,WRITE,EXEC} == MAY_{READ,WRITE,EXEC} */
> -	err = inode_permission(inode, acc & (MAY_READ|MAY_WRITE|MAY_EXEC));
> +	/* This assumes NFSD_MAY_{READ,WRITE,EXEC} == MAY_{READ,WRITE,EXEC}. */
> +	err = inode_permission(inode, acc & (MAY_READ|MAY_WRITE|MAY_EXEC|
> +					     MAY_CREATE_DIR|MAY_CREATE_FILE));
>  
>  	/* Allow read access to binaries even when mode 111 */
>  	if (err == -EACCES && S_ISREG(inode->i_mode) &&
> diff --git a/fs/nfsd/vfs.h b/fs/nfsd/vfs.h
> index 5be875e..384fda4 100644
> --- a/fs/nfsd/vfs.h
> +++ b/fs/nfsd/vfs.h
> @@ -19,18 +19,19 @@
>  #define NFSD_MAY_TRUNC			0x010
>  #define NFSD_MAY_LOCK			0x020
>  #define NFSD_MAY_MASK			0x03f
> +#define NFSD_MAY_CREATE_FILE		0x103 /* == MAY_{EXEC|WRITE|CREATE_FILE} */
> +#define NFSD_MAY_CREATE_DIR		0x203 /* == MAY_{EXEC|WRITE|CREATE_DIR} */
>  
>  /* extra hints to permission and open routines: */
> -#define NFSD_MAY_OWNER_OVERRIDE		0x040
> -#define NFSD_MAY_LOCAL_ACCESS		0x080 /* for device special files */
> -#define NFSD_MAY_BYPASS_GSS_ON_ROOT	0x100
> -#define NFSD_MAY_NOT_BREAK_LEASE	0x200
> -#define NFSD_MAY_BYPASS_GSS		0x400
> -#define NFSD_MAY_READ_IF_EXEC		0x800
> +#define NFSD_MAY_OWNER_OVERRIDE		0x04000
> +#define NFSD_MAY_LOCAL_ACCESS		0x08000 /* for device special files */
> +#define NFSD_MAY_BYPASS_GSS_ON_ROOT	0x10000
> +#define NFSD_MAY_NOT_BREAK_LEASE	0x20000
> +#define NFSD_MAY_BYPASS_GSS		0x40000
> +#define NFSD_MAY_READ_IF_EXEC		0x80000
>  
> -#define NFSD_MAY_64BIT_COOKIE		0x1000 /* 64 bit readdir cookies for >= NFSv3 */
> +#define NFSD_MAY_64BIT_COOKIE		0x100000 /* 64 bit readdir cookies for >= NFSv3 */
>  
> -#define NFSD_MAY_CREATE		(NFSD_MAY_EXEC|NFSD_MAY_WRITE)
>  #define NFSD_MAY_REMOVE		(NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC)
>  
>  /*
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 32/41] nfsd: Add support for the MAY_CREATE_{FILE,DIR} permissions
@ 2015-09-24 20:01         ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-24 20:01 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Sat, Sep 05, 2015 at 12:27:27PM +0200, Andreas Gruenbacher wrote:
> For local file systems, the vfs performs the necessary permission checks
> for operations like creating files and directories.  NFSd duplicates
> several of those checks.  The vfs checks have been extended to check for
> additional permissions like MAY_CREATE_FILE and MY_CREATE_DIR; the nfsd
> checks currently lack those extensions.
> 
> Ideally, all duplicate checks should be removed; for now, just fix the
> duplicate checks instead though.

ACK to the patch, but yes this probably worth some more investigation at
some point.

--b.

> 
> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> ---
>  fs/nfsd/nfs4proc.c |  5 +++--
>  fs/nfsd/nfsfh.c    |  8 ++++----
>  fs/nfsd/vfs.c      | 28 ++++++++++++++++++++--------
>  fs/nfsd/vfs.h      | 17 +++++++++--------
>  4 files changed, 36 insertions(+), 22 deletions(-)
> 
> diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
> index ef9e6cd..5213945 100644
> --- a/fs/nfsd/nfs4proc.c
> +++ b/fs/nfsd/nfs4proc.c
> @@ -601,14 +601,15 @@ static __be32
>  nfsd4_create(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
>  	     struct nfsd4_create *create)
>  {
> +	int access = create->cr_type == NF4DIR ?
> +		NFSD_MAY_CREATE_DIR : NFSD_MAY_CREATE_FILE;
>  	struct svc_fh resfh;
>  	__be32 status;
>  	dev_t rdev;
>  
>  	fh_init(&resfh, NFS4_FHSIZE);
>  
> -	status = fh_verify(rqstp, &cstate->current_fh, S_IFDIR,
> -			   NFSD_MAY_CREATE);
> +	status = fh_verify(rqstp, &cstate->current_fh, S_IFDIR, access);
>  	if (status)
>  		return status;
>  
> diff --git a/fs/nfsd/nfsfh.c b/fs/nfsd/nfsfh.c
> index 350041a..7159316 100644
> --- a/fs/nfsd/nfsfh.c
> +++ b/fs/nfsd/nfsfh.c
> @@ -319,10 +319,10 @@ fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, umode_t type, int access)
>  	/*
>  	 * We still have to do all these permission checks, even when
>  	 * fh_dentry is already set:
> -	 * 	- fh_verify may be called multiple times with different
> -	 * 	  "access" arguments (e.g. nfsd_proc_create calls
> -	 * 	  fh_verify(...,NFSD_MAY_EXEC) first, then later (in
> -	 * 	  nfsd_create) calls fh_verify(...,NFSD_MAY_CREATE).
> +	 *	- fh_verify may be called multiple times with different
> +	 *	  "access" arguments (e.g. nfsd_proc_create calls
> +	 *	  fh_verify(...,NFSD_MAY_EXEC) first, then later (in
> +	 *	  nfsd_create) calls fh_verify(...,NFSD_MAY_CREATE_FILE).
>  	 *	- in the NFSv4 case, the filehandle may have been filled
>  	 *	  in by fh_compose, and given a dentry, but further
>  	 *	  compound operations performed with that filehandle
> diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
> index b5e077a..bd19096 100644
> --- a/fs/nfsd/vfs.c
> +++ b/fs/nfsd/vfs.c
> @@ -1128,6 +1128,8 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
>  	__be32		err;
>  	__be32		err2;
>  	int		host_err;
> +	int access = (type == S_IFDIR) ?
> +		NFSD_MAY_CREATE_DIR : NFSD_MAY_CREATE_FILE;
>  
>  	err = nfserr_perm;
>  	if (!flen)
> @@ -1136,7 +1138,7 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
>  	if (isdotent(fname, flen))
>  		goto out;
>  
> -	err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE);
> +	err = fh_verify(rqstp, fhp, S_IFDIR, access);
>  	if (err)
>  		goto out;
>  
> @@ -1307,7 +1309,7 @@ do_nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
>  
>  	/* If file doesn't exist, check for permissions to create one */
>  	if (d_really_is_negative(dchild)) {
> -		err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE);
> +		err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE_FILE);
>  		if (err)
>  			goto out;
>  	}
> @@ -1491,7 +1493,7 @@ nfsd_symlink(struct svc_rqst *rqstp, struct svc_fh *fhp,
>  	if (isdotent(fname, flen))
>  		goto out;
>  
> -	err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE);
> +	err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE_FILE);
>  	if (err)
>  		goto out;
>  
> @@ -1538,7 +1540,7 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp,
>  	__be32		err;
>  	int		host_err;
>  
> -	err = fh_verify(rqstp, ffhp, S_IFDIR, NFSD_MAY_CREATE);
> +	err = fh_verify(rqstp, ffhp, S_IFDIR, NFSD_MAY_CREATE_FILE);
>  	if (err)
>  		goto out;
>  	err = fh_verify(rqstp, tfhp, 0, NFSD_MAY_NOP);
> @@ -1610,11 +1612,12 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
>  	struct inode	*fdir, *tdir;
>  	__be32		err;
>  	int		host_err;
> +	int		access;
>  
>  	err = fh_verify(rqstp, ffhp, S_IFDIR, NFSD_MAY_REMOVE);
>  	if (err)
>  		goto out;
> -	err = fh_verify(rqstp, tfhp, S_IFDIR, NFSD_MAY_CREATE);
> +	err = fh_verify(rqstp, tfhp, S_IFDIR, NFSD_MAY_NOP);
>  	if (err)
>  		goto out;
>  
> @@ -1653,6 +1656,13 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
>  	if (odentry == trap)
>  		goto out_dput_old;
>  
> +	host_err = 0;
> +	access = S_ISDIR(d_inode(odentry)->i_mode) ?
> +		NFSD_MAY_CREATE_DIR : NFSD_MAY_CREATE_FILE;
> +	err = fh_verify(rqstp, tfhp, S_IFDIR, access);
> +	if (err)
> +		goto out_dput_old;
> +
>  	ndentry = lookup_one_len(tname, tdentry, tlen);
>  	host_err = PTR_ERR(ndentry);
>  	if (IS_ERR(ndentry))
> @@ -1678,7 +1688,8 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
>   out_dput_old:
>  	dput(odentry);
>   out_nfserr:
> -	err = nfserrno(host_err);
> +	if (host_err)
> +		err = nfserrno(host_err);
>  	/*
>  	 * We cannot rely on fh_unlock on the two filehandles,
>  	 * as that would do the wrong thing if the two directories
> @@ -2011,8 +2022,9 @@ nfsd_permission(struct svc_rqst *rqstp, struct svc_export *exp,
>  	    uid_eq(inode->i_uid, current_fsuid()))
>  		return 0;
>  
> -	/* This assumes  NFSD_MAY_{READ,WRITE,EXEC} == MAY_{READ,WRITE,EXEC} */
> -	err = inode_permission(inode, acc & (MAY_READ|MAY_WRITE|MAY_EXEC));
> +	/* This assumes NFSD_MAY_{READ,WRITE,EXEC} == MAY_{READ,WRITE,EXEC}. */
> +	err = inode_permission(inode, acc & (MAY_READ|MAY_WRITE|MAY_EXEC|
> +					     MAY_CREATE_DIR|MAY_CREATE_FILE));
>  
>  	/* Allow read access to binaries even when mode 111 */
>  	if (err == -EACCES && S_ISREG(inode->i_mode) &&
> diff --git a/fs/nfsd/vfs.h b/fs/nfsd/vfs.h
> index 5be875e..384fda4 100644
> --- a/fs/nfsd/vfs.h
> +++ b/fs/nfsd/vfs.h
> @@ -19,18 +19,19 @@
>  #define NFSD_MAY_TRUNC			0x010
>  #define NFSD_MAY_LOCK			0x020
>  #define NFSD_MAY_MASK			0x03f
> +#define NFSD_MAY_CREATE_FILE		0x103 /* == MAY_{EXEC|WRITE|CREATE_FILE} */
> +#define NFSD_MAY_CREATE_DIR		0x203 /* == MAY_{EXEC|WRITE|CREATE_DIR} */
>  
>  /* extra hints to permission and open routines: */
> -#define NFSD_MAY_OWNER_OVERRIDE		0x040
> -#define NFSD_MAY_LOCAL_ACCESS		0x080 /* for device special files */
> -#define NFSD_MAY_BYPASS_GSS_ON_ROOT	0x100
> -#define NFSD_MAY_NOT_BREAK_LEASE	0x200
> -#define NFSD_MAY_BYPASS_GSS		0x400
> -#define NFSD_MAY_READ_IF_EXEC		0x800
> +#define NFSD_MAY_OWNER_OVERRIDE		0x04000
> +#define NFSD_MAY_LOCAL_ACCESS		0x08000 /* for device special files */
> +#define NFSD_MAY_BYPASS_GSS_ON_ROOT	0x10000
> +#define NFSD_MAY_NOT_BREAK_LEASE	0x20000
> +#define NFSD_MAY_BYPASS_GSS		0x40000
> +#define NFSD_MAY_READ_IF_EXEC		0x80000
>  
> -#define NFSD_MAY_64BIT_COOKIE		0x1000 /* 64 bit readdir cookies for >= NFSv3 */
> +#define NFSD_MAY_64BIT_COOKIE		0x100000 /* 64 bit readdir cookies for >= NFSv3 */
>  
> -#define NFSD_MAY_CREATE		(NFSD_MAY_EXEC|NFSD_MAY_WRITE)
>  #define NFSD_MAY_REMOVE		(NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC)
>  
>  /*
> -- 
> 2.4.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 25/41] richacl: Isolate the owner and group classes
  2015-09-23 13:33           ` Andreas Gruenbacher
@ 2015-09-25 11:25               ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-25 11:25 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: J. Bruce Fields, LKML, linux-fsdevel, Linux NFS Mailing List,
	Linux API Mailing List, linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	LSM List

Here is another minor improvement that produces deny aces with fewer
permissions in them and avoids creating unnecessary deny aces in some
cases.

Andreas

---
 fs/richacl_compat.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 2f53394..bc0bcfe 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -605,14 +605,13 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
 	int n;
 
 	/*
-	 * Compute the permissions already denied to @who.  There are no
+	 * Compute the permissions already defined for @who.  There are no
 	 * everyone@ deny aces left in the acl at this stage.
 	 */
 	richacl_for_each_entry(ace, acl) {
 		if (richace_is_inherit_only(ace))
 			continue;
-		if (richace_is_same_identifier(acl, ace, who) &&
-		    richace_is_deny(ace))
+		if (richace_is_same_identifier(acl, ace, who))
 			deny &= ~ace->e_mask;
 	}
 	if (!deny)
-- 
2.4.3

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* Re: [RFC v7 25/41] richacl: Isolate the owner and group classes
@ 2015-09-25 11:25               ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-25 11:25 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: J. Bruce Fields, LKML, linux-fsdevel, Linux NFS Mailing List,
	Linux API Mailing List, linux-cifs, LSM List

Here is another minor improvement that produces deny aces with fewer
permissions in them and avoids creating unnecessary deny aces in some
cases.

Andreas

---
 fs/richacl_compat.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 2f53394..bc0bcfe 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -605,14 +605,13 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
 	int n;
 
 	/*
-	 * Compute the permissions already denied to @who.  There are no
+	 * Compute the permissions already defined for @who.  There are no
 	 * everyone@ deny aces left in the acl at this stage.
 	 */
 	richacl_for_each_entry(ace, acl) {
 		if (richace_is_inherit_only(ace))
 			continue;
-		if (richace_is_same_identifier(acl, ace, who) &&
-		    richace_is_deny(ace))
+		if (richace_is_same_identifier(acl, ace, who))
 			deny &= ~ace->e_mask;
 	}
 	if (!deny)
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* Re: [PATCH] richacl: Possible other write-through fix
  2015-09-24 18:33         ` J. Bruce Fields
  (?)
@ 2015-09-25 16:21             ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-25 16:21 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

2015-09-24 20:33 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> On Sat, Sep 05, 2015 at 12:27:21PM +0200, Andreas Gruenbacher wrote:
>> +int
>> +richacl_apply_masks(struct richacl **acl, kuid_t owner)
>> +{
>> +     if ((*acl)->a_flags & RICHACL_MASKED) {
>> +             struct richacl_alloc alloc = {
>> +                     .acl = richacl_clone(*acl, GFP_KERNEL),
>> +                     .count = (*acl)->a_count,
>> +             };
>> +             if (!alloc.acl)
>> +                     return -ENOMEM;
>> +
>> +             if (richacl_move_everyone_aces_down(&alloc) ||
>> +                 richacl_propagate_everyone(&alloc) ||
>> +                 __richacl_apply_masks(&alloc, owner) ||
>> +                 richacl_set_owner_permissions(&alloc) ||
>> +                 richacl_set_other_permissions(&alloc) ||
>
> Hm, I'm a little unsure about this step: it seems like the one step that
> can actually change the permissions (making them more permissive, in
> this case), whereas the others are neutral.
>
> The following two steps should fix that, but I'm not sure they do.
>
> E.g. if I have an ACL like
>
>         mask 0777, WRITE_THROUGH
>         GROUP@:r--::allow
>
> I think it should result in
>
>         OWNER@:   rwx::allow
>         GROUP@:   -wx::deny
>         GROUP@:   r--::allow
>         EVERYONE@:rwx::allow
>
> But does the GROUP@ deny actually get in there?  It looks to me like the
> denies inserted by richacl_isolate_group_class only take into account
> the group mask, not the actual permissions granted to any group class
> user.
>
> I may be mising something.

Thanks for the test case and analysis. The group@ deny ace that should be
inserted here actually doesn't get inserted. I'm attaching a fix.

---

By the way, all those scenarios can easily be tries out using the test
suite in the user-space richacl package, even without a richacl enabled
kernel. In this case, without the fix, we get:

  $ make tests/richacl-apply-masks
  $ tests/richacl-apply-masks -m 777 group@:r::allow
  group@:r::allow
  everyone@:rwpx::allow

With the fix, we are now getting:

  $ tests/richacl-apply-masks -m 777 group@:r::allow
  owner@:rwpx::allow
  group@:r::allow
  group@:wpx::deny
  everyone@:rwpx::allow

Thanks,
Andreas

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 726d796..bc0bcfe 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -463,23 +463,28 @@ richacl_set_owner_permissions(struct richacl_alloc *alloc)
 
 /**
  * richacl_set_other_permissions  -  set the other permissions to the other mask
+ * @alloc:	acl and number of allocated entries
+ * @added:	permissions added for everyone@
  *
  * Change the acl so that everyone@ is granted the permissions set in the other
  * mask.  This leaves at most one efective everyone@ allow entry at the end of
- * the acl.
+ * the acl.  If everyone@ end up being granted additional permissions, these
+ * permissions are returned in @added.
  */
 static int
-richacl_set_other_permissions(struct richacl_alloc *alloc)
+richacl_set_other_permissions(struct richacl_alloc *alloc, unsigned int *added)
 {
 	struct richacl *acl = alloc->acl;
 	unsigned int x = RICHACE_POSIX_ALWAYS_ALLOWED;
 	unsigned int other_mask = acl->a_other_mask & ~x;
-	struct richace *ace = acl->a_entries + acl->a_count - 1;
+	struct richace *ace;
 
 	if (!(other_mask &&
 	      (acl->a_flags & RICHACL_WRITE_THROUGH)))
 		return 0;
 
+	*added = other_mask;
+	ace = acl->a_entries + acl->a_count - 1;
 	if (acl->a_count == 0 ||
 	    !richace_is_everyone(ace) ||
 	    richace_is_inherit_only(ace)) {
@@ -490,8 +495,10 @@ richacl_set_other_permissions(struct richacl_alloc *alloc)
 		ace->e_flags = RICHACE_SPECIAL_WHO;
 		ace->e_mask = other_mask;
 		ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
-	} else
+	} else {
+		*added &= ~ace->e_mask;
 		richace_change_mask(alloc, &ace, other_mask);
+	}
 	return 0;
 }
 
@@ -645,6 +652,7 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
 /**
  * richacl_isolate_group_class  -  limit the group class to the group file mask
  * @alloc:	acl and number of allocated entries
+ * @deny:	additional permissions to deny
  *
  * POSIX requires that after a chmod, the group class is granted no more
  * permissions than the group file permission bits.  For richacls, this
@@ -679,21 +687,20 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
  *    everyone@:rw::allow
  */
 static int
-richacl_isolate_group_class(struct richacl_alloc *alloc)
+richacl_isolate_group_class(struct richacl_alloc *alloc, unsigned int deny)
 {
 	struct richace who = {
 		.e_flags = RICHACE_SPECIAL_WHO,
 		.e_id.special = RICHACE_GROUP_SPECIAL_ID,
 	};
 	struct richace *ace;
-	unsigned int deny;
 
 	if (!alloc->acl->a_count)
 		return 0;
 	ace = alloc->acl->a_entries + alloc->acl->a_count - 1;
 	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
 		return 0;
-	deny = ace->e_mask & ~alloc->acl->a_group_mask;
+	deny |= ace->e_mask & ~alloc->acl->a_group_mask;
 
 	if (deny) {
 		unsigned int n;
@@ -806,16 +813,17 @@ richacl_apply_masks(struct richacl **acl, kuid_t owner)
 			.acl = richacl_clone(*acl, GFP_KERNEL),
 			.count = (*acl)->a_count,
 		};
+		unsigned int added = 0;
+
 		if (!alloc.acl)
 			return -ENOMEM;
-
 		if (richacl_move_everyone_aces_down(&alloc) ||
 		    richacl_propagate_everyone(&alloc) ||
 		    __richacl_apply_masks(&alloc, owner) ||
+		    richacl_set_other_permissions(&alloc, &added) ||
+		    richacl_isolate_group_class(&alloc, added) ||
 		    richacl_set_owner_permissions(&alloc) ||
-		    richacl_set_other_permissions(&alloc) ||
-		    richacl_isolate_owner_class(&alloc) ||
-		    richacl_isolate_group_class(&alloc)) {
+		    richacl_isolate_owner_class(&alloc)) {
 			richacl_put(alloc.acl);
 			return -ENOMEM;
 		}
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* Re: [PATCH] richacl: Possible other write-through fix
@ 2015-09-25 16:21             ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-25 16:21 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

2015-09-24 20:33 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> On Sat, Sep 05, 2015 at 12:27:21PM +0200, Andreas Gruenbacher wrote:
>> +int
>> +richacl_apply_masks(struct richacl **acl, kuid_t owner)
>> +{
>> +     if ((*acl)->a_flags & RICHACL_MASKED) {
>> +             struct richacl_alloc alloc = {
>> +                     .acl = richacl_clone(*acl, GFP_KERNEL),
>> +                     .count = (*acl)->a_count,
>> +             };
>> +             if (!alloc.acl)
>> +                     return -ENOMEM;
>> +
>> +             if (richacl_move_everyone_aces_down(&alloc) ||
>> +                 richacl_propagate_everyone(&alloc) ||
>> +                 __richacl_apply_masks(&alloc, owner) ||
>> +                 richacl_set_owner_permissions(&alloc) ||
>> +                 richacl_set_other_permissions(&alloc) ||
>
> Hm, I'm a little unsure about this step: it seems like the one step that
> can actually change the permissions (making them more permissive, in
> this case), whereas the others are neutral.
>
> The following two steps should fix that, but I'm not sure they do.
>
> E.g. if I have an ACL like
>
>         mask 0777, WRITE_THROUGH
>         GROUP@:r--::allow
>
> I think it should result in
>
>         OWNER@:   rwx::allow
>         GROUP@:   -wx::deny
>         GROUP@:   r--::allow
>         EVERYONE@:rwx::allow
>
> But does the GROUP@ deny actually get in there?  It looks to me like the
> denies inserted by richacl_isolate_group_class only take into account
> the group mask, not the actual permissions granted to any group class
> user.
>
> I may be mising something.

Thanks for the test case and analysis. The group@ deny ace that should be
inserted here actually doesn't get inserted. I'm attaching a fix.

---

By the way, all those scenarios can easily be tries out using the test
suite in the user-space richacl package, even without a richacl enabled
kernel. In this case, without the fix, we get:

  $ make tests/richacl-apply-masks
  $ tests/richacl-apply-masks -m 777 group@:r::allow
  group@:r::allow
  everyone@:rwpx::allow

With the fix, we are now getting:

  $ tests/richacl-apply-masks -m 777 group@:r::allow
  owner@:rwpx::allow
  group@:r::allow
  group@:wpx::deny
  everyone@:rwpx::allow

Thanks,
Andreas

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 726d796..bc0bcfe 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -463,23 +463,28 @@ richacl_set_owner_permissions(struct richacl_alloc *alloc)
 
 /**
  * richacl_set_other_permissions  -  set the other permissions to the other mask
+ * @alloc:	acl and number of allocated entries
+ * @added:	permissions added for everyone@
  *
  * Change the acl so that everyone@ is granted the permissions set in the other
  * mask.  This leaves at most one efective everyone@ allow entry at the end of
- * the acl.
+ * the acl.  If everyone@ end up being granted additional permissions, these
+ * permissions are returned in @added.
  */
 static int
-richacl_set_other_permissions(struct richacl_alloc *alloc)
+richacl_set_other_permissions(struct richacl_alloc *alloc, unsigned int *added)
 {
 	struct richacl *acl = alloc->acl;
 	unsigned int x = RICHACE_POSIX_ALWAYS_ALLOWED;
 	unsigned int other_mask = acl->a_other_mask & ~x;
-	struct richace *ace = acl->a_entries + acl->a_count - 1;
+	struct richace *ace;
 
 	if (!(other_mask &&
 	      (acl->a_flags & RICHACL_WRITE_THROUGH)))
 		return 0;
 
+	*added = other_mask;
+	ace = acl->a_entries + acl->a_count - 1;
 	if (acl->a_count == 0 ||
 	    !richace_is_everyone(ace) ||
 	    richace_is_inherit_only(ace)) {
@@ -490,8 +495,10 @@ richacl_set_other_permissions(struct richacl_alloc *alloc)
 		ace->e_flags = RICHACE_SPECIAL_WHO;
 		ace->e_mask = other_mask;
 		ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
-	} else
+	} else {
+		*added &= ~ace->e_mask;
 		richace_change_mask(alloc, &ace, other_mask);
+	}
 	return 0;
 }
 
@@ -645,6 +652,7 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
 /**
  * richacl_isolate_group_class  -  limit the group class to the group file mask
  * @alloc:	acl and number of allocated entries
+ * @deny:	additional permissions to deny
  *
  * POSIX requires that after a chmod, the group class is granted no more
  * permissions than the group file permission bits.  For richacls, this
@@ -679,21 +687,20 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
  *    everyone@:rw::allow
  */
 static int
-richacl_isolate_group_class(struct richacl_alloc *alloc)
+richacl_isolate_group_class(struct richacl_alloc *alloc, unsigned int deny)
 {
 	struct richace who = {
 		.e_flags = RICHACE_SPECIAL_WHO,
 		.e_id.special = RICHACE_GROUP_SPECIAL_ID,
 	};
 	struct richace *ace;
-	unsigned int deny;
 
 	if (!alloc->acl->a_count)
 		return 0;
 	ace = alloc->acl->a_entries + alloc->acl->a_count - 1;
 	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
 		return 0;
-	deny = ace->e_mask & ~alloc->acl->a_group_mask;
+	deny |= ace->e_mask & ~alloc->acl->a_group_mask;
 
 	if (deny) {
 		unsigned int n;
@@ -806,16 +813,17 @@ richacl_apply_masks(struct richacl **acl, kuid_t owner)
 			.acl = richacl_clone(*acl, GFP_KERNEL),
 			.count = (*acl)->a_count,
 		};
+		unsigned int added = 0;
+
 		if (!alloc.acl)
 			return -ENOMEM;
-
 		if (richacl_move_everyone_aces_down(&alloc) ||
 		    richacl_propagate_everyone(&alloc) ||
 		    __richacl_apply_masks(&alloc, owner) ||
+		    richacl_set_other_permissions(&alloc, &added) ||
+		    richacl_isolate_group_class(&alloc, added) ||
 		    richacl_set_owner_permissions(&alloc) ||
-		    richacl_set_other_permissions(&alloc) ||
-		    richacl_isolate_owner_class(&alloc) ||
-		    richacl_isolate_group_class(&alloc)) {
+		    richacl_isolate_owner_class(&alloc)) {
 			richacl_put(alloc.acl);
 			return -ENOMEM;
 		}
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* Re: [PATCH] richacl: Possible other write-through fix
@ 2015-09-25 16:21             ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-25 16:21 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

2015-09-24 20:33 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> On Sat, Sep 05, 2015 at 12:27:21PM +0200, Andreas Gruenbacher wrote:
>> +int
>> +richacl_apply_masks(struct richacl **acl, kuid_t owner)
>> +{
>> +     if ((*acl)->a_flags & RICHACL_MASKED) {
>> +             struct richacl_alloc alloc = {
>> +                     .acl = richacl_clone(*acl, GFP_KERNEL),
>> +                     .count = (*acl)->a_count,
>> +             };
>> +             if (!alloc.acl)
>> +                     return -ENOMEM;
>> +
>> +             if (richacl_move_everyone_aces_down(&alloc) ||
>> +                 richacl_propagate_everyone(&alloc) ||
>> +                 __richacl_apply_masks(&alloc, owner) ||
>> +                 richacl_set_owner_permissions(&alloc) ||
>> +                 richacl_set_other_permissions(&alloc) ||
>
> Hm, I'm a little unsure about this step: it seems like the one step that
> can actually change the permissions (making them more permissive, in
> this case), whereas the others are neutral.
>
> The following two steps should fix that, but I'm not sure they do.
>
> E.g. if I have an ACL like
>
>         mask 0777, WRITE_THROUGH
>         GROUP@:r--::allow
>
> I think it should result in
>
>         OWNER@:   rwx::allow
>         GROUP@:   -wx::deny
>         GROUP@:   r--::allow
>         EVERYONE@:rwx::allow
>
> But does the GROUP@ deny actually get in there?  It looks to me like the
> denies inserted by richacl_isolate_group_class only take into account
> the group mask, not the actual permissions granted to any group class
> user.
>
> I may be mising something.

Thanks for the test case and analysis. The group@ deny ace that should be
inserted here actually doesn't get inserted. I'm attaching a fix.

---

By the way, all those scenarios can easily be tries out using the test
suite in the user-space richacl package, even without a richacl enabled
kernel. In this case, without the fix, we get:

  $ make tests/richacl-apply-masks
  $ tests/richacl-apply-masks -m 777 group@:r::allow
  group@:r::allow
  everyone@:rwpx::allow

With the fix, we are now getting:

  $ tests/richacl-apply-masks -m 777 group@:r::allow
  owner@:rwpx::allow
  group@:r::allow
  group@:wpx::deny
  everyone@:rwpx::allow

Thanks,
Andreas

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 726d796..bc0bcfe 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -463,23 +463,28 @@ richacl_set_owner_permissions(struct richacl_alloc *alloc)
 
 /**
  * richacl_set_other_permissions  -  set the other permissions to the other mask
+ * @alloc:	acl and number of allocated entries
+ * @added:	permissions added for everyone@
  *
  * Change the acl so that everyone@ is granted the permissions set in the other
  * mask.  This leaves at most one efective everyone@ allow entry at the end of
- * the acl.
+ * the acl.  If everyone@ end up being granted additional permissions, these
+ * permissions are returned in @added.
  */
 static int
-richacl_set_other_permissions(struct richacl_alloc *alloc)
+richacl_set_other_permissions(struct richacl_alloc *alloc, unsigned int *added)
 {
 	struct richacl *acl = alloc->acl;
 	unsigned int x = RICHACE_POSIX_ALWAYS_ALLOWED;
 	unsigned int other_mask = acl->a_other_mask & ~x;
-	struct richace *ace = acl->a_entries + acl->a_count - 1;
+	struct richace *ace;
 
 	if (!(other_mask &&
 	      (acl->a_flags & RICHACL_WRITE_THROUGH)))
 		return 0;
 
+	*added = other_mask;
+	ace = acl->a_entries + acl->a_count - 1;
 	if (acl->a_count == 0 ||
 	    !richace_is_everyone(ace) ||
 	    richace_is_inherit_only(ace)) {
@@ -490,8 +495,10 @@ richacl_set_other_permissions(struct richacl_alloc *alloc)
 		ace->e_flags = RICHACE_SPECIAL_WHO;
 		ace->e_mask = other_mask;
 		ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
-	} else
+	} else {
+		*added &= ~ace->e_mask;
 		richace_change_mask(alloc, &ace, other_mask);
+	}
 	return 0;
 }
 
@@ -645,6 +652,7 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
 /**
  * richacl_isolate_group_class  -  limit the group class to the group file mask
  * @alloc:	acl and number of allocated entries
+ * @deny:	additional permissions to deny
  *
  * POSIX requires that after a chmod, the group class is granted no more
  * permissions than the group file permission bits.  For richacls, this
@@ -679,21 +687,20 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
  *    everyone@:rw::allow
  */
 static int
-richacl_isolate_group_class(struct richacl_alloc *alloc)
+richacl_isolate_group_class(struct richacl_alloc *alloc, unsigned int deny)
 {
 	struct richace who = {
 		.e_flags = RICHACE_SPECIAL_WHO,
 		.e_id.special = RICHACE_GROUP_SPECIAL_ID,
 	};
 	struct richace *ace;
-	unsigned int deny;
 
 	if (!alloc->acl->a_count)
 		return 0;
 	ace = alloc->acl->a_entries + alloc->acl->a_count - 1;
 	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
 		return 0;
-	deny = ace->e_mask & ~alloc->acl->a_group_mask;
+	deny |= ace->e_mask & ~alloc->acl->a_group_mask;
 
 	if (deny) {
 		unsigned int n;
@@ -806,16 +813,17 @@ richacl_apply_masks(struct richacl **acl, kuid_t owner)
 			.acl = richacl_clone(*acl, GFP_KERNEL),
 			.count = (*acl)->a_count,
 		};
+		unsigned int added = 0;
+
 		if (!alloc.acl)
 			return -ENOMEM;
-
 		if (richacl_move_everyone_aces_down(&alloc) ||
 		    richacl_propagate_everyone(&alloc) ||
 		    __richacl_apply_masks(&alloc, owner) ||
+		    richacl_set_other_permissions(&alloc, &added) ||
+		    richacl_isolate_group_class(&alloc, added) ||
 		    richacl_set_owner_permissions(&alloc) ||
-		    richacl_set_other_permissions(&alloc) ||
-		    richacl_isolate_owner_class(&alloc) ||
-		    richacl_isolate_group_class(&alloc)) {
+		    richacl_isolate_owner_class(&alloc)) {
 			richacl_put(alloc.acl);
 			return -ENOMEM;
 		}
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* Re: [RFC v7 31/41] nfsd: Add support for the v4.1 dacl attribute
  2015-09-24 19:59         ` J. Bruce Fields
  (?)
@ 2015-09-25 16:37         ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-25 16:37 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: LKML, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

2015-09-24 21:59 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> I'd probably move this up top with the other ACL case.

Okay.

Thanks,
Andreas

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [PATCH] richacl: Possible other write-through fix
  2015-09-24 18:33         ` J. Bruce Fields
  (?)
@ 2015-09-25 16:45             ` Andreas Gruenbacher
  -1 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-25 16:45 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

2015-09-24 20:33 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> On Sat, Sep 05, 2015 at 12:27:21PM +0200, Andreas Gruenbacher wrote:
>> +int
>> +richacl_apply_masks(struct richacl **acl, kuid_t owner)
>> +{
>> +     if ((*acl)->a_flags & RICHACL_MASKED) {
>> +             struct richacl_alloc alloc = {
>> +                     .acl = richacl_clone(*acl, GFP_KERNEL),
>> +                     .count = (*acl)->a_count,
>> +             };
>> +             if (!alloc.acl)
>> +                     return -ENOMEM;
>> +
>> +             if (richacl_move_everyone_aces_down(&alloc) ||
>> +                 richacl_propagate_everyone(&alloc) ||
>> +                 __richacl_apply_masks(&alloc, owner) ||
>> +                 richacl_set_owner_permissions(&alloc) ||
>> +                 richacl_set_other_permissions(&alloc) ||
>
> Hm, I'm a little unsure about this step: it seems like the one step that
> can actually change the permissions (making them more permissive, in
> this case), whereas the others are neutral.
>
> The following two steps should fix that, but I'm not sure they do.
>
> E.g. if I have an ACL like
>
>         mask 0777, WRITE_THROUGH
>         GROUP@:r--::allow
>
> I think it should result in
>
>         OWNER@:   rwx::allow
>         GROUP@:   -wx::deny
>         GROUP@:   r--::allow
>         EVERYONE@:rwx::allow
>
> But does the GROUP@ deny actually get in there?  It looks to me like the
> denies inserted by richacl_isolate_group_class only take into account
> the group mask, not the actual permissions granted to any group class
> user.
>
> I may be mising something.

Thanks for the test case and analysis. The group@ deny ace that should be
inserted here actually doesn't get inserted. I'm attaching a fix.

---

By the way, all those scenarios can easily be tries out using the test
suite in the user-space richacl package, even without a richacl enabled
kernel. In this case, without the fix, we get:

  $ make tests/richacl-apply-masks
  $ tests/richacl-apply-masks -m 777 group@:r::allow
  group@:r::allow
  everyone@:rwpx::allow

With the fix, we are now getting:

  $ tests/richacl-apply-masks -m 777 group@:r::allow
  owner@:rwpx::allow
  group@:r::allow
  group@:wpx::deny
  everyone@:rwpx::allow

Thanks,
Andreas

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 726d796..bc0bcfe 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -463,23 +463,28 @@ richacl_set_owner_permissions(struct richacl_alloc *alloc)
 
 /**
  * richacl_set_other_permissions  -  set the other permissions to the other mask
+ * @alloc:	acl and number of allocated entries
+ * @added:	permissions added for everyone@
  *
  * Change the acl so that everyone@ is granted the permissions set in the other
  * mask.  This leaves at most one efective everyone@ allow entry at the end of
- * the acl.
+ * the acl.  If everyone@ end up being granted additional permissions, these
+ * permissions are returned in @added.
  */
 static int
-richacl_set_other_permissions(struct richacl_alloc *alloc)
+richacl_set_other_permissions(struct richacl_alloc *alloc, unsigned int *added)
 {
 	struct richacl *acl = alloc->acl;
 	unsigned int x = RICHACE_POSIX_ALWAYS_ALLOWED;
 	unsigned int other_mask = acl->a_other_mask & ~x;
-	struct richace *ace = acl->a_entries + acl->a_count - 1;
+	struct richace *ace;
 
 	if (!(other_mask &&
 	      (acl->a_flags & RICHACL_WRITE_THROUGH)))
 		return 0;
 
+	*added = other_mask;
+	ace = acl->a_entries + acl->a_count - 1;
 	if (acl->a_count == 0 ||
 	    !richace_is_everyone(ace) ||
 	    richace_is_inherit_only(ace)) {
@@ -490,8 +495,10 @@ richacl_set_other_permissions(struct richacl_alloc *alloc)
 		ace->e_flags = RICHACE_SPECIAL_WHO;
 		ace->e_mask = other_mask;
 		ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
-	} else
+	} else {
+		*added &= ~ace->e_mask;
 		richace_change_mask(alloc, &ace, other_mask);
+	}
 	return 0;
 }
 
@@ -645,6 +652,7 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
 /**
  * richacl_isolate_group_class  -  limit the group class to the group file mask
  * @alloc:	acl and number of allocated entries
+ * @deny:	additional permissions to deny
  *
  * POSIX requires that after a chmod, the group class is granted no more
  * permissions than the group file permission bits.  For richacls, this
@@ -679,21 +687,20 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
  *    everyone@:rw::allow
  */
 static int
-richacl_isolate_group_class(struct richacl_alloc *alloc)
+richacl_isolate_group_class(struct richacl_alloc *alloc, unsigned int deny)
 {
 	struct richace who = {
 		.e_flags = RICHACE_SPECIAL_WHO,
 		.e_id.special = RICHACE_GROUP_SPECIAL_ID,
 	};
 	struct richace *ace;
-	unsigned int deny;
 
 	if (!alloc->acl->a_count)
 		return 0;
 	ace = alloc->acl->a_entries + alloc->acl->a_count - 1;
 	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
 		return 0;
-	deny = ace->e_mask & ~alloc->acl->a_group_mask;
+	deny |= ace->e_mask & ~alloc->acl->a_group_mask;
 
 	if (deny) {
 		unsigned int n;
@@ -806,16 +813,17 @@ richacl_apply_masks(struct richacl **acl, kuid_t owner)
 			.acl = richacl_clone(*acl, GFP_KERNEL),
 			.count = (*acl)->a_count,
 		};
+		unsigned int added = 0;
+
 		if (!alloc.acl)
 			return -ENOMEM;
-
 		if (richacl_move_everyone_aces_down(&alloc) ||
 		    richacl_propagate_everyone(&alloc) ||
 		    __richacl_apply_masks(&alloc, owner) ||
+		    richacl_set_other_permissions(&alloc, &added) ||
+		    richacl_isolate_group_class(&alloc, added) ||
 		    richacl_set_owner_permissions(&alloc) ||
-		    richacl_set_other_permissions(&alloc) ||
-		    richacl_isolate_owner_class(&alloc) ||
-		    richacl_isolate_group_class(&alloc)) {
+		    richacl_isolate_owner_class(&alloc)) {
 			richacl_put(alloc.acl);
 			return -ENOMEM;
 		}
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* Re: [PATCH] richacl: Possible other write-through fix
@ 2015-09-25 16:45             ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-25 16:45 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

2015-09-24 20:33 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> On Sat, Sep 05, 2015 at 12:27:21PM +0200, Andreas Gruenbacher wrote:
>> +int
>> +richacl_apply_masks(struct richacl **acl, kuid_t owner)
>> +{
>> +     if ((*acl)->a_flags & RICHACL_MASKED) {
>> +             struct richacl_alloc alloc = {
>> +                     .acl = richacl_clone(*acl, GFP_KERNEL),
>> +                     .count = (*acl)->a_count,
>> +             };
>> +             if (!alloc.acl)
>> +                     return -ENOMEM;
>> +
>> +             if (richacl_move_everyone_aces_down(&alloc) ||
>> +                 richacl_propagate_everyone(&alloc) ||
>> +                 __richacl_apply_masks(&alloc, owner) ||
>> +                 richacl_set_owner_permissions(&alloc) ||
>> +                 richacl_set_other_permissions(&alloc) ||
>
> Hm, I'm a little unsure about this step: it seems like the one step that
> can actually change the permissions (making them more permissive, in
> this case), whereas the others are neutral.
>
> The following two steps should fix that, but I'm not sure they do.
>
> E.g. if I have an ACL like
>
>         mask 0777, WRITE_THROUGH
>         GROUP@:r--::allow
>
> I think it should result in
>
>         OWNER@:   rwx::allow
>         GROUP@:   -wx::deny
>         GROUP@:   r--::allow
>         EVERYONE@:rwx::allow
>
> But does the GROUP@ deny actually get in there?  It looks to me like the
> denies inserted by richacl_isolate_group_class only take into account
> the group mask, not the actual permissions granted to any group class
> user.
>
> I may be mising something.

Thanks for the test case and analysis. The group@ deny ace that should be
inserted here actually doesn't get inserted. I'm attaching a fix.

---

By the way, all those scenarios can easily be tries out using the test
suite in the user-space richacl package, even without a richacl enabled
kernel. In this case, without the fix, we get:

  $ make tests/richacl-apply-masks
  $ tests/richacl-apply-masks -m 777 group@:r::allow
  group@:r::allow
  everyone@:rwpx::allow

With the fix, we are now getting:

  $ tests/richacl-apply-masks -m 777 group@:r::allow
  owner@:rwpx::allow
  group@:r::allow
  group@:wpx::deny
  everyone@:rwpx::allow

Thanks,
Andreas

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 726d796..bc0bcfe 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -463,23 +463,28 @@ richacl_set_owner_permissions(struct richacl_alloc *alloc)
 
 /**
  * richacl_set_other_permissions  -  set the other permissions to the other mask
+ * @alloc:	acl and number of allocated entries
+ * @added:	permissions added for everyone@
  *
  * Change the acl so that everyone@ is granted the permissions set in the other
  * mask.  This leaves at most one efective everyone@ allow entry at the end of
- * the acl.
+ * the acl.  If everyone@ end up being granted additional permissions, these
+ * permissions are returned in @added.
  */
 static int
-richacl_set_other_permissions(struct richacl_alloc *alloc)
+richacl_set_other_permissions(struct richacl_alloc *alloc, unsigned int *added)
 {
 	struct richacl *acl = alloc->acl;
 	unsigned int x = RICHACE_POSIX_ALWAYS_ALLOWED;
 	unsigned int other_mask = acl->a_other_mask & ~x;
-	struct richace *ace = acl->a_entries + acl->a_count - 1;
+	struct richace *ace;
 
 	if (!(other_mask &&
 	      (acl->a_flags & RICHACL_WRITE_THROUGH)))
 		return 0;
 
+	*added = other_mask;
+	ace = acl->a_entries + acl->a_count - 1;
 	if (acl->a_count == 0 ||
 	    !richace_is_everyone(ace) ||
 	    richace_is_inherit_only(ace)) {
@@ -490,8 +495,10 @@ richacl_set_other_permissions(struct richacl_alloc *alloc)
 		ace->e_flags = RICHACE_SPECIAL_WHO;
 		ace->e_mask = other_mask;
 		ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
-	} else
+	} else {
+		*added &= ~ace->e_mask;
 		richace_change_mask(alloc, &ace, other_mask);
+	}
 	return 0;
 }
 
@@ -645,6 +652,7 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
 /**
  * richacl_isolate_group_class  -  limit the group class to the group file mask
  * @alloc:	acl and number of allocated entries
+ * @deny:	additional permissions to deny
  *
  * POSIX requires that after a chmod, the group class is granted no more
  * permissions than the group file permission bits.  For richacls, this
@@ -679,21 +687,20 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
  *    everyone@:rw::allow
  */
 static int
-richacl_isolate_group_class(struct richacl_alloc *alloc)
+richacl_isolate_group_class(struct richacl_alloc *alloc, unsigned int deny)
 {
 	struct richace who = {
 		.e_flags = RICHACE_SPECIAL_WHO,
 		.e_id.special = RICHACE_GROUP_SPECIAL_ID,
 	};
 	struct richace *ace;
-	unsigned int deny;
 
 	if (!alloc->acl->a_count)
 		return 0;
 	ace = alloc->acl->a_entries + alloc->acl->a_count - 1;
 	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
 		return 0;
-	deny = ace->e_mask & ~alloc->acl->a_group_mask;
+	deny |= ace->e_mask & ~alloc->acl->a_group_mask;
 
 	if (deny) {
 		unsigned int n;
@@ -806,16 +813,17 @@ richacl_apply_masks(struct richacl **acl, kuid_t owner)
 			.acl = richacl_clone(*acl, GFP_KERNEL),
 			.count = (*acl)->a_count,
 		};
+		unsigned int added = 0;
+
 		if (!alloc.acl)
 			return -ENOMEM;
-
 		if (richacl_move_everyone_aces_down(&alloc) ||
 		    richacl_propagate_everyone(&alloc) ||
 		    __richacl_apply_masks(&alloc, owner) ||
+		    richacl_set_other_permissions(&alloc, &added) ||
+		    richacl_isolate_group_class(&alloc, added) ||
 		    richacl_set_owner_permissions(&alloc) ||
-		    richacl_set_other_permissions(&alloc) ||
-		    richacl_isolate_owner_class(&alloc) ||
-		    richacl_isolate_group_class(&alloc)) {
+		    richacl_isolate_owner_class(&alloc)) {
 			richacl_put(alloc.acl);
 			return -ENOMEM;
 		}
-- 
2.4.3


^ permalink raw reply related	[flat|nested] 188+ messages in thread

* Re: [PATCH] richacl: Possible other write-through fix
@ 2015-09-25 16:45             ` Andreas Gruenbacher
  0 siblings, 0 replies; 188+ messages in thread
From: Andreas Gruenbacher @ 2015-09-25 16:45 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

2015-09-24 20:33 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> On Sat, Sep 05, 2015 at 12:27:21PM +0200, Andreas Gruenbacher wrote:
>> +int
>> +richacl_apply_masks(struct richacl **acl, kuid_t owner)
>> +{
>> +     if ((*acl)->a_flags & RICHACL_MASKED) {
>> +             struct richacl_alloc alloc = {
>> +                     .acl = richacl_clone(*acl, GFP_KERNEL),
>> +                     .count = (*acl)->a_count,
>> +             };
>> +             if (!alloc.acl)
>> +                     return -ENOMEM;
>> +
>> +             if (richacl_move_everyone_aces_down(&alloc) ||
>> +                 richacl_propagate_everyone(&alloc) ||
>> +                 __richacl_apply_masks(&alloc, owner) ||
>> +                 richacl_set_owner_permissions(&alloc) ||
>> +                 richacl_set_other_permissions(&alloc) ||
>
> Hm, I'm a little unsure about this step: it seems like the one step that
> can actually change the permissions (making them more permissive, in
> this case), whereas the others are neutral.
>
> The following two steps should fix that, but I'm not sure they do.
>
> E.g. if I have an ACL like
>
>         mask 0777, WRITE_THROUGH
>         GROUP@:r--::allow
>
> I think it should result in
>
>         OWNER@:   rwx::allow
>         GROUP@:   -wx::deny
>         GROUP@:   r--::allow
>         EVERYONE@:rwx::allow
>
> But does the GROUP@ deny actually get in there?  It looks to me like the
> denies inserted by richacl_isolate_group_class only take into account
> the group mask, not the actual permissions granted to any group class
> user.
>
> I may be mising something.

Thanks for the test case and analysis. The group@ deny ace that should be
inserted here actually doesn't get inserted. I'm attaching a fix.

---

By the way, all those scenarios can easily be tries out using the test
suite in the user-space richacl package, even without a richacl enabled
kernel. In this case, without the fix, we get:

  $ make tests/richacl-apply-masks
  $ tests/richacl-apply-masks -m 777 group@:r::allow
  group@:r::allow
  everyone@:rwpx::allow

With the fix, we are now getting:

  $ tests/richacl-apply-masks -m 777 group@:r::allow
  owner@:rwpx::allow
  group@:r::allow
  group@:wpx::deny
  everyone@:rwpx::allow

Thanks,
Andreas

diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index 726d796..bc0bcfe 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -463,23 +463,28 @@ richacl_set_owner_permissions(struct richacl_alloc *alloc)
 
 /**
  * richacl_set_other_permissions  -  set the other permissions to the other mask
+ * @alloc:	acl and number of allocated entries
+ * @added:	permissions added for everyone@
  *
  * Change the acl so that everyone@ is granted the permissions set in the other
  * mask.  This leaves at most one efective everyone@ allow entry at the end of
- * the acl.
+ * the acl.  If everyone@ end up being granted additional permissions, these
+ * permissions are returned in @added.
  */
 static int
-richacl_set_other_permissions(struct richacl_alloc *alloc)
+richacl_set_other_permissions(struct richacl_alloc *alloc, unsigned int *added)
 {
 	struct richacl *acl = alloc->acl;
 	unsigned int x = RICHACE_POSIX_ALWAYS_ALLOWED;
 	unsigned int other_mask = acl->a_other_mask & ~x;
-	struct richace *ace = acl->a_entries + acl->a_count - 1;
+	struct richace *ace;
 
 	if (!(other_mask &&
 	      (acl->a_flags & RICHACL_WRITE_THROUGH)))
 		return 0;
 
+	*added = other_mask;
+	ace = acl->a_entries + acl->a_count - 1;
 	if (acl->a_count == 0 ||
 	    !richace_is_everyone(ace) ||
 	    richace_is_inherit_only(ace)) {
@@ -490,8 +495,10 @@ richacl_set_other_permissions(struct richacl_alloc *alloc)
 		ace->e_flags = RICHACE_SPECIAL_WHO;
 		ace->e_mask = other_mask;
 		ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
-	} else
+	} else {
+		*added &= ~ace->e_mask;
 		richace_change_mask(alloc, &ace, other_mask);
+	}
 	return 0;
 }
 
@@ -645,6 +652,7 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
 /**
  * richacl_isolate_group_class  -  limit the group class to the group file mask
  * @alloc:	acl and number of allocated entries
+ * @deny:	additional permissions to deny
  *
  * POSIX requires that after a chmod, the group class is granted no more
  * permissions than the group file permission bits.  For richacls, this
@@ -679,21 +687,20 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
  *    everyone@:rw::allow
  */
 static int
-richacl_isolate_group_class(struct richacl_alloc *alloc)
+richacl_isolate_group_class(struct richacl_alloc *alloc, unsigned int deny)
 {
 	struct richace who = {
 		.e_flags = RICHACE_SPECIAL_WHO,
 		.e_id.special = RICHACE_GROUP_SPECIAL_ID,
 	};
 	struct richace *ace;
-	unsigned int deny;
 
 	if (!alloc->acl->a_count)
 		return 0;
 	ace = alloc->acl->a_entries + alloc->acl->a_count - 1;
 	if (richace_is_inherit_only(ace) || !richace_is_everyone(ace))
 		return 0;
-	deny = ace->e_mask & ~alloc->acl->a_group_mask;
+	deny |= ace->e_mask & ~alloc->acl->a_group_mask;
 
 	if (deny) {
 		unsigned int n;
@@ -806,16 +813,17 @@ richacl_apply_masks(struct richacl **acl, kuid_t owner)
 			.acl = richacl_clone(*acl, GFP_KERNEL),
 			.count = (*acl)->a_count,
 		};
+		unsigned int added = 0;
+
 		if (!alloc.acl)
 			return -ENOMEM;
-
 		if (richacl_move_everyone_aces_down(&alloc) ||
 		    richacl_propagate_everyone(&alloc) ||
 		    __richacl_apply_masks(&alloc, owner) ||
+		    richacl_set_other_permissions(&alloc, &added) ||
+		    richacl_isolate_group_class(&alloc, added) ||
 		    richacl_set_owner_permissions(&alloc) ||
-		    richacl_set_other_permissions(&alloc) ||
-		    richacl_isolate_owner_class(&alloc) ||
-		    richacl_isolate_group_class(&alloc)) {
+		    richacl_isolate_owner_class(&alloc)) {
 			richacl_put(alloc.acl);
 			return -ENOMEM;
 		}
-- 
2.4.3

^ permalink raw reply related	[flat|nested] 188+ messages in thread

* Re: [PATCH] richacl: Possible other write-through fix
  2015-09-25 16:45             ` Andreas Gruenbacher
@ 2015-09-25 18:36                 ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-25 18:36 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA,
	linux-api-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-security-module-u79uwXL29TY76Z2rM5mHXA

On Fri, Sep 25, 2015 at 06:45:59PM +0200, Andreas Gruenbacher wrote:
> 2015-09-24 20:33 GMT+02:00 J. Bruce Fields <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>:
> > On Sat, Sep 05, 2015 at 12:27:21PM +0200, Andreas Gruenbacher wrote:
> >> +int
> >> +richacl_apply_masks(struct richacl **acl, kuid_t owner)
> >> +{
> >> +     if ((*acl)->a_flags & RICHACL_MASKED) {
> >> +             struct richacl_alloc alloc = {
> >> +                     .acl = richacl_clone(*acl, GFP_KERNEL),
> >> +                     .count = (*acl)->a_count,
> >> +             };
> >> +             if (!alloc.acl)
> >> +                     return -ENOMEM;
> >> +
> >> +             if (richacl_move_everyone_aces_down(&alloc) ||
> >> +                 richacl_propagate_everyone(&alloc) ||
> >> +                 __richacl_apply_masks(&alloc, owner) ||
> >> +                 richacl_set_owner_permissions(&alloc) ||
> >> +                 richacl_set_other_permissions(&alloc) ||
> >
> > Hm, I'm a little unsure about this step: it seems like the one step that
> > can actually change the permissions (making them more permissive, in
> > this case), whereas the others are neutral.
> >
> > The following two steps should fix that, but I'm not sure they do.
> >
> > E.g. if I have an ACL like
> >
> >         mask 0777, WRITE_THROUGH
> >         GROUP@:r--::allow
> >
> > I think it should result in
> >
> >         OWNER@:   rwx::allow
> >         GROUP@:   -wx::deny
> >         GROUP@:   r--::allow
> >         EVERYONE@:rwx::allow
> >
> > But does the GROUP@ deny actually get in there?  It looks to me like the
> > denies inserted by richacl_isolate_group_class only take into account
> > the group mask, not the actual permissions granted to any group class
> > user.
> >
> > I may be mising something.
> 
> Thanks for the test case and analysis. The group@ deny ace that should be
> inserted here actually doesn't get inserted. I'm attaching a fix.

This looks correct with the fix, thanks!

One nit:

>  		if (richacl_move_everyone_aces_down(&alloc) ||
>  		    richacl_propagate_everyone(&alloc) ||
>  		    __richacl_apply_masks(&alloc, owner) ||
> +		    richacl_set_other_permissions(&alloc, &added) ||

It's still the case that this step lacks the property shared by the
other step, that it leaves permissions unchanged.  Instead it increases
permissions, then the following step fixes the problem.  That
complicates review slightly.

But I'm not sure that matters much.

Reviewed-by: J. Bruce Fields <bfields-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>

--b.

> +		    richacl_isolate_group_class(&alloc, added) ||
>  		    richacl_set_owner_permissions(&alloc) ||
> -		    richacl_set_other_permissions(&alloc) ||
> -		    richacl_isolate_owner_class(&alloc) ||
> -		    richacl_isolate_group_class(&alloc)) {
> +		    richacl_isolate_owner_class(&alloc)) {
>  			richacl_put(alloc.acl);
>  			return -ENOMEM;
>  		}
> -- 
> 2.4.3

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [PATCH] richacl: Possible other write-through fix
@ 2015-09-25 18:36                 ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-25 18:36 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: linux-kernel, linux-fsdevel, linux-nfs, linux-api, linux-cifs,
	linux-security-module

On Fri, Sep 25, 2015 at 06:45:59PM +0200, Andreas Gruenbacher wrote:
> 2015-09-24 20:33 GMT+02:00 J. Bruce Fields <bfields@fieldses.org>:
> > On Sat, Sep 05, 2015 at 12:27:21PM +0200, Andreas Gruenbacher wrote:
> >> +int
> >> +richacl_apply_masks(struct richacl **acl, kuid_t owner)
> >> +{
> >> +     if ((*acl)->a_flags & RICHACL_MASKED) {
> >> +             struct richacl_alloc alloc = {
> >> +                     .acl = richacl_clone(*acl, GFP_KERNEL),
> >> +                     .count = (*acl)->a_count,
> >> +             };
> >> +             if (!alloc.acl)
> >> +                     return -ENOMEM;
> >> +
> >> +             if (richacl_move_everyone_aces_down(&alloc) ||
> >> +                 richacl_propagate_everyone(&alloc) ||
> >> +                 __richacl_apply_masks(&alloc, owner) ||
> >> +                 richacl_set_owner_permissions(&alloc) ||
> >> +                 richacl_set_other_permissions(&alloc) ||
> >
> > Hm, I'm a little unsure about this step: it seems like the one step that
> > can actually change the permissions (making them more permissive, in
> > this case), whereas the others are neutral.
> >
> > The following two steps should fix that, but I'm not sure they do.
> >
> > E.g. if I have an ACL like
> >
> >         mask 0777, WRITE_THROUGH
> >         GROUP@:r--::allow
> >
> > I think it should result in
> >
> >         OWNER@:   rwx::allow
> >         GROUP@:   -wx::deny
> >         GROUP@:   r--::allow
> >         EVERYONE@:rwx::allow
> >
> > But does the GROUP@ deny actually get in there?  It looks to me like the
> > denies inserted by richacl_isolate_group_class only take into account
> > the group mask, not the actual permissions granted to any group class
> > user.
> >
> > I may be mising something.
> 
> Thanks for the test case and analysis. The group@ deny ace that should be
> inserted here actually doesn't get inserted. I'm attaching a fix.

This looks correct with the fix, thanks!

One nit:

>  		if (richacl_move_everyone_aces_down(&alloc) ||
>  		    richacl_propagate_everyone(&alloc) ||
>  		    __richacl_apply_masks(&alloc, owner) ||
> +		    richacl_set_other_permissions(&alloc, &added) ||

It's still the case that this step lacks the property shared by the
other step, that it leaves permissions unchanged.  Instead it increases
permissions, then the following step fixes the problem.  That
complicates review slightly.

But I'm not sure that matters much.

Reviewed-by: J. Bruce Fields <bfields@redhat.com>

--b.

> +		    richacl_isolate_group_class(&alloc, added) ||
>  		    richacl_set_owner_permissions(&alloc) ||
> -		    richacl_set_other_permissions(&alloc) ||
> -		    richacl_isolate_owner_class(&alloc) ||
> -		    richacl_isolate_group_class(&alloc)) {
> +		    richacl_isolate_owner_class(&alloc)) {
>  			richacl_put(alloc.acl);
>  			return -ENOMEM;
>  		}
> -- 
> 2.4.3

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 25/41] richacl: Isolate the owner and group classes
  2015-09-25 11:25               ` Andreas Gruenbacher
@ 2015-09-25 20:17                   ` J. Bruce Fields
  -1 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-25 20:17 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: LKML, linux-fsdevel, Linux NFS Mailing List,
	Linux API Mailing List, linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	LSM List

On Fri, Sep 25, 2015 at 01:25:41PM +0200, Andreas Gruenbacher wrote:
> Here is another minor improvement that produces deny aces with fewer
> permissions in them and avoids creating unnecessary deny aces in some
> cases.

Looks good.--b.

> 
> Andreas
> 
> ---
>  fs/richacl_compat.c | 5 ++---
>  1 file changed, 2 insertions(+), 3 deletions(-)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 2f53394..bc0bcfe 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -605,14 +605,13 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
>  	int n;
>  
>  	/*
> -	 * Compute the permissions already denied to @who.  There are no
> +	 * Compute the permissions already defined for @who.  There are no
>  	 * everyone@ deny aces left in the acl at this stage.
>  	 */
>  	richacl_for_each_entry(ace, acl) {
>  		if (richace_is_inherit_only(ace))
>  			continue;
> -		if (richace_is_same_identifier(acl, ace, who) &&
> -		    richace_is_deny(ace))
> +		if (richace_is_same_identifier(acl, ace, who))
>  			deny &= ~ace->e_mask;
>  	}
>  	if (!deny)
> -- 
> 2.4.3

^ permalink raw reply	[flat|nested] 188+ messages in thread

* Re: [RFC v7 25/41] richacl: Isolate the owner and group classes
@ 2015-09-25 20:17                   ` J. Bruce Fields
  0 siblings, 0 replies; 188+ messages in thread
From: J. Bruce Fields @ 2015-09-25 20:17 UTC (permalink / raw)
  To: Andreas Gruenbacher
  Cc: LKML, linux-fsdevel, Linux NFS Mailing List,
	Linux API Mailing List, linux-cifs, LSM List

On Fri, Sep 25, 2015 at 01:25:41PM +0200, Andreas Gruenbacher wrote:
> Here is another minor improvement that produces deny aces with fewer
> permissions in them and avoids creating unnecessary deny aces in some
> cases.

Looks good.--b.

> 
> Andreas
> 
> ---
>  fs/richacl_compat.c | 5 ++---
>  1 file changed, 2 insertions(+), 3 deletions(-)
> 
> diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
> index 2f53394..bc0bcfe 100644
> --- a/fs/richacl_compat.c
> +++ b/fs/richacl_compat.c
> @@ -605,14 +605,13 @@ __richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
>  	int n;
>  
>  	/*
> -	 * Compute the permissions already denied to @who.  There are no
> +	 * Compute the permissions already defined for @who.  There are no
>  	 * everyone@ deny aces left in the acl at this stage.
>  	 */
>  	richacl_for_each_entry(ace, acl) {
>  		if (richace_is_inherit_only(ace))
>  			continue;
> -		if (richace_is_same_identifier(acl, ace, who) &&
> -		    richace_is_deny(ace))
> +		if (richace_is_same_identifier(acl, ace, who))
>  			deny &= ~ace->e_mask;
>  	}
>  	if (!deny)
> -- 
> 2.4.3

^ permalink raw reply	[flat|nested] 188+ messages in thread

end of thread, other threads:[~2015-09-25 20:17 UTC | newest]

Thread overview: 188+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-09-05 10:26 [RFC v7 00/41] Richacls Andreas Gruenbacher
2015-09-05 10:26 ` Andreas Gruenbacher
2015-09-05 10:26 ` [RFC v7 04/41] vfs: Make the inode passed to inode_change_ok non-const Andreas Gruenbacher
2015-09-05 10:27 ` [RFC v7 05/41] vfs: Add permission flags for setting file attributes Andreas Gruenbacher
2015-09-05 10:27 ` [RFC v7 06/41] richacl: In-memory representation and helper functions Andreas Gruenbacher
2015-09-05 10:27 ` [RFC v7 07/41] richacl: Permission mapping functions Andreas Gruenbacher
     [not found] ` <1441448856-13478-1-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2015-09-05 10:26   ` [RFC v7 01/41] vfs: Add IS_ACL() and IS_RICHACL() tests Andreas Gruenbacher
2015-09-05 10:26     ` Andreas Gruenbacher
2015-09-05 10:26   ` [RFC v7 02/41] vfs: Add MAY_CREATE_FILE and MAY_CREATE_DIR permission flags Andreas Gruenbacher
2015-09-05 10:26     ` Andreas Gruenbacher
2015-09-05 10:26   ` [RFC v7 03/41] vfs: Add MAY_DELETE_SELF and MAY_DELETE_CHILD " Andreas Gruenbacher
2015-09-05 10:26     ` Andreas Gruenbacher
2015-09-06  8:14     ` [PATCH] " Andreas Gruenbacher
     [not found]       ` <1441527246-18189-1-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2015-09-11 20:30         ` J. Bruce Fields
2015-09-11 20:30           ` J. Bruce Fields
2015-09-05 10:27   ` [RFC v7 08/41] richacl: Compute maximum file masks from an acl Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
2015-09-05 10:27   ` [RFC v7 09/41] richacl: Update the file masks in chmod() Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
     [not found]     ` <1441448856-13478-10-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2015-09-11 20:35       ` J. Bruce Fields
2015-09-11 20:35         ` J. Bruce Fields
2015-09-05 10:27   ` [RFC v7 10/41] richacl: Permission check algorithm Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
     [not found]     ` <1441448856-13478-11-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2015-09-11 21:16       ` J. Bruce Fields
2015-09-11 21:16         ` J. Bruce Fields
     [not found]         ` <20150911211617.GF11677-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
2015-09-11 22:12           ` Andreas Grünbacher
2015-09-11 22:12             ` Andreas Grünbacher
2015-09-17 17:30             ` J. Bruce Fields
2015-09-05 10:27   ` [RFC v7 11/41] vfs: Cache base_acl objects in inodes Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
2015-09-05 10:27   ` [RFC v7 13/41] richacl: Check if an acl is equivalent to a file mode Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
2015-09-17 18:22     ` J. Bruce Fields
     [not found]       ` <20150917182219.GB13825-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
2015-09-18  0:56         ` J. Bruce Fields
2015-09-18  0:56           ` J. Bruce Fields
     [not found]           ` <20150918005607.GB16699-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
2015-09-21 13:59             ` Austin S Hemmelgarn
2015-09-21 13:59               ` Austin S Hemmelgarn
     [not found]               ` <56000D2B.6000705-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
2015-09-21 14:38                 ` J. Bruce Fields
2015-09-21 14:38                   ` J. Bruce Fields
     [not found]                   ` <20150921143817.GA11256-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
2015-09-21 17:00                     ` Austin S Hemmelgarn
2015-09-21 17:00                       ` Austin S Hemmelgarn
2015-09-21 17:48                       ` J. Bruce Fields
2015-09-21 15:31                 ` J. Bruce Fields
2015-09-21 15:31                   ` J. Bruce Fields
2015-09-21 23:26             ` Andreas Gruenbacher
2015-09-21 23:26               ` Andreas Gruenbacher
2015-09-21 23:20       ` Andreas Gruenbacher
2015-09-17 18:37     ` J. Bruce Fields
2015-09-05 10:27   ` [RFC v7 14/41] richacl: Create-time inheritance Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
     [not found]     ` <1441448856-13478-15-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2015-09-18 17:58       ` J. Bruce Fields
2015-09-18 17:58         ` J. Bruce Fields
     [not found]         ` <20150918175840.GA21506-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
2015-09-21 20:37           ` Andreas Gruenbacher
2015-09-21 20:37             ` Andreas Gruenbacher
2015-09-05 10:27   ` [RFC v7 17/41] vfs: Add richacl permission checking Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
2015-09-05 10:27   ` [RFC v7 19/41] ext4: Add richacl feature flag Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
2015-09-23  2:31     ` Aneesh Kumar K.V
2015-09-05 10:27   ` [RFC v7 20/41] richacl: acl editing helper functions Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
     [not found]     ` <1441448856-13478-21-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2015-09-18 18:54       ` J. Bruce Fields
2015-09-18 18:54         ` J. Bruce Fields
2015-09-05 10:27   ` [RFC v7 22/41] richacl: Propagate everyone@ permissions to other aces Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
     [not found]     ` <1441448856-13478-23-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2015-09-18 21:36       ` J. Bruce Fields
2015-09-18 21:36         ` J. Bruce Fields
     [not found]         ` <20150918213611.GC22671-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
2015-09-21 23:44           ` Andreas Gruenbacher
2015-09-21 23:44             ` Andreas Gruenbacher
2015-09-18 21:56       ` J. Bruce Fields
2015-09-18 21:56         ` J. Bruce Fields
     [not found]         ` <20150918215611.GD22671-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
2015-09-21 19:24           ` J. Bruce Fields
2015-09-21 19:24             ` J. Bruce Fields
     [not found]             ` <20150921192441.GA12968-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
2015-09-23  1:24               ` Andreas Gruenbacher
2015-09-23  1:24                 ` Andreas Gruenbacher
     [not found]             ` <CAHc6FU5Ug3rN2-znFeABpdn+LCHgvzOnSRB4BCepNS6mToJVZg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2015-09-23  1:39               ` Andreas Gruenbacher
2015-09-23  1:39                 ` Andreas Gruenbacher
     [not found]                 ` <1442972384-22757-1-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2015-09-23  1:46                   ` J. Bruce Fields
2015-09-23  1:46                     ` J. Bruce Fields
2015-09-05 10:27   ` [RFC v7 24/41] richacl: Set the other permissions to the other mask Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
2015-09-23 14:03     ` J. Bruce Fields
     [not found]       ` <20150923140307.GB27083-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
2015-09-23 14:12         ` Andreas Grünbacher
2015-09-23 14:12           ` Andreas Grünbacher
2015-09-05 10:27   ` [RFC v7 25/41] richacl: Isolate the owner and group classes Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
     [not found]     ` <1441448856-13478-26-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2015-09-22 16:06       ` J. Bruce Fields
2015-09-22 16:06         ` J. Bruce Fields
     [not found]         ` <20150922160637.GC15838-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
2015-09-23 13:11           ` Andreas Gruenbacher
2015-09-23 13:11             ` Andreas Gruenbacher
     [not found]             ` <CAHc6FU6gnPK5vdJynv0ze=mNju6V_1WuR99oXWC4Fdh2GFMVgA-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2015-09-23 13:15               ` J. Bruce Fields
2015-09-23 13:15                 ` J. Bruce Fields
2015-09-22 19:02     ` J. Bruce Fields
     [not found]       ` <20150922190224.GA19127-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
2015-09-23 13:33         ` Andreas Gruenbacher
2015-09-23 13:33           ` Andreas Gruenbacher
     [not found]           ` <CAHc6FU7_+fbyG0mEu3pUkfaV72AM0DzJnBES=b--koXJgo0a2g-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2015-09-25 11:25             ` Andreas Gruenbacher
2015-09-25 11:25               ` Andreas Gruenbacher
     [not found]               ` <1443180341-22911-1-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2015-09-25 20:17                 ` J. Bruce Fields
2015-09-25 20:17                   ` J. Bruce Fields
2015-09-22 19:02     ` J. Bruce Fields
2015-09-05 10:27   ` [RFC v7 26/41] richacl: Apply the file masks to a richacl Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
2015-09-22 19:11     ` J. Bruce Fields
     [not found]       ` <20150922191108.GC19127-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
2015-09-23 19:18         ` J. Bruce Fields
2015-09-23 19:18           ` J. Bruce Fields
     [not found]           ` <20150923191832.GA29577-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
2015-09-23 20:29             ` Andreas Gruenbacher
2015-09-23 20:29               ` Andreas Gruenbacher
     [not found]               ` <CAHc6FU4YkLJUAQEfH7CG8sAzXYbGzg_ibyT7m26Fc1p26v1=VQ-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2015-09-23 20:33                 ` J. Bruce Fields
2015-09-23 20:33                   ` J. Bruce Fields
     [not found]                   ` <20150923203357.GC30521-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
2015-09-23 20:40                     ` Andreas Gruenbacher
2015-09-23 20:40                       ` Andreas Gruenbacher
2015-09-23 21:05                       ` J. Bruce Fields
     [not found]                         ` <20150923210531.GC29349-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
2015-09-23 22:14                           ` Andreas Gruenbacher
2015-09-23 22:14                             ` Andreas Gruenbacher
2015-09-24 15:28                             ` J. Bruce Fields
     [not found]                               ` <20150924152851.GC3823-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
2015-09-24 15:48                                 ` Andreas Gruenbacher
2015-09-24 15:48                                   ` Andreas Gruenbacher
     [not found]     ` <1441448856-13478-27-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2015-09-22 20:50       ` J. Bruce Fields
2015-09-22 20:50         ` J. Bruce Fields
2015-09-24 18:33       ` J. Bruce Fields
2015-09-24 18:33         ` J. Bruce Fields
     [not found]         ` <20150924183310.GE3823-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
2015-09-25 16:21           ` [PATCH] richacl: Possible other write-through fix Andreas Gruenbacher
2015-09-25 16:21             ` Andreas Gruenbacher
2015-09-25 16:21             ` Andreas Gruenbacher
2015-09-25 16:45           ` Andreas Gruenbacher
2015-09-25 16:45             ` Andreas Gruenbacher
2015-09-25 16:45             ` Andreas Gruenbacher
     [not found]             ` <1443199559-4870-1-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2015-09-25 18:36               ` J. Bruce Fields
2015-09-25 18:36                 ` J. Bruce Fields
2015-09-05 10:27   ` [RFC v7 27/41] richacl: Create richacl from mode values Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
     [not found]     ` <1441448856-13478-28-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2015-09-23 20:11       ` J. Bruce Fields
2015-09-23 20:11         ` J. Bruce Fields
2015-09-05 10:27   ` [RFC v7 28/41] nfsd: Keep list of acls to dispose of in compoundargs Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
     [not found]     ` <1441448856-13478-29-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2015-09-23 20:28       ` J. Bruce Fields
2015-09-23 20:28         ` J. Bruce Fields
2015-09-05 10:27   ` [RFC v7 29/41] nfsd: Use richacls as internal acl representation Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
     [not found]     ` <1441448856-13478-30-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2015-09-24 19:29       ` J. Bruce Fields
2015-09-24 19:29         ` J. Bruce Fields
2015-09-05 10:27   ` [RFC v7 30/41] nfsd: Add richacl support Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
2015-09-24 19:38     ` J. Bruce Fields
2015-09-05 10:27   ` [RFC v7 31/41] nfsd: Add support for the v4.1 dacl attribute Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
     [not found]     ` <1441448856-13478-32-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2015-09-24 19:59       ` J. Bruce Fields
2015-09-24 19:59         ` J. Bruce Fields
2015-09-25 16:37         ` Andreas Gruenbacher
2015-09-05 10:27   ` [RFC v7 32/41] nfsd: Add support for the MAY_CREATE_{FILE,DIR} permissions Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
     [not found]     ` <1441448856-13478-33-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2015-09-24 20:01       ` J. Bruce Fields
2015-09-24 20:01         ` J. Bruce Fields
2015-09-05 10:27   ` [RFC v7 33/41] richacl: Add support for unmapped identifiers Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
2015-09-05 10:27   ` [RFC v7 35/41] sunrpc: Allow to demand-allocate pages to encode into Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
2015-09-05 10:27   ` [RFC v7 36/41] sunrpc: Add xdr_init_encode_pages Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
2015-09-05 10:27   ` [RFC v7 38/41] nfs: Remove unused xdr page offsets in getacl/setacl arguments Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
2015-09-05 10:27   ` [RFC v7 39/41] nfs: Add richacl support Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
2015-09-05 10:27   ` [RFC v7 40/41] nfs: Add support for the v4.1 dacl attribute Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
2015-09-05 10:27   ` [RFC v7 41/41] richacl: uapi header split Andreas Gruenbacher
2015-09-05 10:27     ` Andreas Gruenbacher
2015-09-05 10:27 ` [RFC v7 12/41] vfs: Cache richacl in struct inode Andreas Gruenbacher
2015-09-05 10:27 ` [RFC v7 15/41] richacl: Automatic Inheritance Andreas Gruenbacher
2015-09-18 18:40   ` J. Bruce Fields
2015-09-21 21:19     ` Andreas Gruenbacher
2015-09-22  1:51       ` J. Bruce Fields
     [not found]         ` <20150922015146.GA15960-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
2015-09-23 13:55           ` J. Bruce Fields
2015-09-23 13:55             ` J. Bruce Fields
2015-09-05 10:27 ` [RFC v7 16/41] richacl: xattr mapping functions Andreas Gruenbacher
2015-09-05 10:27 ` [RFC v7 18/41] ext4: Add richacl support Andreas Gruenbacher
     [not found]   ` <1441448856-13478-19-git-send-email-agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2015-09-23  2:30     ` Aneesh Kumar K.V
2015-09-23  2:30       ` Aneesh Kumar K.V
2015-09-05 10:27 ` [RFC v7 21/41] richacl: Move everyone@ aces down the acl Andreas Gruenbacher
2015-09-18 19:35   ` J. Bruce Fields
     [not found]     ` <20150918193524.GA22671-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>
2015-09-21 21:43       ` Andreas Gruenbacher
2015-09-21 21:43         ` Andreas Gruenbacher
     [not found]         ` <CAHc6FU5m8KKSsEg18UhRSnVdsAzoFSD9pdLRv1DdDA==ZCQmdw-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2015-09-22  1:52           ` J. Bruce Fields
2015-09-22  1:52             ` J. Bruce Fields
2015-09-05 10:27 ` [RFC v7 23/41] richacl: Set the owner permissions to the owner mask Andreas Gruenbacher
2015-09-21 21:00   ` J. Bruce Fields
2015-09-05 10:27 ` [RFC v7 34/41] ext4: Don't allow unmapped identifiers in richacls Andreas Gruenbacher
2015-09-05 10:27 ` [RFC v7 37/41] nfs: Fix GETATTR bitmap verification Andreas Gruenbacher

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.