From: Miklos Szeredi mszeredi@redhat.com
commit d78092e4937de9ce55edcb4ee4c5e3c707be0190 upstream.
fix #32833505
After unlock_request() pages from the ap->pages[] array may be put (e.g. by aborting the connection) and the pages can be freed.
Prevent use after free by grabbing a reference to the page before calling unlock_request().
The original patch was created by Pradeep P V K.
Reported-by: Pradeep P V K ppvk@codeaurora.org Cc: stable@vger.kernel.org Signed-off-by: Miklos Szeredi mszeredi@redhat.com Signed-off-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Signed-off-by: Peng Tao tao.peng@linux.alibaba.com --- fs/fuse/dev.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-)
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 3202ead..8ad877a 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -877,15 +877,16 @@ static int fuse_try_move_page(struct fuse_copy_state *cs, struct page **pagep) struct page *newpage; struct pipe_buffer *buf = cs->pipebufs;
+ get_page(oldpage); err = unlock_request(cs->req); if (err) - return err; + goto out_put_old;
fuse_copy_finish(cs);
err = pipe_buf_confirm(cs->pipe, buf); if (err) - return err; + goto out_put_old;
BUG_ON(!cs->nr_segs); cs->currbuf = buf; @@ -925,7 +926,7 @@ static int fuse_try_move_page(struct fuse_copy_state *cs, struct page **pagep) err = replace_page_cache_page(oldpage, newpage, GFP_KERNEL); if (err) { unlock_page(newpage); - return err; + goto out_put_old; }
get_page(newpage); @@ -944,14 +945,19 @@ static int fuse_try_move_page(struct fuse_copy_state *cs, struct page **pagep) if (err) { unlock_page(newpage); put_page(newpage); - return err; + goto out_put_old; }
unlock_page(oldpage); + /* Drop ref for ap->pages[] array */ put_page(oldpage); cs->len = 0;
- return 0; + err = 0; +out_put_old: + /* Drop ref obtained in this function */ + put_page(oldpage); + return err;
out_fallback_unlock: unlock_page(newpage); @@ -960,10 +966,10 @@ static int fuse_try_move_page(struct fuse_copy_state *cs, struct page **pagep) cs->offset = buf->offset;
err = lock_request(cs->req); - if (err) - return err; + if (!err) + err = 1;
- return 1; + goto out_put_old; }
static int fuse_ref_page(struct fuse_copy_state *cs, struct page *page, @@ -975,14 +981,16 @@ static int fuse_ref_page(struct fuse_copy_state *cs, struct page *page, if (cs->nr_segs == cs->pipe->buffers) return -EIO;
+ get_page(page); err = unlock_request(cs->req); - if (err) + if (err) { + put_page(page); return err; + }
fuse_copy_finish(cs);
buf = cs->pipebufs; - get_page(page); buf->page = page; buf->offset = offset; buf->len = count;