Skip to content

Commit 202a343

Browse files
Trond Myklebustgregkh
authored andcommitted
NFS: Fix a race when updating an existing write
commit 76d2e38 upstream. 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> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent c5a6843 commit 202a343

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
/**
@@ -585,19 +575,18 @@ static struct nfs_page *nfs_lock_and_join_requests(struct folio *folio)
585575
}
586576
}
587577

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

594-
ret = nfs_cancel_remove_inode(head, inode);
595-
if (ret < 0)
596-
goto out_unlock;
597-
598-
ret = nfs_page_group_lock(head);
599-
if (ret < 0)
600-
goto out_unlock;
589+
nfs_cancel_remove_inode(head, inode);
601590

602591
/* lock each request in the page group */
603592
for (subreq = head->wb_this_page;
@@ -786,7 +775,8 @@ static void nfs_inode_remove_request(struct nfs_page *req)
786775
{
787776
struct nfs_inode *nfsi = NFS_I(nfs_page_to_inode(req));
788777

789-
if (nfs_page_group_sync_on_bit(req, PG_REMOVE)) {
778+
nfs_page_group_lock(req);
779+
if (nfs_page_group_sync_on_bit_locked(req, PG_REMOVE)) {
790780
struct folio *folio = nfs_page_to_folio(req->wb_head);
791781
struct address_space *mapping = folio->mapping;
792782

@@ -798,6 +788,7 @@ static void nfs_inode_remove_request(struct nfs_page *req)
798788
}
799789
spin_unlock(&mapping->i_private_lock);
800790
}
791+
nfs_page_group_unlock(req);
801792

802793
if (test_and_clear_bit(PG_INODE_REF, &req->wb_flags)) {
803794
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)