* [PATCH net-next v5 00/12] net/rds: make connection lifetime reference-counted
@ 2026-09-19 6:11 Allison Henderson
2026-09-19 6:11 ` [PATCH net-next v5 01/12] net/rds: ib: don't enable interrupts in rds_ib_conn_free() Allison Henderson
` (11 more replies)
0 siblings, 12 replies; 35+ messages in thread
From: Allison Henderson @ 2026-09-19 6:11 UTC (permalink / raw)
To: netdev, linux-rdma, pabeni, edumazet, kuba, horms; +Cc: achender, nicoyip.dev
Hi all,
This is v5 of the connection-lifetime set (v1 at [1], v2 at [2],
v3 at [3], v4 at [6]), following
"net/rds: own the fastpath locks across connection teardown", which is
in net-next. It is targeted at net-next: although the series fixes
real use-after-frees (one syzbot report and one report from Chengfeng
Ye below), it does so by reworking connection lifetime rather than
patching the individual crash sites, and that rework is too invasive
for net.
rds_conn_destroy() frees the connection, its paths and its workqueues
on the spot, relying on the documented assumption that "no one else is
referencing the connection". That assumption stopped being true a
long time ago: connections are destroyed on netns teardown and on a
protocol-version mismatch as well as on rmmod, while pointers to them
still live in socket rs_conn caches, congestion-map conn lists, CM
callbacks and workers, and - for as long as an application leaves data
unread - in every rds_incoming sitting on a receive queue. No single
Fixes: commit covers the rot, so the series carries Reported-by tags
where there are concrete reports instead.
Patch 1 fixes rds_ib_conn_free() re-enabling interrupts under
a caller's irqsave lock.
Patch 2 frees every path's transport data on the
passive-connection exits of __rds_conn_create(), where only path 0 was
freed. Both are pre-existing and stand on their own; they are first
in the series because the reference-counted teardown calls conn_free()
from more places.
Patch 3 guards the five work-arming sites that never tested
rds_destroy_pending(): two IB completion paths, the IB recv refill,
the TCP accept kick and the multipath reconnect in sendmsg.
Patch 4 gives the connection its own destroy-in-progress marker so
that the predicate also covers the one single-connection destroy.
Based on UEK commits:
e2f5005adf63 net/rds: Add krefs to struct rds_connection
https://github.com/oracle/linux-uek/commit/e2f5005adf63
6c53ef92f46e net/rds: Merge uses of conn->c_destroy_in_prog & RDS_DESTROY_PENDING
https://github.com/oracle/linux-uek/commit/6c53ef92f46e
Patch 5 splits rds_conn_destroy() into a synchronous quiesce and a
kref-governed free.
Based on UEK commits:
2c8569e4c880 ("net/rds: Add krefs to struct rds_connection").
https://github.com/oracle/linux-uek/commit/2c8569e4c880
Patch 6 makes each transport's exit path wait for its connections to
actually be freed before the module goes away. The wait is
unbounded and warns every ten seconds: a connection reference can be
held for an application-controlled time (unread data), so a timeout
would only move the use-after-free from freed memory to unloaded
module text. rmmod blocking while data is queued and unread is the
historical RDS contract. For IB the wait re-sweeps the nodev list,
since device connections migrate to it asynchronously.
Based on UEK commits:
ece4b4e39afa ("net/rds: wait_event_timeout until zero connections during rmmod")
https://github.com/oracle/linux-uek/commit/ece4b4e39afa
905ec90e6166 ("net/rds: Each RDS transport should keep its own connection count")
https://github.com/oracle/linux-uek/commit/905ec90e6166
Patch 7 unlinks each transport node before its destroy, so a
free deferred past the teardown loop cannot write into the loop's
stack-local list head. IB marks a gathered node as claimed by the
sweep, since its connect and shutdown paths move the node too.
Patch 8 hands out real references everywhere a connection pointer
previously escaped bare, RCU-annotates parent->c_passive, refuses to
revive a passive connection whose destroy has begun, and serializes
the SIOCRDSSETTOS check with the rs_conn cache.
Based on UEK commits:
2c8569e4c880 ("net/rds: Add krefs to struct rds_connection")
https://github.com/oracle/linux-uek/commit/2c8569e4c880
0e9e3a72b7f7 ("net/rds: rds_sendmsg must use rs_conn only when not being destroyed").
https://github.com/oracle/linux-uek/commit/0e9e3a72b7f7
Patch 9 has rds_send_queue_rm() and rds_send_probe() refuse, under
cp_lock, to queue on a connection whose destroy has begun, and has
the quiesce splice cp_send_queue away under the same lock: a message
queued after the purge would hold a connection reference nothing
ever drops.
Patch 10 pins the connection across the RDMA-CM event handler
and rejects a connect request for a connection whose destroy has
already quiesced it.
Patch 11 drops the now-unused global rds_conn_count.
Based on UEK commits:
905ec90e6166 ("net/rds: Each RDS transport should keep its own connection count").
https://github.com/oracle/linux-uek/commit/905ec90e6166
Patch 12 makes struct rds_incoming hold a reference on i_conn, the
fix for the KASAN use-after-free Chengfeng Ye reported [4].
Based on UEK commits:
99b9a3715419 ("net/rds: fix crash by expanding kref coverage to rds_incoming.i_conn").
https://github.com/oracle/linux-uek/commit/99b9a3715419
Patches 5, 8, 6 and 12 are ports of the connection kref work Sharath
Srinivasan did for Oracle UEK, adapted to the upstream code.
The series has been validated with the RDS selftests over loopback-TCP
and RXE-RDMA, plus churn tests that delete network namespaces and
unload the modules under live rds-stress traffic.
Changes since v4 [6]:
- Patch 7: the IB sweep marks each gathered node with a new
i_ib_node_detached flag instead of relying on list emptiness. A
node parked on the sweep's stack list is not empty, so a concurrent
rds_ib_add_conn() would have unlinked it from under the sweep's
lockless walk; rds_ib_add_conn(), rds_ib_remove_conn() and
rds_ib_conn_free() now leave a claimed node alone.
Changes since v3 [3]:
- Rebased; the version-mismatch destroy that patch 10 also had to
cope with is now an rds_conn_drop() in net (f97d8c7bab78), and
patch 10's changelog says what remains for it to cover.
- The uninitialised transport pointer in the CM event handler that
the second v2 review pass raised turned out to be the bug Aohan
Mei already posted a fix for (v2 at [5], stalled after review); it
is carried forward separately for net rather than added here.
- Patch 7: the teardown walks take a reference on each gathered
connection, so a connection destroyed earlier and freed by a
pending holder cannot vanish under the iterator, and the two IB
list movers tolerate a node the sweep already unlinked instead of
BUG_ON()ing; the nodev sweep gathers entry by entry, so the
resweep from the unload wait no longer relies on list_splice_init().
- Patch 8: SIOCRDSSETTOS check-then-act closed - the install in
rds_sendmsg() re-checks the socket's ToS under rs_lock; rs_conn
documented as a referenced, rs_lock-serialised cache; contract
comment restored to rds_conn_lookup().
- Patch 9: rds_send_probe() gets the same rds_destroy_pending()
test under cp_lock as rds_send_queue_rm() (a probe queued after the
purge pinned the connection for good).
- v3 patch 10 (rds_tcp_accept_one() destroy check) dropped: the check
was not serialised against the destroy, and the race it aimed at is
not reachable - every TCP destroy path stops the listener, flushing
the accept work, first. A comment now records that ordering.
- Changelog and comment corrections from the second v2 review pass
(self-requeue exemption in patch 3, c_refcount comment in patch 5,
the uninterruptible wait and the transport-text wake in patch 6,
"lock-free" wording in patch 11, cross-netns comment in patch 12).
Changes since v2 [2]:
- New patches 1 and 2: pre-existing rds_ib_conn_free() interrupt
state clobber and passive-path transport data leak, surfaced by
review of the teardown changes.
- Patch 6: rds_ib_destroy_nodev_conns() uses list_splice_init(), so
the resweep from the unload wait cannot splice a stale list head.
- Patch 8: SIOCRDSGETTOS reads rs_tos under rs_lock like SETTOS.
- New patch 9: rds_send_queue_rm() refuses a connection whose destroy
has begun, under cp_lock, and the quiesce purges cp_send_queue
under cp_lock (list corruption against an in-flight sender, and a
message that would pin the connection forever).
- New patch 10: rds_tcp_accept_one() does not install a socket on a
connection whose destroy has begun (socket left pointing at a freed
path).
Changes since v1 [1]:
- New patch 1: guard the five work-arming sites that never tested
rds_destroy_pending(); patch 2's changelog and comments narrowed
to what it actually newly covers.
- Patch 4 moved ahead of the reference holders, so no bisect point
has references without the unload wait; wait made unbounded with a
periodic warning instead of a 10 s timeout; IB exit re-sweeps the
nodev list for connections that detach from their device late.
- New patch 5: transport nodes unlinked before destroy (stack list
head use-after-free from a deferred conn_free).
- Patch 6: c_passive RCU-annotated; a destroyed passive child is
refused by __rds_conn_create() and clears the parent's pointer
itself; SIOCRDSSETTOS uses rs_lock; lookup comment reworded;
explicit not-for-stable note.
- New patch 7: reference across the CM event handler, and a
destroy-pending re-check in rds_ib_cm_handle_connect().
- Patch 9: changelog states what a lingering inc keeps alive and
that it blocks module unload.
- Changelog corrections throughout (netns teardown paths named as the
non-rmmod destroyers, stale rds_conn_path_destroy() reference).
[1] https://lore.kernel.org/netdev/20260904070248.160384-1-achender@kernel.org/
[2] https://lore.kernel.org/netdev/20260912035027.27447-1-achender@kernel.org/
[3] https://lore.kernel.org/netdev/20260914033719.138057-1-achender@kernel.org/
[4] https://lore.kernel.org/netdev/20260720184955.3008978-1-nicoyip.dev@gmail.com/
[5] https://lore.kernel.org/netdev/20260825021223.3483044-1-ljp1205831794@gmail.com/
[6] https://lore.kernel.org/netdev/20260917073958.174056-1-achender@kernel.org/
Allison
Allison Henderson (8):
net/rds: ib: don't enable interrupts in rds_ib_conn_free()
net/rds: free every path's transport data on the passive create paths
net/rds: guard every work-requeueing site with rds_destroy_pending()
net/rds: make rds_destroy_pending() cover single-connection destroy
net/rds: unlink transport nodes before a possibly deferred connection
free
net/rds: refuse to queue on a connection being destroyed
net/rds: pin the connection across RDMA-CM event handling
net/rds: drop rds_conn_count in favor of t_conn_count
Sharath Srinivasan (4):
net/rds: split connection destroy into quiesce and kref-governed free
net/rds: wait for connections to be freed on transport unload
net/rds: hold connection references in lookup, sockets and c_passive
net/rds: hold a connection reference from struct rds_incoming
net/rds/af_rds.c | 24 ++-
net/rds/connection.c | 327 +++++++++++++++++++++++++++++++++------
net/rds/ib.c | 22 ++-
net/rds/ib.h | 4 +
net/rds/ib_cm.c | 29 +++-
net/rds/ib_rdma.c | 67 ++++++--
net/rds/ib_recv.c | 6 +-
net/rds/ib_send.c | 18 ++-
net/rds/loop.c | 61 ++++++--
net/rds/message.c | 16 +-
net/rds/rdma_transport.c | 16 +-
net/rds/rds.h | 45 +++++-
net/rds/recv.c | 26 +++-
net/rds/send.c | 96 ++++++++++--
net/rds/tcp.c | 53 ++++++-
net/rds/tcp_listen.c | 21 ++-
16 files changed, 713 insertions(+), 118 deletions(-)
--
2.25.1
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH net-next v5 01/12] net/rds: ib: don't enable interrupts in rds_ib_conn_free()
2026-09-19 6:11 [PATCH net-next v5 00/12] net/rds: make connection lifetime reference-counted Allison Henderson
@ 2026-09-19 6:11 ` Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-19 6:11 ` [PATCH net-next v5 02/12] net/rds: free every path's transport data on the passive create paths Allison Henderson
` (10 subsequent siblings)
11 siblings, 1 reply; 35+ messages in thread
From: Allison Henderson @ 2026-09-19 6:11 UTC (permalink / raw)
To: netdev, linux-rdma, pabeni, edumazet, kuba, horms; +Cc: achender, nicoyip.dev
rds_ib_conn_free() unlinks the connection from its device or nodev
list under spin_lock_irq()/spin_unlock_irq(). It is not only called
from the rmmod path, though: __rds_conn_create() calls
trans->conn_free() to undo a lost creation race while it still holds
rds_conn_lock, taken with spin_lock_irqsave(). The unconditional
spin_unlock_irq() then re-enables interrupts with rds_conn_lock held
and leaves them enabled when the caller's spin_unlock_irqrestore()
runs, defeating the irqsave the caller relied on.
Use the irqsave/irqrestore pair, as rds_tcp_conn_free() and
rds_loop_conn_free() already do.
Fixes: 745cbccac3fe ("RDS: Rewrite connection cleanup, fixing oops on rmmod")
Assisted-by: Claude-Code:claude-fable-5
Signed-off-by: Allison Henderson <achender@kernel.org>
---
net/rds/ib_cm.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/net/rds/ib_cm.c b/net/rds/ib_cm.c
index 4feb0edc360c..118e033229aa 100644
--- a/net/rds/ib_cm.c
+++ b/net/rds/ib_cm.c
@@ -1271,6 +1271,7 @@ void rds_ib_conn_free(void *arg)
{
struct rds_ib_connection *ic = arg;
spinlock_t *lock_ptr;
+ unsigned long flags;
rdsdebug("ic %p\n", ic);
@@ -1278,12 +1279,16 @@ void rds_ib_conn_free(void *arg)
* Conn is either on a dev's list or on the nodev list.
* A race with shutdown() or connect() would cause problems
* (since rds_ibdev would change) but that should never happen.
+ *
+ * Callers may hold rds_conn_lock with interrupts disabled
+ * (__rds_conn_create() undoing a lost creation race), so do not
+ * re-enable interrupts unconditionally here.
*/
lock_ptr = ic->rds_ibdev ? &ic->rds_ibdev->spinlock : &ib_nodev_conns_lock;
- spin_lock_irq(lock_ptr);
+ spin_lock_irqsave(lock_ptr, flags);
list_del(&ic->ib_node);
- spin_unlock_irq(lock_ptr);
+ spin_unlock_irqrestore(lock_ptr, flags);
rds_ib_recv_free_caches(ic);
--
2.25.1
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v5 02/12] net/rds: free every path's transport data on the passive create paths
2026-09-19 6:11 [PATCH net-next v5 00/12] net/rds: make connection lifetime reference-counted Allison Henderson
2026-09-19 6:11 ` [PATCH net-next v5 01/12] net/rds: ib: don't enable interrupts in rds_ib_conn_free() Allison Henderson
@ 2026-09-19 6:11 ` Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
2026-09-19 6:11 ` [PATCH net-next v5 03/12] net/rds: guard every work-requeueing site with rds_destroy_pending() Allison Henderson
` (9 subsequent siblings)
11 siblings, 2 replies; 35+ messages in thread
From: Allison Henderson @ 2026-09-19 6:11 UTC (permalink / raw)
To: netdev, linux-rdma, pabeni, edumazet, kuba, horms; +Cc: achender, nicoyip.dev
trans->conn_alloc() may allocate transport data for every path of a
multipath connection - rds_tcp_conn_alloc() does - which is why the
lost-creation-race exit of __rds_conn_create() loops over all npaths
when it frees the connection it just built. The passive-connection
exit right above it, taken when a loopback parent already has its
c_passive twin, frees only path 0 and leaks the transport data of
paths 1..npaths-1. RDS/TCP loopback is exactly a multipath passive
connection, so this is reachable.
Move the loop into a helper and use it on both exits.
Fixes: 1c5113cf796b ("RDS: Initialize all RDS_MPATH_WORKERS in __rds_conn_create")
Assisted-by: Claude-Code:claude-fable-5
Signed-off-by: Allison Henderson <achender@kernel.org>
---
net/rds/connection.c | 31 ++++++++++++++++++-------------
1 file changed, 18 insertions(+), 13 deletions(-)
diff --git a/net/rds/connection.c b/net/rds/connection.c
index b6c4beb50eaf..a96569a3ee9a 100644
--- a/net/rds/connection.c
+++ b/net/rds/connection.c
@@ -161,6 +161,22 @@ static void __rds_conn_path_init(struct rds_connection *conn,
cp->cp_flags = 0;
}
+/* Undo trans->conn_alloc(): it may have allocated transport data for
+ * every path of a multipath connection, not just for path 0.
+ */
+static void rds_conn_free_transport_data(struct rds_connection *conn,
+ int npaths)
+{
+ struct rds_conn_path *cp;
+ int i;
+
+ for (i = 0; i < npaths; i++) {
+ cp = &conn->c_path[i];
+ if (cp->cp_transport_data)
+ conn->c_trans->conn_free(cp->cp_transport_data);
+ }
+}
+
/*
* There is only every one 'conn' for a given pair of addresses in the
* system at a time. They contain messages to be retransmitted and so
@@ -316,7 +332,7 @@ static struct rds_connection *__rds_conn_create(struct net *net,
if (parent) {
/* Creating passive conn */
if (parent->c_passive) {
- trans->conn_free(conn->c_path[0].cp_transport_data);
+ rds_conn_free_transport_data(conn, npaths);
free_cp = conn->c_path;
kmem_cache_free(rds_conn_slab, conn);
conn = parent->c_passive;
@@ -332,18 +348,7 @@ static struct rds_connection *__rds_conn_create(struct net *net,
found = rds_conn_lookup(net, head, laddr, faddr, trans,
tos, dev_if);
if (found) {
- struct rds_conn_path *cp;
- int i;
-
- for (i = 0; i < npaths; i++) {
- cp = &conn->c_path[i];
- /* The ->conn_alloc invocation may have
- * allocated resource for all paths, so all
- * of them may have to be freed here.
- */
- if (cp->cp_transport_data)
- trans->conn_free(cp->cp_transport_data);
- }
+ rds_conn_free_transport_data(conn, npaths);
free_cp = conn->c_path;
kmem_cache_free(rds_conn_slab, conn);
conn = found;
--
2.25.1
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v5 03/12] net/rds: guard every work-requeueing site with rds_destroy_pending()
2026-09-19 6:11 [PATCH net-next v5 00/12] net/rds: make connection lifetime reference-counted Allison Henderson
2026-09-19 6:11 ` [PATCH net-next v5 01/12] net/rds: ib: don't enable interrupts in rds_ib_conn_free() Allison Henderson
2026-09-19 6:11 ` [PATCH net-next v5 02/12] net/rds: free every path's transport data on the passive create paths Allison Henderson
@ 2026-09-19 6:11 ` Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
2026-09-19 6:11 ` [PATCH net-next v5 04/12] net/rds: make rds_destroy_pending() cover single-connection destroy Allison Henderson
` (8 subsequent siblings)
11 siblings, 2 replies; 35+ messages in thread
From: Allison Henderson @ 2026-09-19 6:11 UTC (permalink / raw)
To: netdev, linux-rdma, pabeni, edumazet, kuba, horms; +Cc: achender, nicoyip.dev
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.
The four self-requeues in rds_send_worker() and rds_recv_worker() are
left alone on purpose: they run from inside the work item itself, and
cancel_delayed_work_sync() disables the work for the duration of the
cancel, so a requeue issued by the still-running callback is dropped
and none can follow once the cancel has returned.
With the predicate as it stands the guards cover the netns teardown and
module unload cases; the following patch extends it to the destroy of a
single connection.
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
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v5 04/12] net/rds: make rds_destroy_pending() cover single-connection destroy
2026-09-19 6:11 [PATCH net-next v5 00/12] net/rds: make connection lifetime reference-counted Allison Henderson
` (2 preceding siblings ...)
2026-09-19 6:11 ` [PATCH net-next v5 03/12] net/rds: guard every work-requeueing site with rds_destroy_pending() Allison Henderson
@ 2026-09-19 6:11 ` Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
2026-09-19 6:11 ` [PATCH net-next v5 05/12] net/rds: split connection destroy into quiesce and kref-governed free Allison Henderson
` (7 subsequent siblings)
11 siblings, 2 replies; 35+ messages in thread
From: Allison Henderson @ 2026-09-19 6:11 UTC (permalink / raw)
To: netdev, linux-rdma, pabeni, edumazet, kuba, horms; +Cc: achender, nicoyip.dev
rds_conn_destroy() cancels the path works and then destroys the
per-path workqueue. However, nothing currently stops the
work-requeueing sites from queueing new work on the connection while
that happens. The existing code would suggest that this protection
is supposed to come from rds_destroy_pending(), since all of those
sites - apart from the workers' own self-requeues, which the sync
cancel in the destroy path already rejects - guard the queueing with
rds_destroy_pending() under rcu_read_lock() (the last stragglers were
converted by the previous patch), and rds_conn_destroy() already issues a
synchronize_rcu() after unhashing the connection. But the predicate
only tests for the two global teardown cases (netns destruction via
check_net(), module unload via ->t_unloading). Because the conn
itself lacks any indication that a destroy is in progress, the
predicate does not cover the destruction of a single connection
outside these two cases.
rds_conn_destroy() is not limited to the global paths:
rds_ib_cm_connect_complete() destroys a single connection whose peer
negotiated an unsupported protocol version. (The other per-transport
caller, rds_ib_destroy_nodev_conns(), is only reached from
rds_ib_exit(), where ->t_unloading already covers it.) While that
destroy runs, a concurrent rds_cong_queue_updates() can
still find the connection on the congestion map's m_conn_list (the
conn is only removed from it after the paths are torn down) and call
queue_delayed_work() on a cp_wq that destroy_workqueue() has already
freed. Additionally, the other requeueing sites can likewise re-arm
works that live in the about-to-be-freed connection unless
rds_destroy_pending() has something to guard it with.
The version-mismatch path used to be covered: commit c90ecbfaf50d2
("rds: Use atomic flag to track connections being destroyed")
introduced the RDS_DESTROY_PENDING cp_flags bit for exactly this, and
after commit ebeeb1ad9b8ad ("rds: tcp: use rds_destroy_pending() to
synchronize netns/module teardown and rds connection/workq management")
it was set right before that rds_conn_destroy() call and
tested via rds_ib_is_unloading(). Commit cdc306a5c9cd3 ("rds: make
v3.1 as compat version") then removed the last set_bit while leaving
the test behind, so the bit has been dead ever since and per-conn
destroy has run unguarded.
Bring the protection back at the connection level: set
conn->c_destroy_in_prog before the unhash + synchronize_rcu() sequence
in rds_conn_destroy() and test it in rds_destroy_pending(). The
existing rcu_read_lock() around every check-and-queue site pairs with
that synchronize_rcu(): once it returns, every new reader observes the
flag and refuses to queue, and anything queued before it is flushed or
cancelled by the existing teardown. Drop the now-unreferenced
RDS_DESTROY_PENDING bit and its dead test.
In the Oracle UEK kernel the equivalent conn->c_destroy_in_prog flag
is part of the larger connection refcounting rework ("net/rds: Add
krefs to struct rds_connection"), including ("net/rds: Merge uses of
conn->c_destroy_in_prog & RDS_DESTROY_PENDING"). This ports the
missing pieces of the requeue guard, which stand on their own.
Fixes: cdc306a5c9cd3 ("rds: make v3.1 as compat version")
Suggested-by: Sharath Srinivasan <sharath.srinivasan@oracle.com>
Assisted-by: Claude-Code:claude-fable-5
Signed-off-by: Allison Henderson <achender@kernel.org>
---
net/rds/connection.c | 8 ++++++++
net/rds/ib.c | 5 +----
net/rds/rds.h | 12 ++++++++++--
3 files changed, 19 insertions(+), 6 deletions(-)
diff --git a/net/rds/connection.c b/net/rds/connection.c
index a96569a3ee9a..242ca0570a47 100644
--- a/net/rds/connection.c
+++ b/net/rds/connection.c
@@ -579,6 +579,14 @@ void rds_conn_destroy(struct rds_connection *conn)
"%pI4\n", conn, &conn->c_laddr,
&conn->c_faddr);
+ /* Make rds_destroy_pending() true for this conn. Together with
+ * the synchronize_rcu() below this stops the work-requeueing
+ * sites (which all test rds_destroy_pending() under
+ * rcu_read_lock()) from queueing new work on the path
+ * workqueues once we start cancelling and destroying them.
+ */
+ WRITE_ONCE(conn->c_destroy_in_prog, true);
+
/* Ensure conn will not be scheduled for reconnect */
spin_lock_irq(&rds_conn_lock);
hlist_del_init_rcu(&conn->c_hash_node);
diff --git a/net/rds/ib.c b/net/rds/ib.c
index 786f39169bc1..9fe3b9951bd3 100644
--- a/net/rds/ib.c
+++ b/net/rds/ib.c
@@ -525,10 +525,7 @@ static void rds_ib_set_unloading(void)
static bool rds_ib_is_unloading(struct rds_connection *conn)
{
- struct rds_conn_path *cp = &conn->c_path[0];
-
- return (test_bit(RDS_DESTROY_PENDING, &cp->cp_flags) ||
- atomic_read(&rds_ib_unloading) != 0);
+ return atomic_read(&rds_ib_unloading) != 0;
}
void rds_ib_exit(void)
diff --git a/net/rds/rds.h b/net/rds/rds.h
index 2db49573dacd..50b08c28ab86 100644
--- a/net/rds/rds.h
+++ b/net/rds/rds.h
@@ -89,7 +89,6 @@ enum {
#define RDS_RECONNECT_PENDING 1
#define RDS_IN_XMIT 2
#define RDS_RECV_REFILL 3
-#define RDS_DESTROY_PENDING 4
/* Max number of multipaths per RDS connection. Must be a power of 2 */
#define RDS_MPATH_WORKERS 8
@@ -148,6 +147,14 @@ struct rds_connection {
c_pad_to_32:29;
int c_npaths;
bool c_with_sport_idx;
+ /* Set once, by rds_conn_destroy(), before it cancels the path
+ * works; read through rds_destroy_pending(). A site that arms
+ * a path work must test the predicate and queue the work inside
+ * one rcu_read_lock() section: the synchronize_rcu() that
+ * follows the store is what keeps a queue issued after the
+ * cancellation from landing on a destroyed workqueue.
+ */
+ bool c_destroy_in_prog;
struct rds_connection *c_passive;
struct rds_transport *c_trans;
@@ -994,7 +1001,8 @@ void __rds_put_mr_final(struct kref *kref);
static inline bool rds_destroy_pending(struct rds_connection *conn)
{
- return !check_net(rds_conn_net(conn)) ||
+ return READ_ONCE(conn->c_destroy_in_prog) ||
+ !check_net(rds_conn_net(conn)) ||
(conn->c_trans->t_unloading && conn->c_trans->t_unloading(conn));
}
--
2.25.1
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v5 05/12] net/rds: split connection destroy into quiesce and kref-governed free
2026-09-19 6:11 [PATCH net-next v5 00/12] net/rds: make connection lifetime reference-counted Allison Henderson
` (3 preceding siblings ...)
2026-09-19 6:11 ` [PATCH net-next v5 04/12] net/rds: make rds_destroy_pending() cover single-connection destroy Allison Henderson
@ 2026-09-19 6:11 ` Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
2026-09-19 6:11 ` [PATCH net-next v5 06/12] net/rds: wait for connections to be freed on transport unload Allison Henderson
` (6 subsequent siblings)
11 siblings, 2 replies; 35+ messages in thread
From: Allison Henderson @ 2026-09-19 6:11 UTC (permalink / raw)
To: netdev, linux-rdma, pabeni, edumazet, kuba, horms; +Cc: achender, nicoyip.dev
From: Sharath Srinivasan <sharath.srinivasan@oracle.com>
rds_conn_destroy() tears down the transport state and immediately
frees the connection, along with its paths and its workqueues. This
relies on the assumption (documented in the rds_conn_destroy()
comments) that "no one else is referencing the connection", which "we
can only ensure ... in the rmmod path". However, the callers stopped
honoring that long ago. Today, connections are also destroyed on
network namespace teardown (rds_tcp_kill_sock() and
rds_loop_kill_conns()) and, one at a time, when a peer negotiates an
unsupported protocol version (rds_ib_cm_connect_complete()).
This leaves loose ends since references to these destroyed
connections still exist. Sockets cache their connections in rs_conn,
and congestion updates will walk the maps' m_conn_list. The CM
callbacks and workers may also still hold the pointer.
Prepare to close those holes by making the connection refcounted:
- kref_init() the connection in __rds_conn_create(); the initial
reference belongs to whoever is responsible for destroying the
connection.
- rds_conn_destroy() still quiesces synchronously exactly as before
(workers cancelled, paths dropped and shut down, queued messages
purged, congestion list removal), but the frees - the transport's
conn_free, the path workqueues, the c_path array and the connection
slab object - move to rds_conn_destroy_fini(), which runs when the
last reference is dropped via rds_conn_put().
- Export rds_conn_get()/rds_conn_put(), plus an inline
rds_conn_get_unless_zero() for holders that may find a connection
already on its way out, for the reference holders introduced in the
following patches.
With no additional reference holders yet, this only sets up the
refcounting framework and is functionally equivalent to the current
code (the initial reference is the only one), so every free still
completes inside rds_conn_destroy(). The next two patches make a
deferred free safe - the transport unload wait and the unlinking of
the transport nodes ahead of the destroy - and only then do the
patches that follow take references at the places that today rely on
bare pointers.
Based on the Oracle UEK commit "net/rds: Add krefs to struct
rds_connection".
Signed-off-by: Sharath Srinivasan <sharath.srinivasan@oracle.com>
[achender: substantial reimplementation for net-next: UEK's
rds_conn_destroy_init()/_fini() split redone against upstream's
rds_conn_destroy()/rds_conn_path_destroy() (no heartbeat/reap/trace
infrastructure, no rds_net, single conn hash); destroy keeps its
one-call external interface; holder coverage split out into follow-up
patches; rewrite commit message]
Assisted-by: Claude-Code:claude-fable-5
Signed-off-by: Allison Henderson <achender@kernel.org>
---
net/rds/connection.c | 96 +++++++++++++++++++++++++++++++++++---------
net/rds/rds.h | 13 ++++++
2 files changed, 91 insertions(+), 18 deletions(-)
diff --git a/net/rds/connection.c b/net/rds/connection.c
index 242ca0570a47..a44aa4d2a5e8 100644
--- a/net/rds/connection.c
+++ b/net/rds/connection.c
@@ -231,6 +231,7 @@ static struct rds_connection *__rds_conn_create(struct net *net,
goto out;
}
+ kref_init(&conn->c_refcount);
INIT_HLIST_NODE(&conn->c_hash_node);
conn->c_laddr = *laddr;
conn->c_isv6 = !ipv6_addr_v4mapped(laddr);
@@ -471,9 +472,10 @@ void rds_conn_shutdown(struct rds_conn_path *cp)
* Quiesce the reconnect timer before bailing
* out, though. When a pending destroy did
* suppress the queue, no later pass runs, and
- * rds_conn_path_destroy() is about to flush
- * cp_down_w and free the path: it must not
- * find cp_conn_w still armed. A successor
+ * rds_conn_path_quiesce() is about to flush
+ * cp_down_w, ahead of the path's deferred
+ * free: it must not find cp_conn_w still
+ * armed. A successor
* pass, when there is one, re-arms the
* reconnect from its own tail.
*/
@@ -520,10 +522,12 @@ void rds_conn_shutdown(struct rds_conn_path *cp)
conn->c_trans->conn_slots_available(conn, false);
}
-/* destroy a single rds_conn_path. rds_conn_destroy() iterates over
- * all paths using rds_conn_path_destroy()
+/* quiesce a single rds_conn_path: shut it down and tear down any
+ * queued messages. rds_conn_destroy() iterates over all paths using
+ * rds_conn_path_quiesce(); the transport state and the workqueue are
+ * freed later, from rds_conn_path_free().
*/
-static void rds_conn_path_destroy(struct rds_conn_path *cp)
+static void rds_conn_path_quiesce(struct rds_conn_path *cp)
{
struct rds_message *rm, *rtmp;
@@ -552,6 +556,16 @@ static void rds_conn_path_destroy(struct rds_conn_path *cp)
WARN_ON(delayed_work_pending(&cp->cp_recv_w));
WARN_ON(delayed_work_pending(&cp->cp_conn_w));
WARN_ON(work_pending(&cp->cp_down_w));
+}
+
+/* free a quiesced rds_conn_path's transport state and workqueue; runs
+ * from rds_conn_destroy_fini() once the last connection reference is
+ * dropped.
+ */
+static void rds_conn_path_free(struct rds_conn_path *cp)
+{
+ if (!cp->cp_transport_data)
+ return;
if (cp->cp_wq != rds_wq) {
destroy_workqueue(cp->cp_wq);
@@ -561,16 +575,52 @@ static void rds_conn_path_destroy(struct rds_conn_path *cp)
cp->cp_conn->c_trans->conn_free(cp->cp_transport_data);
}
+/* Free a connection. This runs from rds_conn_put() when the last
+ * reference is dropped, after rds_conn_destroy() has quiesced the
+ * connection and dropped the initial reference.
+ */
+static void rds_conn_destroy_fini(struct kref *kref)
+{
+ struct rds_connection *conn = container_of(kref, struct rds_connection,
+ c_refcount);
+ int npaths = (conn->c_trans->t_mp_capable ? RDS_MPATH_WORKERS : 1);
+ unsigned long flags;
+ int i;
+
+ for (i = 0; i < npaths; i++)
+ rds_conn_path_free(&conn->c_path[i]);
+
+ kfree(conn->c_path);
+ kmem_cache_free(rds_conn_slab, conn);
+
+ spin_lock_irqsave(&rds_conn_lock, flags);
+ rds_conn_count--;
+ spin_unlock_irqrestore(&rds_conn_lock, flags);
+}
+
+void rds_conn_get(struct rds_connection *conn)
+{
+ kref_get(&conn->c_refcount);
+}
+EXPORT_SYMBOL_GPL(rds_conn_get);
+
+void rds_conn_put(struct rds_connection *conn)
+{
+ kref_put(&conn->c_refcount, rds_conn_destroy_fini);
+}
+EXPORT_SYMBOL_GPL(rds_conn_put);
+
/*
* Stop and free a connection.
*
- * This can only be used in very limited circumstances. It assumes that once
- * the conn has been shutdown that no one else is referencing the connection.
- * We can only ensure this in the rmmod path in the current code.
+ * Quiesces the connection synchronously (workers cancelled, transport
+ * connections shut down, queued messages dropped) and drops the
+ * initial reference. The memory - including the transport's
+ * per-connection state and the path workqueues - is freed once the
+ * last rds_conn_put() runs, which may be after this returns.
*/
void rds_conn_destroy(struct rds_connection *conn)
{
- unsigned long flags;
int i;
struct rds_conn_path *cp;
int npaths = (conn->c_trans->t_mp_capable ? RDS_MPATH_WORKERS : 1);
@@ -584,11 +634,23 @@ void rds_conn_destroy(struct rds_connection *conn)
* sites (which all test rds_destroy_pending() under
* rcu_read_lock()) from queueing new work on the path
* workqueues once we start cancelling and destroying them.
+ *
+ * Now that the transport state stays discoverable (e.g. on the
+ * transports' connection lists) until the final rds_conn_put(),
+ * a conn can be handed to rds_conn_destroy() more than once -
+ * e.g. dropped for a protocol version mismatch and then found
+ * again at module unload. Only the first caller proceeds; the
+ * unhash also happens under rds_conn_lock, so a looked-up conn
+ * can never be quiesced twice.
*/
+ spin_lock_irq(&rds_conn_lock);
+ if (conn->c_destroy_in_prog) {
+ spin_unlock_irq(&rds_conn_lock);
+ return;
+ }
WRITE_ONCE(conn->c_destroy_in_prog, true);
/* Ensure conn will not be scheduled for reconnect */
- spin_lock_irq(&rds_conn_lock);
hlist_del_init_rcu(&conn->c_hash_node);
spin_unlock_irq(&rds_conn_lock);
synchronize_rcu();
@@ -596,7 +658,7 @@ void rds_conn_destroy(struct rds_connection *conn)
/* shut the connection down */
for (i = 0; i < npaths; i++) {
cp = &conn->c_path[i];
- rds_conn_path_destroy(cp);
+ rds_conn_path_quiesce(cp);
BUG_ON(!list_empty(&cp->cp_retrans));
}
@@ -607,12 +669,10 @@ void rds_conn_destroy(struct rds_connection *conn)
*/
rds_cong_remove_conn(conn);
- kfree(conn->c_path);
- kmem_cache_free(rds_conn_slab, conn);
-
- spin_lock_irqsave(&rds_conn_lock, flags);
- rds_conn_count--;
- spin_unlock_irqrestore(&rds_conn_lock, flags);
+ /* drop the initial reference; the connection is freed from
+ * rds_conn_destroy_fini() once every holder has dropped theirs
+ */
+ rds_conn_put(conn);
}
EXPORT_SYMBOL_GPL(rds_conn_destroy);
diff --git a/net/rds/rds.h b/net/rds/rds.h
index 50b08c28ab86..defda3ddefa3 100644
--- a/net/rds/rds.h
+++ b/net/rds/rds.h
@@ -137,6 +137,12 @@ struct rds_conn_path {
/* One rds_connection per RDS address pair */
struct rds_connection {
struct hlist_node c_hash_node;
+ /* rds_conn_destroy() quiesces the connection synchronously;
+ * freeing it - the connection memory, the path workqueues and
+ * the transport's per-connection state - is deferred until the
+ * last reference is dropped via rds_conn_put().
+ */
+ struct kref c_refcount;
struct in6_addr c_laddr;
struct in6_addr c_faddr;
int c_dev_if; /* ifindex used for this conn */
@@ -826,6 +832,13 @@ struct rds_connection *rds_conn_create_outgoing(struct net *net,
u8 tos, gfp_t gfp, int dev_if);
void rds_conn_shutdown(struct rds_conn_path *cpath);
void rds_conn_destroy(struct rds_connection *conn);
+void rds_conn_get(struct rds_connection *conn);
+void rds_conn_put(struct rds_connection *conn);
+/* take a reference unless the connection is already being freed */
+static inline bool rds_conn_get_unless_zero(struct rds_connection *conn)
+{
+ return kref_get_unless_zero(&conn->c_refcount);
+}
void rds_conn_drop(struct rds_connection *conn);
void rds_conn_path_drop(struct rds_conn_path *cpath, bool destroy);
void rds_conn_connect_if_down(struct rds_connection *conn);
--
2.25.1
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v5 06/12] net/rds: wait for connections to be freed on transport unload
2026-09-19 6:11 [PATCH net-next v5 00/12] net/rds: make connection lifetime reference-counted Allison Henderson
` (4 preceding siblings ...)
2026-09-19 6:11 ` [PATCH net-next v5 05/12] net/rds: split connection destroy into quiesce and kref-governed free Allison Henderson
@ 2026-09-19 6:11 ` Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
2026-09-19 6:11 ` [PATCH net-next v5 07/12] net/rds: unlink transport nodes before a possibly deferred connection free Allison Henderson
` (5 subsequent siblings)
11 siblings, 2 replies; 35+ messages in thread
From: Allison Henderson @ 2026-09-19 6:11 UTC (permalink / raw)
To: netdev, linux-rdma, pabeni, edumazet, kuba, horms; +Cc: achender, nicoyip.dev
From: Sharath Srinivasan <sharath.srinivasan@oracle.com>
Since connection free became asynchronous, rds_conn_destroy() only
quiesces the connection; the actual free - including the transport's
conn_free, which lives in the transport module - runs when the last
reference is dropped. The transports' exit paths destroy all of
their connections and then proceed to unload, so a free that is still
pending (a racing lookup-style holder, or simply the destroyer's own
put not yet run when destroy was invoked from another context earlier)
would execute transport module code after that module's text is gone.
Count each transport's live connections in t_conn_count (incremented
when a connection is published in __rds_conn_create(), decremented as
the last step of rds_conn_destroy_fini()) and make the transport exit
paths - rds_ib_exit(), rds_tcp_exit() and rds_loop_exit() - wait for
the count to drop to zero after destroying their connections.
A bound socket holds a module reference on its own transport
(rds_trans_get_preferred()), but that does not bound the wait: once
the following patches make incs hold a connection reference, an
unread datagram pins its connection for as long as the application
leaves it on the receive queue, and rds_find_bound() does not filter
on transport, so that socket may well belong to a different transport
than the connection and pin nothing that stops this unload.
Proceeding after a timeout would turn a leak into a use-after-free in
this module's text, so the wait is unbounded: it polls the count,
warns every ten seconds naming the transport and the number of
connections outstanding, and returns only when the count reaches
zero. rmmod therefore blocks while data is queued and unread, which
is the historical RDS contract - teardown does not discard queued
data.
The wait is deliberately not interruptible: the exit function has
already passed the point of no return, and a killed rmmod would leave
the module half torn down and unloadable, which is worse than a
blocked one. The polling wakes every 100 ms, so the hung task detector
does not fire.
The wake at the end of rds_conn_destroy_fini() can run from a thread
executing transport module text, but never as that thread's last use
of it: a CM event handler's reference is always dropped before the
rdma_destroy_id() in the connection's own shutdown returns, and
rds_rdma_exit() stops the listener - waiting for any handler running
on it - before rds_ib_exit() starts to wait.
rds_ib_exit() has one more wrinkle. rds_ib_dev_shutdown() only drops
the connections still attached to a device, and each of them moves
itself to ib_nodev_conns from its own shutdown work, asynchronously.
A connection that has not migrated by the time
rds_ib_destroy_nodev_conns() sweeps the list would never be
destroyed, and would hold the count up for good. So the wait takes a
resweep callback, which rds_ib_exit() points at
rds_ib_destroy_nodev_conns(): late arrivals get destroyed on the next
poll instead of being waited on.
In rds_ib_exit(), tearing down the last connection can also drop the
final reference on a device, which defers rds_ib_dev_free() - again
this module's text - to rds_wq. Flush the workqueue once after the
connections are gone; rds_ib_dev_free() queues nothing further on
rds_wq, so a single pass drains it.
Based on the Oracle UEK commits "net/rds: wait_event_timeout
until zero connections during rmmod" and "net/rds:
Each RDS transport should keep its own connection count".
Signed-off-by: Sharath Srinivasan <sharath.srinivasan@oracle.com>
[achender: reimplementation for net-next: t_conn_count did not exist
upstream and is introduced here; single global waitqueue instead of
per-transport (the loop transport never goes through
rds_trans_register()); unbounded wait with a periodic warning in
place of UEK's wait_event_timeout() + WARN_ON(), plus the resweep for
IB's asynchronous device detach; also cover rds_loop_exit(); rewrite
commit message]
Assisted-by: Claude-Code:claude-fable-5
Signed-off-by: Allison Henderson <achender@kernel.org>
---
net/rds/connection.c | 50 ++++++++++++++++++++++++++++++++++++++++++++
net/rds/ib.c | 17 +++++++++++++++
net/rds/ib_rdma.c | 2 +-
net/rds/loop.c | 2 ++
net/rds/rds.h | 14 +++++++++++++
net/rds/tcp.c | 1 +
6 files changed, 85 insertions(+), 1 deletion(-)
diff --git a/net/rds/connection.c b/net/rds/connection.c
index a44aa4d2a5e8..1d48da1a794f 100644
--- a/net/rds/connection.c
+++ b/net/rds/connection.c
@@ -48,6 +48,8 @@
/* converting this to RCU is a chore for another day.. */
static DEFINE_SPINLOCK(rds_conn_lock);
static unsigned long rds_conn_count;
+/* woken whenever a transport's t_conn_count drops to zero */
+static DECLARE_WAIT_QUEUE_HEAD(rds_conn_freed_waitq);
static struct hlist_head rds_conn_hash[RDS_CONNECTION_HASH_ENTRIES];
static struct kmem_cache *rds_conn_slab;
@@ -341,6 +343,7 @@ static struct rds_connection *__rds_conn_create(struct net *net,
parent->c_passive = conn;
rds_cong_add_conn(conn);
rds_conn_count++;
+ atomic_inc(&conn->c_trans->t_conn_count);
}
} else {
/* Creating normal conn */
@@ -359,6 +362,7 @@ static struct rds_connection *__rds_conn_create(struct net *net,
hlist_add_head_rcu(&conn->c_hash_node, head);
rds_cong_add_conn(conn);
rds_conn_count++;
+ atomic_inc(&conn->c_trans->t_conn_count);
}
}
spin_unlock_irqrestore(&rds_conn_lock, flags);
@@ -584,6 +588,7 @@ static void rds_conn_destroy_fini(struct kref *kref)
struct rds_connection *conn = container_of(kref, struct rds_connection,
c_refcount);
int npaths = (conn->c_trans->t_mp_capable ? RDS_MPATH_WORKERS : 1);
+ struct rds_transport *trans = conn->c_trans;
unsigned long flags;
int i;
@@ -596,7 +601,52 @@ static void rds_conn_destroy_fini(struct kref *kref)
spin_lock_irqsave(&rds_conn_lock, flags);
rds_conn_count--;
spin_unlock_irqrestore(&rds_conn_lock, flags);
+
+ /* only after everything the transport module owns has been
+ * freed above may its unload proceed
+ */
+ if (!atomic_dec_return(&trans->t_conn_count))
+ wake_up_all(&rds_conn_freed_waitq);
+}
+
+/* Wait for all of @trans's connections to be freed; the free runs
+ * asynchronously once rds_conn_destroy() has quiesced a connection.
+ * Called on transport module unload, after the transport has destroyed
+ * all of its connections. A connection reference can be held for an
+ * application-controlled time - once incs hold one, an unread datagram
+ * pins the inc that carries it, and thus the connection - so the wait
+ * is unbounded: the
+ * frees that run after unload call into this module's text (conn_free,
+ * inc_free) and free into its slabs, so proceeding while any remain
+ * would be a use-after-free, not a leak. Warn periodically so a stuck
+ * count is diagnosable, but never stop waiting. This matches the
+ * historical RDS contract that teardown does not discard queued data.
+ */
+void rds_conn_wait_conns_freed(struct rds_transport *trans,
+ void (*resweep)(void))
+{
+ unsigned long warn_interval =
+ msecs_to_jiffies(RDS_CONN_FREE_WARN_INTERVAL_MS);
+ unsigned long warn_at = jiffies + warn_interval;
+
+ while (!wait_event_timeout(rds_conn_freed_waitq,
+ !atomic_read(&trans->t_conn_count),
+ msecs_to_jiffies(RDS_CONN_FREE_POLL_MS))) {
+ /* A transport whose teardown is asynchronous (IB moves a
+ * connection off its device from the shutdown work) gives
+ * us a resweep to destroy what has arrived since.
+ */
+ if (resweep)
+ resweep();
+ if (time_after_eq(jiffies, warn_at)) {
+ pr_warn("RDS/%s: still waiting for %d connection(s) to be freed before unload\n",
+ trans->t_name,
+ atomic_read(&trans->t_conn_count));
+ warn_at = jiffies + warn_interval;
+ }
+ }
}
+EXPORT_SYMBOL_GPL(rds_conn_wait_conns_freed);
void rds_conn_get(struct rds_connection *conn)
{
diff --git a/net/rds/ib.c b/net/rds/ib.c
index 9fe3b9951bd3..3fc2de9d19d5 100644
--- a/net/rds/ib.c
+++ b/net/rds/ib.c
@@ -537,7 +537,24 @@ void rds_ib_exit(void)
rds_info_deregister_func(RDS6_INFO_IB_CONNECTIONS, rds6_ib_ic_info);
#endif
rds_ib_unregister_client();
+
+ /* rds_ib_dev_shutdown() only dropped the connections still
+ * attached to a device; each moves itself to ib_nodev_conns
+ * from its shutdown work. Destroy what is there now and keep
+ * sweeping the list while the wait sees connections outstanding,
+ * so a late arrival is destroyed rather than waited on forever.
+ */
rds_ib_destroy_nodev_conns();
+ rds_conn_wait_conns_freed(&rds_ib_transport,
+ rds_ib_destroy_nodev_conns);
+
+ /* Tearing down the last connection may have dropped the final
+ * reference on a device, deferring rds_ib_dev_free() to rds_wq.
+ * Drain it before the module goes away; it queues nothing
+ * further on rds_wq.
+ */
+ flush_workqueue(rds_wq);
+
rds_ib_sysctl_exit();
rds_ib_recv_exit();
rds_trans_unregister(&rds_ib_transport);
diff --git a/net/rds/ib_rdma.c b/net/rds/ib_rdma.c
index db7e92e7bd29..a9b27f06cbfc 100644
--- a/net/rds/ib_rdma.c
+++ b/net/rds/ib_rdma.c
@@ -165,7 +165,7 @@ void rds_ib_destroy_nodev_conns(void)
/* avoid calling conn_destroy with irqs off */
spin_lock_irq(&ib_nodev_conns_lock);
- list_splice(&ib_nodev_conns, &tmp_list);
+ list_splice_init(&ib_nodev_conns, &tmp_list);
spin_unlock_irq(&ib_nodev_conns_lock);
list_for_each_entry_safe(ic, _ic, &tmp_list, ib_node)
diff --git a/net/rds/loop.c b/net/rds/loop.c
index e6b0750bbeda..fd774f8080d0 100644
--- a/net/rds/loop.c
+++ b/net/rds/loop.c
@@ -195,6 +195,8 @@ void rds_loop_exit(void)
WARN_ON(lc->conn->c_passive);
rds_conn_destroy(lc->conn);
}
+
+ rds_conn_wait_conns_freed(&rds_loop_transport, NULL);
}
static void rds_loop_kill_conns(struct net *net)
diff --git a/net/rds/rds.h b/net/rds/rds.h
index defda3ddefa3..b3cc0804156e 100644
--- a/net/rds/rds.h
+++ b/net/rds/rds.h
@@ -557,6 +557,12 @@ struct rds_transport {
unsigned int t_prefer_loopback:1,
t_mp_capable:1;
unsigned int t_type;
+ /* Connections of this transport not yet freed; freeing runs
+ * asynchronously once rds_conn_destroy() has quiesced a
+ * connection, so transport module unload has to wait for this
+ * to reach zero (rds_conn_wait_conns_freed()).
+ */
+ atomic_t t_conn_count;
int (*laddr_check)(struct net *net, const struct in6_addr *addr,
__u32 scope_id);
@@ -839,6 +845,14 @@ static inline bool rds_conn_get_unless_zero(struct rds_connection *conn)
{
return kref_get_unless_zero(&conn->c_refcount);
}
+
+/* transport unload waits for its connections to be freed, polling at
+ * the first interval and warning at the second
+ */
+#define RDS_CONN_FREE_POLL_MS 100
+#define RDS_CONN_FREE_WARN_INTERVAL_MS 10000
+void rds_conn_wait_conns_freed(struct rds_transport *trans,
+ void (*resweep)(void));
void rds_conn_drop(struct rds_connection *conn);
void rds_conn_path_drop(struct rds_conn_path *cpath, bool destroy);
void rds_conn_connect_if_down(struct rds_connection *conn);
diff --git a/net/rds/tcp.c b/net/rds/tcp.c
index 774a71f88d37..826e4629e4ee 100644
--- a/net/rds/tcp.c
+++ b/net/rds/tcp.c
@@ -805,6 +805,7 @@ static void rds_tcp_exit(void)
#endif
unregister_pernet_device(&rds_tcp_net_ops);
rds_tcp_destroy_conns();
+ rds_conn_wait_conns_freed(&rds_tcp_transport, NULL);
rds_trans_unregister(&rds_tcp_transport);
rds_tcp_recv_exit();
kmem_cache_destroy(rds_tcp_conn_slab);
--
2.25.1
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v5 07/12] net/rds: unlink transport nodes before a possibly deferred connection free
2026-09-19 6:11 [PATCH net-next v5 00/12] net/rds: make connection lifetime reference-counted Allison Henderson
` (5 preceding siblings ...)
2026-09-19 6:11 ` [PATCH net-next v5 06/12] net/rds: wait for connections to be freed on transport unload Allison Henderson
@ 2026-09-19 6:11 ` Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
2026-09-19 6:11 ` [PATCH net-next v5 08/12] net/rds: hold connection references in lookup, sockets and c_passive Allison Henderson
` (4 subsequent siblings)
11 siblings, 2 replies; 35+ messages in thread
From: Allison Henderson @ 2026-09-19 6:11 UTC (permalink / raw)
To: netdev, linux-rdma, pabeni, edumazet, kuba, horms; +Cc: achender, nicoyip.dev
The transport teardown helpers - rds_tcp_destroy_conns(),
rds_tcp_kill_sock(), rds_ib_destroy_nodev_conns(), rds_loop_exit() and
rds_loop_kill_conns() - gather the per-connection transport nodes onto
a list head on their own stack and call rds_conn_destroy() for each.
The node is unlinked much later, by the transport's conn_free():
rds_tcp_conn_free() and rds_loop_conn_free() list_del() it, and
rds_ib_conn_free() does so unconditionally.
That is fine for as long as rds_conn_destroy() frees the connection
before it returns, which is still the case at this point in the
series: the initial reference is the only one. The following patches
hand out references that outlive the teardown loop - a socket's
cached rs_conn, an inc parked on a receive queue - and with those, a
conn_free() deferred until after the helper has returned would
list_del() the node from a stack frame that no longer exists. Make
the helpers ready for that first.
Unlink each node under the transport lock right before its
rds_conn_destroy(), so that nothing is left on the stack list for a
later free to touch. TCP marks the node detached, as
rds_tcp_kill_sock() already does for the secondary paths of a
multipath connection; loopback uses list_del_init() and has its
conn_free() skip a node that is already empty.
IB needs an explicit flag, i_ib_node_detached, because its node has
other movers: a connect worker moves it from the nodev list to a
device's list in rds_ib_add_conn(), and a shutdown moves it back in
rds_ib_remove_conn(). Either can run while the sweep holds the node
on its stack list, and "the node is linked" cannot tell that list
from the nodev list - an add_conn() that went by list emptiness would
unlink the node from under the sweep's lockless walk. So the sweep
sets the flag when it gathers the node, under ib_nodev_conns_lock,
and from then on add_conn(), remove_conn() and conn_free() leave the
node alone; the node belongs to the sweep, and its walk needs no
lock. Those movers used to assert that the node is linked (and
rds_ib_add_conn() that the nodev list is non-empty); a connect or
shutdown worker can still be running for a connection the sweep has
claimed, and such a connection is about to be destroyed anyway, so
the assertions go.
The walk itself must not lose the entries either. A connection that
was destroyed earlier - dropped for a protocol version mismatch, then
found again at module unload - is kept alive only by whatever
reference is still pending, and that can be dropped at any point
during the walk, freeing the transport node the iterator is about to
read. So the gather takes a reference on each connection it moves
onto the stack list, under the transport lock, and drops it after the
destroy; a connection whose free is already running gets no reference
and is left where it is, since that free unlinks the node itself once
the lock is released. The tmp_list gathering itself remains: it is
what keeps rds_conn_destroy() from being called with the transport
lock held.
Assisted-by: Claude-Code:claude-fable-5
Signed-off-by: Allison Henderson <achender@kernel.org>
---
net/rds/ib.h | 4 +++
net/rds/ib_cm.c | 4 ++-
net/rds/ib_rdma.c | 67 ++++++++++++++++++++++++++++++++++++-----------
net/rds/loop.c | 59 ++++++++++++++++++++++++++++++++---------
net/rds/tcp.c | 52 +++++++++++++++++++++++++++++++-----
5 files changed, 152 insertions(+), 34 deletions(-)
diff --git a/net/rds/ib.h b/net/rds/ib.h
index 5ff346a1e8ba..cb410c3ae8d8 100644
--- a/net/rds/ib.h
+++ b/net/rds/ib.h
@@ -143,6 +143,10 @@ struct rds_ib_device;
struct rds_ib_connection {
struct list_head ib_node;
+ /* set under ib_nodev_conns_lock once a transport teardown has
+ * claimed ib_node; from then on only the teardown touches it
+ */
+ bool i_ib_node_detached;
struct rds_ib_device *rds_ibdev;
struct rds_connection *conn;
diff --git a/net/rds/ib_cm.c b/net/rds/ib_cm.c
index 118e033229aa..ccb7e2b93ff5 100644
--- a/net/rds/ib_cm.c
+++ b/net/rds/ib_cm.c
@@ -1287,7 +1287,9 @@ void rds_ib_conn_free(void *arg)
lock_ptr = ic->rds_ibdev ? &ic->rds_ibdev->spinlock : &ib_nodev_conns_lock;
spin_lock_irqsave(lock_ptr, flags);
- list_del(&ic->ib_node);
+ /* a transport teardown that gathered us first owns the node */
+ if (!ic->i_ib_node_detached)
+ list_del(&ic->ib_node);
spin_unlock_irqrestore(lock_ptr, flags);
rds_ib_recv_free_caches(ic);
diff --git a/net/rds/ib_rdma.c b/net/rds/ib_rdma.c
index a9b27f06cbfc..1548e5be0e55 100644
--- a/net/rds/ib_rdma.c
+++ b/net/rds/ib_rdma.c
@@ -123,15 +123,18 @@ void rds_ib_add_conn(struct rds_ib_device *rds_ibdev, struct rds_connection *con
{
struct rds_ib_connection *ic = conn->c_transport_data;
- /* conn was previously on the nodev_conns_list */
+ /* conn was previously on the nodev_conns_list, unless a teardown
+ * sweep has claimed it ahead of destroying it: then it is on its
+ * way out, and its node belongs to the sweep.
+ */
spin_lock_irq(&ib_nodev_conns_lock);
- BUG_ON(list_empty(&ib_nodev_conns));
- BUG_ON(list_empty(&ic->ib_node));
- list_del(&ic->ib_node);
+ if (!ic->i_ib_node_detached) {
+ list_del(&ic->ib_node);
- spin_lock(&rds_ibdev->spinlock);
- list_add_tail(&ic->ib_node, &rds_ibdev->conn_list);
- spin_unlock(&rds_ibdev->spinlock);
+ spin_lock(&rds_ibdev->spinlock);
+ list_add_tail(&ic->ib_node, &rds_ibdev->conn_list);
+ spin_unlock(&rds_ibdev->spinlock);
+ }
spin_unlock_irq(&ib_nodev_conns_lock);
ic->rds_ibdev = rds_ibdev;
@@ -142,15 +145,22 @@ void rds_ib_remove_conn(struct rds_ib_device *rds_ibdev, struct rds_connection *
{
struct rds_ib_connection *ic = conn->c_transport_data;
- /* place conn on nodev_conns_list */
+ bool detached;
+
+ /* place conn on nodev_conns_list - unless a teardown sweep has
+ * claimed it ahead of destroying it, in which case its node
+ * belongs to the sweep
+ */
spin_lock(&ib_nodev_conns_lock);
spin_lock_irq(&rds_ibdev->spinlock);
- BUG_ON(list_empty(&ic->ib_node));
- list_del(&ic->ib_node);
+ detached = ic->i_ib_node_detached;
+ if (!detached)
+ list_del(&ic->ib_node);
spin_unlock_irq(&rds_ibdev->spinlock);
- list_add_tail(&ic->ib_node, &ib_nodev_conns);
+ if (!detached)
+ list_add_tail(&ic->ib_node, &ib_nodev_conns);
spin_unlock(&ib_nodev_conns_lock);
@@ -163,13 +173,40 @@ void rds_ib_destroy_nodev_conns(void)
struct rds_ib_connection *ic, *_ic;
LIST_HEAD(tmp_list);
- /* avoid calling conn_destroy with irqs off */
+ struct rds_connection *conn;
+
+ /* Gather the connections and take a reference on each, so that
+ * none is freed under the walk below (a connection destroyed
+ * earlier, for a protocol version mismatch, can be on this list
+ * with only a socket's reference still pending). One whose free
+ * is already running gets no reference: its free unlinks the
+ * node itself, under this lock, once we drop it. Marking the
+ * node detached claims it for this sweep: rds_ib_add_conn(),
+ * rds_ib_remove_conn() and rds_ib_conn_free() leave a claimed
+ * node alone, so the walk over tmp_list below needs no lock.
+ * Avoid calling conn_destroy with irqs off.
+ */
spin_lock_irq(&ib_nodev_conns_lock);
- list_splice_init(&ib_nodev_conns, &tmp_list);
+ list_for_each_entry_safe(ic, _ic, &ib_nodev_conns, ib_node) {
+ if (rds_conn_get_unless_zero(ic->conn)) {
+ ic->i_ib_node_detached = true;
+ list_move_tail(&ic->ib_node, &tmp_list);
+ }
+ }
spin_unlock_irq(&ib_nodev_conns_lock);
- list_for_each_entry_safe(ic, _ic, &tmp_list, ib_node)
- rds_conn_destroy(ic->conn);
+ /* rds_conn_destroy() can return before the connection is freed,
+ * and it is the free - rds_ib_conn_free() - that would unlink
+ * ib_node. tmp_list lives on this stack frame, so take each node
+ * off it before its destroy; the free then leaves it alone.
+ */
+ list_for_each_entry_safe(ic, _ic, &tmp_list, ib_node) {
+ conn = ic->conn;
+ list_del_init(&ic->ib_node);
+
+ rds_conn_destroy(conn);
+ rds_conn_put(conn);
+ }
}
void rds_ib_get_mr_info(struct rds_ib_device *rds_ibdev, struct rds_info_rdma_connection *iinfo)
diff --git a/net/rds/loop.c b/net/rds/loop.c
index fd774f8080d0..71f760ccd458 100644
--- a/net/rds/loop.c
+++ b/net/rds/loop.c
@@ -156,6 +156,45 @@ static int rds_loop_conn_alloc(struct rds_connection *conn, gfp_t gfp)
return 0;
}
+/* Destroy the connections whose nodes were gathered on @tmp_list.
+ *
+ * rds_conn_destroy() can return before the connection is freed, and
+ * it is the free - rds_loop_conn_free() - that unlinks loop_node.
+ * @tmp_list lives on the caller's stack, so unlink each node before
+ * its destroy; the free then finds it empty and leaves it alone.
+ */
+static void rds_loop_destroy_gathered_conns(struct list_head *tmp_list)
+{
+ struct rds_loop_connection *lc, *_lc;
+ struct rds_connection *conn;
+
+ list_for_each_entry_safe(lc, _lc, tmp_list, loop_node) {
+ conn = lc->conn;
+ WARN_ON(conn->c_passive);
+
+ spin_lock_irq(&loop_conns_lock);
+ list_del_init(&lc->loop_node);
+ spin_unlock_irq(&loop_conns_lock);
+
+ rds_conn_destroy(conn);
+ rds_conn_put(conn);
+ }
+}
+
+/* Gather @lc's connection for destruction: move the node to the
+ * caller's @tmp_list and take a reference that keeps the connection,
+ * and so the node, alive until rds_loop_destroy_gathered_conns() has
+ * dealt with it. Called with loop_conns_lock held. A connection
+ * whose free is already running gets no reference; its free unlinks
+ * the node itself, under the same lock, once we drop it.
+ */
+static void rds_loop_gather_conn(struct rds_loop_connection *lc,
+ struct list_head *tmp_list)
+{
+ if (rds_conn_get_unless_zero(lc->conn))
+ list_move_tail(&lc->loop_node, tmp_list);
+}
+
static void rds_loop_conn_free(void *arg)
{
struct rds_loop_connection *lc = arg;
@@ -163,7 +202,9 @@ static void rds_loop_conn_free(void *arg)
rdsdebug("lc %p\n", lc);
spin_lock_irqsave(&loop_conns_lock, flags);
- list_del(&lc->loop_node);
+ /* already unlinked if a transport teardown gathered us first */
+ if (!list_empty(&lc->loop_node))
+ list_del(&lc->loop_node);
spin_unlock_irqrestore(&loop_conns_lock, flags);
kfree(lc);
}
@@ -187,14 +228,11 @@ void rds_loop_exit(void)
synchronize_rcu();
/* avoid calling conn_destroy with irqs off */
spin_lock_irq(&loop_conns_lock);
- list_splice(&loop_conns, &tmp_list);
- INIT_LIST_HEAD(&loop_conns);
+ list_for_each_entry_safe(lc, _lc, &loop_conns, loop_node)
+ rds_loop_gather_conn(lc, &tmp_list);
spin_unlock_irq(&loop_conns_lock);
- list_for_each_entry_safe(lc, _lc, &tmp_list, loop_node) {
- WARN_ON(lc->conn->c_passive);
- rds_conn_destroy(lc->conn);
- }
+ rds_loop_destroy_gathered_conns(&tmp_list);
rds_conn_wait_conns_freed(&rds_loop_transport, NULL);
}
@@ -210,14 +248,11 @@ static void rds_loop_kill_conns(struct net *net)
if (net != c_net)
continue;
- list_move_tail(&lc->loop_node, &tmp_list);
+ rds_loop_gather_conn(lc, &tmp_list);
}
spin_unlock_irq(&loop_conns_lock);
- list_for_each_entry_safe(lc, _lc, &tmp_list, loop_node) {
- WARN_ON(lc->conn->c_passive);
- rds_conn_destroy(lc->conn);
- }
+ rds_loop_destroy_gathered_conns(&tmp_list);
}
static void __net_exit rds_loop_exit_net(struct net *net)
diff --git a/net/rds/tcp.c b/net/rds/tcp.c
index 826e4629e4ee..552b32278e30 100644
--- a/net/rds/tcp.c
+++ b/net/rds/tcp.c
@@ -502,6 +502,48 @@ static bool rds_tcp_is_unloading(struct rds_connection *conn)
return atomic_read(&rds_tcp_unloading) != 0;
}
+/* Gather @tc's connection for destruction: move the node to the
+ * caller's @tmp_list and take a reference that keeps the connection,
+ * and so the node, alive until rds_tcp_destroy_gathered_conns() has
+ * dealt with it. Called with rds_tcp_conn_lock held. A connection
+ * whose free is already running gets no reference; its free unlinks
+ * the node itself, under the same lock, once we drop it.
+ */
+static void rds_tcp_gather_conn(struct rds_tcp_connection *tc,
+ struct list_head *tmp_list)
+{
+ if (rds_conn_get_unless_zero(tc->t_cpath->cp_conn))
+ list_move_tail(&tc->t_tcp_node, tmp_list);
+}
+
+/* Destroy the connections whose nodes were gathered on @tmp_list.
+ *
+ * rds_conn_destroy() can return before the connection is freed, and
+ * it is the free - rds_tcp_conn_free() - that unlinks t_tcp_node.
+ * Since @tmp_list lives on the caller's stack, unlink each node here
+ * and mark it detached before its destroy, so that a free that runs
+ * after the caller has returned does not write into a dead frame.
+ * Every entry holds a reference taken by rds_tcp_gather_conn(), so
+ * none can be freed under the walk; each is dropped after its destroy.
+ */
+static void rds_tcp_destroy_gathered_conns(struct list_head *tmp_list)
+{
+ struct rds_tcp_connection *tc, *_tc;
+ struct rds_connection *conn;
+
+ list_for_each_entry_safe(tc, _tc, tmp_list, t_tcp_node) {
+ conn = tc->t_cpath->cp_conn;
+
+ spin_lock_irq(&rds_tcp_conn_lock);
+ list_del_init(&tc->t_tcp_node);
+ tc->t_tcp_node_detached = true;
+ spin_unlock_irq(&rds_tcp_conn_lock);
+
+ rds_conn_destroy(conn);
+ rds_conn_put(conn);
+ }
+}
+
static void rds_tcp_destroy_conns(void)
{
struct rds_tcp_connection *tc, *_tc;
@@ -511,12 +553,11 @@ static void rds_tcp_destroy_conns(void)
spin_lock_irq(&rds_tcp_conn_lock);
list_for_each_entry_safe(tc, _tc, &rds_tcp_conn_list, t_tcp_node) {
if (!list_has_conn(&tmp_list, tc->t_cpath->cp_conn))
- list_move_tail(&tc->t_tcp_node, &tmp_list);
+ rds_tcp_gather_conn(tc, &tmp_list);
}
spin_unlock_irq(&rds_tcp_conn_lock);
- list_for_each_entry_safe(tc, _tc, &tmp_list, t_tcp_node)
- rds_conn_destroy(tc->t_cpath->cp_conn);
+ rds_tcp_destroy_gathered_conns(&tmp_list);
}
static void rds_tcp_exit(void);
@@ -691,15 +732,14 @@ static void rds_tcp_kill_sock(struct net *net)
if (net != c_net)
continue;
if (!list_has_conn(&tmp_list, tc->t_cpath->cp_conn)) {
- list_move_tail(&tc->t_tcp_node, &tmp_list);
+ rds_tcp_gather_conn(tc, &tmp_list);
} else {
list_del(&tc->t_tcp_node);
tc->t_tcp_node_detached = true;
}
}
spin_unlock_irq(&rds_tcp_conn_lock);
- list_for_each_entry_safe(tc, _tc, &tmp_list, t_tcp_node)
- rds_conn_destroy(tc->t_cpath->cp_conn);
+ rds_tcp_destroy_gathered_conns(&tmp_list);
}
static void __net_exit rds_tcp_exit_net(struct net *net)
--
2.25.1
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v5 08/12] net/rds: hold connection references in lookup, sockets and c_passive
2026-09-19 6:11 [PATCH net-next v5 00/12] net/rds: make connection lifetime reference-counted Allison Henderson
` (6 preceding siblings ...)
2026-09-19 6:11 ` [PATCH net-next v5 07/12] net/rds: unlink transport nodes before a possibly deferred connection free Allison Henderson
@ 2026-09-19 6:11 ` Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
2026-09-19 6:11 ` [PATCH net-next v5 09/12] net/rds: refuse to queue on a connection being destroyed Allison Henderson
` (3 subsequent siblings)
11 siblings, 2 replies; 35+ messages in thread
From: Allison Henderson @ 2026-09-19 6:11 UTC (permalink / raw)
To: netdev, linux-rdma, pabeni, edumazet, kuba, horms; +Cc: achender, nicoyip.dev
From: Sharath Srinivasan <sharath.srinivasan@oracle.com>
Hand out real references everywhere a struct rds_connection pointer
previously escaped bare:
- rds_conn_lookup() takes a reference on the connection it returns
(kref_get_unless_zero(), so that it only ever hands out a live
reference), and
__rds_conn_create() returns the connection with a reference held
for the caller on every path: lookup hit, fresh creation, lost
creation race, and the passive-loopback lookup, which now also
holds the parent while it dereferences parent->c_passive.
- The rs->rs_conn sendmsg cache owns a reference, which is dropped
when the cache is replaced or the socket is released.
rds_sendmsg() itself holds a reference for the duration of the
call, during which reads and updates of rs_conn are serialized by
rs_lock. So neither a concurrent rds_conn_destroy() nor another
sender replacing the cache can free the connection under a sender.
The connection may still be destroyed while a send is in flight -
when its device is removed or its netns is torn down - but it is
only quiesced; the free is held off by the sender's reference. A
cached connection whose destruction has begun is no longer reused.
Instead, sendmsg drops it and looks up or creates a live one, so a
socket cannot get stuck returning -EAGAIN forever against a
quiesced connection. Both ToS ioctls use the same lock now (they
used the unrelated global rds_sock_lock before), and since the
create in rds_sendmsg() samples rs_tos without the lock, the install
re-checks under it that the new connection's ToS still matches the
socket's and returns -EAGAIN if a SIOCRDSSETTOS slipped in between,
rather than sending on, and caching, a connection with the old ToS.
- parent->c_passive owns a reference, dropped when the parent is
destroyed. The pointer is read under rcu_read_lock() and written
under rds_conn_lock, so it is RCU-annotated and accessed through
rcu_dereference()/rcu_assign_pointer(). A passive connection whose
own destroy has begun is neither handed out nor left dangling in
the parent: __rds_conn_create() refuses it, and the child's destroy
clears the parent's pointer and drops that reference itself, so a
quiesced passive conn cannot be revived by a later connect request.
Serializing the rs_conn cache under rs_lock also resolves a
syzbot-reported KCSAN data race between concurrent rds_sendmsg()
calls on the same socket, each installing the connection it created
into rs->rs_conn with a plain store:
BUG: KCSAN: data-race in rds_sendmsg / rds_sendmsg
write to 0xffff888101dec818 of 8 bytes by task 30904 on cpu 0:
rds_sendmsg+0xc1f/0x1580 net/rds/send.c:1332
write to 0xffff888101dec818 of 8 bytes by task 30905 on cpu 1:
rds_sendmsg+0xc1f/0x1580 net/rds/send.c:1332
value changed: 0x0000000000000000 -> 0xffff88811b61faf0
cm_id->context still carries no reference of its own after this
patch; the following patch pins the connection for the duration of
the CM event handler.
rds_tcp_accept_one() needs no destroy check of its own even though it
now gets a referenced connection back from rds_conn_create(): TCP
connections are only destroyed on netns teardown and module unload,
and both stop the listener - flushing the accept work and clearing
the listen socket the accept tests first - before destroying anything.
This is not a stable candidate on its own: it depends on the
connection reference counting introduced by the preceding patches,
and the race it closes needs a connection destroyed under a live
socket, which takes netns teardown, module unload or device removal.
Based on the Oracle UEK commits "net/rds: Add krefs to
struct rds_connection" and "net/rds: rds_sendmsg must
use rs_conn only when not being destroyed".
Reported-by: syzbot+879c1877016972360186@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=879c1877016972360186
Signed-off-by: Sharath Srinivasan <sharath.srinivasan@oracle.com>
[achender: substantial reimplementation for net-next: upstream has no
conn reaper, per-conn workers hold no references (destroy cancels
them synchronously before the final put), and the sendmsg cache is
serialized with rs_lock instead of UEK's socket flag; rewrite commit
message]
Assisted-by: Claude-Code:claude-fable-5
Signed-off-by: Allison Henderson <achender@kernel.org>
---
net/rds/af_rds.c | 24 +++++++--
net/rds/connection.c | 125 +++++++++++++++++++++++++++++++++++++++++--
net/rds/ib_cm.c | 9 +++-
net/rds/loop.c | 2 +-
net/rds/rds.h | 6 ++-
net/rds/send.c | 53 ++++++++++++++++--
net/rds/tcp_listen.c | 11 +++-
7 files changed, 210 insertions(+), 20 deletions(-)
diff --git a/net/rds/af_rds.c b/net/rds/af_rds.c
index d5defe9172e3..1cc20b5cfd21 100644
--- a/net/rds/af_rds.c
+++ b/net/rds/af_rds.c
@@ -80,6 +80,14 @@ static int rds_release(struct socket *sock)
rds_notify_queue_get(rs, NULL);
rds_notify_msg_zcopy_purge(&rs->rs_zcookie_queue);
+ /* drop the cached connection reference; no sendmsg can race
+ * with us here, the socket is going away
+ */
+ if (rs->rs_conn) {
+ rds_conn_put(rs->rs_conn);
+ rs->rs_conn = NULL;
+ }
+
spin_lock_bh(&rds_sock_lock);
list_del_init(&rs->rs_item);
spin_unlock_bh(&rds_sock_lock);
@@ -255,6 +263,7 @@ static int rds_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
struct rds_sock *rs = rds_sk_to_rs(sock->sk);
rds_tos_t utos, tos = 0;
+ unsigned long flags;
switch (cmd) {
case SIOCRDSSETTOS:
@@ -267,18 +276,23 @@ static int rds_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
else
return -ENOIOCTLCMD;
- spin_lock_bh(&rds_sock_lock);
+ /* rs_conn is serialized by rs_lock (see rds_sendmsg());
+ * hold it across the "no connection yet" check and the
+ * rs_tos store so a racing sendmsg cannot cache a conn
+ * whose c_tos then disagrees with rs_tos.
+ */
+ spin_lock_irqsave(&rs->rs_lock, flags);
if (rs->rs_tos || rs->rs_conn) {
- spin_unlock_bh(&rds_sock_lock);
+ spin_unlock_irqrestore(&rs->rs_lock, flags);
return -EINVAL;
}
rs->rs_tos = tos;
- spin_unlock_bh(&rds_sock_lock);
+ spin_unlock_irqrestore(&rs->rs_lock, flags);
break;
case SIOCRDSGETTOS:
- spin_lock_bh(&rds_sock_lock);
+ spin_lock_irqsave(&rs->rs_lock, flags);
tos = rs->rs_tos;
- spin_unlock_bh(&rds_sock_lock);
+ spin_unlock_irqrestore(&rs->rs_lock, flags);
if (put_user(tos, (rds_tos_t __user *)arg))
return -EFAULT;
break;
diff --git a/net/rds/connection.c b/net/rds/connection.c
index 1d48da1a794f..965d68e51a1c 100644
--- a/net/rds/connection.c
+++ b/net/rds/connection.c
@@ -81,7 +81,10 @@ static struct hlist_head *rds_conn_bucket(const struct in6_addr *laddr,
var |= RDS_INFO_CONNECTION_FLAG_##suffix; \
} while (0)
-/* rcu read lock must be held or the connection spinlock */
+/* rcu read lock must be held or the connection spinlock.
+ * On success a reference is taken on the returned connection; the
+ * caller must drop it with rds_conn_put().
+ */
static struct rds_connection *rds_conn_lookup(struct net *net,
struct hlist_head *head,
const struct in6_addr *laddr,
@@ -98,6 +101,17 @@ static struct rds_connection *rds_conn_lookup(struct net *net,
conn->c_tos == tos &&
net == rds_conn_net(conn) &&
conn->c_dev_if == dev_if) {
+ /* Only ever hand out a live reference.
+ * rds_conn_destroy() unhashes under
+ * rds_conn_lock and waits a grace period
+ * before dropping the initial reference, so
+ * an entry this traversal reaches still holds
+ * at least that one; the conditional get
+ * documents the contract rather than
+ * papering over a zero-refcount entry.
+ */
+ if (!kref_get_unless_zero(&conn->c_refcount))
+ continue;
ret = conn;
break;
}
@@ -163,6 +177,14 @@ static void __rds_conn_path_init(struct rds_connection *conn,
cp->cp_flags = 0;
}
+/* c_passive is written under rds_conn_lock and read under RCU */
+static struct rds_connection *
+rds_conn_passive_locked(struct rds_connection *conn)
+{
+ return rcu_dereference_protected(conn->c_passive,
+ lockdep_is_held(&rds_conn_lock));
+}
+
/* Undo trans->conn_alloc(): it may have allocated transport data for
* every path of a multipath connection, not just for path 0.
*/
@@ -215,7 +237,20 @@ static struct rds_connection *__rds_conn_create(struct net *net,
* We need a second connection object into which we
* can stick the other QP. */
parent = conn;
- conn = parent->c_passive;
+ /* The c_passive pointer holds a reference which is only
+ * dropped one synchronize_rcu() after the pointer is
+ * cleared, so within this RCU section a fetched pointer
+ * is always safe to take a reference on. A passive conn
+ * whose own destroy has begun is not handed out, though:
+ * it is quiesced and about to clear the parent's pointer
+ * itself, and reusing it would re-arm a connection that
+ * nothing will tear down again.
+ */
+ conn = rcu_dereference(parent->c_passive);
+ if (conn && READ_ONCE(conn->c_destroy_in_prog))
+ conn = NULL;
+ if (conn)
+ rds_conn_get(conn);
}
rcu_read_unlock();
if (conn)
@@ -334,13 +369,44 @@ static struct rds_connection *__rds_conn_create(struct net *net,
spin_lock_irqsave(&rds_conn_lock, flags);
if (parent) {
/* Creating passive conn */
- if (parent->c_passive) {
+ if (READ_ONCE(parent->c_destroy_in_prog)) {
+ /* The parent's destroy has begun (it sets the
+ * flag and snatches c_passive under this
+ * lock); do not install a new passive conn
+ * that nothing would ever destroy.
+ */
+ rds_conn_free_transport_data(conn, npaths);
+ free_cp = conn->c_path;
+ kmem_cache_free(rds_conn_slab, conn);
+ conn = ERR_PTR(-ENETDOWN);
+ } else if (rcu_access_pointer(parent->c_passive)) {
+ struct rds_connection *passive;
+
+ passive = rds_conn_passive_locked(parent);
rds_conn_free_transport_data(conn, npaths);
free_cp = conn->c_path;
kmem_cache_free(rds_conn_slab, conn);
- conn = parent->c_passive;
+ if (READ_ONCE(passive->c_destroy_in_prog)) {
+ /* Its destroy will clear the parent's
+ * pointer under this lock shortly; until
+ * then there is no usable passive conn.
+ */
+ conn = ERR_PTR(-ENETDOWN);
+ } else {
+ rds_conn_get(passive);
+ conn = passive;
+ }
} else {
- parent->c_passive = conn;
+ /* The initial reference belongs to whoever
+ * destroys the conn (the transport's conn
+ * lists, as for any other conn). Take one
+ * for the c_passive pointer - dropped when
+ * the parent is destroyed - and one for our
+ * caller.
+ */
+ rds_conn_get(conn); /* c_passive */
+ rds_conn_get(conn); /* caller */
+ rcu_assign_pointer(parent->c_passive, conn);
rds_cong_add_conn(conn);
rds_conn_count++;
atomic_inc(&conn->c_trans->t_conn_count);
@@ -359,6 +425,10 @@ static struct rds_connection *__rds_conn_create(struct net *net,
} else {
conn->c_my_gen_num = rds_gen_num;
conn->c_peer_gen_num = 0;
+ /* the initial reference belongs to whoever
+ * destroys the conn; take one for our caller
+ */
+ rds_conn_get(conn);
hlist_add_head_rcu(&conn->c_hash_node, head);
rds_cong_add_conn(conn);
rds_conn_count++;
@@ -369,6 +439,8 @@ static struct rds_connection *__rds_conn_create(struct net *net,
rcu_read_unlock();
out:
+ if (parent)
+ rds_conn_put(parent);
if (free_cp) {
for (i = 0; i < npaths; i++)
if (free_cp[i].cp_wq != rds_wq)
@@ -672,6 +744,9 @@ EXPORT_SYMBOL_GPL(rds_conn_put);
void rds_conn_destroy(struct rds_connection *conn)
{
int i;
+ struct rds_connection *passive, *parent;
+ struct hlist_head *head;
+ bool was_passive = false;
struct rds_conn_path *cp;
int npaths = (conn->c_trans->t_mp_capable ? RDS_MPATH_WORKERS : 1);
@@ -702,7 +777,38 @@ void rds_conn_destroy(struct rds_connection *conn)
/* Ensure conn will not be scheduled for reconnect */
hlist_del_init_rcu(&conn->c_hash_node);
+
+ /* Snatch c_passive while holding the lock:
+ * __rds_conn_create() dereferences it under rcu_read_lock()
+ * (and refuses to install a new one once c_destroy_in_prog is
+ * set, which it checks under this lock). After the
+ * synchronize_rcu() below no one can pick the pointer up any
+ * more and its reference can be dropped.
+ */
+ passive = rds_conn_passive_locked(conn);
+ RCU_INIT_POINTER(conn->c_passive, NULL);
+
+ /* If we are a parent's passive twin, invalidate its pointer to
+ * us as well, so that __rds_conn_create() cannot hand out a
+ * connection whose teardown has begun. The parent is the
+ * hashed connection for our key (a passive conn is never
+ * hashed, and we unhashed ourselves above); it holds its
+ * initial reference for as long as it is hashed, so the lookup
+ * reference dropped below cannot be its last.
+ */
+ head = rds_conn_bucket(&conn->c_laddr, &conn->c_faddr);
+ rcu_read_lock();
+ parent = rds_conn_lookup(rds_conn_net(conn), head, &conn->c_laddr,
+ &conn->c_faddr, conn->c_trans, conn->c_tos,
+ conn->c_dev_if);
+ rcu_read_unlock();
+ if (parent && rds_conn_passive_locked(parent) == conn) {
+ RCU_INIT_POINTER(parent->c_passive, NULL);
+ was_passive = true;
+ }
spin_unlock_irq(&rds_conn_lock);
+ if (parent)
+ rds_conn_put(parent);
synchronize_rcu();
/* shut the connection down */
@@ -719,6 +825,15 @@ void rds_conn_destroy(struct rds_connection *conn)
*/
rds_cong_remove_conn(conn);
+ /* drop the reference our c_passive pointer held, if any, and
+ * the one a parent's c_passive pointer held on us; neither can
+ * be the last, since the initial reference is dropped below
+ */
+ if (passive)
+ rds_conn_put(passive);
+ if (was_passive)
+ rds_conn_put(conn);
+
/* drop the initial reference; the connection is freed from
* rds_conn_destroy_fini() once every holder has dropped theirs
*/
diff --git a/net/rds/ib_cm.c b/net/rds/ib_cm.c
index ccb7e2b93ff5..b904208380f1 100644
--- a/net/rds/ib_cm.c
+++ b/net/rds/ib_cm.c
@@ -924,8 +924,15 @@ int rds_ib_cm_handle_connect(struct rdma_cm_id *cm_id,
rds_ib_conn_error(conn, "rdma_accept failed\n");
out:
- if (conn)
+ if (conn) {
mutex_unlock(&conn->c_cm_lock);
+ /* Drop the reference rds_conn_create() handed us. The
+ * conn stays reachable through cm_id->context without a
+ * reference of its own for now; the CM event handler is
+ * given one of its own by a following patch.
+ */
+ rds_conn_put(conn);
+ }
if (err)
rdma_reject(cm_id, &err, sizeof(int),
IB_CM_REJ_CONSUMER_DEFINED);
diff --git a/net/rds/loop.c b/net/rds/loop.c
index 71f760ccd458..5bac858df87e 100644
--- a/net/rds/loop.c
+++ b/net/rds/loop.c
@@ -170,7 +170,7 @@ static void rds_loop_destroy_gathered_conns(struct list_head *tmp_list)
list_for_each_entry_safe(lc, _lc, tmp_list, loop_node) {
conn = lc->conn;
- WARN_ON(conn->c_passive);
+ WARN_ON(rcu_access_pointer(conn->c_passive));
spin_lock_irq(&loop_conns_lock);
list_del_init(&lc->loop_node);
diff --git a/net/rds/rds.h b/net/rds/rds.h
index b3cc0804156e..321f2da9e76d 100644
--- a/net/rds/rds.h
+++ b/net/rds/rds.h
@@ -161,7 +161,7 @@ struct rds_connection {
* cancellation from landing on a destroyed workqueue.
*/
bool c_destroy_in_prog;
- struct rds_connection *c_passive;
+ struct rds_connection __rcu *c_passive;
struct rds_transport *c_trans;
struct rds_cong_map *c_lcong;
@@ -669,7 +669,9 @@ struct rds_sock {
/*
* rds_sendmsg caches the conn it used the last time around.
- * This helps avoid costly lookups.
+ * This helps avoid costly lookups. The cache owns a connection
+ * reference, dropped when it is replaced or the socket is
+ * released, and is read and written under rs_lock.
*/
struct rds_connection *rs_conn;
diff --git a/net/rds/send.c b/net/rds/send.c
index 32c411d10e3e..2d7839438abd 100644
--- a/net/rds/send.c
+++ b/net/rds/send.c
@@ -1159,13 +1159,14 @@ int rds_sendmsg(struct socket *sock, struct msghdr *msg, size_t payload_len)
DECLARE_SOCKADDR(struct sockaddr_in *, usin, msg->msg_name);
__be16 dport;
struct rds_message *rm = NULL;
- struct rds_connection *conn;
+ struct rds_connection *conn = NULL;
int ret = 0;
int queued = 0, allocated_mr = 0;
int nonblock = msg->msg_flags & MSG_DONTWAIT;
long timeo = sock_sndtimeo(sk, nonblock);
struct rds_conn_path *cpath;
struct in6_addr daddr;
+ unsigned long flags;
__u32 scope_id = 0;
size_t rdma_payload_len = 0;
bool zcopy = ((msg->msg_flags & MSG_ZEROCOPY) &&
@@ -1340,11 +1341,29 @@ int rds_sendmsg(struct socket *sock, struct msghdr *msg, size_t payload_len)
rm->m_daddr = daddr;
/* rds_conn_create has a spinlock that runs with IRQ off.
- * Caching the conn in the socket helps a lot. */
- if (rs->rs_conn && ipv6_addr_equal(&rs->rs_conn->c_faddr, &daddr) &&
- rs->rs_tos == rs->rs_conn->c_tos) {
- conn = rs->rs_conn;
+ * Caching the conn in the socket helps a lot.
+ *
+ * The cached rs_conn holds a connection reference; take one of
+ * our own for the duration of this call (dropped on both exit
+ * paths), so that neither a concurrent sender replacing the
+ * cache nor rds_conn_destroy() can free the connection under
+ * us. A cached connection whose destruction has begun is not
+ * reused: dropping it here lets the next sendmsg look up or
+ * create a live one instead of returning -EAGAIN forever.
+ */
+ spin_lock_irqsave(&rs->rs_lock, flags);
+ conn = rs->rs_conn;
+ if (conn && ipv6_addr_equal(&conn->c_faddr, &daddr) &&
+ rs->rs_tos == conn->c_tos && !rds_destroy_pending(conn)) {
+ rds_conn_get(conn);
} else {
+ conn = NULL;
+ }
+ spin_unlock_irqrestore(&rs->rs_lock, flags);
+
+ if (!conn) {
+ struct rds_connection *old;
+
conn = rds_conn_create_outgoing(sock_net(sock->sk),
&rs->rs_bound_addr, &daddr,
rs->rs_transport, rs->rs_tos,
@@ -1352,9 +1371,28 @@ int rds_sendmsg(struct socket *sock, struct msghdr *msg, size_t payload_len)
scope_id);
if (IS_ERR(conn)) {
ret = PTR_ERR(conn);
+ conn = NULL;
goto out;
}
+ /* rs_tos was sampled without rs_lock for the create above,
+ * and SIOCRDSSETTOS only refuses a change once rs_conn is
+ * set, so it can have changed underneath us. Do not
+ * install - or send on - a connection whose ToS no longer
+ * matches the socket's; the retry uses the new one.
+ */
+ spin_lock_irqsave(&rs->rs_lock, flags);
+ if (conn->c_tos != rs->rs_tos) {
+ spin_unlock_irqrestore(&rs->rs_lock, flags);
+ ret = -EAGAIN;
+ goto out;
+ }
+ /* hand the cache its own reference */
+ rds_conn_get(conn);
+ old = rs->rs_conn;
rs->rs_conn = conn;
+ spin_unlock_irqrestore(&rs->rs_lock, flags);
+ if (old)
+ rds_conn_put(old);
}
if (conn->c_trans->t_mp_capable) {
@@ -1474,6 +1512,8 @@ int rds_sendmsg(struct socket *sock, struct msghdr *msg, size_t payload_len)
kfree(vct.vec[ind].iov);
kfree(vct.vec);
+ rds_conn_put(conn);
+
return payload_len;
out:
@@ -1481,6 +1521,9 @@ int rds_sendmsg(struct socket *sock, struct msghdr *msg, size_t payload_len)
kfree(vct.vec[ind].iov);
kfree(vct.vec);
+ if (conn)
+ rds_conn_put(conn);
+
/* If the user included a RDMA_MAP cmsg, we allocated a MR on the fly.
* If the sendmsg goes through, we keep the MR. If it fails with EAGAIN
* or in any other way, we need to destroy the MR again */
diff --git a/net/rds/tcp_listen.c b/net/rds/tcp_listen.c
index 8a0c54aced5e..7fea5501d756 100644
--- a/net/rds/tcp_listen.c
+++ b/net/rds/tcp_listen.c
@@ -153,7 +153,7 @@ int rds_tcp_accept_one(struct rds_tcp_net *rtn)
{
struct socket *listen_sock = rtn->rds_tcp_listen_sock;
struct socket *new_sock = NULL;
- struct rds_connection *conn;
+ struct rds_connection *conn = NULL;
int ret;
struct inet_sock *inet;
struct rds_tcp_connection *rs_tcp = NULL;
@@ -229,6 +229,7 @@ int rds_tcp_accept_one(struct rds_tcp_net *rtn)
if (IS_ERR(conn)) {
ret = PTR_ERR(conn);
+ conn = NULL;
goto out;
}
/* An incoming SYN request came in, and TCP just accepted it.
@@ -277,6 +278,12 @@ int rds_tcp_accept_one(struct rds_tcp_net *rtn)
cp = rs_tcp->t_cpath;
conn_state = rds_conn_path_state(cp);
WARN_ON(conn_state == RDS_CONN_UP);
+ /* A connection whose destroy has begun cannot be found here:
+ * TCP connections are only destroyed on netns teardown and on
+ * module unload, and both run rds_tcp_listen_stop() - which
+ * flushes this work and clears the listen socket that the top
+ * of this function tests - before any connection is destroyed.
+ */
if (conn_state != RDS_CONN_CONNECTING && conn_state != RDS_CONN_ERROR) {
rds_conn_path_drop(cp, 0);
goto rst_nsk;
@@ -347,6 +354,8 @@ int rds_tcp_accept_one(struct rds_tcp_net *rtn)
mutex_unlock(&rs_tcp->t_conn_path_lock);
if (new_sock)
sock_release(new_sock);
+ if (conn)
+ rds_conn_put(conn);
mutex_unlock(&rtn->rds_tcp_accept_lock);
--
2.25.1
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v5 09/12] net/rds: refuse to queue on a connection being destroyed
2026-09-19 6:11 [PATCH net-next v5 00/12] net/rds: make connection lifetime reference-counted Allison Henderson
` (7 preceding siblings ...)
2026-09-19 6:11 ` [PATCH net-next v5 08/12] net/rds: hold connection references in lookup, sockets and c_passive Allison Henderson
@ 2026-09-19 6:11 ` Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
2026-09-19 6:11 ` [PATCH net-next v5 10/12] net/rds: pin the connection across RDMA-CM event handling Allison Henderson
` (2 subsequent siblings)
11 siblings, 2 replies; 35+ messages in thread
From: Allison Henderson @ 2026-09-19 6:11 UTC (permalink / raw)
To: netdev, linux-rdma, pabeni, edumazet, kuba, horms; +Cc: achender, nicoyip.dev
rds_conn_path_quiesce() tears down cp_send_queue by walking it with no
lock held. That was tolerable while a connection could only be
destroyed with no sender in flight, but a sender now holds a
reference across rds_sendmsg(), and rds_conn_destroy() can quiesce
the connection underneath it. rds_send_queue_rm() adds to
cp_send_queue under cp_lock, so the unlocked walk races the add and
can corrupt the list. Worse, a message added after the purge sits on
the queue of a quiesced connection holding the connection reference
rds_send_queue_rm() took for it: the reference is only dropped when
the message is freed, the message is only freed when the queue is
torn down, and the queue is only torn down by the destroy that has
already run. The connection would never be freed, and with it the
transport could never unload.
Splice the queue away under cp_lock in the quiesce, and have
rds_send_queue_rm() test rds_destroy_pending() under that same lock
before it touches either queue. rds_send_probe() adds to
cp_send_queue under cp_lock as well, for pings and pongs, and gets the
same test: a probe queued after the purge would pin the connection
just the same. A sender that gets there first has
its message purged; one that gets there second is refused, and
rds_sendmsg() returns -EAGAIN for it, the same result the early
rds_destroy_pending() check in rds_sendmsg() already produces for a
connection whose destroy had begun before the send started.
rds_send_queue_rm()'s *queued becomes negative on refusal so that the
wait loop in rds_sendmsg() stops waiting for send room that will never
come.
Assisted-by: Claude-Code:claude-fable-5
Signed-off-by: Allison Henderson <achender@kernel.org>
---
net/rds/connection.c | 16 ++++++++++++----
net/rds/send.c | 28 +++++++++++++++++++++++++++-
2 files changed, 39 insertions(+), 5 deletions(-)
diff --git a/net/rds/connection.c b/net/rds/connection.c
index 965d68e51a1c..5699f45e4c37 100644
--- a/net/rds/connection.c
+++ b/net/rds/connection.c
@@ -606,6 +606,8 @@ void rds_conn_shutdown(struct rds_conn_path *cp)
static void rds_conn_path_quiesce(struct rds_conn_path *cp)
{
struct rds_message *rm, *rtmp;
+ unsigned long flags;
+ LIST_HEAD(purge);
if (!cp->cp_transport_data)
return;
@@ -617,10 +619,16 @@ static void rds_conn_path_quiesce(struct rds_conn_path *cp)
rds_conn_path_drop(cp, true);
flush_work(&cp->cp_down_w);
- /* tear down queued messages */
- list_for_each_entry_safe(rm, rtmp,
- &cp->cp_send_queue,
- m_conn_item) {
+ /* Tear down queued messages. Take the queue under cp_lock:
+ * a sender that still holds a reference can be inside
+ * rds_send_queue_rm() right now, and it tests
+ * rds_destroy_pending() under the same lock, so after this
+ * splice nothing is added behind our back.
+ */
+ spin_lock_irqsave(&cp->cp_lock, flags);
+ list_splice_init(&cp->cp_send_queue, &purge);
+ spin_unlock_irqrestore(&cp->cp_lock, flags);
+ list_for_each_entry_safe(rm, rtmp, &purge, m_conn_item) {
list_del_init(&rm->m_conn_item);
BUG_ON(!list_empty(&rm->m_sock_item));
rds_message_put(rm);
diff --git a/net/rds/send.c b/net/rds/send.c
index 2d7839438abd..f7bc4c5446d6 100644
--- a/net/rds/send.c
+++ b/net/rds/send.c
@@ -928,6 +928,19 @@ static int rds_send_queue_rm(struct rds_sock *rs, struct rds_connection *conn,
* and poll() now knows no more data can be sent.
*/
if (rs->rs_snd_bytes < rds_sk_sndbuf(rs)) {
+ /* rds_conn_path_quiesce() empties cp_send_queue under
+ * cp_lock once the connection's destroy has begun. Test
+ * for that under the same lock, before touching either
+ * queue: a message added after the purge would hold a
+ * connection reference nothing ever drops.
+ */
+ spin_lock(&cp->cp_lock);
+ if (rds_destroy_pending(conn)) {
+ spin_unlock(&cp->cp_lock);
+ *queued = -EAGAIN;
+ goto unlock;
+ }
+
rs->rs_snd_bytes += len;
/* let recv side know we are close to send space exhaustion.
@@ -951,7 +964,6 @@ static int rds_send_queue_rm(struct rds_sock *rs, struct rds_connection *conn,
rm->m_inc.i_conn_path = cp;
rds_message_addref(rm);
- spin_lock(&cp->cp_lock);
rm->m_inc.i_hdr.h_sequence = cpu_to_be64(cp->cp_next_tx_seq++);
list_add_tail(&rm->m_conn_item, &cp->cp_send_queue);
set_bit(RDS_MSG_ON_CONN, &rm->m_flags);
@@ -964,6 +976,7 @@ static int rds_send_queue_rm(struct rds_sock *rs, struct rds_connection *conn,
*queued = 1;
}
+unlock:
spin_unlock_irqrestore(&rs->rs_lock, flags);
out:
return *queued;
@@ -1485,6 +1498,11 @@ int rds_sendmsg(struct socket *sock, struct msghdr *msg, size_t payload_len)
ret = -ETIMEDOUT;
goto out;
}
+ /* rds_send_queue_rm() refused: the connection is being destroyed */
+ if (queued < 0) {
+ ret = queued;
+ goto out;
+ }
/*
* By now we've committed to the send. We reuse rds_send_worker()
@@ -1567,6 +1585,14 @@ rds_send_probe(struct rds_conn_path *cp, __be16 sport,
goto out;
spin_lock_irqsave(&cp->cp_lock, flags);
+ /* Same rule as rds_send_queue_rm(): once the destroy has purged
+ * cp_send_queue under this lock, nothing may be added behind it.
+ */
+ if (rds_destroy_pending(cp->cp_conn)) {
+ spin_unlock_irqrestore(&cp->cp_lock, flags);
+ ret = -EAGAIN;
+ goto out;
+ }
list_add_tail(&rm->m_conn_item, &cp->cp_send_queue);
set_bit(RDS_MSG_ON_CONN, &rm->m_flags);
rds_message_addref(rm);
--
2.25.1
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v5 10/12] net/rds: pin the connection across RDMA-CM event handling
2026-09-19 6:11 [PATCH net-next v5 00/12] net/rds: make connection lifetime reference-counted Allison Henderson
` (8 preceding siblings ...)
2026-09-19 6:11 ` [PATCH net-next v5 09/12] net/rds: refuse to queue on a connection being destroyed Allison Henderson
@ 2026-09-19 6:11 ` Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
2026-09-19 6:11 ` [PATCH net-next v5 11/12] net/rds: drop rds_conn_count in favor of t_conn_count Allison Henderson
2026-09-19 6:11 ` [PATCH net-next v5 12/12] net/rds: hold a connection reference from struct rds_incoming Allison Henderson
11 siblings, 2 replies; 35+ messages in thread
From: Allison Henderson @ 2026-09-19 6:11 UTC (permalink / raw)
To: netdev, linux-rdma, pabeni, edumazet, kuba, horms; +Cc: achender, nicoyip.dev
rds_rdma_cm_event_handler_cmn() picks the connection up from
cm_id->context, which carries no reference, and holds c_cm_lock - a
mutex that lives in the connection's path array - across the transport
callbacks. Before this series that was already a use-after-free
whenever a callback destroyed the connection, since rds_conn_destroy()
freed it synchronously and the handler's mutex_unlock() ran on freed
memory; the one such callback, rds_ib_cm_connect_complete() on a
protocol version below 3.1, has meanwhile been switched to
rds_conn_drop() by commit f97d8c7bab78 ("rds: ib: use rds_conn_drop()
on protocol version mismatch"), which also removed the deadlock that
destroy took on c_cm_lock.
Now that a connection is freed by its last reference, the remaining
exposure is a callback that drops the last reference other than the
handler's - which has none - and the reference handed out by
rds_conn_create() to rds_ib_cm_handle_connect() and dropped at its
end. Take a reference for the duration of the handler, and ignore the
event if the connection is already being freed: its cm_id teardown is
what stops event delivery, so an event that still arrives belongs to a
connection whose shutdown has run and whose memory is on its way out.
rds_ib_cm_handle_connect() has the mirror-image hole: a connection
whose destroy has already quiesced it sits in RDS_CONN_DOWN with no
cm_id, which is exactly the state the DOWN -> CONNECTING transition
claims. A connect request arriving then would install a new cm_id and
QP on a connection that is only waiting for its last reference to go
away, and nothing would tear them down again. Re-check
rds_destroy_pending() under c_cm_lock and reject the request instead.
Assisted-by: Claude-Code:claude-fable-5
Signed-off-by: Allison Henderson <achender@kernel.org>
---
net/rds/ib_cm.c | 7 +++++++
net/rds/rdma_transport.c | 16 +++++++++++++++-
2 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/net/rds/ib_cm.c b/net/rds/ib_cm.c
index b904208380f1..cbeedfc4fabd 100644
--- a/net/rds/ib_cm.c
+++ b/net/rds/ib_cm.c
@@ -874,6 +874,13 @@ int rds_ib_cm_handle_connect(struct rdma_cm_id *cm_id,
* see the comment above rds_queue_reconnect()
*/
mutex_lock(&conn->c_cm_lock);
+ /* A destroy that has already quiesced this conn leaves it in
+ * RDS_CONN_DOWN with no cm_id, exactly what the transition
+ * below would happily claim; nothing would tear the new cm_id
+ * and QP down again before the conn is freed. Reject instead.
+ */
+ if (rds_destroy_pending(conn))
+ goto out;
if (!rds_conn_transition(conn, RDS_CONN_DOWN, RDS_CONN_CONNECTING)) {
if (rds_conn_state(conn) == RDS_CONN_UP) {
rdsdebug("incoming connect while connecting\n");
diff --git a/net/rds/rdma_transport.c b/net/rds/rdma_transport.c
index b15cf316b23a..584e9867810f 100644
--- a/net/rds/rdma_transport.c
+++ b/net/rds/rdma_transport.c
@@ -63,6 +63,18 @@ static int rds_rdma_cm_event_handler_cmn(struct rdma_cm_id *cm_id,
if (cm_id->device->node_type == RDMA_NODE_IB_CA)
trans = &rds_ib_transport;
+ /* cm_id->context carries no reference of its own. Pin the
+ * connection for the duration of the handler: what the callbacks
+ * below do may drop the last reference other than ours, and the
+ * mutex released at out: lives in the connection's path array.
+ * A connection already being freed gets no events handled.
+ */
+ if (conn && !rds_conn_get_unless_zero(conn)) {
+ rdsdebug("conn %p id %p is being freed, ignoring event\n",
+ conn, cm_id);
+ return 0;
+ }
+
/* Prevent shutdown from tearing down the connection
* while we're executing. */
if (conn) {
@@ -171,8 +183,10 @@ static int rds_rdma_cm_event_handler_cmn(struct rdma_cm_id *cm_id,
}
out:
- if (conn)
+ if (conn) {
mutex_unlock(&conn->c_cm_lock);
+ rds_conn_put(conn);
+ }
rdsdebug("id %p event %u (%s) handling ret %d\n", cm_id, event->event,
rdma_event_msg(event->event), ret);
--
2.25.1
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v5 11/12] net/rds: drop rds_conn_count in favor of t_conn_count
2026-09-19 6:11 [PATCH net-next v5 00/12] net/rds: make connection lifetime reference-counted Allison Henderson
` (9 preceding siblings ...)
2026-09-19 6:11 ` [PATCH net-next v5 10/12] net/rds: pin the connection across RDMA-CM event handling Allison Henderson
@ 2026-09-19 6:11 ` Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-19 6:11 ` [PATCH net-next v5 12/12] net/rds: hold a connection reference from struct rds_incoming Allison Henderson
11 siblings, 1 reply; 35+ messages in thread
From: Allison Henderson @ 2026-09-19 6:11 UTC (permalink / raw)
To: netdev, linux-rdma, pabeni, edumazet, kuba, horms; +Cc: achender, nicoyip.dev
The previous patch gave each transport its own connection count in
t_conn_count, incremented and decremented at exactly the points where
the global rds_conn_count is. That leaves rds_conn_count with a
single remaining consumer: the seed of the per-path workqueue names
in __rds_conn_create().
Switch the name seed to t_conn_count, as UEK does, and remove
rds_conn_count. The numbering becomes per-transport instead of
global, so connections of different transports can now receive the
same seed; workqueue names carry no uniqueness requirement, and the
seed was already reused as the count rose and fell. Removing the
counter also removes the rds_conn_lock round-trip that
rds_conn_destroy_fini() took solely to decrement it. (The free path
still sleeps in destroy_workqueue(), so the last reference has to be
dropped from process context as before; only that one lock goes.)
Based on the Oracle UEK commit "net/rds: Each RDS transport
should keep its own connection count".
Assisted-by: Claude-Code:claude-fable-5
Signed-off-by: Allison Henderson <achender@kernel.org>
---
net/rds/connection.c | 13 +++----------
1 file changed, 3 insertions(+), 10 deletions(-)
diff --git a/net/rds/connection.c b/net/rds/connection.c
index 5699f45e4c37..546961bb2e12 100644
--- a/net/rds/connection.c
+++ b/net/rds/connection.c
@@ -47,7 +47,6 @@
/* converting this to RCU is a chore for another day.. */
static DEFINE_SPINLOCK(rds_conn_lock);
-static unsigned long rds_conn_count;
/* woken whenever a transport's t_conn_count drops to zero */
static DECLARE_WAIT_QUEUE_HEAD(rds_conn_freed_waitq);
static struct hlist_head rds_conn_hash[RDS_CONNECTION_HASH_ENTRIES];
@@ -332,12 +331,13 @@ static struct rds_connection *__rds_conn_create(struct net *net,
init_waitqueue_head(&conn->c_hs_waitq);
for (i = 0; i < npaths; i++) {
+ int seq = atomic_read(&trans->t_conn_count);
+
__rds_conn_path_init(conn, &conn->c_path[i],
is_outgoing);
conn->c_path[i].cp_index = i;
conn->c_path[i].cp_wq =
- alloc_ordered_workqueue("krds_cp_wq#%lu/%d", 0,
- rds_conn_count, i);
+ alloc_ordered_workqueue("krds_cp_wq#%d/%d", 0, seq, i);
if (!conn->c_path[i].cp_wq)
conn->c_path[i].cp_wq = rds_wq;
}
@@ -408,7 +408,6 @@ static struct rds_connection *__rds_conn_create(struct net *net,
rds_conn_get(conn); /* caller */
rcu_assign_pointer(parent->c_passive, conn);
rds_cong_add_conn(conn);
- rds_conn_count++;
atomic_inc(&conn->c_trans->t_conn_count);
}
} else {
@@ -431,7 +430,6 @@ static struct rds_connection *__rds_conn_create(struct net *net,
rds_conn_get(conn);
hlist_add_head_rcu(&conn->c_hash_node, head);
rds_cong_add_conn(conn);
- rds_conn_count++;
atomic_inc(&conn->c_trans->t_conn_count);
}
}
@@ -669,7 +667,6 @@ static void rds_conn_destroy_fini(struct kref *kref)
c_refcount);
int npaths = (conn->c_trans->t_mp_capable ? RDS_MPATH_WORKERS : 1);
struct rds_transport *trans = conn->c_trans;
- unsigned long flags;
int i;
for (i = 0; i < npaths; i++)
@@ -678,10 +675,6 @@ static void rds_conn_destroy_fini(struct kref *kref)
kfree(conn->c_path);
kmem_cache_free(rds_conn_slab, conn);
- spin_lock_irqsave(&rds_conn_lock, flags);
- rds_conn_count--;
- spin_unlock_irqrestore(&rds_conn_lock, flags);
-
/* only after everything the transport module owns has been
* freed above may its unload proceed
*/
--
2.25.1
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v5 12/12] net/rds: hold a connection reference from struct rds_incoming
2026-09-19 6:11 [PATCH net-next v5 00/12] net/rds: make connection lifetime reference-counted Allison Henderson
` (10 preceding siblings ...)
2026-09-19 6:11 ` [PATCH net-next v5 11/12] net/rds: drop rds_conn_count in favor of t_conn_count Allison Henderson
@ 2026-09-19 6:11 ` Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
11 siblings, 2 replies; 35+ messages in thread
From: Allison Henderson @ 2026-09-19 6:11 UTC (permalink / raw)
To: netdev, linux-rdma, pabeni, edumazet, kuba, horms; +Cc: achender, nicoyip.dev
From: Sharath Srinivasan <sharath.srinivasan@oracle.com>
struct rds_incoming->i_conn stores a pointer to the connection a message
it belongs to, for both received messages and messages the socket sends.
But without taking a reference, nothing keeps that connection alive.
Embedded as the messages m_inc, an inc routinely outlives the connection
it points at, by sitting in the socket's receive queue until the
application reads it, while the connection is destroyed by device removal,
netns teardown or module unload - and every dereference of i_conn after
that point touches freed memory.
Chengfeng Ye reported one way to reach it, where the socket info
callbacks walk a receive queue after rmmod freed the connections:
BUG: KASAN: slab-use-after-free in rds6_inc_info_copy+0x459/0x530 [rds]
Read of size 1 at addr ffff888106031c50 by task poc/101
Call Trace:
rds6_inc_info_copy+0x459/0x530 [rds]
rds6_sock_inc_info+0x2b9/0x3c0 [rds]
rds_info_getsockopt+0x19d/0x380 [rds]
do_sock_getsockopt+0x2ac/0x480
__sys_getsockopt+0x128/0x210
Freed by task 102:
kmem_cache_free+0x1b5/0x3d0
rds_conn_destroy+0x484/0x600 [rds]
rds_loop_exit_net+0x32/0x50 [rds]
unregister_pernet_device+0x2c/0x50
rds_conn_exit+0x13/0xa0 [rds]
rds_exit+0x1a/0xc40 [rds]
__do_sys_delete_module+0x30a/0x4d0
Closing the socket gets there too, with no reader of i_conn other than
RDS itself: freeing an IB inc dereferences i_conn to hand the inc and
its fragments back to the connection's recycle cache, so draining the
receive queue of a socket whose connection is gone crashes in the
transport:
panic
...
rds_ib_recv_cache_put (net/rds/ib_recv.c:703)
rds_ib_inc_free (net/rds/ib_recv.c:207)
rds_clear_recv_queue (net/rds/recv.c:909)
rds_release (net/rds/af_rds.c:212)
__sock_release (net/socket.c:649)
sock_close (net/socket.c:1336)
with the freed connection confirmed by its now-zero reference count:
-trace[9]["inc"].i_conn.c_refcount
(struct kref){
.refcount = (refcount_t){
.refs = (atomic_t){
.counter = (int)0,
},
},
}
Now that connections are reference counted, make every holder of i_conn
own a reference. The pointer is assigned in six places - rds_inc_init()
and rds_inc_path_init() for received messages, rds_recv_incoming() when
it re-points an inc at the connection it arrived on, and
rds_send_queue_rm(), rds_send_probe() and the congestion-map path of
rds_send_xmit() for m_inc - and each of them now takes a reference. The
references are dropped from rds_inc_put() and rds_message_put(), which
are the points where the last user of the pointer goes away.
rds_recv_incoming() takes the new reference before dropping the old one,
so re-pointing an inc at the connection it already refers to cannot free
it. rds_inc_put() drops its reference through a local copy, since
inc_free() may free the memory the inc lives in.
This keeps a connection allocated for as long as messages that arrived
over it are queued on sockets, which is longer than before. What
lingers is the quiesced connection, its per-path workqueues (idle,
every work cancelled) and the transport's per-connection state,
including an IB connection's receive caches: rds_conn_destroy() still
quiesces the connection synchronously, so nothing runs on any of it.
It also means the transport cannot unload while such a datagram is
unread - rds_conn_wait_conns_freed() waits, warning every ten seconds,
until the application reads or closes. That is the intended
behaviour: teardown does not discard queued data, and unloading over a
live reference would be a use-after-free in the transport's text.
The final rds_conn_put() runs the free path, which destroys the per-path
workqueues and therefore may sleep, so the last reference has to be
dropped from process context. It always is. While a connection is
alive its hash-table entry holds the initial reference, so a put from a
completion handler or tasklet can never be the last one; that reference
is dropped by rds_conn_destroy(), from process context, after the
connection has been quiesced and its queued messages freed. The
references that survive that point are the ones held by incs on socket
receive queues and by messages on socket send queues, and those are
dropped from recvmsg and from close - process context in both cases.
This is not a stable candidate: reaching the use-after-free requires
freeing a connection out from under a live socket, which needs
CAP_SYS_MODULE, netns teardown or physical device removal, and the fix
depends on the connection reference counting introduced earlier in this
series.
Based on the Oracle UEK commit "net/rds: fix crash by
expanding kref coverage to rds_incoming.i_conn".
Reported-by: Chengfeng Ye <nicoyip.dev@gmail.com>
Closes: https://lore.kernel.org/netdev/20260720184955.3008978-1-nicoyip.dev@gmail.com/
Signed-off-by: Sharath Srinivasan <sharath.srinivasan@oracle.com>
Signed-off-by: Samasth Norway Ananda <samasth.norway.ananda@oracle.com>
[achender: port to net-next: same six assignment sites, but upstream
splits the message free across rds_message_unpin_worker(), so the
m_inc reference is dropped from a shared rds_message_free() helper
that both paths call; rds_recv_incoming() takes the new reference
before dropping the old; rewrite commit message]
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Allison Henderson <achender@kernel.org>
---
net/rds/message.c | 16 ++++++++++++++--
net/rds/recv.c | 26 +++++++++++++++++++++++---
net/rds/send.c | 4 ++++
3 files changed, 41 insertions(+), 5 deletions(-)
diff --git a/net/rds/message.c b/net/rds/message.c
index 47d5e9ab9b10..cea48ad745ca 100644
--- a/net/rds/message.c
+++ b/net/rds/message.c
@@ -182,6 +182,18 @@ static void rds_message_purge(struct rds_message *rm)
kref_put(&rm->atomic.op_rdma_mr->r_kref, __rds_put_mr_final);
}
+static void rds_message_free(struct rds_message *rm)
+{
+ /* get in rds_send_queue_rm(), rds_send_probe() or the congestion
+ * map path of rds_send_xmit(). Messages that were never queued on
+ * a connection have no reference to drop.
+ */
+ if (rm->m_inc.i_conn)
+ rds_conn_put(rm->m_inc.i_conn);
+
+ kfree(rm);
+}
+
static void rds_message_unpin_worker(struct work_struct *work)
{
struct rds_message *rm = container_of(work, struct rds_message,
@@ -192,7 +204,7 @@ static void rds_message_unpin_worker(struct work_struct *work)
if (rm->atomic.op_unpin_deferred)
rds_atomic_op_unpin_page(&rm->atomic);
- kfree(rm);
+ rds_message_free(rm);
}
void rds_message_put(struct rds_message *rm)
@@ -217,7 +229,7 @@ void rds_message_put(struct rds_message *rm)
return;
}
- kfree(rm);
+ rds_message_free(rm);
}
}
EXPORT_SYMBOL_GPL(rds_message_put);
diff --git a/net/rds/recv.c b/net/rds/recv.c
index 6204e577a90a..1fcd4483d3be 100644
--- a/net/rds/recv.c
+++ b/net/rds/recv.c
@@ -46,6 +46,7 @@ void rds_inc_init(struct rds_incoming *inc, struct rds_connection *conn,
{
refcount_set(&inc->i_refcount, 1);
INIT_LIST_HEAD(&inc->i_item);
+ rds_conn_get(conn); /* put in rds_inc_put() */
inc->i_conn = conn;
inc->i_conn_path = NULL;
inc->i_saddr = *saddr;
@@ -61,6 +62,7 @@ void rds_inc_path_init(struct rds_incoming *inc, struct rds_conn_path *cp,
{
refcount_set(&inc->i_refcount, 1);
INIT_LIST_HEAD(&inc->i_item);
+ rds_conn_get(cp->cp_conn); /* put in rds_inc_put() */
inc->i_conn = cp->cp_conn;
inc->i_conn_path = cp;
inc->i_saddr = *saddr;
@@ -81,9 +83,19 @@ void rds_inc_put(struct rds_incoming *inc)
{
rdsdebug("put inc %p ref %d\n", inc, refcount_read(&inc->i_refcount));
if (refcount_dec_and_test(&inc->i_refcount)) {
+ struct rds_connection *conn = inc->i_conn;
+
BUG_ON(!list_empty(&inc->i_item));
- inc->i_conn->c_trans->inc_free(inc);
+ /* inc_free() can free the memory @inc lives in, so the
+ * connection reference has to be dropped through the
+ * copy taken above.
+ */
+ conn->c_trans->inc_free(inc);
+ /* get in rds_inc_init(), rds_inc_path_init() or
+ * rds_recv_incoming()
+ */
+ rds_conn_put(conn);
}
}
EXPORT_SYMBOL_GPL(rds_inc_put);
@@ -325,6 +337,13 @@ void rds_recv_incoming(struct rds_connection *conn, struct in6_addr *saddr,
unsigned long flags;
struct rds_conn_path *cp;
+ /* every caller initialized @inc with rds_inc_init() or
+ * rds_inc_path_init() first, so i_conn already holds a reference.
+ * Take the new one before dropping the old, so that re-pointing an
+ * inc at the connection it already refers to cannot free it.
+ */
+ rds_conn_get(conn);
+ rds_conn_put(inc->i_conn);
inc->i_conn = conn;
inc->i_rx_jiffies = jiffies;
if (conn->c_trans->t_mp_capable)
@@ -406,8 +425,9 @@ void rds_recv_incoming(struct rds_connection *conn, struct in6_addr *saddr,
* rds_find_bound() uses a global (netns-agnostic) hash table.
* An RDS connection created in netns A can match a socket bound
* in the init netns, delivering inc cross-netns with inc->i_conn
- * pointing into netns A. When cleanup_net() then frees that conn,
- * any subsequent dereference of inc->i_conn is a use-after-free.
+ * pointing into netns A. Cross-netns delivery is wrong on its
+ * own, and the inc's connection reference would keep a
+ * connection of a dead netns around, with a stale c_net.
* Drop the inc if the receiving socket lives in a different netns.
*/
if (!net_eq(sock_net(rds_rs_to_sk(rs)), rds_conn_net(conn))) {
diff --git a/net/rds/send.c b/net/rds/send.c
index f7bc4c5446d6..afb22779ff97 100644
--- a/net/rds/send.c
+++ b/net/rds/send.c
@@ -290,6 +290,8 @@ int rds_send_xmit(struct rds_conn_path *cp)
}
rm->data.op_active = 1;
rm->m_inc.i_conn_path = cp;
+ /* put in rds_message_put() */
+ rds_conn_get(cp->cp_conn);
rm->m_inc.i_conn = cp->cp_conn;
cp->cp_xmit_rm = rm;
@@ -960,6 +962,7 @@ static int rds_send_queue_rm(struct rds_sock *rs, struct rds_connection *conn,
/* The code ordering is a little weird, but we're
trying to minimize the time we hold c_lock */
rds_message_populate_header(&rm->m_inc.i_hdr, sport, dport, 0);
+ rds_conn_get(conn); /* put in rds_message_put() */
rm->m_inc.i_conn = conn;
rm->m_inc.i_conn_path = cp;
rds_message_addref(rm);
@@ -1596,6 +1599,7 @@ rds_send_probe(struct rds_conn_path *cp, __be16 sport,
list_add_tail(&rm->m_conn_item, &cp->cp_send_queue);
set_bit(RDS_MSG_ON_CONN, &rm->m_flags);
rds_message_addref(rm);
+ rds_conn_get(cp->cp_conn); /* put in rds_message_put() */
rm->m_inc.i_conn = cp->cp_conn;
rm->m_inc.i_conn_path = cp;
--
2.25.1
^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 02/12] net/rds: free every path's transport data on the passive create paths
2026-09-19 6:11 ` [PATCH net-next v5 02/12] net/rds: free every path's transport data on the passive create paths Allison Henderson
@ 2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-20 6:12 UTC (permalink / raw)
To: Allison Henderson; +Cc: linux-rdma
> trans->conn_alloc() may allocate transport data for every path of a
> multipath connection - rds_tcp_conn_alloc() does - which is why the
> lost-creation-race exit of __rds_conn_create() loops over all npaths
> when it frees the connection it just built. The passive-connection
> exit right above it, taken when a loopback parent already has its
> c_passive twin, frees only path 0 and leaks the transport data of
> paths 1..npaths-1. RDS/TCP loopback is exactly a multipath passive
> connection, so this is reachable.
>
> Move the loop into a helper and use it on both exits.
>
> Fixes: 1c5113cf796b ("RDS: Initialize all RDS_MPATH_WORKERS in __rds_conn_create")
> Assisted-by: Claude-Code:claude-fable-5
> Signed-off-by: Allison Henderson <achender@kernel.org>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260919061149.250658-1-achender@kernel.org?part=2
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 07/12] net/rds: unlink transport nodes before a possibly deferred connection free
2026-09-19 6:11 ` [PATCH net-next v5 07/12] net/rds: unlink transport nodes before a possibly deferred connection free Allison Henderson
@ 2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-20 6:12 UTC (permalink / raw)
To: Allison Henderson; +Cc: linux-rdma
> The transport teardown helpers - rds_tcp_destroy_conns(),
> rds_tcp_kill_sock(), rds_ib_destroy_nodev_conns(), rds_loop_exit() and
> rds_loop_kill_conns() - gather the per-connection transport nodes onto
> a list head on their own stack and call rds_conn_destroy() for each.
> The node is unlinked much later, by the transport's conn_free():
> rds_tcp_conn_free() and rds_loop_conn_free() list_del() it, and
> rds_ib_conn_free() does so unconditionally.
>
> That is fine for as long as rds_conn_destroy() frees the connection
> before it returns, which is still the case at this point in the
> series: the initial reference is the only one. The following patches
> hand out references that outlive the teardown loop - a socket's
> cached rs_conn, an inc parked on a receive queue - and with those, a
> conn_free() deferred until after the helper has returned would
> list_del() the node from a stack frame that no longer exists. Make
> [ ... ]
> what keeps rds_conn_destroy() from being called with the transport
> lock held.
>
> Assisted-by: Claude-Code:claude-fable-5
> Signed-off-by: Allison Henderson <achender@kernel.org>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260919061149.250658-1-achender@kernel.org?part=7
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 03/12] net/rds: guard every work-requeueing site with rds_destroy_pending()
2026-09-19 6:11 ` [PATCH net-next v5 03/12] net/rds: guard every work-requeueing site with rds_destroy_pending() Allison Henderson
@ 2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-20 6:12 UTC (permalink / raw)
To: Allison Henderson; +Cc: linux-rdma
> 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
> [ ... ]
> single connection.
>
> 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>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260919061149.250658-1-achender@kernel.org?part=3
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 01/12] net/rds: ib: don't enable interrupts in rds_ib_conn_free()
2026-09-19 6:11 ` [PATCH net-next v5 01/12] net/rds: ib: don't enable interrupts in rds_ib_conn_free() Allison Henderson
@ 2026-09-20 6:12 ` sashiko-bot
0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-20 6:12 UTC (permalink / raw)
To: Allison Henderson; +Cc: linux-rdma
> rds_ib_conn_free() unlinks the connection from its device or nodev
> list under spin_lock_irq()/spin_unlock_irq(). It is not only called
> from the rmmod path, though: __rds_conn_create() calls
> trans->conn_free() to undo a lost creation race while it still holds
> rds_conn_lock, taken with spin_lock_irqsave(). The unconditional
> spin_unlock_irq() then re-enables interrupts with rds_conn_lock held
> and leaves them enabled when the caller's spin_unlock_irqrestore()
> runs, defeating the irqsave the caller relied on.
>
> Use the irqsave/irqrestore pair, as rds_tcp_conn_free() and
> rds_loop_conn_free() already do.
>
> Fixes: 745cbccac3fe ("RDS: Rewrite connection cleanup, fixing oops on rmmod")
> Assisted-by: Claude-Code:claude-fable-5
> Signed-off-by: Allison Henderson <achender@kernel.org>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260919061149.250658-1-achender@kernel.org?part=1
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 05/12] net/rds: split connection destroy into quiesce and kref-governed free
2026-09-19 6:11 ` [PATCH net-next v5 05/12] net/rds: split connection destroy into quiesce and kref-governed free Allison Henderson
@ 2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-20 6:12 UTC (permalink / raw)
To: Allison Henderson; +Cc: linux-rdma
> From: Sharath Srinivasan <sharath.srinivasan@oracle.com>
>
> rds_conn_destroy() tears down the transport state and immediately
> frees the connection, along with its paths and its workqueues. This
> relies on the assumption (documented in the rds_conn_destroy()
> comments) that "no one else is referencing the connection", which "we
> can only ensure ... in the rmmod path". However, the callers stopped
> honoring that long ago. Today, connections are also destroyed on
> network namespace teardown (rds_tcp_kill_sock() and
> rds_loop_kill_conns()) and, one at a time, when a peer negotiates an
> unsupported protocol version (rds_ib_cm_connect_complete()).
>
> This leaves loose ends since references to these destroyed
> connections still exist. Sockets cache their connections in rs_conn,
> and congestion updates will walk the maps' m_conn_list. The CM
> [ ... ]
> infrastructure, no rds_net, single conn hash); destroy keeps its
> one-call external interface; holder coverage split out into follow-up
> patches; rewrite commit message]
> Assisted-by: Claude-Code:claude-fable-5
> Signed-off-by: Allison Henderson <achender@kernel.org>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260919061149.250658-1-achender@kernel.org?part=5
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 04/12] net/rds: make rds_destroy_pending() cover single-connection destroy
2026-09-19 6:11 ` [PATCH net-next v5 04/12] net/rds: make rds_destroy_pending() cover single-connection destroy Allison Henderson
@ 2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-20 6:12 UTC (permalink / raw)
To: Allison Henderson; +Cc: linux-rdma
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] rds: unnecessary synchronization logic based on false premise
--
commit 55cca7f8de5c5dd35f087d9e3e677bccea8d4c72
Author: Allison Henderson <achender@kernel.org>
net/rds: make rds_destroy_pending() cover single-connection destroy
The patch introduces a c_destroy_in_prog flag to synchronize connection
teardown. It assumes rds_ib_cm_connect_complete() destroys single connections
and aims to prevent requeueing work on a connection actively being destroyed
outside of global teardown events.
> rds_conn_destroy() is not limited to the global paths:
> rds_ib_cm_connect_complete() destroys a single connection whose peer
> negotiated an unsupported protocol version.
[Severity: Medium]
Does rds_ib_cm_connect_complete() actually destroy the connection?
Code review shows rds_ib_cm_connect_complete() upstream calls rds_conn_drop(),
which merely transitions the connection state and schedules a reconnect, but does
not destroy the connection object:
net/rds/ib_cm.c:rds_ib_cm_connect_complete() {
...
if (conn->c_version < RDS_PROTOCOL_VERSION) {
if (conn->c_version != RDS_PROTOCOL_COMPAT_VERSION) {
pr_notice("RDS/IB: Connection <%pI6c,%pI6c> version %u.%u no longer supported\n",
&conn->c_laddr, &conn->c_faddr,
RDS_PROTOCOL_MAJOR(conn->c_version),
RDS_PROTOCOL_MINOR(conn->c_version));
rds_conn_drop(conn);
return;
}
}
...
}
rds_conn_destroy() is exclusively called during global teardown
(module unload or netns destruction).
During global teardown, rds_destroy_pending() already evaluates to true
via !check_net() or t_unloading().
Is the stated premise invalid because the bug it fixes only existed in the Oracle
UEK tree (where single-connection destruction was present), making the ported
flag redundant for its stated purpose?
> diff --git a/net/rds/connection.c b/net/rds/connection.c
> index a96569a3ee9ad..242ca0570a47e 100644
> --- a/net/rds/connection.c
> +++ b/net/rds/connection.c
[ ... ]
> @@ -579,6 +579,14 @@ void rds_conn_destroy(struct rds_connection *conn)
> "%pI4\n", conn, &conn->c_laddr,
> &conn->c_faddr);
>
> + /* Make rds_destroy_pending() true for this conn. Together with
> + * the synchronize_rcu() below this stops the work-requeueing
> + * sites (which all test rds_destroy_pending() under
> + * rcu_read_lock()) from queueing new work on the path
> + * workqueues once we start cancelling and destroying them.
> + */
> + WRITE_ONCE(conn->c_destroy_in_prog, true);
> +
[Severity: Medium]
Is this synchronization flag actually necessary here?
Since rds_conn_destroy() is only called during module or netns teardown,
it appears rds_destroy_pending() already provides full coverage via
!check_net() and t_unloading() without needing this new c_destroy_in_prog flag.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260919061149.250658-1-achender@kernel.org?part=4
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 06/12] net/rds: wait for connections to be freed on transport unload
2026-09-19 6:11 ` [PATCH net-next v5 06/12] net/rds: wait for connections to be freed on transport unload Allison Henderson
@ 2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-20 6:12 UTC (permalink / raw)
To: Allison Henderson; +Cc: linux-rdma
> From: Sharath Srinivasan <sharath.srinivasan@oracle.com>
>
> Since connection free became asynchronous, rds_conn_destroy() only
> quiesces the connection; the actual free - including the transport's
> conn_free, which lives in the transport module - runs when the last
> reference is dropped. The transports' exit paths destroy all of
> their connections and then proceed to unload, so a free that is still
> pending (a racing lookup-style holder, or simply the destroyer's own
> put not yet run when destroy was invoked from another context earlier)
> would execute transport module code after that module's text is gone.
>
> Count each transport's live connections in t_conn_count (incremented
> when a connection is published in __rds_conn_create(), decremented as
> the last step of rds_conn_destroy_fini()) and make the transport exit
> paths - rds_ib_exit(), rds_tcp_exit() and rds_loop_exit() - wait for
> [ ... ]
> place of UEK's wait_event_timeout() + WARN_ON(), plus the resweep for
> IB's asynchronous device detach; also cover rds_loop_exit(); rewrite
> commit message]
> Assisted-by: Claude-Code:claude-fable-5
> Signed-off-by: Allison Henderson <achender@kernel.org>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260919061149.250658-1-achender@kernel.org?part=6
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 08/12] net/rds: hold connection references in lookup, sockets and c_passive
2026-09-19 6:11 ` [PATCH net-next v5 08/12] net/rds: hold connection references in lookup, sockets and c_passive Allison Henderson
@ 2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-20 6:12 UTC (permalink / raw)
To: Allison Henderson; +Cc: linux-rdma
> From: Sharath Srinivasan <sharath.srinivasan@oracle.com>
>
> Hand out real references everywhere a struct rds_connection pointer
> previously escaped bare:
>
> - rds_conn_lookup() takes a reference on the connection it returns
> (kref_get_unless_zero(), so that it only ever hands out a live
> reference), and
> __rds_conn_create() returns the connection with a reference held
> for the caller on every path: lookup hit, fresh creation, lost
> creation race, and the passive-loopback lookup, which now also
> holds the parent while it dereferences parent->c_passive.
>
> - The rs->rs_conn sendmsg cache owns a reference, which is dropped
> when the cache is replaced or the socket is released.
> [ ... ]
> them synchronously before the final put), and the sendmsg cache is
> serialized with rs_lock instead of UEK's socket flag; rewrite commit
> message]
> Assisted-by: Claude-Code:claude-fable-5
> Signed-off-by: Allison Henderson <achender@kernel.org>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260919061149.250658-1-achender@kernel.org?part=8
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 09/12] net/rds: refuse to queue on a connection being destroyed
2026-09-19 6:11 ` [PATCH net-next v5 09/12] net/rds: refuse to queue on a connection being destroyed Allison Henderson
@ 2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-20 6:12 UTC (permalink / raw)
To: Allison Henderson; +Cc: linux-rdma
> rds_conn_path_quiesce() tears down cp_send_queue by walking it with no
> lock held. That was tolerable while a connection could only be
> destroyed with no sender in flight, but a sender now holds a
> reference across rds_sendmsg(), and rds_conn_destroy() can quiesce
> the connection underneath it. rds_send_queue_rm() adds to
> cp_send_queue under cp_lock, so the unlocked walk races the add and
> can corrupt the list. Worse, a message added after the purge sits on
> the queue of a quiesced connection holding the connection reference
> rds_send_queue_rm() took for it: the reference is only dropped when
> the message is freed, the message is only freed when the queue is
> torn down, and the queue is only torn down by the destroy that has
> already run. The connection would never be freed, and with it the
> transport could never unload.
>
> Splice the queue away under cp_lock in the quiesce, and have
> rds_send_queue_rm() test rds_destroy_pending() under that same lock
> before it touches either queue. rds_send_probe() adds to
> cp_send_queue under cp_lock as well, for pings and pongs, and gets the
> same test: a probe queued after the purge would pin the connection
> just the same. A sender that gets there first has
> its message purged; one that gets there second is refused, and
> rds_sendmsg() returns -EAGAIN for it, the same result the early
> rds_destroy_pending() check in rds_sendmsg() already produces for a
> connection whose destroy had begun before the send started.
> rds_send_queue_rm()'s *queued becomes negative on refusal so that the
> wait loop in rds_sendmsg() stops waiting for send room that will never
> come.
>
> Assisted-by: Claude-Code:claude-fable-5
> Signed-off-by: Allison Henderson <achender@kernel.org>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260919061149.250658-1-achender@kernel.org?part=9
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 10/12] net/rds: pin the connection across RDMA-CM event handling
2026-09-19 6:11 ` [PATCH net-next v5 10/12] net/rds: pin the connection across RDMA-CM event handling Allison Henderson
@ 2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-20 6:12 UTC (permalink / raw)
To: Allison Henderson; +Cc: linux-rdma
> rds_rdma_cm_event_handler_cmn() picks the connection up from
> cm_id->context, which carries no reference, and holds c_cm_lock - a
> mutex that lives in the connection's path array - across the transport
> callbacks. Before this series that was already a use-after-free
> whenever a callback destroyed the connection, since rds_conn_destroy()
> freed it synchronously and the handler's mutex_unlock() ran on freed
> memory; the one such callback, rds_ib_cm_connect_complete() on a
> protocol version below 3.1, has meanwhile been switched to
> rds_conn_drop() by commit f97d8c7bab78 ("rds: ib: use rds_conn_drop()
> on protocol version mismatch"), which also removed the deadlock that
> destroy took on c_cm_lock.
>
> Now that a connection is freed by its last reference, the remaining
> exposure is a callback that drops the last reference other than the
> handler's - which has none - and the reference handed out by
> [ ... ]
> away, and nothing would tear them down again. Re-check
> rds_destroy_pending() under c_cm_lock and reject the request instead.
>
> Assisted-by: Claude-Code:claude-fable-5
> Signed-off-by: Allison Henderson <achender@kernel.org>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260919061149.250658-1-achender@kernel.org?part=10
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 11/12] net/rds: drop rds_conn_count in favor of t_conn_count
2026-09-19 6:11 ` [PATCH net-next v5 11/12] net/rds: drop rds_conn_count in favor of t_conn_count Allison Henderson
@ 2026-09-20 6:12 ` sashiko-bot
0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-20 6:12 UTC (permalink / raw)
To: Allison Henderson; +Cc: linux-rdma
> The previous patch gave each transport its own connection count in
> t_conn_count, incremented and decremented at exactly the points where
> the global rds_conn_count is. That leaves rds_conn_count with a
> single remaining consumer: the seed of the per-path workqueue names
> in __rds_conn_create().
>
> Switch the name seed to t_conn_count, as UEK does, and remove
> rds_conn_count. The numbering becomes per-transport instead of
> global, so connections of different transports can now receive the
> same seed; workqueue names carry no uniqueness requirement, and the
> seed was already reused as the count rose and fell. Removing the
> counter also removes the rds_conn_lock round-trip that
> rds_conn_destroy_fini() took solely to decrement it. (The free path
> still sleeps in destroy_workqueue(), so the last reference has to be
> dropped from process context as before; only that one lock goes.)
>
> Based on the Oracle UEK commit "net/rds: Each RDS transport
> should keep its own connection count".
>
> Assisted-by: Claude-Code:claude-fable-5
> Signed-off-by: Allison Henderson <achender@kernel.org>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260919061149.250658-1-achender@kernel.org?part=11
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 12/12] net/rds: hold a connection reference from struct rds_incoming
2026-09-19 6:11 ` [PATCH net-next v5 12/12] net/rds: hold a connection reference from struct rds_incoming Allison Henderson
@ 2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-20 6:12 UTC (permalink / raw)
To: Allison Henderson; +Cc: linux-rdma
> From: Sharath Srinivasan <sharath.srinivasan@oracle.com>
>
> struct rds_incoming->i_conn stores a pointer to the connection a message
> it belongs to, for both received messages and messages the socket sends.
> But without taking a reference, nothing keeps that connection alive.
> Embedded as the messages m_inc, an inc routinely outlives the connection
> it points at, by sitting in the socket's receive queue until the
> application reads it, while the connection is destroyed by device removal,
> netns teardown or module unload - and every dereference of i_conn after
> that point touches freed memory.
>
> Chengfeng Ye reported one way to reach it, where the socket info
> callbacks walk a receive queue after rmmod freed the connections:
>
> BUG: KASAN: slab-use-after-free in rds6_inc_info_copy+0x459/0x530 [rds]
> [ ... ]
> m_inc reference is dropped from a shared rds_message_free() helper
> that both paths call; rds_recv_incoming() takes the new reference
> before dropping the old; rewrite commit message]
> Assisted-by: Claude-Code:claude-opus-5
> Signed-off-by: Allison Henderson <achender@kernel.org>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260919061149.250658-1-achender@kernel.org?part=12
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 02/12] net/rds: free every path's transport data on the passive create paths
2026-09-19 6:11 ` [PATCH net-next v5 02/12] net/rds: free every path's transport data on the passive create paths Allison Henderson
2026-09-20 6:12 ` sashiko-bot
@ 2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: netdev-bot+sashiko @ 2026-09-23 7:11 UTC (permalink / raw)
To: achender; +Cc: netdev, linux-rdma, pabeni, edumazet, kuba, horms, nicoyip.dev
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 0 (1) · Medium: 1 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] Commit message misstates reachability: the passive-create exit
changed by this patch can never run with npaths > 1, so the advertised…
Pre-existing issues:
- [High] Per-path workqueue leak from the create-vs-destroy npaths
asymmetry that this patch generalizes on the create side but leaves…
--
net/rds: free every path's transport data on the passive create paths
This moves the per-path cp_transport_data free loop from the
lost-creation-race exit of __rds_conn_create() into a new helper,
rds_conn_free_transport_data(), and calls that helper from the
passive-connection exit as well as the race exit.
> diff --git a/net/rds/connection.c b/net/rds/connection.c
> index b6c4beb50eaf..a96569a3ee9a 100644
> --- a/net/rds/connection.c
> +++ b/net/rds/connection.c
> @@ -161,6 +161,22 @@ static void __rds_conn_path_init(struct rds_connection *conn,
> cp->cp_flags = 0;
> }
>
> +/* Undo trans->conn_alloc(): it may have allocated transport data for
> + * every path of a multipath connection, not just for path 0.
> + */
> +static void rds_conn_free_transport_data(struct rds_connection *conn,
> + int npaths)
> +{
> + struct rds_conn_path *cp;
> + int i;
> +
> + for (i = 0; i < npaths; i++) {
> + cp = &conn->c_path[i];
> + if (cp->cp_transport_data)
> + conn->c_trans->conn_free(cp->cp_transport_data);
> + }
> +}
> +
[Severity: High]
This isn't a bug introduced by this patch, but while looking at the
create-side npaths handling that this helper generalizes, the teardown side
looks asymmetric for the same reason.
__rds_conn_create() computes npaths from the caller's transport, before
trans can be swapped:
int npaths = (trans->t_mp_capable ? RDS_MPATH_WORKERS : 1);
...
if (trans->t_prefer_loopback) {
if (likely(is_outgoing)) {
trans = &rds_loop_transport;
...
conn->c_trans = trans;
for (i = 0; i < npaths; i++) {
...
conn->c_path[i].cp_wq =
alloc_ordered_workqueue("krds_cp_wq#%d/%d", 0, seq, i);
So a conn created with trans == rds_tcp_transport (t_mp_capable, npaths ==
RDS_MPATH_WORKERS) but whose c_trans ends up as rds_loop_transport has a
workqueue on every one of the 8 paths.
rds_conn_destroy() then recomputes the bound from the stored transport:
int npaths = (conn->c_trans->t_mp_capable ? RDS_MPATH_WORKERS : 1);
which is 1 for rds_loop_transport, so c_path[1..7] are never passed to
rds_conn_path_destroy() before kfree(conn->c_path). Does this leak the
ordered workqueues allocated for paths 1..7 on every loopback connection
teardown, for example when rds_loop_kill_conns() runs on netns exit?
There also looks to be a second obstacle even if the destroy bound were
widened, since rds_conn_path_destroy() returns before it reaches the
workqueue release:
static void rds_conn_path_destroy(struct rds_conn_path *cp)
{
struct rds_message *rm, *rtmp;
if (!cp->cp_transport_data)
return;
...
if (cp->cp_wq != rds_wq)
destroy_workqueue(cp->cp_wq);
rds_loop_conn_alloc() only sets c_path[0].cp_transport_data (through the
c_transport_data macro), so paths 1..7 hold a workqueue and no transport
data. Should that early return move below the cp_wq teardown?
The same pattern appears later in the series, in rds_conn_destroy_fini()
and rds_conn_path_free() at 9157f3187071.
> /*
> * There is only every one 'conn' for a given pair of addresses in the
> * system at a time. They contain messages to be retransmitted and so
> @@ -316,7 +332,7 @@ static struct rds_connection *__rds_conn_create(struct net *net,
> if (parent) {
> /* Creating passive conn */
> if (parent->c_passive) {
> - trans->conn_free(conn->c_path[0].cp_transport_data);
> + rds_conn_free_transport_data(conn, npaths);
[Severity: Medium]
Can npaths ever be greater than 1 at this exit?
The commit message says:
The passive-connection exit right above it, taken when a loopback
parent already has its c_passive twin, frees only path 0 and leaks
the transport data of paths 1..npaths-1. RDS/TCP loopback is
exactly a multipath passive connection, so this is reachable.
npaths comes only from the caller's transport:
int npaths = (trans->t_mp_capable ? RDS_MPATH_WORKERS : 1);
and t_mp_capable = 1 is set only by rds_tcp_transport in net/rds/tcp.c.
parent is set only here:
conn = rds_conn_lookup(net, head, laddr, faddr, trans, tos, dev_if);
if (conn &&
conn->c_loopback &&
conn->c_trans != &rds_loop_transport &&
ipv6_addr_equal(laddr, faddr) &&
!is_outgoing) {
parent = conn;
rds_conn_lookup() additionally requires conn->c_trans == trans, so the
parent's transport is the same transport that produced npaths.
c_loopback = 1 is assigned in exactly one place, and since
rds_tcp_transport sets t_prefer_loopback = 1 that block either swaps trans
for &rds_loop_transport (is_outgoing), which the parent test excludes and
which a lookup with trans == tcp cannot match, or bails out with
-EOPNOTSUPP (!is_outgoing) before any conn is installed:
conn->c_loopback = 1;
if (trans->t_prefer_loopback) {
if (likely(is_outgoing)) {
trans = &rds_loop_transport;
} else {
...
conn = ERR_PTR(-EOPNOTSUPP);
That seems to leave only a non-prefer-loopback transport (IB, which does not
set t_mp_capable) able to reach the parent branch, i.e. npaths == 1, where
the previous single free of c_path[0].cp_transport_data already covered
everything.
The in-tree incoming TCP path also rejects a local peer before
rds_conn_create() is reached:
net/rds/tcp_listen.c:rds_tcp_accept_one() {
if (!rds_tcp_laddr_check(sock_net(listen_sock->sk), peer_addr, dev_if)) {
/* local address connection is only allowed via loopback */
ret = -EOPNOTSUPP;
If that analysis holds, the new loop is a no-op on the reachable passive
path. Should the changelog and the Fixes: 1c5113cf796b tag be adjusted so
this is described as consolidation rather than a memory leak fix, to avoid
an unnecessary stable backport?
> free_cp = conn->c_path;
> kmem_cache_free(rds_conn_slab, conn);
> conn = parent->c_passive;
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260919061149.250658-1-achender%40kernel.org
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 03/12] net/rds: guard every work-requeueing site with rds_destroy_pending()
2026-09-19 6:11 ` [PATCH net-next v5 03/12] net/rds: guard every work-requeueing site with rds_destroy_pending() Allison Henderson
2026-09-20 6:12 ` sashiko-bot
@ 2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: netdev-bot+sashiko @ 2026-09-23 7:11 UTC (permalink / raw)
To: achender; +Cc: netdev, linux-rdma, pabeni, edumazet, kuba, horms, nicoyip.dev
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] Commit-message precision only (no reachable bug at this SHA): the
opening paragraph of "net/rds: guard every work-requeueing site with…
--
net/rds: guard every work-requeueing site with rds_destroy_pending()
This adds the rcu_read_lock() + rds_destroy_pending() guard to five sites
that re-arm path works: the two IB send paths, the IB recv refill, the TCP
accept path and the multipath reconnect in rds_sendmsg(). The four
self-requeues in rds_send_worker() and rds_recv_worker() are deliberately
left unguarded.
The code changes themselves look fine to me; my only question is about the
changelog, so the diff hunks are snipped.
> net/rds: guard every work-requeueing site with rds_destroy_pending()
>
> 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.
[Severity: Low]
Is "paired with the synchronize_rcu() in the destroy path" the right
attribution at this point in the series?
At this commit the predicate has no per-connection destroy term:
net/rds/rds.h:
static inline bool rds_destroy_pending(struct rds_connection *conn)
{
return !check_net(rds_conn_net(conn)) ||
(conn->c_trans->t_unloading && conn->c_trans->t_unloading(conn));
}
The only per-path destroy bit the IB hook consults is RDS_DESTROY_PENDING:
net/rds/ib.c:rds_ib_is_unloading()
return (test_bit(RDS_DESTROY_PENDING, &cp->cp_flags) ||
atomic_read(&rds_ib_unloading) != 0);
and that bit is defined in rds.h but never set anywhere in net/rds, so the
IB hook reduces to "the module is unloading".
The synchronize_rcu() inside rds_conn_destroy() publishes only the
hlist_del_init_rcu() of c_hash_node:
net/rds/connection.c:rds_conn_destroy()
spin_lock_irq(&rds_conn_lock);
hlist_del_init_rcu(&conn->c_hash_node);
spin_unlock_irq(&rds_conn_lock);
synchronize_rcu();
rds_destroy_pending() does not read c_hash_node, so that grace period is
not what makes the new guards fire. The grace periods that do are in the
callers: rds_ib_exit() and rds_tcp_exit() call their set_unloading()
helpers followed by synchronize_rcu(), and cleanup_net() is what makes
check_net() false for the netns-teardown callers.
The last paragraph of the changelog does disclose the limitation:
> With the predicate as it stands the guards cover the netns teardown and
> module unload cases; the following patch extends it to the destroy of a
> single connection.
and the following patch in the series does close the gap: rds.h grows
READ_ONCE(conn->c_destroy_in_prog) in the predicate, rds_conn_destroy()
does WRITE_ONCE(conn->c_destroy_in_prog, true) before hlist_del_init_rcu()
and synchronize_rcu(), RDS_DESTROY_PENDING is removed, and all five guards
added here survive.
Walking the rds_conn_destroy() callers at this commit, none of them runs
with the netns alive and the module loaded: rds_ib_destroy_nodev_conns()
is reached only from rds_ib_exit(), rds_tcp_destroy_conns() and
rds_tcp_kill_sock() only from rds_tcp_exit() and rds_tcp_exit_net(),
rds_loop_kill_conns() likewise, and ib_cm.c no longer calls it. So no
crash path is reachable here and no code change seems needed - would it be
worth rewording the first paragraph to credit the unload and netns grace
periods rather than rds_conn_destroy()'s own synchronize_rcu()?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260919061149.250658-1-achender%40kernel.org
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 04/12] net/rds: make rds_destroy_pending() cover single-connection destroy
2026-09-19 6:11 ` [PATCH net-next v5 04/12] net/rds: make rds_destroy_pending() cover single-connection destroy Allison Henderson
2026-09-20 6:12 ` sashiko-bot
@ 2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: netdev-bot+sashiko @ 2026-09-23 7:11 UTC (permalink / raw)
To: achender; +Cc: netdev, linux-rdma, pabeni, edumazet, kuba, horms, nicoyip.dev
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] Commit message justification does not exist in this tree: the
message states "rds_ib_cm_connect_complete() destroys a single…
- [Low] The two comments added by this patch state an unconditional
contract that three in-tree arming sites do not honour.
--
net/rds: make rds_destroy_pending() cover single-connection destroy
This adds a conn->c_destroy_in_prog flag, set by rds_conn_destroy() before
the unhash + synchronize_rcu() sequence, and tests it in
rds_destroy_pending() so that the work-arming sites stop queueing on the
path workqueues while a single connection is being torn down. The dead
RDS_DESTROY_PENDING cp_flags bit and its remaining test in
rds_ib_is_unloading() are removed.
Two comments on the changelog and the new code comments below.
> rds_conn_destroy() is not limited to the global paths:
> rds_ib_cm_connect_complete() destroys a single connection whose peer
> negotiated an unsupported protocol version. (The other per-transport
> caller, rds_ib_destroy_nodev_conns(), is only reached from
> rds_ib_exit(), where ->t_unloading already covers it.) While that
> destroy runs, a concurrent rds_cong_queue_updates() can
> still find the connection on the congestion map's m_conn_list (the
> conn is only removed from it after the paths are torn down) and call
> queue_delayed_work() on a cp_wq that destroy_workqueue() has already
> freed.
[Severity: Medium]
Does rds_ib_cm_connect_complete() still call rds_conn_destroy()? At the
baseline (8830e65) and at this commit, the version-mismatch arm in
net/rds/ib_cm.c reads:
if (conn->c_version < RDS_PROTOCOL_VERSION) {
if (conn->c_version != RDS_PROTOCOL_COMPAT_VERSION) {
pr_notice("RDS/IB: Connection <%pI6c,%pI6c> version %u.%u no longer supported\n",
...);
rds_conn_drop(conn);
return;
}
}
git grep rds_conn_destroy in net/rds/ matches only connection.c, rds.h,
ib_rdma.c, loop.c and tcp.c - ib_cm.c has no call at all. The later patch
in this series ("net/rds: pin the connection across RDMA-CM event
handling") also states that this path "has meanwhile been switched to
rds_conn_drop() by commit f97d8c7bab78".
If that is right, can the described rds_cong_queue_updates() race against a
freed cp_wq be reached at all through the named caller?
Looking at the five remaining callers, four already satisfy one of the two
old terms:
rds_ib_destroy_nodev_conns() - only from rds_ib_exit(), after
rds_ib_set_unloading()
rds_tcp_destroy_conns() - only from rds_tcp_exit(), after
rds_tcp_set_unloading()
rds_loop_exit() - after rds_loop_set_unloading()
rds_tcp_kill_sock() - from rds_tcp_exit_net(), where either
check_net() is false or ->t_unloading is
already set
The one caller that does appear to escape the old predicate is the loop
pernet exit, which the changelog does not mention:
rds_conn_exit()
rds_loop_net_exit() /* unregister_pernet_device() */
rds_loop_exit_net()
rds_loop_kill_conns()
rds_conn_destroy()
rds_loop_exit() /* sets the loop unloading flag */
rds_loop_kill_conns() runs for every live netns, so check_net() is still
true, and the unloading flag is only set afterwards by rds_loop_exit().
Given that, is the Fixes: cdc306a5c9cd3 ("rds: make v3.1 as compat
version") attribution correct? RDS_DESTROY_PENDING was never set on the
loop path, so removing its last set_bit does not look like what left this
caller unguarded. Would it make sense to re-point the changelog (and the
Fixes: tag, or drop it) at the rds_loop_net_exit() path instead, so that a
stable backport is not requested for the version-mismatch path?
> diff --git a/net/rds/connection.c b/net/rds/connection.c
> index a96569a3ee9a..242ca0570a47 100644
> --- a/net/rds/connection.c
> +++ b/net/rds/connection.c
> @@ -579,6 +579,14 @@ void rds_conn_destroy(struct rds_connection *conn)
> "%pI4\n", conn, &conn->c_laddr,
> &conn->c_faddr);
>
> + /* Make rds_destroy_pending() true for this conn. Together with
> + * the synchronize_rcu() below this stops the work-requeueing
> + * sites (which all test rds_destroy_pending() under
> + * rcu_read_lock()) from queueing new work on the path
> + * workqueues once we start cancelling and destroying them.
> + */
[Severity: Low]
This isn't a bug, but is the "which all test rds_destroy_pending() under
rcu_read_lock()" claim accurate? There are three arming sites in tree that
do not.
rds_send_worker() and rds_recv_worker() in net/rds/threads.c re-arm with a
bare queue:
case -EAGAIN:
rds_stats_inc(s_send_immediate_retry);
queue_delayed_work(cp->cp_wq, &cp->cp_send_w, 0);
break;
The previous patch's changelog explains that these self-requeues are
covered by the cancel_delayed_work_sync() calls in
rds_conn_path_destroy(), but nothing in the code says so.
rds_conn_path_drop() skips the predicate entirely when destroy is true:
rcu_read_lock();
if (!destroy && rds_destroy_pending(cp->cp_conn)) {
rcu_read_unlock();
return;
}
queue_work(cp->cp_wq, &cp->cp_down_w);
rcu_read_unlock();
That bypass is needed by rds_conn_path_destroy() itself, but it is also
used from outside the destroy path, by rds_ib_dev_shutdown() on IB device
removal:
list_for_each_entry(ic, &rds_ibdev->conn_list, ib_node)
rds_conn_path_drop(&ic->conn->c_path[0], true);
There, neither check_net(), ->t_unloading nor the new c_destroy_in_prog can
suppress the queue_work().
Could the comment be reworded to name the exceptions, so a later reader
does not conclude the new flag closes the whole window? The same wording
appears in the rds.h comment below.
> + WRITE_ONCE(conn->c_destroy_in_prog, true);
> +
> /* Ensure conn will not be scheduled for reconnect */
> spin_lock_irq(&rds_conn_lock);
> hlist_del_init_rcu(&conn->c_hash_node);
[ ... ]
> diff --git a/net/rds/rds.h b/net/rds/rds.h
> index 2db49573dacd..50b08c28ab86 100644
> --- a/net/rds/rds.h
> +++ b/net/rds/rds.h
> @@ -148,6 +147,14 @@ struct rds_connection {
> c_pad_to_32:29;
> int c_npaths;
> bool c_with_sport_idx;
> + /* Set once, by rds_conn_destroy(), before it cancels the path
> + * works; read through rds_destroy_pending(). A site that arms
> + * a path work must test the predicate and queue the work inside
> + * one rcu_read_lock() section: the synchronize_rcu() that
> + * follows the store is what keeps a queue issued after the
> + * cancellation from landing on a destroyed workqueue.
> + */
> + bool c_destroy_in_prog;
> struct rds_connection *c_passive;
> struct rds_transport *c_trans;
>
> @@ -994,7 +1001,8 @@ void __rds_put_mr_final(struct kref *kref);
>
> static inline bool rds_destroy_pending(struct rds_connection *conn)
> {
> - return !check_net(rds_conn_net(conn)) ||
> + return READ_ONCE(conn->c_destroy_in_prog) ||
> + !check_net(rds_conn_net(conn)) ||
> (conn->c_trans->t_unloading && conn->c_trans->t_unloading(conn));
> }
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260919061149.250658-1-achender%40kernel.org
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 05/12] net/rds: split connection destroy into quiesce and kref-governed free
2026-09-19 6:11 ` [PATCH net-next v5 05/12] net/rds: split connection destroy into quiesce and kref-governed free Allison Henderson
2026-09-20 6:12 ` sashiko-bot
@ 2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: netdev-bot+sashiko @ 2026-09-23 7:11 UTC (permalink / raw)
To: achender; +Cc: netdev, linux-rdma, pabeni, edumazet, kuba, horms, nicoyip.dev
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] Undisclosed behavioural change plus a comment whose premise is
false at this commit: rds_conn_destroy()…
- [Low] The commit message's motivating example is stale, and the stale
claim is copied into a new in-tree comment.
--
net/rds: split connection destroy into quiesce and kref-governed free
This adds a kref to struct rds_connection, keeps the synchronous quiesce
work in rds_conn_destroy() and moves the actual frees (transport
conn_free, path workqueues, c_path array, slab object) into
rds_conn_destroy_fini(), which runs on the last rds_conn_put(). It also
exports rds_conn_get()/rds_conn_put() and adds rds_conn_get_unless_zero().
A couple of questions about the new duplicate-destroy guard and the
descriptions around it.
> diff --git a/net/rds/connection.c b/net/rds/connection.c
> index 242ca0570a47..a44aa4d2a5e8 100644
> --- a/net/rds/connection.c
> +++ b/net/rds/connection.c
> @@ -231,6 +231,7 @@ static struct rds_connection *__rds_conn_create(struct net *net,
> goto out;
> }
>
> + kref_init(&conn->c_refcount);
> INIT_HLIST_NODE(&conn->c_hash_node);
> conn->c_laddr = *laddr;
> conn->c_isv6 = !ipv6_addr_v4mapped(laddr);
[ ... ]
> @@ -561,16 +575,52 @@ static void rds_conn_path_destroy(struct rds_conn_path *cp)
> cp->cp_conn->c_trans->conn_free(cp->cp_transport_data);
> }
>
> +/* Free a connection. This runs from rds_conn_put() when the last
> + * reference is dropped, after rds_conn_destroy() has quiesced the
> + * connection and dropped the initial reference.
> + */
> +static void rds_conn_destroy_fini(struct kref *kref)
> +{
> + struct rds_connection *conn = container_of(kref, struct rds_connection,
> + c_refcount);
> + int npaths = (conn->c_trans->t_mp_capable ? RDS_MPATH_WORKERS : 1);
> + unsigned long flags;
> + int i;
> +
> + for (i = 0; i < npaths; i++)
> + rds_conn_path_free(&conn->c_path[i]);
> +
> + kfree(conn->c_path);
> + kmem_cache_free(rds_conn_slab, conn);
> +
> + spin_lock_irqsave(&rds_conn_lock, flags);
> + rds_conn_count--;
> + spin_unlock_irqrestore(&rds_conn_lock, flags);
> +}
> +
> +void rds_conn_get(struct rds_connection *conn)
> +{
> + kref_get(&conn->c_refcount);
> +}
> +EXPORT_SYMBOL_GPL(rds_conn_get);
> +
> +void rds_conn_put(struct rds_connection *conn)
> +{
> + kref_put(&conn->c_refcount, rds_conn_destroy_fini);
> +}
> +EXPORT_SYMBOL_GPL(rds_conn_put);
> +
> /*
> * Stop and free a connection.
> *
> - * This can only be used in very limited circumstances. It assumes that once
> - * the conn has been shutdown that no one else is referencing the connection.
> - * We can only ensure this in the rmmod path in the current code.
> + * Quiesces the connection synchronously (workers cancelled, transport
> + * connections shut down, queued messages dropped) and drops the
> + * initial reference. The memory - including the transport's
> + * per-connection state and the path workqueues - is freed once the
> + * last rds_conn_put() runs, which may be after this returns.
> */
> void rds_conn_destroy(struct rds_connection *conn)
> {
> - unsigned long flags;
> int i;
> struct rds_conn_path *cp;
> int npaths = (conn->c_trans->t_mp_capable ? RDS_MPATH_WORKERS : 1);
> @@ -584,11 +634,23 @@ void rds_conn_destroy(struct rds_connection *conn)
> * sites (which all test rds_destroy_pending() under
> * rcu_read_lock()) from queueing new work on the path
> * workqueues once we start cancelling and destroying them.
> + *
> + * Now that the transport state stays discoverable (e.g. on the
> + * transports' connection lists) until the final rds_conn_put(),
> + * a conn can be handed to rds_conn_destroy() more than once -
> + * e.g. dropped for a protocol version mismatch and then found
> + * again at module unload. Only the first caller proceeds; the
> + * unhash also happens under rds_conn_lock, so a looked-up conn
> + * can never be quiesced twice.
> */
[Severity: Low]
Is the "dropped for a protocol version mismatch" example still accurate?
The changelog makes the same claim:
"Today, connections are also destroyed on network namespace teardown
(rds_tcp_kill_sock() and rds_loop_kill_conns()) and, one at a time,
when a peer negotiates an unsupported protocol version
(rds_ib_cm_connect_complete())."
In the current tree that branch of rds_ib_cm_connect_complete() only drops
the connection:
net/rds/ib_cm.c:rds_ib_cm_connect_complete() {
...
pr_notice("RDS/IB: Connection <%pI6c,%pI6c> version %u.%u no longer supported\n",
...);
rds_conn_drop(conn);
return;
...
}
and rds_conn_drop() -> rds_conn_path_drop(cp, false) only sets
RDS_CONN_ERROR and queues cp_down_w, so nothing is destroyed or freed
there. That was changed by commit f97d8c7bab78 ("rds: ib: use
rds_conn_drop() on protocol version mismatch"), which is already in the
baseline, and a later patch in this series ("net/rds: pin the connection
across RDMA-CM event handling") acknowledges it.
A grep for rds_conn_destroy() callers at this commit finds only
rds_ib_destroy_nodev_conns(), rds_tcp_destroy_conns(),
rds_tcp_kill_sock(), rds_loop_exit() and rds_loop_kill_conns(), all
module- or netns-teardown paths. Could the changelog and this new
comment be updated to drop the per-connection version-mismatch example?
> + spin_lock_irq(&rds_conn_lock);
> + if (conn->c_destroy_in_prog) {
> + spin_unlock_irq(&rds_conn_lock);
> + return;
> + }
> WRITE_ONCE(conn->c_destroy_in_prog, true);
[Severity: Medium]
This new early return changes the semantics of an EXPORT_SYMBOL_GPL
teardown interface into "first caller wins", but the changelog lists only
kref_init(), the destroy/fini split and the new exports, and says:
"rds_conn_destroy() still quiesces synchronously exactly as before"
"With no additional reference holders yet, this only sets up the
refcounting framework and is functionally equivalent to the current
code (the initial reference is the only one), so every free still
completes inside rds_conn_destroy()."
Would it be worth calling the guard out explicitly? A second caller now
returns without quiescing anything and without waiting for the first
caller's synchronize_rcu()/quiesce to finish, while the function comment
added just above and the new c_refcount comment in net/rds/rds.h both
describe the quiesce and the reference drop unconditionally.
The justification in the comment also seems to rest on a property this
patch does not yet provide. It says the conn "stays discoverable ...
until the final rds_conn_put()", but with the initial reference being the
only reference, the rds_conn_put() at the end of rds_conn_destroy() runs
rds_conn_destroy_fini() inline:
net/rds/connection.c:rds_conn_destroy_fini() {
...
kfree(conn->c_path);
kmem_cache_free(rds_conn_slab, conn);
...
}
So at this commit a hypothetical second rds_conn_destroy() on the same
pointer would read conn->c_destroy_in_prog out of freed slab memory
rather than be rejected by the guard. c_destroy_in_prog is written only
by rds_conn_destroy() itself, and all five in-tree callers unlink the
transport node under the transport lock before destroying, so the branch
looks like dead code here.
Would it make more sense to move the guard to the patch that actually
lets a conn outlive rds_conn_destroy() ("net/rds: unlink transport nodes
before a possibly deferred connection free" / "net/rds: hold connection
references in lookup, sockets and c_passive"), or otherwise to say in the
changelog and in the comment that it is forward-looking scaffolding and
inert at this point in the series?
>
> /* Ensure conn will not be scheduled for reconnect */
> - spin_lock_irq(&rds_conn_lock);
> hlist_del_init_rcu(&conn->c_hash_node);
> spin_unlock_irq(&rds_conn_lock);
> synchronize_rcu();
[ ... ]
> @@ -607,12 +669,10 @@ void rds_conn_destroy(struct rds_connection *conn)
> */
> rds_cong_remove_conn(conn);
>
> - kfree(conn->c_path);
> - kmem_cache_free(rds_conn_slab, conn);
> -
> - spin_lock_irqsave(&rds_conn_lock, flags);
> - rds_conn_count--;
> - spin_unlock_irqrestore(&rds_conn_lock, flags);
> + /* drop the initial reference; the connection is freed from
> + * rds_conn_destroy_fini() once every holder has dropped theirs
> + */
> + rds_conn_put(conn);
> }
> EXPORT_SYMBOL_GPL(rds_conn_destroy);
>
> diff --git a/net/rds/rds.h b/net/rds/rds.h
> index 50b08c28ab86..defda3ddefa3 100644
> --- a/net/rds/rds.h
> +++ b/net/rds/rds.h
> @@ -137,6 +137,12 @@ struct rds_conn_path {
> /* One rds_connection per RDS address pair */
> struct rds_connection {
> struct hlist_node c_hash_node;
> + /* rds_conn_destroy() quiesces the connection synchronously;
> + * freeing it - the connection memory, the path workqueues and
> + * the transport's per-connection state - is deferred until the
> + * last reference is dropped via rds_conn_put().
> + */
> + struct kref c_refcount;
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260919061149.250658-1-achender%40kernel.org
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 06/12] net/rds: wait for connections to be freed on transport unload
2026-09-19 6:11 ` [PATCH net-next v5 06/12] net/rds: wait for connections to be freed on transport unload Allison Henderson
2026-09-20 6:12 ` sashiko-bot
@ 2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: netdev-bot+sashiko @ 2026-09-23 7:11 UTC (permalink / raw)
To: achender; +Cc: netdev, linux-rdma, pabeni, edumazet, kuba, horms, nicoyip.dev
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 2
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] This patch makes rds_ib_destroy_nodev_conns() leave
ib_nodev_conns genuinely empty (list_splice_init(),…
- [Low] At this commit nothing makes a queued incoming datagram hold a
connection reference: rds_inc_init()/rds_inc_path_init()…
- [Low] The commit message justifies the new wait with reference holders
that do not exist at this commit and describes them in the present…
--
net/rds: wait for connections to be freed on transport unload
This adds a per-transport live connection count (t_conn_count), bumped when
a connection is published in __rds_conn_create() and dropped as the last
step of rds_conn_destroy_fini(), and makes rds_ib_exit(), rds_tcp_exit()
and rds_loop_exit() poll-wait for that count to reach zero before
unloading. rds_ib_exit() passes rds_ib_destroy_nodev_conns() as a resweep
callback so connections that migrate to ib_nodev_conns late still get
destroyed, and flushes rds_wq afterwards.
A couple of questions below, one on the commit message and two on the code.
[Severity: Low]
The commit message describes two reference holders in the present tense:
"so a free that is still pending (a racing lookup-style holder, or
simply the destroyer's own put not yet run when destroy was invoked
from another context earlier) would execute transport module code
after that module's text is gone."
and
"a CM event handler's reference is always dropped before the
rdma_destroy_id() in the connection's own shutdown returns"
Do either of those holders exist at this commit? At 130d1507 the kref is
initialized to 1 in __rds_conn_create(), and grepping net/rds finds no
caller of rds_conn_get() or rds_conn_get_unless_zero() at all - only the
definition in net/rds/connection.c and the declaration in net/rds/rds.h.
The lookup references arrive with "net/rds: hold connection references in
lookup, sockets and c_passive" and the CM handler reference with "net/rds:
pin the connection across RDMA-CM event handling", both later in the
series.
The inc dependency is already flagged as future work ("once the following
patches make incs hold a connection reference"), so would it be worth
qualifying these two the same way, since the wait is effectively a no-op at
this commit?
> diff --git a/net/rds/connection.c b/net/rds/connection.c
> index a44aa4d2a5e8..1d48da1a794f 100644
> --- a/net/rds/connection.c
> +++ b/net/rds/connection.c
[ ... ]
> @@ -596,7 +601,52 @@ static void rds_conn_destroy_fini(struct kref *kref)
> spin_lock_irqsave(&rds_conn_lock, flags);
> rds_conn_count--;
> spin_unlock_irqrestore(&rds_conn_lock, flags);
> +
> + /* only after everything the transport module owns has been
> + * freed above may its unload proceed
> + */
> + if (!atomic_dec_return(&trans->t_conn_count))
> + wake_up_all(&rds_conn_freed_waitq);
> +}
> +
> +/* Wait for all of @trans's connections to be freed; the free runs
> + * asynchronously once rds_conn_destroy() has quiesced a connection.
> + * Called on transport module unload, after the transport has destroyed
> + * all of its connections. A connection reference can be held for an
> + * application-controlled time - once incs hold one, an unread datagram
> + * pins the inc that carries it, and thus the connection - so the wait
> + * is unbounded: the
> + * frees that run after unload call into this module's text (conn_free,
> + * inc_free) and free into its slabs, so proceeding while any remain
> + * would be a use-after-free, not a leak. Warn periodically so a stuck
> + * count is diagnosable, but never stop waiting. This matches the
> + * historical RDS contract that teardown does not discard queued data.
> + */
[Severity: Low]
Does this comment describe a guarantee that is not yet in force at this
commit? It states that the wait covers inc_free and the transport's slabs,
but nothing here makes a queued incoming datagram hold a connection
reference yet.
net/rds/recv.c:rds_inc_path_init() only stores the pointer:
inc->i_conn = cp->cp_conn;
and rds_inc_put() dereferences it without a matching put:
inc->i_conn->c_trans->inc_free(inc);
So t_conn_count does not account for unread datagrams at this commit, and
rds_conn_wait_conns_freed() can return with incs still queued, after which
rds_tcp_exit()/rds_ib_exit() go on to rds_trans_unregister() and slab
teardown (rds_ib_recv_exit() even WARN_ONs on rds_ib_allocation and
destroys rds_ib_incoming_slab/rds_ib_frag_slab).
The gap is closed later in the series by "net/rds: hold a connection
reference from struct rds_incoming", which adds the rds_conn_get() in
rds_inc_init()/rds_inc_path_init() and the matching rds_conn_put() in
rds_inc_put(). Would it read better to phrase the inc part of this comment
the same way the commit message does, as something the following patches
establish?
Also, the line wrapping in the middle of that comment looks accidental:
* is unbounded: the
* frees that run after unload call into this module's text
> +void rds_conn_wait_conns_freed(struct rds_transport *trans,
> + void (*resweep)(void))
> +{
> + unsigned long warn_interval =
> + msecs_to_jiffies(RDS_CONN_FREE_WARN_INTERVAL_MS);
> + unsigned long warn_at = jiffies + warn_interval;
> +
> + while (!wait_event_timeout(rds_conn_freed_waitq,
> + !atomic_read(&trans->t_conn_count),
> + msecs_to_jiffies(RDS_CONN_FREE_POLL_MS))) {
> + /* A transport whose teardown is asynchronous (IB moves a
> + * connection off its device from the shutdown work) gives
> + * us a resweep to destroy what has arrived since.
> + */
> + if (resweep)
> + resweep();
[ ... ]
> diff --git a/net/rds/ib.c b/net/rds/ib.c
> index 9fe3b9951bd3..3fc2de9d19d5 100644
> --- a/net/rds/ib.c
> +++ b/net/rds/ib.c
> @@ -537,7 +537,24 @@ void rds_ib_exit(void)
> rds_info_deregister_func(RDS6_INFO_IB_CONNECTIONS, rds6_ib_ic_info);
> #endif
> rds_ib_unregister_client();
> +
> + /* rds_ib_dev_shutdown() only dropped the connections still
> + * attached to a device; each moves itself to ib_nodev_conns
> + * from its shutdown work. Destroy what is there now and keep
> + * sweeping the list while the wait sees connections outstanding,
> + * so a late arrival is destroyed rather than waited on forever.
> + */
> rds_ib_destroy_nodev_conns();
> + rds_conn_wait_conns_freed(&rds_ib_transport,
> + rds_ib_destroy_nodev_conns);
[ ... ]
> diff --git a/net/rds/ib_rdma.c b/net/rds/ib_rdma.c
> index db7e92e7bd29..a9b27f06cbfc 100644
> --- a/net/rds/ib_rdma.c
> +++ b/net/rds/ib_rdma.c
> @@ -165,7 +165,7 @@ void rds_ib_destroy_nodev_conns(void)
>
> /* avoid calling conn_destroy with irqs off */
> spin_lock_irq(&ib_nodev_conns_lock);
> - list_splice(&ib_nodev_conns, &tmp_list);
> + list_splice_init(&ib_nodev_conns, &tmp_list);
> spin_unlock_irq(&ib_nodev_conns_lock);
[Severity: Medium]
Can this panic a machine that is establishing an IB connection while
rds_rdma is unloading? rds_ib_add_conn() in the same file still opens
with:
spin_lock_irq(&ib_nodev_conns_lock);
BUG_ON(list_empty(&ib_nodev_conns));
BUG_ON(list_empty(&ic->ib_node));
Before this change, list_splice() left the ib_nodev_conns head pointing at
the spliced entries, so it never looked empty and the first BUG_ON could
not fire. With list_splice_init() the head is genuinely empty after a
sweep.
A connect still in flight can reach rds_ib_add_conn() after that point.
rds_ib_get_client_data() drops the RCU section as soon as it has the
device reference:
rcu_read_lock();
rds_ibdev = ib_get_client_data(device, &rds_ib_client);
if (rds_ibdev)
refcount_inc(&rds_ibdev->refcount);
rcu_read_unlock();
so the synchronize_rcu() in rds_ib_remove_one() does not wait for that
caller, and rds_ib_setup_qp() then calls:
/* add the conn now so that connection establishment has the dev */
rds_ib_add_conn(rds_ibdev, conn);
The resweep callback passed from rds_ib_exit() re-empties ib_nodev_conns on
every 100 ms poll for the whole unbounded wait, so this is not a single
instant but the full duration of the wait.
The BUG_ONs in rds_ib_add_conn() are removed later in the series by
"net/rds: unlink transport nodes before a possibly deferred connection
free", which replaces them with the ic->i_ib_node_detached check, so only
this intermediate commit is exposed. Would it make sense to order those
two changes the other way around, so the tree does not panic at this step
of a bisect?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260919061149.250658-1-achender%40kernel.org
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 07/12] net/rds: unlink transport nodes before a possibly deferred connection free
2026-09-19 6:11 ` [PATCH net-next v5 07/12] net/rds: unlink transport nodes before a possibly deferred connection free Allison Henderson
2026-09-20 6:12 ` sashiko-bot
@ 2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: netdev-bot+sashiko @ 2026-09-23 7:11 UTC (permalink / raw)
To: achender; +Cc: netdev, linux-rdma, pabeni, edumazet, kuba, horms, nicoyip.dev
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 0 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] net/rds/ib_rdma.c:rds_ib_add_conn(): the new `if
(!ic->i_ib_node_detached)` guard covers only the ib_node list…
- [Low] rds_ib_conn_free()'s header comment (net/rds/ib_cm.c:1278-1281)
still asserts a two-state invariant and denies the very race this…
--
net/rds: unlink transport nodes before a possibly deferred connection free
The transport teardown helpers now unlink each per-connection transport node
under the transport lock right before calling rds_conn_destroy(), so a
conn_free() deferred past the helper cannot list_del() from a stack frame
that no longer exists. IB gets an explicit i_ib_node_detached flag and the
two BUG_ON()s in rds_ib_add_conn()/rds_ib_remove_conn() are removed, and the
gather loops now take a connection reference for each node they move.
A couple of questions about the IB side below.
> diff --git a/net/rds/ib.h b/net/rds/ib.h
> index 5ff346a1e8ba..cb410c3ae8d8 100644
> --- a/net/rds/ib.h
> +++ b/net/rds/ib.h
> @@ -143,6 +143,10 @@ struct rds_ib_device;
> struct rds_ib_connection {
>
> struct list_head ib_node;
> + /* set under ib_nodev_conns_lock once a transport teardown has
> + * claimed ib_node; from then on only the teardown touches it
> + */
> + bool i_ib_node_detached;
> struct rds_ib_device *rds_ibdev;
> struct rds_connection *conn;
>
> diff --git a/net/rds/ib_cm.c b/net/rds/ib_cm.c
> index 53147793d44b..89340ecc3116 100644
> --- a/net/rds/ib_cm.c
> +++ b/net/rds/ib_cm.c
> @@ -1287,7 +1287,9 @@ void rds_ib_conn_free(void *arg)
> lock_ptr = ic->rds_ibdev ? &ic->rds_ibdev->spinlock : &ib_nodev_conns_lock;
>
> spin_lock_irqsave(lock_ptr, flags);
> - list_del(&ic->ib_node);
> + /* a transport teardown that gathered us first owns the node */
> + if (!ic->i_ib_node_detached)
> + list_del(&ic->ib_node);
> spin_unlock_irqrestore(lock_ptr, flags);
>
> rds_ib_recv_free_caches(ic);
[Severity: Low]
This isn't a bug, but the comment a few lines above the new check, in
rds_ib_conn_free(), still describes a world this patch has changed:
net/rds/ib_cm.c:rds_ib_conn_free() {
/*
* Conn is either on a dev's list or on the nodev list.
* A race with shutdown() or connect() would cause problems
* (since rds_ibdev would change) but that should never happen.
*/
lock_ptr = ic->rds_ibdev ? &ic->rds_ibdev->spinlock : &ib_nodev_conns_lock;
After this patch there is a third state: the node sits on
rds_ib_destroy_nodev_conns()'s stack-local tmp_list with
i_ib_node_detached set, and is then list_del_init()'ed, so the connection
is on neither a device list nor the nodev list.
The commit message also says "a connect or shutdown worker can still be
running for a connection the sweep has claimed", which is the
shutdown()/connect() race this comment says should never happen.
Could the comment be updated to describe the detached state, and to note
that the lock choice only matters while i_ib_node_detached is false?
> diff --git a/net/rds/ib_rdma.c b/net/rds/ib_rdma.c
> index a9b27f06cbfc..1548e5be0e55 100644
> --- a/net/rds/ib_rdma.c
> +++ b/net/rds/ib_rdma.c
> @@ -123,15 +123,18 @@ void rds_ib_add_conn(struct rds_ib_device *rds_ibdev, struct rds_connection *con
> {
> struct rds_ib_connection *ic = conn->c_transport_data;
>
> - /* conn was previously on the nodev_conns_list */
> + /* conn was previously on the nodev_conns_list, unless a teardown
> + * sweep has claimed it ahead of destroying it: then it is on its
> + * way out, and its node belongs to the sweep.
> + */
> spin_lock_irq(&ib_nodev_conns_lock);
> - BUG_ON(list_empty(&ib_nodev_conns));
> - BUG_ON(list_empty(&ic->ib_node));
> - list_del(&ic->ib_node);
> + if (!ic->i_ib_node_detached) {
> + list_del(&ic->ib_node);
>
> - spin_lock(&rds_ibdev->spinlock);
> - list_add_tail(&ic->ib_node, &rds_ibdev->conn_list);
> - spin_unlock(&rds_ibdev->spinlock);
> + spin_lock(&rds_ibdev->spinlock);
> + list_add_tail(&ic->ib_node, &rds_ibdev->conn_list);
> + spin_unlock(&rds_ibdev->spinlock);
> + }
> spin_unlock_irq(&ib_nodev_conns_lock);
>
> ic->rds_ibdev = rds_ibdev;
[Severity: High]
The new guard covers only the list movement, but the two statements that
follow still run for a connection the sweep has already claimed:
net/rds/ib_rdma.c:rds_ib_add_conn() {
spin_unlock_irq(&ib_nodev_conns_lock);
ic->rds_ibdev = rds_ibdev;
refcount_inc(&rds_ibdev->refcount);
}
Can this leak the rds_ib_device reference?
rds_ib_add_conn() is reached from rds_ib_setup_qp() via the RDMA-CM event
handlers, rds_ib_cm_initiate_connect() on the active side and
rds_ib_cm_handle_connect() on the passive side, which the sweep does not
serialize against.
The only site that clears ic->rds_ibdev and drops that reference is
rds_ib_conn_path_shutdown():
net/rds/ib_cm.c:rds_ib_conn_path_shutdown() {
...
if (ic->rds_ibdev)
rds_ib_remove_conn(ic->rds_ibdev, conn);
...
}
and rds_ib_remove_conn() ends with ic->rds_ibdev = NULL plus
rds_ib_dev_put(). Once the sweep's rds_conn_destroy() has quiesced the
paths, rds_conn_path_drop(cp, false) is gated by rds_destroy_pending(), so
no further shutdown pass runs for that connection.
rds_ib_conn_free() never calls rds_ib_dev_put(), so does an add_conn() that
lands after the quiesce leave ic->rds_ibdev set forever, with
rds_ibdev->refcount never reaching zero? That would mean rds_ib_dev_free()
is never queued and the device struct, its PD and its 1M/8K MR pools are
never released, which rds_ib_exit()'s flush_workqueue(rds_wq) cannot
recover. The QP, CQs and cm_id created by that late rds_ib_setup_qp() also
look like they stay around, since rdma_destroy_id() only happens in
rds_ib_conn_path_shutdown().
There is a second effect in the same window: the connection is bound to the
HCA but deliberately kept off rds_ibdev->conn_list, and device removal only
walks that list:
net/rds/ib.c:rds_ib_dev_shutdown() {
spin_lock_irqsave(&rds_ibdev->spinlock, flags);
list_for_each_entry(ic, &rds_ibdev->conn_list, ib_node)
rds_conn_path_drop(&ic->conn->c_path[0], true);
spin_unlock_irqrestore(&rds_ibdev->spinlock, flags);
}
Before this patch the node was still moved onto the device conn_list here,
so ib_device removal could at least find it. Is losing that intentional?
And in the reverse interleaving, rds_ib_conn_path_shutdown() tests
ic->rds_ibdev without a lock and then asserts on it:
net/rds/ib_cm.c:rds_ib_conn_path_shutdown() {
if (ic->rds_ibdev)
rds_ib_remove_conn(ic->rds_ibdev, conn);
...
}
BUG_ON(ic->rds_ibdev);
Can a concurrent rds_ib_add_conn() for the claimed connection store
ic->rds_ibdev between that test and the BUG_ON, making it fire?
For what it is worth, at the end of the series the later patch "net/rds: pin
the connection across RDMA-CM event handling" adds an
rds_destroy_pending() check only to rds_ib_cm_handle_connect(), so the
active path rds_ib_cm_initiate_connect() -> rds_ib_setup_qp() ->
rds_ib_add_conn() still looks unguarded, and rds_ib_add_conn() and
rds_ib_conn_free() are otherwise unchanged there.
> @@ -163,13 +173,40 @@ void rds_ib_destroy_nodev_conns(void)
> struct rds_ib_connection *ic, *_ic;
> LIST_HEAD(tmp_list);
>
> - /* avoid calling conn_destroy with irqs off */
> + struct rds_connection *conn;
> +
> + /* Gather the connections and take a reference on each, so that
> + * none is freed under the walk below (a connection destroyed
> + * earlier, for a protocol version mismatch, can be on this list
> + * with only a socket's reference still pending). One whose free
> + * is already running gets no reference: its free unlinks the
> + * node itself, under this lock, once we drop it. Marking the
> + * node detached claims it for this sweep: rds_ib_add_conn(),
> + * rds_ib_remove_conn() and rds_ib_conn_free() leave a claimed
> + * node alone, so the walk over tmp_list below needs no lock.
> + * Avoid calling conn_destroy with irqs off.
> + */
> spin_lock_irq(&ib_nodev_conns_lock);
> - list_splice_init(&ib_nodev_conns, &tmp_list);
> + list_for_each_entry_safe(ic, _ic, &ib_nodev_conns, ib_node) {
> + if (rds_conn_get_unless_zero(ic->conn)) {
> + ic->i_ib_node_detached = true;
> + list_move_tail(&ic->ib_node, &tmp_list);
> + }
> + }
> spin_unlock_irq(&ib_nodev_conns_lock);
>
> - list_for_each_entry_safe(ic, _ic, &tmp_list, ib_node)
> - rds_conn_destroy(ic->conn);
> + /* rds_conn_destroy() can return before the connection is freed,
> + * and it is the free - rds_ib_conn_free() - that would unlink
> + * ib_node. tmp_list lives on this stack frame, so take each node
> + * off it before its destroy; the free then leaves it alone.
> + */
> + list_for_each_entry_safe(ic, _ic, &tmp_list, ib_node) {
> + conn = ic->conn;
> + list_del_init(&ic->ib_node);
> +
> + rds_conn_destroy(conn);
> + rds_conn_put(conn);
> + }
> }
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260919061149.250658-1-achender%40kernel.org
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 08/12] net/rds: hold connection references in lookup, sockets and c_passive
2026-09-19 6:11 ` [PATCH net-next v5 08/12] net/rds: hold connection references in lookup, sockets and c_passive Allison Henderson
2026-09-20 6:12 ` sashiko-bot
@ 2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: netdev-bot+sashiko @ 2026-09-23 7:11 UTC (permalink / raw)
To: achender; +Cc: netdev, linux-rdma, pabeni, edumazet, kuba, horms, nicoyip.dev
Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider.
Critical: 0 · High: 0 (1) · Medium: 1 · Low: 4
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] rds_sendmsg() detects a cached connection whose destroy has
begun but never drops it from the cache: only the local variable…
- [Low] The commit fixes a syzbot-reported KCSAN data race on rs->rs_conn
but carries no Fixes: tag (only Reported-by: and Closes:).
- [Low] The patch documents rs_conn in struct rds_sock as "read and
written under rs_lock" and converts rds_ioctl(SIOCRDSSETTOS/GETTOS) to…
- [Low] The comment on c_destroy_in_prog in struct rds_connection states
the flag is "read through rds_destroy_pending()", but this patch adds…
- [Low] rds_sendmsg() still performs a plain, unlocked read of rs->rs_tos
when it passes the ToS to rds_conn_create_outgoing(), while…
Pre-existing issues:
- [High] __rds_conn_create() derives npaths from the caller-supplied
transport before it may switch trans to &rds_loop_transport, but…
--
net/rds: hold connection references in lookup, sockets and c_passive
This patch makes every escaping struct rds_connection pointer carry a real
reference: rds_conn_lookup() and __rds_conn_create() hand one to the caller,
the rs->rs_conn sendmsg cache owns one, and parent->c_passive owns one and
becomes RCU-annotated. It also serializes the rs_conn cache under rs_lock,
which closes a syzbot-reported KCSAN data race between concurrent
rds_sendmsg() calls.
Closes: https://syzkaller.appspot.com/bug?extid=879c1877016972360186
> Reported-by: syzbot+879c1877016972360186@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=879c1877016972360186
[Severity: Low]
A syzbot-reported KCSAN race is being fixed here, but the trailer block has
only Reported-by: and Closes: and no Fixes: tag.
The commit message does already explain the reasoning:
This is not a stable candidate on its own: it depends on the
connection reference counting introduced by the preceding patches,
and the race it closes needs a connection destroyed under a live
socket, which takes netns teardown, module unload or device removal.
Is that intended to stand in for the missing Fixes: tag, or would a Fixes:
plus an explicit "not for stable" note be preferred here?
> diff --git a/net/rds/af_rds.c b/net/rds/af_rds.c
> index d5defe9172e3..1cc20b5cfd21 100644
> --- a/net/rds/af_rds.c
> +++ b/net/rds/af_rds.c
> @@ -80,6 +80,14 @@ static int rds_release(struct socket *sock)
> rds_notify_queue_get(rs, NULL);
> rds_notify_msg_zcopy_purge(&rs->rs_zcookie_queue);
>
> + /* drop the cached connection reference; no sendmsg can race
> + * with us here, the socket is going away
> + */
> + if (rs->rs_conn) {
> + rds_conn_put(rs->rs_conn);
> + rs->rs_conn = NULL;
> + }
> +
> spin_lock_bh(&rds_sock_lock);
> list_del_init(&rs->rs_item);
> spin_unlock_bh(&rds_sock_lock);
> @@ -255,6 +263,7 @@ static int rds_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
> {
> struct rds_sock *rs = rds_sk_to_rs(sock->sk);
> rds_tos_t utos, tos = 0;
> + unsigned long flags;
>
> switch (cmd) {
> case SIOCRDSSETTOS:
> @@ -267,18 +276,23 @@ static int rds_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
> else
> return -ENOIOCTLCMD;
>
> - spin_lock_bh(&rds_sock_lock);
> + /* rs_conn is serialized by rs_lock (see rds_sendmsg());
> + * hold it across the "no connection yet" check and the
> + * rs_tos store so a racing sendmsg cannot cache a conn
> + * whose c_tos then disagrees with rs_tos.
> + */
> + spin_lock_irqsave(&rs->rs_lock, flags);
> if (rs->rs_tos || rs->rs_conn) {
> - spin_unlock_bh(&rds_sock_lock);
> + spin_unlock_irqrestore(&rs->rs_lock, flags);
> return -EINVAL;
> }
> rs->rs_tos = tos;
> - spin_unlock_bh(&rds_sock_lock);
> + spin_unlock_irqrestore(&rs->rs_lock, flags);
> break;
[ ... ]
> diff --git a/net/rds/connection.c b/net/rds/connection.c
> index 1d48da1a794f..965d68e51a1c 100644
> --- a/net/rds/connection.c
> +++ b/net/rds/connection.c
[ ... ]
> @@ -215,7 +237,20 @@ static struct rds_connection *__rds_conn_create(struct net *net,
> * We need a second connection object into which we
> * can stick the other QP. */
> parent = conn;
> - conn = parent->c_passive;
> + /* The c_passive pointer holds a reference which is only
> + * dropped one synchronize_rcu() after the pointer is
> + * cleared, so within this RCU section a fetched pointer
> + * is always safe to take a reference on. A passive conn
> + * whose own destroy has begun is not handed out, though:
> + * it is quiesced and about to clear the parent's pointer
> + * itself, and reusing it would re-arm a connection that
> + * nothing will tear down again.
> + */
> + conn = rcu_dereference(parent->c_passive);
> + if (conn && READ_ONCE(conn->c_destroy_in_prog))
> + conn = NULL;
> + if (conn)
> + rds_conn_get(conn);
> }
[Severity: Low]
The kerneldoc-style comment on c_destroy_in_prog in struct rds_connection
says the flag is "read through rds_destroy_pending()", but three new sites
here read the raw flag instead:
conn = rcu_dereference(parent->c_passive);
if (conn && READ_ONCE(conn->c_destroy_in_prog))
if (READ_ONCE(parent->c_destroy_in_prog)) {
if (READ_ONCE(passive->c_destroy_in_prog)) {
The distinction looks deliberate, since rds_destroy_pending() also reports
netns teardown and module unload, which these sites do not want.
Could the comment in rds.h be updated to describe the narrower "this conn's
destroy has begun" read, so a later reader does not convert these sites to
the helper?
> @@ -334,13 +369,44 @@ static struct rds_connection *__rds_conn_create(struct net *net,
> spin_lock_irqsave(&rds_conn_lock, flags);
> if (parent) {
> /* Creating passive conn */
> - if (parent->c_passive) {
> + if (READ_ONCE(parent->c_destroy_in_prog)) {
> + /* The parent's destroy has begun (it sets the
> + * flag and snatches c_passive under this
> + * lock); do not install a new passive conn
> + * that nothing would ever destroy.
> + */
> + rds_conn_free_transport_data(conn, npaths);
> + free_cp = conn->c_path;
> + kmem_cache_free(rds_conn_slab, conn);
> + conn = ERR_PTR(-ENETDOWN);
> + } else if (rcu_access_pointer(parent->c_passive)) {
> + struct rds_connection *passive;
> +
> + passive = rds_conn_passive_locked(parent);
> rds_conn_free_transport_data(conn, npaths);
> free_cp = conn->c_path;
> kmem_cache_free(rds_conn_slab, conn);
> - conn = parent->c_passive;
> + if (READ_ONCE(passive->c_destroy_in_prog)) {
> + /* Its destroy will clear the parent's
> + * pointer under this lock shortly; until
> + * then there is no usable passive conn.
> + */
> + conn = ERR_PTR(-ENETDOWN);
> + } else {
> + rds_conn_get(passive);
> + conn = passive;
> + }
> } else {
[ ... ]
> @@ -672,6 +744,9 @@ EXPORT_SYMBOL_GPL(rds_conn_put);
> void rds_conn_destroy(struct rds_connection *conn)
> {
> int i;
> + struct rds_connection *passive, *parent;
> + struct hlist_head *head;
> + bool was_passive = false;
> struct rds_conn_path *cp;
> int npaths = (conn->c_trans->t_mp_capable ? RDS_MPATH_WORKERS : 1);
^^^^
[Severity: High]
This isn't a bug introduced by this patch - baseline 8830e65 has the same
mismatch - but since the series rewrites these teardown functions, is the
npaths derivation here still correct for loopback-converted connections?
__rds_conn_create() computes npaths from the transport the caller passed in,
before it may switch trans:
int npaths = (trans->t_mp_capable ? RDS_MPATH_WORKERS : 1);
...
trans = &rds_loop_transport;
...
conn->c_trans = trans;
for (i = 0; i < npaths; i++) {
...
conn->c_path[i].cp_wq = alloc_ordered_workqueue(...);
For an outgoing RDS/TCP send to a local address, rds_tcp_transport has both
t_mp_capable and t_prefer_loopback set, so npaths is RDS_MPATH_WORKERS and
8 ordered workqueues are allocated, but c_trans ends up as
rds_loop_transport, whose t_mp_capable is 0.
rds_conn_destroy() and rds_conn_destroy_fini() then recompute npaths from
conn->c_trans and get 1, so only path 0 is quiesced and freed before
kfree(conn->c_path) drops the pointers to the other 7 workqueues.
rds_conn_path_free() also returns early for paths without transport data:
if (!cp->cp_transport_data)
return;
if (cp->cp_wq != rds_wq) {
destroy_workqueue(cp->cp_wq);
and rds_loop_conn_alloc() only populates path 0 (c_transport_data is a macro
for c_path[0].cp_transport_data), so even a corrected npaths would still skip
destroy_workqueue() for paths 1..7.
Can this leak 7 workqueues per loopback TCP connection, repeatable via
create netns / send to 127.0.0.1 / exit netns?
Note the rollback path in __rds_conn_create() uses the local, pre-switch
npaths and no transport-data gate:
for (i = 0; i < npaths; i++)
if (free_cp[i].cp_wq != rds_wq)
destroy_workqueue(free_cp[i].cp_wq);
which suggests the two derivations are not interchangeable.
[ ... ]
> diff --git a/net/rds/rds.h b/net/rds/rds.h
> index b3cc0804156e..321f2da9e76d 100644
> --- a/net/rds/rds.h
> +++ b/net/rds/rds.h
[ ... ]
> @@ -669,7 +669,9 @@ struct rds_sock {
>
> /*
> * rds_sendmsg caches the conn it used the last time around.
> - * This helps avoid costly lookups.
> + * This helps avoid costly lookups. The cache owns a connection
> + * reference, dropped when it is replaced or the socket is
> + * released, and is read and written under rs_lock.
> */
> struct rds_connection *rs_conn;
[Severity: Low]
The new text says rs_conn "is read and written under rs_lock", and
rds_ioctl(SIOCRDSSETTOS/SIOCRDSGETTOS) now relies on that. But rds_release()
in net/rds/af_rds.c reads it, puts it and stores NULL with no lock held:
if (rs->rs_conn) {
rds_conn_put(rs->rs_conn);
rs->rs_conn = NULL;
}
That looks safe today because rds_release() runs after the last fd reference
is gone, so no concurrent rds_sendmsg()/rds_ioctl() is possible. Could the
comment note that release-time exception?
Related: rs_tos is now read and written under rs_lock by both ioctls and read
under rs_lock by rds_sendmsg(), yet the "rs_lock protects all these adjacent
members before the newline" comment below only spans rs_send_queue through
rs_notify_queue. Should rs_tos (and rs_conn) be listed as rs_lock-protected
there?
> diff --git a/net/rds/send.c b/net/rds/send.c
> index 32c411d10e3e..2d7839438abd 100644
> --- a/net/rds/send.c
> +++ b/net/rds/send.c
[ ... ]
> @@ -1340,11 +1341,29 @@ int rds_sendmsg(struct socket *sock, struct msghdr *msg, size_t payload_len)
> rm->m_daddr = daddr;
>
> /* rds_conn_create has a spinlock that runs with IRQ off.
> - * Caching the conn in the socket helps a lot. */
> - if (rs->rs_conn && ipv6_addr_equal(&rs->rs_conn->c_faddr, &daddr) &&
> - rs->rs_tos == rs->rs_conn->c_tos) {
> - conn = rs->rs_conn;
> + * Caching the conn in the socket helps a lot.
> + *
> + * The cached rs_conn holds a connection reference; take one of
> + * our own for the duration of this call (dropped on both exit
> + * paths), so that neither a concurrent sender replacing the
> + * cache nor rds_conn_destroy() can free the connection under
> + * us. A cached connection whose destruction has begun is not
> + * reused: dropping it here lets the next sendmsg look up or
> + * create a live one instead of returning -EAGAIN forever.
> + */
> + spin_lock_irqsave(&rs->rs_lock, flags);
> + conn = rs->rs_conn;
> + if (conn && ipv6_addr_equal(&conn->c_faddr, &daddr) &&
> + rs->rs_tos == conn->c_tos && !rds_destroy_pending(conn)) {
> + rds_conn_get(conn);
> } else {
> + conn = NULL;
> + }
> + spin_unlock_irqrestore(&rs->rs_lock, flags);
[Severity: Medium]
Does the code actually drop the destroy-pending connection from the cache?
The commit message says "sendmsg drops it and looks up or creates a live
one", and the comment above says "dropping it here lets the next sendmsg look
up or create a live one". But when rds_destroy_pending(conn) is true, only
the local variable is cleared:
conn = rs->rs_conn;
if (conn && ... && !rds_destroy_pending(conn)) {
rds_conn_get(conn);
} else {
conn = NULL;
}
rs->rs_conn still points at the quiesced connection and still owns the
reference this patch gave it. The cache is only replaced further down, after
a successful create and a passing ToS re-check:
rds_conn_get(conn);
old = rs->rs_conn;
rs->rs_conn = conn;
spin_unlock_irqrestore(&rs->rs_lock, flags);
if (old)
rds_conn_put(old);
So if rds_conn_create_outgoing() fails with -ENOMEM, or the ToS re-check
returns -EAGAIN, or the socket simply never sends again after an IB device
removal quiesced the connection, doesn't the destroyed conn stay pinned by
rs->rs_conn until rds_release()?
That would keep the conn slab object, its c_path array, its per-path ordered
workqueues, the transport per-connection state and the elevated
trans->t_conn_count alive for the socket's lifetime.
Would clearing rs->rs_conn and calling rds_conn_put() on it right where
rds_destroy_pending() is observed, still under rs_lock, match the described
behaviour?
> +
> + if (!conn) {
> + struct rds_connection *old;
> +
> conn = rds_conn_create_outgoing(sock_net(sock->sk),
> &rs->rs_bound_addr, &daddr,
> rs->rs_transport, rs->rs_tos,
^^^^^^^^^^
[Severity: Low]
This read of rs->rs_tos is a plain access with no lock held, while
rds_ioctl(SIOCRDSSETTOS) does a plain store under rs_lock:
spin_lock_irqsave(&rs->rs_lock, flags);
if (rs->rs_tos || rs->rs_conn) {
spin_unlock_irqrestore(&rs->rs_lock, flags);
return -EINVAL;
}
rs->rs_tos = tos;
On a first send, rs_conn is NULL, so the ioctl does not refuse the change and
both accesses can run concurrently. Isn't that still a KCSAN-reportable data
race on rs_tos, in a patch whose purpose is removing such a race on the
adjacent rs_conn?
The locked re-check below keeps a stale-ToS connection from being installed
or used, so the effect looks limited to the report itself. Would taking the
rs_tos snapshot under rs_lock (or with READ_ONCE()) and keeping the re-check
close it?
> scope_id);
> if (IS_ERR(conn)) {
> ret = PTR_ERR(conn);
> + conn = NULL;
> goto out;
> }
> + /* rs_tos was sampled without rs_lock for the create above,
> + * and SIOCRDSSETTOS only refuses a change once rs_conn is
> + * set, so it can have changed underneath us. Do not
> + * install - or send on - a connection whose ToS no longer
> + * matches the socket's; the retry uses the new one.
> + */
> + spin_lock_irqsave(&rs->rs_lock, flags);
> + if (conn->c_tos != rs->rs_tos) {
> + spin_unlock_irqrestore(&rs->rs_lock, flags);
> + ret = -EAGAIN;
> + goto out;
> + }
> + /* hand the cache its own reference */
> + rds_conn_get(conn);
> + old = rs->rs_conn;
> rs->rs_conn = conn;
> + spin_unlock_irqrestore(&rs->rs_lock, flags);
> + if (old)
> + rds_conn_put(old);
> }
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260919061149.250658-1-achender%40kernel.org
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 09/12] net/rds: refuse to queue on a connection being destroyed
2026-09-19 6:11 ` [PATCH net-next v5 09/12] net/rds: refuse to queue on a connection being destroyed Allison Henderson
2026-09-20 6:12 ` sashiko-bot
@ 2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: netdev-bot+sashiko @ 2026-09-23 7:11 UTC (permalink / raw)
To: achender; +Cc: netdev, linux-rdma, pabeni, edumazet, kuba, horms, nicoyip.dev
Thank you for your contribution! Sashiko AI review found 6 potential
issue(s) to consider.
Critical: 0 · High: 3 · Medium: 1 · Low: 2
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] rds_conn_path_quiesce() (net/rds/connection.c:628-634) purges
cp_send_queue by only retiring the *connection-side* state of each…
- [High] The new rds_destroy_pending() refusal in rds_send_queue_rm() is
nested inside the `if (rs->rs_snd_bytes < rds_sk_sndbuf(rs))` branch…
- [High] The quiesce splices cp_send_queue into an on-stack list under
cp_lock, drops the lock, and then walks and frees that list unlocked…
- [Medium] Concern (baseline verdict: FALSE POSITIVE for the use-after-
free; the commit-message inaccuracy is real): queued outgoing messages…
- [Low] Moving spin_lock(&cp->cp_lock) above the socket-side bookkeeping
in rds_send_queue_rm() (net/rds/send.c:937) makes cp_lock cover the…
- [Low] Concern (baseline verdict: FALSE POSITIVE): the patch is claimed
to lack a Fixes:/dependency note for the hazard enabled by the…
--
net/rds: refuse to queue on a connection being destroyed
This changes rds_conn_path_quiesce() to splice cp_send_queue onto a local
list under cp_lock before purging it, and adds an rds_destroy_pending()
test under cp_lock to rds_send_queue_rm() and rds_send_probe() so that
nothing is queued after the purge. rds_send_queue_rm() now signals the
refusal by setting *queued negative, and rds_sendmsg() turns that into
-EAGAIN.
A few questions on the commit message and the new locking below.
> rds_send_queue_rm() adds to
> cp_send_queue under cp_lock, so the unlocked walk races the add and
> can corrupt the list. Worse, a message added after the purge sits on
> the queue of a quiesced connection holding the connection reference
> rds_send_queue_rm() took for it: the reference is only dropped when
> the message is freed, the message is only freed when the queue is
> torn down, and the queue is only torn down by the destroy that has
> already run. The connection would never be freed, and with it the
> transport could never unload.
[Severity: Medium]
Is this description accurate at this point in the series? Reading
rds_send_queue_rm() at this commit, the queueing sequence is:
net/rds/send.c:rds_send_queue_rm() {
...
rds_message_populate_header(&rm->m_inc.i_hdr, sport, dport, 0);
rm->m_inc.i_conn = conn;
rm->m_inc.i_conn_path = cp;
rds_message_addref(rm);
...
}
There is no rds_conn_get() here, and rds_message_put() has no matching
rds_conn_put(), so a message does not hold a connection reference yet.
That reference appears later in the series, in "net/rds: hold a
connection reference from struct rds_incoming".
Should the wording be adjusted so it does not describe a reference that
does not exist until a later patch, or should the patch ordering change?
> diff --git a/net/rds/connection.c b/net/rds/connection.c
> index 965d68e51a1c..5699f45e4c37 100644
> --- a/net/rds/connection.c
> +++ b/net/rds/connection.c
[ ... ]
> @@ -617,10 +619,16 @@ static void rds_conn_path_quiesce(struct rds_conn_path *cp)
> rds_conn_path_drop(cp, true);
> flush_work(&cp->cp_down_w);
>
> - /* tear down queued messages */
> - list_for_each_entry_safe(rm, rtmp,
> - &cp->cp_send_queue,
> - m_conn_item) {
> + /* Tear down queued messages. Take the queue under cp_lock:
> + * a sender that still holds a reference can be inside
> + * rds_send_queue_rm() right now, and it tests
> + * rds_destroy_pending() under the same lock, so after this
> + * splice nothing is added behind our back.
> + */
> + spin_lock_irqsave(&cp->cp_lock, flags);
> + list_splice_init(&cp->cp_send_queue, &purge);
> + spin_unlock_irqrestore(&cp->cp_lock, flags);
> + list_for_each_entry_safe(rm, rtmp, &purge, m_conn_item) {
> list_del_init(&rm->m_conn_item);
> BUG_ON(!list_empty(&rm->m_sock_item));
> rds_message_put(rm);
[Severity: High]
Can this purge hit the BUG_ON for a message a socket still owns?
The commit message says "A sender that gets there first has its message
purged". A message that got there first was linked on both queues by
rds_send_queue_rm():
net/rds/send.c:rds_send_queue_rm() {
...
list_add_tail(&rm->m_sock_item, &rs->rs_send_queue);
set_bit(RDS_MSG_ON_SOCK, &rm->m_flags);
rds_message_addref(rm);
sock_hold(rds_rs_to_sk(rs));
rm->m_rs = rs;
...
list_add_tail(&rm->m_conn_item, &cp->cp_send_queue);
set_bit(RDS_MSG_ON_CONN, &rm->m_flags);
...
}
m_sock_item is only unlinked by rds_send_remove_from_sock() (ack driven)
and rds_send_drop_to() (close() or RDS_CANCEL_SENT_TO). The teardown
path rds_conn_path_drop(cp, true) -> rds_conn_shutdown() ->
rds_send_path_reset() only puts cp_xmit_rm and splices cp_retrans back
into cp_send_queue, so it does not touch the socket side.
Does that mean any unacked message still on cp_send_queue reaches the
purge with a non-empty m_sock_item and trips
BUG_ON(!list_empty(&rm->m_sock_item))?
And for messages whose m_sock_item was already unlinked by a racing
rds_send_drop_to(), do rs_snd_bytes and the sock_hold() taken above stay
charged, since the purge neither calls rds_send_sndbuf_remove() nor
sock_put()?
[Severity: High]
Separately: the splice serializes additions, but does it serialize
removals? The purged messages keep RDS_MSG_ON_CONN set, and
rds_send_drop_to() decides ownership of the connection-queue reference
purely from that bit:
net/rds/send.c:rds_send_drop_to() {
...
spin_lock_irqsave(&cp->cp_lock, flags);
if (!test_and_clear_bit(RDS_MSG_ON_CONN, &rm->m_flags)) {
spin_unlock_irqrestore(&cp->cp_lock, flags);
continue;
}
list_del_init(&rm->m_conn_item);
spin_unlock_irqrestore(&cp->cp_lock, flags);
...
rds_message_put(rm);
}
After the splice, m_conn_item points into the purge list whose head is on
rds_conn_path_quiesce()'s stack. If a close() or
setsockopt(RDS_CANCEL_SENT_TO) runs while the unlocked
list_for_each_entry_safe() walk is in progress, can drop_to unlink an
entry and rewrite that stack list head behind the walker, so the cached
next pointer is followed into an already freed message?
Does the same stale bit also give two rds_message_put() calls for one
connection-queue reference, one from the purge and one from drop_to?
The invariant in net/rds/rds.h seems to expect the flag to be cleared
along with the list removal:
* The RDS_MSG_ON_SOCK and RDS_MSG_ON_CONN flags are used to avoid lock
* nesting. As paths iterate over messages on a sock, or conn, they must
* also lock the conn, or sock, to remove the message from those lists too.
Would clearing RDS_MSG_ON_CONN under cp_lock as part of the splice, or
holding cp_lock across the walk, close this?
> diff --git a/net/rds/send.c b/net/rds/send.c
> index 2d7839438abd..f7bc4c5446d6 100644
> --- a/net/rds/send.c
> +++ b/net/rds/send.c
> @@ -928,6 +928,19 @@ static int rds_send_queue_rm(struct rds_sock *rs, struct rds_connection *conn,
> * and poll() now knows no more data can be sent.
> */
> if (rs->rs_snd_bytes < rds_sk_sndbuf(rs)) {
> + /* rds_conn_path_quiesce() empties cp_send_queue under
> + * cp_lock once the connection's destroy has begun. Test
> + * for that under the same lock, before touching either
> + * queue: a message added after the purge would hold a
> + * connection reference nothing ever drops.
> + */
> + spin_lock(&cp->cp_lock);
> + if (rds_destroy_pending(conn)) {
> + spin_unlock(&cp->cp_lock);
> + *queued = -EAGAIN;
> + goto unlock;
> + }
> +
[Severity: High]
The refusal sits inside the "if (rs->rs_snd_bytes < rds_sk_sndbuf(rs))"
branch, so it is only evaluated when the socket already has send room.
With a full send buffer rds_send_queue_rm() returns *queued == 0 without
ever testing rds_destroy_pending(), and the sendmsg loop keeps sleeping:
net/rds/send.c:rds_sendmsg() {
...
timeo = wait_event_interruptible_timeout(*sk_sleep(sk),
rds_send_queue_rm(rs, conn, cpath, rm,
rs->rs_bound_port,
dport,
&queued),
timeo);
...
if (timeo > 0 || timeo == MAX_SCHEDULE_TIMEOUT)
continue;
...
}
For a blocking socket timeo is MAX_SCHEDULE_TIMEOUT, so the continue arm
re-enters the wait. Send room is released only by
rds_send_sndbuf_remove() from rds_send_remove_from_sock() (ack driven,
which cannot happen on a quiesced connection) or from
rds_send_drop_to(), and rds_conn_path_quiesce() neither decrements
rs_snd_bytes nor calls rds_wake_sk_sleep().
Does that make the new "if (queued < 0)" escape in rds_sendmsg()
unreachable in exactly the case the commit message names, "so that the
wait loop in rds_sendmsg() stops waiting for send room that will never
come"?
Since that sleeping thread holds the connection reference rds_sendmsg()
took, does this also keep rds_conn_wait_conns_freed() blocked and the
transport module unloadable?
Would hoisting the rds_destroy_pending() test above the sndbuf check
address both?
> rs->rs_snd_bytes += len;
>
> /* let recv side know we are close to send space exhaustion.
> @@ -951,7 +964,6 @@ static int rds_send_queue_rm(struct rds_sock *rs, struct rds_connection *conn,
> rm->m_inc.i_conn_path = cp;
> rds_message_addref(rm);
>
> - spin_lock(&cp->cp_lock);
> rm->m_inc.i_hdr.h_sequence = cpu_to_be64(cp->cp_next_tx_seq++);
> list_add_tail(&rm->m_conn_item, &cp->cp_send_queue);
> set_bit(RDS_MSG_ON_CONN, &rm->m_flags);
[Severity: Low]
This isn't a bug, but with the lock taken earlier, the comment a few
lines above the removed spin_lock() is now stale:
net/rds/send.c:rds_send_queue_rm() {
...
/* The code ordering is a little weird, but we're
trying to minimize the time we hold c_lock */
rds_message_populate_header(&rm->m_inc.i_hdr, sport, dport, 0);
...
}
cp_lock now covers the rs_snd_bytes update, the rs_send_queue insertion,
the two rds_message_addref() calls, sock_hold() and the header memset in
rds_message_populate_header(). Could the comment be updated or dropped,
or the destroy test done in its own short cp_lock section?
> @@ -964,6 +976,7 @@ static int rds_send_queue_rm(struct rds_sock *rs, struct rds_connection *conn,
> *queued = 1;
> }
>
> +unlock:
> spin_unlock_irqrestore(&rs->rs_lock, flags);
> out:
> return *queued;
> @@ -1485,6 +1498,11 @@ int rds_sendmsg(struct socket *sock, struct msghdr *msg, size_t payload_len)
> ret = -ETIMEDOUT;
> goto out;
> }
> + /* rds_send_queue_rm() refused: the connection is being destroyed */
> + if (queued < 0) {
> + ret = queued;
> + goto out;
> + }
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260919061149.250658-1-achender%40kernel.org
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 10/12] net/rds: pin the connection across RDMA-CM event handling
2026-09-19 6:11 ` [PATCH net-next v5 10/12] net/rds: pin the connection across RDMA-CM event handling Allison Henderson
2026-09-20 6:12 ` sashiko-bot
@ 2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: netdev-bot+sashiko @ 2026-09-23 7:11 UTC (permalink / raw)
To: achender; +Cc: netdev, linux-rdma, pabeni, edumazet, kuba, horms, nicoyip.dev
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] Stale forward-referencing comment: the ownership comment at the
`out:` label of rds_ib_cm_handle_connect() (net/rds/ib_cm.c:936-940)…
- [Low] Misattributed trigger in both the commit message and the new in-
code comment: the message states "the remaining exposure is a callback…
--
net/rds: pin the connection across RDMA-CM event handling
The RDMA-CM event handler takes a reference on the connection it picks up
from cm_id->context and drops it at the out: label, ignoring the event
entirely if the connection is already on its way out.
rds_ib_cm_handle_connect() also re-checks rds_destroy_pending() under
c_cm_lock and rejects the connect request instead of claiming the
DOWN -> CONNECTING transition.
A couple of questions about the commit message and about a comment that
this patch appears to invalidate.
> Now that a connection is freed by its last reference, the remaining
> exposure is a callback that drops the last reference other than the
> handler's - which has none - and the reference handed out by
> rds_conn_create() to rds_ib_cm_handle_connect() and dropped at its
> end.
[Severity: Low]
Is there really a callback below the handler that drops a connection
reference?
For most events the handler calls rds_conn_drop(), which ends in
rds_conn_path_drop() in net/rds/connection.c:
atomic_set(&cp->cp_state, RDS_CONN_ERROR);
...
queue_work(cp->cp_wq, &cp->cp_down_w);
That only flips the state and queues the shutdown work, it releases no
reference. rds_ib_cm_initiate_connect() and rds_ib_cm_connect_complete()
have no rds_conn_put() or rds_conn_destroy() either. The only
rds_conn_put() in net/rds/ib_cm.c is the one at the end of
rds_ib_cm_handle_connect(), and that path only runs when cm_id->context
was NULL (BUG_ON(cm_id->context) just above it), so the handler's own conn
is NULL there and neither the new reference nor c_cm_lock is in play.
The pin still looks necessary, but for a different reason: a concurrent
rds_conn_destroy() from transport or device teardown can drop the last
reference while the handler is still inside the switch holding c_cm_lock.
For example rds_ib_destroy_nodev_conns() in net/rds/ib_rdma.c:
rds_conn_destroy(conn);
rds_conn_put(conn);
rds_conn_shutdown() only takes cp_cm_lock around the state transition, and
skips it when the path is already DOWN, so that thread is not serialized
against a handler sitting in the switch. Without the new reference the
handler's mutex_unlock(&conn->c_cm_lock) would run on freed memory.
Would it be clearer to describe that path instead? As written a reader
could look for the callback that drops the reference, not find one, and
conclude the pin is not needed.
> diff --git a/net/rds/ib_cm.c b/net/rds/ib_cm.c
> index 786ddcb45bcb..abe5919c368a 100644
> --- a/net/rds/ib_cm.c
> +++ b/net/rds/ib_cm.c
> @@ -874,6 +874,13 @@ int rds_ib_cm_handle_connect(struct rdma_cm_id *cm_id,
> * see the comment above rds_queue_reconnect()
> */
> mutex_lock(&conn->c_cm_lock);
> + /* A destroy that has already quiesced this conn leaves it in
> + * RDS_CONN_DOWN with no cm_id, exactly what the transition
> + * below would happily claim; nothing would tear the new cm_id
> + * and QP down again before the conn is freed. Reject instead.
> + */
> + if (rds_destroy_pending(conn))
> + goto out;
> if (!rds_conn_transition(conn, RDS_CONN_DOWN, RDS_CONN_CONNECTING)) {
> if (rds_conn_state(conn) == RDS_CONN_UP) {
> rdsdebug("incoming connect while connecting\n");
[Severity: Low]
Should the comment at the out: label further down in
rds_ib_cm_handle_connect() be updated by this patch? It currently reads:
net/rds/ib_cm.c:rds_ib_cm_handle_connect() {
...
out:
if (conn) {
mutex_unlock(&conn->c_cm_lock);
/* Drop the reference rds_conn_create() handed us. The
* conn stays reachable through cm_id->context without a
* reference of its own for now; the CM event handler is
* given one of its own by a following patch.
*/
rds_conn_put(conn);
}
...
}
This patch is the "following patch" that comment points at, so from here
on the statement that the CM event handler holds no reference of its own
is no longer true, and the forward reference has nothing left to look
forward to. The comment is still unchanged at the tip of the series, so
the two files end up describing contradictory reference rules for CM
event handling.
> diff --git a/net/rds/rdma_transport.c b/net/rds/rdma_transport.c
> index b15cf316b23a..584e9867810f 100644
> --- a/net/rds/rdma_transport.c
> +++ b/net/rds/rdma_transport.c
> @@ -63,6 +63,18 @@ static int rds_rdma_cm_event_handler_cmn(struct rdma_cm_id *cm_id,
> if (cm_id->device->node_type == RDMA_NODE_IB_CA)
> trans = &rds_ib_transport;
>
> + /* cm_id->context carries no reference of its own. Pin the
> + * connection for the duration of the handler: what the callbacks
> + * below do may drop the last reference other than ours, and the
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[Severity: Low]
Same question here as on the changelog: which callback below drops a
connection reference? rds_conn_drop() only queues cp_down_w, and neither
rds_ib_cm_initiate_connect() nor rds_ib_cm_connect_complete() calls
rds_conn_put(). Would naming the concurrent rds_conn_destroy() path,
such as rds_ib_destroy_nodev_conns(), describe what this pin protects
against?
> + * mutex released at out: lives in the connection's path array.
> + * A connection already being freed gets no events handled.
> + */
> + if (conn && !rds_conn_get_unless_zero(conn)) {
> + rdsdebug("conn %p id %p is being freed, ignoring event\n",
> + conn, cm_id);
> + return 0;
> + }
> +
> /* Prevent shutdown from tearing down the connection
> * while we're executing. */
> if (conn) {
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260919061149.250658-1-achender%40kernel.org
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v5 12/12] net/rds: hold a connection reference from struct rds_incoming
2026-09-19 6:11 ` [PATCH net-next v5 12/12] net/rds: hold a connection reference from struct rds_incoming Allison Henderson
2026-09-20 6:12 ` sashiko-bot
@ 2026-09-23 7:11 ` netdev-bot+sashiko
1 sibling, 0 replies; 35+ messages in thread
From: netdev-bot+sashiko @ 2026-09-23 7:11 UTC (permalink / raw)
To: achender; +Cc: netdev, linux-rdma, pabeni, edumazet, kuba, horms, nicoyip.dev
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] Making every socket-queued `rds_incoming` own a connection
reference (`rds_inc_init()`/`rds_inc_path_init()`,…
- [Low] The patch fixes a real, user-visible use-after-free
(`rds6_inc_info_copy()` reading `inc->i_conn` after…
--
net/rds: hold a connection reference from struct rds_incoming
Every site that stores a connection pointer in rds_incoming->i_conn now
takes a connection reference, and the reference is dropped from
rds_inc_put() and from a new rds_message_free() helper. This closes the
reported use-after-free where an inc queued on a socket outlives the
connection it points at.
Closes: https://lore.kernel.org/netdev/20260720184955.3008978-1-nicoyip.dev@gmail.com/
Two questions below, one about the commit trailers and one about the
lifetime this introduces.
The changelog documents a confirmed KASAN slab-use-after-free and carries
Reported-by:/Closes: for it:
> Reported-by: Chengfeng Ye <nicoyip.dev@gmail.com>
> Closes: https://lore.kernel.org/netdev/20260720184955.3008978-1-nicoyip.dev@gmail.com/
[Severity: Low]
Should this also carry a Fixes: tag naming the commit that introduced the
bare receive-side assignment in rds_inc_init()?
Other bug fixes in this series do carry one, for example "net/rds: guard
every work-requeueing site with rds_destroy_pending()" with
Fixes: ebeeb1ad9b8a and "net/rds: make rds_destroy_pending() cover
single-connection destroy" with Fixes: cdc306a5c9cd3, so the omission
looks inconsistent within the series.
The explanation for why this is not stable material could stay in the
changelog next to the tag.
> diff --git a/net/rds/recv.c b/net/rds/recv.c
> index 6204e577a90a..1fcd4483d3be 100644
> --- a/net/rds/recv.c
> +++ b/net/rds/recv.c
> @@ -46,6 +46,7 @@ void rds_inc_init(struct rds_incoming *inc, struct rds_connection *conn,
> {
> refcount_set(&inc->i_refcount, 1);
> INIT_LIST_HEAD(&inc->i_item);
> + rds_conn_get(conn); /* put in rds_inc_put() */
> inc->i_conn = conn;
> inc->i_conn_path = NULL;
> inc->i_saddr = *saddr;
> @@ -61,6 +62,7 @@ void rds_inc_path_init(struct rds_incoming *inc, struct rds_conn_path *cp,
> {
> refcount_set(&inc->i_refcount, 1);
> INIT_LIST_HEAD(&inc->i_item);
> + rds_conn_get(cp->cp_conn); /* put in rds_inc_put() */
> inc->i_conn = cp->cp_conn;
> inc->i_conn_path = cp;
> inc->i_saddr = *saddr;
[Severity: Medium]
With a reference now attached to every received inc, does this hand an
unprivileged reader control over how long rds_conn_wait_conns_freed()
blocks?
The reference taken here is released only from rds_inc_put(), which runs
from rds_recvmsg() or from rds_clear_recv_queue() on close, so an
application that simply never reads a queued datagram keeps the
connection allocated.
rds_conn_wait_conns_freed() then has no way out:
net/rds/connection.c:rds_conn_wait_conns_freed() {
while (!wait_event_timeout(rds_conn_freed_waitq,
!atomic_read(&trans->t_conn_count),
msecs_to_jiffies(RDS_CONN_FREE_POLL_MS))) {
...
if (time_after_eq(jiffies, warn_at)) {
pr_warn("RDS/%s: still waiting for %d connection(s) to be freed before unload\n",
...
}
That wait is uninterruptible and is reached from module unload paths,
rds_tcp_exit() -> rds_conn_wait_conns_freed(), rds_ib_exit() and
rds_loop_exit(), so the unloading task sits in D state until the
application reads or closes, and the module stays in MODULE_STATE_GOING
in the meantime.
A bound socket holds a module reference on its own transport through
rds_trans_get_preferred(), but rds_find_bound() does not filter on
transport. Can a datagram that arrived over, say, a TCP connection sit
unread on a socket whose rs_transport is a different module, so that
rmmod rds_tcp hangs rather than failing with -EBUSY?
Would it make sense to bound the wait, discard or quarantine the incs
still queued at teardown, or hold a module reference for queued incs so
unload fails instead of blocking?
> memset(inc->i_rx_lat_trace, 0, sizeof(inc->i_rx_lat_trace));
> }
> @@ -81,9 +83,19 @@ void rds_inc_put(struct rds_incoming *inc)
> {
> rdsdebug("put inc %p ref %d\n", inc, refcount_read(&inc->i_refcount));
> if (refcount_dec_and_test(&inc->i_refcount)) {
> + struct rds_connection *conn = inc->i_conn;
> +
> BUG_ON(!list_empty(&inc->i_item));
>
> - inc->i_conn->c_trans->inc_free(inc);
> + /* inc_free() can free the memory @inc lives in, so the
> + * connection reference has to be dropped through the
> + * copy taken above.
> + */
> + conn->c_trans->inc_free(inc);
> + /* get in rds_inc_init(), rds_inc_path_init() or
> + * rds_recv_incoming()
> + */
> + rds_conn_put(conn);
> }
> }
> EXPORT_SYMBOL_GPL(rds_inc_put);
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260919061149.250658-1-achender%40kernel.org
^ permalink raw reply [flat|nested] 35+ messages in thread
end of thread, other threads:[~2026-09-23 7:11 UTC | newest]
Thread overview: 35+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-19 6:11 [PATCH net-next v5 00/12] net/rds: make connection lifetime reference-counted Allison Henderson
2026-09-19 6:11 ` [PATCH net-next v5 01/12] net/rds: ib: don't enable interrupts in rds_ib_conn_free() Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-19 6:11 ` [PATCH net-next v5 02/12] net/rds: free every path's transport data on the passive create paths Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
2026-09-19 6:11 ` [PATCH net-next v5 03/12] net/rds: guard every work-requeueing site with rds_destroy_pending() Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
2026-09-19 6:11 ` [PATCH net-next v5 04/12] net/rds: make rds_destroy_pending() cover single-connection destroy Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
2026-09-19 6:11 ` [PATCH net-next v5 05/12] net/rds: split connection destroy into quiesce and kref-governed free Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
2026-09-19 6:11 ` [PATCH net-next v5 06/12] net/rds: wait for connections to be freed on transport unload Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
2026-09-19 6:11 ` [PATCH net-next v5 07/12] net/rds: unlink transport nodes before a possibly deferred connection free Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
2026-09-19 6:11 ` [PATCH net-next v5 08/12] net/rds: hold connection references in lookup, sockets and c_passive Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
2026-09-19 6:11 ` [PATCH net-next v5 09/12] net/rds: refuse to queue on a connection being destroyed Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
2026-09-19 6:11 ` [PATCH net-next v5 10/12] net/rds: pin the connection across RDMA-CM event handling Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
2026-09-19 6:11 ` [PATCH net-next v5 11/12] net/rds: drop rds_conn_count in favor of t_conn_count Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-19 6:11 ` [PATCH net-next v5 12/12] net/rds: hold a connection reference from struct rds_incoming Allison Henderson
2026-09-20 6:12 ` sashiko-bot
2026-09-23 7:11 ` netdev-bot+sashiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox