From: Sasha Levin <sashal@kernel.org>
To: stable@vger.kernel.org
Cc: Jeff Layton <jlayton@kernel.org>,
Chuck Lever <chuck.lever@oracle.com>,
Sasha Levin <sashal@kernel.org>
Subject: [PATCH 6.1.y 5/7] nfsd: prevent callback tasks running concurrently
Date: Sat, 5 Sep 2026 17:43:30 -0400 [thread overview]
Message-ID: <20260905214332.701369-5-sashal@kernel.org> (raw)
In-Reply-To: <20260905214332.701369-1-sashal@kernel.org>
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
next prev parent reply other threads:[~2026-09-05 21:43 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` Sasha Levin [this message]
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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260905214332.701369-5-sashal@kernel.org \
--to=sashal@kernel.org \
--cc=chuck.lever@oracle.com \
--cc=jlayton@kernel.org \
--cc=stable@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.