[PATCH v2] RDMA/siw: Fix early peer close handling on listeners

From: Jaeyeong Lee

Date: Wed Nov 04 2026 - 05:10:21 EST


A TCP child socket inherits the listener callbacks and sk_user_data before
kernel_accept() associates it with a new connection endpoint. If the peer
closes during this window, siw_cm_llp_state_change() queues a PEER_CLOSE
work item against the listening endpoint.

The worker releases that endpoint without removing it from the listener
list. A later listener teardown then walks the dangling entry, causing a
use-after-free in siw_destroy_listen().

Ignoring close notifications while the endpoint is LISTENING is not
sufficient. The endpoint remains IDLE for a short interval after listen()
starts accepting connections, and a close delivered before the accepted
socket is handed to its new endpoint would otherwise be lost.

Ignore close notifications from unaccepted child sockets during the IDLE
interval as well. During accept, serialize the endpoint handoff with TCP
state changes and reject a child that has already reached TCP_CLOSE or
TCP_CLOSE_WAIT. Live accepted sockets continue through the normal close
path.

The issue was found by static analysis. It was reproduced with SIW over
loopback in an x86_64 QEMU guest. KASAN reported the listener endpoint
use-after-free on the unpatched kernel. Tests covering RST before accept,
an MPA request followed by FIN, and an extended post-listen IDLE window
completed without KASAN or lockdep reports after this change.

Fixes: 6c52fdc244b5 ("rdma/siw: connection management")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Jaeyeong Lee <lee@xxxxxxxxxxx>
---
Changes in v2:
- Cover the IDLE interval between listen() and the LISTENING transition.
- Serialize accepted-socket handoff and reject an already closed child.

drivers/infiniband/sw/siw/siw_cm.c | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/drivers/infiniband/sw/siw/siw_cm.c b/drivers/infiniband/sw/siw/siw_cm.c
index ed49818793dde..92fcbcabb5984 100644
--- a/drivers/infiniband/sw/siw/siw_cm.c
+++ b/drivers/infiniband/sw/siw/siw_cm.c
@@ -973,6 +973,7 @@ static void siw_accept_newconn(struct siw_cep *cep)
struct socket *s = cep->sock;
struct socket *new_s = NULL;
struct siw_cep *new_cep = NULL;
+ bool peer_closed;
int rv = 0; /* debug only. should disappear */

if (cep->state != SIW_EPSTATE_LISTENING)
@@ -1009,11 +1010,23 @@ static void siw_accept_newconn(struct siw_cep *cep)
}
new_cep->sock = new_s;
siw_cep_get(new_cep);
- new_s->sk->sk_user_data = new_cep;

if (siw_tcp_nagle == false)
tcp_sock_set_nodelay(new_s->sk);
+
+ lock_sock(new_s->sk);
+ write_lock_bh(&new_s->sk->sk_callback_lock);
new_cep->state = SIW_EPSTATE_AWAIT_MPAREQ;
+ new_s->sk->sk_user_data = new_cep;
+ peer_closed = new_s->sk->sk_state == TCP_CLOSE ||
+ new_s->sk->sk_state == TCP_CLOSE_WAIT;
+ write_unlock_bh(&new_s->sk->sk_callback_lock);
+ release_sock(new_s->sk);
+
+ if (peer_closed) {
+ rv = -ECONNRESET;
+ goto error;
+ }

rv = siw_cm_queue_work(new_cep, SIW_CM_WORK_MPATIMEOUT);
if (rv)
@@ -1361,6 +1374,10 @@ static void siw_cm_llp_state_change(struct sock *sk)

case TCP_CLOSE:
case TCP_CLOSE_WAIT:
+ if (cep->state == SIW_EPSTATE_LISTENING ||
+ (cep->state == SIW_EPSTATE_IDLE &&
+ !READ_ONCE(sk->sk_socket)))
+ break;
if (cep->qp)
cep->qp->tx_ctx.tx_suspend = 1;
siw_cm_queue_work(cep, SIW_CM_WORK_PEER_CLOSE);
--
2.43.0