2015-11-05 11:39:18

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 00/22] Richacls (Core and Ext4)

Here is another update to the richacl patch queue. This posting contains
the patches ready to be merged; the patches later in the queue still need
some more review.

Changes since the last posting (http://lwn.net/Articles/662881/):

* Functions {get,put}_base_acl renamed to base_acl_{get,put}. Functions
base_acl_init and base_acl_refcount added and used where appropriate.
Better document that struct base_acl must be the first member in an ACL.

* Function acl_by_type un-exported and changed to return a struct base_acl
pointer.

* The "Cache base_acl objects in inodes" and "Cache richacl in struct
inode" patches were not perfectly split.

* Fix the commit message of "Add richacl feature flag".


The complete patch queue is available in git form here:

git://git.kernel.org/pub/scm/linux/kernel/git/agruen/linux-richacl.git \
richacl-2015-11-05


The richacl user-space utilitites, man pages, and test suite are available
here:

https://github.com/andreas-gruenbacher/richacl


Changes to other user-space packages for richacl are available here:

https://github.com/andreas-gruenbacher/coreutils
https://github.com/andreas-gruenbacher/e2fsprogs
https://github.com/andreas-gruenbacher/xfsprogs-dev
https://github.com/andreas-gruenbacher/nfs-utils


Please see the richacl homepage for more information:

http://www.bestbits.at/richacl/


Thanks,
Andreas

Andreas Gruenbacher (20):
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: Permission check algorithm
posix_acl: Unexport acl_by_type and make it static
vfs: Cache base_acl objects in inodes
vfs: Add get_richacl and set_richacl inode operations
vfs: Cache richacl in struct inode
richacl: Update the file masks in chmod()
richacl: Check if an acl is equivalent to a file mode
richacl: Create-time inheritance
richacl: Automatic Inheritance
richacl: xattr mapping functions
richacl: Add richacl xattr handler
vfs: Add richacl permission checking

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 | 3 +
fs/Makefile | 2 +
fs/attr.c | 81 +++-
fs/ext4/Kconfig | 11 +
fs/ext4/Makefile | 1 +
fs/ext4/ext4.h | 6 +-
fs/ext4/file.c | 3 +
fs/ext4/ialloc.c | 11 +-
fs/ext4/inode.c | 12 +-
fs/ext4/namei.c | 5 +
fs/ext4/richacl.c | 142 ++++++
fs/ext4/richacl.h | 40 ++
fs/ext4/super.c | 49 +-
fs/ext4/xattr.c | 7 +
fs/f2fs/acl.c | 4 +-
fs/inode.c | 15 +-
fs/jffs2/acl.c | 10 +-
fs/namei.c | 111 +++--
fs/posix_acl.c | 50 +--
fs/richacl_base.c | 564 ++++++++++++++++++++++++
fs/richacl_inode.c | 333 ++++++++++++++
fs/richacl_xattr.c | 298 +++++++++++++
fs/xattr.c | 34 +-
include/linux/fs.h | 60 ++-
include/linux/posix_acl.h | 13 +-
include/linux/richacl.h | 208 +++++++++
include/linux/richacl_xattr.h | 44 ++
include/uapi/linux/Kbuild | 2 +
include/uapi/linux/fs.h | 3 +-
include/uapi/linux/richacl.h | 152 +++++++
include/uapi/linux/richacl_xattr.h | 44 ++
include/uapi/linux/xattr.h | 2 +
33 files changed, 2213 insertions(+), 109 deletions(-)
create mode 100644 fs/ext4/richacl.c
create mode 100644 fs/ext4/richacl.h
create mode 100644 fs/richacl_base.c
create mode 100644 fs/richacl_inode.c
create mode 100644 fs/richacl_xattr.c
create mode 100644 include/linux/richacl.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.5.0


2015-11-05 11:39:19

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 01/22] vfs: Add IS_ACL() and IS_RICHACL() tests

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 <[email protected]>
Reviewed-by: J. Bruce Fields <[email protected]>
---
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 da3f32f..bff2879 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -56,6 +56,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 33e9495..224ecf1 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2798,7 +2798,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);
@@ -2982,7 +2982,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
@@ -3553,7 +3553,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)
@@ -3622,7 +3622,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 72d8a84..4efa435 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1781,6 +1781,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)
@@ -1794,6 +1800,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.5.0

2015-11-05 11:39:20

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 02/22] vfs: Add MAY_CREATE_FILE and MAY_CREATE_DIR permission flags

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 <[email protected]>
Reviewed-by: J. Bruce Fields <[email protected]>
---
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 224ecf1..0259392 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)
{
@@ -2549,10 +2551,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;
@@ -2561,7 +2564,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))
@@ -2592,14 +2597,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);
}

/*
@@ -2649,7 +2656,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;

@@ -3494,7 +3501,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;
@@ -3586,7 +3593,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)
@@ -3667,7 +3674,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;
@@ -3789,7 +3796,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;
@@ -3923,7 +3930,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;
@@ -4006,7 +4013,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;

@@ -4194,19 +4201,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;
@@ -4469,7 +4476,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 4efa435..d6e2330 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.5.0

2015-11-05 11:39:21

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 03/22] vfs: Add MAY_DELETE_SELF and MAY_DELETE_CHILD permission flags

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 <[email protected]>
Reviewed-by: J. Bruce Fields <[email protected]>
---
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 0259392..2eab19e 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)
{
@@ -2555,7 +2555,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;
@@ -2565,15 +2565,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 d6e2330..402acd7 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.5.0

_______________________________________________
xfs mailing list
[email protected]
http://oss.sgi.com/mailman/listinfo/xfs

2015-11-05 11:39:22

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 04/22] vfs: Make the inode passed to inode_change_ok non-const

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 <[email protected]>
Reviewed-by: J. Bruce Fields <[email protected]>
---
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 402acd7..aab32c8 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2871,7 +2871,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.5.0

_______________________________________________
xfs mailing list
[email protected]
http://oss.sgi.com/mailman/listinfo/xfs

2015-11-05 11:39:25

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 07/22] richacl: Permission mapping functions

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 <[email protected]>
Reviewed-by: J. Bruce Fields <[email protected]>
---
fs/richacl_base.c | 118 +++++++++++++++++++++++++++++++++++++++++++
include/linux/richacl.h | 3 ++
include/uapi/linux/richacl.h | 44 ++++++++++++++++
3 files changed, 165 insertions(+)

diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index c3ec928..a393001 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -65,3 +65,121 @@ 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
+ * @mode: mode to convert to richacl permissions
+ *
+ * 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(umode_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 edb8480..9102ef0 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -175,5 +175,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(umode_t);
+extern unsigned int richacl_want_to_mask(unsigned int);

#endif /* __RICHACL_H */
diff --git a/include/uapi/linux/richacl.h b/include/uapi/linux/richacl.h
index 08856f8..1ed48ac 100644
--- a/include/uapi/linux/richacl.h
+++ b/include/uapi/linux/richacl.h
@@ -96,4 +96,48 @@
RICHACE_WRITE_OWNER | \
RICHACE_SYNCHRONIZE )

+/*
+ * 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 */
--
2.5.0

_______________________________________________
xfs mailing list
[email protected]
http://oss.sgi.com/mailman/listinfo/xfs

2015-11-05 11:39:27

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 09/22] richacl: Permission check algorithm

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 <[email protected]>
Reviewed-by: "J. Bruce Fields" <[email protected]>
---
fs/Makefile | 2 +-
fs/richacl_inode.c | 149 ++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/richacl.h | 3 +
3 files changed, 153 insertions(+), 1 deletion(-)
create mode 100644 fs/richacl_inode.c

diff --git a/fs/Makefile b/fs/Makefile
index fe3e9dd..ec665fd 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -49,7 +49,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..99b3c93
--- /dev/null
+++ b/fs/richacl_inode.c
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2010 Novell, Inc.
+ * Copyright (C) 2015 Red Hat, Inc.
+ * Written by Andreas Gruenbacher <[email protected]>
+ *
+ * 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;
+ if (uid_eq(current_fsuid(), inode->i_uid))
+ goto entry_matches_owner;
+ } 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 3559b2c..1d9f5f7 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -180,4 +180,7 @@ extern unsigned int richacl_mode_to_mask(umode_t);
extern unsigned int richacl_want_to_mask(unsigned int);
extern void richacl_compute_max_masks(struct richacl *);

+/* richacl_inode.c */
+extern int richacl_permission(struct inode *, const struct richacl *, int);
+
#endif /* __RICHACL_H */
--
2.5.0

2015-11-05 11:39:26

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 08/22] richacl: Compute maximum file masks from an acl

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 <[email protected]>
Reviewed-by: J. Bruce Fields <[email protected]>
---
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 a393001..69b806c 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -183,3 +183,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 9102ef0..3559b2c 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -178,5 +178,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(umode_t);
extern unsigned int richacl_want_to_mask(unsigned int);
+extern void richacl_compute_max_masks(struct richacl *);

#endif /* __RICHACL_H */
--
2.5.0

_______________________________________________
xfs mailing list
[email protected]
http://oss.sgi.com/mailman/listinfo/xfs

2015-11-05 11:39:28

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 10/22] posix_acl: Unexport acl_by_type and make it static

acl_by_type(inode, type) returns a pointer to either inode->i_acl or
inode->i_default_acl depending on type. This is useful in
fs/posix_acl.c, but should never have been visible outside that file.

