FAILED: patch "[PATCH] nfsd: RCU-protect cl_cb_session to fix use-after-free on" failed to apply to 5.10-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 5.10-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-5.10.y
git checkout FETCH_HEAD
git cherry-pick -x 01c5d5f58a5db9b0ee5afba2e49d3157788687b2
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026090304-heroism-outclass-581e@gregkh' --subject-prefix 'PATCH 5.10.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 01c5d5f58a5db9b0ee5afba2e49d3157788687b2 Mon Sep 17 00:00:00 2001
From: Jeff Layton <[email protected]>
Date: Sat, 30 May 2026 09:19:18 -0400
Subject: [PATCH] nfsd: RCU-protect cl_cb_session to fix use-after-free on
session teardown
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
After a DESTROY_SESSION the per-session teardown path can free a session while rpciod still holds an inflight callback rpc_task that dereferences clp->cl_cb_session. nfsd4_probe_callback_sync() flushes cl_callback_wq, but once nfsd4_run_cb_work() has called rpc_call_async() the rpc_task lives on rpciod; flushing the workqueue does not wait for it. rpc_shutdown_client() does drain rpciod tasks, but uses a 1-second wait_event_timeout — tasks stuck in rpc_delay() (e.g. 2-second NFS4ERR_DELAY retries) can outlive the drain.
destroy path rpciod
------------ ------
unhash_session(ses)
nfsd4_probe_callback_sync(clp)
flush_workqueue(cl_callback_wq)
/* returns; rpc_task still live */
nfsd4_put_session_locked(ses)
free_session(ses) -> kfree(ses)
nfsd4_cb_sequence_done()
reads cb_clp->cl_cb_session
/* freed slab */A second window exists in nfsd4_process_cb_update(). When __nfsd4_find_backchannel() returns NULL because unhash_session() has already removed the destroyed session from cl_sessions, setup_callback_client() takes the v4.1 early return so clp->cl_cb_session = ses never fires and the field retains a pointer to the about-to-be-freed session.
Fix both by converting cl_cb_session to an RCU-protected pointer:
- Move the cl_cb_session = ses assignment in setup_callback_client() to after rpc_create() succeeds, so it is only published when a working backchannel exists. Clear cl_cb_session on the error return in nfsd4_process_cb_update(). Both stores use rcu_assign_pointer().
- Annotate cl_cb_session with __rcu. All rpciod-side readers use rcu_read_lock()/rcu_dereference() and check for NULL, bailing to the appropriate error or requeue path:
encode_cb_sequence4args(), decode_cb_sequence4resok(), nfsd41_cb_get_slot(), nfsd41_cb_release_slot(), nfsd4_cb_prepare(), and nfsd4_cb_sequence_done().
- Switch __free_session() from kfree() to kfree_rcu() so the session slab is not reclaimed until after an RCU grace period, guaranteeing that rpciod readers inside rcu_read_lock() never dereference freed memory.
- Pass the session pointer to the nfsd_cb_seq_status and nfsd_cb_free_slot tracepoints instead of having them re-read cl_cb_session.
- nfsd4_cb_prepare() calls rpc_exit() when the session is NULL, routing through the done/release path to requeue the callback.
fs/nfsd/nfs4callback.c +90 -19
@@ -456,13 +456,20 @@ static void encode_cb_sequence4args(struct xdr_stream *xdr,const struct nfsd4_callback *cb, struct nfs4_cb_compound_hdr *hdr) { - struct nfsd4_session *session = cb->cb_clp->cl_cb_session; + struct nfsd4_session *session;struct nfsd4_referring_call_list *rcl;__be32 *p;if (hdr->minorversion ==0 )return ; +rcu_read_lock (); + session =rcu_dereference (cb->cb_clp->cl_cb_session); +if (!session) { +rcu_read_unlock (); +return ; + } +encode_nfs_cb_opnum4 (xdr,OP_CB_SEQUENCE );encode_sessionid4 (xdr, session);
@@ -478,6 +485,7 @@ static void encode_cb_sequence4args(struct xdr_stream *xdr,encode_referring_call_list4 (xdr, rcl); hdr->nops++; +rcu_read_unlock (); }static void update_cb_slot_table (struct nfsd4_session *ses,u32 target)
@@ -529,21 +537,32 @@ static void update_cb_slot_table(struct nfsd4_session *ses, u32 target)static int decode_cb_sequence4resok (struct xdr_stream *xdr,struct nfsd4_callback *cb) { -struct nfsd4_session *session = cb->cb_clp->cl_cb_session; +struct nfsd4_session *session;int status = -ESERVERFAULT ;__be32 *p;u32 seqid, slotid, target; +rcu_read_lock (); + session =rcu_dereference (cb->cb_clp->cl_cb_session); +if (!session) { +rcu_read_unlock (); + cb->cb_seq_status = -NFS4ERR_BADSESSION ; +return -NFS4ERR_BADSESSION ; + } +/* * If the server returns different values for sessionID, slotID or * sequence number, the server is looney tunes. */ p =xdr_inline_decode (xdr,NFS4_MAX_SESSIONID_LEN +4 +4 +4 +4 ); -if (unlikely (p ==NULL )) +if (unlikely (p ==NULL )) { +rcu_read_unlock (); goto out_overflow; + }if (memcmp (p, session->se_sessionid.data,NFS4_MAX_SESSIONID_LEN )) {dprintk ("NFS: %s Invalid session id\n" , __func__); +rcu_read_unlock (); goto out; } p +=XDR_QUADLEN (NFS4_MAX_SESSIONID_LEN );
@@ -551,12 +570,14 @@ static int decode_cb_sequence4resok(struct xdr_stream *xdr, seqid =be32_to_cpup (p++);if (seqid != session->se_cb_seq_nr[cb->cb_held_slot]) {dprintk ("NFS: %s Invalid sequence number\n" , __func__); +rcu_read_unlock (); goto out; } slotid =be32_to_cpup (p++);if (slotid != cb->cb_held_slot) {dprintk ("NFS: %s Invalid slotid\n" , __func__); +rcu_read_unlock (); goto out; }
@@ -564,6 +585,7 @@ static int decode_cb_sequence4resok(struct xdr_stream *xdr, target =be32_to_cpup (p++);update_cb_slot_table (session, target); +rcu_read_unlock (); status =0 ; out: cb->cb_seq_status = status;
@@ -1150,9 +1172,8 @@ static int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *c }else {if (!conn->cb_xprt || !ses)return -EINVAL ; - clp->cl_cb_session = ses; args.bc_xprt = conn->cb_xprt; - args.prognumber = clp->cl_cb_session->se_cb_prog; + args.prognumber = ses->se_cb_prog; args.protocol = conn->cb_xprt->xpt_class->xcl_ident |XPRT_TRANSPORT_BC ; args.authflavor = ses->se_cb_sec.flavor;
@@ -1170,8 +1191,10 @@ static int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *creturn -ENOMEM ; } -if (clp->cl_minorversion !=0 ) +if (clp->cl_minorversion !=0 ) { clp->cl_cb_conn.cb_xprt = conn->cb_xprt; +rcu_assign_pointer (clp->cl_cb_session, ses); + } clp->cl_cb_client = client; clp->cl_cb_cred = cred;rcu_read_lock ();
@@ -1278,18 +1301,33 @@ static int grab_slot(struct nfsd4_session *ses)static bool nfsd41_cb_get_slot (struct nfsd4_callback *cb,struct rpc_task *task) {struct nfs4_client *clp = cb->cb_clp; -struct nfsd4_session *ses = clp->cl_cb_session; +struct nfsd4_session *ses;if (cb->cb_held_slot >=0 )return true; + +rcu_read_lock (); + ses =rcu_dereference (clp->cl_cb_session); +if (!ses) { +rcu_read_unlock (); +rpc_sleep_on (&clp->cl_cb_waitq, task,NULL ); +return false; + } cb->cb_held_slot =grab_slot (ses);if (cb->cb_held_slot <0 ) { +rcu_read_unlock ();rpc_sleep_on (&clp->cl_cb_waitq, task,NULL );/* Race breaker */ - cb->cb_held_slot =grab_slot (ses); +rcu_read_lock (); + ses =rcu_dereference (clp->cl_cb_session); +if (ses) + cb->cb_held_slot =grab_slot (ses); +rcu_read_unlock ();if (cb->cb_held_slot <0 )return false;rpc_wake_up_queued_task (&clp->cl_cb_waitq, task); + }else { +rcu_read_unlock (); }return true; }
@@ -1297,12 +1335,17 @@ static bool nfsd41_cb_get_slot(struct nfsd4_callback *cb, struct rpc_task *task)static void nfsd41_cb_release_slot (struct nfsd4_callback *cb) {struct nfs4_client *clp = cb->cb_clp; -struct nfsd4_session *ses = clp->cl_cb_session; +struct nfsd4_session *ses;if (cb->cb_held_slot >=0 ) { -spin_lock (&ses->se_lock); - ses->se_cb_slot_avail |=BIT (cb->cb_held_slot); -spin_unlock (&ses->se_lock); +rcu_read_lock (); + ses =rcu_dereference (clp->cl_cb_session); +if (ses) { +spin_lock (&ses->se_lock); + ses->se_cb_slot_avail |=BIT (cb->cb_held_slot); +spin_unlock (&ses->se_lock); + } +rcu_read_unlock (); cb->cb_held_slot =-1 ;rpc_wake_up_next (&clp->cl_cb_waitq); }
@@ -1434,22 +1477,35 @@ static void nfsd4_cb_prepare(struct rpc_task *task, void *calldata)trace_nfsd_cb_rpc_prepare (clp); cb->cb_seq_status =1 ; cb->cb_status =0 ; -if (minorversion && !nfsd41_cb_get_slot (cb, task)) -return ; +if (minorversion) { +if (!rcu_access_pointer (clp->cl_cb_session)) { +rpc_exit (task, -EIO ); +return ; + } +if (!nfsd41_cb_get_slot (cb, task)) +return ; + }rpc_call_start (task); }/* Returns true if CB_COMPOUND processing should continue */ static bool nfsd4_cb_sequence_done (struct rpc_task *task,struct nfsd4_callback *cb) { -struct nfsd4_session *session = cb->cb_clp->cl_cb_session; +struct nfsd4_session *session;bool ret = false;if (cb->cb_held_slot <0 ) goto requeue; +rcu_read_lock (); + session =rcu_dereference (cb->cb_clp->cl_cb_session); +if (!session) { +rcu_read_unlock (); + goto requeue; + } +/* This is the operation status code for CB_SEQUENCE */ -trace_nfsd_cb_seq_status (task, cb); +trace_nfsd_cb_seq_status (task, cb, session);switch (cb->cb_seq_status) {case 0 :/*
@@ -1481,12 +1537,16 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback fallthrough;case -NFS4ERR_BADSESSION :nfsd4_mark_cb_fault (cb->cb_clp); +rcu_read_unlock (); goto requeue;case -NFS4ERR_DELAY : cb->cb_seq_status =1 ; -if (RPC_SIGNALLED (task) || !rpc_restart_call (task)) +if (RPC_SIGNALLED (task) || !rpc_restart_call (task)) { +rcu_read_unlock (); goto requeue; + }rpc_delay (task,2 *HZ ); +rcu_read_unlock ();return false;case -NFS4ERR_SEQ_MISORDERED :case -NFS4ERR_BADSLOT :
@@ -1498,11 +1558,13 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback*/ nfsd4_mark_cb_fault (cb->cb_clp); cb->cb_held_slot =-1 ; +rcu_read_unlock (); goto retry_nowait;default :nfsd4_mark_cb_fault (cb->cb_clp); } -trace_nfsd_cb_free_slot (task, cb); +trace_nfsd_cb_free_slot (task, cb, session); +rcu_read_unlock ();nfsd41_cb_release_slot (cb);return ret; retry_nowait:
@@ -1624,7 +1686,15 @@ static struct nfsd4_conn * __nfsd4_find_backchannel(struct nfs4_client *clp)* Note there isn't a lot of locking in this code; instead we depend on * the fact that it is run from clp->cl_callback_wq, which won't run two * work items at once. So, for example, clp->cl_callback_wq handles all -* access of cl_cb_client and all calls to rpc_create or rpc_shutdown_client. +* access of cl_cb_client, and all calls to rpc_create or +* rpc_shutdown_client. +* +* cl_cb_session is written only from cl_callback_wq (via +* rcu_assign_pointer) and read from rpciod under rcu_read_lock (via +* rcu_dereference) by encode_cb_sequence4args(), decode_cb_sequence4resok(), +* nfsd4_cb_sequence_done(), and the cb-slot helpers. Sessions are freed +* with kfree_rcu() so that rpciod readers in an RCU read-side critical +* section never dereference a freed session. */ static void nfsd4_process_cb_update (struct nfsd4_callback *cb) {
@@ -1676,6 +1746,7 @@ static void nfsd4_process_cb_update(struct nfsd4_callback *cb)nfsd4_mark_cb_down (clp);if (c)svc_xprt_put (c->cn_xprt); +rcu_assign_pointer (clp->cl_cb_session, ses);return ; } }
fs/nfsd/nfs4state.c +2 -2
@@ -2295,7 +2295,7 @@ static void __free_session(struct nfsd4_session *ses) {free_session_slots (ses,0 );xa_destroy (&ses->se_slots); -kfree (ses); +kfree_rcu (ses, rcu_head); }static void free_session (struct nfsd4_session *ses)
@@ -3414,7 +3414,7 @@ static struct nfs4_client *create_client(struct xdr_netobj name, clp->cl_time =ktime_get_boottime_seconds ();copy_verf (clp, verf);memcpy (&clp->cl_addr, sa,sizeof (struct sockaddr_storage )); - clp->cl_cb_session =NULL ; +RCU_INIT_POINTER (clp->cl_cb_session,NULL ); clp->net = net; clp->cl_nfsd_dentry = nfsd_client_mkdir( nn, &clp->cl_nfsdfs,
fs/nfsd/state.h +2 -1
@@ -384,6 +384,7 @@ struct nfsd4_session {u16 se_slot_gen;bool se_dead;u32 se_target_maxslots; +struct rcu_head rcu_head; };/* formatted contents of nfs4_sessionid */
@@ -496,7 +497,7 @@ struct nfs4_client {#define NFSD4_CB_FAULT 3int cl_cb_state;struct nfsd4_callback cl_cb_null; -struct nfsd4_session *cl_cb_session; +struct nfsd4_session __rcu *cl_cb_session;/* for all client information that callback code might need: */ spinlock_t cl_lock;
fs/nfsd/trace.h +6 -8
@@ -1727,9 +1727,10 @@ DEFINE_NFSD_CB_LIFETIME_EVENT(bc_shutdown); TRACE_EVENT(nfsd_cb_seq_status, TP_PROTO( const struct rpc_task *task, - const struct nfsd4_callback *cb + const struct nfsd4_callback *cb, + const struct nfsd4_session *session ), - TP_ARGS(task, cb), + TP_ARGS(task, cb, session), TP_STRUCT__entry( __field(unsigned int, task_id) __field(unsigned int, client_id)
@@ -1741,8 +1742,6 @@ TRACE_EVENT(nfsd_cb_seq_status,__field (int, seq_status) ),TP_fast_assign ( - const struct nfs4_client *clp = cb->cb_clp; -const struct nfsd4_session *session = clp->cl_cb_session; const struct nfsd4_sessionid *sid = (struct nfsd4_sessionid *)&session->se_sessionid;
@@ -1768,9 +1767,10 @@ TRACE_EVENT(nfsd_cb_seq_status, TRACE_EVENT(nfsd_cb_free_slot, TP_PROTO( const struct rpc_task *task, - const struct nfsd4_callback *cb + const struct nfsd4_callback *cb, + const struct nfsd4_session *session ), - TP_ARGS(task, cb), + TP_ARGS(task, cb, session), TP_STRUCT__entry( __field(unsigned int, task_id) __field(unsigned int, client_id)
@@ -1781,8 +1781,6 @@ TRACE_EVENT(nfsd_cb_free_slot,__field (u32, slot_seqno) ),TP_fast_assign ( - const struct nfs4_client *clp = cb->cb_clp; -const struct nfsd4_session *session = clp->cl_cb_session; const struct nfsd4_sessionid *sid = (struct nfsd4_sessionid *)&session->se_sessionid;
Sasha Levin
maintainerPATCH 1/7 nfsd: prepare nfsd4_cb_sequence_done() for error handling rework
From: Jeff Layton <[email protected]>
[ Upstream commit 1c2d0957dc66620338eba63e0267122b6e2175cc ]
There is only one case where we want to proceed with processing the rest of the CB_COMPOUND, and that's when the cb_seq_status is 0. Make the default return value be false, and only set it to true in that case.
Rename the "need_restart" label to "requeue", to better indicate that it's being requeued to the workqueue.
Stable-dep-of: 01c5d5f58a5d ("nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown")
--- fs/nfsd/nfs4callback.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-)
fs/nfsd/nfs4callback.c +11 -11
@@ -1157,11 +1157,12 @@ static void nfsd4_cb_prepare(struct rpc_task *task, void *calldata)rpc_call_start (task); } +/* Returns true if CB_COMPOUND processing should continue */ static bool nfsd4_cb_sequence_done (struct rpc_task *task,struct nfsd4_callback *cb) {struct nfs4_client *clp = cb->cb_clp;struct nfsd4_session *session = clp->cl_cb_session; -bool ret = true; +bool ret = false;if (!clp->cl_minorversion) {/*
@@ -1174,13 +1175,13 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback* handle that case here. */ if (RPC_SIGNALLED (task)) - goto need_restart; + goto requeue;return true; }if (!cb->cb_holds_slot) - goto need_restart; + goto requeue;switch (cb->cb_seq_status) {case 0 :
@@ -1192,20 +1193,20 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback* (sequence ID, cached reply) MUST NOT change. */ ++session->se_cb_seq_nr; + ret = true;break ;case -ESERVERFAULT : ++session->se_cb_seq_nr; - fallthrough; +nfsd4_mark_cb_fault (cb->cb_clp, cb->cb_seq_status); +break ;case 1 :case -NFS4ERR_BADSESSION :nfsd4_mark_cb_fault (cb->cb_clp, cb->cb_seq_status); - ret = false; -break ; + goto requeue;case -NFS4ERR_DELAY : cb->cb_seq_status =1 ;if (!rpc_restart_call (task)) goto out; -rpc_delay (task,2 *HZ );return false;case -NFS4ERR_BADSLOT :
@@ -1227,14 +1228,13 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback clp->cl_cb_session->se_cb_seq_nr);if (RPC_SIGNALLED (task)) - goto need_restart; + goto requeue; out:return ret; retry_nowait: -if (rpc_restart_call_prepare (task)) - ret = false; +rpc_restart_call_prepare (task); goto out; -need_restart: +requeue:if (!test_bit (NFSD4_CLIENT_CB_KILL , &clp->cl_flags)) { task->tk_status =0 ; cb->cb_need_restart = true;
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/7 nfsd: lift NFSv4.0 handling out of nfsd4_cb_sequence_done()
From: Jeff Layton <[email protected]>
[ Upstream commit 6c1cefb84b3dee08e94b6908617f23f3fcc045e9 ]
It's a bit strange to call nfsd4_cb_sequence_done() on a callback with no CB_SEQUENCE. Lift the handling of restarting a call into a new helper, and move the handling of NFSv4.0 into nfsd4_cb_done().
Stable-dep-of: 01c5d5f58a5d ("nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown")
--- fs/nfsd/nfs4callback.c | 52 +++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 24 deletions(-)
fs/nfsd/nfs4callback.c +28 -24
@@ -914,6 +914,17 @@ static bool nfsd4_queue_cb(struct nfsd4_callback *cb)return queue_work (callback_wq, &cb->cb_work); } +#define nfsd4_requeue_cb (task, cb) \ + do { \ + struct rpc_task *__task = (task); \ + struct nfsd4_callback *__cb = (cb); \ + struct nfs4_client *__clp = __cb->cb_clp; \ + if (!test_bit(NFSD4_CLIENT_CB_KILL, &__clp->cl_flags)) { \ + __task->tk_status = 0; \ + __cb->cb_need_restart = true; \ + } \ + } while (0) +static void nfsd41_cb_inflight_begin (struct nfs4_client *clp) {atomic_inc (&clp->cl_cb_inflight);
@@ -1160,26 +1171,9 @@ static void nfsd4_cb_prepare(struct rpc_task *task, void *calldata)/* Returns true if CB_COMPOUND processing should continue */ static bool nfsd4_cb_sequence_done (struct rpc_task *task,struct nfsd4_callback *cb) { -struct nfs4_client *clp = cb->cb_clp; -struct nfsd4_session *session = clp->cl_cb_session; +struct nfsd4_session *session = cb->cb_clp->cl_cb_session;bool ret = false; -if (!clp->cl_minorversion) { -/* -* If the backchannel connection was shut down while this -* task was queued, we need to resubmit it after setting up -* a new backchannel connection. -* -* Note that if we lost our callback connection permanently -* the submission code will error out, so we don't need to -* handle that case here. -*/ -if (RPC_SIGNALLED (task)) - goto requeue; - -return true; - } -if (!cb->cb_holds_slot) goto requeue;
@@ -1225,7 +1219,7 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callbacknfsd41_cb_release_slot (cb);dprintk ("%s: freed slot, new seqid=%d\n" , __func__, - clp->cl_cb_session->se_cb_seq_nr); + session->se_cb_seq_nr);if (RPC_SIGNALLED (task)) goto requeue;
@@ -1235,10 +1229,7 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callbackrpc_restart_call_prepare (task); goto out; requeue: -if (!test_bit (NFSD4_CLIENT_CB_KILL , &clp->cl_flags)) { - task->tk_status =0 ; - cb->cb_need_restart = true; - } +nfsd4_requeue_cb (task, cb);return false; }
@@ -1247,8 +1238,21 @@ static void nfsd4_cb_done(struct rpc_task *task, void *calldata)struct nfsd4_callback *cb = calldata;struct nfs4_client *clp = cb->cb_clp; -if (!nfsd4_cb_sequence_done (task, cb)) +if (!clp->cl_minorversion) { +/* +* If the backchannel connection was shut down while this +* task was queued, we need to resubmit it after setting up +* a new backchannel connection. +* +* Note that if we lost our callback connection permanently +* the submission code will error out, so we don't need to +* handle that case here. +*/ +if (RPC_SIGNALLED (task)) +nfsd4_requeue_cb (task, cb); + }else if (!nfsd4_cb_sequence_done (task, cb)) {return ; + }if (cb->cb_status) {WARN_ON_ONCE (task->tk_status);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 3/7 nfsd: only check RPC_SIGNALLED() when restarting rpc_task
In reply to Sasha LevinFrom: Jeff Layton <[email protected]>
[ Upstream commit f049911b5b98feceb949430f33fb5d2c9c55833c ]
nfsd4_cb_sequence_done() currently checks RPC_SIGNALLED() when processing the compound and releasing the slot. If RPC_SIGNALLED() returns true, then that means that the client is going to be torn down.
Don't check RPC_SIGNALLED() after processing a successful reply. Check it only before restarting the rpc_task. If it returns true, then requeue the callback instead of restarting the task.
Also, handle rpc_restart_call() and rpc_restart_call_prepare() failures correctly, by requeueing the callback.
Stable-dep-of: 01c5d5f58a5d ("nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown")
--- fs/nfsd/nfs4callback.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-)
fs/nfsd/nfs4callback.c +11 -8
@@ -1199,8 +1199,8 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback goto requeue;case -NFS4ERR_DELAY : cb->cb_seq_status =1 ; -if (!rpc_restart_call (task)) - goto out; +if (RPC_SIGNALLED (task) || !rpc_restart_call (task)) + goto requeue;rpc_delay (task,2 *HZ );return false;case -NFS4ERR_BADSLOT :
@@ -1220,15 +1220,18 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callbacknfsd41_cb_release_slot (cb);dprintk ("%s: freed slot, new seqid=%d\n" , __func__, session->se_cb_seq_nr); - -if (RPC_SIGNALLED (task)) - goto requeue; -out:return ret; retry_nowait: -rpc_restart_call_prepare (task); - goto out; +/* +* RPC_SIGNALLED() means that the rpc_client is being torn down and +* (possibly) recreated. Requeue the call in that case. +*/ +if (!RPC_SIGNALLED (task)) { +if (rpc_restart_call_prepare (task)) +return false; + } requeue: +nfsd41_cb_release_slot (cb);nfsd4_requeue_cb (task, cb);return false; }
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 4/7 nfsd: handle NFS4ERR_BADSLOT on CB_SEQUENCE better
In reply to Sasha LevinFrom: Jeff Layton <[email protected]>
[ Upstream commit 999595a651a87f4285c53710b5f600d6a141f526 ]
Currently it just restarts the call, without getting a new slot.
Stable-dep-of: 01c5d5f58a5d ("nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown")
--- fs/nfsd/nfs4callback.c | 7 +++++++ 1 file changed, 7 insertions(+)
fs/nfsd/nfs4callback.c +7 -0
@@ -1204,6 +1204,13 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callbackrpc_delay (task,2 *HZ );return false;case -NFS4ERR_BADSLOT : +/* +* BADSLOT means that the client and server are out of sync +* as to the backchannel parameters. Mark the backchannel faulty +* and restart the RPC, but leak the slot so no one uses it. +*/ +nfsd4_mark_cb_fault (cb->cb_clp); + cb->cb_held_slot =-1 ; goto retry_nowait;case -NFS4ERR_SEQ_MISORDERED :if (session->se_cb_seq_nr !=1 ) {
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 5/7 nfsd: prevent callback tasks running concurrently
In reply to Sasha LevinFrom: Jeff Layton <[email protected]>
[ Upstream commit 1054e8ffc5c492f341bdf1888b882f1d163dd3d8 ]
The nfsd4_callback workqueue jobs exist to queue backchannel RPCs to rpciod. Because they run in different workqueue contexts, the rpc_task can run concurrently with the workqueue job itself, should it become requeued. This is problematic as there is no locking when accessing the fields in the nfsd4_callback.
Add a new unsigned long to nfsd4_callback and declare a new NFSD4_CALLBACK_RUNNING flag to be set in it. When attempting to run a workqueue job, do a test_and_set_bit() on that flag first, and don't queue the workqueue job if it returns true. Clear NFSD4_CALLBACK_RUNNING in nfsd41_destroy_cb().
This also gives us a more reliable mechanism for handling queueing failures in codepaths where we have to take references under spinlocks. We can now do the test_and_set_bit on NFSD4_CALLBACK_RUNNING first, and only take references to the objects if that returns false.
Most of the nfsd4_run_cb() callers are converted to use this new flag or the nfsd4_try_run_cb() wrapper. The main exception is the callback channel probe, which has its own synchronization.
Stable-dep-of: 01c5d5f58a5d ("nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown")
--- fs/nfsd/nfs4callback.c | 9 ++------- fs/nfsd/nfs4layouts.c | 7 ++++--- fs/nfsd/nfs4proc.c | 3 ++- fs/nfsd/nfs4state.c | 16 +++++++++++++--- fs/nfsd/state.h | 2 ++ 5 files changed, 23 insertions(+), 14 deletions(-)
fs/nfsd/nfs4callback.c +2 -7
@@ -1142,6 +1142,7 @@ static void nfsd41_destroy_cb(struct nfsd4_callback *cb)struct nfs4_client *clp = cb->cb_clp;nfsd41_cb_release_slot (cb); +clear_bit (NFSD4_CALLBACK_RUNNING , &cb->cb_flags);if (cb->cb_ops && cb->cb_ops->release) cb->cb_ops->release (cb);nfsd41_cb_inflight_end (clp);
@@ -1204,13 +1205,6 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callbackrpc_delay (task,2 *HZ );return false;case -NFS4ERR_BADSLOT : -/* -* BADSLOT means that the client and server are out of sync -* as to the backchannel parameters. Mark the backchannel faulty -* and restart the RPC, but leak the slot so no one uses it. -*/ -nfsd4_mark_cb_fault (cb->cb_clp); - cb->cb_held_slot =-1 ; goto retry_nowait;case -NFS4ERR_SEQ_MISORDERED :if (session->se_cb_seq_nr !=1 ) {
@@ -1455,6 +1449,7 @@ void nfsd4_init_cb(struct nfsd4_callback *cb, struct nfs4_client *clp, cb->cb_msg.rpc_proc = &nfs4_cb_procedures[op]; cb->cb_msg.rpc_argp = cb; cb->cb_msg.rpc_resp = cb; + cb->cb_flags =0 ; cb->cb_ops = ops;INIT_WORK (&cb->cb_work, nfsd4_run_cb_work); cb->cb_seq_status =1 ;
fs/nfsd/nfs4layouts.c +4 -3
@@ -326,9 +326,10 @@ nfsd4_recall_file_layout(struct nfs4_layout_stateid *ls)atomic_inc (&ls->ls_stid.sc_file->fi_lo_recalls);trace_nfsd_layout_recall (&ls->ls_stid.sc_stateid); -refcount_inc (&ls->ls_stid.sc_count); -nfsd4_run_cb (&ls->ls_recall); - +if (!test_and_set_bit (NFSD4_CALLBACK_RUNNING , &ls->ls_recall.cb_flags)) { +refcount_inc (&ls->ls_stid.sc_count); +nfsd4_run_cb (&ls->ls_recall); + } out_unlock:spin_unlock (&ls->ls_lock); }
fs/nfsd/nfs4proc.c +2 -1
@@ -1711,7 +1711,8 @@ static void nfsd4_send_cb_offload(struct nfsd4_copy *copy, __be32 nfserr) NFSPROC4_CLNT_CB_OFFLOAD);trace_nfsd_cb_offload (copy->cp_clp, &cbo->co_res.cb_stateid, &cbo->co_fh, copy->cp_count, nfserr); -nfsd4_run_cb (&cbo->co_cb); +if (!test_and_set_bit (NFSD4_CALLBACK_RUNNING , &cbo->co_cb.cb_flags)) +WARN_ON_ONCE (!nfsd4_run_cb (&cbo->co_cb)); }/**
fs/nfsd/nfs4state.c +13 -3
@@ -4942,6 +4942,10 @@ static const struct nfsd4_callback_ops nfsd4_cb_recall_ops = {static void nfsd_break_one_deleg (struct nfs4_delegation *dp) {bool queued; + +if (test_and_set_bit (NFSD4_CALLBACK_RUNNING , &dp->dl_recall.cb_flags)) +return ; +/* * We're assuming the state code never drops its reference * without first removing the lease. Since we're in this lease
@@ -4952,8 +4956,10 @@ static void nfsd_break_one_deleg(struct nfs4_delegation *dp)refcount_inc (&dp->dl_stid.sc_count); queued =nfsd4_run_cb (&dp->dl_recall);WARN_ON_ONCE (!queued); -if (!queued) +if (!queued) {refcount_dec (&dp->dl_stid.sc_count); +clear_bit (NFSD4_CALLBACK_RUNNING , &dp->dl_recall.cb_flags); + } }/* Called from break_lease() with flc_lock held. */
@@ -6330,7 +6336,9 @@ deleg_reaper(struct nfsd_net *nn)list_del_init (&clp->cl_ra_cblist); clp->cl_ra->ra_keep =0 ; clp->cl_ra->ra_bmval[0 ] =BIT (RCA4_TYPE_MASK_RDATA_DLG ); -nfsd4_run_cb (&clp->cl_ra->ra_cb); +if (!test_and_set_bit (NFSD4_CALLBACK_RUNNING , + &clp->cl_ra->ra_cb.cb_flags)) +WARN_ON_ONCE (!nfsd4_run_cb (&clp->cl_ra->ra_cb)); } }
@@ -7221,7 +7229,9 @@ nfsd4_lm_notify(struct file_lock *fl)if (queue) {trace_nfsd_cb_notify_lock (lo, nbl); -nfsd4_run_cb (&nbl->nbl_cb); +if (!test_and_set_bit (NFSD4_CALLBACK_RUNNING , + &nbl->nbl_cb.cb_flags)) +WARN_ON_ONCE (!nfsd4_run_cb (&nbl->nbl_cb)); } }
fs/nfsd/state.h +2 -0
@@ -67,6 +67,8 @@ typedef struct {struct nfsd4_callback {struct nfs4_client *cb_clp;struct rpc_message cb_msg; +#define NFSD4_CALLBACK_RUNNING (0) +unsigned long cb_flags;const struct nfsd4_callback_ops *cb_ops;struct work_struct cb_work;int cb_seq_status;
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 6/7 nfsd: move cb_need_restart flag into cb_flags
In reply to Sasha LevinFrom: Jeff Layton <[email protected]>
[ Upstream commit 32ce62c0f09cdc5a6b06bb38daec9b9a47302437 ]
Since there is now a cb_flags word, use a new NFSD4_CALLBACK_REQUEUE flag in that instead of a separate boolean.
Since there is now a cb_flags word, use a new NFSD4_CALLBACK_REQUEUE flag in that instead of a separate boolean.
On this stable branch, callback requeue handling is consolidated in the nfsd4_requeue_cb macro. Convert that macro and its release/workqueue consumers to the flag, retain the older cb_holds_slot representation, and omit the callback lifetime tracepoint hunk because that tracepoint is not present here.
[ sashal: Reduced backport -- upstream 32ce62c0f09cd touches 3 file(s), this
backport carries 2. Not backported here:
fs/nfsd/trace.h
This note is generated from the file lists only; see the resolution record for the reasoning. ]
Stable-dep-of: 01c5d5f58a5d ("nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown")
--- fs/nfsd/nfs4callback.c | 9 +++------ fs/nfsd/state.h | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-)
fs/nfsd/nfs4callback.c +3 -6
@@ -921,7 +921,7 @@ static bool nfsd4_queue_cb(struct nfsd4_callback *cb)struct nfs4_client *__clp = __cb->cb_clp; \if (!test_bit (NFSD4_CLIENT_CB_KILL , &__clp->cl_flags)) { \ __task->tk_status =0 ; \ - __cb->cb_need_restart = true; \ +set_bit (NFSD4_CALLBACK_REQUEUE , &__cb->cb_flags); \ } \ }while (0 )
@@ -1285,7 +1285,7 @@ static void nfsd4_cb_release(void *calldata) {struct nfsd4_callback *cb = calldata; -if (cb->cb_need_restart) +if (test_bit (NFSD4_CALLBACK_REQUEUE , &cb->cb_flags))nfsd4_queue_cb (cb);else nfsd41_destroy_cb (cb);
@@ -1407,9 +1407,7 @@ nfsd4_run_cb_work(struct work_struct *work)struct rpc_clnt *clnt;int flags; -if (cb->cb_need_restart) { - cb->cb_need_restart = false; - }else { +if (!test_and_clear_bit (NFSD4_CALLBACK_REQUEUE , &cb->cb_flags)) {if (cb->cb_ops && cb->cb_ops->prepare) cb->cb_ops->prepare (cb); }
@@ -1454,7 +1452,6 @@ void nfsd4_init_cb(struct nfsd4_callback *cb, struct nfs4_client *clp,INIT_WORK (&cb->cb_work, nfsd4_run_cb_work); cb->cb_seq_status =1 ; cb->cb_status =0 ; - cb->cb_need_restart = false; cb->cb_holds_slot = false; }
fs/nfsd/state.h +1 -1
@@ -68,12 +68,12 @@ struct nfsd4_callback {struct nfs4_client *cb_clp;struct rpc_message cb_msg;#define NFSD4_CALLBACK_RUNNING (0) +#define NFSD4_CALLBACK_REQUEUE (1)unsigned long cb_flags;const struct nfsd4_callback_ops *cb_ops;struct work_struct cb_work;int cb_seq_status;int cb_status; -bool cb_need_restart;bool cb_holds_slot; };
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 7/7 nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown
In reply to Sasha LevinFrom: Jeff Layton <[email protected]>
[ Upstream commit 01c5d5f58a5db9b0ee5afba2e49d3157788687b2 ]
After a DESTROY_SESSION the per-session teardown path can free a session while rpciod still holds an inflight callback rpc_task that dereferences clp->cl_cb_session. nfsd4_probe_callback_sync() flushes cl_callback_wq, but once nfsd4_run_cb_work() has called rpc_call_async() the rpc_task lives on rpciod; flushing the workqueue does not wait for it. rpc_shutdown_client() does drain rpciod tasks, but uses a 1-second wait_event_timeout — tasks stuck in rpc_delay() (e.g. 2-second NFS4ERR_DELAY retries) can outlive the drain.
destroy path rpciod
------------ ------
unhash_session(ses)
nfsd4_probe_callback_sync(clp)
flush_workqueue(cl_callback_wq)
/* returns; rpc_task still live */
nfsd4_put_session_locked(ses)
free_session(ses) -> kfree(ses)
nfsd4_cb_sequence_done()
reads cb_clp->cl_cb_session
/* freed slab */A second window exists in nfsd4_process_cb_update(). When __nfsd4_find_backchannel() returns NULL because unhash_session() has already removed the destroyed session from cl_sessions, setup_callback_client() takes the v4.1 early return so clp->cl_cb_session = ses never fires and the field retains a pointer to the about-to-be-freed session.
Fix both by converting cl_cb_session to an RCU-protected pointer:
- Move the cl_cb_session = ses assignment in setup_callback_client() to after rpc_create() succeeds, so it is only published when a working backchannel exists. Clear cl_cb_session on the error return in nfsd4_process_cb_update(). Both stores use rcu_assign_pointer().
- Annotate cl_cb_session with __rcu. All rpciod-side readers use rcu_read_lock()/rcu_dereference() and check for NULL, bailing to the appropriate error or requeue path:
encode_cb_sequence4args(), decode_cb_sequence4resok(), nfsd41_cb_get_slot(), nfsd41_cb_release_slot(), nfsd4_cb_prepare(), and nfsd4_cb_sequence_done().
- Switch __free_session() from kfree() to kfree_rcu() so the session slab is not reclaimed until after an RCU grace period, guaranteeing that rpciod readers inside rcu_read_lock() never dereference freed memory.
- Pass the session pointer to the nfsd_cb_seq_status and nfsd_cb_free_slot tracepoints instead of having them re-read cl_cb_session.
- nfsd4_cb_prepare() calls rpc_exit() when the session is NULL, routing through the done/release path to requeue the callback.
--- fs/nfsd/nfs4callback.c | 74 +++++++++++++++++++++++++++++++++++------- fs/nfsd/nfs4state.c | 4 +-- fs/nfsd/state.h | 3 +- 3 files changed, 66 insertions(+), 15 deletions(-)
fs/nfsd/nfs4callback.c +62 -12
@@ -373,12 +373,19 @@ static void encode_cb_sequence4args(struct xdr_stream *xdr,const struct nfsd4_callback *cb, struct nfs4_cb_compound_hdr *hdr) { - struct nfsd4_session *session = cb->cb_clp->cl_cb_session; + struct nfsd4_session *session;__be32 *p;if (hdr->minorversion ==0 )return ; +rcu_read_lock (); + session =rcu_dereference (cb->cb_clp->cl_cb_session); +if (!session) { +rcu_read_unlock (); +return ; + } +encode_nfs_cb_opnum4 (xdr,OP_CB_SEQUENCE );encode_sessionid4 (xdr, session);
@@ -390,6 +397,7 @@ static void encode_cb_sequence4args(struct xdr_stream *xdr,xdr_encode_empty_array (p);/* csa_referring_call_lists */ hdr->nops++; +rcu_read_unlock (); }/*
@@ -416,21 +424,32 @@ static void encode_cb_sequence4args(struct xdr_stream *xdr,static int decode_cb_sequence4resok (struct xdr_stream *xdr,struct nfsd4_callback *cb) { -struct nfsd4_session *session = cb->cb_clp->cl_cb_session; +struct nfsd4_session *session;int status = -ESERVERFAULT ;__be32 *p;u32 dummy; +rcu_read_lock (); + session =rcu_dereference (cb->cb_clp->cl_cb_session); +if (!session) { +rcu_read_unlock (); + cb->cb_seq_status = -NFS4ERR_BADSESSION ; +return -NFS4ERR_BADSESSION ; + } +/* * If the server returns different values for sessionID, slotID or * sequence number, the server is looney tunes. */ p =xdr_inline_decode (xdr,NFS4_MAX_SESSIONID_LEN +4 +4 +4 +4 ); -if (unlikely (p ==NULL )) +if (unlikely (p ==NULL )) { +rcu_read_unlock (); goto out_overflow; + }if (memcmp (p, session->se_sessionid.data,NFS4_MAX_SESSIONID_LEN )) {dprintk ("NFS: %s Invalid session id\n" , __func__); +rcu_read_unlock (); goto out; } p +=XDR_QUADLEN (NFS4_MAX_SESSIONID_LEN );
@@ -438,18 +457,21 @@ static int decode_cb_sequence4resok(struct xdr_stream *xdr, dummy =be32_to_cpup (p++);if (dummy != session->se_cb_seq_nr) {dprintk ("NFS: %s Invalid sequence number\n" , __func__); +rcu_read_unlock (); goto out; } dummy =be32_to_cpup (p++);if (dummy !=0 ) {dprintk ("NFS: %s Invalid slotid\n" , __func__); +rcu_read_unlock (); goto out; }/* * FIXME: process highest slotid and target highest slotid */ +rcu_read_unlock (); status =0 ; out: cb->cb_seq_status = status;
@@ -999,9 +1021,8 @@ static int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *c }else {if (!conn->cb_xprt || !ses)return -EINVAL ; - clp->cl_cb_session = ses; args.bc_xprt = conn->cb_xprt; - args.prognumber = clp->cl_cb_session->se_cb_prog; + args.prognumber = ses->se_cb_prog; args.protocol = conn->cb_xprt->xpt_class->xcl_ident |XPRT_TRANSPORT_BC ; args.authflavor = ses->se_cb_sec.flavor;
@@ -1019,8 +1040,10 @@ static int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *creturn -ENOMEM ; } -if (clp->cl_minorversion !=0 ) +if (clp->cl_minorversion !=0 ) { clp->cl_cb_conn.cb_xprt = conn->cb_xprt; +rcu_assign_pointer (clp->cl_cb_session, ses); + } clp->cl_cb_client = client; clp->cl_cb_cred = cred;rcu_read_lock ();
@@ -1164,20 +1187,32 @@ static void nfsd4_cb_prepare(struct rpc_task *task, void *calldata)*/ cb->cb_seq_status =1 ; cb->cb_status =0 ; -if (minorversion && !nfsd41_cb_get_slot (cb, task)) -return ; +if (minorversion) { +if (!rcu_access_pointer (clp->cl_cb_session)) { +rpc_exit (task, -EIO ); +return ; + } +if (!nfsd41_cb_get_slot (cb, task)) +return ; + }rpc_call_start (task); }/* Returns true if CB_COMPOUND processing should continue */ static bool nfsd4_cb_sequence_done (struct rpc_task *task,struct nfsd4_callback *cb) { -struct nfsd4_session *session = cb->cb_clp->cl_cb_session; +struct nfsd4_session *session;bool ret = false;if (!cb->cb_holds_slot) goto requeue; +rcu_read_lock (); + session =rcu_dereference (cb->cb_clp->cl_cb_session); +if (!session) { +rcu_read_unlock (); + goto requeue; + }switch (cb->cb_seq_status) {case 0 :/*
@@ -1197,18 +1232,24 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callbackcase 1 :case -NFS4ERR_BADSESSION :nfsd4_mark_cb_fault (cb->cb_clp, cb->cb_seq_status); +rcu_read_unlock (); goto requeue;case -NFS4ERR_DELAY : cb->cb_seq_status =1 ; -if (RPC_SIGNALLED (task) || !rpc_restart_call (task)) +if (RPC_SIGNALLED (task) || !rpc_restart_call (task)) { +rcu_read_unlock (); goto requeue; + }rpc_delay (task,2 *HZ ); +rcu_read_unlock ();return false;case -NFS4ERR_BADSLOT : +rcu_read_unlock (); goto retry_nowait;case -NFS4ERR_SEQ_MISORDERED :if (session->se_cb_seq_nr !=1 ) { session->se_cb_seq_nr =1 ; +rcu_read_unlock (); goto retry_nowait; }break ;
@@ -1217,10 +1258,10 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callbackdprintk ("%s: unprocessed error %d\n" , __func__, cb->cb_seq_status); } -nfsd41_cb_release_slot (cb);dprintk ("%s: freed slot, new seqid=%d\n" , __func__, session->se_cb_seq_nr); +rcu_read_unlock ();return ret; retry_nowait:/*
@@ -1347,7 +1388,15 @@ static struct nfsd4_conn * __nfsd4_find_backchannel(struct nfs4_client *clp)* Note there isn't a lot of locking in this code; instead we depend on * the fact that it is run from the callback_wq, which won't run two * work items at once. So, for example, callback_wq handles all access -* of cl_cb_client and all calls to rpc_create or rpc_shutdown_client. +* of cl_cb_client, and all calls to rpc_create or +* rpc_shutdown_client. +* +* cl_cb_session is written only from callback_wq (via +* rcu_assign_pointer) and read from rpciod under rcu_read_lock (via +* rcu_dereference) by encode_cb_sequence4args(), decode_cb_sequence4resok(), +* and nfsd4_cb_sequence_done(). Sessions are freed with kfree_rcu() so that +* rpciod readers in an RCU read-side critical section never dereference a +* freed session. */ static void nfsd4_process_cb_update (struct nfsd4_callback *cb) {
@@ -1394,6 +1443,7 @@ static void nfsd4_process_cb_update(struct nfsd4_callback *cb)nfsd4_mark_cb_down (clp, err);if (c)svc_xprt_put (c->cn_xprt); +rcu_assign_pointer (clp->cl_cb_session, ses);return ; } }
fs/nfsd/nfs4state.c +2 -2
@@ -1976,7 +1976,7 @@ static void nfsd4_del_conns(struct nfsd4_session *s)static void __free_session (struct nfsd4_session *ses) {free_session_slots (ses); -kfree (ses); +kfree_rcu (ses, rcu_head); }static void free_session (struct nfsd4_session *ses)
@@ -2942,7 +2942,7 @@ static struct nfs4_client *create_client(struct xdr_netobj name,clear_bit (0 , &clp->cl_cb_slot_busy);copy_verf (clp, verf);memcpy (&clp->cl_addr, sa,sizeof (struct sockaddr_storage )); - clp->cl_cb_session =NULL ; +RCU_INIT_POINTER (clp->cl_cb_session,NULL ); clp->net = net; clp->cl_nfsd_dentry = nfsd_client_mkdir( nn, &clp->cl_nfsdfs,
fs/nfsd/state.h +2 -1
@@ -273,6 +273,7 @@ struct nfsd4_session {struct list_head se_conns;u32 se_cb_prog;u32 se_cb_seq_nr; +struct rcu_head rcu_head;struct nfsd4_slot *se_slots[];/* forward channel slots */ };
@@ -381,7 +382,7 @@ struct nfs4_client {#define NFSD4_CB_FAULT 3int cl_cb_state;struct nfsd4_callback cl_cb_null; -struct nfsd4_session *cl_cb_session; +struct nfsd4_session __rcu *cl_cb_session;/* for all client information that callback code might need: */ spinlock_t cl_lock;
Signature · 1 line
2.53.0