fuse: fix page dereference after free

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>
This commit is contained in:
Miklos Szeredi 2020-09-18 10:36:50 +02:00
parent 9a752d18c8
commit d78092e493
1 changed files with 18 additions and 10 deletions

View File

@ -785,15 +785,16 @@ static int fuse_try_move_page(struct fuse_copy_state *cs, struct page **pagep)
struct page *newpage; struct page *newpage;
struct pipe_buffer *buf = cs->pipebufs; struct pipe_buffer *buf = cs->pipebufs;
get_page(oldpage);
err = unlock_request(cs->req); err = unlock_request(cs->req);
if (err) if (err)
return err; goto out_put_old;
fuse_copy_finish(cs); fuse_copy_finish(cs);
err = pipe_buf_confirm(cs->pipe, buf); err = pipe_buf_confirm(cs->pipe, buf);
if (err) if (err)
return err; goto out_put_old;
BUG_ON(!cs->nr_segs); BUG_ON(!cs->nr_segs);
cs->currbuf = buf; cs->currbuf = buf;
@ -833,7 +834,7 @@ static int fuse_try_move_page(struct fuse_copy_state *cs, struct page **pagep)
err = replace_page_cache_page(oldpage, newpage, GFP_KERNEL); err = replace_page_cache_page(oldpage, newpage, GFP_KERNEL);
if (err) { if (err) {
unlock_page(newpage); unlock_page(newpage);
return err; goto out_put_old;
} }
get_page(newpage); get_page(newpage);
@ -852,14 +853,19 @@ static int fuse_try_move_page(struct fuse_copy_state *cs, struct page **pagep)
if (err) { if (err) {
unlock_page(newpage); unlock_page(newpage);
put_page(newpage); put_page(newpage);
return err; goto out_put_old;
} }
unlock_page(oldpage); unlock_page(oldpage);
/* Drop ref for ap->pages[] array */
put_page(oldpage); put_page(oldpage);
cs->len = 0; cs->len = 0;
return 0; err = 0;
out_put_old:
/* Drop ref obtained in this function */
put_page(oldpage);
return err;
out_fallback_unlock: out_fallback_unlock:
unlock_page(newpage); unlock_page(newpage);
@ -868,10 +874,10 @@ out_fallback:
cs->offset = buf->offset; cs->offset = buf->offset;
err = lock_request(cs->req); err = lock_request(cs->req);
if (err) if (!err)
return err; err = 1;
return 1; goto out_put_old;
} }
static int fuse_ref_page(struct fuse_copy_state *cs, struct page *page, static int fuse_ref_page(struct fuse_copy_state *cs, struct page *page,
@ -883,14 +889,16 @@ static int fuse_ref_page(struct fuse_copy_state *cs, struct page *page,
if (cs->nr_segs >= cs->pipe->max_usage) if (cs->nr_segs >= cs->pipe->max_usage)
return -EIO; return -EIO;
get_page(page);
err = unlock_request(cs->req); err = unlock_request(cs->req);
if (err) if (err) {
put_page(page);
return err; return err;
}
fuse_copy_finish(cs); fuse_copy_finish(cs);
buf = cs->pipebufs; buf = cs->pipebufs;
get_page(page);
buf->page = page; buf->page = page;
buf->offset = offset; buf->offset = offset;
buf->len = count; buf->len = count;