On Mon, May 20, 2019 at 04:41:37PM +0200, Miklos Szeredi wrote:
> On Wed, May 15, 2019 at 03:26:47PM -0400, Vivek Goyal wrote:
> > If fuse daemon is started with cache=never, fuse falls back to direct IO.
> > In that write path we don'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.
> 
> I think better sulution is to tell the server if the suid bit needs to be
> removed, so it can do so in a race free way.
> 
> Here's the kernel patch, and I'll reply with the libfuse patch.

Here are the patches for libfuse and passthrough_ll.
---
 include/fuse_common.h |    5 ++++-
 include/fuse_kernel.h |    2 ++
 lib/fuse_lowlevel.c   |   12 ++++++++----
 3 files changed, 14 insertions(+), 5 deletions(-)

--- a/include/fuse_common.h
+++ b/include/fuse_common.h
@@ -64,8 +64,11 @@ struct fuse_file_info {
           May only be set in ->release(). */
        unsigned int flock_release : 1;
 
+       /* Kill suid and sgid bits on write */
+       unsigned int write_kill_priv : 1;
+
        /** Padding.  Do not use*/
-       unsigned int padding : 27;
+       unsigned int padding : 26;
 
        /** File handle.  May be filled in by filesystem in open().
            Available in all other file operations */
--- a/include/fuse_kernel.h
+++ b/include/fuse_kernel.h
@@ -304,9 +304,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
--- a/lib/fuse_lowlevel.c
+++ b/lib/fuse_lowlevel.c
@@ -1315,12 +1315,14 @@ static void do_write(fuse_req_t req, fus
 
        memset(&fi, 0, sizeof(fi));
        fi.fh = arg->fh;
-       fi.writepage = (arg->write_flags & 1) != 0;
+       fi.writepage = (arg->write_flags & FUSE_WRITE_CACHE) != 0;
+       fi.write_kill_priv = (arg->write_flags & FUSE_WRITE_KILL_PRIV) != 0;
 
        if (req->se->conn.proto_minor < 9) {
                param = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE;
        } else {
-               fi.lock_owner = arg->lock_owner;
+               if (arg->write_flags & FUSE_WRITE_LOCKOWNER)
+                       fi.lock_owner = arg->lock_owner;
                fi.flags = arg->flags;
                param = PARAM(arg);
        }
@@ -1345,7 +1347,8 @@ static void do_write_buf(fuse_req_t req,
 
        memset(&fi, 0, sizeof(fi));
        fi.fh = arg->fh;
-       fi.writepage = arg->write_flags & 1;
+       fi.writepage = (arg->write_flags & FUSE_WRITE_CACHE) != 0;
+       fi.write_kill_priv = (arg->write_flags & FUSE_WRITE_KILL_PRIV) != 0;
 
        if (se->conn.proto_minor < 9) {
                bufv.buf[0].mem = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE;
@@ -1353,7 +1356,8 @@ static void do_write_buf(fuse_req_t req,
                        FUSE_COMPAT_WRITE_IN_SIZE;
                assert(!(bufv.buf[0].flags & FUSE_BUF_IS_FD));
        } else {
-               fi.lock_owner = arg->lock_owner;
+               if (arg->write_flags & FUSE_WRITE_LOCKOWNER)
+                       fi.lock_owner = arg->lock_owner;
                fi.flags = arg->flags;
                if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
                        bufv.buf[0].mem = PARAM(arg);
---
 example/passthrough_ll.c |   29 ++++++++++++++++++++++++++++-
 1 file changed, 28 insertions(+), 1 deletion(-)

--- a/example/passthrough_ll.c
+++ b/example/passthrough_ll.c
@@ -56,6 +56,7 @@
 #include <sys/file.h>
 #include <sys/xattr.h>
 #include <sys/syscall.h>
+#include <sys/capability.h>
 
 /* We are re-using pointers to our `struct lo_inode` and `struct
    lo_dirp` elements as inodes. This means that we must be able to
@@ -965,6 +966,11 @@ static void lo_write_buf(fuse_req_t req,
        (void) ino;
        ssize_t res;
        struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
+       struct __user_cap_header_struct cap_hdr = {
+               .version = _LINUX_CAPABILITY_VERSION_1,
+       };
+       struct __user_cap_data_struct cap_orig;
+       struct __user_cap_data_struct cap_new;
 
        out_buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK;
        out_buf.buf[0].fd = fi->fh;
@@ -974,7 +980,28 @@ static void lo_write_buf(fuse_req_t req,
                fprintf(stderr, "lo_write(ino=%" PRIu64 ", size=%zd, 
off=%lu)\n",
                        ino, out_buf.buf[0].size, (unsigned long) off);
 
+       if (fi->write_kill_priv) {
+               res = capget(&cap_hdr, &cap_orig);
+               if (res == -1) {
+                       fuse_reply_err(req, errno);
+                       return;
+               }
+               cap_new = cap_orig;
+               cap_new.effective &= ~(1 << CAP_FSETID);
+               res = capset(&cap_hdr, &cap_new);
+               if (res == -1) {
+                       fuse_reply_err(req, errno);
+                       return;
+               }
+       }
+
        res = fuse_buf_copy(&out_buf, in_buf, 0);
+
+       if (fi->write_kill_priv) {
+               if (capset(&cap_hdr, &cap_orig) != 0)
+                       abort();
+       }
+
        if(res < 0)
                fuse_reply_err(req, -res);
        else
@@ -1215,7 +1242,7 @@ static void lo_copy_file_range(fuse_req_
        res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
                              flags);
        if (res < 0)
-               fuse_reply_err(req, -errno);
+               fuse_reply_err(req, errno);
        else
                fuse_reply_write(req, res);
 }

Reply via email to