NFS: Fix a use after free in nfs_direct_join_group()
authorTrond Myklebust <trond.myklebust@hammerspace.com>
Wed, 9 Aug 2023 01:17:11 +0000 (21:17 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 30 Aug 2023 14:11:06 +0000 (16:11 +0200)
commit be2fd1560eb57b7298aa3c258ddcca0d53ecdea3 upstream.

Be more careful when tearing down the subrequests of an O_DIRECT write
as part of a retransmission.

Reported-by: Chris Mason <clm@fb.com>
Fixes: ed5d588fe47f ("NFS: Try to join page groups before an O_DIRECT retransmission")
Cc: stable@vger.kernel.org
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/nfs/direct.c

index 1707f46b1335ccf5d0669332f2d446dff09016ac..cf34d0c30945913dc35fa8307b4d120b0c6a2609 100644 (file)
@@ -474,20 +474,26 @@ out:
        return result;
 }
 
-static void
-nfs_direct_join_group(struct list_head *list, struct inode *inode)
+static void nfs_direct_join_group(struct list_head *list, struct inode *inode)
 {
-       struct nfs_page *req, *next;
+       struct nfs_page *req, *subreq;
 
        list_for_each_entry(req, list, wb_list) {
-               if (req->wb_head != req || req->wb_this_page == req)
+               if (req->wb_head != req)
                        continue;
-               for (next = req->wb_this_page;
-                               next != req->wb_head;
-                               next = next->wb_this_page) {
-                       nfs_list_remove_request(next);
-                       nfs_release_request(next);
-               }
+               subreq = req->wb_this_page;
+               if (subreq == req)
+                       continue;
+               do {
+                       /*
+                        * Remove subrequests from this list before freeing
+                        * them in the call to nfs_join_page_group().
+                        */
+                       if (!list_empty(&subreq->wb_list)) {
+                               nfs_list_remove_request(subreq);
+                               nfs_release_request(subreq);
+                       }
+               } while ((subreq = subreq->wb_this_page) != req);
                nfs_join_page_group(req, inode);
        }
 }