The teardown walk iterated conn_idr by incrementing a numeric ID while holding idr_lock, so it could scan a large range of unused IDs without letting a connection's final reference release make progress. An entry whose last reference had already been dropped could also be resurrected by the unconditional conn_get() while its release callback was blocked on the same lock. Walk conn_idr with idr_get_next(), release the lock and reschedule when no entry can be taken, and use kref_get_unless_zero() so a connection that is already being released cannot be revived. Fixes: 35e22e49a5d6 ("tipc: fix cleanup at module unload") Fixes: 667eeab4999e ("tipc: Fix use-after-free in tipc_conn_close().") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Signed-off-by: Yuqi Xu Reviewed-by: Ren Wei --- net/tipc/topsrv.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/net/tipc/topsrv.c b/net/tipc/topsrv.c index 908622a3d0fc..9333e36a74de 100644 --- a/net/tipc/topsrv.c +++ b/net/tipc/topsrv.c @@ -710,15 +710,20 @@ static void tipc_topsrv_stop(struct net *net) cancel_work_sync(&srv->awork); spin_lock_bh(&srv->idr_lock); - for (id = 0; srv->idr_in_use; id++) { - con = idr_find(&srv->conn_idr, id); - if (con) { - conn_get(con); + for (id = 0; srv->idr_in_use;) { + con = idr_get_next(&srv->conn_idr, &id); + if (!con || !kref_get_unless_zero(&con->kref)) { spin_unlock_bh(&srv->idr_lock); - tipc_conn_close(con); - conn_put(con); + cond_resched(); spin_lock_bh(&srv->idr_lock); + id = 0; + continue; } + id++; + spin_unlock_bh(&srv->idr_lock); + tipc_conn_close(con); + conn_put(con); + spin_lock_bh(&srv->idr_lock); } __module_get(lsock->ops->owner); __module_get(lsock->sk->sk_prot_creator->owner); -- 2.55.0