fuse: handle killpriv in userspace fs
authorMiklos Szeredi <mszeredi@redhat.com>
Sat, 1 Oct 2016 05:32:32 +0000 (07:32 +0200)
committerMiklos Szeredi <mszeredi@redhat.com>
Sat, 1 Oct 2016 05:32:32 +0000 (07:32 +0200)
Only userspace filesystem can do the killing of suid/sgid without races.
So introduce an INIT flag and negotiate support for this.

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
fs/fuse/dir.c
fs/fuse/fuse_i.h
fs/fuse/inode.c
include/uapi/linux/fuse.h

index b7a690ed6a756abb344ad5a4c3ef39e70288f4c1..7cb68b18eb3f22f3483046f8d72c8a2bf5677f1b 100644 (file)
@@ -1703,6 +1703,7 @@ error:
 static int fuse_setattr(struct dentry *entry, struct iattr *attr)
 {
        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;
 
@@ -1710,27 +1711,36 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr)
                return -EACCES;
 
        if (attr->ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID)) {
-               int kill;
-
                attr->ia_valid &= ~(ATTR_KILL_SUID | ATTR_KILL_SGID |
                                    ATTR_MODE);
+
                /*
-                * ia_mode calculation may have used stale i_mode.  Refresh and
-                * recalculate.
+                * 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().
                 */
-               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;
+               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;
+                       }
                }
        }
        if (!attr->ia_valid)
index 6db54d0bd81be3c6faeec432f40896d03d3ee5b6..9940a648c9857e18c2c265719264e648fdfbc302 100644 (file)
@@ -547,6 +547,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
index 1e535f31fed09872cbc6c0176fea61d8e8e6f2a7..b965934939cb221f82e90324aafcbb7605bb91a0 100644 (file)
@@ -910,6 +910,8 @@ 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;
                } else {
@@ -941,7 +943,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;
        req->in.h.opcode = FUSE_INIT;
        req->in.numargs = 1;
        req->in.args[0].size = sizeof(*arg);
index 27e17363263abcc0ee9a6459ecd247de07b71a86..14ca2f10d354d0720aac57a7e02f4c80339c06d9 100644 (file)
  *
  *  7.25
  *  - add FUSE_PARALLEL_DIROPS
+ *
+ *  7.26
+ *  - add FUSE_HANDLE_KILLPRIV
  */
 
 #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 +241,7 @@ 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
  */
 #define FUSE_ASYNC_READ                (1 << 0)
 #define FUSE_POSIX_LOCKS       (1 << 1)
@@ -258,6 +262,7 @@ 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)
 
 /**
  * CUSE INIT request/reply flags