tipc_topsrv_stop() destroyed the receive workqueue while the listener socket still had its data-ready callback and sk_user_data installed. An incoming connection request could then queue srv->awork on the freed workqueue from tipc_topsrv_listener_data_ready(). Reject new accepts, clear sk_user_data under sk_callback_lock and cancel pending accept work before the workqueues are torn down. Fixes: 0ef897be12b8 ("tipc: separate topology server listener socket from subcsriber sockets") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Signed-off-by: Yuqi Xu Reviewed-by: Ren Wei --- Changes in v2: - Rebased onto the current net/main tip; patch content unchanged. net/tipc/topsrv.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/net/tipc/topsrv.c b/net/tipc/topsrv.c index af530c9ed840..908622a3d0fc 100644 --- a/net/tipc/topsrv.c +++ b/net/tipc/topsrv.c @@ -700,6 +700,15 @@ static void tipc_topsrv_stop(struct net *net) struct tipc_conn *con; int id; + spin_lock_bh(&srv->idr_lock); + srv->listener = NULL; + spin_unlock_bh(&srv->idr_lock); + + write_lock_bh(&lsock->sk->sk_callback_lock); + lsock->sk->sk_user_data = NULL; + write_unlock_bh(&lsock->sk->sk_callback_lock); + 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); @@ -713,7 +722,6 @@ static void tipc_topsrv_stop(struct net *net) } __module_get(lsock->ops->owner); __module_get(lsock->sk->sk_prot_creator->owner); - srv->listener = NULL; spin_unlock_bh(&srv->idr_lock); tipc_topsrv_work_stop(srv); -- 2.55.0 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 --- Changes in v2: - Rebased onto the current net/main tip; patch content unchanged. 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