[PATCH v4 1/3] IB/isert: do not publish connections being released on a second list

From: Yehyeong Lee

Date: Mon Nov 02 2026 - 22:30:38 EST


isert_free_np() moves the connections left on isert_np->pending and
isert_np->accepted onto a stack-local list and releases them from there,
so isert_conn->node is not empty once isert_np->mutex is dropped. That
emptiness is how the CM handlers decide whether a connection is still
theirs, so isert_handle_unbound_conn() and isert_connect_error() unlink
it from a list they do not own and release it a second time, and
isert_connected_handler() moves it back onto isert_np->pending.

Take the connections off isert_np one at a time, so that the node is
empty once the mutex is dropped, and release them by dropping references
instead of calling isert_connect_release() directly. The two CM handlers
test the node under isert_np->mutex and stand back when it is empty;
connect_error also returns zero there, so free_np's rdma_destroy_id()
destroys the cm_id rather than cma destroying it as well. Whoever
empties the node owns the release, which still runs with no mutex held,
as commit 691b0480933f ("IB/isert: Fix dead lock in ib_isert") needs.

Reproduced with a test-only delay in isert_free_np()'s release loop: 36
reports in each of three unpatched runs, none in three patched runs.

BUG: KASAN: slab-use-after-free in isert_free_np+0x6a4/0xab0
Read of size 8 at addr ffff888109249040 by task rmdir/193

CPU: 1 UID: 0 PID: 193 Comm: rmdir Not tainted 7.3.0-BASET-13248-g103b309e2033 #15 PREEMPT(full)
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+0x53/0x70
print_report+0xd0/0x630
? __pfx__raw_spin_lock_irqsave+0x10/0x10
? isert_free_np+0x6a4/0xab0
kasan_report+0xce/0x100
? isert_free_np+0x6a4/0xab0
isert_free_np+0x6a4/0xab0
? __pfx_isert_free_np+0x10/0x10
iscsit_del_np+0x163/0x340
iscsit_tpg_del_network_portal+0x3c3/0x530
? mutex_unlock+0x7b/0xd0
lio_target_call_delnpfromtpg+0x40/0x60
config_item_cleanup+0x115/0x190
configfs_rmdir+0x7c7/0x9f0
? __pfx_configfs_rmdir+0x10/0x10
vfs_rmdir+0x291/0x770
? lookup_one_qstr_excl+0x1a/0x1d0
filename_rmdir+0x35d/0x4f0
? __pfx_filename_rmdir+0x10/0x10
? kasan_save_track+0x14/0x30
? __kasan_slab_alloc+0x6e/0x70
? strncpy_from_user+0x2b/0x1c0
__x64_sys_rmdir+0x41/0x60
do_syscall_64+0xdd/0x4a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
</TASK>

Allocated by task 10:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
__kasan_kmalloc+0x8f/0xa0
__kmalloc_cache_noprof+0x148/0x360
isert_cma_handler+0x1e3/0x2840
cma_cm_event_handler+0x3e/0x240
cma_ib_req_handler+0x17d9/0x4480
cm_process_work+0x41/0x330
cm_work_handler+0x5727/0xc160
process_one_work+0x697/0x1060
worker_thread+0x45b/0xd10
kthread+0x2c8/0x3b0
ret_from_fork+0x36e/0x5a0
ret_from_fork_asm+0x1a/0x30

Freed by task 43:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
kasan_save_free_info+0x3b/0x60
__kasan_slab_free+0x43/0x70
kfree+0x121/0x390
process_one_work+0x697/0x1060
worker_thread+0x45b/0xd10
kthread+0x2c8/0x3b0
ret_from_fork+0x36e/0x5a0
ret_from_fork_asm+0x1a/0x30

Last potentially related work creation:
kasan_save_stack+0x33/0x60
kasan_record_aux_stack+0x8c/0xa0
__queue_work+0x7a1/0x15e0
queue_work_on+0x5d/0x70
isert_disconnected_handler.isra.0+0x20b/0x380
isert_cma_handler+0xd71/0x2840
cma_cm_event_handler+0x3e/0x240
cma_ib_handler+0x190/0x750
cm_process_work+0x41/0x330
cm_work_handler+0x582b/0xc160
process_one_work+0x697/0x1060
worker_thread+0x45b/0xd10
kthread+0x2c8/0x3b0
ret_from_fork+0x36e/0x5a0
ret_from_fork_asm+0x1a/0x30

The buggy address belongs to the object at ffff888109248000
which belongs to the cache kmalloc-8k of size 8192
The buggy address is located 4160 bytes inside of
freed 8192-byte region [ffff888109248000, ffff88810924a000)

Fixes: 691b0480933f ("IB/isert: Fix dead lock in ib_isert")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Yehyeong Lee <yhlee@xxxxxxxxxxxxxxxxxx>
---
drivers/infiniband/ulp/isert/ib_isert.c | 84 ++++++++++++++++---------
1 file changed, 55 insertions(+), 29 deletions(-)

diff --git a/drivers/infiniband/ulp/isert/ib_isert.c b/drivers/infiniband/ulp/isert/ib_isert.c
index efa8d7b7844c97..8255208e30c7d6 100644
--- a/drivers/infiniband/ulp/isert/ib_isert.c
+++ b/drivers/infiniband/ulp/isert/ib_isert.c
@@ -521,13 +521,18 @@ isert_connected_handler(struct rdma_cm_id *cma_id)
isert_info("conn %p\n", isert_conn);

mutex_lock(&isert_conn->mutex);
+ mutex_lock(&isert_np->mutex);
+ if (list_empty(&isert_conn->node)) {
+ mutex_unlock(&isert_np->mutex);
+ mutex_unlock(&isert_conn->mutex);
+ isert_info("conn %p is being released already\n", isert_conn);
+ return;
+ }
isert_conn->state = ISER_CONN_UP;
kref_get(&isert_conn->kref);
- mutex_unlock(&isert_conn->mutex);
-
- mutex_lock(&isert_np->mutex);
list_move_tail(&isert_conn->node, &isert_np->pending);
mutex_unlock(&isert_np->mutex);
+ mutex_unlock(&isert_conn->mutex);

isert_info("np %p: Allow accept_np to continue\n", isert_np);
up(&isert_np->sem);
@@ -658,12 +663,19 @@ isert_connect_error(struct rdma_cm_id *cma_id)
{
struct isert_conn *isert_conn = cma_id->qp->qp_context;
struct isert_np *isert_np = cma_id->context;
-
- ib_drain_qp(isert_conn->qp);
+ bool owned;

mutex_lock(&isert_np->mutex);
+ owned = !list_empty(&isert_conn->node);
list_del_init(&isert_conn->node);
mutex_unlock(&isert_np->mutex);
+
+ if (!owned) {
+ isert_info("conn %p is being released already\n", isert_conn);
+ return 0;
+ }
+
+ ib_drain_qp(isert_conn->qp);
isert_conn->cm_id = NULL;
isert_put_conn(isert_conn);

@@ -2479,12 +2491,33 @@ isert_accept_np(struct iscsi_np *np, struct iscsit_conn *conn)
return 0;
}

+static struct isert_conn *
+isert_np_take_conn(struct isert_np *isert_np, bool *promoted)
+{
+ struct isert_conn *isert_conn;
+
+ lockdep_assert_held(&isert_np->mutex);
+
+ *promoted = true;
+ isert_conn = list_first_entry_or_null(&isert_np->pending,
+ struct isert_conn, node);
+ if (!isert_conn) {
+ *promoted = false;
+ isert_conn = list_first_entry_or_null(&isert_np->accepted,
+ struct isert_conn, node);
+ }
+ if (isert_conn)
+ list_del_init(&isert_conn->node);
+
+ return isert_conn;
+}
+
static void
isert_free_np(struct iscsi_np *np)
{
struct isert_np *isert_np = np->np_context;
- struct isert_conn *isert_conn, *n;
- LIST_HEAD(drop_conn_list);
+ struct isert_conn *isert_conn;
+ bool promoted;

if (isert_np->cm_id)
rdma_destroy_id(isert_np->cm_id);
@@ -2497,32 +2530,25 @@ isert_free_np(struct iscsi_np *np)
* up in accepted and pending lists.
*/
mutex_lock(&isert_np->mutex);
- if (!list_empty(&isert_np->pending)) {
+ if (!list_empty(&isert_np->pending))
isert_info("Still have isert pending connections\n");
- list_for_each_entry_safe(isert_conn, n,
- &isert_np->pending,
- node) {
- isert_info("cleaning isert_conn %p state (%d)\n",
- isert_conn, isert_conn->state);
- list_move_tail(&isert_conn->node, &drop_conn_list);
- }
- }
-
- if (!list_empty(&isert_np->accepted)) {
+ if (!list_empty(&isert_np->accepted))
isert_info("Still have isert accepted connections\n");
- list_for_each_entry_safe(isert_conn, n,
- &isert_np->accepted,
- node) {
- isert_info("cleaning isert_conn %p state (%d)\n",
- isert_conn, isert_conn->state);
- list_move_tail(&isert_conn->node, &drop_conn_list);
- }
- }
mutex_unlock(&isert_np->mutex);

- list_for_each_entry_safe(isert_conn, n, &drop_conn_list, node) {
- list_del_init(&isert_conn->node);
- isert_connect_release(isert_conn);
+ for (;;) {
+ mutex_lock(&isert_np->mutex);
+ isert_conn = isert_np_take_conn(isert_np, &promoted);
+ mutex_unlock(&isert_np->mutex);
+
+ if (!isert_conn)
+ break;
+
+ isert_info("cleaning isert_conn %p state (%d)\n",
+ isert_conn, isert_conn->state);
+ if (promoted)
+ isert_put_conn(isert_conn);
+ isert_put_conn(isert_conn);
}

np->np_context = NULL;
--
2.43.0