Skip to content

Commit

Permalink
fuse: fix page stealing
Browse files Browse the repository at this point in the history
ANBZ: torvalds#208

commit 712a951 upstream.

It is possible to trigger a crash by splicing anon pipe bufs to the fuse
device.

The reason for this is that anon_pipe_buf_release() will reuse buf->page if
the refcount is 1, but that page might have already been stolen and its
flags modified (e.g. PG_lru added).

This happens in the unlikely case of fuse_dev_splice_write() getting around
to calling pipe_buf_release() after a page has been stolen, added to the
page cache and removed from the page cache.

Fix by calling pipe_buf_release() right after the page was inserted into
the page cache.  In this case the page has an elevated refcount so any
release function will know that the page isn't reusable.

Reported-by: Frank Dinoff <fdinoff@google.com>
Link: https://lore.kernel.org/r/CAAmZXrsGg2xsP1CK+cbuEMumtrqdvD-NKnWzhNcvn71RV3c1yw@mail.gmail.com/
Fixes: dd3bb14 ("fuse: support splice() writing to fuse device")
Cc: <stable@vger.kernel.org> # v2.6.35
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Hongnan Li <hongnan.li@linux.alibaba.com>
Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
  • Loading branch information
Miklos Szeredi authored and josephhz committed Dec 27, 2021
1 parent 6300e01 commit 6ba6332
Showing 1 changed file with 12 additions and 2 deletions.
14 changes: 12 additions & 2 deletions fs/fuse/dev.c
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,12 @@ static int fuse_try_move_page(struct fuse_copy_state *cs, struct page **pagep)
goto out_put_old;
}

/*
* Release while we have extra ref on stolen page. Otherwise
* anon_pipe_buf_release() might think the page can be reused.
*/
pipe_buf_release(cs->pipe, buf);

get_page(newpage);

if (!(buf->flags & PIPE_BUF_FLAG_LRU))
Expand Down Expand Up @@ -2072,8 +2078,12 @@ static ssize_t fuse_dev_splice_write(struct pipe_inode_info *pipe,

pipe_lock(pipe);
out_free:
for (idx = 0; idx < nbuf; idx++)
pipe_buf_release(pipe, &bufs[idx]);
for (idx = 0; idx < nbuf; idx++) {
struct pipe_buffer *buf = &bufs[idx];

if (buf->ops)
pipe_buf_release(pipe, buf);
}
pipe_unlock(pipe);

kvfree(bufs);
Expand Down

0 comments on commit 6ba6332

Please sign in to comment.