Skip to content

Commit 778c420

Browse files
committed
NFS: Fix a race when updating an existing write
jira VULN-136535 cve CVE-2025-39697 commit-author Trond Myklebust <[email protected]> commit 76d2e38 upstream-diff Used linux-5.15.y backport f230d40147cc37eb3aef4d50e2e2c06ea73d9a77, which applied cleanly 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 <[email protected]> Tested-by: Joe Quanaim <[email protected]> Tested-by: Andrew Steffen <[email protected]> Reviewed-by: Jeff Layton <[email protected]> Fixes: bd37d6f ("NFSv4: Convert nfs_lock_and_join_requests() to use nfs_page_find_head_request()") Cc: [email protected] Signed-off-by: Trond Myklebust <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]> (cherry picked from commit f230d40147cc37eb3aef4d50e2e2c06ea73d9a77) Signed-off-by: Marcin Wcisło <[email protected]>
1 parent 381e0b9 commit 778c420

File tree

3 files changed

+29
-47
lines changed

3 files changed

+29
-47
lines changed

fs/nfs/pagelist.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,13 +233,14 @@ nfs_page_group_unlock(struct nfs_page *req)
233233
nfs_page_clear_headlock(req);
234234
}
235235

236-
/*
237-
* nfs_page_group_sync_on_bit_locked
236+
/**
237+
* nfs_page_group_sync_on_bit_locked - Test if all requests have @bit set
238+
* @req: request in page group
239+
* @bit: PG_* bit that is used to sync page group
238240
*
239241
* must be called with page group lock held
240242
*/
241-
static bool
242-
nfs_page_group_sync_on_bit_locked(struct nfs_page *req, unsigned int bit)
243+
bool nfs_page_group_sync_on_bit_locked(struct nfs_page *req, unsigned int bit)
243244
{
244245
struct nfs_page *head = req->wb_head;
245246
struct nfs_page *tmp;

fs/nfs/write.c

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

157-
static int
158-
nfs_cancel_remove_inode(struct nfs_page *req, struct inode *inode)
157+
static void nfs_cancel_remove_inode(struct nfs_page *req, struct inode *inode)
159158
{
160-
int ret;
161-
162-
if (!test_bit(PG_REMOVE, &req->wb_flags))
163-
return 0;
164-
ret = nfs_page_group_lock(req);
165-
if (ret)
166-
return ret;
167159
if (test_and_clear_bit(PG_REMOVE, &req->wb_flags))
168160
nfs_page_set_inode_ref(req, inode);
169-
nfs_page_group_unlock(req);
170-
return 0;
171161
}
172162

173163
static struct nfs_page *
@@ -239,36 +229,6 @@ static struct nfs_page *nfs_page_find_head_request(struct page *page)
239229
return req;
240230
}
241231

242-
static struct nfs_page *nfs_find_and_lock_page_request(struct page *page)
243-
{
244-
struct inode *inode = page_file_mapping(page)->host;
245-
struct nfs_page *req, *head;
246-
int ret;
247-
248-
for (;;) {
249-
req = nfs_page_find_head_request(page);
250-
if (!req)
251-
return req;
252-
head = nfs_page_group_lock_head(req);
253-
if (head != req)
254-
nfs_release_request(req);
255-
if (IS_ERR(head))
256-
return head;
257-
ret = nfs_cancel_remove_inode(head, inode);
258-
if (ret < 0) {
259-
nfs_unlock_and_release_request(head);
260-
return ERR_PTR(ret);
261-
}
262-
/* Ensure that nobody removed the request before we locked it */
263-
if (head == nfs_page_private_request(page))
264-
break;
265-
if (PageSwapCache(page))
266-
break;
267-
nfs_unlock_and_release_request(head);
268-
}
269-
return head;
270-
}
271-
272232
/* Adjust the file length if we're writing beyond the end */
273233
static void nfs_grow_file(struct page *page, unsigned int offset, unsigned int count)
274234
{
@@ -625,14 +585,32 @@ nfs_lock_and_join_requests(struct page *page)
625585
* reference to the whole page group - the group will not be destroyed
626586
* until the head reference is released.
627587
*/
628-
head = nfs_find_and_lock_page_request(page);
588+
retry:
589+
head = nfs_page_find_head_request(page);
629590
if (IS_ERR_OR_NULL(head))
630591
return head;
631592

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+
}
600+
632601
ret = nfs_page_group_lock(head);
633602
if (ret < 0)
634603
goto out_unlock;
635604

605+
/* Ensure that nobody removed the request before we locked it */
606+
if (head != nfs_page_private_request(page) && !PageSwapCache(page)) {
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+
636614
/* lock each request in the page group */
637615
for (subreq = head->wb_this_page;
638616
subreq != head;
@@ -850,7 +828,8 @@ static void nfs_inode_remove_request(struct nfs_page *req)
850828
struct nfs_inode *nfsi = NFS_I(inode);
851829
struct nfs_page *head;
852830

853-
if (nfs_page_group_sync_on_bit(req, PG_REMOVE)) {
831+
nfs_page_group_lock(req);
832+
if (nfs_page_group_sync_on_bit_locked(req, PG_REMOVE)) {
854833
head = req->wb_head;
855834

856835
spin_lock(&mapping->private_lock);
@@ -861,6 +840,7 @@ static void nfs_inode_remove_request(struct nfs_page *req)
861840
}
862841
spin_unlock(&mapping->private_lock);
863842
}
843+
nfs_page_group_unlock(req);
864844

865845
if (test_and_clear_bit(PG_INODE_REF, &req->wb_flags)) {
866846
nfs_release_request(req);

include/linux/nfs_page.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ extern void nfs_join_page_group(struct nfs_page *head, struct inode *inode);
148148
extern int nfs_page_group_lock(struct nfs_page *);
149149
extern void nfs_page_group_unlock(struct nfs_page *);
150150
extern bool nfs_page_group_sync_on_bit(struct nfs_page *, unsigned int);
151+
extern bool nfs_page_group_sync_on_bit_locked(struct nfs_page *, unsigned int);
151152
extern int nfs_page_set_headlock(struct nfs_page *req);
152153
extern void nfs_page_clear_headlock(struct nfs_page *req);
153154
extern bool nfs_async_iocounter_wait(struct rpc_task *, struct nfs_lock_context *);

0 commit comments

Comments
 (0)