* [PATCH v3] RDMA/siw: Fix early peer close handling on listeners
@ 2026-11-05 2:22 Jaeyeong Lee
2026-11-05 2:46 ` sashiko-bot
0 siblings, 1 reply; 2+ messages in thread
From: Jaeyeong Lee @ 2026-11-05 2:22 UTC (permalink / raw)
To: Bernard Metzler
Cc: Jason Gunthorpe, Leon Romanovsky, linux-rdma, linux-kernel
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(). KASAN reports:
==================================================================
BUG: KASAN: slab-use-after-free in __list_del_entry_valid_or_report+0x32/0x1a0
Read of size 8 at addr ffff888008f502c8 by task rping/59
CPU: 0 UID: 0 PID: 59 Comm: rping Tainted: G B 7.4.0-rc1-g6628e7d4c6b2 #1 PREEMPT(lazy)
Tainted: [B]=BAD_PAGE
Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0x54/0x70
print_address_description+0x73/0x1f0
? __list_del_entry_valid_or_report+0x32/0x1a0
print_report+0x58/0x70
kasan_report+0x117/0x150
? add_taint+0x43/0x90
? __list_del_entry_valid_or_report+0x32/0x1a0
__list_del_entry_valid_or_report+0x32/0x1a0
siw_destroy_listen+0xe1/0x630
destroy_cm_id+0x25c/0x480
? srso_return_thunk+0x5/0x5f
? srso_return_thunk+0x5/0x5f
? lock_acquire+0xf7/0x220
? _destroy_id+0x2d1/0xe30
iw_destroy_cm_id+0xf/0x80
_destroy_id+0x6cc/0xe30
_cma_cancel_listens+0x2ee/0x360
_destroy_id+0x2ad/0xe30
ucma_destroy_private_ctx+0xb7/0x9d0
? srso_return_thunk+0x5/0x5f
? do_raw_spin_unlock+0x4d/0x230
ucma_close+0xd5/0x130
__fput+0x31a/0x720
fput_close_sync+0x91/0x110
filp_close_sync+0x18/0x30
put_files_struct+0x25a/0x280
do_exit+0x70e/0x1d00
? lock_acquire+0xf7/0x220
? get_signal+0x21c/0xf60
do_group_exit+0x19e/0x250
get_signal+0xf20/0xf60
? futex_wait+0x99/0x230
arch_do_signal_or_restart+0x46/0x420
exit_to_user_mode_loop+0xbd/0x510
do_syscall_64+0x2ed/0x3b0
? entry_SYSCALL_64_after_hwframe+0x76/0x7e
? srso_return_thunk+0x5/0x5f
? trace_hardirqs_off+0x65/0x90
entry_SYSCALL_64_after_hwframe+0x76/0x7e
RIP: 0033:0x7f92984c6e51
Code: Unable to access opcode bytes at 0x7f92984c6e27.
RSP: 002b:00007ffeae782d10 EFLAGS: 00000246 ORIG_RAX: 00000000000000ca
RAX: fffffffffffffe00 RBX: 000055fb4e9819b8 RCX: 00007f92984c6e51
RDX: 0000000000000000 RSI: 0000000000000189 RDI: 000055fb4e9819b8
RBP: 00007ffeae782d50 R08: 0000000000000000 R09: 00000000ffffffff
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 0000000000000000 R14: 000055fb4e9819b8 R15: 000055fb4e97da25
</TASK>
Allocated by task 59:
kasan_save_track+0x3e/0x80
__kasan_kmalloc+0x72/0x90
__kmalloc_cache_noprof+0x245/0x3c0
siw_cep_alloc+0x4f/0x390
siw_create_listen+0x39e/0xd60
iw_cm_listen+0xfc/0x1d0
rdma_listen+0x749/0xa90
cma_listen_on_dev+0x404/0x760
rdma_listen+0x3dc/0xa90
ucma_listen+0xe2/0x180
ucma_write+0x1bd/0x1e0
vfs_write+0x1a9/0x8e0
ksys_write+0xba/0x180
do_syscall_64+0xee/0x3b0
entry_SYSCALL_64_after_hwframe+0x76/0x7e
Freed by task 30:
kasan_save_track+0x3e/0x80
kasan_save_free_info+0x40/0x50
__kasan_slab_free+0x3a/0x60
kfree+0x155/0x470
siw_cm_work_handler+0x7af/0x2d40
process_scheduled_works+0x970/0x12d0
worker_thread+0x894/0xd20
kthread+0x2db/0x3b0
ret_from_fork+0x266/0x570
ret_from_fork_asm+0x1a/0x30
The buggy address belongs to the object at ffff888008f50200
which belongs to the cache kmalloc-512 of size 512
The buggy address is located 200 bytes inside of
freed 512-byte region [ffff888008f50200, ffff888008f50400)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000
index:0xffff888008f51400 pfn:0x8f50
head: order:2 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0x4000000000000240(workingset|head|zone=1)
page_type: f5(slab)
raw: 4000000000000240 ffff888005842040 ffff888005840d08 ffff888005840d08
raw: ffff888008f51400 00000000000a0003 00000000f5000000 0000000000000000
head: 4000000000000240 ffff888005842040 ffff888005840d08 ffff888005840d08
head: ffff888008f51400 00000000000a0003 00000000f5000000 0000000000000000
head: 4000000000000002 ffffea000023d401 00000000ffffffff 00000000ffffffff
head: 0000000000000000 0000000000000000 00000000ffffffff 0000000000000000
page dumped because: kasan: bad access detected
Memory state around the buggy address:
ffff888008f50180: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
ffff888008f50200: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff888008f50280: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff888008f50300: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff888008f50380: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
There is a second race while creating a listener. listen() starts
accepting connections before the endpoint is linked to the CM ID and
changed from IDLE to LISTENING. ACCEPT work queued in this interval can
run immediately, see the IDLE state, and return without accepting the
first child.
Keep the listener endpoint in use from allocation until its CM ID, listener
list entry, and LISTENING state have all been published. This makes early
ACCEPT work wait for listener setup to complete.
During accept, serialize the endpoint handoff with socket state changes and
reject a child that has already reached TCP_CLOSE or TCP_CLOSE_WAIT. Ignore
close notifications inherited from the listener before that handoff. Live
accepted sockets continue through the normal close path.
The issue was found by static analysis.
The unpatched kernel reproduced the KASAN report above in an x86_64 QEMU
guest. With a temporary 500 ms delay after listen(), the previous version
lost the first child and timed out waiting for an MPA response; this
version accepted the same child and returned an MPA response.
RST-before-accept and MPA-request-followed-by-FIN tests also completed
without KASAN or lockdep reports. The final SIW object was built with
LLVM 22.1.8 and W=1.
Fixes: 6c52fdc244b5 ("rdma/siw: connection management")
Suggested-by: Stefan Metzmacher <metze@samba.org>
Link: https://lore.kernel.org/r/33abff2233b36a51e468d691cb4327d0294d2734.1620343860.git.metze@samba.org
Cc: stable@vger.kernel.org
Signed-off-by: Jaeyeong Lee <lee@jaeyeong.cc>
---
Changes in v3:
- Serialize listener setup so early ACCEPT work cannot be consumed in IDLE.
- Include the complete KASAN report requested by Leon.
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 | 23 ++++++++++++++++++++---
1 file changed, 20 insertions(+), 3 deletions(-)
diff --git a/drivers/infiniband/sw/siw/siw_cm.c b/drivers/infiniband/sw/siw/siw_cm.c
index ed49818793dde..90adb9543d9d0 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);
@@ -1880,6 +1897,7 @@ int siw_create_listen(struct iw_cm_id *id, int backlog)
rv = -ENOMEM;
goto error;
}
+ siw_cep_set_inuse(cep);
siw_cep_socket_assoc(cep, s);
rv = siw_cm_alloc_work(cep, backlog);
@@ -1932,14 +1950,13 @@ int siw_create_listen(struct iw_cm_id *id, int backlog)
siw_dbg(id->device, "Listen at laddr %pISp\n", &id->local_addr);
+ siw_cep_set_free(cep);
return 0;
error:
siw_dbg(id->device, "failed: %d\n", rv);
if (cep) {
- siw_cep_set_inuse(cep);
-
siw_free_cm_id(cep);
siw_socket_disassoc(s);
cep->state = SIW_EPSTATE_CLOSED;
--
2.43.0
^ permalink raw reply related [flat|nested] 2+ messages in thread* Re: [PATCH v3] RDMA/siw: Fix early peer close handling on listeners
2026-11-05 2:22 [PATCH v3] RDMA/siw: Fix early peer close handling on listeners Jaeyeong Lee
@ 2026-11-05 2:46 ` sashiko-bot
0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-11-05 2:46 UTC (permalink / raw)
To: Jaeyeong Lee; +Cc: linux-rdma
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] RDMA/siw: premature listener CEP release before sock_release() on error
--
commit 9e6e7c6fb2387746734048153461994802a461df
Author: Jaeyeong Lee <lee@jaeyeong.cc>
RDMA/siw: Fix early peer close handling on listeners
This commit fixes a use-after-free in siw_destroy_listen() and a race during
listener creation by ignoring early peer close events on listening sockets,
serializing accepted child socket handoff with socket state changes, and
holding the listener endpoint in use until setup is complete.
Link: https://lore.kernel.org/r/33abff2233b36a51e468d691cb4327d0294d2734.1620343860.git.metze@samba.org
> diff --git a/drivers/infiniband/sw/siw/siw_cm.c b/drivers/infiniband/sw/siw/siw_cm.c
> index ed49818793dde..90adb9543d9d0 100644
> --- a/drivers/infiniband/sw/siw/siw_cm.c
> +++ b/drivers/infiniband/sw/siw/siw_cm.c
[ ... ]
> @@ -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);
> @@ -1880,6 +1897,7 @@ int siw_create_listen(struct iw_cm_id *id, int backlog)
> rv = -ENOMEM;
> goto error;
> }
> + siw_cep_set_inuse(cep);
> siw_cep_socket_assoc(cep, s);
>
> rv = siw_cm_alloc_work(cep, backlog);
> @@ -1932,14 +1950,13 @@ int siw_create_listen(struct iw_cm_id *id, int backlog)
>
> siw_dbg(id->device, "Listen at laddr %pISp\n", &id->local_addr);
>
> + siw_cep_set_free(cep);
> return 0;
>
> error:
> siw_dbg(id->device, "failed: %d\n", rv);
>
> if (cep) {
> - siw_cep_set_inuse(cep);
> -
> siw_free_cm_id(cep);
> siw_socket_disassoc(s);
> cep->state = SIW_EPSTATE_CLOSED;
[Severity: High]
Can the error cleanup path in siw_create_listen() trigger a use-after-free on
cep if id->provider_data allocation fails after s->ops->listen() has started?
In siw_create_listen(), s->ops->listen(s, backlog) places s->sk into
TCP_LISTEN before id->provider_data is allocated:
rv = s->ops->listen(s, backlog);
if (rv) {
siw_dbg(id->device, "listen error %d\n", rv);
goto error;
}
cep->cm_id = id;
id->add_ref(id);
...
if (!id->provider_data) {
id->provider_data =
kmalloc_obj(struct list_head);
if (!id->provider_data) {
rv = -ENOMEM;
goto error;
}
During this window, an incoming connection can complete the TCP handshake in
softirq context, creating a child socket in the accept queue that inherits
sk_user_data = cep and the siw_cm_llp_* callbacks, and queueing
SIW_CM_WORK_ACCEPT on cep.
If kmalloc_obj() then fails and jumps to error, siw_socket_disassoc(s) only
clears sk_user_data and restores callbacks on the parent listening socket
s->sk, leaving the unaccepted child socket with sk_user_data = cep:
error:
if (cep) {
siw_free_cm_id(cep);
siw_socket_disassoc(s);
cep->state = SIW_EPSTATE_CLOSED;
siw_cep_set_free_and_put(cep);
}
sock_release(s);
Because cep->state = SIW_EPSTATE_CLOSED and siw_cep_set_free_and_put(cep) run
before sock_release(s) (unlike in siw_drop_listeners(), where sock_release(s)
is called while cep->in_use is held and before cep->state is set to
SIW_EPSTATE_CLOSED), two problems can occur before sock_release(s) finishes
tearing down the unaccepted child socket:
1. Once siw_cep_set_free_and_put(cep) releases cep->in_use and drops the
listener reference, the pending SIW_CM_WORK_ACCEPT work item on siw_cm_wq
wakes up, sees cep->state != SIW_EPSTATE_LISTENING in siw_accept_newconn(),
and drops the last reference on cep, freeing it. When sock_release(s) then
runs:
sock_release(s)
-> inet_release()
-> tcp_close()
-> inet_csk_listen_stop()
-> inet_child_forget()
-> tcp_disconnect()
-> siw_cm_llp_error_report(child)
siw_cm_llp_error_report() dereferences the freed cep via sk_to_cep(child).
2. If the peer closes or resets the unaccepted child after cep->state is set
to SIW_EPSTATE_CLOSED, siw_cm_llp_state_change(child) only ignores
SIW_EPSTATE_LISTENING and SIW_EPSTATE_IDLE, so it will proceed to queue
SIW_CM_WORK_PEER_CLOSE on cep (or on already-freed memory).
Should siw_create_listen() call sock_release(s) before setting cep->state =
SIW_EPSTATE_CLOSED and calling siw_cep_set_free_and_put(cep) (matching
siw_drop_listeners()), or allocate id->provider_data before calling
s->ops->listen()?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261105022253.368316-1-lee@jaeyeong.cc?part=1
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-11-05 2:46 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-05 2:22 [PATCH v3] RDMA/siw: Fix early peer close handling on listeners Jaeyeong Lee
2026-11-05 2:46 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox