Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi...
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 5 Oct 2016 17:58:15 +0000 (10:58 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 5 Oct 2016 17:58:15 +0000 (10:58 -0700)
Pull fuse updates from Miklos Szeredi:
 "This adds POSIX ACL permission checking to the fuse kernel module.

  In addition there are minor bug fixes as well as cleanups"

* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse:
  fuse: limit xattr returned size
  fuse: remove duplicate cs->offset assignment
  fuse: don't use fuse_ioctl_copy_user() helper
  fuse_ioctl_copy_user(): don't open-code copy_page_{to,from}_iter()
  fuse: get rid of fc->flags
  fuse: use timespec64
  fuse: don't use ->d_time
  fuse: Add posix ACL support
  fuse: handle killpriv in userspace fs
  fuse: fix killing s[ug]id in setattr
  fuse: invalidate dir dentry after chmod
  fuse: Use generic xattr ops
  fuse: listxattr: verify xattr list

fs/fuse/Kconfig
fs/fuse/Makefile
fs/fuse/acl.c [new file with mode: 0644]
fs/fuse/dev.c
fs/fuse/dir.c
fs/fuse/file.c
fs/fuse/fuse_i.h
fs/fuse/inode.c
fs/fuse/xattr.c [new file with mode: 0644]
include/uapi/linux/fuse.h

index 1b2f6c2..76f09ce 100644 (file)
@@ -1,5 +1,6 @@
 config FUSE_FS
        tristate "FUSE (Filesystem in Userspace) support"
+       select FS_POSIX_ACL
        help
          With FUSE it is possible to implement a fully functional filesystem
          in a userspace program.
index e95eeb4..60da84a 100644 (file)
@@ -5,4 +5,4 @@
 obj-$(CONFIG_FUSE_FS) += fuse.o
 obj-$(CONFIG_CUSE) += cuse.o
 
-fuse-objs := dev.o dir.o file.o inode.o control.o
+fuse-objs := dev.o dir.o file.o inode.o control.o xattr.o acl.o
diff --git a/fs/fuse/acl.c b/fs/fuse/acl.c
new file mode 100644 (file)
index 0000000..ec85765
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2016 Canonical Ltd. <seth.forshee@canonical.com>
+ *
+ * This program can be distributed under the terms of the GNU GPL.
+ * See the file COPYING.
+ */
+
+#include "fuse_i.h"
+
+#include <linux/posix_acl.h>
+#include <linux/posix_acl_xattr.h>
+
+struct posix_acl *fuse_get_acl(struct inode *inode, int type)
+{
+       struct fuse_conn *fc = get_fuse_conn(inode);
+       int size;
+       const char *name;
+       void *value = NULL;
+       struct posix_acl *acl;
+
+       if (!fc->posix_acl || fc->no_getxattr)
+               return NULL;
+
+       if (type == ACL_TYPE_ACCESS)
+               name = XATTR_NAME_POSIX_ACL_ACCESS;
+       else if (type == ACL_TYPE_DEFAULT)
+               name = XATTR_NAME_POSIX_ACL_DEFAULT;
+       else
+               return ERR_PTR(-EOPNOTSUPP);
+
+       value = kmalloc(PAGE_SIZE, GFP_KERNEL);
+       if (!value)
+               return ERR_PTR(-ENOMEM);
+       size = fuse_getxattr(inode, name, value, PAGE_SIZE);
+       if (size > 0)
+               acl = posix_acl_from_xattr(&init_user_ns, value, size);
+       else if ((size == 0) || (size == -ENODATA) ||
+                (size == -EOPNOTSUPP && fc->no_getxattr))
+               acl = NULL;
+       else if (size == -ERANGE)
+               acl = ERR_PTR(-E2BIG);
+       else
+               acl = ERR_PTR(size);
+
+       kfree(value);
+       return acl;
+}
+
+int fuse_set_acl(struct inode *inode, struct posix_acl *acl, int type)
+{
+       struct fuse_conn *fc = get_fuse_conn(inode);
+       const char *name;
+       int ret;
+
+       if (!fc->posix_acl || fc->no_setxattr)
+               return -EOPNOTSUPP;
+
+       if (type == ACL_TYPE_ACCESS)
+               name = XATTR_NAME_POSIX_ACL_ACCESS;
+       else if (type == ACL_TYPE_DEFAULT)
+               name = XATTR_NAME_POSIX_ACL_DEFAULT;
+       else
+               return -EINVAL;
+
+       if (acl) {
+               /*
+                * Fuse userspace is responsible for updating access
+                * permissions in the inode, if needed. fuse_setxattr
+                * invalidates the inode attributes, which will force
+                * them to be refreshed the next time they are used,
+                * and it also updates i_ctime.
+                */
+               size_t size = posix_acl_xattr_size(acl->a_count);
+               void *value;
+
+               if (size > PAGE_SIZE)
+                       return -E2BIG;
+
+               value = kmalloc(size, GFP_KERNEL);
+               if (!value)
+                       return -ENOMEM;
+
+               ret = posix_acl_to_xattr(&init_user_ns, acl, value, size);
+               if (ret < 0) {
+                       kfree(value);
+                       return ret;
+               }
+
+               ret = fuse_setxattr(inode, name, value, size, 0);
+               kfree(value);
+       } else {
+               ret = fuse_removexattr(inode, name);
+       }
+       forget_all_cached_acls(inode);
+       fuse_invalidate_attr(inode);
+
+       return ret;
+}
index a94d2ed..c41bde2 100644 (file)
@@ -767,7 +767,6 @@ static int fuse_copy_fill(struct fuse_copy_state *cs)
                cs->len = err;
                cs->offset = off;
                cs->pg = page;
-               cs->offset = off;
                iov_iter_advance(cs->iter, err);
        }
 
