From d237dfec7ca504d32b32f766398fa75380fd1432 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 22 Sep 2026 20:52:02 +0300 Subject: [PATCH] =?UTF-8?q?diag:=20=D0=BA=D0=BE=D0=BB=D1=8C=D1=86=D0=B5?= =?UTF-8?q?=D0=B2=D0=BE=D0=B9=20=D1=82=D1=80=D0=B5=D0=B9=D1=81=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B7=D0=BE=D0=B2=D0=BE=D0=B2=20teardown=20+=20=D0=B4?= =?UTF-8?q?=D0=B0=D0=BC=D0=BF=20=D0=BF=D1=80=D0=B8=20SIGSEGV=20(=D1=80?= =?UTF-8?q?=D0=B0=D0=B7=D0=BC=D0=BE=D1=82=D0=BA=D0=B0=20UAF=20link=20close?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Makefile.am | 1 + src/transport_layer/call_ring.c | 71 ++++++++++++++++++++++++++ src/transport_layer/call_ring.h | 52 +++++++++++++++++++ src/transport_layer/etcp.c | 3 ++ src/transport_layer/etcp_connections.c | 9 ++++ src/transport_layer/node_conn_direct.c | 2 + src/transport_layer/stcp.c | 5 ++ src/transport_layer/stcp_link.c | 4 ++ src/utun.c | 15 ++++++ 9 files changed, 162 insertions(+) create mode 100644 src/transport_layer/call_ring.c create mode 100644 src/transport_layer/call_ring.h diff --git a/src/Makefile.am b/src/Makefile.am index 3343660f..791a5e1d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -152,6 +152,7 @@ libutun_a_SOURCES = \ transport_layer/node_conn_direct.c \ transport_layer/socket_monitor.c \ transport_layer/auto_socket.c \ + transport_layer/call_ring.c \ control_server.c \ firewall.c \ eim_nat.c \ diff --git a/src/transport_layer/call_ring.c b/src/transport_layer/call_ring.c new file mode 100644 index 00000000..8be1077f --- /dev/null +++ b/src/transport_layer/call_ring.c @@ -0,0 +1,71 @@ +// call_ring.c — статический кольцевой трейс вызовов (диагностика UAF в teardown) +#include "call_ring.h" +#include + +#if defined(_WIN32) +#include +#else +#include +#include +#endif + +callring_entry_t g_callring[CALLRING_SIZE]; +uint32_t g_callring_idx = 0; + +static const char *cr_name(uint32_t code) { + switch (code) { + case CR_LINK_NEW: return "LINK_NEW"; + case CR_LINK_CLOSE_IN: return "LINK_CLOSE_IN"; + case CR_LINK_CLOSE_TCP: return "LINK_CLOSE_TCP"; + case CR_LINK_CLOSE_FREE: return "LINK_CLOSE_FREE"; + case CR_TCPCB_IN: return "TCPCB_IN"; + case CR_TCPCB_FREE: return "TCPCB_FREE"; + case CR_SRV_ONLINK: return "SRV_ONLINK"; + case CR_SLINK_CLOSE: return "SLINK_CLOSE"; + case CR_SLINK_IMPL: return "SLINK_IMPL"; + case CR_SLINK_ONSTCP: return "SLINK_ONSTCP"; + case CR_CONN_SETONCLOSE: return "CONN_SETONCLOSE"; + case CR_CONN_DOCLOSE_IN: return "CONN_DOCLOSE_IN"; + case CR_CONN_DOCLOSE_CB: return "CONN_DOCLOSE_CB"; + case CR_CONN_FREE: return "CONN_FREE"; + case CR_CCLOSE_IN: return "CCLOSE_IN"; + case CR_CFREE_RES: return "CFREE_RES"; + case CR_NCD_DEFER: return "NCD_DEFER"; + default: return "?"; + } +} + +/* Дамп последних записей кольцевого буфера в /tmp/utun_callring. (и stderr + * при ошибке). Форматирование только через snprintf в стековый буфер — в + * обработчике сигнала не выделяет память. */ +void callring_dump(void) { + char buf[192]; + int fd = -1; + +#if defined(_WIN32) + fd = 2; +#else + { + char path[96]; + snprintf(path, sizeof(path), "/tmp/utun_callring.%d", (int)getpid()); + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + } + if (fd < 0) fd = 2; +#endif + + int n = snprintf(buf, sizeof(buf), "callring dump idx=%u\n", g_callring_idx); + if (n > 0) { ssize_t w = write(fd, buf, (size_t)n); (void)w; } + + uint32_t total = g_callring_idx < CALLRING_SIZE ? g_callring_idx : CALLRING_SIZE; + uint32_t start = g_callring_idx >= CALLRING_SIZE ? g_callring_idx - CALLRING_SIZE : 0; + for (uint32_t k = 0; k < total; k++) { + const callring_entry_t *e = &g_callring[(start + k) % CALLRING_SIZE]; + n = snprintf(buf, sizeof(buf), "%s a=0x%lx b=0x%lx\n", cr_name(e->code), + (unsigned long)e->a, (unsigned long)e->b); + if (n > 0) { ssize_t w = write(fd, buf, (size_t)n); (void)w; } + } + +#if !defined(_WIN32) + if (fd != 2) close(fd); +#endif +} diff --git a/src/transport_layer/call_ring.h b/src/transport_layer/call_ring.h new file mode 100644 index 00000000..52785deb --- /dev/null +++ b/src/transport_layer/call_ring.h @@ -0,0 +1,52 @@ +// call_ring.h — статический кольцевой трейс вызовов (диагностика UAF в teardown) +// +// Каждая контрольная точка пишет в общий кольцевой буфер номер (code) и два +// указателя-объекта (a, b). По последовательности записей + адресам из core-дампа +// можно размотать, кто кого вызвал и кто освободил линк без обнуления обратных ссылок. +#ifndef CALL_RING_H +#define CALL_RING_H + +#include +#include + +#define CALLRING_SIZE 1024 + +typedef struct { + uint32_t code; // код контрольной точки (число) + uintptr_t a; // объект 1 (link/sl/conn/etcp) + uintptr_t b; // объект 2 (close_arg/tcp_link/etcp_link) +} callring_entry_t; + +extern callring_entry_t g_callring[CALLRING_SIZE]; +extern uint32_t g_callring_idx; + +static inline void callring_put(uint32_t code, uintptr_t a, uintptr_t b) { + callring_entry_t *e = &g_callring[g_callring_idx % CALLRING_SIZE]; + e->code = code; e->a = a; e->b = b; + g_callring_idx++; +} + +void callring_dump(void); + +/* коды контрольных точек */ +enum { + CR_LINK_NEW = 1, /* etcp_link_new → a=link b=etcp */ + CR_LINK_CLOSE_IN = 2, /* etcp_link_close вход → a=link b=etcp */ + CR_LINK_CLOSE_TCP = 3, /* etcp_link_close is_tcp-ветка → a=tcp_link b=link */ + CR_LINK_CLOSE_FREE = 4, /* etcp_link_close перед u_free → a=link */ + CR_TCPCB_IN = 5, /* tcp_link_close_cb вход → a=sl b=arg(link) */ + CR_TCPCB_FREE = 6, /* tcp_link_close_cb → etcp_link_close → a=link */ + CR_SRV_ONLINK = 7, /* tcp_server_on_link → a=tlink b=sl */ + CR_SLINK_CLOSE = 10, /* stcp_link_close → a=sl b=etcp_link */ + CR_SLINK_IMPL = 11, /* stcp_link_close_impl → a=sl b=conn */ + CR_SLINK_ONSTCP = 12, /* stcp_link_on_stcp_close → a=conn b=arg(sl) */ + CR_CONN_SETONCLOSE = 20, /* stcp_conn_set_on_close → a=c b=arg */ + CR_CONN_DOCLOSE_IN = 21, /* stcp_conn_do_close вход → a=c */ + CR_CONN_DOCLOSE_CB = 22, /* stcp_conn_do_close перед cb → a=c b=close_arg */ + CR_CONN_FREE = 23, /* stcp_conn_free → a=c */ + CR_CCLOSE_IN = 30, /* etcp_connection_close вход → a=etcp */ + CR_CFREE_RES = 31, /* etcp_connection_free_resources → a=etcp */ + CR_NCD_DEFER = 40, /* ncd_deferred_close_conn → a=conn */ +}; + +#endif /* CALL_RING_H */ diff --git a/src/transport_layer/etcp.c b/src/transport_layer/etcp.c index 8eed7bf0..25571c28 100644 --- a/src/transport_layer/etcp.c +++ b/src/transport_layer/etcp.c @@ -4,6 +4,7 @@ #include "etcp_api.h" #include "etcp_connect.h" #include "etcp_debug.h" +#include "call_ring.h" #include "etcp_loadbalancer.h" #include "etcp_router.h" #include "routing.h" @@ -349,6 +350,7 @@ static void etcp_on_down(struct ETCP_CONN* etcp, struct ETCP_LINK* down_link) { // Phase 2 resources cleanup (callable both sync and async via call_soon) static void etcp_connection_free_resources(struct ETCP_CONN* etcp) { if (!etcp) return; + callring_put(CR_CFREE_RES, (uintptr_t)etcp, 0); DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "[%s] freeing resources phase 2 conn=%p peer=%016llx", etcp->log_name, (void*)etcp, (unsigned long long)etcp->peer_node_id); @@ -388,6 +390,7 @@ void etcp_connection_close(struct ETCP_CONN* etcp) { DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); if (!etcp) return; if (etcp->state == 2) { DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] already deleted", etcp->log_name); return; } + callring_put(CR_CCLOSE_IN, (uintptr_t)etcp, 0); if (etcp->callbacks_running) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] FATAL: etcp_connection_close called from inside callback chain — SEGFAULTING to show backtrace", diff --git a/src/transport_layer/etcp_connections.c b/src/transport_layer/etcp_connections.c index 7f26253d..9c0a5c20 100644 --- a/src/transport_layer/etcp_connections.c +++ b/src/transport_layer/etcp_connections.c @@ -1,5 +1,6 @@ #include "etcp_connections.h" #include "etcp_api.h" +#include "call_ring.h" #include "../lib/socket_compat.h" #include "../lib/platform_compat.h" #include "../lib/getmyip.h" @@ -132,6 +133,7 @@ void tcp_server_on_link(struct stcp_link *link, struct ETCP_SOCKET *tcp_sock) { stcp_link_set_etcp_conn(link, conn); stcp_link_set_etcp_link(link, tlink); stcp_link_set_on_close(link, tcp_link_close_cb, tlink); + callring_put(CR_SRV_ONLINK, (uintptr_t)tlink, (uintptr_t)link); etcp_link_enter_ready_tcp(tlink); } @@ -1074,6 +1076,7 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn etcp_link_enter_init(link); } + callring_put(CR_LINK_NEW, (uintptr_t)link, (uintptr_t)etcp); return link; } @@ -1104,9 +1107,11 @@ static void etcp_inflight_nullify_link(struct ll_queue* q, struct ETCP_LINK* dea void etcp_link_close(struct ETCP_LINK* link) { DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); if (!link) return; + callring_put(CR_LINK_CLOSE_IN, (uintptr_t)link, (uintptr_t)(link->etcp)); if (link->etcp && link->etcp->last_rr_link == link) link->etcp->last_rr_link = NULL; if (link->is_tcp) { + callring_put(CR_LINK_CLOSE_TCP, (uintptr_t)(link->tcp_link), (uintptr_t)link); if (link->tcp_reconnect_timer) { uasync_cancel_timeout(link->etcp->instance->ua, link->tcp_reconnect_timer); link->tcp_reconnect_timer = NULL; } if (link->tcp_link) { stcp_link_set_on_close(link->tcp_link, NULL, NULL); stcp_link_close(link->tcp_link); link->tcp_link = NULL; } } @@ -1126,6 +1131,7 @@ void etcp_link_close(struct ETCP_LINK* link) { etcp_conn_on_inflight_lim_changed(link->etcp); etcp_inflight_nullify_link(link->etcp->input_wait_ack, link); etcp_inflight_nullify_link(link->etcp->input_send_q, link); + callring_put(CR_LINK_CLOSE_FREE, (uintptr_t)link, 0); u_free(link->bbr); u_free(link); return; @@ -1169,6 +1175,7 @@ void etcp_link_close(struct ETCP_LINK* link) { etcp_inflight_nullify_link(link->etcp->input_wait_ack, link); etcp_inflight_nullify_link(link->etcp->input_send_q, link); etcp_on_link_down(link->etcp, link); + callring_put(CR_LINK_CLOSE_FREE, (uintptr_t)link, 0); u_free(link->bbr); u_free(link); } @@ -1258,6 +1265,7 @@ void etcp_tcp_link_start_reconnect(struct ETCP_LINK *link) { // Callback закрытия STCP-линка: серверный линк закрываем, клиентский — отправляем на реконнект. static void tcp_link_close_cb(struct stcp_link *sl, int err, void *arg) { struct ETCP_LINK *link = (struct ETCP_LINK *)arg; + callring_put(CR_TCPCB_IN, (uintptr_t)sl, (uintptr_t)arg); if (!link || !link->etcp) return; if (link->etcp->state == 2) return; /* Сохраняем до каскада: etcp_on_link_down может закрыть conn и освободить link */ @@ -1274,6 +1282,7 @@ static void tcp_link_close_cb(struct stcp_link *sl, int err, void *arg) { DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] TCP server link %d down err=%d, closing", etcp->log_name, local_link_id, err); etcp_on_link_down(etcp, link); if (etcp->state != 2) { + callring_put(CR_TCPCB_FREE, (uintptr_t)link, 0); etcp_link_close(link); } else { DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] TCP server link %d already closed by conn teardown, skip", diff --git a/src/transport_layer/node_conn_direct.c b/src/transport_layer/node_conn_direct.c index cc1fbcd3..d1fb261e 100644 --- a/src/transport_layer/node_conn_direct.c +++ b/src/transport_layer/node_conn_direct.c @@ -10,6 +10,7 @@ #include "etcp_api.h" #include "etcp.h" #include "etcp_connections.h" +#include "call_ring.h" #include "secure_channel.h" #include "utun_instance.h" #include "topo_node.h" @@ -334,6 +335,7 @@ static void ncd_deferred_close(void* arg) { */ static void ncd_deferred_close_conn(void* arg) { struct ETCP_CONN* conn = (struct ETCP_CONN*)arg; + callring_put(CR_NCD_DEFER, (uintptr_t)conn, 0); if (conn) etcp_connection_close(conn); } /* diff --git a/src/transport_layer/stcp.c b/src/transport_layer/stcp.c index cf9b253f..0c7de7b8 100644 --- a/src/transport_layer/stcp.c +++ b/src/transport_layer/stcp.c @@ -1,5 +1,6 @@ // stcp.c — shared stcp_conn lifecycle + frame encrypt/decrypt + pending queue + send + recv FSM #include "stcp.h" +#include "call_ring.h" #include "../lib/ll_queue.h" #include "../lib/mem.h" #include "../lib/debug_config.h" @@ -24,6 +25,7 @@ void stcp_conn_set_rx_queue(struct stcp_conn *c, struct ll_queue *q) { void stcp_conn_set_on_close(struct stcp_conn *c, void (*cb)(struct stcp_conn *conn, int err, void *arg), void *arg) { if (!c || !cb) return; + callring_put(CR_CONN_SETONCLOSE, (uintptr_t)c, (uintptr_t)arg); c->on_close = cb; c->close_arg = arg; } @@ -43,6 +45,7 @@ const char* stcp_conn_peer_str(struct stcp_conn *c) { void stcp_conn_free(struct stcp_conn *c) { if (!c) return; + callring_put(CR_CONN_FREE, (uintptr_t)c, 0); stcp_server_remove_conn(c); DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "stcp_conn_free: c=%p sock=%d sock_id=%p recv_buf=%p state=%d allocated=%d", (void*)c, (int)c->sock, (void*)c->socket_id, (void*)c->recv_buf, (int)c->state, (int)c->allocated); @@ -311,6 +314,7 @@ void hs_timeout_cb(void *arg) { void stcp_conn_do_close(struct stcp_conn *c, int err) { if (!c) return; if (c->state == STCP_STATE_CLOSED || c->state == STCP_STATE_ERROR) return; + callring_put(CR_CONN_DOCLOSE_IN, (uintptr_t)c, 0); int prev = c->state; c->state = STCP_STATE_CLOSED; @@ -331,6 +335,7 @@ void stcp_conn_do_close(struct stcp_conn *c, int err) { if (c->on_close) { void (*cb)(struct stcp_conn*, int, void*) = c->on_close; c->on_close = NULL; + callring_put(CR_CONN_DOCLOSE_CB, (uintptr_t)c, (uintptr_t)c->close_arg); cb(c, err, c->close_arg); } if (c->free_on_close) { diff --git a/src/transport_layer/stcp_link.c b/src/transport_layer/stcp_link.c index 66311d2d..c6a87ab8 100644 --- a/src/transport_layer/stcp_link.c +++ b/src/transport_layer/stcp_link.c @@ -1,5 +1,6 @@ // stcp_link.c — STCP link management implementation #include "stcp_link.h" +#include "call_ring.h" #include "stcp.h" #include "stcp_server.h" #include "stcp_client.h" @@ -96,6 +97,7 @@ static void link_rx_cb(struct ll_queue *q, void *arg) { static void stcp_link_on_stcp_close(struct stcp_conn *conn, int err, void *arg) { struct stcp_link *link = (struct stcp_link *)arg; (void)conn; (void)err; + callring_put(CR_SLINK_ONSTCP, (uintptr_t)conn, (uintptr_t)arg); link->ready = 0; if (link->etcp_link) { link->etcp_link->recv_keepalive = 0; link->etcp_link->link_status = 0; } if (link->on_close_cb) link->on_close_cb(link, err, link->close_arg); @@ -263,6 +265,7 @@ struct stcp_link *stcp_link_connect(struct ETCP_LINK *etcp_link, static void stcp_link_close_impl(void *arg) { struct stcp_link *link = (struct stcp_link *)arg; + callring_put(CR_SLINK_IMPL, (uintptr_t)link, (uintptr_t)(link->conn)); if (link->cli) { struct stcp_conn *c = stcp_client_get_conn(link->cli); @@ -290,6 +293,7 @@ void stcp_link_close(struct stcp_link *link) { if (!link) return; if (link->closing) return; link->closing = 1; + callring_put(CR_SLINK_CLOSE, (uintptr_t)link, (uintptr_t)(link->etcp_link)); /* Обрыв вперёд-ссылки родителя: после отложенного free (close_impl) * etcp_link->tcp_link не должен остаться висячим. */ if (link->etcp_link && link->etcp_link->tcp_link == link) link->etcp_link->tcp_link = NULL; diff --git a/src/utun.c b/src/utun.c index a33cba56..e6ee6e7a 100644 --- a/src/utun.c +++ b/src/utun.c @@ -3,6 +3,7 @@ #define _POSIX_C_SOURCE 200809L #include "config_parser.h" #include "etcp_connections.h" +#include "transport_layer/call_ring.h" #include "tun_if.h" #include "secure_channel.h" #include "route_lib.h" @@ -256,6 +257,16 @@ static void signal_handler(int sig) { } } +/* Дамп кольцевого трейса вызовов при краше, затем переброс сигнала по умолчанию + * (чтобы остался core dump). Форматирование в callring_dump не выделяет память. */ +#ifndef _WIN32 +static void crash_handler(int sig) { + callring_dump(); + signal(sig, SIG_DFL); + raise(sig); +} +#endif + void test_tmr(void* arg) { struct UASYNC* ua = (struct UASYNC*)arg; uasync_set_timeout(ua, 10000, ua, test_tmr, "utun_test"); @@ -339,6 +350,10 @@ int main(int argc, char *argv[]) { // не должен молча убивать демон — ошибки EPIPE обрабатываются по errno в коде. #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); + /* Дампить кольцевой трейс при краше (до core dump). */ + signal(SIGSEGV, crash_handler); + signal(SIGABRT, crash_handler); + signal(SIGBUS, crash_handler); #endif // Enable TUN initialization for VPN functionality