Browse Source

diag: кольцевой трейс вызовов teardown + дамп при SIGSEGV (размотка UAF link close)

proxy
evgeny 1 week ago
parent
commit
d237dfec7c
  1. 1
      src/Makefile.am
  2. 71
      src/transport_layer/call_ring.c
  3. 52
      src/transport_layer/call_ring.h
  4. 3
      src/transport_layer/etcp.c
  5. 9
      src/transport_layer/etcp_connections.c
  6. 2
      src/transport_layer/node_conn_direct.c
  7. 5
      src/transport_layer/stcp.c
  8. 4
      src/transport_layer/stcp_link.c
  9. 15
      src/utun.c

1
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 \

71
src/transport_layer/call_ring.c

@ -0,0 +1,71 @@
// call_ring.c — статический кольцевой трейс вызовов (диагностика UAF в teardown)
#include "call_ring.h"
#include <stdio.h>
#if defined(_WIN32)
#include <io.h>
#else
#include <unistd.h>
#include <fcntl.h>
#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.<pid> (и 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
}

52
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 <stdint.h>
#include <stddef.h>
#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 */

3
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",

9
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",

2
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);
}
/*

5
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) {

4
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;

15
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

Loading…
Cancel
Save