[PATCH net-next v3 03/13] net/rds: guard every work-requeueing site with rds_destroy_pending()
From: Allison Henderson <achender@kernel.org>
Date: 2026-09-14 03:37:22
Also in:
netdev
Subsystem:
networking [general], rds - reliable datagram sockets, the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Allison Henderson, Linus Torvalds
rds_conn_destroy() cancels the path works and then destroys the
per-path workqueue. The sites that can re-arm those works are
supposed to test rds_destroy_pending() under rcu_read_lock() first,
paired with the synchronize_rcu() in the destroy path, so that no new
work can be queued once the cancellation has begun.
Five arming sites never got that guard:
- rds_ib_send_cqe_handler() and rds_ib_send_add_credits() re-arm
cp_send_w when a send completion or a credit update clears
RDS_LL_SEND_FULL,
- rds_ib_recv_refill() re-arms cp_recv_w when the recv ring runs
low,
- rds_tcp_accept_one() kicks cp_recv_w on the freshly accepted
socket, and
- rds_sendmsg() arms cp_conn_w for a multipath connection whose
path 0 is not up yet.
The IB completion sites are reachable from soft-irq at any point
before the QP is drained, so a completion landing in the window
between the cancel and destroy_workqueue() in rds_conn_path_destroy()
re-arms a work on a workqueue that is about to be destroyed: with
delay 0 the work is queued directly on the freed workqueue, and with
delay 1 the timer survives destroy_workqueue() unseen and fires
afterwards, queueing from a timer_list that lives in the freed c_path
array.
Wrap all five sites in the same rcu_read_lock() +
rds_destroy_pending() pattern the other arming sites already use.
Fixes: ebeeb1ad9b8a ("rds: tcp: use rds_destroy_pending() to synchronize netns/module teardown and rds connection/workq management")
Assisted-by: Claude-Code:claude-fable-5
Signed-off-by: Allison Henderson <achender@kernel.org>
---
net/rds/ib_recv.c | 6 +++++-
net/rds/ib_send.c | 18 ++++++++++++++----
net/rds/send.c | 11 ++++++++---
net/rds/tcp_listen.c | 10 +++++++---
4 files changed, 34 insertions(+), 11 deletions(-)
diff --git a/net/rds/ib_recv.c b/net/rds/ib_recv.c
index bd6cb3ffaa57..7d45808544a0 100644
--- a/net/rds/ib_recv.c
+++ b/net/rds/ib_recv.c@@ -458,7 +458,11 @@ void rds_ib_recv_refill(struct rds_connection *conn, int prefill, gfp_t gfp) (must_wake || (can_wait && rds_ib_ring_low(&ic->i_recv_ring)) || rds_ib_ring_empty(&ic->i_recv_ring))) { - queue_delayed_work(conn->c_path->cp_wq, &conn->c_recv_w, 1); + rcu_read_lock(); + if (!rds_destroy_pending(conn)) + queue_delayed_work(conn->c_path->cp_wq, + &conn->c_recv_w, 1); + rcu_read_unlock(); } if (can_wait) cond_resched();
diff --git a/net/rds/ib_send.c b/net/rds/ib_send.c
index d6be95542119..bc411e96ad12 100644
--- a/net/rds/ib_send.c
+++ b/net/rds/ib_send.c@@ -298,8 +298,13 @@ void rds_ib_send_cqe_handler(struct rds_ib_connection *ic, struct ib_wc *wc) rds_ib_sub_signaled(ic, nr_sig); if (test_and_clear_bit(RDS_LL_SEND_FULL, &conn->c_flags) || - test_bit(0, &conn->c_map_queued)) - queue_delayed_work(conn->c_path->cp_wq, &conn->c_send_w, 0); + test_bit(0, &conn->c_map_queued)) { + rcu_read_lock(); + if (!rds_destroy_pending(conn)) + queue_delayed_work(conn->c_path->cp_wq, + &conn->c_send_w, 0); + rcu_read_unlock(); + } /* We expect errors as the qp is drained during shutdown */ if (wc->status != IB_WC_SUCCESS && rds_conn_up(conn)) {
@@ -420,8 +425,13 @@ void rds_ib_send_add_credits(struct rds_connection *conn, unsigned int credits) test_bit(RDS_LL_SEND_FULL, &conn->c_flags) ? ", ll_send_full" : ""); atomic_add(IB_SET_SEND_CREDITS(credits), &ic->i_credits); - if (test_and_clear_bit(RDS_LL_SEND_FULL, &conn->c_flags)) - queue_delayed_work(conn->c_path->cp_wq, &conn->c_send_w, 0); + if (test_and_clear_bit(RDS_LL_SEND_FULL, &conn->c_flags)) { + rcu_read_lock(); + if (!rds_destroy_pending(conn)) + queue_delayed_work(conn->c_path->cp_wq, + &conn->c_send_w, 0); + rcu_read_unlock(); + } WARN_ON(IB_GET_SEND_CREDITS(credits) >= 16384);
diff --git a/net/rds/send.c b/net/rds/send.c
index 1afa981e5c06..32c411d10e3e 100644
--- a/net/rds/send.c
+++ b/net/rds/send.c@@ -1378,9 +1378,14 @@ int rds_sendmsg(struct socket *sock, struct msghdr *msg, size_t payload_len) * outstanding. */ if (!test_and_set_bit(RDS_RECONNECT_PENDING, - &conn->c_path[0].cp_flags)) - queue_delayed_work(conn->c_path[0].cp_wq, - &conn->c_path[0].cp_conn_w, 0); + &conn->c_path[0].cp_flags)) { + rcu_read_lock(); + if (!rds_destroy_pending(conn)) + queue_delayed_work(conn->c_path[0].cp_wq, + &conn->c_path[0].cp_conn_w, + 0); + rcu_read_unlock(); + } rds_send_ping(conn, 0); }
diff --git a/net/rds/tcp_listen.c b/net/rds/tcp_listen.c
index 13fa60c1985b..8a0c54aced5e 100644
--- a/net/rds/tcp_listen.c
+++ b/net/rds/tcp_listen.c@@ -316,10 +316,14 @@ int rds_tcp_accept_one(struct rds_tcp_net *rtn) */ if (READ_ONCE(sk->sk_state) == TCP_CLOSE_WAIT || READ_ONCE(sk->sk_state) == TCP_LAST_ACK || - READ_ONCE(sk->sk_state) == TCP_CLOSE) + READ_ONCE(sk->sk_state) == TCP_CLOSE) { rds_conn_path_drop(cp, 0); - else - queue_delayed_work(cp->cp_wq, &cp->cp_recv_w, 0); + } else { + rcu_read_lock(); + if (!rds_destroy_pending(cp->cp_conn)) + queue_delayed_work(cp->cp_wq, &cp->cp_recv_w, 0); + rcu_read_unlock(); + } sock_put(sk);
--
2.25.1