Skip to content

Commit 70015d5

Browse files
committed
NFS: Fix a race when updating an existing write
jira KERNEL-216 cve CVE-2025-39697 Rebuild_History Non-Buildable kernel-5.14.0-611.9.1.el9_7 commit-author Trond Myklebust <trond.myklebust@hammerspace.com> commit 76d2e38 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: bd37d6f ("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> (cherry picked from commit 76d2e38) Signed-off-by: Jonathan Maple <jmaple@ciq.com>
1 parent 191be67 commit 70015d5

File tree

3 files changed

+16
-23
lines changed

3 files changed

+16
-23
lines changed

fs/nfs/pagelist.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -253,13 +253,14 @@ nfs_page_group_unlock(struct nfs_page *req)
253253
nfs_page_clear_headlock(req);
254254
}
255255

256-
/*
257-
* nfs_page_group_sync_on_bit_locked
256+
/**
257+
* nfs_page_group_sync_on_bit_locked - Test if all requests have @bit set
258+
* @req: request in page group
259+
* @bit: PG_* bit that is used to sync page group
258260
*
259261
* must be called with page group lock held
260262
*/
261-
static bool
262-
nfs_page_group_sync_on_bit_locked(struct nfs_page *req, unsigned int bit)
263+
bool nfs_page_group_sync_on_bit_locked(struct nfs_page *req, unsigned int bit)
263264
{
264265
struct nfs_page *head = req->wb_head;
265266
struct nfs_page *tmp;

fs/nfs/write.c

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -153,20 +153,10 @@ nfs_page_set_inode_ref(struct nfs_page *req, struct inode *inode)
153153
}
154154
}
155155

156-
static int
157-
nfs_cancel_remove_inode(struct nfs_page *req, struct inode *inode)
156+
static void nfs_cancel_remove_inode(struct nfs_page *req, struct inode *inode)
158157
{
159-
int ret;
160-
161-
if (!test_bit(PG_REMOVE, &req->wb_flags))
162-
return 0;
163-
ret = nfs_page_group_lock(req);
164-
if (ret)
165-
return ret;
166158
if (test_and_clear_bit(PG_REMOVE, &req->wb_flags))
167159
nfs_page_set_inode_ref(req, inode);
168-
nfs_page_group_unlock(req);
169-
return 0;
170160
}
171161

172162
/**
@@ -584,19 +574,18 @@ static struct nfs_page *nfs_lock_and_join_requests(struct folio *folio)
584574
return ERR_PTR(ret);
585575
}
586576

577+
ret = nfs_page_group_lock(head);
578+
if (ret < 0)
579+
goto out_unlock;
580+
587581
/* Ensure that nobody removed the request before we locked it */
588582
if (head != folio->private) {
583+
nfs_page_group_unlock(head);
589584
nfs_unlock_and_release_request(head);
590585
goto retry;
591586
}
592587

593-
ret = nfs_cancel_remove_inode(head, inode);
594-
if (ret < 0)
595-
goto out_unlock;
596-
597-
ret = nfs_page_group_lock(head);
598-
if (ret < 0)
599-
goto out_unlock;
588+
nfs_cancel_remove_inode(head, inode);
600589

601590
/* lock each request in the page group */
602591
for (subreq = head->wb_this_page;
@@ -801,7 +790,8 @@ static void nfs_inode_remove_request(struct nfs_page *req)
801790
{
802791
struct nfs_inode *nfsi = NFS_I(nfs_page_to_inode(req));
803792

804-
if (nfs_page_group_sync_on_bit(req, PG_REMOVE)) {
793+
nfs_page_group_lock(req);
794+
if (nfs_page_group_sync_on_bit_locked(req, PG_REMOVE)) {
805795
struct folio *folio = nfs_page_to_folio(req->wb_head);
806796
struct address_space *mapping = folio->mapping;
807797

@@ -813,6 +803,7 @@ static void nfs_inode_remove_request(struct nfs_page *req)
813803
}
814804
spin_unlock(&mapping->private_lock);
815805
}
806+
nfs_page_group_unlock(req);
816807

817808
if (test_and_clear_bit(PG_INODE_REF, &req->wb_flags)) {
818809
atomic_long_dec(&nfsi->nrequests);

include/linux/nfs_page.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ extern void nfs_join_page_group(struct nfs_page *head,
160160
extern int nfs_page_group_lock(struct nfs_page *);
161161
extern void nfs_page_group_unlock(struct nfs_page *);
162162
extern bool nfs_page_group_sync_on_bit(struct nfs_page *, unsigned int);
163+
extern bool nfs_page_group_sync_on_bit_locked(struct nfs_page *, unsigned int);
163164
extern int nfs_page_set_headlock(struct nfs_page *req);
164165
extern void nfs_page_clear_headlock(struct nfs_page *req);
165166
extern bool nfs_async_iocounter_wait(struct rpc_task *, struct nfs_lock_context *);

0 commit comments

Comments
 (0)