Signed-off-by: Andreas Gruenbacher <[email protected]>
---
fs/posix_acl.c | 3 +--
include/linux/posix_acl.h | 1 -
2 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/fs/posix_acl.c b/fs/posix_acl.c
index 4fb17de..aacfb58 100644
--- a/fs/posix_acl.c
+++ b/fs/posix_acl.c
@@ -21,7 +21,7 @@
#include <linux/export.h>
#include <linux/user_namespace.h>

-struct posix_acl **acl_by_type(struct inode *inode, int type)
+static struct posix_acl **acl_by_type(struct inode *inode, int type)
{
switch (type) {
case ACL_TYPE_ACCESS:
@@ -32,7 +32,6 @@ struct posix_acl **acl_by_type(struct inode *inode, int type)
BUG();
}
}
-EXPORT_SYMBOL(acl_by_type);

struct posix_acl *get_cached_acl(struct inode *inode, int type)
{
diff --git a/include/linux/posix_acl.h b/include/linux/posix_acl.h
index 3e96a6a..5b5a80c 100644
--- a/include/linux/posix_acl.h
+++ b/include/linux/posix_acl.h
@@ -99,7 +99,6 @@ extern int posix_acl_create(struct inode *, umode_t *, struct posix_acl **,
extern int simple_set_acl(struct inode *, struct posix_acl *, int);
extern int simple_acl_create(struct inode *, struct inode *);

-struct posix_acl **acl_by_type(struct inode *inode, int type);
struct posix_acl *get_cached_acl(struct inode *inode, int type);
struct posix_acl *get_cached_acl_rcu(struct inode *inode, int type);
void set_cached_acl(struct inode *inode, int type, struct posix_acl *acl);
--
2.5.0

_______________________________________________
xfs mailing list
[email protected]
http://oss.sgi.com/mailman/listinfo/xfs

2015-11-05 11:39:29

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 11/22] vfs: Cache base_acl objects in inodes

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 care which
kind of acl an inode caches (if any).

Signed-off-by: Andreas Gruenbacher <[email protected]>
---
drivers/staging/lustre/lustre/llite/llite_lib.c | 2 +-
fs/f2fs/acl.c | 4 +--
fs/inode.c | 4 +--
fs/jffs2/acl.c | 10 ++++--
fs/posix_acl.c | 41 +++++++++++++------------
fs/richacl_base.c | 4 +--
include/linux/fs.h | 34 ++++++++++++++++++--
include/linux/posix_acl.h | 12 +++-----
include/linux/richacl.h | 9 +++---
9 files changed, 75 insertions(+), 45 deletions(-)

diff --git a/drivers/staging/lustre/lustre/llite/llite_lib.c b/drivers/staging/lustre/lustre/llite/llite_lib.c
index b4ed6c8..46912c2 100644
--- a/drivers/staging/lustre/lustre/llite/llite_lib.c
+++ b/drivers/staging/lustre/lustre/llite/llite_lib.c
@@ -1118,7 +1118,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(base_acl_refcount(&lli->lli_posix_acl->a_base) == 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..9646197 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);
+ base_acl_init(&clone->a_base);
}
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(base_acl_refcount(&acl->a_base) == 1); */

FOREACH_ACL_ENTRY(pa, acl, pe) {
switch(pa->e_tag) {
diff --git a/fs/inode.c b/fs/inode.c
index 78a17b8..5c46ae5 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -233,9 +233,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);
+ base_acl_put(inode->i_acl);
if (inode->i_default_acl && inode->i_default_acl != ACL_NOT_CACHED)
- posix_acl_release(inode->i_default_acl);
+ base_acl_put(inode->i_default_acl);
#endif
this_cpu_dec(nr_inodes);
}
diff --git a/fs/jffs2/acl.c b/fs/jffs2/acl.c
index 2f7a3c0..569cb1b 100644
--- a/fs/jffs2/acl.c
+++ b/fs/jffs2/acl.c
@@ -294,13 +294,19 @@ 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);
+ struct posix_acl *default_acl = container_of(
+ inode->i_default_acl, struct posix_acl, a_base);
+
+ rc = __jffs2_set_acl(inode, JFFS2_XPREFIX_ACL_DEFAULT, default_acl);
if (rc)
return rc;
}

if (inode->i_acl) {
- rc = __jffs2_set_acl(inode, JFFS2_XPREFIX_ACL_ACCESS, inode->i_acl);
+ struct posix_acl *acl = container_of(
+ inode->i_acl, struct posix_acl, a_base);
+
+ rc = __jffs2_set_acl(inode, JFFS2_XPREFIX_ACL_ACCESS, acl);
if (rc)
return rc;
}
diff --git a/fs/posix_acl.c b/fs/posix_acl.c
index aacfb58..4573315 100644
--- a/fs/posix_acl.c
+++ b/fs/posix_acl.c
@@ -21,7 +21,7 @@
#include <linux/export.h>
#include <linux/user_namespace.h>

-static struct posix_acl **acl_by_type(struct inode *inode, int type)
+static struct base_acl **acl_by_type(struct inode *inode, int type)
{
switch (type) {
case ACL_TYPE_ACCESS:
@@ -35,63 +35,64 @@ static struct posix_acl **acl_by_type(struct inode *inode, int type)

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);
+ struct base_acl **p = acl_by_type(inode, type);
+ struct base_acl *acl = ACCESS_ONCE(*p);
if (acl) {
spin_lock(&inode->i_lock);
acl = *p;
if (acl != ACL_NOT_CACHED)
- acl = posix_acl_dup(acl);
+ base_acl_get(acl);
spin_unlock(&inode->i_lock);
}
- return acl;
+ return container_of(acl, struct posix_acl, a_base);
}
EXPORT_SYMBOL(get_cached_acl);

struct posix_acl *get_cached_acl_rcu(struct inode *inode, int type)
{
- return rcu_dereference(*acl_by_type(inode, type));
+ struct base_acl *acl = rcu_dereference(*acl_by_type(inode, type));
+ return container_of(acl, struct posix_acl, a_base);
}
EXPORT_SYMBOL(get_cached_acl_rcu);

void set_cached_acl(struct inode *inode, int type, struct posix_acl *acl)
{
- struct posix_acl **p = acl_by_type(inode, type);
- struct posix_acl *old;
+ struct base_acl **p = acl_by_type(inode, type);
+ struct base_acl *old;
spin_lock(&inode->i_lock);
old = *p;
- rcu_assign_pointer(*p, posix_acl_dup(acl));
+ rcu_assign_pointer(*p, &posix_acl_dup(acl)->a_base);
spin_unlock(&inode->i_lock);
if (old != ACL_NOT_CACHED)
- posix_acl_release(old);
+ base_acl_put(old);
}
EXPORT_SYMBOL(set_cached_acl);

void forget_cached_acl(struct inode *inode, int type)
{
- struct posix_acl **p = acl_by_type(inode, type);
- struct posix_acl *old;
+ struct base_acl **p = acl_by_type(inode, type);
+ struct base_acl *old;
spin_lock(&inode->i_lock);
old = *p;
*p = ACL_NOT_CACHED;
spin_unlock(&inode->i_lock);
if (old != ACL_NOT_CACHED)
- posix_acl_release(old);
+ base_acl_put(old);
}
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);
+ base_acl_put(old_access);
if (old_default != ACL_NOT_CACHED)
- posix_acl_release(old_default);
+ base_acl_put(old_default);
}
EXPORT_SYMBOL(forget_all_cached_acls);

@@ -128,7 +129,7 @@ EXPORT_SYMBOL(get_acl);
void
posix_acl_init(struct posix_acl *acl, int count)
{
- atomic_set(&acl->a_refcount, 1);
+ base_acl_init(&acl->a_base);
acl->a_count = count;
}
EXPORT_SYMBOL(posix_acl_init);
@@ -161,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);
+ base_acl_init(&clone->a_base);
}
return clone;
}
@@ -383,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(base_acl_refcount(&acl->a_base) == 1); */

FOREACH_ACL_ENTRY(pa, acl, pe) {
switch(pa->e_tag) {
@@ -438,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(base_acl_refcount(&acl->a_base) == 1); */

FOREACH_ACL_ENTRY(pa, acl, pe) {
switch(pa->e_tag) {
diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index 69b806c..5826842 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);
+ base_acl_init(&acl->a_base);
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);
+ base_acl_init(&dup->a_base);
}
return dup;
}
diff --git a/include/linux/fs.h b/include/linux/fs.h
index ba91a89..2fd840e 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;
@@ -3059,4 +3065,26 @@ static inline bool dir_relax(struct inode *inode)

extern bool path_noexec(const struct path *path);

+static inline void base_acl_get(struct base_acl *acl)
+{
+ if (acl)
+ atomic_inc(&acl->ba_refcount);
+}
+
+static inline void base_acl_put(struct base_acl *acl)
+{
+ if (acl && atomic_dec_and_test(&acl->ba_refcount))
+ kfree_rcu(acl, ba_rcu);
+}
+
+static inline void base_acl_init(struct base_acl *acl)
+{
+ atomic_set(&acl->ba_refcount, 1);
+}
+
+static inline int base_acl_refcount(struct base_acl *acl)
+{
+ return atomic_read(&acl->ba_refcount);
+}
+
#endif /* _LINUX_FS_H */
diff --git a/include/linux/posix_acl.h b/include/linux/posix_acl.h
index 5b5a80c..cef5428 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; /* must be first, see posix_acl_release() */
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);
+ base_acl_get(&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);
+ base_acl_put(&acl->a_base);
}


diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index 1d9f5f7..7628fad 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -31,7 +31,7 @@ struct richace {
};

struct richacl {
- atomic_t a_refcount;
+ struct base_acl a_base; /* must be first, see richacl_put() */
unsigned int a_owner_mask;
unsigned int a_group_mask;
unsigned int a_other_mask;
@@ -56,8 +56,7 @@ struct richacl {
static inline struct richacl *
richacl_get(struct richacl *acl)
{
- if (acl)
- atomic_inc(&acl->a_refcount);
+ base_acl_get(&acl->a_base);
return acl;
}

@@ -67,8 +66,8 @@ 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);
+ base_acl_put(&acl->a_base);
}

/**
--
2.5.0

2015-11-05 11:39:30

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 12/22] vfs: Add get_richacl and set_richacl inode operations

These operations are similar to the get_acl and set_acl operations for
POSIX ACLs. The distinction between access and default ACLs doesn't exist
for richacls.

Signed-off-by: Andreas Gruenbacher <[email protected]>
---
include/linux/fs.h | 2 ++
1 file changed, 2 insertions(+)

diff --git a/include/linux/fs.h b/include/linux/fs.h
index 2fd840e..3c36c72 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1662,6 +1662,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 *);
@@ -1691,6 +1692,7 @@ struct inode_operations {
umode_t create_mode, int *opened);
int (*tmpfile) (struct inode *, struct dentry *, umode_t);
int (*set_acl)(struct inode *, struct posix_acl *, int);
+ int (*set_richacl)(struct inode *, struct richacl *);

/* WARNING: probably going away soon, do not use! */
} ____cacheline_aligned;
--
2.5.0

2015-11-05 11:39:31

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 13/22] vfs: Cache richacl in struct inode

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 <[email protected]>
---
fs/inode.c | 11 +++++--
fs/posix_acl.c | 2 +-
fs/richacl_inode.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/fs.h | 5 +++-
include/linux/richacl.h | 6 ++++
5 files changed, 96 insertions(+), 5 deletions(-)

diff --git a/fs/inode.c b/fs/inode.c
index 5c46ae5..5e8710c 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -174,8 +174,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
@@ -231,11 +234,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)
base_acl_put(inode->i_acl);
+# if defined(CONFIG_FS_POSIX_ACL)
if (inode->i_default_acl && inode->i_default_acl != ACL_NOT_CACHED)
base_acl_put(inode->i_default_acl);
+# endif
#endif
this_cpu_dec(nr_inodes);
}
diff --git a/fs/posix_acl.c b/fs/posix_acl.c
index 4573315..b0eb1dc 100644
--- a/fs/posix_acl.c
+++ b/fs/posix_acl.c
@@ -37,7 +37,7 @@ struct posix_acl *get_cached_acl(struct inode *inode, int type)
{
struct base_acl **p = acl_by_type(inode, type);
struct base_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_inode.c b/fs/richacl_inode.c
index 99b3c93..52c1595 100644
--- a/fs/richacl_inode.c
+++ b/fs/richacl_inode.c
@@ -20,6 +20,83 @@
#include <linux/slab.h>
#include <linux/richacl.h>

+struct richacl *get_cached_richacl(struct inode *inode)
+{
+ struct base_acl *acl;
+
+ acl = ACCESS_ONCE(inode->i_acl);
+ if (acl && IS_RICHACL(inode)) {
+ spin_lock(&inode->i_lock);
+ acl = inode->i_acl;
+ if (acl != ACL_NOT_CACHED)
+ base_acl_get(acl);
+ spin_unlock(&inode->i_lock);
+ }
+ return container_of(acl, struct richacl, a_base);
+}
+EXPORT_SYMBOL_GPL(get_cached_richacl);
+
+struct richacl *get_cached_richacl_rcu(struct inode *inode)
+{
+ struct base_acl *acl = rcu_dereference(inode->i_acl);
+
+ return container_of(acl, struct richacl, a_base);
+}
+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)
+ base_acl_put(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)
+ base_acl_put(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 3c36c72..05fb943 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;
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index 7628fad..7bf912b6 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -70,6 +70,12 @@ richacl_put(struct richacl *acl)
base_acl_put(&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.5.0

2015-11-05 11:39:32

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 14/22] richacl: Update the file masks in chmod()

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 <[email protected]>
Reviewed-by: J. Bruce Fields <[email protected]>
---
fs/richacl_base.c | 42 ++++++++++++++++++++++++++++++++++++++++++
fs/richacl_inode.c | 30 ++++++++++++++++++++++++++++++
include/linux/richacl.h | 2 ++
3 files changed, 74 insertions(+)

diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index 5826842..e4dd779 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -340,3 +340,45 @@ 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
+ * @acl: access control list
+ * @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, umode_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_MASKED) &&
+ (acl->a_flags & RICHACL_WRITE_THROUGH))
+ 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/fs/richacl_inode.c b/fs/richacl_inode.c
index 52c1595..e329826 100644
--- a/fs/richacl_inode.c
+++ b/fs/richacl_inode.c
@@ -224,3 +224,33 @@ out:
return denied ? -EACCES : 0;
}
EXPORT_SYMBOL_GPL(richacl_permission);
+
+/**
+ * richacl_chmod - filesystem chmod helper
+ * @inode: inode whose file permission bits to change
+ * @mode: new file permission bits including the file type
+ *
+ * Helper for filesystems to use to perform a chmod on the richacl of an inode.
+ */
+int
+richacl_chmod(struct inode *inode, umode_t mode)
+{
+ struct richacl *acl;
+ int retval;
+
+ if (S_ISLNK(mode))
+ return -EOPNOTSUPP;
+ if (!inode->i_op->set_richacl)
+ return -EOPNOTSUPP;
+ acl = get_richacl(inode);
+ if (IS_ERR_OR_NULL(acl))
+ return PTR_ERR(acl);
+ acl = __richacl_chmod(acl, mode);
+ if (IS_ERR(acl))
+ return PTR_ERR(acl);
+ retval = inode->i_op->set_richacl(inode, acl);
+ richacl_put(acl);
+
+ return retval;
+}
+EXPORT_SYMBOL(richacl_chmod);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index 7bf912b6..a2d5600 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -184,8 +184,10 @@ extern int richacl_masks_to_mode(const struct richacl *);
extern unsigned int richacl_mode_to_mask(umode_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 *, umode_t);

/* richacl_inode.c */
extern int richacl_permission(struct inode *, const struct richacl *, int);
+extern int richacl_chmod(struct inode *, umode_t);

#endif /* __RICHACL_H */
--
2.5.0

2015-11-05 11:39:33

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 15/22] richacl: Check if an acl is equivalent to a file mode

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 <[email protected]>
Reviewed-by: J. Bruce Fields <[email protected]>
---
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 e4dd779..74e5cb5 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -382,3 +382,107 @@ __richacl_chmod(struct richacl *acl, umode_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, umode_t *mode_p)
+{
+ umode_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 a2d5600..b7128bf 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -185,6 +185,7 @@ extern unsigned int richacl_mode_to_mask(umode_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 *, umode_t);
+extern int richacl_equiv_mode(const struct richacl *, umode_t *);

/* richacl_inode.c */
extern int richacl_permission(struct inode *, const struct richacl *, int);
--
2.5.0

2015-11-05 11:39:34

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 16/22] richacl: Create-time inheritance

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 <[email protected]>
Reviewed-by: J. Bruce Fields <[email protected]>
---
fs/richacl_base.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++
fs/richacl_inode.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/richacl.h | 2 ++
3 files changed, 140 insertions(+)

diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index 74e5cb5..3753216 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -486,3 +486,71 @@ richacl_equiv_mode(const struct richacl *acl, umode_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_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 e329826..ec3d2c8 100644
--- a/fs/richacl_inode.c
+++ b/fs/richacl_inode.c
@@ -254,3 +254,73 @@ richacl_chmod(struct inode *inode, umode_t mode)
return retval;
}
EXPORT_SYMBOL(richacl_chmod);
+
+/*
+ * richacl_inherit_inode - compute inherited acl and file mode
+ * @dir_acl: acl of the containing directory
+ * @mode_p: mode of the new inode
+ *
+ * The file permission bits in @mode_p must be set to the create mode by the
+ * caller.
+ *
+ * If there is an inheritable acl, the maximum permissions that the acl grants
+ * are computed and the file masks of the new acl are set accordingly.
+ */
+static struct richacl *
+richacl_inherit_inode(const struct richacl *dir_acl, umode_t *mode_p)
+{
+ struct richacl *acl;
+ umode_t mode = *mode_p;
+
+ acl = richacl_inherit(dir_acl, S_ISDIR(mode));
+ if (acl) {
+ if (richacl_equiv_mode(acl, &mode) == 0) {
+ *mode_p &= mode;
+ 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(mode >> 6);
+ acl->a_group_mask &=
+ richacl_mode_to_mask(mode >> 3);
+ acl->a_other_mask &=
+ richacl_mode_to_mask(mode);
+ }
+ } else
+ *mode_p &= ~current_umask();
+
+ return acl;
+}
+
+/**
+ * richacl_create - filesystem create helper
+ * @mode_p: mode of the new inode
+ * @dir: containing directory
+ *
+ * Compute the inherited acl for a new inode. If there is no acl to inherit,
+ * apply the umask. Use when creating a new inode on a richacl enabled file
+ * system.
+ */
+struct richacl *richacl_create(umode_t *mode_p, struct inode *dir)
+{
+ struct richacl *dir_acl, *acl = NULL;
+
+ if (S_ISLNK(*mode_p))
+ return NULL;
+ dir_acl = get_richacl(dir);
+ if (dir_acl) {
+ if (IS_ERR(dir_acl))
+ return dir_acl;
+ acl = richacl_inherit_inode(dir_acl, mode_p);
+ richacl_put(dir_acl);
+ } else
+ *mode_p &= ~current_umask();
+ return acl;
+}
+EXPORT_SYMBOL_GPL(richacl_create);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index b7128bf..c8fae91a 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -186,9 +186,11 @@ extern unsigned int richacl_want_to_mask(unsigned int);
extern void richacl_compute_max_masks(struct richacl *);
extern struct richacl *__richacl_chmod(struct richacl *, umode_t);
extern int richacl_equiv_mode(const struct richacl *, umode_t *);
+extern struct richacl *richacl_inherit(const struct richacl *, int);

/* richacl_inode.c */
extern int richacl_permission(struct inode *, const struct richacl *, int);
extern int richacl_chmod(struct inode *, umode_t);
+extern struct richacl *richacl_create(umode_t *, struct inode *);

#endif /* __RICHACL_H */
--
2.5.0

2015-11-05 11:39:37

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 19/22] richacl: Add richacl xattr handler

Add richacl xattr handler implementing the xattr operations based on the
get_richacl and set_richacl inode operations.

Signed-off-by: Andreas Gruenbacher <[email protected]>
---
fs/richacl_xattr.c | 78 +++++++++++++++++++++++++++++++++++++++++++
include/linux/richacl_xattr.h | 2 ++
2 files changed, 80 insertions(+)

diff --git a/fs/richacl_xattr.c b/fs/richacl_xattr.c
index 38473b6..767c1f7 100644
--- a/fs/richacl_xattr.c
+++ b/fs/richacl_xattr.c
@@ -18,7 +18,9 @@
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/module.h>
+#include <linux/xattr.h>
#include <linux/richacl_xattr.h>
+#include <uapi/linux/xattr.h>

MODULE_LICENSE("GPL");

@@ -161,6 +163,82 @@ richacl_to_xattr(struct user_namespace *user_ns,
}
EXPORT_SYMBOL_GPL(richacl_to_xattr);

+static size_t
+richacl_xattr_list(struct dentry *dentry, char *list, size_t list_len,
+ const char *name, size_t name_len, int handler_flags)
+{
+ const size_t size = sizeof(XATTR_NAME_RICHACL);
+
+ if (!IS_RICHACL(d_backing_inode(dentry)))
+ return 0;
+ if (list && size <= list_len)
+ memcpy(list, XATTR_NAME_RICHACL, size);
+ return size;
+}
+
+static int
+richacl_xattr_get(struct dentry *dentry, const char *name, void *buffer,
+ size_t buffer_size, int handler_flags)
+{
+ struct inode *inode = d_backing_inode(dentry);
+ struct richacl *acl;
+ int error;
+
+ if (strcmp(name, "") != 0)
+ return -EINVAL;
+ if (!IS_RICHACL(inode))
+ return EOPNOTSUPP;
+ if (S_ISLNK(inode->i_mode))
+ return -EOPNOTSUPP;
+ acl = get_richacl(inode);
+ 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
+richacl_xattr_set(struct dentry *dentry, const char *name,
+ const void *value, size_t size, int flags, int handler_flags)
+{
+ struct inode *inode = d_backing_inode(dentry);
+ struct richacl *acl = NULL;
+ int ret;
+
+ if (strcmp(name, "") != 0)
+ return -EINVAL;
+ if (!IS_RICHACL(inode))
+ return -EOPNOTSUPP;
+ if (!inode->i_op->set_richacl)
+ return -EOPNOTSUPP;
+
+ 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);
+ }
+
+ ret = inode->i_op->set_richacl(inode, acl);
+ richacl_put(acl);
+ return ret;
+}
+
+struct xattr_handler richacl_xattr_handler = {
+ .prefix = XATTR_NAME_RICHACL,
+ .list = richacl_xattr_list,
+ .get = richacl_xattr_get,
+ .set = richacl_xattr_set,
+};
+EXPORT_SYMBOL(richacl_xattr_handler);
+
/*
* Fix up the uids and gids in richacl extended attributes in place.
*/
diff --git a/include/linux/richacl_xattr.h b/include/linux/richacl_xattr.h
index 7fc5ca8..45ec27a 100644
--- a/include/linux/richacl_xattr.h
+++ b/include/linux/richacl_xattr.h
@@ -39,4 +39,6 @@ static inline void richacl_fix_xattr_to_user(void *value, size_t size)
}
#endif

+extern struct xattr_handler richacl_xattr_handler;
+
#endif /* __RICHACL_XATTR_H */
--
2.5.0

2015-11-05 11:39:36

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 18/22] richacl: xattr mapping functions

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

Signed-off-by: Andreas Gruenbacher <[email protected]>
---
fs/Makefile | 2 +-
fs/richacl_xattr.c | 220 +++++++++++++++++++++++++++++++++++++
fs/xattr.c | 34 +++++-
include/linux/richacl_xattr.h | 42 +++++++
include/uapi/linux/Kbuild | 1 +
include/uapi/linux/richacl_xattr.h | 44 ++++++++
include/uapi/linux/xattr.h | 2 +
7 files changed, 338 insertions(+), 7 deletions(-)
create mode 100644 fs/richacl_xattr.c
create mode 100644 include/linux/richacl_xattr.h
create mode 100644 include/uapi/linux/richacl_xattr.h

diff --git a/fs/Makefile b/fs/Makefile
index ec665fd..35e640d 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -49,7 +49,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..38473b6
--- /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 <[email protected]>
+ *
+ * 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..7fc5ca8
--- /dev/null
+++ b/include/linux/richacl_xattr.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2006, 2010 Novell, Inc.
+ * Copyright (C) 2015 Red Hat, Inc.
+ * Written by Andreas Gruenbacher <[email protected]>
+ *
+ * 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 <uapi/linux/richacl_xattr.h>
+#include <linux/richacl.h>
+
+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/Kbuild b/include/uapi/linux/Kbuild
index 8c82010..18ad070 100644
--- a/include/uapi/linux/Kbuild
+++ b/include/uapi/linux/Kbuild
@@ -349,6 +349,7 @@ 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_xattr.h b/include/uapi/linux/richacl_xattr.h
new file mode 100644
index 0000000..5178ca6
--- /dev/null
+++ b/include/uapi/linux/richacl_xattr.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2006, 2010 Novell, Inc.
+ * Copyright (C) 2015 Red Hat, Inc.
+ * Written by Andreas Gruenbacher <[email protected]>
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser General Public License for more details.
+ */
+
+#ifndef __UAPI_RICHACL_XATTR_H
+#define __UAPI_RICHACL_XATTR_H
+
+#include <linux/types.h>
+#include <linux/limits.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))
+
+#endif /* __UAPI_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.5.0

2015-11-05 11:39:38

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 20/22] vfs: Add richacl permission checking

Hook the richacl permission checking function into the vfs.

Signed-off-by: Andreas Gruenbacher <[email protected]>
---
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 2eab19e..3822b5e 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 b0eb1dc..6dbddb6 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.5.0

2015-11-05 11:39:40

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 22/22] ext4: Add richacl feature flag

From: "Aneesh Kumar K.V" <[email protected]>

This feature flag selects richacl instead of POSIX ACL support on the
filesystem. When this feature is off, the "acl" and "noacl" mount options
control whether POSIX ACLs are enabled. When it is on, richacls are
automatically enabled and using the "noacl" mount option leads to an error.

Signed-off-by: Aneesh Kumar K.V <[email protected]>
Signed-off-by: Andreas Gruenbacher <[email protected]>
---
fs/ext4/ext4.h | 6 ++++--
fs/ext4/super.c | 49 ++++++++++++++++++++++++++++++++++++++++---------
2 files changed, 44 insertions(+), 11 deletions(-)

diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index fd1f28b..b97a3b1 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 a63c7b0..7457ea8 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1270,6 +1270,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 [email protected] if you think we should keep it.\n";
@@ -1416,9 +1438,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},
@@ -1466,6 +1488,13 @@ static int handle_mount_opt(struct super_block *sb, char *opt, int token,
#endif
switch (token) {
case Opt_noacl:
+#ifdef CONFIG_EXT4_FS_RICHACL
+ if (EXT4_HAS_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_RICHACL)) {
+ ext4_msg(sb, KERN_ERR, "Mount option \"%s\" incompatible "
+ "with richacl feature", opt);
+ return -1;
+ }
+#endif
case Opt_nouser_xattr:
ext4_msg(sb, KERN_WARNING, deprecated_msg, opt, "3.5");
break;
@@ -3576,8 +3605,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))
@@ -3660,8 +3689,9 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
sb->s_iflags |= SB_I_CGROUPWB;
}

- 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) ||
@@ -4981,8 +5011,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.5.0

2015-11-05 11:39:39

by Andreas Gruenbacher

[permalink] [raw]
Subject: [PATCH v14 21/22] ext4: Add richacl support