index c47b778..f7c84ab 100644 (file)
@@ -13,6 +13,8 @@
 #include <linux/sched.h>
 #include <linux/namei.h>
 #include <linux/slab.h>
+#include <linux/xattr.h>
+#include <linux/posix_acl.h>
 
 static bool fuse_use_readdirplus(struct inode *dir, struct dir_context *ctx)
 {
@@ -37,47 +39,39 @@ static void fuse_advise_use_readdirplus(struct inode *dir)
        set_bit(FUSE_I_ADVISE_RDPLUS, &fi->state);
 }
 
-#if BITS_PER_LONG >= 64
+union fuse_dentry {
+       u64 time;
+       struct rcu_head rcu;
+};
+
 static inline void fuse_dentry_settime(struct dentry *entry, u64 time)
 {
-       entry->d_time = time;
+       ((union fuse_dentry *) entry->d_fsdata)->time = time;
 }
 
 static inline u64 fuse_dentry_time(struct dentry *entry)
 {
-       return entry->d_time;
-}
-#else
-/*
- * On 32 bit archs store the high 32 bits of time in d_fsdata
- */
-static void fuse_dentry_settime(struct dentry *entry, u64 time)
-{
-       entry->d_time = time;
-       entry->d_fsdata = (void *) (unsigned long) (time >> 32);
-}
-
-static u64 fuse_dentry_time(struct dentry *entry)
-{
-       return (u64) entry->d_time +
-               ((u64) (unsigned long) entry->d_fsdata << 32);
+       return ((union fuse_dentry *) entry->d_fsdata)->time;
 }
-#endif
 
 /*
  * FUSE caches dentries and attributes with separate timeout.  The
  * time in jiffies until the dentry/attributes are valid is stored in
- * dentry->d_time and fuse_inode->i_time respectively.
+ * dentry->d_fsdata and fuse_inode->i_time respectively.
  */
 
 /*
  * Calculate the time in jiffies until a dentry/attributes are valid
  */
-static u64 time_to_jiffies(unsigned long sec, unsigned long nsec)
+static u64 time_to_jiffies(u64 sec, u32 nsec)
 {
        if (sec || nsec) {
-               struct timespec ts = {sec, nsec};
-               return get_jiffies_64() + timespec_to_jiffies(&ts);
+               struct timespec64 ts = {
+                       sec,
+                       max_t(u32, nsec, NSEC_PER_SEC - 1)
+               };
+
+               return get_jiffies_64() + timespec64_to_jiffies(&ts);
        } else
                return 0;
 }
@@ -243,6 +237,7 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
                if (ret || (outarg.attr.mode ^ inode->i_mode) & S_IFMT)
                        goto invalid;
 
+               forget_all_cached_acls(inode);
                fuse_change_attributes(inode, &outarg.attr,
                                       entry_attr_timeout(&outarg),
                                       attr_version);
@@ -272,8 +267,23 @@ static int invalid_nodeid(u64 nodeid)
        return !nodeid || nodeid == FUSE_ROOT_ID;
 }
 
+static int fuse_dentry_init(struct dentry *dentry)
+{
+       dentry->d_fsdata = kzalloc(sizeof(union fuse_dentry), GFP_KERNEL);
+
+       return dentry->d_fsdata ? 0 : -ENOMEM;
+}
+static void fuse_dentry_release(struct dentry *dentry)
+{
+       union fuse_dentry *fd = dentry->d_fsdata;
+
+       kfree_rcu(fd, rcu);
+}
+
 const struct dentry_operations fuse_dentry_operations = {
        .d_revalidate   = fuse_dentry_revalidate,
+       .d_init         = fuse_dentry_init,
+       .d_release      = fuse_dentry_release,
 };
 
 int fuse_valid_type(int m)
@@ -634,7 +644,7 @@ static int fuse_symlink(struct inode *dir, struct dentry *entry,
        return create_new_entry(fc, &args, dir, entry, S_IFLNK);
 }
 
