Skip to content

Commit 30b7164

Browse files
committed
NFS: Fix a race when updating an existing write
jira VULN-136536 cve CVE-2025-39697 commit-author Trond Myklebust <trond.myklebust@hammerspace.com> commit 76d2e38 upstream-diff Used linux-6.6.y backport 181feb41f0b268e6288bf9a7b984624d7fe2031d for the clean cherry pick 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 181feb41f0b268e6288bf9a7b984624d7fe2031d) Signed-off-by: Marcin Wcisło <marcin.wcislo@conclusive.pl>
1 parent d099aac commit 30b7164

3 files changed

Lines changed: 31 additions & 50 deletions

File tree

fs/nfs/pagelist.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -271,13 +271,14 @@ nfs_page_group_unlock(struct nfs_page *req)
271271
nfs_page_clear_headlock(req);
272272
}
273273

274-
/*
275-
* nfs_page_group_sync_on_bit_locked
274+
/**
275+
* nfs_page_group_sync_on_bit_locked - Test if all requests have @bit set
276+
* @req: request in page group
277+
* @bit: PG_* bit that is used to sync page group
276278
*
277279
* must be called with page group lock held
278280
*/
279-
static bool
280-
nfs_page_group_sync_on_bit_locked(struct nfs_page *req, unsigned int bit)
281+
bool nfs_page_group_sync_on_bit_locked(struct nfs_page *req, unsigned int bit)
281282
{
282283
struct nfs_page *head = req->wb_head;
283284
struct nfs_page *tmp;

fs/nfs/write.c

Lines changed: 25 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -155,20 +155,10 @@ nfs_page_set_inode_ref(struct nfs_page *req, struct inode *inode)
155155
}
156156
}
157157

158-
static int
159-
nfs_cancel_remove_inode(struct nfs_page *req, struct inode *inode)
158+
static void nfs_cancel_remove_inode(struct nfs_page *req, struct inode *inode)
160159
{
161-
int ret;
162-
163-
if (!test_bit(PG_REMOVE, &req->wb_flags))
164-
return 0;
165-
ret = nfs_page_group_lock(req);
166-
if (ret)
167-
return ret;
168160
if (test_and_clear_bit(PG_REMOVE, &req->wb_flags))
169161
nfs_page_set_inode_ref(req, inode);
170-
nfs_page_group_unlock(req);
171-
return 0;
172162
}
173163

174164
static struct nfs_page *nfs_folio_private_request(struct folio *folio)
@@ -237,36 +227,6 @@ static struct nfs_page *nfs_folio_find_head_request(struct folio *folio)
237227
return req;
238228
}
239229

240-
static struct nfs_page *nfs_folio_find_and_lock_request(struct folio *folio)
241-
{
242-
struct inode *inode = folio_file_mapping(folio)->host;
243-
struct nfs_page *req, *head;
244-
int ret;
245-
246-
for (;;) {
247-
req = nfs_folio_find_head_request(folio);
248-
if (!req)
249-
return req;
250-
head = nfs_page_group_lock_head(req);
251-
if (head != req)
252-
nfs_release_request(req);
253-
if (IS_ERR(head))
254-
return head;
255-
ret = nfs_cancel_remove_inode(head, inode);
256-
if (ret < 0) {
257-
nfs_unlock_and_release_request(head);
258-
return ERR_PTR(ret);
259-
}
260-
/* Ensure that nobody removed the request before we locked it */
261-
if (head == nfs_folio_private_request(folio))
262-
break;
263-
if (folio_test_swapcache(folio))
264-
break;
265-
nfs_unlock_and_release_request(head);
266-
}
267-
return head;
268-
}
269-
270230
/* Adjust the file length if we're writing beyond the end */
271231
static void nfs_grow_file(struct folio *folio, unsigned int offset,
272232
unsigned int count)
@@ -620,20 +580,37 @@ static struct nfs_page *nfs_lock_and_join_requests(struct folio *folio)
620580
struct nfs_commit_info cinfo;
621581
int ret;
622582

623-
nfs_init_cinfo_from_inode(&cinfo, inode);
624583
/*
625584
* A reference is taken only on the head request which acts as a
626585
* reference to the whole page group - the group will not be destroyed
627586
* until the head reference is released.
628587
*/
629-
head = nfs_folio_find_and_lock_request(folio);
630-
if (IS_ERR_OR_NULL(head))
631-
return head;
588+
retry:
589+
head = nfs_folio_find_head_request(folio);
590+
if (!head)
591+
return NULL;
592+
593+
while (!nfs_lock_request(head)) {
594+
ret = nfs_wait_on_request(head);
595+
if (ret < 0) {
596+
nfs_release_request(head);
597+
return ERR_PTR(ret);
598+
}
599+
}
632600

633601
ret = nfs_page_group_lock(head);
634602
if (ret < 0)
635603
goto out_unlock;
636604

605+
/* Ensure that nobody removed the request before we locked it */
606+
if (head != folio->private && !folio_test_swapcache(folio)) {
607+
nfs_page_group_unlock(head);
608+
nfs_unlock_and_release_request(head);
609+
goto retry;
610+
}
611+
612+
nfs_cancel_remove_inode(head, inode);
613+
637614
/* lock each request in the page group */
638615
for (subreq = head->wb_this_page;
639616
subreq != head;
@@ -838,7 +815,8 @@ static void nfs_inode_remove_request(struct nfs_page *req)
838815
{
839816
struct nfs_inode *nfsi = NFS_I(nfs_page_to_inode(req));
840817

841-
if (nfs_page_group_sync_on_bit(req, PG_REMOVE)) {
818+
nfs_page_group_lock(req);
819+
if (nfs_page_group_sync_on_bit_locked(req, PG_REMOVE)) {
842820
struct folio *folio = nfs_page_to_folio(req->wb_head);
843821
struct address_space *mapping = folio_file_mapping(folio);
844822

@@ -850,6 +828,7 @@ static void nfs_inode_remove_request(struct nfs_page *req)
850828
}
851829
spin_unlock(&mapping->private_lock);
852830
}
831+
nfs_page_group_unlock(req);
853832

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

include/linux/nfs_page.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ extern void nfs_join_page_group(struct nfs_page *head,
162162
extern int nfs_page_group_lock(struct nfs_page *);
163163
extern void nfs_page_group_unlock(struct nfs_page *);
164164
extern bool nfs_page_group_sync_on_bit(struct nfs_page *, unsigned int);
165+
extern bool nfs_page_group_sync_on_bit_locked(struct nfs_page *, unsigned int);
165166
extern int nfs_page_set_headlock(struct nfs_page *req);
166167
extern void nfs_page_clear_headlock(struct nfs_page *req);
167168
extern bool nfs_async_iocounter_wait(struct rpc_task *, struct nfs_lock_context *);

0 commit comments

Comments
 (0)