From: "Aneesh Kumar K.V" <[email protected]>

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 <[email protected]>
Signed-off-by: Andreas Gruenbacher <[email protected]>
Reviewed-by: Andreas Dilger <[email protected]>
---
fs/ext4/Kconfig | 11 +++++
fs/ext4/Makefile | 1 +
fs/ext4/file.c | 3 ++
fs/ext4/ialloc.c | 11 ++++-
fs/ext4/inode.c | 12 ++++-
fs/ext4/namei.c | 5 ++
fs/ext4/richacl.c | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
fs/ext4/richacl.h | 40 +++++++++++++++
fs/ext4/xattr.c | 7 +++
9 files changed, 229 insertions(+), 3 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 b46e9fc..65c5230 100644
--- a/fs/ext4/Kconfig
+++ b/fs/ext4/Kconfig
@@ -22,6 +22,17 @@ config EXT3_FS_POSIX_ACL
This config option is here only for backward compatibility. ext3
filesystem is now handled by the ext4 driver.

+config EXT4_FS_RICHACL
+ bool "Ext4 Rich Access Control Lists (EXPERIMENTAL)"
+ depends on EXT4_FS
+ select FS_RICHACL
+ help
+ Richacls are an implementation of NFSv4 ACLs, extended by file masks
+ to cleanly integrate into the POSIX file permission model. To learn
+ more about them, see http://www.bestbits.at/richacl/.
+
+ If you don't know what Richacls are, say N.
+
config EXT3_FS_SECURITY
bool "Ext3 Security Labels"
depends on EXT3_FS
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/file.c b/fs/ext4/file.c
index 113837e..a03b4a5 100644
--- a/fs/ext4/file.c
+++ b/fs/ext4/file.c
@@ -30,6 +30,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
@@ -719,6 +720,8 @@ const struct inode_operations ext4_file_inode_operations = {
.removexattr = generic_removexattr,
.get_acl = ext4_get_acl,
.set_acl = ext4_set_acl,
+ .get_richacl = ext4_get_richacl,
+ .set_richacl = ext4_set_richacl,
.fiemap = ext4_fiemap,
};

diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c
index 619bfc1..9657b3a 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>

@@ -697,6 +698,14 @@ out:
return ret;
}

+static inline int
+ext4_new_acl(handle_t *handle, struct inode *inode, struct inode *dir)
+{
+ if (IS_RICHACL(dir))
+ return ext4_init_richacl(handle, inode, dir);
+ return ext4_init_acl(handle, inode, dir);
+}
+
/*
* There are two policies for allocating an inode. If the new inode is
* a directory, then a forward search is made for a block group with both
@@ -1052,7 +1061,7 @@ got:
if (err)
goto fail_drop;

- err = ext4_init_acl(handle, inode, dir);
+ err = ext4_new_acl(handle, inode, dir);
if (err)
goto fail_free_drop;

diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 612fbcf..647f3c3 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -42,6 +42,7 @@
#include "xattr.h"
#include "acl.h"
#include "truncate.h"
+#include "richacl.h"

#include <trace/events/ext4.h>

@@ -4638,6 +4639,14 @@ static void ext4_wait_for_tail_page_commit(struct inode *inode)
}
}

+static inline int
+ext4_acl_chmod(struct inode *inode, umode_t mode)
+{
+ if (IS_RICHACL(inode))
+ return richacl_chmod(inode, inode->i_mode);
+ return posix_acl_chmod(inode, inode->i_mode);
+}
+
/*
* ext4_setattr()
*
@@ -4806,8 +4815,7 @@ int ext4_setattr(struct dentry *dentry, struct iattr *attr)
ext4_orphan_del(NULL, inode);

if (!rc && (ia_valid & ATTR_MODE))
- rc = posix_acl_chmod(inode, inode->i_mode);
-
+ rc = ext4_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 9f61e76..9b6e8b9 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>
/*
@@ -3854,6 +3855,8 @@ const struct inode_operations ext4_dir_inode_operations = {
.removexattr = generic_removexattr,
.get_acl = ext4_get_acl,
.set_acl = ext4_set_acl,
+ .get_richacl = ext4_get_richacl,
+ .set_richacl = ext4_set_richacl,
.fiemap = ext4_fiemap,
};

@@ -3865,4 +3868,6 @@ const struct inode_operations ext4_special_inode_operations = {
.removexattr = generic_removexattr,
.get_acl = ext4_get_acl,
.set_acl = ext4_set_acl,
+ .get_richacl = ext4_get_richacl,
+ .set_richacl = ext4_set_richacl,
};
diff --git a/fs/ext4/richacl.c b/fs/ext4/richacl.c
new file mode 100644
index 0000000..d581be4
--- /dev/null
+++ b/fs/ext4/richacl.c
@@ -0,0 +1,142 @@
+/*
+ * Copyright IBM Corporation, 2010
+ * Copyright (C) 2015 Red Hat, Inc.
+ * Author: Aneesh Kumar K.V <[email protected]>,
+ * Andreas Gruenbacher <[email protected]>
+ *
+ * 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 = NULL;
+ int retval;
+
+ 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 = ERR_PTR(retval);
+ }
+ kfree(value);
+
+ if (!IS_ERR(acl))
+ set_cached_richacl(inode, acl);
+
+ return acl;
+}
+
+static int
+__ext4_remove_richacl(handle_t *handle, struct inode *inode)
+{
+ const int name_index = EXT4_XATTR_INDEX_RICHACL;
+ int retval;
+
+ retval = ext4_xattr_set_handle(handle, inode, name_index, "",
+ NULL, 0, 0);
+ if (!retval)
+ set_cached_richacl(inode, NULL);
+ return retval;
+}
+
+static int
+__ext4_set_richacl(handle_t *handle, struct inode *inode, struct richacl *acl)
+{
+ const int name_index = EXT4_XATTR_INDEX_RICHACL;
+ umode_t mode = inode->i_mode;
+ int retval, size;
+ void *value;
+
+ if (richacl_equiv_mode(acl, &mode) == 0) {
+ inode->i_ctime = ext4_current_time(inode);
+ inode->i_mode = mode;
+ ext4_mark_inode_dirty(handle, inode);
+ return __ext4_remove_richacl(handle, inode);
+ }
+
+ mode &= ~S_IRWXUGO;
+ mode |= richacl_masks_to_mode(acl);
+
+ size = richacl_xattr_size(acl);
+ value = kmalloc(size, GFP_NOFS);
+ if (!value)
+ return -ENOMEM;
+ richacl_to_xattr(&init_user_ns, acl, value, size);
+ inode->i_mode = mode;
+ retval = ext4_xattr_set_handle(handle, inode, name_index, "",
+ value, size, 0);
+ kfree(value);
+ if (retval)
+ return retval;
+
+ set_cached_richacl(inode, acl);
+
+ return 0;
+}
+
+int
+ext4_set_richacl(struct inode *inode, struct richacl *acl)
+{
+ handle_t *handle;
+ int retval, retries = 0;
+
+retry:
+ handle = ext4_journal_start(inode, EXT4_HT_XATTR,
+ ext4_jbd2_credits_xattr(inode));
+ if (IS_ERR(handle))
+ return PTR_ERR(handle);
+
+ if (acl)
+ retval = __ext4_set_richacl(handle, inode, acl);
+ else
+ retval = __ext4_remove_richacl(handle, inode);
+
+ ext4_journal_stop(handle);
+ if (retval == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries))
+ goto retry;
+ return retval;
+}
+
+int
+ext4_init_richacl(handle_t *handle, struct inode *inode, struct inode *dir)
+{
+ struct richacl *acl = richacl_create(&inode->i_mode, 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;
+}
diff --git a/fs/ext4/richacl.h b/fs/ext4/richacl.h
new file mode 100644
index 0000000..6fe9a92
--- /dev/null
+++ b/fs/ext4/richacl.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright IBM Corporation, 2010
+ * Copyright (C) 2015 Red Hat, Inc.
+ * Author Aneesh Kumar K.V <[email protected]>
+ *
+ * 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
+
+extern struct richacl *ext4_get_richacl(struct inode *);
+extern int ext4_set_richacl(struct inode *, struct richacl *);
+
+extern int ext4_init_richacl(handle_t *, struct inode *, struct inode *);
+
+#else /* CONFIG_EXT4_FS_RICHACL */
+
+#define ext4_get_richacl NULL
+#define ext4_set_richacl NULL
+
+static inline int
+ext4_init_richacl(handle_t *handle, struct inode *inode, struct inode *dir)
+{
+ return 0;
+}
+
+#endif /* CONFIG_EXT4_FS_RICHACL */
+#endif /* __FS_EXT4_RICHACL_H */
diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c
index 16e28c0..4d79adb 100644
--- a/fs/ext4/xattr.c
+++ b/fs/ext4/xattr.c
@@ -55,6 +55,7 @@
#include <linux/slab.h>
#include <linux/mbcache.h>
#include <linux/quotaops.h>
+#include <linux/richacl_xattr.h>
#include "ext4_jbd2.h"
#include "ext4.h"
#include "xattr.h"
@@ -99,6 +100,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] = &richacl_xattr_handler,
+#endif
};

