Re: [PATCH v2 03/10] nfsd: fix stale s2s_cp_stateids IDR entry for async COPY
From: Chuck Lever
Date: Thu Jul 09 2026 - 17:19:50 EST
On Thu, Jul 9, 2026, at 2:47 PM, Jeff Layton wrote:
> 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
Sashiko spotted some severe issues:
- [Critical] Async copy cancellation paths remove the copy from `clp->async_copies` and free the `async_copy` object, but fail to call `nfs4_free_copy_state()`. This leaks the IDR entry in `nn->s2s_cp_stateids` and leaves it pointing to freed memory, causing a remote Use-After-Free (UAF) DoS.
- [High] Reordering `dup_copy_fields()` before `nfs4_init_copy_state()` leaves `async_copy->cp_res.cb_stateid` uninitialized, causing the server to send an invalid (all-zero) stateid in the `CB_OFFLOAD` callback.
The second one was also spotted by gpt-5.6-sol.
The new code comment here is a design breadcrumb. Probably not useful
to carry it as part of the code, but YMMV.
--
Chuck Lever