-static inline void fuse_update_ctime(struct inode *inode)
+void fuse_update_ctime(struct inode *inode)
 {
        if (!IS_NOCMTIME(inode)) {
                inode->i_ctime = current_fs_time(inode->i_sb);
@@ -917,6 +927,7 @@ int fuse_update_attributes(struct inode *inode, struct kstat *stat,
 
        if (time_before64(fi->i_time, get_jiffies_64())) {
                r = true;
+               forget_all_cached_acls(inode);
                err = fuse_do_getattr(inode, stat, file);
        } else {
                r = false;
@@ -1017,7 +1028,7 @@ int fuse_allow_current_process(struct fuse_conn *fc)
 {
        const struct cred *cred;
 
-       if (fc->flags & FUSE_ALLOW_OTHER)
+       if (fc->allow_other)
                return 1;
 
        cred = current_cred();
@@ -1064,6 +1075,7 @@ static int fuse_perm_getattr(struct inode *inode, int mask)
        if (mask & MAY_NOT_BLOCK)
                return -ECHILD;
 
+       forget_all_cached_acls(inode);
        return fuse_do_getattr(inode, NULL, NULL);
 }
 
@@ -1092,7 +1104,7 @@ static int fuse_permission(struct inode *inode, int mask)
        /*
         * If attributes are needed, refresh them before proceeding
         */
-       if ((fc->flags & FUSE_DEFAULT_PERMISSIONS) ||
+       if (fc->default_permissions ||
            ((mask & MAY_EXEC) && S_ISREG(inode->i_mode))) {
                struct fuse_inode *fi = get_fuse_inode(inode);
 
@@ -1105,7 +1117,7 @@ static int fuse_permission(struct inode *inode, int mask)
                }
        }
 
-       if (fc->flags & FUSE_DEFAULT_PERMISSIONS) {
+       if (fc->default_permissions) {
                err = generic_permission(inode, mask);
 
                /* If permission is denied, try to refresh file
@@ -1233,6 +1245,7 @@ retry:
                fi->nlookup++;
                spin_unlock(&fc->lock);
 
+               forget_all_cached_acls(inode);
                fuse_change_attributes(inode, &o->attr,
                                       entry_attr_timeout(o),
                                       attr_version);
@@ -1605,7 +1618,7 @@ int fuse_do_setattr(struct inode *inode, struct iattr *attr,
        int err;
        bool trust_local_cmtime = is_wb && S_ISREG(inode->i_mode);
 
-       if (!(fc->flags & FUSE_DEFAULT_PERMISSIONS))
+       if (!fc->default_permissions)
                attr->ia_valid |= ATTR_FORCE;
 
        err = inode_change_ok(inode, attr);
@@ -1700,174 +1713,77 @@ error:
 }
 
 static int fuse_setattr(struct dentry *entry, struct iattr *attr)
-{
-       struct inode *inode = d_inode(entry);
-
-       if (!fuse_allow_current_process(get_fuse_conn(inode)))
-               return -EACCES;
-
-       if (attr->ia_valid & ATTR_FILE)
-               return fuse_do_setattr(inode, attr, attr->ia_file);
-       else
-               return fuse_do_setattr(inode, attr, NULL);
-}
-
-static int fuse_getattr(struct vfsmount *mnt, struct dentry *entry,
-                       struct kstat *stat)
 {
        struct inode *inode = d_inode(entry);
        struct fuse_conn *fc = get_fuse_conn(inode);
+       struct file *file = (attr->ia_valid & ATTR_FILE) ? attr->ia_file : NULL;
+       int ret;
 
-       if (!fuse_allow_current_process(fc))
+       if (!fuse_allow_current_process(get_fuse_conn(inode)))
                return -EACCES;
 
-       return fuse_update_attributes(inode, stat, NULL, NULL);
-}
-
-static int fuse_setxattr(struct dentry *unused, struct inode *inode,
-                        const char *name, const void *value,
-                        size_t size, int flags)
-{
-       struct fuse_conn *fc = get_fuse_conn(inode);
-       FUSE_ARGS(args);
-       struct fuse_setxattr_in inarg;
-       int err;
-
-       if (fc->no_setxattr)
-               return -EOPNOTSUPP;
+       if (attr->ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID)) {
+               attr->ia_valid &= ~(ATTR_KILL_SUID | ATTR_KILL_SGID |
+                                   ATTR_MODE);
 
-       memset(&inarg, 0, sizeof(inarg));
-       inarg.size = size;
-       inarg.flags = flags;
-       args.in.h.opcode = FUSE_SETXATTR;
-       args.in.h.nodeid = get_node_id(inode);
-       args.in.numargs = 3;
-       args.in.args[0].size = sizeof(inarg);
-       args.in.args[0].value = &inarg;
-       args.in.args[1].size = strlen(name) + 1;
-       args.in.args[1].value = name;
-       args.in.args[2].size = size;
-       args.in.args[2].value = value;
-       err = fuse_simple_request(fc, &args);
-       if (err == -ENOSYS) {
-               fc->no_setxattr = 1;
-               err = -EOPNOTSUPP;
-       }
-       if (!err) {
-               fuse_invalidate_attr(inode);
-               fuse_update_ctime(inode);
+               /*
+                * The only sane way to reliably kill suid/sgid is to do it in
+                * the userspace filesystem
+                *
+                * This should be done on write(), truncate() and chown().
+                */
+               if (!fc->handle_killpriv) {
+                       int kill;
+
+                       /*
+                        * ia_mode calculation may have used stale i_mode.
+                        * Refresh and recalculate.
+                        */
+                       ret = fuse_do_getattr(inode, NULL, file);
+                       if (ret)
+                               return ret;
+
+                       attr->ia_mode = inode->i_mode;
+                       kill = should_remove_suid(entry);
+                       if (kill & ATTR_KILL_SUID) {
+                               attr->ia_valid |= ATTR_MODE;
+                               attr->ia_mode &= ~S_ISUID;
+                       }
+                       if (kill & ATTR_KILL_SGID) {
+                               attr->ia_valid |= ATTR_MODE;
+                               attr->ia_mode &= ~S_ISGID;
+                       }
+               }
        }
-       return err;
-}
-
-static ssize_t fuse_getxattr(struct dentry *entry, struct inode *inode,
-                            const char *name, void *value, size_t size)
-{
-       struct fuse_conn *fc = get_fuse_conn(inode);
-       FUSE_ARGS(args);
-       struct fuse_getxattr_in inarg;
-       struct fuse_getxattr_out outarg;
-       ssize_t ret;
+       if (!attr->ia_valid)
+               return 0;
 
-       if (fc->no_getxattr)
-               return -EOPNOTSUPP;
+       ret = fuse_do_setattr(inode, attr, file);
+       if (!ret) {
+               /*
+                * If filesystem supports acls it may have updated acl xattrs in
+                * the filesystem, so forget cached acls for the inode.
+                */
+               if (fc->posix_acl)
+                       forget_all_cached_acls(inode);
 
-       memset(&inarg, 0, sizeof(inarg));
-       inarg.size = size;
-       args.in.h.opcode = FUSE_GETXATTR;
-       args.in.h.nodeid = get_node_id(inode);
-       args.in.numargs = 2;
-       args.in.args[0].size = sizeof(inarg);
-       args.in.args[0].value = &inarg;
-       args.in.args[1].size = strlen(name) + 1;
-       args.in.args[1].value = name;
-       /* This is really two different operations rolled into one */
-       args.out.numargs = 1;
-       if (size) {
-               args.out.argvar = 1;
-               args.out.args[0].size = size;
-               args.out.args[0].value = value;
-       } else {
-               args.out.args[0].size = sizeof(outarg);
-               args.out.args[0].value = &outarg;
-       }
-       ret = fuse_simple_request(fc, &args);
-       if (!ret && !size)
-               ret = outarg.size;
-       if (ret == -ENOSYS) {
-               fc->no_getxattr = 1;
-               ret = -EOPNOTSUPP;
+               /* Directory mode changed, may need to revalidate access */
+               if (d_is_dir(entry) && (attr->ia_valid & ATTR_MODE))
+                       fuse_invalidate_entry_cache(entry);
        }
        return ret;
 }
 
-static ssize_t fuse_listxattr(struct dentry *entry, char *list, size_t size)
+static int fuse_getattr(struct vfsmount *mnt, struct dentry *entry,
+                       struct kstat *stat)
 {
        struct inode *inode = d_inode(entry);
        struct fuse_conn *fc = get_fuse_conn(inode);
-       FUSE_ARGS(args);
-       struct fuse_getxattr_in inarg;
-       struct fuse_getxattr_out outarg;
-       ssize_t ret;
 
        if (!fuse_allow_current_process(fc))
                return -EACCES;
 
-       if (fc->no_listxattr)
-               return -EOPNOTSUPP;
-
-       memset(&inarg, 0, sizeof(inarg));
-       inarg.size = size;
-       args.in.h.opcode = FUSE_LISTXATTR;
-       args.in.h.nodeid = get_node_id(inode);
-       args.in.numargs = 1;
-       args.in.args[0].size = sizeof(inarg);
-       args.in.args[0].value = &inarg;
-       /* This is really two different operations rolled into one */
-       args.out.numargs = 1;
-       if (size) {
-               args.out.argvar = 1;
-               args.out.args[0].size = size;
-               args.out.args[0].value = list;
-       } else {
-               args.out.args[0].size = sizeof(outarg);
-               args.out.args[0].value = &outarg;
-       }
-       ret = fuse_simple_request(fc, &args);
-       if (!ret && !size)
-               ret = outarg.size;
-       if (ret == -ENOSYS) {
-               fc->no_listxattr = 1;
-               ret = -EOPNOTSUPP;
-       }
-       return ret;
-}
-
-static int fuse_removexattr(struct dentry *entry, const char *name)
-{
-       struct inode *inode = d_inode(entry);
-       struct fuse_conn *fc = get_fuse_conn(inode);
-       FUSE_ARGS(args);
-       int err;
-
-       if (fc->no_removexattr)
-               return -EOPNOTSUPP;
-
-       args.in.h.opcode = FUSE_REMOVEXATTR;
-       args.in.h.nodeid = get_node_id(inode);
-       args.in.numargs = 1;
-       args.in.args[0].size = strlen(name) + 1;
-       args.in.args[0].value = name;
-       err = fuse_simple_request(fc, &args);
-       if (err == -ENOSYS) {
-               fc->no_removexattr = 1;
-               err = -EOPNOTSUPP;
-       }
-       if (!err) {
-               fuse_invalidate_attr(inode);
-               fuse_update_ctime(inode);
-       }
-       return err;
+       return fuse_update_attributes(inode, stat, NULL, NULL);
 }
 
 static const struct inode_operations fuse_dir_inode_operations = {
@@ -1884,10 +1800,12 @@ static const struct inode_operations fuse_dir_inode_operations = {
        .mknod          = fuse_mknod,
        .permission     = fuse_permission,
        .getattr        = fuse_getattr,
-       .setxattr       = fuse_setxattr,
-       .getxattr       = fuse_getxattr,
+       .setxattr       = generic_setxattr,
+       .getxattr       = generic_getxattr,
        .listxattr      = fuse_listxattr,
-       .removexattr    = fuse_removexattr,
+       .removexattr    = generic_removexattr,
+       .get_acl        = fuse_get_acl,
+       .set_acl        = fuse_set_acl,
 };
 
 static const struct file_operations fuse_dir_operations = {
@@ -1905,10 +1823,12 @@ static const struct inode_operations fuse_common_inode_operations = {
        .setattr        = fuse_setattr,
        .permission     = fuse_permission,
        .getattr        = fuse_getattr,
-       .setxattr       = fuse_setxattr,
-       .getxattr       = fuse_getxattr,
+       .setxattr       = generic_setxattr,
+       .getxattr       = generic_getxattr,
        .listxattr      = fuse_listxattr,
-       .removexattr    = fuse_removexattr,
+       .removexattr    = generic_removexattr,
+       .get_acl        = fuse_get_acl,
+       .set_acl        = fuse_set_acl,
 };
 
 static const struct inode_operations fuse_symlink_inode_operations = {
@@ -1916,10 +1836,10 @@ static const struct inode_operations fuse_symlink_inode_operations = {
        .get_link       = fuse_get_link,
        .readlink       = generic_readlink,
        .getattr        = fuse_getattr,
-       .setxattr       = fuse_setxattr,
-       .getxattr       = fuse_getxattr,
+       .setxattr       = generic_setxattr,
+       .getxattr       = generic_getxattr,
        .listxattr      = fuse_listxattr,
-       .removexattr    = fuse_removexattr,
+       .removexattr    = generic_removexattr,
 };
 
 void fuse_init_common(struct inode *inode)
index 3988b43..b7beb67 100644 (file)
@@ -2326,49 +2326,6 @@ static loff_t fuse_file_llseek(struct file *file, loff_t offset, int whence)
        return retval;
 }
 
-static int fuse_ioctl_copy_user(struct page **pages, struct iovec *iov,
-                       unsigned int nr_segs, size_t bytes, bool to_user)
-{
-       struct iov_iter ii;
-       int page_idx = 0;
-
-       if (!bytes)
-               return 0;
-
-       iov_iter_init(&ii, to_user ? READ : WRITE, iov, nr_segs, bytes);
-
-       while (iov_iter_count(&ii)) {
-               struct page *page = pages[page_idx++];
-               size_t todo = min_t(size_t, PAGE_SIZE, iov_iter_count(&ii));
-               void *kaddr;
-
-               kaddr = kmap(page);
-
-               while (todo) {
-                       char __user *uaddr = ii.iov->iov_base + ii.iov_offset;
-                       size_t iov_len = ii.iov->iov_len - ii.iov_offset;
-                       size_t copy = min(todo, iov_len);
-                       size_t left;
-
-                       if (!to_user)
-                               left = copy_from_user(kaddr, uaddr, copy);
-                       else
-                               left = copy_to_user(uaddr, kaddr, copy);
-
-                       if (unlikely(left))
-                               return -EFAULT;
-
-                       iov_iter_advance(&ii, copy);
-                       todo -= copy;
-                       kaddr += copy;
-               }
-
-               kunmap(page);
-       }
-
-       return 0;
-}
-
 /*
  * CUSE servers compiled on 32bit broke on 64bit kernels because the
  * ABI was defined to be 'struct iovec' which is different on 32bit
@@ -2520,8 +2477,9 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
        struct iovec *iov_page = NULL;
        struct iovec *in_iov = NULL, *out_iov = NULL;
        unsigned int in_iovs = 0, out_iovs = 0, num_pages = 0, max_pages;
-       size_t in_size, out_size, transferred;
-       int err;
+       size_t in_size, out_size, transferred, c;
+       int err, i;
+       struct iov_iter ii;
 
 #if BITS_PER_LONG == 32
        inarg.flags |= FUSE_IOCTL_32BIT;
@@ -2603,10 +2561,13 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
                req->in.args[1].size = in_size;
                req->in.argpages = 1;
 
-               err = fuse_ioctl_copy_user(pages, in_iov, in_iovs, in_size,
-                                          false);
-               if (err)
-                       goto out;
+               err = -EFAULT;
+               iov_iter_init(&ii, WRITE, in_iov, in_iovs, in_size);
+               for (i = 0; iov_iter_count(&ii) && !WARN_ON(i >= num_pages); i++) {
+                       c = copy_page_from_iter(pages[i], 0, PAGE_SIZE, &ii);
+                       if (c != PAGE_SIZE && iov_iter_count(&ii))
+                               goto out;
+               }
        }
 
        req->out.numargs = 2;
@@ -2672,7 +2633,14 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
        if (transferred > inarg.out_size)
                goto out;
 
-       err = fuse_ioctl_copy_user(pages, out_iov, out_iovs, transferred, true);
+       err = -EFAULT;
+       iov_iter_init(&ii, READ, out_iov, out_iovs, transferred);
+       for (i = 0; iov_iter_count(&ii) && !WARN_ON(i >= num_pages); i++) {
+               c = copy_page_to_iter(pages[i], 0, PAGE_SIZE, &ii);
+               if (c != PAGE_SIZE && iov_iter_count(&ii))
+                       goto out;
+       }
+       err = 0;
  out:
        if (req)
                fuse_put_request(fc, req);
index d98d8cc..24ada5d 100644 (file)
@@ -23,6 +23,7 @@
 #include <linux/poll.h>
 #include <linux/workqueue.h>
 #include <linux/kref.h>
+#include <linux/xattr.h>
 
 /** Max number of pages that can be used in a single read request */
 #define FUSE_MAX_PAGES_PER_REQ 32
 /** Number of dentries for each connection in the control filesystem */
 #define FUSE_CTL_NUM_DENTRIES 5
 
-/** If the FUSE_DEFAULT_PERMISSIONS flag is given, the filesystem
-    module will check permissions based on the file mode.  Otherwise no
-    permission checking is done in the kernel */
-#define FUSE_DEFAULT_PERMISSIONS (1 << 0)
-
-/** If the FUSE_ALLOW_OTHER flag is given, then not only the user
-    doing the mount will be allowed to access the filesystem */
-#define FUSE_ALLOW_OTHER         (1 << 1)
-
 /** Number of page pointers embedded in fuse_req */
 #define FUSE_REQ_INLINE_PAGES 1
 
@@ -469,9 +461,6 @@ struct fuse_conn {
        /** The group id for this mount */
        kgid_t group_id;
 
-       /** The fuse mount flags for this mount */
-       unsigned flags;
-
        /** Maximum read size */
        unsigned max_read;
 
@@ -547,6 +536,9 @@ struct fuse_conn {
        /** allow parallel lookups and readdir (default is serialized) */
        unsigned parallel_dirops:1;
 
+       /** handle fs handles killing suid/sgid/cap on write/chown/trunc */
+       unsigned handle_killpriv:1;
+
        /*
         * The following bitfields are only for optimization purposes
         * and hence races in setting them will not cause malfunction
@@ -624,6 +616,15 @@ struct fuse_conn {
        /** Is lseek not implemented by fs? */
        unsigned no_lseek:1;
 
+       /** Does the filesystem support posix acls? */
+       unsigned posix_acl:1;
+
+       /** Check permissions based on the file mode or not? */
+       unsigned default_permissions:1;
+
+       /** Allow other than the mounter user to access the filesystem ? */
+       unsigned allow_other:1;
+
        /** The number of requests waiting for completion */
        atomic_t num_waiting;
 
@@ -902,6 +903,8 @@ int fuse_allow_current_process(struct fuse_conn *fc);
 
 u64 fuse_lock_owner_id(struct fuse_conn *fc, fl_owner_t id);
 
+void fuse_update_ctime(struct inode *inode);
+
 int fuse_update_attributes(struct inode *inode, struct kstat *stat,
                           struct file *file, bool *refreshed);
 
@@ -966,4 +969,17 @@ void fuse_set_initialized(struct fuse_conn *fc);
 void fuse_unlock_inode(struct inode *inode);
 void fuse_lock_inode(struct inode *inode);
 
+int fuse_setxattr(struct inode *inode, const char *name, const void *value,
+                 size_t size, int flags);
+ssize_t fuse_getxattr(struct inode *inode, const char *name, void *value,
+                     size_t size);
+ssize_t fuse_listxattr(struct dentry *entry, char *list, size_t size);
+int fuse_removexattr(struct inode *inode, const char *name);
+extern const struct xattr_handler *fuse_xattr_handlers[];
+extern const struct xattr_handler *fuse_acl_xattr_handlers[];
+
+struct posix_acl;
+struct posix_acl *fuse_get_acl(struct inode *inode, int type);
+int fuse_set_acl(struct inode *inode, struct posix_acl *acl, int type);
+
 #endif /* _FS_FUSE_I_H */
index 4e05b51..1714109 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/random.h>
 #include <linux/sched.h>
 #include <linux/exportfs.h>
+#include <linux/posix_acl.h>
 
 MODULE_AUTHOR("Miklos Szeredi <miklos@szeredi.hu>");
 MODULE_DESCRIPTION("Filesystem in Userspace");
@@ -66,7 +67,8 @@ struct fuse_mount_data {
        unsigned rootmode_present:1;
        unsigned user_id_present:1;
        unsigned group_id_present:1;
-       unsigned flags;
+       unsigned default_permissions:1;
+       unsigned allow_other:1;
        unsigned max_read;
        unsigned blksize;
 };
@@ -192,7 +194,7 @@ void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr,
         * check in may_delete().
         */
        fi->orig_i_mode = inode->i_mode;
-       if (!(fc->flags & FUSE_DEFAULT_PERMISSIONS))
+       if (!fc->default_permissions)
                inode->i_mode &= ~S_ISVTX;
 
        fi->orig_ino = attr->ino;
@@ -340,6 +342,7 @@ int fuse_reverse_inval_inode(struct super_block *sb, u64 nodeid,
                return -ENOENT;
 
        fuse_invalidate_attr(inode);
+       forget_all_cached_acls(inode);
        if (offset >= 0) {
                pg_start = offset >> PAGE_SHIFT;
                if (len <= 0)
@@ -532,11 +535,11 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d, int is_bdev)
                        break;
 
                case OPT_DEFAULT_PERMISSIONS:
-                       d->flags |= FUSE_DEFAULT_PERMISSIONS;
+                       d->default_permissions = 1;
                        break;
 
                case OPT_ALLOW_OTHER:
-                       d->flags |= FUSE_ALLOW_OTHER;
+                       d->allow_other = 1;
                        break;
 
                case OPT_MAX_READ:
@@ -570,9 +573,9 @@ static int fuse_show_options(struct seq_file *m, struct dentry *root)
 
        seq_printf(m, ",user_id=%u", from_kuid_munged(&init_user_ns, fc->user_id));
        seq_printf(m, ",group_id=%u", from_kgid_munged(&init_user_ns, fc->group_id));
-       if (fc->flags & FUSE_DEFAULT_PERMISSIONS)
+       if (fc->default_permissions)
                seq_puts(m, ",default_permissions");
-       if (fc->flags & FUSE_ALLOW_OTHER)
+       if (fc->allow_other)
                seq_puts(m, ",allow_other");
        if (fc->max_read != ~0)
                seq_printf(m, ",max_read=%u", fc->max_read);
@@ -910,8 +913,15 @@ static void process_init_reply(struct fuse_conn *fc, struct fuse_req *req)
                                fc->writeback_cache = 1;
                        if (arg->flags & FUSE_PARALLEL_DIROPS)
                                fc->parallel_dirops = 1;
+                       if (arg->flags & FUSE_HANDLE_KILLPRIV)
+                               fc->handle_killpriv = 1;
                        if (arg->time_gran && arg->time_gran <= 1000000000)
                                fc->sb->s_time_gran = arg->time_gran;
+                       if ((arg->flags & FUSE_POSIX_ACL)) {
+                               fc->default_permissions = 1;
+                               fc->posix_acl = 1;
+                               fc->sb->s_xattr = fuse_acl_xattr_handlers;
+                       }
                } else {
                        ra_pages = fc->max_read / PAGE_SIZE;
                        fc->no_lock = 1;
@@ -941,7 +951,7 @@ static void fuse_send_init(struct fuse_conn *fc, struct fuse_req *req)
                FUSE_FLOCK_LOCKS | FUSE_HAS_IOCTL_DIR | FUSE_AUTO_INVAL_DATA |
                FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO | FUSE_ASYNC_DIO |
                FUSE_WRITEBACK_CACHE | FUSE_NO_OPEN_SUPPORT |
-               FUSE_PARALLEL_DIROPS;
+               FUSE_PARALLEL_DIROPS | FUSE_HANDLE_KILLPRIV | FUSE_POSIX_ACL;
        req->in.h.opcode = FUSE_INIT;
        req->in.numargs = 1;
        req->in.args[0].size = sizeof(*arg);
@@ -1071,6 +1081,7 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent)
        }
        sb->s_magic = FUSE_SUPER_MAGIC;
        sb->s_op = &fuse_super_operations;
+       sb->s_xattr = fuse_xattr_handlers;
        sb->s_maxbytes = MAX_LFS_FILESIZE;
        sb->s_time_gran = 1;
        sb->s_export_op = &fuse_export_operations;
@@ -1109,7 +1120,8 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent)
                fc->dont_mask = 1;
        sb->s_flags |= MS_POSIXACL;
 
-       fc->flags = d.flags;
+       fc->default_permissions = d.default_permissions;
+       fc->allow_other = d.allow_other;
        fc->user_id = d.user_id;
        fc->group_id = d.group_id;
        fc->max_read = max_t(unsigned, 4096, d.max_read);
diff --git a/fs/fuse/xattr.c b/fs/fuse/xattr.c
new file mode 100644 (file)
index 0000000..3caac46
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2001-2016  Miklos Szeredi <miklos@szeredi.hu>
+ *
+ * This program can be distributed under the terms of the GNU GPL.
+ * See the file COPYING.
+ */
+
+#include "fuse_i.h"
+
+#include <linux/xattr.h>
+#include <linux/posix_acl_xattr.h>
+
+int fuse_setxattr(struct inode *inode, const char *name, const void *value,
+                 size_t size, int flags)
+{
+       struct fuse_conn *fc = get_fuse_conn(inode);
+       FUSE_ARGS(args);
+       struct fuse_setxattr_in inarg;
+       int err;
+
+       if (fc->no_setxattr)
+               return -EOPNOTSUPP;
+
+       memset(&inarg, 0, sizeof(inarg));
+       inarg.size = size;
+       inarg.flags = flags;
+       args.in.h.opcode = FUSE_SETXATTR;
+       args.in.h.nodeid = get_node_id(inode);
+       args.in.numargs = 3;
+       args.in.args[0].size = sizeof(inarg);
+       args.in.args[0].value = &inarg;
+       args.in.args[1].size = strlen(name) + 1;
+       args.in.args[1].value = name;
+       args.in.args[2].size = size;
+       args.in.args[2].value = value;
+       err = fuse_simple_request(fc, &args);
+       if (err == -ENOSYS) {
+               fc->no_setxattr = 1;
+               err = -EOPNOTSUPP;
+       }
+       if (!err) {
+               fuse_invalidate_attr(inode);
+               fuse_update_ctime(inode);
+       }
+       return err;
+}
+
+ssize_t fuse_getxattr(struct inode *inode, const char *name, void *value,
+                     size_t size)
+{
+       struct fuse_conn *fc = get_fuse_conn(inode);
+       FUSE_ARGS(args);
+       struct fuse_getxattr_in inarg;
+       struct fuse_getxattr_out outarg;
+       ssize_t ret;
+
+       if (fc->no_getxattr)
+               return -EOPNOTSUPP;
+
+       memset(&inarg, 0, sizeof(inarg));
+       inarg.size = size;
+       args.in.h.opcode = FUSE_GETXATTR;
+       args.in.h.nodeid = get_node_id(inode);
+       args.in.numargs = 2;
+       args.in.args[0].size = sizeof(inarg);
+       args.in.args[0].value = &inarg;
+       args.in.args[1].size = strlen(name) + 1;
+       args.in.args[1].value = name;
+       /* This is really two different operations rolled into one */
+       args.out.numargs = 1;
+       if (size) {
+               args.out.argvar = 1;
+               args.out.args[0].size = size;
+               args.out.args[0].value = value;
+       } else {
+               args.out.args[0].size = sizeof(outarg);
+               args.out.args[0].value = &outarg;
+       }
+       ret = fuse_simple_request(fc, &args);
+       if (!ret && !size)
+               ret = min_t(ssize_t, outarg.size, XATTR_SIZE_MAX);
+       if (ret == -ENOSYS) {
+               fc->no_getxattr = 1;
+               ret = -EOPNOTSUPP;
+       }
+       return ret;
+}
+
+static int fuse_verify_xattr_list(char *list, size_t size)
+{
+       size_t origsize = size;
+
+       while (size) {
+               size_t thislen = strnlen(list, size);
+
+               if (!thislen || thislen == size)
+                       return -EIO;
+
+               size -= thislen + 1;
+               list += thislen + 1;
+       }
+
+       return origsize;
+}
+
+ssize_t fuse_listxattr(struct dentry *entry, char *list, size_t size)
+{
+       struct inode *inode = d_inode(entry);
+       struct fuse_conn *fc = get_fuse_conn(inode);
+       FUSE_ARGS(args);
+       struct fuse_getxattr_in inarg;
+       struct fuse_getxattr_out outarg;
+       ssize_t ret;
+
+       if (!fuse_allow_current_process(fc))
+               return -EACCES;
+
+       if (fc->no_listxattr)
+               return -EOPNOTSUPP;
+
+       memset(&inarg, 0, sizeof(inarg));
+       inarg.size = size;
+       args.in.h.opcode = FUSE_LISTXATTR;
+       args.in.h.nodeid = get_node_id(inode);
+       args.in.numargs = 1;
+       args.in.args[0].size = sizeof(inarg);
+       args.in.args[0].value = &inarg;
+       /* This is really two different operations rolled into one */
+       args.out.numargs = 1;
+       if (size) {
+               args.out.argvar = 1;
+               args.out.args[0].size = size;
+               args.out.args[0].value = list;
+       } else {
+               args.out.args[0].size = sizeof(outarg);
+               args.out.args[0].value = &outarg;
+       }
+       ret = fuse_simple_request(fc, &args);
+       if (!ret && !size)
+               ret = min_t(ssize_t, outarg.size, XATTR_LIST_MAX);
+       if (ret > 0 && size)
+               ret = fuse_verify_xattr_list(list, ret);
+       if (ret == -ENOSYS) {
+               fc->no_listxattr = 1;
+               ret = -EOPNOTSUPP;
+       }
+       return ret;
+}
+
+int fuse_removexattr(struct inode *inode, const char *name)
+{
+       struct fuse_conn *fc = get_fuse_conn(inode);
+       FUSE_ARGS(args);
+       int err;
+
+       if (fc->no_removexattr)
+               return -EOPNOTSUPP;
+
+       args.in.h.opcode = FUSE_REMOVEXATTR;
+       args.in.h.nodeid = get_node_id(inode);
+       args.in.numargs = 1;
+       args.in.args[0].size = strlen(name) + 1;
+       args.in.args[0].value = name;
+       err = fuse_simple_request(fc, &args);
+       if (err == -ENOSYS) {
+               fc->no_removexattr = 1;
+               err = -EOPNOTSUPP;
+       }
+       if (!err) {
+               fuse_invalidate_attr(inode);
+               fuse_update_ctime(inode);
+       }
+       return err;
+}
+
+static int fuse_xattr_get(const struct xattr_handler *handler,
+                        struct dentry *dentry, struct inode *inode,
+                        const char *name, void *value, size_t size)
+{
+       return fuse_getxattr(inode, name, value, size);
+}
+
+static int fuse_xattr_set(const struct xattr_handler *handler,
+                         struct dentry *dentry, struct inode *inode,
+                         const char *name, const void *value, size_t size,
+                         int flags)
+{
+       if (!value)
+               return fuse_removexattr(inode, name);
+
+       return fuse_setxattr(inode, name, value, size, flags);
+}
+
+static const struct xattr_handler fuse_xattr_handler = {
+       .prefix = "",
+       .get    = fuse_xattr_get,
+       .set    = fuse_xattr_set,
+};
+
+const struct xattr_handler *fuse_xattr_handlers[] = {
+       &fuse_xattr_handler,
+       NULL
+};
+
+const struct xattr_handler *fuse_acl_xattr_handlers[] = {
+       &posix_acl_access_xattr_handler,
+       &posix_acl_default_xattr_handler,
+       &fuse_xattr_handler,
+       NULL
+};
index 27e1736..42fa977 100644 (file)
  *
  *  7.25
  *  - add FUSE_PARALLEL_DIROPS
+ *
+ *  7.26
+ *  - add FUSE_HANDLE_KILLPRIV
+ *  - add FUSE_POSIX_ACL
  */
 
 #ifndef _LINUX_FUSE_H
 #define FUSE_KERNEL_VERSION 7
 
 /** Minor version number of this interface */
-#define FUSE_KERNEL_MINOR_VERSION 25
+#define FUSE_KERNEL_MINOR_VERSION 26
 
 /** The node ID of the root inode */
 #define FUSE_ROOT_ID 1
@@ -238,6 +242,8 @@ struct fuse_file_lock {
  * FUSE_WRITEBACK_CACHE: use writeback cache for buffered writes
  * FUSE_NO_OPEN_SUPPORT: kernel supports zero-message opens
  * FUSE_PARALLEL_DIROPS: allow parallel lookups and readdir
+ * FUSE_HANDLE_KILLPRIV: fs handles killing suid/sgid/cap on write/chown/trunc
+ * FUSE_POSIX_ACL: filesystem supports posix acls
  */
 #define FUSE_ASYNC_READ                (1 << 0)
 #define FUSE_POSIX_LOCKS       (1 << 1)
@@ -258,6 +264,8 @@ struct fuse_file_lock {
 #define FUSE_WRITEBACK_CACHE   (1 << 16)
 #define FUSE_NO_OPEN_SUPPORT   (1 << 17)
 #define FUSE_PARALLEL_DIROPS    (1 << 18)
+#define FUSE_HANDLE_KILLPRIV   (1 << 19)
+#define FUSE_POSIX_ACL         (1 << 20)
 
 /**
  * CUSE INIT request/reply flags