From d5d1fb4b99c666317d10a6160a732ac3b49fb142 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:47 +0530 Subject: [PATCH 01/27] vfs: Indicate that the permission functions take all the MAY_* flags Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- fs/namei.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 0b3138de2a3b79..2a4574f48001ba 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -257,7 +257,7 @@ static int acl_permission_check(struct inode *inode, int mask) /** * generic_permission - check for access rights on a Posix-like filesystem * @inode: inode to check access rights for - * @mask: right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC) + * @mask: right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC, ...) * * Used to check for read/write/execute permissions on a file. * We use "fsuid" for this, letting us set arbitrary permissions @@ -331,7 +331,7 @@ static inline int do_inode_permission(struct inode *inode, int mask) /** * inode_permission - check for access rights to a given inode * @inode: inode to check permission on - * @mask: right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC) + * @mask: right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC, ...) * * Used to check for read/write/execute permissions on an inode. * We use "fsuid" for this, letting us set arbitrary permissions From 0f69c40e4f3a7f0ce7b1677b90c9fd6c470970fe Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V" Date: Tue, 18 Oct 2011 15:17:47 +0530 Subject: [PATCH 02/27] vfs: Add hex format for MAY_* flag values We are going to add more flags and having them in hex format make it simpler Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Aneesh Kumar K.V --- include/linux/fs.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/include/linux/fs.h b/include/linux/fs.h index 277f497923a29b..c1884e974ff4bf 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -58,14 +58,15 @@ struct inodes_stat_t { #define NR_FILE 8192 /* this can well be larger on a larger system */ -#define MAY_EXEC 1 -#define MAY_WRITE 2 -#define MAY_READ 4 -#define MAY_APPEND 8 -#define MAY_ACCESS 16 -#define MAY_OPEN 32 -#define MAY_CHDIR 64 -#define MAY_NOT_BLOCK 128 /* called from RCU mode, don't block */ +#define MAY_EXEC 0x00000001 +#define MAY_WRITE 0x00000002 +#define MAY_READ 0x00000004 +#define MAY_APPEND 0x00000008 +#define MAY_ACCESS 0x00000010 +#define MAY_OPEN 0x00000020 +#define MAY_CHDIR 0x00000040 +/* called from RCU mode, don't block */ +#define MAY_NOT_BLOCK 0x00000080 /* * flags in file.f_mode. Note that FMODE_READ and FMODE_WRITE must correspond From bf56180fe4458d2a40bf05c279a5581ccdcc99cf Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:47 +0530 Subject: [PATCH 03/27] vfs: Pass all mask flags down to iop->check_acl Some file permission models differentiate between writing to a file (MAY_WRITE) and appending to it (MAY_WRITE | MAY_APPEND). Pass all the mask flags down to iop->check_acl so that filesystems can distinguish between writing and appending. All users of iop->check_acl pass the mask value back into posix_acl_permission(); strip off the additional mask flags there. Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- fs/namei.c | 2 -- fs/posix_acl.c | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 2a4574f48001ba..276cd30ab8f89a 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -227,8 +227,6 @@ static int acl_permission_check(struct inode *inode, int mask) { unsigned int mode = inode->i_mode; - mask &= MAY_READ | MAY_WRITE | MAY_EXEC | MAY_NOT_BLOCK; - if (current_user_ns() != inode_userns(inode)) goto other_perms; diff --git a/fs/posix_acl.c b/fs/posix_acl.c index 10027b42b7e29a..cea4623f1ed630 100644 --- a/fs/posix_acl.c +++ b/fs/posix_acl.c @@ -218,6 +218,8 @@ posix_acl_permission(struct inode *inode, const struct posix_acl *acl, int want) const struct posix_acl_entry *pa, *pe, *mask_obj; int found = 0; + want &= MAY_READ | MAY_WRITE | MAY_EXEC | MAY_NOT_BLOCK; + FOREACH_ACL_ENTRY(pa, acl, pe) { switch(pa->e_tag) { case ACL_USER_OBJ: From 518cbba6071d9cab3c9bd601af840f6cfb79d70e Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:48 +0530 Subject: [PATCH 04/27] vfs: Add a comment to inode_permission() Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- fs/namei.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 276cd30ab8f89a..9061157e39d6e7 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -221,7 +221,7 @@ static int check_acl(struct inode *inode, int mask) } /* - * This does basic POSIX ACL permission checking + * This does the basic permission checking */ static int acl_permission_check(struct inode *inode, int mask) { @@ -271,7 +271,7 @@ int generic_permission(struct inode *inode, int mask) int ret; /* - * Do the basic POSIX ACL permission checks. + * Do the basic permission checks. */ ret = acl_permission_check(inode, mask); if (ret != -EACCES) @@ -335,6 +335,8 @@ static inline int do_inode_permission(struct inode *inode, int mask) * We use "fsuid" for 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. */ int inode_permission(struct inode *inode, int mask) { From 89e10335292110afd27d827e6fd932d7818f0d0a Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:48 +0530 Subject: [PATCH 05/27] vfs: Add generic IS_ACL() test for acl support When IS_POSIXACL() is true, the vfs does not apply the umask. Other acl models will need the same exception, so introduce a separate IS_ACL() test. The IS_POSIX_ACL() test is still needed so that nfsd can determine when the underlying file system supports POSIX ACLs (as opposed to some other kind). Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- fs/namei.c | 6 +++--- include/linux/fs.h | 8 +++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 9061157e39d6e7..cf8b2f0dbf87ba 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2166,7 +2166,7 @@ static struct file *do_last(struct nameidata *nd, struct path *path, /* Negative dentry, just create the file */ if (!dentry->d_inode) { int 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 @@ -2484,7 +2484,7 @@ SYSCALL_DEFINE4(mknodat, int, dfd, const char __user *, filename, int, mode, 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 = may_mknod(mode); if (error) @@ -2553,7 +2553,7 @@ SYSCALL_DEFINE3(mkdirat, int, dfd, const char __user *, pathname, int, mode) 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 = mnt_want_write(path.mnt); if (error) diff --git a/include/linux/fs.h b/include/linux/fs.h index c1884e974ff4bf..1994b84047a051 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -201,7 +201,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 */ @@ -279,6 +279,12 @@ struct inodes_stat_t { #define IS_AUTOMOUNT(inode) ((inode)->i_flags & S_AUTOMOUNT) #define IS_NOSEC(inode) ((inode)->i_flags & S_NOSEC) +/* + * 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) + /* the read-only stuff doesn't really belong here, but any other place is probably as bad and I don't want to create yet another include file. */ From 45481f1cb71a262b9753c86fa89647c57481cf39 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:48 +0530 Subject: [PATCH 06/27] vfs: Add IS_RICHACL() test for richacl support Introduce a new MS_RICHACL super-block flag and a new IS_RICHACL() test which file systems like nfs can use. IS_ACL() is true if IS_POSIXACL() or IS_RICHACL() is true. Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- include/linux/fs.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/linux/fs.h b/include/linux/fs.h index 1994b84047a051..7b4bfe68098d4b 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -210,6 +210,7 @@ struct inodes_stat_t { #define MS_KERNMOUNT (1<<22) /* this is a kern_mount call */ #define MS_I_VERSION (1<<23) /* Update inode I_version field */ #define MS_STRICTATIME (1<<24) /* Always perform atime updates */ +#define MS_RICHACL (1<<25) /* Supports richacls */ #define MS_NOSEC (1<<28) #define MS_BORN (1<<29) #define MS_ACTIVE (1<<30) @@ -270,6 +271,7 @@ struct inodes_stat_t { #define IS_APPEND(inode) ((inode)->i_flags & S_APPEND) #define IS_IMMUTABLE(inode) ((inode)->i_flags & S_IMMUTABLE) #define IS_POSIXACL(inode) __IS_FLG(inode, MS_POSIXACL) +#define IS_RICHACL(inode) __IS_FLG(inode, MS_RICHACL) #define IS_DEADDIR(inode) ((inode)->i_flags & S_DEAD) #define IS_NOCMTIME(inode) ((inode)->i_flags & S_NOCMTIME) @@ -283,7 +285,7 @@ struct inodes_stat_t { * 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) +#define IS_ACL(inode) __IS_FLG(inode, MS_POSIXACL | MS_RICHACL) /* the read-only stuff doesn't really belong here, but any other place is probably as bad and I don't want to create yet another include file. */ From c68491836b8fc0687785e70ba679b7bb2377683c Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:49 +0530 Subject: [PATCH 07/27] vfs: Optimize out IS_RICHACL() if CONFIG_FS_RICHACL is not defined if CONFIG_FS_RICHACL is not defined optimize out the ACL check function. Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- fs/Kconfig | 3 +++ include/linux/fs.h | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/fs/Kconfig b/fs/Kconfig index 9fe0b349f4cd9e..79391908f994dc 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -46,6 +46,9 @@ endif # BLOCK config FS_POSIX_ACL def_bool n +config FS_RICHACL + def_bool n + config EXPORTFS tristate diff --git a/include/linux/fs.h b/include/linux/fs.h index 7b4bfe68098d4b..f3ebf86ac574e2 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -271,7 +271,12 @@ struct inodes_stat_t { #define IS_APPEND(inode) ((inode)->i_flags & S_APPEND) #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) From 004723e13a6b56d9bf11823711ce7353d2e4196c Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:49 +0530 Subject: [PATCH 08/27] vfs: Add new file and directory create permission flags Some permission models distinguish between the permission to create a non-directory and a directory. Pass this information down to inode_permission() as mask flags Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- fs/namei.c | 26 +++++++++++++++----------- include/linux/fs.h | 2 ++ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index cf8b2f0dbf87ba..f6184b82055198 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -336,7 +336,8 @@ static inline int do_inode_permission(struct inode *inode, int mask) * 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. + * When checking for MAY_APPEND, MAY_CREATE_FILE, MAY_CREATE_DIR, + * MAY_WRITE must also be set in @mask. */ int inode_permission(struct inode *inode, int mask) { @@ -1914,13 +1915,15 @@ static int may_delete(struct inode *dir,struct dentry *victim,int 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, int isdir) { + int mask = isdir ? MAY_CREATE_DIR : MAY_CREATE_FILE; + 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); } /* @@ -1968,7 +1971,7 @@ void unlock_rename(struct dentry *p1, struct dentry *p2) int vfs_create(struct inode *dir, struct dentry *dentry, int mode, struct nameidata *nd) { - int error = may_create(dir, dentry); + int error = may_create(dir, dentry, 0); if (error) return error; @@ -2427,7 +2430,7 @@ EXPORT_SYMBOL(user_path_create); int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev) { - int error = may_create(dir, dentry); + int error = may_create(dir, dentry, 0); if (error) return error; @@ -2524,7 +2527,7 @@ SYSCALL_DEFINE3(mknod, const char __user *, filename, int, mode, unsigned, dev) int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode) { - int error = may_create(dir, dentry); + int error = may_create(dir, dentry, 1); if (error) return error; @@ -2806,7 +2809,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, 0); if (error) return error; @@ -2872,7 +2875,10 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de if (!inode) return -ENOENT; - error = may_create(dir, new_dentry); + if (S_ISDIR(inode->i_mode)) + return -EPERM; + + error = may_create(dir, new_dentry, 0); if (error) return error; @@ -2886,8 +2892,6 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de return -EPERM; if (!dir->i_op->link) return -EPERM; - if (S_ISDIR(inode->i_mode)) - return -EPERM; error = security_inode_link(old_dentry, dir, new_dentry); if (error) @@ -3097,7 +3101,7 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, return error; if (!new_dentry->d_inode) - error = may_create(new_dir, new_dentry); + error = may_create(new_dir, new_dentry, is_dir); else error = may_delete(new_dir, new_dentry, is_dir); if (error) diff --git a/include/linux/fs.h b/include/linux/fs.h index f3ebf86ac574e2..60361c68797f18 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -67,6 +67,8 @@ struct inodes_stat_t { #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 From 1654a09579c5ebb4f8b4d5b3284a79707d1bb5ce Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:50 +0530 Subject: [PATCH 09/27] vfs: Add delete child and delete self permission flags Normally, deleting a file requires write access to the parent directory. Some permission models use a different permission on the parent directory to indicate delete access. In addition, a process can have per-file delete access even without delete access on the parent directory. Introduce two new inode_permission() mask flags and use them in may_delete() Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- fs/namei.c | 53 +++++++++++++++++++++++++++++++++------------- include/linux/fs.h | 2 ++ 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index f6184b82055198..044b6d1fe9ca7b 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -337,7 +337,7 @@ static inline int do_inode_permission(struct inode *inode, int mask) * are used for other things. * * When checking for MAY_APPEND, MAY_CREATE_FILE, MAY_CREATE_DIR, - * MAY_WRITE must also be set in @mask. + * MAY_DELETE_CHILD, MAY_DELETE_SELF, MAY_WRITE must also be set in @mask. */ int inode_permission(struct inode *inode, int mask) { @@ -1835,11 +1835,25 @@ static int user_path_parent(int dfd, const char __user *path, return error; } + +/* + * We should have exec permission on directory and MAY_DELETE_SELF + * on the object being deleted. + */ +static int richacl_may_selfdelete(struct inode *dir, + struct inode *inode, int replace_mask) +{ + return (IS_RICHACL(inode) && + (inode_permission(dir, MAY_EXEC | replace_mask) == 0) && + (inode_permission(inode, MAY_DELETE_SELF) == 0)); +} + /* * It's inline, so penalty for filesystems that don't use sticky bit is * minimal. */ -static inline int check_sticky(struct inode *dir, struct inode *inode) +static inline int check_sticky(struct inode *dir, + struct inode *inode, int replace_mask) { uid_t fsuid = current_fsuid(); @@ -1851,7 +1865,8 @@ static inline int check_sticky(struct inode *dir, struct inode *inode) return 0; if (dir->i_uid == fsuid) return 0; - + if (richacl_may_selfdelete(dir, inode, replace_mask)) + return 0; other_userns: return !ns_capable(inode_userns(inode), CAP_FOWNER); } @@ -1875,30 +1890,38 @@ static inline int check_sticky(struct inode *dir, struct inode *inode) * 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,int isdir) +static int may_delete(struct inode *dir, struct dentry *victim, + int isdir, int replace) { - int error; + int mask, replace_mask = 0, error; + struct inode *inode = victim->d_inode; - if (!victim->d_inode) + if (!inode) return -ENOENT; BUG_ON(victim->d_parent->d_inode != dir); audit_inode_child(victim, dir); - error = inode_permission(dir, MAY_WRITE | MAY_EXEC); + mask = MAY_WRITE | MAY_EXEC | MAY_DELETE_CHILD; + if (replace) + replace_mask = S_ISDIR(inode->i_mode) ? + MAY_CREATE_DIR : MAY_CREATE_FILE; + error = inode_permission(dir, mask | replace_mask); + if (error && richacl_may_selfdelete(dir, inode, replace_mask)) + error = 0; if (error) return error; if (IS_APPEND(dir)) return -EPERM; - if (check_sticky(dir, victim->d_inode)||IS_APPEND(victim->d_inode)|| - IS_IMMUTABLE(victim->d_inode) || IS_SWAPFILE(victim->d_inode)) + if (check_sticky(dir, inode, replace_mask) || IS_APPEND(inode) || + IS_IMMUTABLE(inode) || IS_SWAPFILE(inode)) return -EPERM; if (isdir) { - if (!S_ISDIR(victim->d_inode->i_mode)) + if (!S_ISDIR(inode->i_mode)) return -ENOTDIR; if (IS_ROOT(victim)) return -EBUSY; - } else if (S_ISDIR(victim->d_inode->i_mode)) + } else if (S_ISDIR(inode->i_mode)) return -EISDIR; if (IS_DEADDIR(dir)) return -ENOENT; @@ -2605,7 +2628,7 @@ void dentry_unhash(struct dentry *dentry) int vfs_rmdir(struct inode *dir, struct dentry *dentry) { - int error = may_delete(dir, dentry, 1); + int error = may_delete(dir, dentry, 1, 0); if (error) return error; @@ -2700,7 +2723,7 @@ SYSCALL_DEFINE1(rmdir, const char __user *, pathname) int vfs_unlink(struct inode *dir, struct dentry *dentry) { - int error = may_delete(dir, dentry, 0); + int error = may_delete(dir, dentry, 0, 0); if (error) return error; @@ -3096,14 +3119,14 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, if (old_dentry->d_inode == new_dentry->d_inode) return 0; - error = may_delete(old_dir, old_dentry, is_dir); + error = may_delete(old_dir, old_dentry, is_dir, 0); if (error) return error; if (!new_dentry->d_inode) error = may_create(new_dir, new_dentry, is_dir); else - error = may_delete(new_dir, new_dentry, is_dir); + error = may_delete(new_dir, new_dentry, is_dir, 1); if (error) return error; diff --git a/include/linux/fs.h b/include/linux/fs.h index 60361c68797f18..ccece4038bb801 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -69,6 +69,8 @@ struct inodes_stat_t { #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 From 3437a822773c7aaf241043a9ce803065f84b84c2 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:50 +0530 Subject: [PATCH 10/27] 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. Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- 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 538e27959d3f7f..f15e9e31aabe26 100644 --- a/fs/attr.c +++ b/fs/attr.c @@ -26,7 +26,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 ccece4038bb801..724a4f43e338ef 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2566,7 +2566,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); From e963a4ceee6dc188c8cba48625ac58cff79654b5 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:50 +0530 Subject: [PATCH 11/27] vfs: Add permission flags for setting file attributes Some permission models can allow processes to take ownership of a file, change the file permissions, and set the file timestamps. Introduce new permission mask flags and check for those permissions in inode_change_ok(). Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- fs/attr.c | 70 ++++++++++++++++++++++++++++++++++++++-------- fs/namei.c | 2 +- include/linux/fs.h | 4 +++ 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/fs/attr.c b/fs/attr.c index f15e9e31aabe26..00578b95499901 100644 --- a/fs/attr.c +++ b/fs/attr.c @@ -14,6 +14,55 @@ #include #include +static int richacl_change_ok(struct inode *inode, int mask) +{ + if (!IS_RICHACL(inode)) + return -EPERM; + + if (inode->i_op->permission) + return inode->i_op->permission(inode, mask); + + return check_acl(inode, mask); +} + +static bool inode_uid_change_ok(struct inode *inode, uid_t ia_uid) +{ + if (current_fsuid() == inode->i_uid && ia_uid == inode->i_uid) + return true; + if (current_fsuid() == ia_uid && + richacl_change_ok(inode, MAY_TAKE_OWNERSHIP) == 0) + return true; + if (capable(CAP_CHOWN)) + return true; + return false; +} + +static bool inode_gid_change_ok(struct inode *inode, gid_t ia_gid) +{ + int in_group = in_group_p(ia_gid); + if (current_fsuid() == inode->i_uid && + (in_group || ia_gid == inode->i_gid)) + return true; + if (in_group && richacl_change_ok(inode, MAY_TAKE_OWNERSHIP) == 0) + return true; + if (capable(CAP_CHOWN)) + return true; + return false; +} + +static bool inode_owner_permitted_or_capable(struct inode *inode, int mask) +{ + struct user_namespace *ns = inode_userns(inode); + + if (current_user_ns() == ns && current_fsuid() == inode->i_uid) + return true; + if (richacl_change_ok(inode, mask) == 0) + return true; + if (ns_capable(ns, CAP_FOWNER)) + return true; + return false; +} + /** * inode_change_ok - check if attribute changes to an inode are allowed * @inode: inode to check @@ -45,21 +94,20 @@ int inode_change_ok(struct inode *inode, struct iattr *attr) return 0; /* Make sure a caller can chown. */ - if ((ia_valid & ATTR_UID) && - (current_fsuid() != inode->i_uid || - attr->ia_uid != inode->i_uid) && !capable(CAP_CHOWN)) - return -EPERM; + if (ia_valid & ATTR_UID) { + if (!inode_uid_change_ok(inode, attr->ia_uid)) + return -EPERM; + } /* Make sure caller can chgrp. */ - if ((ia_valid & ATTR_GID) && - (current_fsuid() != inode->i_uid || - (!in_group_p(attr->ia_gid) && attr->ia_gid != inode->i_gid)) && - !capable(CAP_CHOWN)) - return -EPERM; + if (ia_valid & ATTR_GID) { + if (!inode_gid_change_ok(inode, attr->ia_gid)) + return -EPERM; + } /* Make sure a caller can chmod. */ if (ia_valid & ATTR_MODE) { - if (!inode_owner_or_capable(inode)) + if (!inode_owner_permitted_or_capable(inode, MAY_CHMOD)) return -EPERM; /* Also check the setgid bit! */ if (!in_group_p((ia_valid & ATTR_GID) ? attr->ia_gid : @@ -69,7 +117,7 @@ int inode_change_ok(struct inode *inode, struct iattr *attr) /* Check for setting the inode time. */ if (ia_valid & (ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET)) { - if (!inode_owner_or_capable(inode)) + if (!inode_owner_permitted_or_capable(inode, MAY_SET_TIMES)) return -EPERM; } diff --git a/fs/namei.c b/fs/namei.c index 044b6d1fe9ca7b..de8c7d3de6f5d1 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -174,7 +174,7 @@ void putname(const char *name) EXPORT_SYMBOL(putname); #endif -static int check_acl(struct inode *inode, int mask) +int check_acl(struct inode *inode, int mask) { #ifdef CONFIG_FS_POSIX_ACL struct posix_acl *acl; diff --git a/include/linux/fs.h b/include/linux/fs.h index 724a4f43e338ef..ac1d8e5a325c83 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -71,6 +71,9 @@ struct inodes_stat_t { #define MAY_CREATE_DIR 0x00000200 #define MAY_DELETE_CHILD 0x00000400 #define MAY_DELETE_SELF 0x00000800 +#define MAY_TAKE_OWNERSHIP 0x00001000 +#define MAY_CHMOD 0x00002000 +#define MAY_SET_TIMES 0x00004000 /* * flags in file.f_mode. Note that FMODE_READ and FMODE_WRITE must correspond @@ -2232,6 +2235,7 @@ extern sector_t bmap(struct inode *, sector_t); extern int notify_change(struct dentry *, struct iattr *); extern int inode_permission(struct inode *, int); extern int generic_permission(struct inode *, int); +extern int check_acl(struct inode *, int); static inline bool execute_ok(struct inode *inode) { From 26dc449ec33f7432bd96ae6529e14b02a29e01bf Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:51 +0530 Subject: [PATCH 12/27] vfs: Make acl_permission_check() work for richacls Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- fs/namei.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/fs/namei.c b/fs/namei.c index de8c7d3de6f5d1..2685e04cfbb69c 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -230,6 +230,20 @@ static int acl_permission_check(struct inode *inode, int mask) if (current_user_ns() != inode_userns(inode)) goto other_perms; + if (IS_RICHACL(inode)) { + int error = check_acl(inode, mask); + if (error != -EAGAIN) + return error; + if (mask & (MAY_DELETE_SELF | MAY_TAKE_OWNERSHIP | + MAY_CHMOD | MAY_SET_TIMES)) { + /* + * The file permission bit cannot grant these + * permissions. + */ + return -EACCES; + } + } + if (likely(current_fsuid() == inode->i_uid)) mode >>= 6; else { From e210ab8434fa222917229b9f7b72539f40ea6e75 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:51 +0530 Subject: [PATCH 13/27] richacl: In-memory representation and helper functions A richacl consists of an NFSv4 acl and an owner, group, and other mask. These three masks correspond to the owner, group, and other file permission bits, but they contain NFSv4 permissions instead of POSIX permissions. Each entry in the NFSv4 acl applies to the file owner (OWNER@), the owning group (GROUP@), literally everyone (EVERYONE@), or to a specific uid or gid. As in the standard POSIX file permission model, each process is the owner, group, or other file class. A richacl grants a requested access only if the NFSv4 acl in the richacl grants the access (according to the NFSv4 permission check algorithm), and the file mask that applies to the process includes the requested permissions. Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- fs/Makefile | 2 + fs/richacl_base.c | 69 ++++++++++++ include/linux/richacl.h | 237 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 308 insertions(+) create mode 100644 fs/richacl_base.c create mode 100644 include/linux/richacl.h diff --git a/fs/Makefile b/fs/Makefile index afc109691a9b90..76121688c9a6eb 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -48,6 +48,8 @@ obj-$(CONFIG_NFS_COMMON) += nfs_common/ obj-$(CONFIG_GENERIC_ACL) += generic_acl.o obj-$(CONFIG_FHANDLE) += fhandle.o +obj-$(CONFIG_FS_RICHACL) += richacl.o +richacl-y := richacl_base.o obj-y += quota/ diff --git a/fs/richacl_base.c b/fs/richacl_base.c new file mode 100644 index 00000000000000..689e1a6dace7f2 --- /dev/null +++ b/fs/richacl_base.c @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2006, 2010 Novell, Inc. + * Written by Andreas Gruenbacher + * + * 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 +#include +#include +#include + +MODULE_LICENSE("GPL"); + +/** + * richacl_alloc - allocate a richacl + * @count: number of entries + */ +struct richacl * +richacl_alloc(int count) +{ + size_t size = sizeof(struct richacl) + count * sizeof(struct richace); + struct richacl *acl = kzalloc(size, GFP_KERNEL); + + if (acl) { + atomic_set(&acl->a_refcount, 1); + acl->a_count = count; + } + return acl; +} +EXPORT_SYMBOL_GPL(richacl_alloc); + +/** + * richacl_clone - create a copy of a richacl + */ +static struct richacl * +richacl_clone(const struct richacl *acl) +{ + int count = acl->a_count; + size_t size = sizeof(struct richacl) + count * sizeof(struct richace); + struct richacl *dup = kmalloc(size, GFP_KERNEL); + + if (dup) { + memcpy(dup, acl, size); + atomic_set(&dup->a_refcount, 1); + } + return dup; +} + +/** + * richace_is_same_identifier - are both identifiers the same? + */ +int +richace_is_same_identifier(const struct richace *a, const struct richace *b) +{ +#define WHO_FLAGS (ACE4_SPECIAL_WHO | ACE4_IDENTIFIER_GROUP) + if ((a->e_flags & WHO_FLAGS) != (b->e_flags & WHO_FLAGS)) + return 0; + return a->e_id == b->e_id; +#undef WHO_FLAGS +} diff --git a/include/linux/richacl.h b/include/linux/richacl.h new file mode 100644 index 00000000000000..51d6937651f156 --- /dev/null +++ b/include/linux/richacl.h @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2006, 2010 Novell, Inc. + * Written by Andreas Gruenbacher + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#ifndef __RICHACL_H +#define __RICHACL_H +#include + +#define ACE_OWNER_ID 130 +#define ACE_GROUP_ID 131 +#define ACE_EVERYONE_ID 110 + +struct richace { + unsigned short e_type; + unsigned short e_flags; + unsigned int e_mask; + unsigned int e_id; +}; + +struct richacl { + atomic_t a_refcount; + unsigned int a_owner_mask; + unsigned int a_group_mask; + unsigned int a_other_mask; + unsigned short a_count; + unsigned short a_flags; + struct richace a_entries[0]; +}; + +#define richacl_for_each_entry(_ace, _acl) \ + for (_ace = _acl->a_entries; \ + _ace != _acl->a_entries + _acl->a_count; \ + _ace++) + +#define richacl_for_each_entry_reverse(_ace, _acl) \ + for (_ace = _acl->a_entries + _acl->a_count - 1; \ + _ace != _acl->a_entries - 1; \ + _ace--) + +/* Flag values defined by rich-acl */ +#define ACL4_MASKED 0x80 + +#define ACL4_VALID_FLAGS ( \ + ACL4_MASKED) + +/* e_type values */ +#define ACE4_ACCESS_ALLOWED_ACE_TYPE 0x0000 +#define ACE4_ACCESS_DENIED_ACE_TYPE 0x0001 +/*#define ACE4_SYSTEM_AUDIT_ACE_TYPE 0x0002*/ +/*#define ACE4_SYSTEM_ALARM_ACE_TYPE 0x0003*/ + +/* e_flags bitflags */ +#define ACE4_FILE_INHERIT_ACE 0x0001 +#define ACE4_DIRECTORY_INHERIT_ACE 0x0002 +#define ACE4_NO_PROPAGATE_INHERIT_ACE 0x0004 +#define ACE4_INHERIT_ONLY_ACE 0x0008 +/*#define ACE4_SUCCESSFUL_ACCESS_ACE_FLAG 0x0010*/ +/*#define ACE4_FAILED_ACCESS_ACE_FLAG 0x0020*/ +#define ACE4_IDENTIFIER_GROUP 0x0040 +/* richacl specific flag values */ +#define ACE4_SPECIAL_WHO 0x4000 + +#define ACE4_VALID_FLAGS ( \ + ACE4_FILE_INHERIT_ACE | \ + ACE4_DIRECTORY_INHERIT_ACE | \ + ACE4_NO_PROPAGATE_INHERIT_ACE | \ + ACE4_INHERIT_ONLY_ACE | \ + ACE4_IDENTIFIER_GROUP | \ + ACE4_SPECIAL_WHO) + +/* e_mask bitflags */ +#define ACE4_READ_DATA 0x00000001 +#define ACE4_LIST_DIRECTORY 0x00000001 +#define ACE4_WRITE_DATA 0x00000002 +#define ACE4_ADD_FILE 0x00000002 +#define ACE4_APPEND_DATA 0x00000004 +#define ACE4_ADD_SUBDIRECTORY 0x00000004 +#define ACE4_READ_NAMED_ATTRS 0x00000008 +#define ACE4_WRITE_NAMED_ATTRS 0x00000010 +#define ACE4_EXECUTE 0x00000020 +#define ACE4_DELETE_CHILD 0x00000040 +#define ACE4_READ_ATTRIBUTES 0x00000080 +#define ACE4_WRITE_ATTRIBUTES 0x00000100 +#define ACE4_WRITE_RETENTION 0x00000200 +#define ACE4_WRITE_RETENTION_HOLD 0x00000400 +#define ACE4_DELETE 0x00010000 +#define ACE4_READ_ACL 0x00020000 +#define ACE4_WRITE_ACL 0x00040000 +#define ACE4_WRITE_OWNER 0x00080000 +#define ACE4_SYNCHRONIZE 0x00100000 + +/* Valid ACE4_* flags for directories and non-directories */ +#define ACE4_VALID_MASK ( \ + ACE4_READ_DATA | ACE4_LIST_DIRECTORY | \ + ACE4_WRITE_DATA | ACE4_ADD_FILE | \ + ACE4_APPEND_DATA | ACE4_ADD_SUBDIRECTORY | \ + ACE4_READ_NAMED_ATTRS | \ + ACE4_WRITE_NAMED_ATTRS | \ + ACE4_EXECUTE | \ + ACE4_DELETE_CHILD | \ + ACE4_READ_ATTRIBUTES | \ + ACE4_WRITE_ATTRIBUTES | \ + ACE4_WRITE_RETENTION | \ + ACE4_WRITE_RETENTION_HOLD | \ + ACE4_DELETE | \ + ACE4_READ_ACL | \ + ACE4_WRITE_ACL | \ + ACE4_WRITE_OWNER | \ + ACE4_SYNCHRONIZE) + +/** + * richacl_get - grab another reference to a richacl handle + */ +static inline struct richacl * +richacl_get(struct richacl *acl) +{ + if (acl) + atomic_inc(&acl->a_refcount); + return acl; +} + +/** + * richacl_put - free a richacl handle + */ +static inline void +richacl_put(struct richacl *acl) +{ + if (acl && atomic_dec_and_test(&acl->a_refcount)) + kfree(acl); +} + +/** + * richace_is_owner - check if @ace is an OWNER@ entry + */ +static inline int +richace_is_owner(const struct richace *ace) +{ + return (ace->e_flags & ACE4_SPECIAL_WHO) && + ace->e_id == ACE_OWNER_ID; +} + +/** + * richace_is_group - check if @ace is a GROUP@ entry + */ +static inline int +richace_is_group(const struct richace *ace) +{ + return (ace->e_flags & ACE4_SPECIAL_WHO) && + ace->e_id == ACE_GROUP_ID; +} + +/** + * richace_is_everyone - check if @ace is an EVERYONE@ entry + */ +static inline int +richace_is_everyone(const struct richace *ace) +{ + return (ace->e_flags & ACE4_SPECIAL_WHO) && + ace->e_id == ACE_EVERYONE_ID; +} + +/** + * richace_is_unix_id - check if @ace applies to a specific uid or gid + */ +static inline int +richace_is_unix_id(const struct richace *ace) +{ + return !(ace->e_flags & ACE4_SPECIAL_WHO); +} + +/** + * richace_is_inherit_only - check if @ace is for inheritance only + * + * ACEs with the %ACE4_INHERIT_ONLY_ACE flag set have no effect during + * permission checking. + */ +static inline int +richace_is_inherit_only(const struct richace *ace) +{ + return ace->e_flags & ACE4_INHERIT_ONLY_ACE; +} + +/** + * richace_is_inheritable - check if @ace is inheritable + */ +static inline int +richace_is_inheritable(const struct richace *ace) +{ + return ace->e_flags & (ACE4_FILE_INHERIT_ACE | + ACE4_DIRECTORY_INHERIT_ACE); +} + +/** + * richace_clear_inheritance_flags - clear all inheritance flags in @ace + */ +static inline void +richace_clear_inheritance_flags(struct richace *ace) +{ + ace->e_flags &= ~(ACE4_FILE_INHERIT_ACE | + ACE4_DIRECTORY_INHERIT_ACE | + ACE4_NO_PROPAGATE_INHERIT_ACE | + ACE4_INHERIT_ONLY_ACE); +} + +/** + * richace_is_allow - check if @ace is an %ALLOW type entry + */ +static inline int +richace_is_allow(const struct richace *ace) +{ + return ace->e_type == ACE4_ACCESS_ALLOWED_ACE_TYPE; +} + +/** + * richace_is_deny - check if @ace is a %DENY type entry + */ +static inline int +richace_is_deny(const struct richace *ace) +{ + return ace->e_type == ACE4_ACCESS_DENIED_ACE_TYPE; +} + +extern struct richacl *richacl_alloc(int); +extern int richace_is_same_identifier(const struct richace *, + const struct richace *); +#endif /* __RICHACL_H */ From 36ee026b06dcb09d303985a42aa0027abaffbadf Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:52 +0530 Subject: [PATCH 14/27] 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. Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- fs/richacl_base.c | 117 ++++++++++++++++++++++++++++++++++++++++ include/linux/richacl.h | 47 ++++++++++++++++ 2 files changed, 164 insertions(+) diff --git a/fs/richacl_base.c b/fs/richacl_base.c index 689e1a6dace7f2..bca20936aed109 100644 --- a/fs/richacl_base.c +++ b/fs/richacl_base.c @@ -55,6 +55,123 @@ richacl_clone(const struct richacl *acl) return dup; } +/** + * richacl_mask_to_mode - compute the file permission bits which correspond to @mask + * @mask: %ACE4_* permission mask + * + * See richacl_masks_to_mode(). + */ +static int +richacl_mask_to_mode(unsigned int mask) +{ + int mode = 0; + + if (mask & ACE4_POSIX_MODE_READ) + mode |= MAY_READ; + if (mask & ACE4_POSIX_MODE_WRITE) + mode |= MAY_WRITE; + if (mask & ACE4_POSIX_MODE_EXEC) + mode |= MAY_EXEC; + + return mode; +} + +/** + * richacl_masks_to_mode - compute the file permission bits from the 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 + * ACE4_APPEND_DATA even if it does not also contain ACE4_WRITE_DATA. + * + * Permissions which are not in ACE4_POSIX_MODE_READ, ACE4_POSIX_MODE_WRITE, or + * ACE4_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(), and only if they were set explicitly, for example, by setting a + * richacl. + */ +int +richacl_masks_to_mode(const struct richacl *acl) +{ + return richacl_mask_to_mode(acl->a_owner_mask) << 6 | + richacl_mask_to_mode(acl->a_group_mask) << 3 | + richacl_mask_to_mode(acl->a_other_mask); +} +EXPORT_SYMBOL_GPL(richacl_masks_to_mode); + +/** + * richacl_mode_to_mask - compute a file mask from the lowest three mode bits + * + * When the file permission bits of a file are set with chmod(), this specifies + * the maximum permissions that processes will get. All permissions beyond + * that will be removed from the file masks, and become ineffective. + * + * We also add in the permissions which are always allowed no matter what the + * acl says. + */ +unsigned int +richacl_mode_to_mask(mode_t mode) +{ + unsigned int mask = ACE4_POSIX_ALWAYS_ALLOWED; + + if (mode & MAY_READ) + mask |= ACE4_POSIX_MODE_READ; + if (mode & MAY_WRITE) + mask |= ACE4_POSIX_MODE_WRITE; + if (mode & MAY_EXEC) + mask |= ACE4_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 ACE4_ADD_FILE, ACE4_ADD_SUBDIRECTORY, and ACE4_DELETE_CHILD + * here. + */ +unsigned int +richacl_want_to_mask(unsigned int want) +{ + unsigned int mask = 0; + + if (want & MAY_READ) + mask |= ACE4_READ_DATA; + if (want & MAY_DELETE_SELF) + mask |= ACE4_DELETE; + if (want & MAY_TAKE_OWNERSHIP) + mask |= ACE4_WRITE_OWNER; + if (want & MAY_CHMOD) + mask |= ACE4_WRITE_ACL; + if (want & MAY_SET_TIMES) + mask |= ACE4_WRITE_ATTRIBUTES; + if (want & MAY_EXEC) + mask |= ACE4_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 |= ACE4_APPEND_DATA; + if (want & MAY_CREATE_FILE) + mask |= ACE4_ADD_FILE; + if (want & MAY_CREATE_DIR) + mask |= ACE4_ADD_SUBDIRECTORY; + if (want & MAY_DELETE_CHILD) + mask |= ACE4_DELETE_CHILD; + } else if (want & MAY_WRITE) + mask |= ACE4_WRITE_DATA; + return mask; +} +EXPORT_SYMBOL_GPL(richacl_want_to_mask); + /** * richace_is_same_identifier - are both identifiers the same? */ diff --git a/include/linux/richacl.h b/include/linux/richacl.h index 51d6937651f156..fe875fe24636b9 100644 --- a/include/linux/richacl.h +++ b/include/linux/richacl.h @@ -119,6 +119,49 @@ struct richacl { ACE4_WRITE_OWNER | \ ACE4_SYNCHRONIZE) +/* + * The POSIX permissions are supersets of the following NFSv4 permissions: + * + * - MAY_READ maps to READ_DATA or LIST_DIRECTORY, depending on the type + * of the file system object. + * + * - MAY_WRITE maps to WRITE_DATA or ACE4_APPEND_DATA for files, and to + * ADD_FILE, ACE4_ADD_SUBDIRECTORY, or ACE4_DELETE_CHILD for directories. + * + * - MAY_EXECUTE maps to ACE4_EXECUTE. + * + * (Some of these NFSv4 permissions have the same bit values.) + */ +#define ACE4_POSIX_MODE_READ ( \ + ACE4_READ_DATA | \ + ACE4_LIST_DIRECTORY) +#define ACE4_POSIX_MODE_WRITE ( \ + ACE4_WRITE_DATA | \ + ACE4_ADD_FILE | \ + ACE4_APPEND_DATA | \ + ACE4_ADD_SUBDIRECTORY | \ + ACE4_DELETE_CHILD) +#define ACE4_POSIX_MODE_EXEC ACE4_EXECUTE +#define ACE4_POSIX_MODE_ALL ( \ + ACE4_POSIX_MODE_READ | \ + ACE4_POSIX_MODE_WRITE | \ + ACE4_POSIX_MODE_EXEC) +/* + * These permissions are always allowed + * no matter what the acl says. + */ +#define ACE4_POSIX_ALWAYS_ALLOWED ( \ + ACE4_SYNCHRONIZE | \ + ACE4_READ_ATTRIBUTES | \ + ACE4_READ_ACL) +/* + * The owner is implicitly granted + * these permissions under POSIX. + */ +#define ACE4_POSIX_OWNER_ALLOWED ( \ + ACE4_WRITE_ATTRIBUTES | \ + ACE4_WRITE_OWNER | \ + ACE4_WRITE_ACL) /** * richacl_get - grab another reference to a richacl handle */ @@ -234,4 +277,8 @@ richace_is_deny(const struct richace *ace) extern struct richacl *richacl_alloc(int); extern int richace_is_same_identifier(const struct richace *, const struct richace *); +extern int richacl_masks_to_mode(const struct richacl *); +extern unsigned int richacl_mode_to_mask(mode_t); +extern unsigned int richacl_want_to_mask(unsigned int); + #endif /* __RICHACL_H */ From dc240c0d7cbe666c61ba5ac1ef695f48467444bb Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:52 +0530 Subject: [PATCH 15/27] 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 specify file modes (such as via nfsd). When user-space sets an acl, the file masks are passed in as part of the xattr. When setting a richacl, the file masks determine what the file permission bits will be set to; see richacl_masks_to_mode(). Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- fs/richacl_base.c | 128 ++++++++++++++++++++++++++++++++++++++++ include/linux/richacl.h | 1 + 2 files changed, 129 insertions(+) diff --git a/fs/richacl_base.c b/fs/richacl_base.c index bca20936aed109..d73f1b0a9c5f31 100644 --- a/fs/richacl_base.c +++ b/fs/richacl_base.c @@ -184,3 +184,131 @@ richace_is_same_identifier(const struct richace *a, const struct richace *b) return a->e_id == b->e_id; #undef WHO_FLAGS } + +/** + * richacl_allowed_to_who - mask flags allowed to a specific who value + * + * Computes the mask values allowed to a specific who value, taking + * EVERYONE@ entries 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 the group class is allowed + * + * 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 (!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 mask flags allowed by the acl are disabled (for any choice of the + * file owner or group membership). + */ +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 or 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. + */ + +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 &= ~ACL4_MASKED; +} +EXPORT_SYMBOL_GPL(richacl_compute_max_masks); diff --git a/include/linux/richacl.h b/include/linux/richacl.h index fe875fe24636b9..14f18b597be324 100644 --- a/include/linux/richacl.h +++ b/include/linux/richacl.h @@ -280,5 +280,6 @@ extern int richace_is_same_identifier(const struct richace *, extern int richacl_masks_to_mode(const struct richacl *); extern unsigned int richacl_mode_to_mask(mode_t); extern unsigned int richacl_want_to_mask(unsigned int); +extern void richacl_compute_max_masks(struct richacl *); #endif /* __RICHACL_H */ From 6be646b13bbccea9fb49a00784945f7bbf36b174 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:52 +0530 Subject: [PATCH 16/27] 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 ACE4_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. Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- fs/richacl_base.c | 40 ++++++++++++++++++++++++++++++++++++++++ include/linux/richacl.h | 1 + 2 files changed, 41 insertions(+) diff --git a/fs/richacl_base.c b/fs/richacl_base.c index d73f1b0a9c5f31..41ff82d10d4d4d 100644 --- a/fs/richacl_base.c +++ b/fs/richacl_base.c @@ -312,3 +312,43 @@ void richacl_compute_max_masks(struct richacl *acl) acl->a_flags &= ~ACL4_MASKED; } EXPORT_SYMBOL_GPL(richacl_compute_max_masks); + +/** + * richacl_chmod - update the file masks to reflect the new mode + * @mode: new file permission bits + * + * Return a copy of @acl where the file masks have been replaced by the file + * masks corresponding to the file permission bits in @mode, or returns @acl + * itself if the file masks are already up to date. Takes over a reference + * to @acl. + */ +struct richacl * +richacl_chmod(struct richacl *acl, mode_t mode) +{ + unsigned int owner_mask, group_mask, other_mask; + struct richacl *clone; + + owner_mask = richacl_mode_to_mask(mode >> 6) | + ACE4_POSIX_OWNER_ALLOWED; + group_mask = richacl_mode_to_mask(mode >> 3); + other_mask = richacl_mode_to_mask(mode); + + if (acl->a_owner_mask == owner_mask && + acl->a_group_mask == group_mask && + acl->a_other_mask == other_mask && + (acl->a_flags & ACL4_MASKED)) + return acl; + + clone = richacl_clone(acl); + richacl_put(acl); + if (!clone) + return ERR_PTR(-ENOMEM); + + clone->a_flags |= ACL4_MASKED; + clone->a_owner_mask = owner_mask; + clone->a_group_mask = group_mask; + clone->a_other_mask = other_mask; + + return clone; +} +EXPORT_SYMBOL_GPL(richacl_chmod); diff --git a/include/linux/richacl.h b/include/linux/richacl.h index 14f18b597be324..a1ff8a38409c01 100644 --- a/include/linux/richacl.h +++ b/include/linux/richacl.h @@ -281,5 +281,6 @@ extern int richacl_masks_to_mode(const struct richacl *); extern unsigned int richacl_mode_to_mask(mode_t); extern unsigned int richacl_want_to_mask(unsigned int); extern void richacl_compute_max_masks(struct richacl *); +extern struct richacl *richacl_chmod(struct richacl *, mode_t); #endif /* __RICHACL_H */ From 61bf1349bb04d434f849c516034ca65650ddd1ac Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:53 +0530 Subject: [PATCH 17/27] richacl: Permission check algorithm As in the standard POSIX file permission model, each process is the owner, group, or other file class. 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. Each file class is associated with a file mask. A richacl 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. Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- fs/richacl_base.c | 99 +++++++++++++++++++++++++++++++++++++++++ include/linux/richacl.h | 2 + 2 files changed, 101 insertions(+) diff --git a/fs/richacl_base.c b/fs/richacl_base.c index 41ff82d10d4d4d..ab9139e1582584 100644 --- a/fs/richacl_base.c +++ b/fs/richacl_base.c @@ -352,3 +352,102 @@ richacl_chmod(struct richacl *acl, mode_t mode) return clone; } EXPORT_SYMBOL_GPL(richacl_chmod); + +/** + * richacl_permission - richacl permission check algorithm + * @inode: inode to check + * @acl: rich acl of the inode + * @mask: requested access (ACE4_* bitmask) + * + * Checks if the current process is granted @mask flags in @acl. + */ +int +richacl_permission(struct inode *inode, const struct richacl *acl, + unsigned int mask) +{ + const struct richace *ace; + 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; + + /* + * We don't need to know which class the process is in when the acl is + * not masked. + */ + if (!(acl->a_flags & ACL4_MASKED)) + in_owner_or_group_class = 1; + + /* + * 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. + */ + + /* + * 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 (current_fsuid() != inode->i_uid) + continue; + goto is_owner; + } else if (richace_is_group(ace)) { + if (!in_owning_group) + continue; + } else if (richace_is_unix_id(ace)) { + if (ace->e_flags & ACE4_IDENTIFIER_GROUP) { + if (!in_group_p(ace->e_id)) + continue; + } else { + if (current_fsuid() != ace->e_id) + continue; + } + } else + goto is_everyone; + +is_owner: + /* The process is in the owner or group file class. */ + in_owner_or_group_class = 1; + +is_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 & ACL4_MASKED) { + unsigned int file_mask; + + /* + * The file class a process is in determines which file mask + * applies. Check if that file mask also grants the requested + * access. + */ + if (current_fsuid() == inode->i_uid) + file_mask = acl->a_owner_mask; + else if (in_owner_or_group_class) + file_mask = acl->a_group_mask; + else + file_mask = acl->a_other_mask; + denied |= requested & ~file_mask; + } + + return denied ? -EACCES : 0; +} +EXPORT_SYMBOL_GPL(richacl_permission); diff --git a/include/linux/richacl.h b/include/linux/richacl.h index a1ff8a38409c01..d43700a179d31b 100644 --- a/include/linux/richacl.h +++ b/include/linux/richacl.h @@ -282,5 +282,7 @@ extern unsigned int richacl_mode_to_mask(mode_t); extern unsigned int richacl_want_to_mask(unsigned int); extern void richacl_compute_max_masks(struct richacl *); extern struct richacl *richacl_chmod(struct richacl *, mode_t); +extern int richacl_permission(struct inode *, const struct richacl *, + unsigned int); #endif /* __RICHACL_H */ From 9d9bef3788daeeec62c6bd5aa53dcfffe3373dd8 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:53 +0530 Subject: [PATCH 18/27] 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. Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- fs/Makefile | 2 +- fs/richacl_base.c | 69 +++++++++++++++++++++++++++++++++++++++++ fs/richacl_inode.c | 59 +++++++++++++++++++++++++++++++++++ include/linux/richacl.h | 4 +++ 4 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 fs/richacl_inode.c diff --git a/fs/Makefile b/fs/Makefile index 76121688c9a6eb..1ecf9f216b7098 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -49,7 +49,7 @@ obj-$(CONFIG_GENERIC_ACL) += generic_acl.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_base.c b/fs/richacl_base.c index ab9139e1582584..67f452d2bd9c94 100644 --- a/fs/richacl_base.c +++ b/fs/richacl_base.c @@ -451,3 +451,72 @@ richacl_permission(struct inode *inode, const struct richacl *acl, return denied ? -EACCES : 0; } EXPORT_SYMBOL_GPL(richacl_permission); + +/** + * 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); + 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; + memcpy(ace, dir_ace, sizeof(struct richace)); + if (dir_ace->e_flags & ACE4_NO_PROPAGATE_INHERIT_ACE) + richace_clear_inheritance_flags(ace); + if ((dir_ace->e_flags & ACE4_FILE_INHERIT_ACE) && + !(dir_ace->e_flags & ACE4_DIRECTORY_INHERIT_ACE)) + ace->e_flags |= ACE4_INHERIT_ONLY_ACE; + ace++; + } + } else { + richacl_for_each_entry(dir_ace, dir_acl) { + if (!(dir_ace->e_flags & ACE4_FILE_INHERIT_ACE)) + continue; + count++; + } + if (!count) + return NULL; + acl = richacl_alloc(count); + if (!acl) + return ERR_PTR(-ENOMEM); + ace = acl->a_entries; + richacl_for_each_entry(dir_ace, dir_acl) { + if (!(dir_ace->e_flags & ACE4_FILE_INHERIT_ACE)) + continue; + memcpy(ace, dir_ace, sizeof(struct richace)); + richace_clear_inheritance_flags(ace); + /* + * ACE4_DELETE_CHILD is meaningless for + * non-directories, so clear it. + */ + ace->e_mask &= ~ACE4_DELETE_CHILD; + ace++; + } + } + + return acl; +} diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c new file mode 100644 index 00000000000000..f590fb52681264 --- /dev/null +++ b/fs/richacl_inode.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2010 Novell, Inc. + * Written by Andreas Gruenbacher + * + * 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 +#include +#include +#include + +/** + * richacl_inherit_inode - compute inherited acl and file mode + * @dir_acl: acl of the containing directory + * @inode: inode of the new file (create mode in i_mode) + * + * The file permission bits in inode->i_mode must be set to the create mode. + * If there is an inheritable acl, the maximum permissions that the acl grants + * will be computed and permissions not granted by the acl will be removed from + * inode->i_mode. If there is no inheritable acl, the umask will be applied + * instead. + */ +struct richacl * +richacl_inherit_inode(const struct richacl *dir_acl, struct inode *inode) +{ + struct richacl *acl; + mode_t mask; + + acl = richacl_inherit(dir_acl, S_ISDIR(inode->i_mode)); + if (acl) { + + richacl_compute_max_masks(acl); + + /* + * Ensure that the acl will not grant any permissions beyond + * the create mode. + */ + acl->a_flags |= ACL4_MASKED; + acl->a_owner_mask &= richacl_mode_to_mask(inode->i_mode >> 6) | + ACE4_POSIX_OWNER_ALLOWED; + acl->a_group_mask &= richacl_mode_to_mask(inode->i_mode >> 3); + acl->a_other_mask &= richacl_mode_to_mask(inode->i_mode); + mask = ~S_IRWXUGO | richacl_masks_to_mode(acl); + } else + mask = ~current_umask(); + + inode->i_mode &= mask; + return acl; +} +EXPORT_SYMBOL_GPL(richacl_inherit_inode); diff --git a/include/linux/richacl.h b/include/linux/richacl.h index d43700a179d31b..47855ec8db4dc6 100644 --- a/include/linux/richacl.h +++ b/include/linux/richacl.h @@ -284,5 +284,9 @@ extern void richacl_compute_max_masks(struct richacl *); extern struct richacl *richacl_chmod(struct richacl *, mode_t); extern int richacl_permission(struct inode *, const struct richacl *, unsigned int); +extern struct richacl *richacl_inherit(const struct richacl *, int); +/* richacl_inode.c */ +extern struct richacl *richacl_inherit_inode(const struct richacl *, + struct inode *); #endif /* __RICHACL_H */ From 8013f16c484b839a57e3c9864628db68db5588b3 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:54 +0530 Subject: [PATCH 19/27] richacl: Check if an acl is equivalent to a file mode This function is used to avoid storing richacls on disk if the acl can be computed from the file permission bits. Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- fs/richacl_base.c | 54 +++++++++++++++++++++++++++++++++++++++++ include/linux/richacl.h | 1 + 2 files changed, 55 insertions(+) diff --git a/fs/richacl_base.c b/fs/richacl_base.c index 67f452d2bd9c94..ae11fac4c7de2e 100644 --- a/fs/richacl_base.c +++ b/fs/richacl_base.c @@ -520,3 +520,57 @@ richacl_inherit(const struct richacl *dir_acl, int isdir) return acl; } + +/** + * richacl_equiv_mode - check if @acl is equivalent to file permission bits + * @mode_p: the file mode (including the file type) + * + * If @acl can be fully represented by file permission bits, this function + * returns 0, and the file permission bits in @mode_p are set to the equivalent + * of @acl. + * + * This function is used to avoid storing richacls on disk if the acl can be + * computed from the file permission bits. It allows user-space to make sure + * that a file has no explicit richacl set. + */ +int +richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p) +{ + const struct richace *ace = acl->a_entries; + unsigned int x; + mode_t mode; + + if (acl->a_count != 1 || + acl->a_flags != ACL4_MASKED || + !richace_is_everyone(ace) || + !richace_is_allow(ace) || + ace->e_flags & ~ACE4_SPECIAL_WHO) + return -1; + + /* + * Figure out the permissions we care about: ACE4_DELETE_CHILD is + * meaningless for non-directories, so we ignore it. + */ + x = ~ACE4_POSIX_ALWAYS_ALLOWED; + if (!S_ISDIR(*mode_p)) + x &= ~ACE4_DELETE_CHILD; + + mode = richacl_masks_to_mode(acl); + if ((acl->a_group_mask & x) != (richacl_mode_to_mask(mode >> 3) & x) || + (acl->a_other_mask & x) != (richacl_mode_to_mask(mode) & x)) + return -1; + + /* + * Ignore permissions which the owner is always allowed. + */ + x &= ~ACE4_POSIX_OWNER_ALLOWED; + if ((acl->a_owner_mask & x) != (richacl_mode_to_mask(mode >> 6) & x)) + return -1; + + if ((ace->e_mask & x) != (ACE4_POSIX_MODE_ALL & x)) + return -1; + + *mode_p = (*mode_p & ~S_IRWXUGO) | mode; + return 0; +} +EXPORT_SYMBOL_GPL(richacl_equiv_mode); diff --git a/include/linux/richacl.h b/include/linux/richacl.h index 47855ec8db4dc6..7ac6587cb166a0 100644 --- a/include/linux/richacl.h +++ b/include/linux/richacl.h @@ -285,6 +285,7 @@ extern struct richacl *richacl_chmod(struct richacl *, mode_t); extern int richacl_permission(struct inode *, const struct richacl *, unsigned int); extern struct richacl *richacl_inherit(const struct richacl *, int); +extern int richacl_equiv_mode(const struct richacl *, mode_t *); /* richacl_inode.c */ extern struct richacl *richacl_inherit_inode(const struct richacl *, From 5882a819b187d5267c5981a6789c992519d21261 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:54 +0530 Subject: [PATCH 20/27] richacl: Automatic Inheritance Automatic Inheritance (AI) allows changes to the acl of a directory to recursively propagate down to files and directories in the directory. To implement this, the kernel keeps track of which permissions have been inherited, and makes sure that permission propagation is turned off when the file permission bits of a file are changed (upon create or chmod). The actual permission propagation is implemented in user space. AI works as follows: - When the ACL4_AUTO_INHERIT flag in the acl of a file is cleared, the file is not affected by AI. - When the ACL4_AUTO_INHERIT flag in the acl of a directory is set and a file or subdirectory is created in that directory, files created in the directory will have the ACL4_AUTO_INHERIT flag set, and all inherited aces will have the ACE4_INHERITED_ACE flag set. This allows user space to distinguish between aces which have been inherited, and aces which have been explicitly added. - When the ACL4_PROTECTED acl flag in the acl of a file is set, AI will not modify the acl of the file. This does not affect propagation of permissions from the file to its children (if the file is a directory). Linux does not have a way of creating files without setting the file permission bits, so all files created inside a directory with ACL4_AUTO_INHERIT set will also have the ACL4_PROTECTED flag set. This effectively disables AI. Protocols which support creating files without specifying permissions can explicitly clear the ACL4_PROTECTED flag after creating a file (and reset the file masks to "undo" applying the create mode; see richacl_compute_max_masks()). This is a workaround; a per-create or per-process flag indicating to ignore the create mode when AI is in effect would fix this problem. Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- fs/richacl_base.c | 10 +++++++++- fs/richacl_inode.c | 7 ++++++- include/linux/richacl.h | 23 ++++++++++++++++++++++- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/fs/richacl_base.c b/fs/richacl_base.c index ae11fac4c7de2e..ba9d3855cb8060 100644 --- a/fs/richacl_base.c +++ b/fs/richacl_base.c @@ -336,7 +336,8 @@ richacl_chmod(struct richacl *acl, mode_t mode) if (acl->a_owner_mask == owner_mask && acl->a_group_mask == group_mask && acl->a_other_mask == other_mask && - (acl->a_flags & ACL4_MASKED)) + (acl->a_flags & ACL4_MASKED) && + (!richacl_is_auto_inherit(acl) || richacl_is_protected(acl))) return acl; clone = richacl_clone(acl); @@ -348,6 +349,8 @@ richacl_chmod(struct richacl *acl, mode_t mode) clone->a_owner_mask = owner_mask; clone->a_group_mask = group_mask; clone->a_other_mask = other_mask; + if (richacl_is_auto_inherit(clone)) + clone->a_flags |= ACL4_PROTECTED; return clone; } @@ -517,6 +520,11 @@ richacl_inherit(const struct richacl *dir_acl, int isdir) ace++; } } + if (richacl_is_auto_inherit(dir_acl)) { + acl->a_flags = ACL4_AUTO_INHERIT; + richacl_for_each_entry(ace, acl) + ace->e_flags |= ACE4_INHERITED_ACE; + } return acl; } diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c index f590fb52681264..c1297ad909dc46 100644 --- a/fs/richacl_inode.c +++ b/fs/richacl_inode.c @@ -37,9 +37,14 @@ richacl_inherit_inode(const struct richacl *dir_acl, struct inode *inode) acl = richacl_inherit(dir_acl, S_ISDIR(inode->i_mode)); if (acl) { + /* + * We need to set ACL4_PROTECTED because we are + * doing an implicit chmod + */ + if (richacl_is_auto_inherit(acl)) + acl->a_flags |= ACL4_PROTECTED; richacl_compute_max_masks(acl); - /* * Ensure that the acl will not grant any permissions beyond * the create mode. diff --git a/include/linux/richacl.h b/include/linux/richacl.h index 7ac6587cb166a0..88f95d78b897dd 100644 --- a/include/linux/richacl.h +++ b/include/linux/richacl.h @@ -48,10 +48,16 @@ struct richacl { _ace != _acl->a_entries - 1; \ _ace--) +/* a_flags values */ +#define ACL4_AUTO_INHERIT 0x01 +#define ACL4_PROTECTED 0x02 +/* #define ACL4_DEFAULTED 0x04 */ /* Flag values defined by rich-acl */ #define ACL4_MASKED 0x80 #define ACL4_VALID_FLAGS ( \ + ACL4_AUTO_INHERIT | \ + ACL4_PROTECTED | \ ACL4_MASKED) /* e_type values */ @@ -68,6 +74,7 @@ struct richacl { /*#define ACE4_SUCCESSFUL_ACCESS_ACE_FLAG 0x0010*/ /*#define ACE4_FAILED_ACCESS_ACE_FLAG 0x0020*/ #define ACE4_IDENTIFIER_GROUP 0x0040 +#define ACE4_INHERITED_ACE 0x0080 /* richacl specific flag values */ #define ACE4_SPECIAL_WHO 0x4000 @@ -77,6 +84,7 @@ struct richacl { ACE4_NO_PROPAGATE_INHERIT_ACE | \ ACE4_INHERIT_ONLY_ACE | \ ACE4_IDENTIFIER_GROUP | \ + ACE4_INHERITED_ACE | \ ACE4_SPECIAL_WHO) /* e_mask bitflags */ @@ -183,6 +191,18 @@ richacl_put(struct richacl *acl) kfree(acl); } +static inline int +richacl_is_auto_inherit(const struct richacl *acl) +{ + return acl->a_flags & ACL4_AUTO_INHERIT; +} + +static inline int +richacl_is_protected(const struct richacl *acl) +{ + return acl->a_flags & ACL4_PROTECTED; +} + /** * richace_is_owner - check if @ace is an OWNER@ entry */ @@ -253,7 +273,8 @@ richace_clear_inheritance_flags(struct richace *ace) ace->e_flags &= ~(ACE4_FILE_INHERIT_ACE | ACE4_DIRECTORY_INHERIT_ACE | ACE4_NO_PROPAGATE_INHERIT_ACE | - ACE4_INHERIT_ONLY_ACE); + ACE4_INHERIT_ONLY_ACE | + ACE4_INHERITED_ACE); } /** From 862d8614cae82affd7b4c8c1f280ffd40d3259a8 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:54 +0530 Subject: [PATCH 21/27] richacl: xattr mapping functions Map between "system.richacl" xattrs and the in-kernel representation. Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- fs/Makefile | 2 +- fs/richacl_xattr.c | 130 ++++++++++++++++++++++++++++++++++ include/linux/richacl_xattr.h | 46 ++++++++++++ 3 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 fs/richacl_xattr.c create mode 100644 include/linux/richacl_xattr.h diff --git a/fs/Makefile b/fs/Makefile index 1ecf9f216b7098..e217c65755e2df 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -49,7 +49,7 @@ obj-$(CONFIG_GENERIC_ACL) += generic_acl.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 00000000000000..e7a25e1732753b --- /dev/null +++ b/fs/richacl_xattr.c @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2006, 2010 Novell, Inc. + * Written by Andreas Gruenbacher + * + * 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 +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); + +/** + * richacl_from_xattr - convert a richacl xattr into the in-memory representation + */ +struct richacl * +richacl_from_xattr(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(struct richacl_xattr) || + xattr_acl->a_version != ACL4_XATTR_VERSION || + (xattr_acl->a_flags & ~ACL4_VALID_FLAGS)) + return ERR_PTR(-EINVAL); + + count = le16_to_cpu(xattr_acl->a_count); + if (count > ACL4_XATTR_MAX_COUNT) + return ERR_PTR(-EINVAL); + + acl = richacl_alloc(count); + 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 & ~ACE4_VALID_MASK) + goto fail_einval; + acl->a_group_mask = le32_to_cpu(xattr_acl->a_group_mask); + if (acl->a_group_mask & ~ACE4_VALID_MASK) + goto fail_einval; + acl->a_other_mask = le32_to_cpu(xattr_acl->a_other_mask); + if (acl->a_other_mask & ~ACE4_VALID_MASK) + goto fail_einval; + + if (((void *)xattr_ace + count * sizeof(*xattr_ace)) > (value + size)) + 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); + ace->e_id = le32_to_cpu(xattr_ace->e_id); + + if (ace->e_flags & ~ACE4_VALID_FLAGS) + goto fail_einval; + if (ace->e_type > ACE4_ACCESS_DENIED_ACE_TYPE || + (ace->e_mask & ~ACE4_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 of size richacl_xattr_size(@acl) for the result + */ +void +richacl_to_xattr(const struct richacl *acl, void *buffer) +{ + struct richacl_xattr *xattr_acl = buffer; + struct richace_xattr *xattr_ace; + const struct richace *ace; + + xattr_acl->a_version = ACL4_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 & + ACE4_VALID_FLAGS); + xattr_ace->e_mask = cpu_to_le32(ace->e_mask); + xattr_ace->e_id = cpu_to_le32(ace->e_id); + xattr_ace++; + } +} +EXPORT_SYMBOL_GPL(richacl_to_xattr); diff --git a/include/linux/richacl_xattr.h b/include/linux/richacl_xattr.h new file mode 100644 index 00000000000000..792abccce47bbe --- /dev/null +++ b/include/linux/richacl_xattr.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2006, 2010 Novell, Inc. + * Written by Andreas Gruenbacher + * + * 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 + +#define RICHACL_XATTR "system.richacl" + +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 ACL4_XATTR_VERSION 0 +#define ACL4_XATTR_MAX_COUNT 1024 + +extern struct richacl *richacl_from_xattr(const void *, size_t); +extern size_t richacl_xattr_size(const struct richacl *acl); +extern void richacl_to_xattr(const struct richacl *, void *); + +#endif /* __RICHACL_XATTR_H */ From 69d9df5683dcdd2473a29ed4498dfc09f4e94e01 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 18 Oct 2011 15:17:55 +0530 Subject: [PATCH 22/27] vfs: Cache richacl in struct inode Cache richacls in struct inode so that this doesn't have to be done individually in each filesystem. Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Andreas Gruenbacher Signed-off-by: Aneesh Kumar K.V --- fs/inode.c | 25 +++++++++++++++---- include/linux/fs.h | 12 ++++++++-- include/linux/richacl.h | 53 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 7 deletions(-) diff --git a/fs/inode.c b/fs/inode.c index ec7924696a1398..1b442cf0735eac 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -26,6 +26,7 @@ #include #include #include /* for inode_has_buffers */ +#include #include "internal.h" /* @@ -192,7 +193,12 @@ int inode_init_always(struct super_block *sb, struct inode *inode) inode->i_private = NULL; inode->i_mapping = mapping; #ifdef CONFIG_FS_POSIX_ACL - inode->i_acl = inode->i_default_acl = ACL_NOT_CACHED; + if (IS_POSIXACL(inode)) + inode->i_acl = inode->i_default_acl = ACL_NOT_CACHED; +#endif +#ifdef CONFIG_FS_RICHACL + if (IS_RICHACL(inode)) + inode->i_richacl = ACL_NOT_CACHED; #endif #ifdef CONFIG_FSNOTIFY @@ -242,10 +248,19 @@ void __destroy_inode(struct inode *inode) security_inode_free(inode); fsnotify_inode_delete(inode); #ifdef CONFIG_FS_POSIX_ACL - if (inode->i_acl && inode->i_acl != ACL_NOT_CACHED) - posix_acl_release(inode->i_acl); - if (inode->i_default_acl && inode->i_default_acl != ACL_NOT_CACHED) - posix_acl_release(inode->i_default_acl); + if (IS_POSIXACL(inode)) { + if (inode->i_acl && inode->i_acl != ACL_NOT_CACHED) + posix_acl_release(inode->i_acl); + if (inode->i_default_acl && + inode->i_default_acl != ACL_NOT_CACHED) + posix_acl_release(inode->i_default_acl); + } +#endif +#ifdef CONFIG_FS_RICHACL + if (IS_RICHACL(inode)) { + if (inode->i_richacl && inode->i_richacl != ACL_NOT_CACHED) + richacl_put(inode->i_richacl); + } #endif this_cpu_dec(nr_inodes); } diff --git a/include/linux/fs.h b/include/linux/fs.h index ac1d8e5a325c83..771955cc67dfce 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -755,6 +755,7 @@ static inline int mapping_writably_mapped(struct address_space *mapping) #endif struct posix_acl; +struct richacl; #define ACL_NOT_CACHED ((void *)(-1)) #define IOP_FASTPERM 0x0001 @@ -773,10 +774,17 @@ struct inode { gid_t i_gid; unsigned int i_flags; + union { #ifdef CONFIG_FS_POSIX_ACL - struct posix_acl *i_acl; - struct posix_acl *i_default_acl; + struct { + struct posix_acl *i_acl; + struct posix_acl *i_default_acl; + }; #endif +#ifdef CONFIG_FS_RICHACL + struct richacl *i_richacl; +#endif + }; const struct inode_operations *i_op; struct super_block *i_sb; diff --git a/include/linux/richacl.h b/include/linux/richacl.h index 88f95d78b897dd..a7db341e4ee973 100644 --- a/include/linux/richacl.h +++ b/include/linux/richacl.h @@ -191,6 +191,59 @@ richacl_put(struct richacl *acl) kfree(acl); } +#ifdef CONFIG_FS_RICHACL +static inline struct richacl *get_cached_richacl(struct inode *inode) +{ + struct richacl **p, *acl; + + p = &inode->i_richacl; + acl = ACCESS_ONCE(*p); + if (acl) { + spin_lock(&inode->i_lock); + acl = *p; + if (acl != ACL_NOT_CACHED) + acl = richacl_get(acl); + spin_unlock(&inode->i_lock); + } + return acl; +} + +static inline void set_cached_richacl(struct inode *inode, + struct richacl *acl) +{ + struct richacl *old = NULL; + spin_lock(&inode->i_lock); + old = inode->i_richacl; + inode->i_richacl = richacl_get(acl); + spin_unlock(&inode->i_lock); + if (old != ACL_NOT_CACHED) + richacl_put(old); +} + +static inline void forget_cached_richacl(struct inode *inode) +{ + struct richacl *old = NULL; + spin_lock(&inode->i_lock); + old = inode->i_richacl; + inode->i_richacl = ACL_NOT_CACHED; + spin_unlock(&inode->i_lock); + if (old != ACL_NOT_CACHED) + richacl_put(old); +} + +static inline int negative_cached_richacl(struct inode *inode) +{ + struct richacl **p, *acl; + + p = &inode->i_richacl; + acl = ACCESS_ONCE(*p); + if (acl) + return 0; + return 1; +} + +#endif + static inline int richacl_is_auto_inherit(const struct richacl *acl) { From e86e410588d7b45d3f5386f41cf3cf059503ffa5 Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V" Date: Tue, 18 Oct 2011 15:17:55 +0530 Subject: [PATCH 23/27] vfs: Add richacl permission check Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Aneesh Kumar K.V --- fs/attr.c | 6 +++-- fs/namei.c | 13 +++++++++- fs/richacl_base.c | 54 +++++++++++++++++++++++++++++++++++++++++ include/linux/fs.h | 2 +- include/linux/richacl.h | 2 ++ 5 files changed, 73 insertions(+), 4 deletions(-) diff --git a/fs/attr.c b/fs/attr.c index 00578b95499901..2b445bac3fd020 100644 --- a/fs/attr.c +++ b/fs/attr.c @@ -13,6 +13,7 @@ #include #include #include +#include static int richacl_change_ok(struct inode *inode, int mask) { @@ -21,8 +22,9 @@ static int richacl_change_ok(struct inode *inode, int mask) if (inode->i_op->permission) return inode->i_op->permission(inode, mask); - - return check_acl(inode, mask); + if (inode->i_op->get_richacl) + return check_richacl(inode, mask); + return -EPERM; } static bool inode_uid_change_ok(struct inode *inode, uid_t ia_uid) diff --git a/fs/namei.c b/fs/namei.c index 2685e04cfbb69c..30cba243cea0fc 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include "internal.h" @@ -174,7 +175,7 @@ void putname(const char *name) EXPORT_SYMBOL(putname); #endif -int check_acl(struct inode *inode, int mask) +static int check_posix_acl(struct inode *inode, int mask) { #ifdef CONFIG_FS_POSIX_ACL struct posix_acl *acl; @@ -220,6 +221,16 @@ int check_acl(struct inode *inode, int mask) return -EAGAIN; } +static int check_acl(struct inode *inode, int mask) +{ + if (IS_POSIXACL(inode)) + return check_posix_acl(inode, mask); + else if (IS_RICHACL(inode)) + return check_richacl(inode, mask); + else + return -EAGAIN; +} + /* * This does the basic permission checking */ diff --git a/fs/richacl_base.c b/fs/richacl_base.c index ba9d3855cb8060..fcc37d6a7eda2b 100644 --- a/fs/richacl_base.c +++ b/fs/richacl_base.c @@ -582,3 +582,57 @@ richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p) return 0; } EXPORT_SYMBOL_GPL(richacl_equiv_mode); + +int check_richacl(struct inode *inode, int want) +{ +#ifdef CONFIG_FS_RICHACL + struct richacl *acl; + int richacl_mask = richacl_want_to_mask(want); + + if (want & MAY_NOT_BLOCK) { + acl = rcu_dereference(inode->i_richacl); + if (!acl) + return -EAGAIN; + /* no ->get_acl() calls in RCU mode... */ + if (acl == ACL_NOT_CACHED) + return -ECHILD; + return richacl_permission(inode, acl, richacl_mask); + } + return richacl_check_acl(inode, richacl_mask); +#endif + return -EAGAIN; +} + +int richacl_check_acl(struct inode *inode, int richacl_mask) +{ + +#ifdef CONFIG_FS_RICHACL + struct richacl *acl; + acl = get_cached_richacl(inode); + /* + * 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_acl call. + * + * If the filesystem doesn't have a get_acl() function at all, we'll + * just create the negative cache entry. + */ + if (acl == ACL_NOT_CACHED) { + if (inode->i_op->get_acl) { + acl = inode->i_op->get_richacl(inode); + if (IS_ERR(acl)) + return PTR_ERR(acl); + } else { + set_cached_richacl(inode, NULL); + return -EAGAIN; + } + } + if (acl) { + int error = richacl_permission(inode, acl, richacl_mask); + richacl_put(acl); + return error; + } +#endif + return -EAGAIN; +} +EXPORT_SYMBOL_GPL(richacl_check_acl); diff --git a/include/linux/fs.h b/include/linux/fs.h index 771955cc67dfce..e01bad7020e626 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1624,6 +1624,7 @@ struct inode_operations { void * (*follow_link) (struct dentry *, struct nameidata *); 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 dentry *, struct nameidata *, void *); @@ -2243,7 +2244,6 @@ extern sector_t bmap(struct inode *, sector_t); extern int notify_change(struct dentry *, struct iattr *); extern int inode_permission(struct inode *, int); extern int generic_permission(struct inode *, int); -extern int check_acl(struct inode *, int); static inline bool execute_ok(struct inode *inode) { diff --git a/include/linux/richacl.h b/include/linux/richacl.h index a7db341e4ee973..17b58d470798d1 100644 --- a/include/linux/richacl.h +++ b/include/linux/richacl.h @@ -360,6 +360,8 @@ extern int richacl_permission(struct inode *, const struct richacl *, unsigned int); extern struct richacl *richacl_inherit(const struct richacl *, int); extern int richacl_equiv_mode(const struct richacl *, mode_t *); +extern int check_richacl(struct inode *, int); +extern int richacl_check_acl(struct inode *, int); /* richacl_inode.c */ extern struct richacl *richacl_inherit_inode(const struct richacl *, From 434e3989f247743770312bf83a0efee08f015725 Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V" Date: Tue, 18 Oct 2011 15:17:56 +0530 Subject: [PATCH 24/27] ext4: Use IS_POSIXACL() to check for POSIX ACL support Use IS_POSIXACL() instead of a file system specific mount flag since we have IS_POSIXACL() in the vfs already, anyway. Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Aneesh Kumar K.V Signed-off-by: Andreas Gruenbacher --- fs/ext4/acl.c | 16 ++++++++-------- fs/ext4/ext4.h | 1 - fs/ext4/super.c | 16 +++++----------- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/fs/ext4/acl.c b/fs/ext4/acl.c index a5c29bb3b835d7..525bbc3cc4875b 100644 --- a/fs/ext4/acl.c +++ b/fs/ext4/acl.c @@ -139,7 +139,7 @@ ext4_get_acl(struct inode *inode, int type) struct posix_acl *acl; int retval; - if (!test_opt(inode->i_sb, POSIX_ACL)) + if (!IS_POSIXACL(inode)) return NULL; acl = get_cached_acl(inode, type); @@ -248,7 +248,7 @@ ext4_init_acl(handle_t *handle, struct inode *inode, struct inode *dir) int error = 0; if (!S_ISLNK(inode->i_mode)) { - if (test_opt(dir->i_sb, POSIX_ACL)) { + if (IS_POSIXACL(inode)) { acl = ext4_get_acl(dir, ACL_TYPE_DEFAULT); if (IS_ERR(acl)) return PTR_ERR(acl); @@ -256,7 +256,7 @@ ext4_init_acl(handle_t *handle, struct inode *inode, struct inode *dir) if (!acl) inode->i_mode &= ~current_umask(); } - if (test_opt(inode->i_sb, POSIX_ACL) && acl) { + if (IS_POSIXACL(inode) && acl) { if (S_ISDIR(inode->i_mode)) { error = ext4_set_acl(handle, inode, ACL_TYPE_DEFAULT, acl); @@ -302,7 +302,7 @@ ext4_acl_chmod(struct inode *inode) if (S_ISLNK(inode->i_mode)) return -EOPNOTSUPP; - if (!test_opt(inode->i_sb, POSIX_ACL)) + if (!IS_POSIXACL(inode)) return 0; acl = ext4_get_acl(inode, ACL_TYPE_ACCESS); if (IS_ERR(acl) || !acl) @@ -337,7 +337,7 @@ ext4_xattr_list_acl_access(struct dentry *dentry, char *list, size_t list_len, { const size_t size = sizeof(POSIX_ACL_XATTR_ACCESS); - if (!test_opt(dentry->d_sb, POSIX_ACL)) + if (!IS_POSIXACL(dentry->d_inode)) return 0; if (list && size <= list_len) memcpy(list, POSIX_ACL_XATTR_ACCESS, size); @@ -350,7 +350,7 @@ ext4_xattr_list_acl_default(struct dentry *dentry, char *list, size_t list_len, { const size_t size = sizeof(POSIX_ACL_XATTR_DEFAULT); - if (!test_opt(dentry->d_sb, POSIX_ACL)) + if (!IS_POSIXACL(dentry->d_inode)) return 0; if (list && size <= list_len) memcpy(list, POSIX_ACL_XATTR_DEFAULT, size); @@ -366,7 +366,7 @@ ext4_xattr_get_acl(struct dentry *dentry, const char *name, void *buffer, if (strcmp(name, "") != 0) return -EINVAL; - if (!test_opt(dentry->d_sb, POSIX_ACL)) + if (!IS_POSIXACL(dentry->d_inode)) return -EOPNOTSUPP; acl = ext4_get_acl(dentry->d_inode, type); @@ -391,7 +391,7 @@ ext4_xattr_set_acl(struct dentry *dentry, const char *name, const void *value, if (strcmp(name, "") != 0) return -EINVAL; - if (!test_opt(inode->i_sb, POSIX_ACL)) + if (!IS_POSIXACL(dentry->d_inode)) return -EOPNOTSUPP; if (!inode_owner_or_capable(inode)) return -EPERM; diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index b7d7bd0f066ef4..6627cc86db2750 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -901,7 +901,6 @@ 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_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 */ diff --git a/fs/ext4/super.c b/fs/ext4/super.c index 44d0c8db2239f9..99d72cf4e4e3aa 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c @@ -1066,9 +1066,9 @@ static int ext4_show_options(struct seq_file *seq, struct vfsmount *vfs) seq_puts(seq, ",nouser_xattr"); #endif #ifdef CONFIG_EXT4_FS_POSIX_ACL - if (test_opt(sb, POSIX_ACL) && !(def_mount_opts & EXT4_DEFM_ACL)) + if ((sb->s_flags & MS_POSIXACL) && !(def_mount_opts & EXT4_DEFM_ACL)) seq_puts(seq, ",acl"); - if (!test_opt(sb, POSIX_ACL) && (def_mount_opts & EXT4_DEFM_ACL)) + if (!(sb->s_flags & MS_POSIXACL) && (def_mount_opts & EXT4_DEFM_ACL)) seq_puts(seq, ",noacl"); #endif if (sbi->s_commit_interval != JBD2_DEFAULT_MAX_COMMIT_AGE*HZ) { @@ -1587,10 +1587,10 @@ static int parse_options(char *options, struct super_block *sb, #endif #ifdef CONFIG_EXT4_FS_POSIX_ACL case Opt_acl: - set_opt(sb, POSIX_ACL); + sb->s_flags |= MS_POSIXACL; break; case Opt_noacl: - clear_opt(sb, POSIX_ACL); + sb->s_flags &= ~MS_POSIXACL; break; #else case Opt_acl: @@ -3170,7 +3170,7 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent) set_opt(sb, XATTR_USER); #endif #ifdef CONFIG_EXT4_FS_POSIX_ACL - set_opt(sb, POSIX_ACL); + sb->s_flags |= MS_POSIXACL; #endif set_opt(sb, MBLK_IO_SUBMIT); if ((def_mount_opts & EXT4_DEFM_JMODE) == EXT4_DEFM_JMODE_DATA) @@ -3224,9 +3224,6 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent) &journal_ioprio, NULL, 0)) goto failed_mount; - sb->s_flags = (sb->s_flags & ~MS_POSIXACL) | - (test_opt(sb, POSIX_ACL) ? MS_POSIXACL : 0); - if (le32_to_cpu(es->s_rev_level) == EXT4_GOOD_OLD_REV && (EXT4_HAS_COMPAT_FEATURE(sb, ~0U) || EXT4_HAS_RO_COMPAT_FEATURE(sb, ~0U) || @@ -4351,9 +4348,6 @@ 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); - es = sbi->s_es; if (sbi->s_journal) { From 69b47fdc0c04637e9cef2a773a57777378dfadc2 Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V" Date: Tue, 18 Oct 2011 15:17:57 +0530 Subject: [PATCH 25/27] ext4: Implement rich acl for ext4 Support the richacl permission model in ext4. The richacls are stored in "system.richacl" xattrs.This need to be enabled by tune2fs or during mkfs.ext4 Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Aneesh Kumar K.V Signed-off-by: Andreas Gruenbacher --- fs/ext4/Kconfig | 15 +++ fs/ext4/Makefile | 1 + fs/ext4/acl.c | 9 +- fs/ext4/acl.h | 4 +- fs/ext4/file.c | 4 +- fs/ext4/ialloc.c | 7 +- fs/ext4/inode.c | 10 +- fs/ext4/namei.c | 7 +- fs/ext4/richacl.c | 227 ++++++++++++++++++++++++++++++++++++++++++++++ fs/ext4/richacl.h | 46 ++++++++++ fs/ext4/xattr.c | 6 ++ fs/ext4/xattr.h | 2 + 12 files changed, 324 insertions(+), 14 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 9ed1bb1f319f38..a22b8f1d41b520 100644 --- a/fs/ext4/Kconfig +++ b/fs/ext4/Kconfig @@ -83,3 +83,18 @@ config EXT4_DEBUG If you select Y here, then you will be able to turn on debugging with a command such as "echo 1 > /sys/kernel/debug/ext4/mballoc-debug" + +config EXT4_FS_RICHACL + bool "Ext4 Rich Access Control Lists (EXPERIMENTAL)" + depends on EXT4_FS_XATTR && EXPERIMENTAL + select FS_RICHACL + help + Rich ACLs are an implementation of NFSv4 ACLs, extended by file masks + to fit into the standard POSIX file permission model. They are + designed to work seamlessly locally as well as across the NFSv4 and + CIFS/SMB2 network file system protocols. + + To learn more about Rich ACL, visit + http://acl.bestbits.at/richacl/ + + If you don't know what Rich ACLs are, say N diff --git a/fs/ext4/Makefile b/fs/ext4/Makefile index 56fd8f865930e8..9cd271a2b123a3 100644 --- a/fs/ext4/Makefile +++ b/fs/ext4/Makefile @@ -12,3 +12,4 @@ ext4-y := balloc.o bitmap.o dir.o file.o fsync.o ialloc.o inode.o page-io.o \ ext4-$(CONFIG_EXT4_FS_XATTR) += xattr.o xattr_user.o xattr_trusted.o ext4-$(CONFIG_EXT4_FS_POSIX_ACL) += acl.o ext4-$(CONFIG_EXT4_FS_SECURITY) += xattr_security.o +ext4-$(CONFIG_EXT4_FS_RICHACL) += richacl.o diff --git a/fs/ext4/acl.c b/fs/ext4/acl.c index 525bbc3cc4875b..00e54b8b30a8e7 100644 --- a/fs/ext4/acl.c +++ b/fs/ext4/acl.c @@ -131,8 +131,7 @@ ext4_acl_to_disk(const struct posix_acl *acl, size_t *size) * * inode->i_mutex: don't care */ -struct posix_acl * -ext4_get_acl(struct inode *inode, int type) +struct posix_acl *ext4_get_posix_acl(struct inode *inode, int type) { int name_index; char *value = NULL; @@ -249,7 +248,7 @@ ext4_init_acl(handle_t *handle, struct inode *inode, struct inode *dir) if (!S_ISLNK(inode->i_mode)) { if (IS_POSIXACL(inode)) { - acl = ext4_get_acl(dir, ACL_TYPE_DEFAULT); + acl = ext4_get_posix_acl(dir, ACL_TYPE_DEFAULT); if (IS_ERR(acl)) return PTR_ERR(acl); } @@ -304,7 +303,7 @@ ext4_acl_chmod(struct inode *inode) return -EOPNOTSUPP; if (!IS_POSIXACL(inode)) return 0; - acl = ext4_get_acl(inode, ACL_TYPE_ACCESS); + acl = ext4_get_posix_acl(inode, ACL_TYPE_ACCESS); if (IS_ERR(acl) || !acl) return PTR_ERR(acl); error = posix_acl_chmod(&acl, GFP_KERNEL, inode->i_mode); @@ -369,7 +368,7 @@ ext4_xattr_get_acl(struct dentry *dentry, const char *name, void *buffer, if (!IS_POSIXACL(dentry->d_inode)) return -EOPNOTSUPP; - acl = ext4_get_acl(dentry->d_inode, type); + acl = ext4_get_posix_acl(dentry->d_inode, type); if (IS_ERR(acl)) return PTR_ERR(acl); if (acl == NULL) diff --git a/fs/ext4/acl.h b/fs/ext4/acl.h index 18cb39ed7c7bbb..ac2bad2f54b5d9 100644 --- a/fs/ext4/acl.h +++ b/fs/ext4/acl.h @@ -54,13 +54,13 @@ static inline int ext4_acl_count(size_t size) #ifdef CONFIG_EXT4_FS_POSIX_ACL /* acl.c */ -struct posix_acl *ext4_get_acl(struct inode *inode, int type); +struct posix_acl *ext4_get_posix_acl(struct inode *inode, int type); extern int ext4_acl_chmod(struct inode *); extern int ext4_init_acl(handle_t *, struct inode *, struct inode *); #else /* CONFIG_EXT4_FS_POSIX_ACL */ #include -#define ext4_get_acl NULL +#define ext4_get_posix_acl NULL static inline int ext4_acl_chmod(struct inode *inode) diff --git a/fs/ext4/file.c b/fs/ext4/file.c index e4095e988ebacb..2f515a294984bb 100644 --- a/fs/ext4/file.c +++ b/fs/ext4/file.c @@ -28,6 +28,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 @@ -301,7 +302,8 @@ const struct inode_operations ext4_file_inode_operations = { .listxattr = ext4_listxattr, .removexattr = generic_removexattr, #endif - .get_acl = ext4_get_acl, + .get_acl = ext4_get_posix_acl, + .get_richacl = ext4_get_richacl, .fiemap = ext4_fiemap, }; diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c index 9c63f273b55049..77ea40b9f707b6 100644 --- a/fs/ext4/ialloc.c +++ b/fs/ext4/ialloc.c @@ -28,6 +28,7 @@ #include "ext4_jbd2.h" #include "xattr.h" #include "acl.h" +#include "richacl.h" #include @@ -1039,7 +1040,11 @@ struct inode *ext4_new_inode(handle_t *handle, struct inode *dir, int mode, if (err) goto fail_drop; - err = ext4_init_acl(handle, inode, dir); + if (EXT4_IS_RICHACL(dir)) + err = ext4_init_richacl(handle, inode, dir); + else + err = ext4_init_acl(handle, inode, dir); + if (err) goto fail_free_drop; diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 986e2388f031dd..4b536e599752c7 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -44,6 +44,7 @@ #include "acl.h" #include "ext4_extents.h" #include "truncate.h" +#include "richacl.h" #include @@ -3945,9 +3946,12 @@ int ext4_setattr(struct dentry *dentry, struct iattr *attr) if (orphan && inode->i_nlink) ext4_orphan_del(NULL, inode); - if (!rc && (ia_valid & ATTR_MODE)) - rc = ext4_acl_chmod(inode); - + if (!rc && (ia_valid & ATTR_MODE)) { + if (EXT4_IS_RICHACL(inode)) + rc = ext4_richacl_chmod(inode); + else + rc = ext4_acl_chmod(inode); + } err_out: ext4_std_error(inode->i_sb, error); if (!error) diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 1c924faeb6c8ed..b03efb5de77a08 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -39,6 +39,7 @@ #include "xattr.h" #include "acl.h" +#include "richacl.h" #include /* @@ -2586,7 +2587,8 @@ const struct inode_operations ext4_dir_inode_operations = { .listxattr = ext4_listxattr, .removexattr = generic_removexattr, #endif - .get_acl = ext4_get_acl, + .get_acl = ext4_get_posix_acl, + .get_richacl = ext4_get_richacl, .fiemap = ext4_fiemap, }; @@ -2598,5 +2600,6 @@ const struct inode_operations ext4_special_inode_operations = { .listxattr = ext4_listxattr, .removexattr = generic_removexattr, #endif - .get_acl = ext4_get_acl, + .get_acl = ext4_get_posix_acl, + .get_richacl = ext4_get_richacl, }; diff --git a/fs/ext4/richacl.c b/fs/ext4/richacl.c new file mode 100644 index 00000000000000..a0f63f883f9c7f --- /dev/null +++ b/fs/ext4/richacl.c @@ -0,0 +1,227 @@ +/* + * Copyright IBM Corporation, 2010 + * Author Aneesh Kumar K.V + * + * 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 +#include +#include + +#include "ext4.h" +#include "ext4_jbd2.h" +#include "xattr.h" +#include "acl.h" +#include "richacl.h" + +struct richacl * +ext4_get_richacl(struct inode *inode) +{ + const int name_index = EXT4_XATTR_INDEX_RICHACL; + void *value = NULL; + struct richacl *acl; + int retval; + + if (!IS_RICHACL(inode)) + return ERR_PTR(-EOPNOTSUPP); + acl = get_cached_richacl(inode); + if (acl != ACL_NOT_CACHED) + return acl; + retval = ext4_xattr_get(inode, name_index, "", NULL, 0); + if (retval > 0) { + value = kmalloc(retval, GFP_KERNEL); + if (!value) + return ERR_PTR(-ENOMEM); + retval = ext4_xattr_get(inode, name_index, "", value, retval); + } + if (retval > 0) { + acl = richacl_from_xattr(value, retval); + if (acl == ERR_PTR(-EINVAL)) + acl = ERR_PTR(-EIO); + } else if (retval == -ENODATA || retval == -ENOSYS) + acl = NULL; + else + acl = ERR_PTR(retval); + kfree(value); + + if (!IS_ERR_OR_NULL(acl)) + set_cached_richacl(inode, acl); + + return acl; +} + +static int +ext4_set_richacl(handle_t *handle, struct inode *inode, struct richacl *acl) +{ + const int name_index = EXT4_XATTR_INDEX_RICHACL; + size_t size = 0; + void *value = NULL; + int retval; + + if (acl) { + mode_t mode = inode->i_mode; + if (richacl_equiv_mode(acl, &mode) == 0) { + inode->i_mode = mode; + ext4_mark_inode_dirty(handle, inode); + acl = NULL; + } + } + if (acl) { + size = richacl_xattr_size(acl); + value = kmalloc(size, GFP_KERNEL); + if (!value) + return -ENOMEM; + richacl_to_xattr(acl, value); + } + if (handle) + retval = ext4_xattr_set_handle(handle, inode, name_index, "", + value, size, 0); + else + retval = ext4_xattr_set(inode, name_index, "", value, size, 0); + kfree(value); + if (!retval) + set_cached_richacl(inode, acl); + + return retval; +} + +int +ext4_init_richacl(handle_t *handle, struct inode *inode, struct inode *dir) +{ + struct richacl *dir_acl = NULL; + + if (!S_ISLNK(inode->i_mode)) { + dir_acl = ext4_get_richacl(dir); + if (IS_ERR(dir_acl)) + return PTR_ERR(dir_acl); + } + if (dir_acl) { + struct richacl *acl; + int retval; + + acl = richacl_inherit_inode(dir_acl, inode); + richacl_put(dir_acl); + + retval = PTR_ERR(acl); + if (acl && !IS_ERR(acl)) { + retval = ext4_set_richacl(handle, inode, acl); + richacl_put(acl); + } + return retval; + } else { + inode->i_mode &= ~current_umask(); + return 0; + } +} + +int +ext4_richacl_chmod(struct inode *inode) +{ + struct richacl *acl; + int retval; + + if (S_ISLNK(inode->i_mode)) + return -EOPNOTSUPP; + acl = ext4_get_richacl(inode); + if (IS_ERR_OR_NULL(acl)) + return PTR_ERR(acl); + acl = richacl_chmod(acl, inode->i_mode); + if (IS_ERR(acl)) + return PTR_ERR(acl); + retval = ext4_set_richacl(NULL, inode, acl); + richacl_put(acl); + + return retval; +} + +static size_t +ext4_xattr_list_richacl(struct dentry *dentry, char *list, size_t list_len, + const char *name, size_t name_len, int type) +{ + const size_t size = sizeof(RICHACL_XATTR); + if (!IS_RICHACL(dentry->d_inode)) + return 0; + if (list && size <= list_len) + memcpy(list, RICHACL_XATTR, size); + return size; +} + +static int +ext4_xattr_get_richacl(struct dentry *dentry, const char *name, void *buffer, + size_t buffer_size, int type) +{ + struct richacl *acl; + size_t size; + + if (strcmp(name, "") != 0) + return -EINVAL; + acl = ext4_get_richacl(dentry->d_inode); + if (IS_ERR(acl)) + return PTR_ERR(acl); + if (acl == NULL) + return -ENODATA; + size = richacl_xattr_size(acl); + if (buffer) { + if (size > buffer_size) + return -ERANGE; + richacl_to_xattr(acl, buffer); + } + richacl_put(acl); + + return size; +} + +static int +ext4_xattr_set_richacl(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags, int type) +{ + handle_t *handle; + struct richacl *acl = NULL; + int retval, retries = 0; + struct inode *inode = dentry->d_inode; + + if (!IS_RICHACL(dentry->d_inode)) + return -EOPNOTSUPP; + if (S_ISLNK(inode->i_mode)) + return -EOPNOTSUPP; + if (strcmp(name, "") != 0) + return -EINVAL; + if (current_fsuid() != inode->i_uid && + richacl_check_acl(inode, ACE4_WRITE_ACL) && + !capable(CAP_FOWNER)) + return -EPERM; + if (value) { + acl = richacl_from_xattr(value, size); + if (IS_ERR(acl)) + return PTR_ERR(acl); + + inode->i_mode &= ~S_IRWXUGO; + inode->i_mode |= richacl_masks_to_mode(acl); + } + +retry: + handle = ext4_journal_start(inode, EXT4_DATA_TRANS_BLOCKS(inode->i_sb)); + if (IS_ERR(handle)) + return PTR_ERR(handle); + retval = ext4_set_richacl(handle, inode, acl); + ext4_journal_stop(handle); + if (retval == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries)) + goto retry; + richacl_put(acl); + return retval; +} + +const struct xattr_handler ext4_richacl_xattr_handler = { + .prefix = RICHACL_XATTR, + .list = ext4_xattr_list_richacl, + .get = ext4_xattr_get_richacl, + .set = ext4_xattr_set_richacl, +}; diff --git a/fs/ext4/richacl.h b/fs/ext4/richacl.h new file mode 100644 index 00000000000000..2577c34e27414e --- /dev/null +++ b/fs/ext4/richacl.h @@ -0,0 +1,46 @@ +/* + * Copyright IBM Corporation, 2010 + * Author Aneesh Kumar K.V + * + * 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 + +#ifdef CONFIG_EXT4_FS_RICHACL + +#define EXT4_IS_RICHACL(inode) IS_RICHACL(inode) + +extern struct richacl *ext4_get_richacl(struct inode *); +extern int ext4_init_richacl(handle_t *, struct inode *, struct inode *); +extern int ext4_richacl_chmod(struct inode *); + +#else /* CONFIG_FS_EXT4_RICHACL */ + +#define EXT4_IS_RICHACL(inode) (0) +#define ext4_get_richacl NULL + +static inline int +ext4_init_richacl(handle_t *handle, struct inode *inode, struct inode *dir) +{ + return 0; +} + +static inline int +ext4_richacl_chmod(struct inode *inode) +{ + return 0; +} + +#endif /* CONFIG_FS_EXT4_RICHACL */ +#endif /* __FS_EXT4_RICHACL_H */ diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c index c757adc972506d..9a00772969b331 100644 --- a/fs/ext4/xattr.c +++ b/fs/ext4/xattr.c @@ -107,6 +107,9 @@ static const struct xattr_handler *ext4_xattr_handler_map[] = { #ifdef CONFIG_EXT4_FS_SECURITY [EXT4_XATTR_INDEX_SECURITY] = &ext4_xattr_security_handler, #endif +#ifdef CONFIG_EXT4_FS_RICHACL + [EXT4_XATTR_INDEX_RICHACL] = &ext4_richacl_xattr_handler, +#endif }; const struct xattr_handler *ext4_xattr_handlers[] = { @@ -118,6 +121,9 @@ const struct xattr_handler *ext4_xattr_handlers[] = { #endif #ifdef CONFIG_EXT4_FS_SECURITY &ext4_xattr_security_handler, +#endif +#ifdef CONFIG_EXT4_FS_RICHACL + &ext4_richacl_xattr_handler, #endif NULL }; diff --git a/fs/ext4/xattr.h b/fs/ext4/xattr.h index 25b7387ff183f8..d5ad729001d599 100644 --- a/fs/ext4/xattr.h +++ b/fs/ext4/xattr.h @@ -21,6 +21,7 @@ #define EXT4_XATTR_INDEX_TRUSTED 4 #define EXT4_XATTR_INDEX_LUSTRE 5 #define EXT4_XATTR_INDEX_SECURITY 6 +#define EXT4_XATTR_INDEX_RICHACL 7 struct ext4_xattr_header { __le32 h_magic; /* magic number for identification */ @@ -70,6 +71,7 @@ extern const struct xattr_handler ext4_xattr_trusted_handler; extern const struct xattr_handler ext4_xattr_acl_access_handler; extern const struct xattr_handler ext4_xattr_acl_default_handler; extern const struct xattr_handler ext4_xattr_security_handler; +extern const struct xattr_handler ext4_richacl_xattr_handler; extern ssize_t ext4_listxattr(struct dentry *, char *, size_t); From e17ad316aa00e574aac19216eeb8ff5020112ab5 Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V" Date: Tue, 18 Oct 2011 15:20:16 +0530 Subject: [PATCH 26/27] ext4: Add Ext4 compat richacl feature flag This feature flag can be used to enable richacl on the file system. Once enabled the "acl" mount option will enable richacl instead of posix acl Acked-by: J. Bruce Fields Acked-by: David Howells Signed-off-by: Aneesh Kumar K.V --- fs/ext4/ext4.h | 1 + fs/ext4/super.c | 49 +++++++++++++++++++++++++++++++++++-------------- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index 6627cc86db2750..c71d9fe21b827f 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -1350,6 +1350,7 @@ static inline void ext4_clear_state_flags(struct ext4_inode_info *ei) #define EXT4_FEATURE_COMPAT_EXT_ATTR 0x0008 #define EXT4_FEATURE_COMPAT_RESIZE_INODE 0x0010 #define EXT4_FEATURE_COMPAT_DIR_INDEX 0x0020 +#define EXT4_FEATURE_COMPAT_RICHACL 0x0200 #define EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001 #define EXT4_FEATURE_RO_COMPAT_LARGE_FILE 0x0002 diff --git a/fs/ext4/super.c b/fs/ext4/super.c index 99d72cf4e4e3aa..4a3f0dd67afdf1 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c @@ -1065,10 +1065,12 @@ static int ext4_show_options(struct seq_file *seq, struct vfsmount *vfs) if (!test_opt(sb, XATTR_USER)) seq_puts(seq, ",nouser_xattr"); #endif -#ifdef CONFIG_EXT4_FS_POSIX_ACL - if ((sb->s_flags & MS_POSIXACL) && !(def_mount_opts & EXT4_DEFM_ACL)) +#if defined(CONFIG_EXT4_FS_POSIX_ACL) || defined(CONFIG_EXT4_FS_RICHACL) + if ((sb->s_flags & (MS_POSIXACL|MS_RICHACL)) && + !(def_mount_opts & EXT4_DEFM_ACL)) seq_puts(seq, ",acl"); - if (!(sb->s_flags & MS_POSIXACL) && (def_mount_opts & EXT4_DEFM_ACL)) + if (!(sb->s_flags & (MS_POSIXACL|MS_RICHACL)) && + (def_mount_opts & EXT4_DEFM_ACL)) seq_puts(seq, ",noacl"); #endif if (sbi->s_commit_interval != JBD2_DEFAULT_MAX_COMMIT_AGE*HZ) { @@ -1421,6 +1423,32 @@ static ext4_fsblk_t get_sb_block(void **data) return sb_block; } +static void enable_acl(struct super_block *sb) +{ +#if !defined(CONFIG_EXT4_FS_POSIX_ACL) && !defined(CONFIG_EXT4_FS_RICHACL) + ext4_msg(sb, KERN_ERR, "acl options not supported"); + return; +#endif + if (EXT4_HAS_COMPAT_FEATURE(sb, EXT4_FEATURE_COMPAT_RICHACL)) { + sb->s_flags |= MS_RICHACL; + sb->s_flags &= ~MS_POSIXACL; + } else { + sb->s_flags |= MS_POSIXACL; + sb->s_flags &= ~MS_RICHACL; + } + return; +} + +static void disable_acl(struct super_block *sb) +{ +#if !defined(CONFIG_EXT4_FS_POSIX_ACL) && !defined(CONFIG_EXT4_FS_RICHACL) + ext4_msg(sb, KERN_ERR, "acl options not supported"); + return; +#endif + sb->s_flags &= ~(MS_POSIXACL | MS_RICHACL); + return; +} + #define DEFAULT_JOURNAL_IOPRIO (IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 3)) static char deprecated_msg[] = "Mount option \"%s\" will be removed by %s\n" "Contact linux-ext4@vger.kernel.org if you think we should keep it.\n"; @@ -1585,19 +1613,12 @@ static int parse_options(char *options, struct super_block *sb, ext4_msg(sb, KERN_ERR, "(no)user_xattr options not supported"); break; #endif -#ifdef CONFIG_EXT4_FS_POSIX_ACL case Opt_acl: - sb->s_flags |= MS_POSIXACL; + enable_acl(sb); break; case Opt_noacl: - sb->s_flags &= ~MS_POSIXACL; + disable_acl(sb); break; -#else - case Opt_acl: - case Opt_noacl: - ext4_msg(sb, KERN_ERR, "(no)acl options not supported"); - break; -#endif case Opt_journal_update: /* @@@ FIXME */ /* Eventually we will want to be able to create @@ -3169,8 +3190,8 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent) #ifdef CONFIG_EXT4_FS_XATTR set_opt(sb, XATTR_USER); #endif -#ifdef CONFIG_EXT4_FS_POSIX_ACL - sb->s_flags |= MS_POSIXACL; +#if defined(CONFIG_EXT4_FS_POSIX_ACL) || defined(CONFIG_EXT4_FS_RICHACL) + enable_acl(sb); #endif set_opt(sb, MBLK_IO_SUBMIT); if ((def_mount_opts & EXT4_DEFM_JMODE) == EXT4_DEFM_JMODE_DATA) From 13fc2374a648a23303b24d1bf31ff4f71f8cdffe Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V" Date: Tue, 18 Oct 2011 15:21:49 +0530 Subject: [PATCH 27/27] ext4: Add temporary richacl mount option for ext4 This helps in easy testing of the patchset. The mount option will be later removed in favour of a feature flag. ***Should be folded before merging*** Signed-off-by: Aneesh Kumar K.V --- fs/ext4/ext4.h | 1 - fs/ext4/super.c | 46 ++++++++++++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index c71d9fe21b827f..6627cc86db2750 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -1350,7 +1350,6 @@ static inline void ext4_clear_state_flags(struct ext4_inode_info *ei) #define EXT4_FEATURE_COMPAT_EXT_ATTR 0x0008 #define EXT4_FEATURE_COMPAT_RESIZE_INODE 0x0010 #define EXT4_FEATURE_COMPAT_DIR_INDEX 0x0020 -#define EXT4_FEATURE_COMPAT_RICHACL 0x0200 #define EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001 #define EXT4_FEATURE_RO_COMPAT_LARGE_FILE 0x0002 diff --git a/fs/ext4/super.c b/fs/ext4/super.c index 4a3f0dd67afdf1..71241b0844b40d 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c @@ -1065,13 +1065,15 @@ static int ext4_show_options(struct seq_file *seq, struct vfsmount *vfs) if (!test_opt(sb, XATTR_USER)) seq_puts(seq, ",nouser_xattr"); #endif -#if defined(CONFIG_EXT4_FS_POSIX_ACL) || defined(CONFIG_EXT4_FS_RICHACL) - if ((sb->s_flags & (MS_POSIXACL|MS_RICHACL)) && - !(def_mount_opts & EXT4_DEFM_ACL)) +#ifdef CONFIG_EXT4_FS_POSIX_ACL + if ((sb->s_flags & MS_POSIXACL) && !(def_mount_opts & EXT4_DEFM_ACL)) seq_puts(seq, ",acl"); - if (!(sb->s_flags & (MS_POSIXACL|MS_RICHACL)) && - (def_mount_opts & EXT4_DEFM_ACL)) + if (!(sb->s_flags & MS_POSIXACL) && (def_mount_opts & EXT4_DEFM_ACL)) seq_puts(seq, ",noacl"); +#endif +#ifdef CONFIG_EXT4_FS_RICHACL + if (sb->s_flags & MS_RICHACL) + seq_puts(seq, ",richacl"); #endif if (sbi->s_commit_interval != JBD2_DEFAULT_MAX_COMMIT_AGE*HZ) { seq_printf(seq, ",commit=%u", @@ -1322,6 +1324,7 @@ enum { Opt_dioread_nolock, Opt_dioread_lock, Opt_discard, Opt_nodiscard, Opt_init_inode_table, Opt_noinit_inode_table, + Opt_richacl, }; static const match_table_t tokens = { @@ -1397,6 +1400,7 @@ static const match_table_t tokens = { {Opt_init_inode_table, "init_itable=%u"}, {Opt_init_inode_table, "init_itable"}, {Opt_noinit_inode_table, "noinit_itable"}, + {Opt_richacl, "richacl"}, {Opt_err, NULL}, }; @@ -1425,27 +1429,31 @@ static ext4_fsblk_t get_sb_block(void **data) static void enable_acl(struct super_block *sb) { -#if !defined(CONFIG_EXT4_FS_POSIX_ACL) && !defined(CONFIG_EXT4_FS_RICHACL) +#if !defined(CONFIG_EXT4_FS_POSIX_ACL) ext4_msg(sb, KERN_ERR, "acl options not supported"); return; #endif - if (EXT4_HAS_COMPAT_FEATURE(sb, EXT4_FEATURE_COMPAT_RICHACL)) { - sb->s_flags |= MS_RICHACL; - sb->s_flags &= ~MS_POSIXACL; - } else { - sb->s_flags |= MS_POSIXACL; - sb->s_flags &= ~MS_RICHACL; - } - return; + sb->s_flags |= MS_POSIXACL; } static void disable_acl(struct super_block *sb) { -#if !defined(CONFIG_EXT4_FS_POSIX_ACL) && !defined(CONFIG_EXT4_FS_RICHACL) +#if !defined(CONFIG_EXT4_FS_POSIX_ACL) ext4_msg(sb, KERN_ERR, "acl options not supported"); return; #endif - sb->s_flags &= ~(MS_POSIXACL | MS_RICHACL); + sb->s_flags &= ~MS_POSIXACL; + return; +} + +static void enable_richacl(struct super_block *sb) +{ +#if !defined(CONFIG_EXT4_FS_RICHACL) + ext4_msg(sb, KERN_ERR, "richacl options not supported"); + return; +#endif + sb->s_flags |= MS_RICHACL; + sb->s_flags &= ~MS_POSIXACL; return; } @@ -1905,6 +1913,8 @@ static int parse_options(char *options, struct super_block *sb, break; case Opt_noinit_inode_table: clear_opt(sb, INIT_INODE_TABLE); + case Opt_richacl: + enable_richacl(sb); break; default: ext4_msg(sb, KERN_ERR, @@ -3190,8 +3200,8 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent) #ifdef CONFIG_EXT4_FS_XATTR set_opt(sb, XATTR_USER); #endif -#if defined(CONFIG_EXT4_FS_POSIX_ACL) || defined(CONFIG_EXT4_FS_RICHACL) - enable_acl(sb); +#ifdef CONFIG_EXT4_FS_POSIX_ACL + enable_acl(sb); #endif set_opt(sb, MBLK_IO_SUBMIT); if ((def_mount_opts & EXT4_DEFM_JMODE) == EXT4_DEFM_JMODE_DATA)