const struct xattr_handler *ext4_xattr_handlers[] = {
@@ -111,6 +115,9 @@ const struct xattr_handler *ext4_xattr_handlers[] = {
#ifdef CONFIG_EXT4_FS_SECURITY
&ext4_xattr_security_handler,
#endif
+#ifdef CONFIG_EXT4_FS_RICHACL
+ &richacl_xattr_handler,
+#endif
NULL
};

--
2.5.0

2015-11-06 20:40:19

by Andreas Dilger

[permalink] [raw]
Subject: Re: [PATCH v14 01/22] vfs: Add IS_ACL() and IS_RICHACL() tests

On Nov 5, 2015, at 4:39 AM, Andreas Gruenbacher <[email protected]> wrote:
>
> 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 <[email protected]>
> Reviewed-by: J. Bruce Fields <[email protected]>

Looks good to me.

Reviewed-by: Andreas Dilger <[email protected]>

> ---
> 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 da3f32f..bff2879 100644
> --- a/fs/Kconfig
> +++ b/fs/Kconfig
> @@ -56,6 +56,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 33e9495..224ecf1 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -2798,7 +2798,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);
> @@ -2982,7 +2982,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
> @@ -3553,7 +3553,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)
> @@ -3622,7 +3622,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 72d8a84..4efa435 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -1781,6 +1781,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)
> @@ -1794,6 +1800,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.5.0
>


Cheers, Andreas






Attachments:
signature.asc (833.00 B)
Message signed with OpenPGP using GPGMail
(No filename) (121.00 B)
Download all attachments

2015-11-06 20:58:42

by Andreas Dilger

[permalink] [raw]
Subject: Re: [PATCH v14 02/22] vfs: Add MAY_CREATE_FILE and MAY_CREATE_DIR permission flags

On Nov 5, 2015, at 4:39 AM, Andreas Gruenbacher <[email protected]> wrote:
>
> 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().

I thought you proposed adding an enum for these parameters, and possibly
making them a single parameter flag, to make the code in the caller more
readable. Flags like below for example, though I'm not stuck on "MAY_IS"
as a prefix, just my first thought:

