fuse: add FUSE_WRITE_KILL_PRIV
authorMiklos Szeredi <mszeredi@redhat.com>
Mon, 27 May 2019 07:08:12 +0000 (09:08 +0200)
committerMiklos Szeredi <mszeredi@redhat.com>
Mon, 27 May 2019 09:42:36 +0000 (11:42 +0200)
In the FOPEN_DIRECT_IO case the write path doesn't call file_remove_privs()
and that means setuid bit is not cleared if unpriviliged user writes to a
file with setuid bit set.

pjdfstest chmod test 12.t tests this and fails.

Fix this by adding a flag to the FUSE_WRITE message that requests clearing
privileges on the given file.  This needs

This better than just calling fuse_remove_privs(), because the attributes
may not be up to date, so in that case a write may miss clearing the
privileges.

Test case:

  $ passthrough_ll /mnt/pasthrough-mnt -o default_permissions,allow_other,cache=never
  $ mkdir /mnt/pasthrough-mnt/testdir
  $ cd /mnt/pasthrough-mnt/testdir
  $ prove -rv pjdfstests/tests/chmod/12.t

Reported-by: Vivek Goyal <vgoyal@redhat.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
Tested-by: Vivek Goyal <vgoyal@redhat.com>
fs/fuse/file.c
include/uapi/linux/fuse.h

index 143c54f..c106486 100644 (file)
@@ -1377,10 +1377,17 @@ ssize_t fuse_direct_io(struct fuse_io_priv *io, struct iov_iter *iter,
                if (err && !nbytes)
                        break;
 
-               if (write)
+               if (write) {
+                       if (!capable(CAP_FSETID)) {
+                               struct fuse_write_in *inarg;
+
+                               inarg = &req->misc.write.in;
+                               inarg->write_flags |= FUSE_WRITE_KILL_PRIV;
+                       }
                        nres = fuse_send_write(req, io, pos, nbytes, owner);
-               else
+               } else {
                        nres = fuse_send_read(req, io, pos, nbytes, owner);
+               }
 
                if (!io->async)
                        fuse_release_user_pages(req, io->should_dirty);
index 19fb55e..2971d29 100644 (file)
  *  7.30
  *  - add FUSE_EXPLICIT_INVAL_DATA
  *  - add FUSE_IOCTL_COMPAT_X32
+ *
+ *  7.31
+ *  - add FUSE_WRITE_KILL_PRIV flag
  */
 
 #ifndef _LINUX_FUSE_H
 #define FUSE_KERNEL_VERSION 7
 
 /** Minor version number of this interface */
-#define FUSE_KERNEL_MINOR_VERSION 30
+#define FUSE_KERNEL_MINOR_VERSION 31
 
 /** The node ID of the root inode */
 #define FUSE_ROOT_ID 1
@@ -327,9 +330,11 @@ struct fuse_file_lock {
  *
  * FUSE_WRITE_CACHE: delayed write from page cache, file handle is guessed
  * FUSE_WRITE_LOCKOWNER: lock_owner field is valid
+ * FUSE_WRITE_KILL_PRIV: kill suid and sgid bits
  */
 #define FUSE_WRITE_CACHE       (1 << 0)
 #define FUSE_WRITE_LOCKOWNER   (1 << 1)
+#define FUSE_WRITE_KILL_PRIV   (1 << 2)
 
 /**
  * Read flags