fuse: fix page dereference after free
authorMiklos Szeredi <mszeredi@redhat.com>
Fri, 18 Sep 2020 08:36:50 +0000 (10:36 +0200)
committerMiklos Szeredi <mszeredi@redhat.com>
Fri, 18 Sep 2020 08:36:50 +0000 (10:36 +0200)
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>
fs/fuse/dev.c

index 02b3c36..5078a6c 100644 (file)
@@ -785,15 +785,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;
@@ -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);
        if (err) {
                unlock_page(newpage);
-               return err;
+               goto out_put_old;
        }
 
        get_page(newpage);
@@ -852,14 +853,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);
@@ -868,10 +874,10 @@ out_fallback:
        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,
@@ -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)
                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;