From 35d6fcbb7c3e296a52136347346a698a35af3fda Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Mon, 27 May 2019 11:42:07 +0200 Subject: fuse: fallocate: fix return with locked inode Do the proper cleanup in case the size check fails. Tested with xfstests:generic/228 Reported-by: kbuild test robot Reported-by: Dan Carpenter Fixes: 0cbade024ba5 ("fuse: honor RLIMIT_FSIZE in fuse_file_fallocate") Cc: Liu Bo Cc: # v3.5 Signed-off-by: Miklos Szeredi --- fs/fuse/file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 3959f08279e6..143c54f93f2d 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -3055,7 +3055,7 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset, offset + length > i_size_read(inode)) { err = inode_newsize_ok(inode, offset + length); if (err) - return err; + goto out; } if (!(mode & FALLOC_FL_KEEP_SIZE)) -- cgit v1.2.3 From 4a2abf99f9c2877039006b8dfb3ec4e0d1278fe8 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Mon, 27 May 2019 09:08:12 +0200 Subject: fuse: add FUSE_WRITE_KILL_PRIV 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 Signed-off-by: Miklos Szeredi Tested-by: Vivek Goyal --- fs/fuse/file.c | 11 +++++++++-- include/uapi/linux/fuse.h | 7 ++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 143c54f93f2d..c1064866c402 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -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); diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index 19fb55e3c73e..2971d29a42e4 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -130,6 +130,9 @@ * 7.30 * - add FUSE_EXPLICIT_INVAL_DATA * - add FUSE_IOCTL_COMPAT_X32 + * + * 7.31 + * - add FUSE_WRITE_KILL_PRIV flag */ #ifndef _LINUX_FUSE_H @@ -165,7 +168,7 @@ #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 -- cgit v1.2.3 From a2bc92362941006830afa3dfad6caec1f99acbf5 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 28 May 2019 13:22:50 +0200 Subject: fuse: fix copy_file_range() in the writeback case Prior to sending COPY_FILE_RANGE to userspace filesystem, we must flush all dirty pages in both the source and destination files. This patch adds the missing flush of the source file. Tested on libfuse-3.5.0 with: libfuse/example/passthrough_ll /mnt/fuse/ -o writeback libfuse/test/test_syscalls /mnt/fuse/tmp/test Fixes: 88bc7d5097a1 ("fuse: add support for copy_file_range()") Cc: # v4.20 Signed-off-by: Miklos Szeredi --- fs/fuse/file.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index c1064866c402..4141d94de4ff 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -3110,6 +3110,7 @@ static ssize_t fuse_copy_file_range(struct file *file_in, loff_t pos_in, { struct fuse_file *ff_in = file_in->private_data; struct fuse_file *ff_out = file_out->private_data; + struct inode *inode_in = file_inode(file_in); struct inode *inode_out = file_inode(file_out); struct fuse_inode *fi_out = get_fuse_inode(inode_out); struct fuse_conn *fc = ff_in->fc; @@ -3133,6 +3134,17 @@ static ssize_t fuse_copy_file_range(struct file *file_in, loff_t pos_in, if (fc->no_copy_file_range) return -EOPNOTSUPP; + if (fc->writeback_cache) { + inode_lock(inode_in); + err = filemap_write_and_wait_range(inode_in->i_mapping, + pos_in, pos_in + len); + if (!err) + fuse_sync_writes(inode_in); + inode_unlock(inode_in); + if (err) + return err; + } + inode_lock(inode_out); if (fc->writeback_cache) { -- cgit v1.2.3 From 26eb3bae5003585806124c880bba5bef82c80a23 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 28 May 2019 13:22:50 +0200 Subject: fuse: extract helper for range writeback The fuse_writeback_range() helper flushes dirty data to the userspace filesystem. When the function returns, the WRITE requests for the data in the given range have all been completed. This is not equivalent to fsync() on the given range, since the userspace filesystem may not yet have the data on stable storage. Signed-off-by: Miklos Szeredi --- fs/fuse/file.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 4141d94de4ff..b8f9c83835d5 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -3021,6 +3021,16 @@ fuse_direct_IO(struct kiocb *iocb, struct iov_iter *iter) return ret; } +static int fuse_writeback_range(struct inode *inode, loff_t start, loff_t end) +{ + int err = filemap_write_and_wait_range(inode->i_mapping, start, end); + + if (!err) + fuse_sync_writes(inode); + + return err; +} + static long fuse_file_fallocate(struct file *file, int mode, loff_t offset, loff_t length) { @@ -3049,12 +3059,10 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset, inode_lock(inode); if (mode & FALLOC_FL_PUNCH_HOLE) { loff_t endbyte = offset + length - 1; - err = filemap_write_and_wait_range(inode->i_mapping, - offset, endbyte); + + err = fuse_writeback_range(inode, offset, endbyte); if (err) goto out; - - fuse_sync_writes(inode); } } @@ -3136,10 +3144,7 @@ static ssize_t fuse_copy_file_range(struct file *file_in, loff_t pos_in, if (fc->writeback_cache) { inode_lock(inode_in); - err = filemap_write_and_wait_range(inode_in->i_mapping, - pos_in, pos_in + len); - if (!err) - fuse_sync_writes(inode_in); + err = fuse_writeback_range(inode_in, pos_in, pos_in + len); inode_unlock(inode_in); if (err) return err; @@ -3148,12 +3153,9 @@ static ssize_t fuse_copy_file_range(struct file *file_in, loff_t pos_in, inode_lock(inode_out); if (fc->writeback_cache) { - err = filemap_write_and_wait_range(inode_out->i_mapping, - pos_out, pos_out + len); + err = fuse_writeback_range(inode_out, pos_out, pos_out + len); if (err) goto out; - - fuse_sync_writes(inode_out); } if (is_unstable) -- cgit v1.2.3