[PATCH v2 03/10] nfsd: fix stale s2s_cp_stateids IDR entry for async COPY
From: Jeff Layton
Date: Thu Jul 09 2026 - 15:05:35 EST
For an async COPY, nfsd4_copy() called nfs4_init_copy_state() before
dup_copy_fields(), so the s2s_cp_stateids IDR was pointed at
&u->copy->cp_stateid -- memory in the per-rqstp COMPOUND buffer that is
reused by the next request. dup_copy_fields() copies only the value into
async_copy, so the IDR slot dangled at the transient buffer for the whole
background copy. Any IDR walker then dereferences reused request memory:
the laundromat reads cs_type from it and, if the bytes look like an
expired NFS4_COPYNOTIFY_STID, follows into
refcount_dec()/idr_remove()/kfree() on garbage; manage_cpntf_state() has
the same exposure via idr_find().
Duplicate the fields first, then register the stateid on the stable
async_copy. result->cb_stateid is unchanged.
Fixes: e0639dc5805a ("NFSD introduce async copy feature")
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Jeff Layton <jlayton@xxxxxxxxxx>
---
fs/nfsd/nfs4proc.c | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index fad01d67bf3f..1c674479d4ca 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -2272,11 +2272,21 @@ nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
async_copy->cp_src = kmalloc_obj(*async_copy->cp_src);
if (!async_copy->cp_src)
goto out_dec_async_copy_err;
- if (!nfs4_init_copy_state(nn, copy))
+ dup_copy_fields(copy, async_copy);
+ /*
+ * Register the copy stateid on the long-lived async_copy
+ * rather than on the transient COMPOUND argument buffer
+ * (&u->copy). nfs4_init_copy_state() installs a pointer to
+ * the copy_stateid_t in nn->s2s_cp_stateids, and that pointer
+ * outlives this call (it is removed only when the background
+ * copy finishes). Pointing it at &u->copy would leave a stale
+ * pointer into reused request memory that the laundromat and
+ * OFFLOAD_CANCEL later dereference.
+ */
+ if (!nfs4_init_copy_state(nn, async_copy))
goto out_dec_async_copy_err;
- memcpy(&result->cb_stateid, ©->cp_stateid.cs_stid,
+ memcpy(&result->cb_stateid, &async_copy->cp_stateid.cs_stid,
sizeof(result->cb_stateid));
- dup_copy_fields(copy, async_copy);
if ((READ_ONCE(copy->nf_dst->nf_file->f_mode) &
FMODE_NOCMTIME) != 0)
async_copy->attr_update = true;
--
2.55.0