From 311cee0ee125964d1139a0725c66777696257c59 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 10 Sep 2026 19:01:21 +0300 Subject: [PATCH] Fix group node scan stall and UAF on server link teardown - node_conn_direct_close: immediate conn cleanup when links are down (graceful fin_wait on a down conn left a lingering entry, so re-open in Phase3 stuck in 'pending' with no timer and scan never continued) - tcp_link_close_cb: skip etcp_link_close if conn was already closed synchronously during etcp_on_link_down cascade (fixes use-after-free) --- src/transport_layer/etcp_connections.c | 10 ++++++++-- src/transport_layer/node_conn_direct.c | 5 +++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/transport_layer/etcp_connections.c b/src/transport_layer/etcp_connections.c index 60cf10f1..cf20f0fd 100644 --- a/src/transport_layer/etcp_connections.c +++ b/src/transport_layer/etcp_connections.c @@ -1119,6 +1119,7 @@ static void tcp_link_close_cb(struct stcp_link *sl, int err, void *arg) { if (!link || !link->etcp) return; if (link->etcp->state == 2) return; int old_state = link->link_state; + struct ETCP_CONN *etcp = link->etcp; /* сохранить до каскада: etcp_on_link_down может закрыть conn и освободить link */ stcp_link_close(sl); link->tcp_link = NULL; @@ -1126,8 +1127,13 @@ static void tcp_link_close_cb(struct stcp_link *sl, int err, void *arg) { if (link->is_server) { DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] TCP server link %d down err=%d, closing", link->etcp->log_name, link->local_link_id, err); - etcp_on_link_down(link->etcp, link); - etcp_link_close(link); + etcp_on_link_down(etcp, link); + if (etcp->state != 2) { + etcp_link_close(link); + } else { + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] TCP server link %d already closed by conn teardown, skip", + etcp->log_name, link->local_link_id); + } } else { DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] TCP link %d down err=%d, scheduling reconnect", link->etcp->log_name, link->local_link_id, err); etcp_on_link_down(link->etcp, link); diff --git a/src/transport_layer/node_conn_direct.c b/src/transport_layer/node_conn_direct.c index 5aa42eac..abe540d7 100644 --- a/src/transport_layer/node_conn_direct.c +++ b/src/transport_layer/node_conn_direct.c @@ -874,7 +874,7 @@ void node_conn_direct_close(struct NODE_CONN_DIRECT* h) { } h->cb = NULL; h->cb_arg = NULL; if (entry->connect_timer) { uasync_cancel_timeout(entry->ua, entry->connect_timer); entry->connect_timer = NULL; } - if (conn && conn->state != 2 && !entry->timed_out) { + if (conn && conn->state != 2 && !entry->timed_out && conn->links_up > 0) { /* устанавливаем fin_wait, отправляем CLOSE, ставим таймер */ conn->fin_wait = 1; conn->fin_wait_clear_cb = ncd_fin_wait_cancelled; @@ -903,8 +903,9 @@ void node_conn_direct_close(struct NODE_CONN_DIRECT* h) { /* закрываем conn синхронно чтобы линки сразу ушли из socket->links_queue: * иначе в том же тике новый open того же узла создаст дубль-conn и коллизию адресов. * u_free(conn) всё равно отложен (phase 2 в etcp_connection_close), UAF нет. */ - DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] close: node=0x%016llx state=%d timed_out=%d cb_running=%d -> %s", + DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] close: node=0x%016llx state=%d timed_out=%d cb_running=%d links_up=%d -> %s", (unsigned long long)node_id, conn->state, entry->timed_out, conn->callbacks_running, + conn->links_up, conn->callbacks_running ? "deferred" : "sync"); if (conn->callbacks_running) uasync_call_soon(entry->ua, conn, ncd_deferred_close_conn); else etcp_connection_close(conn);