
From: Trond Myklebust <trond.myklebust@hammerspace.com> mainline inclusion from mainline-v6.17-rc3 commit 76d2e3890fb169168c73f2e4f8375c7cc24a765e category: bugfix bugzilla: https://gitee.com/src-openeuler/kernel/issues/ICWO2H CVE: CVE-2025-39697 Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?i... -------------------------------- After nfs_lock_and_join_requests() tests for whether the request is still attached to the mapping, nothing prevents a call to nfs_inode_remove_request() from succeeding until we actually lock the page group. The reason is that whoever called nfs_inode_remove_request() doesn't necessarily have a lock on the page group head. So in order to avoid races, let's take the page group lock earlier in nfs_lock_and_join_requests(), and hold it across the removal of the request in nfs_inode_remove_request(). Reported-by: Jeff Layton <jlayton@kernel.org> Tested-by: Joe Quanaim <jdq@meta.com> Tested-by: Andrew Steffen <aksteffen@meta.com> Reviewed-by: Jeff Layton <jlayton@kernel.org> Fixes: bd37d6fce184 ("NFSv4: Convert nfs_lock_and_join_requests() to use nfs_page_find_head_request()") Cc: stable@vger.kernel.org Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com> Conflicts: fs/nfs/write.c [Commit 6a6d4644ce93 ("NFS: Fix potential oops in nfs_inode_remove_request()") add the definition of nfsi in nfs_inode_remove_request(); commit e00ed89d7bd5 ("NFS: Refactor nfs_lock_and_join_requests()") add the definition of nfs_cancel_remove_inode().] Signed-off-by: Li Lingfeng <lilingfeng3@huawei.com> --- fs/nfs/pagelist.c | 9 +++++---- fs/nfs/write.c | 13 ++++++++----- include/linux/nfs_page.h | 1 + 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/fs/nfs/pagelist.c b/fs/nfs/pagelist.c index c5f862834b16..a4f971245d06 100644 --- a/fs/nfs/pagelist.c +++ b/fs/nfs/pagelist.c @@ -198,13 +198,14 @@ nfs_page_group_unlock(struct nfs_page *req) nfs_page_clear_headlock(req); } -/* - * nfs_page_group_sync_on_bit_locked +/** + * nfs_page_group_sync_on_bit_locked - Test if all requests have @bit set + * @req: request in page group + * @bit: PG_* bit that is used to sync page group * * must be called with page group lock held */ -static bool -nfs_page_group_sync_on_bit_locked(struct nfs_page *req, unsigned int bit) +bool nfs_page_group_sync_on_bit_locked(struct nfs_page *req, unsigned int bit) { struct nfs_page *head = req->wb_head; struct nfs_page *tmp; diff --git a/fs/nfs/write.c b/fs/nfs/write.c index 706ed79e23e2..c37654bbf7cd 100644 --- a/fs/nfs/write.c +++ b/fs/nfs/write.c @@ -498,16 +498,17 @@ nfs_lock_and_join_requests(struct page *page) goto try_again; } + ret = nfs_page_group_lock(head); + if (ret < 0) + goto release_request; + /* Ensure that nobody removed the request before we locked it */ if (head != nfs_page_private_request(page) && !PageSwapCache(page)) { + nfs_page_group_unlock(head); nfs_unlock_and_release_request(head); goto try_again; } - ret = nfs_page_group_lock(head); - if (ret < 0) - goto release_request; - /* lock each request in the page group */ total_bytes = head->wb_bytes; for (subreq = head->wb_this_page; subreq != head; @@ -803,7 +804,8 @@ static void nfs_inode_remove_request(struct nfs_page *req) struct nfs_inode *nfsi = NFS_I(inode); struct nfs_page *head; - if (nfs_page_group_sync_on_bit(req, PG_REMOVE)) { + nfs_page_group_lock(req); + if (nfs_page_group_sync_on_bit_locked(req, PG_REMOVE)) { head = req->wb_head; spin_lock(&mapping->private_lock); @@ -814,6 +816,7 @@ static void nfs_inode_remove_request(struct nfs_page *req) } spin_unlock(&mapping->private_lock); } + nfs_page_group_unlock(req); if (test_and_clear_bit(PG_INODE_REF, &req->wb_flags)) { nfs_release_request(req); diff --git a/include/linux/nfs_page.h b/include/linux/nfs_page.h index 5162fc1533c2..23ca6071695e 100644 --- a/include/linux/nfs_page.h +++ b/include/linux/nfs_page.h @@ -142,6 +142,7 @@ extern void nfs_unlock_and_release_request(struct nfs_page *); extern int nfs_page_group_lock(struct nfs_page *); extern void nfs_page_group_unlock(struct nfs_page *); extern bool nfs_page_group_sync_on_bit(struct nfs_page *, unsigned int); +extern bool nfs_page_group_sync_on_bit_locked(struct nfs_page *, unsigned int); extern int nfs_page_set_headlock(struct nfs_page *req); extern void nfs_page_clear_headlock(struct nfs_page *req); extern bool nfs_async_iocounter_wait(struct rpc_task *, struct nfs_lock_context *); -- 2.31.1