Browse Source

Fix TCP proxy use-after-free during synchronous close

master
evgeny 1 day ago
parent
commit
52ffb91101
  1. 16
      src/proxy/tcp_proxy_server.c
  2. 9
      tests/test_proxy_regressions.c

16
src/proxy/tcp_proxy_server.c

@ -55,6 +55,18 @@ static int send_msg(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst,
return proxy_send(inst, dst, ETCP_RT_ID_TCP_PROXY_CLIENT, subcmd, sid, data, len, force); return proxy_send(inst, dst, ETCP_RT_ID_TCP_PROXY_CLIENT, subcmd, sid, data, len, force);
} }
// CLOSE может синхронно освободить rc через on_closed: резервируем состояние до push.
static void server_queue_close(struct tcp_proxy_server_conn* rc) {
if (rc->close_queued) return;
rc->close_queued = 1;
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "proxy exit: queue CLOSE peer=%016llx sid=%08x fd=%d",
(unsigned long long)rc->peer_node_id, rc->stream_id, (int)rc->tc->sock);
if (tcp_conn_push_close(rc->tc) == 0) return;
rc->close_queued = 0;
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "proxy exit: queue CLOSE failed peer=%016llx sid=%08x",
(unsigned long long)rc->peer_node_id, rc->stream_id);
}
// FIN относится только к прочитанному направлению: сначала передаём весь read_queue. // FIN относится только к прочитанному направлению: сначала передаём весь read_queue.
static void server_maybe_finish(struct tcp_proxy_server_conn* rc) { static void server_maybe_finish(struct tcp_proxy_server_conn* rc) {
struct tcp_conn* tc = rc->tc; struct tcp_conn* tc = rc->tc;
@ -64,7 +76,7 @@ static void server_maybe_finish(struct tcp_proxy_server_conn* rc) {
proxy_flow_flush(&rc->flow); proxy_flow_flush(&rc->flow);
} }
if (rc->flow.fin_sent && rc->flow.fin_received && tc->fin_local) { if (rc->flow.fin_sent && rc->flow.fin_received && tc->fin_local) {
if (tcp_conn_push_close(tc) == 0) rc->close_queued = 1; server_queue_close(rc);
} }
} }
@ -377,7 +389,7 @@ void tcp_proxy_server_handle_close(struct UTUN_INSTANCE* inst, uint64_t peer, ui
} }
} }
if (rc->tc->connected) { if (rc->tc->connected) {
if (!rc->close_queued && tcp_conn_push_close(rc->tc) == 0) rc->close_queued = 1; server_queue_close(rc);
} else tcp_proxy_server_conn_free(rc); } else tcp_proxy_server_conn_free(rc);
} }

9
tests/test_proxy_regressions.c

@ -204,6 +204,15 @@ static void server_tests(struct UTUN_INSTANCE* inst) {
CHECK(inst->tcp_proxy_server.conn_count == 1); CHECK(inst->tcp_proxy_server.conn_count == 1);
deliver(inst, 22, ETCP_RT_ID_TCP_PROXY_SERVER, TCP_PROXY_SUBCMD_CLOSE, 2, NULL, 0); deliver(inst, 22, ETCP_RT_ID_TCP_PROXY_SERVER, TCP_PROXY_SUBCMD_CLOSE, 2, NULL, 0);
CHECK(inst->tcp_proxy_server.conn_count == 0); CHECK(inst->tcp_proxy_server.conn_count == 0);
// Сначала FIN от ETCP, затем FIN из OS: on_fin закрывает и освобождает exit-поток.
deliver(inst, 11, ETCP_RT_ID_TCP_PROXY_SERVER, TCP_PROXY_SUBCMD_CONNECT, 3, connect_data, 6);
socket_t c = accept(listener, NULL, NULL); CHECK(c != SOCKET_INVALID); socket_set_nonblocking(c);
deliver(inst, 11, ETCP_RT_ID_TCP_PROXY_SERVER, TCP_PROXY_SUBCMD_FIN, 3, NULL, 0);
CHECK(inst->tcp_proxy_server.conn_count == 1 && recv(c, buf, sizeof(buf), 0) == 0);
message_count = 0;
CHECK(shutdown(c, SHUT_WR) == 0); pump();
CHECK(inst->tcp_proxy_server.conn_count == 0 && count_cmd(TCP_PROXY_SUBCMD_FIN) == 1);
socket_close_wrapper(c);
socket_close_wrapper(a); socket_close_wrapper(b); socket_close_wrapper(listener); socket_close_wrapper(a); socket_close_wrapper(b); socket_close_wrapper(listener);
inst->tcp_proxy_server.enabled = 0; pump(); inst->tcp_proxy_server.enabled = 0; pump();
puts("[PASS] exit streams isolated by node + stream ID, duplicate CONNECT rejected"); puts("[PASS] exit streams isolated by node + stream ID, duplicate CONNECT rejected");

Loading…
Cancel
Save