* FAILED: patch "[PATCH] nfsd: RCU-protect cl_cb_session to fix use-after-free on" failed to apply to 6.1-stable tree
@ 2026-09-03 13:07 gregkh
2026-09-05 21:43 ` [PATCH 6.1.y 1/7] nfsd: prepare nfsd4_cb_sequence_done() for error handling rework Sasha Levin
0 siblings, 1 reply; 8+ messages in thread
From: gregkh @ 2026-09-03 13:07 UTC (permalink / raw)
To: jlayton, chuck.lever, clm; +Cc: stable
The patch below does not apply to the 6.1-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 <stable@vger.kernel.org>.
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.1.y
git checkout FETCH_HEAD
git cherry-pick -x 01c5d5f58a5db9b0ee5afba2e49d3157788687b2
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<stable@vger.kernel.org>' --in-reply-to '2026090302-gulf-kilometer-60be@gregkh' --subject-prefix 'PATCH 6.1.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 <jlayton@kernel.org>
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.
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>
diff --git a/fs/nfsd/nfs4callback.c b/fs/nfsd/nfs4callback.c
index 50827405468d..1628bb9ef9dd 100644
--- a/fs/nfsd/nfs4callback.c
+++ b/fs/nfsd/nfs4callback.c
@@ -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 *c
return -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;
}
}
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index c7eb558cb86b..c99538d36470 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -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,
diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h
index dec83e92650d..ac6fd0d6d099 100644
--- a/fs/nfsd/state.h
+++ b/fs/nfsd/state.h
@@ -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 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;
diff --git a/fs/nfsd/trace.h b/fs/nfsd/trace.h
index 1c5a1e50f946..830d9ceb4fe3 100644
--- a/fs/nfsd/trace.h
+++ b/fs/nfsd/trace.h
@@ -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;
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 6.1.y 1/7] nfsd: prepare nfsd4_cb_sequence_done() for error handling rework
2026-09-03 13:07 FAILED: patch "[PATCH] nfsd: RCU-protect cl_cb_session to fix use-after-free on" failed to apply to 6.1-stable tree gregkh
@ 2026-09-05 21:43 ` Sasha Levin
2026-09-05 21:43 ` [PATCH 6.1.y 2/7] nfsd: lift NFSv4.0 handling out of nfsd4_cb_sequence_done() Sasha Levin
` (5 more replies)
0 siblings, 6 replies; 8+ messages in thread
From: Sasha Levin @ 2026-09-05 21:43 UTC (permalink / raw)
To: stable; +Cc: Jeff Layton, Chuck Lever, Sasha Levin
From: Jeff Layton <jlayton@kernel.org>
[ 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.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Stable-dep-of: 01c5d5f58a5d ("nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/nfsd/nfs4callback.c | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/fs/nfsd/nfs4callback.c b/fs/nfsd/nfs4callback.c
index 272d3facfff9d..f9c28ba1d986b 100644
--- a/fs/nfsd/nfs4callback.c
+++ b/fs/nfsd/nfs4callback.c
@@ -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,6 +1193,7 @@ 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;
@@ -1199,13 +1201,11 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback
case 1:
case -NFS4ERR_BADSESSION:
nfsd4_mark_cb_fault(cb->cb_clp, cb->cb_seq_status);
- ret = false;
break;
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 +1227,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;
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 6.1.y 2/7] nfsd: lift NFSv4.0 handling out of nfsd4_cb_sequence_done()
2026-09-05 21:43 ` [PATCH 6.1.y 1/7] nfsd: prepare nfsd4_cb_sequence_done() for error handling rework Sasha Levin
@ 2026-09-05 21:43 ` Sasha Levin
2026-09-05 21:43 ` [PATCH 6.1.y 3/7] nfsd: only check RPC_SIGNALLED() when restarting rpc_task Sasha Levin
` (4 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Sasha Levin @ 2026-09-05 21:43 UTC (permalink / raw)
To: stable; +Cc: Jeff Layton, Chuck Lever, Sasha Levin
From: Jeff Layton <jlayton@kernel.org>
[ 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().
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Stable-dep-of: 01c5d5f58a5d ("nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/nfsd/nfs4callback.c | 51 ++++++++++++++++++++++--------------------
1 file changed, 27 insertions(+), 24 deletions(-)
diff --git a/fs/nfsd/nfs4callback.c b/fs/nfsd/nfs4callback.c
index f9c28ba1d986b..7333068371c14 100644
--- a/fs/nfsd/nfs4callback.c
+++ b/fs/nfsd/nfs4callback.c
@@ -914,6 +914,16 @@ static bool nfsd4_queue_cb(struct nfsd4_callback *cb)
return queue_work(callback_wq, &cb->cb_work);
}
+static void nfsd4_requeue_cb(struct rpc_task *task, 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;
+ }
+}
+
static void nfsd41_cb_inflight_begin(struct nfs4_client *clp)
{
atomic_inc(&clp->cl_cb_inflight);
@@ -1160,26 +1170,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;
@@ -1224,7 +1217,7 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback
nfsd41_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;
@@ -1234,10 +1227,7 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback
rpc_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;
}
@@ -1246,8 +1236,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);
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 6.1.y 3/7] nfsd: only check RPC_SIGNALLED() when restarting rpc_task
2026-09-05 21:43 ` [PATCH 6.1.y 1/7] nfsd: prepare nfsd4_cb_sequence_done() for error handling rework Sasha Levin
2026-09-05 21:43 ` [PATCH 6.1.y 2/7] nfsd: lift NFSv4.0 handling out of nfsd4_cb_sequence_done() Sasha Levin
@ 2026-09-05 21:43 ` Sasha Levin
2026-09-05 21:43 ` [PATCH 6.1.y 4/7] nfsd: handle NFS4ERR_BADSLOT on CB_SEQUENCE better Sasha Levin
` (3 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Sasha Levin @ 2026-09-05 21:43 UTC (permalink / raw)
To: stable; +Cc: Jeff Layton, Chuck Lever, Sasha Levin
From: Jeff Layton <jlayton@kernel.org>
[ 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.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Stable-dep-of: 01c5d5f58a5d ("nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/nfsd/nfs4callback.c | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/fs/nfsd/nfs4callback.c b/fs/nfsd/nfs4callback.c
index 7333068371c14..d0e5f46f0f7cd 100644
--- a/fs/nfsd/nfs4callback.c
+++ b/fs/nfsd/nfs4callback.c
@@ -1197,8 +1197,8 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback
break;
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:
@@ -1218,14 +1218,16 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback
nfsd41_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:
nfsd4_requeue_cb(task, cb);
return false;
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 6.1.y 4/7] nfsd: handle NFS4ERR_BADSLOT on CB_SEQUENCE better
2026-09-05 21:43 ` [PATCH 6.1.y 1/7] nfsd: prepare nfsd4_cb_sequence_done() for error handling rework Sasha Levin
2026-09-05 21:43 ` [PATCH 6.1.y 2/7] nfsd: lift NFSv4.0 handling out of nfsd4_cb_sequence_done() Sasha Levin
2026-09-05 21:43 ` [PATCH 6.1.y 3/7] nfsd: only check RPC_SIGNALLED() when restarting rpc_task Sasha Levin
@ 2026-09-05 21:43 ` Sasha Levin
2026-09-05 21:43 ` [PATCH 6.1.y 5/7] nfsd: prevent callback tasks running concurrently Sasha Levin
` (2 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Sasha Levin @ 2026-09-05 21:43 UTC (permalink / raw)
To: stable; +Cc: Jeff Layton, Chuck Lever, Sasha Levin
From: Jeff Layton <jlayton@kernel.org>
[ Upstream commit 999595a651a87f4285c53710b5f600d6a141f526 ]
Currently it just restarts the call, without getting a new slot.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Stable-dep-of: 01c5d5f58a5d ("nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/nfsd/nfs4callback.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/fs/nfsd/nfs4callback.c b/fs/nfsd/nfs4callback.c
index d0e5f46f0f7cd..c918fd3452f31 100644
--- a/fs/nfsd/nfs4callback.c
+++ b/fs/nfsd/nfs4callback.c
@@ -1202,6 +1202,13 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback
rpc_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) {
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 6.1.y 5/7] nfsd: prevent callback tasks running concurrently
2026-09-05 21:43 ` [PATCH 6.1.y 1/7] nfsd: prepare nfsd4_cb_sequence_done() for error handling rework Sasha Levin
` (2 preceding siblings ...)
2026-09-05 21:43 ` [PATCH 6.1.y 4/7] nfsd: handle NFS4ERR_BADSLOT on CB_SEQUENCE better Sasha Levin
@ 2026-09-05 21:43 ` Sasha Levin
2026-09-05 21:43 ` [PATCH 6.1.y 6/7] nfsd: move cb_need_restart flag into cb_flags Sasha Levin
2026-09-05 21:43 ` [PATCH 6.1.y 7/7] nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown Sasha Levin
5 siblings, 0 replies; 8+ messages in thread
From: Sasha Levin @ 2026-09-05 21:43 UTC (permalink / raw)
To: stable; +Cc: Jeff Layton, Chuck Lever, Sasha Levin
From: Jeff Layton <jlayton@kernel.org>
[ 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.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Stable-dep-of: 01c5d5f58a5d ("nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/nfsd/nfs4callback.c | 36 +++++++++++++-----------------------
fs/nfsd/nfs4layouts.c | 7 ++++---
fs/nfsd/nfs4proc.c | 3 ++-
fs/nfsd/nfs4state.c | 12 ++++++++++--
fs/nfsd/state.h | 2 ++
5 files changed, 31 insertions(+), 29 deletions(-)
diff --git a/fs/nfsd/nfs4callback.c b/fs/nfsd/nfs4callback.c
index c918fd3452f31..138fa901837eb 100644
--- a/fs/nfsd/nfs4callback.c
+++ b/fs/nfsd/nfs4callback.c
@@ -914,16 +914,6 @@ static bool nfsd4_queue_cb(struct nfsd4_callback *cb)
return queue_work(callback_wq, &cb->cb_work);
}
-static void nfsd4_requeue_cb(struct rpc_task *task, 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;
- }
-}
-
static void nfsd41_cb_inflight_begin(struct nfs4_client *clp)
{
atomic_inc(&clp->cl_cb_inflight);
@@ -1141,6 +1131,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);
@@ -1170,7 +1161,8 @@ 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 nfsd4_session *session = cb->cb_clp->cl_cb_session;
+ struct nfs4_client *clp = cb->cb_clp;
+ struct nfsd4_session *session = clp->cl_cb_session;
bool ret = false;
if (!cb->cb_holds_slot)
@@ -1202,13 +1194,6 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback
rpc_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) {
@@ -1223,8 +1208,6 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback
}
nfsd41_cb_release_slot(cb);
- dprintk("%s: freed slot, new seqid=%d\n", __func__,
- session->se_cb_seq_nr);
return ret;
retry_nowait:
/*
@@ -1236,7 +1219,10 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback
return false;
}
requeue:
- nfsd4_requeue_cb(task, cb);
+ if (!test_bit(NFSD4_CLIENT_CB_KILL, &clp->cl_flags)) {
+ task->tk_status = 0;
+ cb->cb_need_restart = true;
+ }
return false;
}
@@ -1255,8 +1241,11 @@ static void nfsd4_cb_done(struct rpc_task *task, void *calldata)
* 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);
+ if (RPC_SIGNALLED(task) &&
+ !test_bit(NFSD4_CLIENT_CB_KILL, &clp->cl_flags)) {
+ task->tk_status = 0;
+ cb->cb_need_restart = true;
+ }
} else if (!nfsd4_cb_sequence_done(task, cb)) {
return;
}
@@ -1452,6 +1441,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;
diff --git a/fs/nfsd/nfs4layouts.c b/fs/nfsd/nfs4layouts.c
index 84bb200e24ade..cd5b0dfc3e8e4 100644
--- a/fs/nfsd/nfs4layouts.c
+++ b/fs/nfsd/nfs4layouts.c
@@ -327,9 +327,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);
}
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index d29a37f696a44..3f8bf98a4b22b 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -1710,7 +1710,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));
}
/**
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index f390716ce3ffe..2d433b9742fd5 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -4945,6 +4945,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
@@ -6334,7 +6338,9 @@ deleg_reaper(struct nfsd_net *nn)
clp->cl_ra->ra_keep = 0;
clp->cl_ra->ra_bmval[0] = BIT(RCA4_TYPE_MASK_RDATA_DLG);
trace_nfsd_cb_recall_any(clp->cl_ra);
- 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));
}
}
@@ -7211,7 +7217,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));
}
}
diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h
index 53298bdcfb3d0..d2134782b4021 100644
--- a/fs/nfsd/state.h
+++ b/fs/nfsd/state.h
@@ -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;
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 6.1.y 6/7] nfsd: move cb_need_restart flag into cb_flags
2026-09-05 21:43 ` [PATCH 6.1.y 1/7] nfsd: prepare nfsd4_cb_sequence_done() for error handling rework Sasha Levin
` (3 preceding siblings ...)
2026-09-05 21:43 ` [PATCH 6.1.y 5/7] nfsd: prevent callback tasks running concurrently Sasha Levin
@ 2026-09-05 21:43 ` Sasha Levin
2026-09-05 21:43 ` [PATCH 6.1.y 7/7] nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown Sasha Levin
5 siblings, 0 replies; 8+ messages in thread
From: Sasha Levin @ 2026-09-05 21:43 UTC (permalink / raw)
To: stable; +Cc: Jeff Layton, Chuck Lever, Sasha Levin
From: Jeff Layton <jlayton@kernel.org>
[ 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.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
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 remains open-coded in both the NFSv4.1 sequence completion path and the NFSv4.0 completion path. Convert both sites to the flag and omit the callback lifetime tracepoint hunk, since 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")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/nfsd/nfs4callback.c | 11 ++++-------
fs/nfsd/state.h | 2 +-
2 files changed, 5 insertions(+), 8 deletions(-)
diff --git a/fs/nfsd/nfs4callback.c b/fs/nfsd/nfs4callback.c
index 138fa901837eb..964d7f56dfc79 100644
--- a/fs/nfsd/nfs4callback.c
+++ b/fs/nfsd/nfs4callback.c
@@ -1221,7 +1221,7 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback
requeue:
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);
}
return false;
}
@@ -1244,7 +1244,7 @@ static void nfsd4_cb_done(struct rpc_task *task, void *calldata)
if (RPC_SIGNALLED(task) &&
!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);
}
} else if (!nfsd4_cb_sequence_done(task, cb)) {
return;
@@ -1277,7 +1277,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);
@@ -1399,9 +1399,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);
}
@@ -1446,7 +1444,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;
}
diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h
index d2134782b4021..10d1b418e0c7c 100644
--- a/fs/nfsd/state.h
+++ b/fs/nfsd/state.h
@@ -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;
};
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 6.1.y 7/7] nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown
2026-09-05 21:43 ` [PATCH 6.1.y 1/7] nfsd: prepare nfsd4_cb_sequence_done() for error handling rework Sasha Levin
` (4 preceding siblings ...)
2026-09-05 21:43 ` [PATCH 6.1.y 6/7] nfsd: move cb_need_restart flag into cb_flags Sasha Levin
@ 2026-09-05 21:43 ` Sasha Levin
5 siblings, 0 replies; 8+ messages in thread
From: Sasha Levin @ 2026-09-05 21:43 UTC (permalink / raw)
To: stable; +Cc: Jeff Layton, Chris Mason, Chuck Lever, Sasha Levin
From: Jeff Layton <jlayton@kernel.org>
[ 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.
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>
[ adapted RCU session handling to the single-slot callback model and flexible-array session layout ]
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/nfsd/nfs4callback.c | 72 +++++++++++++++++++++++++++++++++++-------
fs/nfsd/nfs4state.c | 4 +--
fs/nfsd/state.h | 3 +-
3 files changed, 65 insertions(+), 14 deletions(-)
diff --git a/fs/nfsd/nfs4callback.c b/fs/nfsd/nfs4callback.c
index 964d7f56dfc79..6baff3d026434 100644
--- a/fs/nfsd/nfs4callback.c
+++ b/fs/nfsd/nfs4callback.c
@@ -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;
@@ -988,9 +1010,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;
@@ -1008,8 +1029,10 @@ static int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *c
return -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();
@@ -1153,8 +1176,14 @@ 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);
}
@@ -1162,12 +1191,19 @@ 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 = false;
if (!cb->cb_holds_slot)
goto requeue;
+ rcu_read_lock();
+ session = rcu_dereference(clp->cl_cb_session);
+ if (!session) {
+ rcu_read_unlock();
+ goto requeue;
+ }
+
switch (cb->cb_seq_status) {
case 0:
/*
@@ -1189,15 +1225,20 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback
break;
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;
@@ -1207,6 +1248,7 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback
cb->cb_seq_status);
}
+ rcu_read_unlock();
nfsd41_cb_release_slot(cb);
return ret;
retry_nowait:
@@ -1339,7 +1381,14 @@ 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)
{
@@ -1386,6 +1435,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;
}
}
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 2d433b9742fd5..5245ab7dfa8f7 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -1978,7 +1978,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)
@@ -2945,7 +2945,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,
diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h
index 10d1b418e0c7c..649ca2c0844b0 100644
--- a/fs/nfsd/state.h
+++ b/fs/nfsd/state.h
@@ -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 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.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-09-05 21:43 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-03 13:07 FAILED: patch "[PATCH] nfsd: RCU-protect cl_cb_session to fix use-after-free on" failed to apply to 6.1-stable tree gregkh
2026-09-05 21:43 ` [PATCH 6.1.y 1/7] nfsd: prepare nfsd4_cb_sequence_done() for error handling rework Sasha Levin
2026-09-05 21:43 ` [PATCH 6.1.y 2/7] nfsd: lift NFSv4.0 handling out of nfsd4_cb_sequence_done() Sasha Levin
2026-09-05 21:43 ` [PATCH 6.1.y 3/7] nfsd: only check RPC_SIGNALLED() when restarting rpc_task Sasha Levin
2026-09-05 21:43 ` [PATCH 6.1.y 4/7] nfsd: handle NFS4ERR_BADSLOT on CB_SEQUENCE better Sasha Levin
2026-09-05 21:43 ` [PATCH 6.1.y 5/7] nfsd: prevent callback tasks running concurrently Sasha Levin
2026-09-05 21:43 ` [PATCH 6.1.y 6/7] nfsd: move cb_need_restart flag into cb_flags Sasha Levin
2026-09-05 21:43 ` [PATCH 6.1.y 7/7] nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown Sasha Levin
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.