[PATCH openEuler-1.0-LTS] nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown
From: Jeff Layton <jlayton@kernel.org> mainline inclusion from mainline-v7.3-rc1 commit 01c5d5f58a5db9b0ee5afba2e49d3157788687b2 category: bugfix bugzilla: https://atomgit.com/src-openeuler/kernel/issues/18799 CVE: CVE-2026-89708 Reference: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=... -------------------------------- 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. Fixes: dcbeaa68dbbd ("nfsd4: allow backchannel recovery") Cc: stable@vger.kernel.org Reported-by: Chris Mason <clm@meta.com> Signed-off-by: Chris Mason <clm@meta.com> Signed-off-by: Jeff Layton <jlayton@kernel.org> Link: https://patch.msgid.link/20260530-nfsd-fixes-v2-2-f27e8eb4d974@kernel.org Signed-off-by: Chuck Lever <chuck.lever@oracle.com> Conflicts: fs/nfsd/nfs4callback.c fs/nfsd/nfs4state.c fs/nfsd/state.h [The target 4.18 tree lacks the nfsd_cb_seq_status/nfsd_cb_free_slot tracepoints and the modern cb slot-table (grab_slot/cb_held_slot, se_cb_seq_nr[] array, se_slots xarray, se_slot_gen/se_target_maxslots) that this upstream fix is built on; it uses the legacy single cl_cb_slot_busy slot and a scalar se_cb_seq_nr with a flexible-array se_slots[]. The RCU conversion was therefore adapted to those structures: only the cl_cb_session readers/writers and the kfree_rcu teardown are backported, the tracepoint-hunk and slot-table rework are skipped as not present in this tree.] Co-authored-by: BackportAgent@deepseek-v4-flash Signed-off-by: Hulk Robot <hulkrobot@huawei.com> Signed-off-by: Jinjiang Tu <tujinjiang@huawei.com> --- fs/nfsd/nfs4callback.c | 59 ++++++++++++++++++++++++++++++++++++------ fs/nfsd/nfs4state.c | 4 +-- fs/nfsd/state.h | 3 ++- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/fs/nfsd/nfs4callback.c b/fs/nfsd/nfs4callback.c index 5ed13b765517..5a439124c6ef 100644 --- a/fs/nfsd/nfs4callback.c +++ b/fs/nfsd/nfs4callback.c @@ -353,12 +353,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); @@ -370,6 +377,7 @@ static void encode_cb_sequence4args(struct xdr_stream *xdr, xdr_encode_empty_array(p); /* csa_referring_call_lists */ hdr->nops++; + rcu_read_unlock(); } /* @@ -396,21 +404,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); @@ -418,12 +437,14 @@ 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; } @@ -431,6 +452,7 @@ static int decode_cb_sequence4resok(struct xdr_stream *xdr, * FIXME: process highest slotid and target highest slotid */ status = 0; + rcu_read_unlock(); out: cb->cb_seq_status = status; return status; @@ -801,9 +823,8 @@ static int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *c if (!conn->cb_xprt || !ses) return -EINVAL; clp->cl_cb_conn.cb_xprt = conn->cb_xprt; - 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; @@ -820,6 +841,8 @@ static int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *c rpc_shutdown_client(client); return PTR_ERR(cred); } + if (clp->cl_minorversion != 0) + rcu_assign_pointer(clp->cl_cb_session, ses); clp->cl_cb_client = client; clp->cl_cb_cred = cred; return 0; @@ -926,6 +949,10 @@ static void nfsd4_cb_prepare(struct rpc_task *task, void *calldata) cb->cb_seq_status = 1; cb->cb_status = 0; if (minorversion) { + if (!rcu_access_pointer(clp->cl_cb_session)) { + rpc_exit(task, -EIO); + return; + } if (!cb->cb_holds_slot && !nfsd41_cb_get_slot(clp, task)) return; cb->cb_holds_slot = true; @@ -936,7 +963,7 @@ static void nfsd4_cb_prepare(struct rpc_task *task, void *calldata) 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; bool ret = true; if (!clp->cl_minorversion) { @@ -958,6 +985,15 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback if (!cb->cb_holds_slot) goto need_restart; + rcu_read_lock(); + session = rcu_dereference(clp->cl_cb_session); + if (!session) { + rcu_read_unlock(); + clear_bit(0, &clp->cl_cb_slot_busy); + rpc_wake_up_next(&clp->cl_cb_waitq); + goto need_restart; + } + switch (cb->cb_seq_status) { case 0: /* @@ -978,16 +1014,21 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback ret = false; break; case -NFS4ERR_DELAY: - if (!rpc_restart_call(task)) + if (!rpc_restart_call(task)) { + rcu_read_unlock(); goto out; + } 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; @@ -1000,7 +1041,8 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback clear_bit(0, &clp->cl_cb_slot_busy); rpc_wake_up_next(&clp->cl_cb_waitq); dprintk("%s: freed slot, new seqid=%d\n", __func__, - clp->cl_cb_session->se_cb_seq_nr); + session->se_cb_seq_nr); + rcu_read_unlock(); if (task->tk_flags & RPC_TASK_KILLED) goto need_restart; @@ -1149,6 +1191,7 @@ static void nfsd4_process_cb_update(struct nfsd4_callback *cb) err = setup_callback_client(clp, &conn, ses); if (err) { nfsd4_mark_cb_down(clp, err); + rcu_assign_pointer(clp->cl_cb_session, ses); return; } } diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index ba6ae4626a6c..fdd657d38d6d 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -1713,7 +1713,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) @@ -2207,7 +2207,7 @@ static struct nfs4_client *create_client(struct xdr_netobj name, clear_bit(0, &clp->cl_cb_slot_busy); copy_verf(clp, verf); rpc_copy_addr((struct sockaddr *) &clp->cl_addr, sa); - clp->cl_cb_session = NULL; + RCU_INIT_POINTER(clp->cl_cb_session, NULL); clp->net = net; return clp; } diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h index c6e4ea41d93a..3c39a0b5ddf6 100644 --- a/fs/nfsd/state.h +++ b/fs/nfsd/state.h @@ -256,6 +256,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 */ }; @@ -337,7 +338,7 @@ struct nfs4_client { #define NFSD4_CB_FAULT 3 int 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; -- 2.43.0
反馈: 您发送到kernel@openeuler.org的补丁/补丁集,已成功转换为PR! PR链接地址: https://atomgit.com/openeuler/kernel/merge_requests/29138 邮件列表地址:https://mailweb.openeuler.org/archives/list/kernel@openeuler.org/message/LSI... FeedBack: The patch(es) which you have sent to kernel@openeuler.org mailing list has been converted to a pull request successfully! Pull request link: https://atomgit.com/openeuler/kernel/merge_requests/29138 Mailing list address: https://mailweb.openeuler.org/archives/list/kernel@openeuler.org/message/LSI...
participants (2)
-
Jinjiang Tu -
patchwork bot