[PATCH] fuse: Don't make buffered read forward overflow value to a userspace process

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Buffered read in fuse normally goes via:
-> generic_file_buffered_read()
  ------------------------------
  -> fuse_readpages()
    -> fuse_send_readpages()
  or
  -> fuse_readpage() [if fuse_readpages() fails to get page]
    -> fuse_do_readpage()
  ------------------------------
      -> fuse_simple_request()

Buffered read changes original offset to page-aligned length by left-shift
and extends original count to be multiples of PAGE_SIZE and then fuse
forwards these new parameters to a userspace process, so it is possible
for the resulting offset(e.g page-aligned offset + extended count) to
exceed the whole file size(even the max value of off_t) when the userspace
process does read with new parameters.

xfstests generic/525 gets "pread: Invalid argument" error on virtiofs
because it triggers this issue.  See the following explanation:
PAGE_SIZE: 4096, file size: 2^63 - 1
Original: offset: 2^63 - 2, count: 1
Changed by buffered read: offset: 2^63 - 4096, count: 4096
New offset + new count exceeds the file size as well as LLONG_MAX

Make fuse calculate the number of bytes of data pages contain as
nfs_page_length() and generic_file_buffered_read() do, and then forward
page-aligned offset and normal count to a userspace process.

Signed-off-by: Xiao Yang <ice_yangxiao@xxxxxxx>
---
 fs/fuse/file.c | 28 ++++++++++++++++++++++++----
 1 file changed, 24 insertions(+), 4 deletions(-)

diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index ce715380143c..5afc4b623eaf 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -19,6 +19,23 @@
 #include <linux/falloc.h>
 #include <linux/uio.h>
 
+static unsigned int fuse_page_length(struct page *page)
+{
+	loff_t i_size = i_size_read(page->mapping->host);
+
+	if (i_size > 0) {
+		pgoff_t index = page_index(page);
+		pgoff_t end_index = (i_size - 1) >> PAGE_SHIFT;
+
+		if (index < end_index)
+			return PAGE_SIZE;
+		if (index == end_index)
+			return ((i_size - 1) & ~PAGE_MASK) + 1;
+	}
+
+	return 0;
+}
+
 static struct page **fuse_pages_alloc(unsigned int npages, gfp_t flags,
 				      struct fuse_page_desc **desc)
 {
@@ -783,7 +800,7 @@ static int fuse_do_readpage(struct file *file, struct page *page)
 	struct inode *inode = page->mapping->host;
 	struct fuse_conn *fc = get_fuse_conn(inode);
 	loff_t pos = page_offset(page);
-	struct fuse_page_desc desc = { .length = PAGE_SIZE };
+	struct fuse_page_desc desc = { .length = fuse_page_length(page) };
 	struct fuse_io_args ia = {
 		.ap.args.page_zeroing = true,
 		.ap.args.out_pages = true,
@@ -881,9 +898,12 @@ static void fuse_send_readpages(struct fuse_io_args *ia, struct file *file)
 	struct fuse_conn *fc = ff->fc;
 	struct fuse_args_pages *ap = &ia->ap;
 	loff_t pos = page_offset(ap->pages[0]);
-	size_t count = ap->num_pages << PAGE_SHIFT;
+	size_t count = 0;
 	ssize_t res;
-	int err;
+	int err, i;
+
+	for (i = 0; i < ap->num_pages; i++)
+		count += ap->descs[i].length;
 
 	ap->args.out_pages = true;
 	ap->args.page_zeroing = true;
@@ -944,7 +964,7 @@ static int fuse_readpages_fill(void *_data, struct page *page)
 
 	get_page(page);
 	ap->pages[ap->num_pages] = page;
-	ap->descs[ap->num_pages].length = PAGE_SIZE;
+	ap->descs[ap->num_pages].length = fuse_page_length(page);
 	ap->num_pages++;
 	data->nr_pages--;
 	return 0;
-- 
2.21.0





[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [Samba]     [Device Mapper]     [CEPH Development]

  Powered by Linux