enum may_flags {
MAY_IS_FILE = 0x0, /* Essentially !MAY_IS_DIR */
MAY_IS_DIR = 0x1, /* Operation only allowed on directory */
MAY_IS_REPLACE = 0x2, /* Operation only
};

would make it immediately clear what is being passed to the function.
Some examples inline below. If Al hates it, fine, but having functions
with multiple "true, false" or "false, true" arguments is ugly and error
prone, IMHO.

> Signed-off-by: Andreas Gruenbacher <[email protected]>
> Reviewed-by: J. Bruce Fields <[email protected]>
> ---
> 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 224ecf1..0259392 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)
> {
> @@ -2549,10 +2551,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;
> @@ -2561,7 +2564,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))
> @@ -2592,14 +2597,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);
> }
>
> /*
> @@ -2649,7 +2656,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);

int error = may_create(dir, dentry, MAY_IS_FILE);

> if (error)
> return error;
>
> @@ -3494,7 +3501,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;
> @@ -3586,7 +3593,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);

int error = may_create(dir, dentry, MAY_IS_DIR);

> unsigned max_links = dir->i_sb->s_max_links;
>
> if (error)
> @@ -3667,7 +3674,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);


The cases where there are multiple parameters is where it makes the most
improvement, since the code is more readable:

int error = may_delete(dir, dentry, MAY_IS_DIR);

> if (error)
> return error;
> @@ -3789,7 +3796,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);

int error = may_delete(dir, dentry, MAY_IS_FILE);

Cheers, Andreas

> if (error)
> return error;
> @@ -3923,7 +3930,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;
> @@ -4006,7 +4013,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;
>
> @@ -4194,19 +4201,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;
> @@ -4469,7 +4476,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 4efa435..d6e2330 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.5.0
>


Cheers, Andreas






Attachments:
signature.asc (833.00 B)
Message signed with OpenPGP using GPGMail
(No filename) (121.00 B)
Download all attachments

2015-11-06 21:04:07

by Andreas Dilger

[permalink] [raw]
Subject: Re: [PATCH v14 04/22] vfs: Make the inode passed to inode_change_ok non-const


> On Nov 5, 2015, at 4:39 AM, Andreas Gruenbacher <[email protected]> wrote:
>
> 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.

Seems unfortunate that those functions cannot themselves be changed to
take a const struct inode, but it doesn't look possible.

Reviewed-by: Andreas Dilger <[email protected]>

> Signed-off-by: Andreas Gruenbacher <[email protected]>
> Reviewed-by: J. Bruce Fields <[email protected]>
> ---
> 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 402acd7..aab32c8 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -2871,7 +2871,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.5.0
>


Cheers, Andreas






Attachments:
signature.asc (833.00 B)
Message signed with OpenPGP using GPGMail

2015-11-06 21:26:45

by Andreas Dilger

[permalink] [raw]
Subject: Re: [PATCH v14 03/22] vfs: Add MAY_DELETE_SELF and MAY_DELETE_CHILD permission flags

On Nov 5, 2015, at 4:39 AM, Andreas Gruenbacher <[email protected]> wrote:
>
> 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 <[email protected]>
> Reviewed-by: J. Bruce Fields <[email protected]>
> ---
> 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 0259392..2eab19e 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)
> {
> @@ -2555,7 +2555,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;
> @@ -2565,15 +2565,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;

This looks like a serious bug, as it is overriding other errors returned
from inode_permission() such as -EROFS from sb_permission() or even from
generic_permission->acl_permission_check(). Clearing the error returned
by an earlier check doesn't seem safe, only new errors should be added.

The call to inode_permission(inode) is also duplicating the sb_permission()
check from inode_permission(dir), so at most should be __inode_permission().

It looks like this would be correct if you check MAY_DELETE_SELF together
with check_sticky():

if (!error && check_sticky(dir, inode) &&
!__inode_permission(inode, MAY_DELETE_SELF))
error = -EPERM;

I ommitted the IS_RICHACL() check here, since that should probably be in
__inode_permission() when MAY_DELETE_SELF is passed?

Cheers, Andreas

> 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 d6e2330..402acd7 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.5.0
>


Cheers, Andreas






Attachments:
signature.asc (833.00 B)
Message signed with OpenPGP using GPGMail

2015-11-06 21:26:45

by Andreas Dilger

[permalink] [raw]
Subject: Re: [PATCH v14 03/22] vfs: Add MAY_DELETE_SELF and MAY_DELETE_CHILD permission flags

On Nov 5, 2015, at 4:39 AM, Andreas Gruenbacher <[email protected]> wrote:
>
> 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 <[email protected]>
> Reviewed-by: J. Bruce Fields <[email protected]>
> ---
> 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 0259392..2eab19e 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)
> {
> @@ -2555,7 +2555,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;
> @@ -2565,15 +2565,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;

This looks like a serious bug, as it is overriding other errors returned
from inode_permission() such as -EROFS from sb_permission() or even from
generic_permission->acl_permission_check(). Clearing the error returned
by an earlier check doesn't seem safe, only new errors should be added.

The call to inode_permission(inode) is also duplicating the sb_permission()
check from inode_permission(dir), so at most should be __inode_permission().

It looks like this would be correct if you check MAY_DELETE_SELF together
with check_sticky():

if (!error && check_sticky(dir, inode) &&
!__inode_permission(inode, MAY_DELETE_SELF))
error = -EPERM;

I ommitted the IS_RICHACL() check here, since that should probably be in
__inode_permission() when MAY_DELETE_SELF is passed?

Cheers, Andreas

> 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 d6e2330..402acd7 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.5.0
>


Cheers, Andreas






Attachments:
signature.asc (833.00 B)
Message signed with OpenPGP using GPGMail
(No filename) (121.00 B)
Download all attachments

2015-11-07 17:44:28

by Andreas Gruenbacher

[permalink] [raw]
Subject: Re: [PATCH v14 02/22] vfs: Add MAY_CREATE_FILE and MAY_CREATE_DIR permission flags

On Fri, Nov 6, 2015 at 9:58 PM, Andreas Dilger <[email protected]> wrote:
> I thought you proposed adding an enum for these parameters, and possibly
> making them a single parameter flag, to make the code in the caller more
> readable.

No, the result would just be different but not any better; vfs_rename would
become messy. I've played with this patch some more, and I find the approach
below which splits may_delete into may_delete and may_replace much better.
What do you think?

Thanks,
Andreas

---------- 8< ----------

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.

Add may_replace() to allow checking for delete and create access when
replacing an existing file in vfs_rename().

Signed-off-by: Andreas Gruenbacher <[email protected]>
Reviewed-by: J. Bruce Fields <[email protected]>
---
fs/namei.c | 49 +++++++++++++++++++++++++++++++++----------------
include/linux/fs.h | 2 ++
2 files changed, 35 insertions(+), 16 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index 224ecf1..4e5dc2f 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)
{
@@ -2549,7 +2551,8 @@ 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_replace(struct inode *dir, struct dentry *victim,
+ bool isdir, int mask)
{
struct inode *inode = d_backing_inode(victim);
int error;
@@ -2561,7 +2564,7 @@ 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);
+ error = inode_permission(dir, mask | MAY_WRITE | MAY_EXEC);
if (error)
return error;
if (IS_APPEND(dir))
@@ -2584,6 +2587,18 @@ static int may_delete(struct inode *dir, struct dentry *victim, bool isdir)
return 0;
}

+static int may_delete(struct inode *dir, struct dentry *victim, bool isdir)
+{
+ return may_delete_replace(dir, victim, isdir, 0);
+}
+
+static int may_replace(struct inode *dir, struct dentry *victim, bool isdir)
+{
+ int mask = isdir ? MAY_CREATE_DIR : MAY_CREATE_FILE;
+
+ return may_delete_replace(dir, victim, isdir, mask);
+}
+
/* Check whether we can create an object with dentry child in directory
* dir.
* 1. We can't do it if child already exists (open has special treatment for
@@ -2592,14 +2607,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);
}

/*
@@ -2649,7 +2666,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;

@@ -3494,7 +3511,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;
@@ -3586,7 +3603,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)
@@ -3667,7 +3684,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);

if (error)
return error;
@@ -3789,7 +3806,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);

if (error)
return error;
@@ -3923,7 +3940,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;
@@ -4006,7 +4023,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;

@@ -4199,14 +4216,14 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
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_replace(new_dir, new_dentry, is_dir);
else
- error = may_delete(new_dir, new_dentry, new_is_dir);
+ error = may_replace(new_dir, new_dentry, new_is_dir);
}
if (error)
return error;
@@ -4469,7 +4486,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 4efa435..d6e2330 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.5.0

2015-11-07 20:47:28

by Andreas Gruenbacher

[permalink] [raw]
Subject: Re: [PATCH v14 03/22] vfs: Add MAY_DELETE_SELF and MAY_DELETE_CHILD permission flags

On Fri, Nov 6, 2015 at 10:26 PM, Andreas Dilger <[email protected]> wrote:
> On Nov 5, 2015, at 4:39 AM, Andreas Gruenbacher <[email protected]> wrote:
>>
>> 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 <[email protected]>
>> Reviewed-by: J. Bruce Fields <[email protected]>
>> ---
>> 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 0259392..2eab19e 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)
>> {
>> @@ -2555,7 +2555,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;
>> @@ -2565,15 +2565,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;
>
> This looks like a serious bug, as it is overriding other errors returned
> from inode_permission() such as -EROFS from sb_permission() or even from
> generic_permission->acl_permission_check(). Clearing the error returned
> by an earlier check doesn't seem safe, only new errors should be added.

See further below ...

> The call to inode_permission(inode) is also duplicating the sb_permission()
> check from inode_permission(dir), so at most should be __inode_permission().

This could be optimized, but that would make the code harder to read.
I don't think it really matters: this only affects file with the
MAY_DELETE_SELF permission, and sb_permissions is really cheap anyway.

> It looks like this would be correct if you check MAY_DELETE_SELF together
> with check_sticky():
>
> if (!error && check_sticky(dir, inode) &&
> !__inode_permission(inode, MAY_DELETE_SELF))
> error = -EPERM;

Nope, this version would deny users with MAY_DELETE_SELF access to a
file and MAY_EXEC access to the directory the right to delete the file
because @error would already be set then.

You are right that the check for MAY_DELETE_SELF overrides too much
here though; we need an additional inode_permission(dir, ...) check in
there.

I've pushed a fixed version here that I will do some more testing on
before reposting:

git://git.kernel.org/pub/scm/linux/kernel/git/agruen/linux-richacl.git
richacl-wip

> I ommitted the IS_RICHACL() check here, since that should probably be in
> __inode_permission() when MAY_DELETE_SELF is passed?

The IS_RICHACL() check is indeed done in inode_permission ->
__inode_permission -> do_inode_permission -> generic_permission ->
acl_permission_check. I've duplicated that check here to avoid slowing
down may_delete in the common case where the filesystem doesn't
support richacls.

Thanks,
Andreas

_______________________________________________
xfs mailing list
[email protected]
http://oss.sgi.com/mailman/listinfo/xfs

2015-11-08 08:06:00

by Andreas Dilger

[permalink] [raw]
Subject: Re: [PATCH v14 11/22] vfs: Cache base_acl objects in inodes

On Nov 5, 2015, at 4:39 AM, Andreas Gruenbacher <[email protected]> wrote:
>
> 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 care which
> kind of acl an inode caches (if any).
>
> Signed-off-by: Andreas Gruenbacher <[email protected]>

Reviewed-by: Andreas Dilger <[email protected]>

> ---
> drivers/staging/lustre/lustre/llite/llite_lib.c | 2 +-
> fs/f2fs/acl.c | 4 +--
> fs/inode.c | 4 +--
> fs/jffs2/acl.c | 10 ++++--
> fs/posix_acl.c | 41 +++++++++++++------------
> fs/richacl_base.c | 4 +--
> include/linux/fs.h | 34 ++++++++++++++++++--
> include/linux/posix_acl.h | 12 +++-----
> include/linux/richacl.h | 9 +++---
> 9 files changed, 75 insertions(+), 45 deletions(-)
>
> diff --git a/drivers/staging/lustre/lustre/llite/llite_lib.c b/drivers/staging/lustre/lustre/llite/llite_lib.c
> index b4ed6c8..46912c2 100644
> --- a/drivers/staging/lustre/lustre/llite/llite_lib.c
> +++ b/drivers/staging/lustre/lustre/llite/llite_lib.c
> @@ -1118,7 +1118,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(base_acl_refcount(&lli->lli_posix_acl->a_base) == 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..9646197 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);
> + base_acl_init(&clone->a_base);
> }
> 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(base_acl_refcount(&acl->a_base) == 1); */
>
> FOREACH_ACL_ENTRY(pa, acl, pe) {
> switch(pa->e_tag) {
> diff --git a/fs/inode.c b/fs/inode.c
> index 78a17b8..5c46ae5 100644
> --- a/fs/inode.c
> +++ b/fs/inode.c
> @@ -233,9 +233,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);
> + base_acl_put(inode->i_acl);
> if (inode->i_default_acl && inode->i_default_acl != ACL_NOT_CACHED)
> - posix_acl_release(inode->i_default_acl);
> + base_acl_put(inode->i_default_acl);
> #endif
> this_cpu_dec(nr_inodes);
> }
> diff --git a/fs/jffs2/acl.c b/fs/jffs2/acl.c
> index 2f7a3c0..569cb1b 100644
> --- a/fs/jffs2/acl.c
> +++ b/fs/jffs2/acl.c
> @@ -294,13 +294,19 @@ 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);
> + struct posix_acl *default_acl = container_of(
> + inode->i_default_acl, struct posix_acl, a_base);
> +
> + rc = __jffs2_set_acl(inode, JFFS2_XPREFIX_ACL_DEFAULT, default_acl);
> if (rc)
> return rc;
> }
>
> if (inode->i_acl) {
> - rc = __jffs2_set_acl(inode, JFFS2_XPREFIX_ACL_ACCESS, inode->i_acl);
> + struct posix_acl *acl = container_of(
> + inode->i_acl, struct posix_acl, a_base);
> +
> + rc = __jffs2_set_acl(inode, JFFS2_XPREFIX_ACL_ACCESS, acl);
> if (rc)
> return rc;
> }
> diff --git a/fs/posix_acl.c b/fs/posix_acl.c
> index aacfb58..4573315 100644
> --- a/fs/posix_acl.c
> +++ b/fs/posix_acl.c
> @@ -21,7 +21,7 @@
> #include <linux/export.h>
> #include <linux/user_namespace.h>
>
> -static struct posix_acl **acl_by_type(struct inode *inode, int type)
> +static struct base_acl **acl_by_type(struct inode *inode, int type)
> {
> switch (type) {
> case ACL_TYPE_ACCESS:
> @@ -35,63 +35,64 @@ static struct posix_acl **acl_by_type(struct inode *inode, int type)
>
> 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);
> + struct base_acl **p = acl_by_type(inode, type);
> + struct base_acl *acl = ACCESS_ONCE(*p);
> if (acl) {
> spin_lock(&inode->i_lock);
> acl = *p;
> if (acl != ACL_NOT_CACHED)
> - acl = posix_acl_dup(acl);
> + base_acl_get(acl);
> spin_unlock(&inode->i_lock);
> }
> - return acl;
> + return container_of(acl, struct posix_acl, a_base);
> }
> EXPORT_SYMBOL(get_cached_acl);
>
> struct posix_acl *get_cached_acl_rcu(struct inode *inode, int type)
> {
> - return rcu_dereference(*acl_by_type(inode, type));
> + struct base_acl *acl = rcu_dereference(*acl_by_type(inode, type));
> + return container_of(acl, struct posix_acl, a_base);
> }
> EXPORT_SYMBOL(get_cached_acl_rcu);
>
> void set_cached_acl(struct inode *inode, int type, struct posix_acl *acl)
> {
> - struct posix_acl **p = acl_by_type(inode, type);
> - struct posix_acl *old;
> + struct base_acl **p = acl_by_type(inode, type);
> + struct base_acl *old;
> spin_lock(&inode->i_lock);
> old = *p;
> - rcu_assign_pointer(*p, posix_acl_dup(acl));
> + rcu_assign_pointer(*p, &posix_acl_dup(acl)->a_base);
> spin_unlock(&inode->i_lock);
> if (old != ACL_NOT_CACHED)
> - posix_acl_release(old);
> + base_acl_put(old);
> }
> EXPORT_SYMBOL(set_cached_acl);
>
> void forget_cached_acl(struct inode *inode, int type)
> {
> - struct posix_acl **p = acl_by_type(inode, type);
> - struct posix_acl *old;
> + struct base_acl **p = acl_by_type(inode, type);
> + struct base_acl *old;
> spin_lock(&inode->i_lock);
> old = *p;
> *p = ACL_NOT_CACHED;
> spin_unlock(&inode->i_lock);
> if (old != ACL_NOT_CACHED)
> - posix_acl_release(old);
> + base_acl_put(old);
> }
> 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);
> + base_acl_put(old_access);
> if (old_default != ACL_NOT_CACHED)
> - posix_acl_release(old_default);
> + base_acl_put(old_default);
> }
> EXPORT_SYMBOL(forget_all_cached_acls);
>
> @@ -128,7 +129,7 @@ EXPORT_SYMBOL(get_acl);
> void
> posix_acl_init(struct posix_acl *acl, int count)
> {
> - atomic_set(&acl->a_refcount, 1);
> + base_acl_init(&acl->a_base);
> acl->a_count = count;
> }
> EXPORT_SYMBOL(posix_acl_init);
> @@ -161,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);
> + base_acl_init(&clone->a_base);
> }
> return clone;
> }
> @@ -383,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(base_acl_refcount(&acl->a_base) == 1); */
>
> FOREACH_ACL_ENTRY(pa, acl, pe) {
> switch(pa->e_tag) {
> @@ -438,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(base_acl_refcount(&acl->a_base) == 1); */
>
> FOREACH_ACL_ENTRY(pa, acl, pe) {
> switch(pa->e_tag) {
> diff --git a/fs/richacl_base.c b/fs/richacl_base.c
> index 69b806c..5826842 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);
> + base_acl_init(&acl->a_base);
> 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);
> + base_acl_init(&dup->a_base);
> }
> return dup;
> }
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index ba91a89..2fd840e 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;
> @@ -3059,4 +3065,26 @@ static inline bool dir_relax(struct inode *inode)
>
> extern bool path_noexec(const struct path *path);
>
> +static inline void base_acl_get(struct base_acl *acl)
> +{
> + if (acl)
> + atomic_inc(&acl->ba_refcount);
> +}
> +
> +static inline void base_acl_put(struct base_acl *acl)
> +{
> + if (acl && atomic_dec_and_test(&acl->ba_refcount))
> + kfree_rcu(acl, ba_rcu);
> +}
> +
> +static inline void base_acl_init(struct base_acl *acl)
> +{
> + atomic_set(&acl->ba_refcount, 1);
> +}
> +
> +static inline int base_acl_refcount(struct base_acl *acl)
> +{
> + return atomic_read(&acl->ba_refcount);
> +}
> +
> #endif /* _LINUX_FS_H */
> diff --git a/include/linux/posix_acl.h b/include/linux/posix_acl.h
> index 5b5a80c..cef5428 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; /* must be first, see posix_acl_release() */
> 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);
> + base_acl_get(&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);
> + base_acl_put(&acl->a_base);
> }
>
>
> diff --git a/include/linux/richacl.h b/include/linux/richacl.h
> index 1d9f5f7..7628fad 100644
> --- a/include/linux/richacl.h
> +++ b/include/linux/richacl.h
> @@ -31,7 +31,7 @@ struct richace {
> };
>
> struct richacl {
> - atomic_t a_refcount;
> + struct base_acl a_base; /* must be first, see richacl_put() */
> unsigned int a_owner_mask;
> unsigned int a_group_mask;
> unsigned int a_other_mask;
> @@ -56,8 +56,7 @@ struct richacl {
> static inline struct richacl *
> richacl_get(struct richacl *acl)
> {
> - if (acl)
> - atomic_inc(&acl->a_refcount);
> + base_acl_get(&acl->a_base);
> return acl;
> }
>
> @@ -67,8 +66,8 @@ 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);
> + base_acl_put(&acl->a_base);
> }
>
> /**
> --
> 2.5.0
>


Cheers, Andreas






Attachments:
signature.asc (833.00 B)
Message signed with OpenPGP using GPGMail

2015-11-08 08:12:48

by Andreas Dilger

[permalink] [raw]
Subject: Re: [PATCH v14 22/22] ext4: Add richacl feature flag

On Nov 5, 2015, at 4:39 AM, Andreas Gruenbacher <[email protected]> wrote:
>
> From: "Aneesh Kumar K.V" <[email protected]>
>
> This feature flag selects richacl instead of POSIX ACL support on the
> filesystem. When this feature is off, the "acl" and "noacl" mount options
> control whether POSIX ACLs are enabled. When it is on, richacls are
> automatically enabled and using the "noacl" mount option leads to an error.
>
> Signed-off-by: Aneesh Kumar K.V <[email protected]>
> Signed-off-by: Andreas Gruenbacher <[email protected]>

Reviewed-by: Andreas Dilger <[email protected]>

> ---
> fs/ext4/ext4.h | 6 ++++--
> fs/ext4/super.c | 49 ++++++++++++++++++++++++++++++++++++++++---------
> 2 files changed, 44 insertions(+), 11 deletions(-)
>
> diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
> index fd1f28b..b97a3b1 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 a63c7b0..7457ea8 100644
> --- a/fs/ext4/super.c
> +++ b/fs/ext4/super.c
> @@ -1270,6 +1270,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 [email protected] if you think we should keep it.\n";
> @@ -1416,9 +1438,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},
> @@ -1466,6 +1488,13 @@ static int handle_mount_opt(struct super_block *sb, char *opt, int token,
> #endif
> switch (token) {
> case Opt_noacl:
> +#ifdef CONFIG_EXT4_FS_RICHACL
> + if (EXT4_HAS_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_RICHACL)) {
> + ext4_msg(sb, KERN_ERR, "Mount option \"%s\" incompatible "
> + "with richacl feature", opt);
> + return -1;
> + }
> +#endif
> case Opt_nouser_xattr:
> ext4_msg(sb, KERN_WARNING, deprecated_msg, opt, "3.5");
> break;
> @@ -3576,8 +3605,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))
> @@ -3660,8 +3689,9 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
> sb->s_iflags |= SB_I_CGROUPWB;
> }
>
> - 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) ||
> @@ -4981,8 +5011,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.5.0
>


Cheers, Andreas






Attachments:
signature.asc (833.00 B)
Message signed with OpenPGP using GPGMail
(No filename) (121.00 B)
Download all attachments

2015-11-08 08:18:55

by Andreas Dilger

[permalink] [raw]
Subject: Re: [PATCH v14 02/22] vfs: Add MAY_CREATE_FILE and MAY_CREATE_DIR permission flags

On Nov 7, 2015, at 10:44 AM, Andreas Gruenbacher <[email protected]> wrote:
>
> On Fri, Nov 6, 2015 at 9:58 PM, Andreas Dilger <[email protected]> wrote:
>> I thought you proposed adding an enum for these parameters, and possibly
>> making them a single parameter flag, to make the code in the caller more
>> readable.
>
> No, the result would just be different but not any better; vfs_rename would
> become messy. I've played with this patch some more, and I find the approach
> below which splits may_delete into may_delete and may_replace much better.
> What do you think?

This is definitely better than the previous patch. You can add:

Reviewed-by: Andreas Dilger <[email protected]>

Cheers, Andreas

> ---------- 8< ----------
>
> 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.
>
> Add may_replace() to allow checking for delete and create access when
> replacing an existing file in vfs_rename().
>
> Signed-off-by: Andreas Gruenbacher <[email protected]>
> Reviewed-by: J. Bruce Fields <[email protected]>
> ---
> fs/namei.c | 49 +++++++++++++++++++++++++++++++++----------------
> include/linux/fs.h | 2 ++
> 2 files changed, 35 insertions(+), 16 deletions(-)
>
> diff --git a/fs/namei.c b/fs/namei.c
> index 224ecf1..4e5dc2f 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)
> {
> @@ -2549,7 +2551,8 @@ 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_replace(struct inode *dir, struct dentry *victim,
> + bool isdir, int mask)
> {
> struct inode *inode = d_backing_inode(victim);
> int error;
> @@ -2561,7 +2564,7 @@ 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);
> + error = inode_permission(dir, mask | MAY_WRITE | MAY_EXEC);
> if (error)
> return error;
> if (IS_APPEND(dir))
> @@ -2584,6 +2587,18 @@ static int may_delete(struct inode *dir, struct dentry *victim, bool isdir)
> return 0;
> }
>
> +static int may_delete(struct inode *dir, struct dentry *victim, bool isdir)
> +{
> + return may_delete_replace(dir, victim, isdir, 0);
> +}
> +
> +static int may_replace(struct inode *dir, struct dentry *victim, bool isdir)
> +{
> + int mask = isdir ? MAY_CREATE_DIR : MAY_CREATE_FILE;
> +
> + return may_delete_replace(dir, victim, isdir, mask);
> +}
> +
> /* Check whether we can create an object with dentry child in directory
> * dir.
> * 1. We can't do it if child already exists (open has special treatment for
> @@ -2592,14 +2607,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);
> }
>
> /*
> @@ -2649,7 +2666,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;
>
> @@ -3494,7 +3511,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;
> @@ -3586,7 +3603,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)
> @@ -3667,7 +3684,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);
>
> if (error)
> return error;
> @@ -3789,7 +3806,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);
>
> if (error)
> return error;
> @@ -3923,7 +3940,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;
> @@ -4006,7 +4023,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;
>
> @@ -4199,14 +4216,14 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
> 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_replace(new_dir, new_dentry, is_dir);
> else
> - error = may_delete(new_dir, new_dentry, new_is_dir);
> + error = may_replace(new_dir, new_dentry, new_is_dir);
> }
> if (error)
> return error;
> @@ -4469,7 +4486,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 4efa435..d6e2330 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.5.0
>


Cheers, Andreas






Attachments:
signature.asc (833.00 B)
Message signed with OpenPGP using GPGMail