FAILED: patch "[PATCH] nfsd: fix UAF in async copy cancel and shutdown" failed to apply to 6.18-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 6.18-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.18.y
git checkout FETCH_HEAD
git cherry-pick -x 62c0f6eaf050bb9284c1f9cac6ed1770092e6b95
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026090332-exact-stature-60b9@gregkh' --subject-prefix 'PATCH 6.18.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 62c0f6eaf050bb9284c1f9cac6ed1770092e6b95 Mon Sep 17 00:00:00 2001
From: Jeff Layton <[email protected]>
Date: Fri, 10 Jul 2026 10:00:06 -0400
Subject: [PATCH] nfsd: fix UAF in async copy cancel and shutdown
An async copy could be freed or used after free while a teardown caller (OFFLOAD_CANCEL, nfsd4_shutdown_copy, nfsd4_cancel_copy_by_sb) raced the copy kthread:
- find_async_copy() bumped copy->refcount but left the copy on clp->async_copies, so the reaper's cleanup_async_copy() could run release_copy_files() concurrently with a cancel/shutdown caller. Both put and NULL nf_src/nf_dst without a common lock, double-putting the nfsd_file and freeing it early.
- nfsd4_do_async_copy() set NFSD4_COPY_F_STOPPED before its final uses of the copy (nfsd_update_cmtime_attr() on copy->nf_dst, nfsd4_send_cb_offload()). nfsd4_stop_copy() treats a set STOPPED bit as "kthread done, skip kthread_stop()", so a teardown caller ran release_copy_files() -- which puts and NULLs nf_dst -- while the kthread still dereferenced it (NULL/UAF).
- copy->copy_task was never pinned. The one-shot kthread self-reaps on return, so kthread_stop()'s get_task_struct() could touch a freed task_struct.
- co_cb is embedded in the copy, but nfsd4_send_cb_offload() held a reference only on the client, so a concurrent teardown could free the copy while the CB_OFFLOAD callback was in flight.
Fix the teardown lifetime as a whole:
- find_async_copy() unlinks the copy (clear cp_clp, list_del_init) under async_lock; the cancel, shutdown, and sb-cancel paths drop the list-membership reference via nfs4_put_copy() after nfsd4_stop_copy(). Drop the now-redundant list_del fixup from cleanup_async_copy().
- Because unlinking hides the copy from the reaper, its cleanup_async_copy() can no longer remove the copy's s2s_cp_stateids entry; the cancel/shutdown/sb-cancel paths now call nfs4_free_copy_state() themselves (while cp_clp is still valid) so the entry does not dangle at freed memory for the laundromat and manage_cpntf_state() to dereference.
- Give the kthread its own reference, taken in nfsd4_copy() before wake_up_process() and dropped at the end of nfsd4_do_async_copy(); call wake_up_process() before list_add().
- Pin the task_struct with get_task_struct() in nfsd4_copy(), released in nfs4_put_copy(), so kthread_stop() is safe whenever the kthread exits. Set NFSD4_COPY_F_STOPPED only in nfsd4_stop_copy(), which now always kthread_stop()s before release_copy_files(); completion is still reported via NFSD4_COPY_F_COMPLETED, so nfsd4_has_active_async_copies() is unaffected. Each teardown caller removes the copy from clp->async_copies first, so kthread_stop() runs exactly once.
- Take a copy reference in nfsd4_send_cb_offload(), dropped in nfsd4_cb_offload_release(). The kthread still holds its own reference there, so the refcount_inc() cannot race the final free.
- Read cp_clp with smp_load_acquire() to pair with the unordered set_bit()/clear_bit() writers (Documentation/atomic_bitops.rst).
fs/nfsd/nfs4proc.c +85 -35
@@ -1519,6 +1519,9 @@ static void nfs4_put_copy(struct nfsd4_copy *copy) {if (!refcount_dec_and_test (©->refcount))return ; +/* Drop the task_struct pinned in nfsd4_copy(); NULL on sync copies. */ +if (copy->copy_task) +put_task_struct (copy->copy_task);kfree (copy->cp_src);kfree (copy); }
@@ -1528,20 +1531,18 @@ static void release_copy_files(struct nfsd4_copy *copy);static void nfsd4_stop_copy (struct nfsd4_copy *copy) {trace_nfsd_copy_async_cancel (copy); -if (!test_and_set_bit (NFSD4_COPY_F_STOPPED , ©->cp_flags)) { -kthread_stop (copy->copy_task); -if (!test_bit (NFSD4_COPY_F_CB_ERROR , ©->cp_flags)) - copy->nfserr = nfs_ok; -set_bit (NFSD4_COPY_F_COMPLETED , ©->cp_flags); - } -/* -* The copy was removed from async_copies before this function -* was called, so the reaper cannot clean it up. Release files -* here regardless of who won the STOPPED race. If the thread -* set STOPPED, it has finished using the files. If STOPPED -* was set here, kthread_stop() waited for the thread to exit. +* Join the kthread before releasing its resources. The task_struct is +* pinned in nfsd4_copy(), so kthread_stop() is safe even after the +* one-shot kthread has exited. The caller already unlinked the copy, +* so this runs once per copy. */ +set_bit (NFSD4_COPY_F_STOPPED , ©->cp_flags); +kthread_stop (copy->copy_task); +if (!test_bit (NFSD4_COPY_F_CB_ERROR , ©->cp_flags)) + copy->nfserr = nfs_ok; +set_bit (NFSD4_COPY_F_COMPLETED , ©->cp_flags); +release_copy_files (copy);nfs4_put_copy (copy); }
@@ -1555,7 +1556,13 @@ static struct nfsd4_copy *nfsd4_unhash_copy(struct nfs4_client *clp) copy = list_first_entry(&clp->async_copies, struct nfsd4_copy, copies);refcount_inc (©->refcount); - copy->cp_clp =NULL ; +/* +* Unlinking hides the copy from the reaper, so drop its +* s2s_cp_stateids entry here while cp_clp is still valid. +*/ +nfs4_free_copy_state (copy); +/* Pairs with smp_load_acquire() in nfsd4_send_cb_offload(). */ +smp_store_release (©->cp_clp,NULL );if (!list_empty (©->copies))list_del_init (©->copies); }
@@ -1567,8 +1574,11 @@ void nfsd4_shutdown_copy(struct nfs4_client *clp) {struct nfsd4_copy *copy; -while ((copy =nfsd4_unhash_copy (clp)) !=NULL ) +while ((copy =nfsd4_unhash_copy (clp)) !=NULL ) {nfsd4_stop_copy (copy); +/* Reaper can't reach the unhashed copy; drop its membership ref. */ +nfs4_put_copy (copy); + } } static bool nfsd4_copy_on_sb(const struct nfsd4_copy *copy,
@@ -1636,7 +1646,11 @@ void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb)struct nfs4_client *clp = copy->cp_clp;list_del_init (©->copies); +/* Reaper can't reach it; drop the s2s entry while cp_clp is valid. */ +nfs4_free_copy_state (copy);nfsd4_stop_copy (copy); +/* Drop the membership ref the reaper would have dropped. */ +nfs4_put_copy (copy);nfsd4_put_client (clp); } }
@@ -1931,6 +1945,8 @@ static void nfsd4_cb_offload_release(struct nfsd4_callback *cb)set_bit (NFSD4_COPY_F_OFFLOAD_DONE , ©->cp_flags);nfsd4_put_client (cb->cb_clp); +/* Drop the copy reference taken in nfsd4_send_cb_offload(). */ +nfs4_put_copy (copy); } static int nfsd4_cb_offload_done(struct nfsd4_callback *cb,
@@ -2062,28 +2078,29 @@ static void release_copy_files(struct nfsd4_copy *copy) } } +/* +* Called from the reaper and from nfsd4_copy()'s error path; in both +* cases the copy is already unreachable from clp->async_copies. +*/ static void cleanup_async_copy (struct nfsd4_copy *copy) {nfs4_free_copy_state (copy);release_copy_files (copy); -if (copy->cp_clp) { -spin_lock (©->cp_clp->async_lock); -if (!list_empty (©->copies)) -list_del_init (©->copies); -spin_unlock (©->cp_clp->async_lock); - }nfs4_put_copy (copy); }static void nfsd4_send_cb_offload (struct nfsd4_copy *copy) {struct nfsd4_cb_offload *cbo = ©->cp_cb_offload; -struct nfs4_client *clp = copy->cp_clp; +struct nfs4_client *clp;/* -* cp_clp is NULL when called via nfsd4_shutdown_copy() during -* client destruction. Skip the callback; the client is gone. +* Pairs with smp_store_release(&cp_clp) in find_async_copy() and +* nfsd4_unhash_copy(); the set_bit/clear_bit writers are unordered. +* cp_clp is NULL once the copy was canceled; skip the callback, the +* canceling path owns the notification. */ + clp =smp_load_acquire (©->cp_clp);if (!clp) {set_bit (NFSD4_COPY_F_OFFLOAD_DONE , ©->cp_flags);return ;
@@ -2095,10 +2112,12 @@ static void nfsd4_send_cb_offload(struct nfsd4_copy *copy) cbo->co_retries =5 ;/* -* Hold a reference on the client while the callback is in flight. -* Released in nfsd4_cb_offload_release(). +* Hold the client and the copy across the in-flight callback; co_cb is +* embedded in the copy, so it must outlive the callback. Both are +* dropped in nfsd4_cb_offload_release(). */ kref_get (&clp->cl_nfsdfs.cl_ref); +refcount_inc (©->refcount);nfsd4_init_cb (&cbo->co_cb, clp, &nfsd4_cb_offload_ops,NFSPROC4_CLNT_CB_OFFLOAD );
@@ -2150,16 +2169,20 @@ static int nfsd4_do_async_copy(void *data) do_callback:if (!test_bit (NFSD4_COPY_F_CB_ERROR , ©->cp_flags)) copy->nfserr = nfserr; -/* The kthread exits forthwith. Ensure that a subsequent -* OFFLOAD_CANCEL won't try to kill it again. */ -set_bit (NFSD4_COPY_F_STOPPED , ©->cp_flags); - +/* +* Don't set NFSD4_COPY_F_STOPPED here: it tells a teardown caller it +* may skip kthread_stop(), which would then release nf_dst and the +* client while still in use. Only nfsd4_stop_copy() sets it, after +* joining. +*/ set_bit (NFSD4_COPY_F_COMPLETED , ©->cp_flags);trace_nfsd_copy_async_done (copy);atomic_dec (©->cp_nn->pending_async_copies);if (copy->cp_res.wr_bytes_written >0 && copy->attr_update)nfsd_update_cmtime_attr (copy->nf_dst->nf_file,0 );nfsd4_send_cb_offload (copy); +/* Drop the kthread's reference (taken in nfsd4_copy()); copy may be freed after this. */ +nfs4_put_copy (copy);return 0 ; }
@@ -2198,6 +2221,8 @@ nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,memcpy (©->fh, &cstate->current_fh.fh_handle,sizeof (struct knfsd_fh ));if (nfsd4_copy_is_async (copy)) { +struct task_struct *task; + async_copy = kzalloc_obj(struct nfsd4_copy);if (!async_copy) goto out_err;
@@ -2225,15 +2250,27 @@ nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, NFS4_MAX_SESSIONID_LEN); async_copy->cp_cb_offload.co_referring_slotid = cstate->slot->sl_index; async_copy->cp_cb_offload.co_referring_seqno = cstate->slot->sl_seqid; - async_copy->copy_task =kthread_create (nfsd4_do_async_copy, - async_copy,"%s" ,"copy thread" ); -if (IS_ERR (async_copy->copy_task)) + task =kthread_create (nfsd4_do_async_copy, async_copy, +"%s" ,"copy thread" ); +if (IS_ERR (task)) goto out_dec_async_copy_err; +/* +* Pin the task_struct so kthread_stop() is safe after this +* one-shot kthread exits. Released by nfs4_put_copy(). +*/ +get_task_struct (task); + async_copy->copy_task = task; +/* +* Take the kthread's ref and wake it before publishing, so the +* publisher touches async_copy no further and teardown can +* drain it. +*/ +refcount_inc (&async_copy->refcount); +wake_up_process (async_copy->copy_task);spin_lock (&async_copy->cp_clp->async_lock);list_add (&async_copy->copies, &async_copy->cp_clp->async_copies);spin_unlock (&async_copy->cp_clp->async_lock); -wake_up_process (async_copy->copy_task); status = nfs_ok; }else { status = nfsd4_do_copy(copy, copy->nf_src->nf_file,
@@ -2287,8 +2324,18 @@ find_async_copy(struct nfs4_client *clp, stateid_t *stateid)spin_lock (&clp->async_lock); copy =find_async_copy_locked (clp, stateid); -if (copy) +if (copy) {refcount_inc (©->refcount); +nfs4_free_copy_state (copy); +/* +* Mirror nfsd4_unhash_copy(): unlink and clear cp_clp under +* async_lock so the reaper can't reach it. Caller drops the +* membership ref after nfsd4_stop_copy(). +*/ +smp_store_release (©->cp_clp,NULL ); +if (!list_empty (©->copies)) +list_del_init (©->copies); + }spin_unlock (&clp->async_lock);return copy; }
@@ -2307,8 +2354,11 @@ nfsd4_offload_cancel(struct svc_rqst *rqstp,struct nfsd_net *nn =net_generic (SVC_NET (rqstp), nfsd_net_id);return manage_cpntf_state (nn, &os->stateid, clp,NULL ); - }else + }else {nfsd4_stop_copy (copy); +/* find_async_copy() unlinked it from the reaper; drop the membership ref. */ +nfs4_put_copy (copy); + }return nfs_ok; }
Sasha Levin
maintainerPATCH 1/1 nfsd: fix UAF in async copy cancel and shutdown
From: Jeff Layton <[email protected]>
[ Upstream commit 62c0f6eaf050bb9284c1f9cac6ed1770092e6b95 ]
An async copy could be freed or used after free while a teardown caller (OFFLOAD_CANCEL, nfsd4_shutdown_copy, nfsd4_cancel_copy_by_sb) raced the copy kthread:
- find_async_copy() bumped copy->refcount but left the copy on clp->async_copies, so the reaper's cleanup_async_copy() could run release_copy_files() concurrently with a cancel/shutdown caller. Both put and NULL nf_src/nf_dst without a common lock, double-putting the nfsd_file and freeing it early.
- nfsd4_do_async_copy() set NFSD4_COPY_F_STOPPED before its final uses of the copy (nfsd_update_cmtime_attr() on copy->nf_dst, nfsd4_send_cb_offload()). nfsd4_stop_copy() treats a set STOPPED bit as "kthread done, skip kthread_stop()", so a teardown caller ran release_copy_files() -- which puts and NULLs nf_dst -- while the kthread still dereferenced it (NULL/UAF).
- copy->copy_task was never pinned. The one-shot kthread self-reaps on return, so kthread_stop()'s get_task_struct() could touch a freed task_struct.
- co_cb is embedded in the copy, but nfsd4_send_cb_offload() held a reference only on the client, so a concurrent teardown could free the copy while the CB_OFFLOAD callback was in flight.
Fix the teardown lifetime as a whole:
- find_async_copy() unlinks the copy (clear cp_clp, list_del_init) under async_lock; the cancel, shutdown, and sb-cancel paths drop the list-membership reference via nfs4_put_copy() after nfsd4_stop_copy(). Drop the now-redundant list_del fixup from cleanup_async_copy().
- Because unlinking hides the copy from the reaper, its cleanup_async_copy() can no longer remove the copy's s2s_cp_stateids entry; the cancel/shutdown/sb-cancel paths now call nfs4_free_copy_state() themselves (while cp_clp is still valid) so the entry does not dangle at freed memory for the laundromat and manage_cpntf_state() to dereference.
- Give the kthread its own reference, taken in nfsd4_copy() before wake_up_process() and dropped at the end of nfsd4_do_async_copy(); call wake_up_process() before list_add().
- Pin the task_struct with get_task_struct() in nfsd4_copy(), released in nfs4_put_copy(), so kthread_stop() is safe whenever the kthread exits. Set NFSD4_COPY_F_STOPPED only in nfsd4_stop_copy(), which now always kthread_stop()s before release_copy_files(); completion is still reported via NFSD4_COPY_F_COMPLETED, so nfsd4_has_active_async_copies() is unaffected. Each teardown caller removes the copy from clp->async_copies first, so kthread_stop() runs exactly once.
- Take a copy reference in nfsd4_send_cb_offload(), dropped in nfsd4_cb_offload_release(). The kthread still holds its own reference there, so the refcount_inc() cannot race the final free.
- Read cp_clp with smp_load_acquire() to pair with the unordered set_bit()/clear_bit() writers (Documentation/atomic_bitops.rst).
--- fs/nfsd/nfs4proc.c | 122 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 97 insertions(+), 25 deletions(-)
fs/nfsd/nfs4proc.c +97 -25
@@ -1428,18 +1428,30 @@ static void nfs4_put_copy(struct nfsd4_copy *copy) {if (!refcount_dec_and_test (©->refcount))return ; +/* Drop the task_struct pinned in nfsd4_copy(); NULL on sync copies. */ +if (copy->copy_task) +put_task_struct (copy->copy_task);kfree (copy->cp_src);kfree (copy); } +static void release_copy_files (struct nfsd4_copy *copy); +static void nfsd4_stop_copy (struct nfsd4_copy *copy) {trace_nfsd_copy_async_cancel (copy); -if (!test_and_set_bit (NFSD4_COPY_F_STOPPED , ©->cp_flags)) { -kthread_stop (copy->copy_task); - copy->nfserr = nfs_ok; -set_bit (NFSD4_COPY_F_COMPLETED , ©->cp_flags); - } +/* +* Join the kthread before releasing its resources. The task_struct is +* pinned in nfsd4_copy(), so kthread_stop() is safe even after the +* one-shot kthread has exited. The caller already unlinked the copy, +* so this runs once per copy. +*/ +set_bit (NFSD4_COPY_F_STOPPED , ©->cp_flags); +kthread_stop (copy->copy_task); + copy->nfserr = nfs_ok; +set_bit (NFSD4_COPY_F_COMPLETED , ©->cp_flags); + +release_copy_files (copy);nfs4_put_copy (copy); }
@@ -1452,7 +1464,13 @@ static struct nfsd4_copy *nfsd4_unhash_copy(struct nfs4_client *clp) copy = list_first_entry(&clp->async_copies, struct nfsd4_copy, copies);refcount_inc (©->refcount); - copy->cp_clp =NULL ; +/* +* Unlinking hides the copy from the reaper, so drop its +* s2s_cp_stateids entry here while cp_clp is still valid. +*/ +nfs4_free_copy_state (copy); +/* Pairs with smp_load_acquire() in nfsd4_send_cb_offload(). */ +smp_store_release (©->cp_clp,NULL );if (!list_empty (©->copies))list_del_init (©->copies); }
@@ -1464,8 +1482,11 @@ void nfsd4_shutdown_copy(struct nfs4_client *clp) {struct nfsd4_copy *copy; -while ((copy =nfsd4_unhash_copy (clp)) !=NULL ) +while ((copy =nfsd4_unhash_copy (clp)) !=NULL ) {nfsd4_stop_copy (copy); +/* Reaper can't reach the unhashed copy; drop its membership ref. */ +nfs4_put_copy (copy); + } }#ifdef CONFIG_NFSD_V4_2_INTER_SSC
@@ -1756,6 +1777,8 @@ static void nfsd4_cb_offload_release(struct nfsd4_callback *cb) container_of(cbo, struct nfsd4_copy, cp_cb_offload);set_bit (NFSD4_COPY_F_OFFLOAD_DONE , ©->cp_flags); +/* Drop the copy reference taken in nfsd4_send_cb_offload(). */ +nfs4_put_copy (copy); } static int nfsd4_cb_offload_done(struct nfsd4_callback *cb,
@@ -1881,34 +1904,52 @@ static void release_copy_files(struct nfsd4_copy *copy)nfsd_file_put (copy->nf_dst); } +/* +* Called from the reaper and from nfsd4_copy()'s error path; in both +* cases the copy is already unreachable from clp->async_copies. +*/ static void cleanup_async_copy (struct nfsd4_copy *copy) {nfs4_free_copy_state (copy);release_copy_files (copy); -if (copy->cp_clp) { -spin_lock (©->cp_clp->async_lock); -if (!list_empty (©->copies)) -list_del_init (©->copies); -spin_unlock (©->cp_clp->async_lock); - }nfs4_put_copy (copy); }static void nfsd4_send_cb_offload (struct nfsd4_copy *copy) {struct nfsd4_cb_offload *cbo = ©->cp_cb_offload; +struct nfs4_client *clp; + +/* +* Pairs with smp_store_release(&cp_clp) in find_async_copy() and +* nfsd4_unhash_copy(); the set_bit/clear_bit writers are unordered. +* cp_clp is NULL once the copy was canceled; skip the callback, the +* canceling path owns the notification. +*/ + clp =smp_load_acquire (©->cp_clp); +if (!clp) { +set_bit (NFSD4_COPY_F_OFFLOAD_DONE , ©->cp_flags); +return ; + }memcpy (&cbo->co_res, ©->cp_res,sizeof (copy->cp_res));memcpy (&cbo->co_fh, ©->fh,sizeof (copy->fh)); cbo->co_nfserr = copy->nfserr; cbo->co_retries =5 ; -nfsd4_init_cb (&cbo->co_cb, copy->cp_clp, &nfsd4_cb_offload_ops, +/* +* Hold the copy across the in-flight callback; co_cb is embedded in +* the copy, so it must outlive the callback. The reference is dropped +* in nfsd4_cb_offload_release(). +*/ +refcount_inc (©->refcount); + +nfsd4_init_cb (&cbo->co_cb, clp, &nfsd4_cb_offload_ops,NFSPROC4_CLNT_CB_OFFLOAD );nfsd41_cb_referring_call (&cbo->co_cb, &cbo->co_referring_sessionid, cbo->co_referring_slotid, cbo->co_referring_seqno); -trace_nfsd_cb_offload (copy->cp_clp, &cbo->co_res.cb_stateid, +trace_nfsd_cb_offload (clp, &cbo->co_res.cb_stateid, &cbo->co_fh, copy->cp_count, copy->nfserr);nfsd4_try_run_cb (&cbo->co_cb); }
@@ -1950,16 +1991,20 @@ static int nfsd4_do_async_copy(void *data) } do_callback: -/* The kthread exits forthwith. Ensure that a subsequent -* OFFLOAD_CANCEL won't try to kill it again. */ -set_bit (NFSD4_COPY_F_STOPPED , ©->cp_flags); - +/* +* Don't set NFSD4_COPY_F_STOPPED here: it tells a teardown caller it +* may skip kthread_stop(), which would then release nf_dst and the +* client while still in use. Only nfsd4_stop_copy() sets it, after +* joining. +*/ set_bit (NFSD4_COPY_F_COMPLETED , ©->cp_flags);trace_nfsd_copy_async_done (copy);atomic_dec (©->cp_nn->pending_async_copies);if (copy->cp_res.wr_bytes_written >0 && copy->attr_update)nfsd_update_cmtime_attr (copy->nf_dst->nf_file,0 );nfsd4_send_cb_offload (copy); +/* Drop the kthread's reference (taken in nfsd4_copy()); copy may be freed after this. */ +nfs4_put_copy (copy);return 0 ; }
@@ -2000,6 +2045,8 @@ nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,memcpy (©->fh, &cstate->current_fh.fh_handle,sizeof (struct knfsd_fh ));if (nfsd4_copy_is_async (copy)) { +struct task_struct *task; + async_copy =kzalloc (sizeof (struct nfsd4_copy ),GFP_KERNEL );if (!async_copy) goto out_err;
@@ -2027,15 +2074,27 @@ nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, NFS4_MAX_SESSIONID_LEN); async_copy->cp_cb_offload.co_referring_slotid = cstate->slot->sl_index; async_copy->cp_cb_offload.co_referring_seqno = cstate->slot->sl_seqid; - async_copy->copy_task =kthread_create (nfsd4_do_async_copy, - async_copy,"%s" ,"copy thread" ); -if (IS_ERR (async_copy->copy_task)) + task =kthread_create (nfsd4_do_async_copy, async_copy, +"%s" ,"copy thread" ); +if (IS_ERR (task)) goto out_dec_async_copy_err; +/* +* Pin the task_struct so kthread_stop() is safe after this +* one-shot kthread exits. Released by nfs4_put_copy(). +*/ +get_task_struct (task); + async_copy->copy_task = task; +/* +* Take the kthread's ref and wake it before publishing, so the +* publisher touches async_copy no further and teardown can +* drain it. +*/ +refcount_inc (&async_copy->refcount); +wake_up_process (async_copy->copy_task);spin_lock (&async_copy->cp_clp->async_lock);list_add (&async_copy->copies, &async_copy->cp_clp->async_copies);spin_unlock (&async_copy->cp_clp->async_lock); -wake_up_process (async_copy->copy_task); status = nfs_ok; }else { status = nfsd4_do_copy(copy, copy->nf_src->nf_file,
@@ -2089,8 +2148,18 @@ find_async_copy(struct nfs4_client *clp, stateid_t *stateid)spin_lock (&clp->async_lock); copy =find_async_copy_locked (clp, stateid); -if (copy) +if (copy) {refcount_inc (©->refcount); +nfs4_free_copy_state (copy); +/* +* Mirror nfsd4_unhash_copy(): unlink and clear cp_clp under +* async_lock so the reaper can't reach it. Caller drops the +* membership ref after nfsd4_stop_copy(). +*/ +smp_store_release (©->cp_clp,NULL ); +if (!list_empty (©->copies)) +list_del_init (©->copies); + }spin_unlock (&clp->async_lock);return copy; }
@@ -2109,8 +2178,11 @@ nfsd4_offload_cancel(struct svc_rqst *rqstp,struct nfsd_net *nn =net_generic (SVC_NET (rqstp), nfsd_net_id);return manage_cpntf_state (nn, &os->stateid, clp,NULL ); - }else + }else {nfsd4_stop_copy (copy); +/* find_async_copy() unlinked it from the reaper; drop the membership ref. */ +nfs4_put_copy (copy); + }return nfs_ok; }
Signature · 1 line
2.53.0