]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
fuse: fix pipe buffer lifetime for direct_io
authorMiklos Szeredi <mszeredi@redhat.com>
Mon, 7 Mar 2022 15:30:44 +0000 (16:30 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 16 Mar 2022 13:26:49 +0000 (14:26 +0100)
commit 0c4bcfdecb1ac0967619ee7ff44871d93c08c909 upstream.

In FOPEN_DIRECT_IO mode, fuse_file_write_iter() calls
fuse_direct_write_iter(), which normally calls fuse_direct_io(), which then
imports the write buffer with fuse_get_user_pages(), which uses
iov_iter_get_pages() to grab references to userspace pages instead of
actually copying memory.

On the filesystem device side, these pages can then either be read to
userspace (via fuse_dev_read()), or splice()d over into a pipe using
fuse_dev_splice_read() as pipe buffers with &nosteal_pipe_buf_ops.

This is wrong because after fuse_dev_do_read() unlocks the FUSE request,
the userspace filesystem can mark the request as completed, causing write()
to return. At that point, the userspace filesystem should no longer have
access to the pipe buffer.

Fix by copying pages coming from the user address space to new pipe
buffers.

Reported-by: Jann Horn <jannh@google.com>
Fixes: c3021629a0d8 ("fuse: support splice() reading from fuse device")
Cc: <stable@vger.kernel.org>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/fuse/dev.c
fs/fuse/file.c
fs/fuse/fuse_i.h

index cd54a529460da9b98ef56838b3fd04cb517ae3b0..592730fd6e424fa33027932db66fbaba9f4f8854 100644 (file)
@@ -941,7 +941,17 @@ static int fuse_copy_page(struct fuse_copy_state *cs, struct page **pagep,
 
        while (count) {
                if (cs->write && cs->pipebufs && page) {
-                       return fuse_ref_page(cs, page, offset, count);
+                       /*
+                        * Can't control lifetime of pipe buffers, so always
+                        * copy user pages.
+                        */
+                       if (cs->req->args->user_pages) {
+                               err = fuse_copy_fill(cs);
+                               if (err)
+                                       return err;
+                       } else {
+                               return fuse_ref_page(cs, page, offset, count);
+                       }
                } else if (!cs->len) {
                        if (cs->move_pages && page &&
                            offset == 0 && count == PAGE_SIZE) {
index df81768c81a73efdaf7d1b46dc238686dfa12469..032b0b2cc8d99e229f34464b4f494a56968da3c6 100644 (file)
@@ -1413,6 +1413,7 @@ static int fuse_get_user_pages(struct fuse_args_pages *ap, struct iov_iter *ii,
                        (PAGE_SIZE - ret) & (PAGE_SIZE - 1);
        }
 
+       ap->args.user_pages = true;
        if (write)
                ap->args.in_pages = true;
        else
index 198637b41e194e69712fa8725b3d269de8ef3a85..62fc7e2f94c815b1a15950af7884e1cac996a6ae 100644 (file)
@@ -256,6 +256,7 @@ struct fuse_args {
        bool nocreds:1;
        bool in_pages:1;
        bool out_pages:1;
+       bool user_pages:1;
        bool out_argvar:1;
        bool page_zeroing:1;
        bool page_replace:1;