diag: кольцевой трейс вызовов teardown + дамп при SIGSEGV (размотка UAF link close)
This commit is contained in:
1 parent
4f29839de5
commit
d237dfec7c
9 files changed
+162
No files matched your search
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
/*
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user