From 42017c2e0ae391565af3c894020379194f668d54 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 18 Oct 2004 18:05:13 -0700 Subject: [PATCH] [PATCH] generic acl support for ->permission Currently we every filesystem with Posix ACLs has it's own reimplemtation of the generic permission checking code with additonal ACL support. This patch - adds an optional callback to vfs_permission that filesystems can use for ACL support (and renames it to generic_permission because the old name was wrong - it wasn't like the other vfs_* functions at all) - uses it in ext2, ext3 and jfs. XFS will follow a little later as it's permission checking is burried under several layers of abstraction. From: Dave Kleikamp jfs doesn't currently set MS_POSIXACL (it doesn't require the acl mount option), so this test would fail here. The patch below will set it. Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/cifs/CHANGES | 4 +- fs/cifs/cifsfs.c | 2 +- fs/exec.c | 2 +- fs/ext2/acl.c | 66 ++++++++------------------------- fs/ext3/acl.c | 66 ++++++++------------------------- fs/hfs/inode.c | 2 +- fs/hfsplus/inode.c | 2 +- fs/hostfs/hostfs_kern.c | 2 +- fs/jfs/acl.c | 81 +++++------------------------------------ fs/jfs/super.c | 4 ++ fs/namei.c | 33 ++++++++++++----- fs/nfs/dir.c | 2 +- fs/proc/base.c | 2 +- include/linux/fs.h | 4 +- 14 files changed, 79 insertions(+), 193 deletions(-) diff --git a/fs/cifs/CHANGES b/fs/cifs/CHANGES index 087be36211eb9..2a1924078059c 100644 --- a/fs/cifs/CHANGES +++ b/fs/cifs/CHANGES @@ -7,8 +7,8 @@ or out of order NULL pointer checks in little used error paths). Version 1.21 ------------ -Add new mount parm to control whether mode check (vfs_permission) is done on -the client. If Unix extensions are enabled and the uids on the client +Add new mount parm to control whether mode check (generic_permission) is done +on the client. If Unix extensions are enabled and the uids on the client and server do not match, client permission checks are meaningless on server uids that do not exist on the client (this does not affect the normal ACL check which occurs on the server). Fix default uid diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index 729cdba71530d..996fc298a3066 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -200,7 +200,7 @@ static int cifs_permission(struct inode * inode, int mask, struct nameidata *nd) on the client (above and beyond ACL on servers) for servers which do not support setting and viewing mode bits, so allowing client to check permissions is useful */ - return vfs_permission(inode, mask); + return generic_permission(inode, mask, NULL); } static kmem_cache_t *cifs_inode_cachep; diff --git a/fs/exec.c b/fs/exec.c index 4124b39bdd275..4915bffb045dd 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -886,7 +886,7 @@ int prepare_binprm(struct linux_binprm *bprm) mode = inode->i_mode; /* * Check execute perms again - if the caller has CAP_DAC_OVERRIDE, - * vfs_permission lets a non-executable through + * generic_permission lets a non-executable through */ if (!(mode & 0111)) /* with at least _one_ execute bit set */ return -EACCES; diff --git a/fs/ext2/acl.c b/fs/ext2/acl.c index 89d1df91411f6..0d961d5e00ede 100644 --- a/fs/ext2/acl.c +++ b/fs/ext2/acl.c @@ -280,60 +280,24 @@ ext2_set_acl(struct inode *inode, int type, struct posix_acl *acl) return error; } -/* - * Inode operation permission(). - * - * inode->i_sem: don't care - */ -int -ext2_permission(struct inode *inode, int mask, struct nameidata *nd) +static int +ext2_check_acl(struct inode *inode, int mask) { - int mode = inode->i_mode; - - /* Nobody gets write access to a read-only fs */ - if ((mask & MAY_WRITE) && IS_RDONLY(inode) && - (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) - return -EROFS; - /* Nobody gets write access to an immutable file */ - if ((mask & MAY_WRITE) && IS_IMMUTABLE(inode)) - return -EACCES; - if (current->fsuid == inode->i_uid) { - mode >>= 6; - } else if (test_opt(inode->i_sb, POSIX_ACL)) { - struct posix_acl *acl; - - /* The access ACL cannot grant access if the group class - permission bits don't contain all requested permissions. */ - if (((mode >> 3) & mask & S_IRWXO) != mask) - goto check_groups; - acl = ext2_get_acl(inode, ACL_TYPE_ACCESS); - if (acl) { - int error = posix_acl_permission(inode, acl, mask); - posix_acl_release(acl); - if (error == -EACCES) - goto check_capabilities; - return error; - } else - goto check_groups; - } else { -check_groups: - if (in_group_p(inode->i_gid)) - mode >>= 3; + struct posix_acl *acl = ext2_get_acl(inode, ACL_TYPE_ACCESS); + + if (acl) { + int error = posix_acl_permission(inode, acl, mask); + posix_acl_release(acl); + return error; } - if ((mode & mask & S_IRWXO) == mask) - return 0; -check_capabilities: - /* Allowed to override Discretionary Access Control? */ - if (!(mask & MAY_EXEC) || - (inode->i_mode & S_IXUGO) || S_ISDIR(inode->i_mode)) - if (capable(CAP_DAC_OVERRIDE)) - return 0; - /* Read and search granted if capable(CAP_DAC_READ_SEARCH) */ - if (capable(CAP_DAC_READ_SEARCH) && ((mask == MAY_READ) || - (S_ISDIR(inode->i_mode) && !(mask & MAY_WRITE)))) - return 0; - return -EACCES; + return -EAGAIN; +} + +int +ext2_permission(struct inode *inode, int mask, struct nameidata *nd) +{ + return generic_permission(inode, mask, ext2_check_acl); } /* diff --git a/fs/ext3/acl.c b/fs/ext3/acl.c index a3cf77de0e43b..a335fd1343526 100644 --- a/fs/ext3/acl.c +++ b/fs/ext3/acl.c @@ -285,60 +285,24 @@ ext3_set_acl(handle_t *handle, struct inode *inode, int type, return error; } -/* - * Inode operation permission(). - * - * inode->i_sem: don't care - */ -int -ext3_permission(struct inode *inode, int mask, struct nameidata *nd) +static int +ext3_check_acl(struct inode *inode, int mask) { - int mode = inode->i_mode; - - /* Nobody gets write access to a read-only fs */ - if ((mask & MAY_WRITE) && IS_RDONLY(inode) && - (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) - return -EROFS; - /* Nobody gets write access to an immutable file */ - if ((mask & MAY_WRITE) && IS_IMMUTABLE(inode)) - return -EACCES; - if (current->fsuid == inode->i_uid) { - mode >>= 6; - } else if (test_opt(inode->i_sb, POSIX_ACL)) { - struct posix_acl *acl; - - /* The access ACL cannot grant access if the group class - permission bits don't contain all requested permissions. */ - if (((mode >> 3) & mask & S_IRWXO) != mask) - goto check_groups; - acl = ext3_get_acl(inode, ACL_TYPE_ACCESS); - if (acl) { - int error = posix_acl_permission(inode, acl, mask); - posix_acl_release(acl); - if (error == -EACCES) - goto check_capabilities; - return error; - } else - goto check_groups; - } else { -check_groups: - if (in_group_p(inode->i_gid)) - mode >>= 3; + struct posix_acl *acl = ext3_get_acl(inode, ACL_TYPE_ACCESS); + + if (acl) { + int error = posix_acl_permission(inode, acl, mask); + posix_acl_release(acl); + return error; } - if ((mode & mask & S_IRWXO) == mask) - return 0; -check_capabilities: - /* Allowed to override Discretionary Access Control? */ - if (!(mask & MAY_EXEC) || - (inode->i_mode & S_IXUGO) || S_ISDIR(inode->i_mode)) - if (capable(CAP_DAC_OVERRIDE)) - return 0; - /* Read and search granted if capable(CAP_DAC_READ_SEARCH) */ - if (capable(CAP_DAC_READ_SEARCH) && ((mask == MAY_READ) || - (S_ISDIR(inode->i_mode) && !(mask & MAY_WRITE)))) - return 0; - return -EACCES; + return -EAGAIN; +} + +int +ext3_permission(struct inode *inode, int mask, struct nameidata *nd) +{ + return generic_permission(inode, mask, ext3_check_acl); } /* diff --git a/fs/hfs/inode.c b/fs/hfs/inode.c index 6c869f3779648..ae55ce1a54053 100644 --- a/fs/hfs/inode.c +++ b/fs/hfs/inode.c @@ -517,7 +517,7 @@ static int hfs_permission(struct inode *inode, int mask, { if (S_ISREG(inode->i_mode) && mask & MAY_EXEC) return 0; - return vfs_permission(inode, mask); + return generic_permission(inode, mask, NULL); } static int hfs_file_open(struct inode *inode, struct file *file) diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c index eff1c987b6fb0..f58025cb38af7 100644 --- a/fs/hfsplus/inode.c +++ b/fs/hfsplus/inode.c @@ -260,7 +260,7 @@ static int hfsplus_permission(struct inode *inode, int mask, struct nameidata *n */ if (S_ISREG(inode->i_mode) && mask & MAY_EXEC && !(inode->i_mode & 0111)) return 0; - return vfs_permission(inode, mask); + return generic_permission(inode, mask, NULL); } diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index 7d42da0a304d4..937fac26b34ae 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -808,7 +808,7 @@ int hostfs_permission(struct inode *ino, int desired, struct nameidata *nd) if(name == NULL) return(-ENOMEM); err = access_file(name, r, w, x); kfree(name); - if(!err) err = vfs_permission(ino, desired); + if(!err) err = generic_permission(ino, desired, NULL); return(err); } diff --git a/fs/jfs/acl.c b/fs/jfs/acl.c index fc584f2194f4f..8d2a9ab981d43 100644 --- a/fs/jfs/acl.c +++ b/fs/jfs/acl.c @@ -123,88 +123,25 @@ out: return rc; } -/* - * jfs_permission() - * - * modified vfs_permission to check posix acl - */ -int jfs_permission(struct inode * inode, int mask, struct nameidata *nd) +static int jfs_check_acl(struct inode *inode, int mask) { - umode_t mode = inode->i_mode; struct jfs_inode_info *ji = JFS_IP(inode); - if (mask & MAY_WRITE) { - /* - * Nobody gets write access to a read-only fs. - */ - if (IS_RDONLY(inode) && - (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) - return -EROFS; - - /* - * Nobody gets write access to an immutable file. - */ - if (IS_IMMUTABLE(inode)) - return -EACCES; - } - - if (current->fsuid == inode->i_uid) { - mode >>= 6; - goto check_mode; - } - /* - * ACL can't contain additional permissions if the ACL_MASK entry - * is zero. - */ - if (!(mode & S_IRWXG)) - goto check_groups; - if (ji->i_acl == JFS_ACL_NOT_CACHED) { - struct posix_acl *acl; - - acl = jfs_get_acl(inode, ACL_TYPE_ACCESS); - + struct posix_acl *acl = jfs_get_acl(inode, ACL_TYPE_ACCESS); if (IS_ERR(acl)) return PTR_ERR(acl); posix_acl_release(acl); } - if (ji->i_acl) { - int rc = posix_acl_permission(inode, ji->i_acl, mask); - if (rc == -EACCES) - goto check_capabilities; - return rc; - } - -check_groups: - if (in_group_p(inode->i_gid)) - mode >>= 3; - -check_mode: - /* - * If the DACs are ok we don't need any capability check. - */ - if (((mode & mask & (MAY_READ|MAY_WRITE|MAY_EXEC)) == mask)) - return 0; + if (ji->i_acl) + return posix_acl_permission(inode, ji->i_acl, mask); + return -EAGAIN; +} -check_capabilities: - /* - * Read/write DACs are always overridable. - * Executable DACs are overridable if at least one exec bit is set. - */ - if (!(mask & MAY_EXEC) || - (inode->i_mode & S_IXUGO) || S_ISDIR(inode->i_mode)) - if (capable(CAP_DAC_OVERRIDE)) - return 0; - - /* - * Searching includes executable on directories, else just read. - */ - if (mask == MAY_READ || (S_ISDIR(inode->i_mode) && !(mask & MAY_WRITE))) - if (capable(CAP_DAC_READ_SEARCH)) - return 0; - - return -EACCES; +int jfs_permission(struct inode *inode, int mask, struct nameidata *nd) +{ + return generic_permission(inode, mask, jfs_check_acl); } int jfs_init_acl(struct inode *inode, struct inode *dir) diff --git a/fs/jfs/super.c b/fs/jfs/super.c index 7c91ccfe382f4..ecf1bca730506 100644 --- a/fs/jfs/super.c +++ b/fs/jfs/super.c @@ -403,6 +403,10 @@ static int jfs_fill_super(struct super_block *sb, void *data, int silent) } sbi->flag = flag; +#ifdef CONFIG_JFS_POSIX_ACL + sb->s_flags |= MS_POSIXACL; +#endif + if (newLVSize) { printk(KERN_ERR "resize option for remount only\n"); return -EINVAL; diff --git a/fs/namei.c b/fs/namei.c index bf259a9c5b217..287339bb7b7c5 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -152,15 +152,19 @@ char * getname(const char __user * filename) return result; } -/* - * vfs_permission() +/** + * 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) + * @check_acl: optional callback to check for Posix ACLs * - * is used to check for read/write/execute permissions on a file. + * Used to check for read/write/execute permissions on a file. * We use "fsuid" for this, letting us set arbitrary permissions * for filesystem access without changing the "normal" uids which * are used for other things.. */ -int vfs_permission(struct inode * inode, int mask) +int generic_permission(struct inode *inode, int mask, + int (*check_acl)(struct inode *inode, int mask)) { umode_t mode = inode->i_mode; @@ -181,8 +185,18 @@ int vfs_permission(struct inode * inode, int mask) if (current->fsuid == inode->i_uid) mode >>= 6; - else if (in_group_p(inode->i_gid)) - mode >>= 3; + else { + if (IS_POSIXACL(inode) && (mode & S_IRWXG) && check_acl) { + int error = check_acl(inode, mask); + if (error == -EACCES) + goto check_capabilities; + else if (error != -EAGAIN) + return error; + } + + if (in_group_p(inode->i_gid)) + mode >>= 3; + } /* * If the DACs are ok we don't need any capability check. @@ -190,6 +204,7 @@ int vfs_permission(struct inode * inode, int mask) if (((mode & mask & (MAY_READ|MAY_WRITE|MAY_EXEC)) == mask)) return 0; + check_capabilities: /* * Read/write DACs are always overridable. * Executable DACs are overridable if at least one exec bit is set. @@ -220,7 +235,7 @@ int permission(struct inode * inode,int mask, struct nameidata *nd) if (inode->i_op && inode->i_op->permission) retval = inode->i_op->permission(inode, submask, nd); else - retval = vfs_permission(inode, submask); + retval = generic_permission(inode, submask, NULL); if (retval) return retval; @@ -315,7 +330,7 @@ static struct dentry * cached_lookup(struct dentry * parent, struct qstr * name, /* * Short-cut version of permission(), for calling by * path_walk(), when dcache lock is held. Combines parts - * of permission() and vfs_permission(), and tests ONLY for + * of permission() and generic_permission(), and tests ONLY for * MAY_EXEC permission. * * If appropriate, check DAC only. If not appropriate, or @@ -2456,7 +2471,7 @@ EXPORT_SYMBOL(vfs_follow_link); EXPORT_SYMBOL(vfs_link); EXPORT_SYMBOL(vfs_mkdir); EXPORT_SYMBOL(vfs_mknod); -EXPORT_SYMBOL(vfs_permission); +EXPORT_SYMBOL(generic_permission); EXPORT_SYMBOL(vfs_readlink); EXPORT_SYMBOL(vfs_rename); EXPORT_SYMBOL(vfs_rmdir); diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 626252aec95a3..eac30582ff9e9 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -1600,7 +1600,7 @@ int nfs_permission(struct inode *inode, int mask, struct nameidata *nd) return res; out_notsup: nfs_revalidate_inode(NFS_SERVER(inode), inode); - res = vfs_permission(inode, mask); + res = generic_permission(inode, mask, NULL); unlock_kernel(); return res; } diff --git a/fs/proc/base.c b/fs/proc/base.c index 987240398c4a8..91060b9921cca 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -473,7 +473,7 @@ out: static int proc_permission(struct inode *inode, int mask, struct nameidata *nd) { - if (vfs_permission(inode, mask) != 0) + if (generic_permission(inode, mask, NULL) != 0) return -EACCES; return proc_check_root(inode); } diff --git a/include/linux/fs.h b/include/linux/fs.h index dc457c3fede2f..13e1b4cb7c748 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1339,7 +1339,9 @@ extern sector_t bmap(struct inode *, sector_t); extern int setattr_mask(unsigned int); extern int notify_change(struct dentry *, struct iattr *); extern int permission(struct inode *, int, struct nameidata *); -extern int vfs_permission(struct inode *, int); +extern int generic_permission(struct inode *, int, + int (*check_acl)(struct inode *, int)); + extern int get_write_access(struct inode *); extern int deny_write_access(struct file *); static inline void put_write_access(struct inode * inode) -- 2.53.0