From f667e7bd74893ad0f0ae305f13c0b569403cbcc9 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 11 Aug 2026 13:37:42 +0300 Subject: [PATCH] fix: prevent use-after-free in etcp_ack_recv when link closed with pending inflight When etcp_link_close() frees a link while INFLIGHT_PACKET entries still reference it via last_link, a later ACK causes SIGSEGV in bbr_main(link->bbr=NULL). Fix: - etcp_inflight_nullify_link(): nullifies last_link in both input_wait_ack and input_send_q for entries pointing to dead_link - Called from etcp_link_close() before u_free(link) - Guard etcp_ack_recv: check acked_pkt->last_link->bbr before accessing BBR/inflight stats - Also fix bbr leak in etcp_link_close !link->conn path Verified: 30/30 runs of test_etcp_link_stress pass (was ~50% crash) Full suite: 71/71 pass --- src/transport_layer/etcp.c | 2 +- src/transport_layer/etcp_connections.c | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/transport_layer/etcp.c b/src/transport_layer/etcp.c index bcc79ebf..87b22ecd 100644 --- a/src/transport_layer/etcp.c +++ b/src/transport_layer/etcp.c @@ -1410,7 +1410,7 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d } // === subtract inflight from the LAST link the packet was sent on === - if (acked_pkt->last_link) { + if (acked_pkt->last_link && acked_pkt->last_link->bbr) { struct ETCP_LINK* link = acked_pkt->last_link; link->inflight_bytes -= acked_pkt->ll.len; link->inflight_packets--; diff --git a/src/transport_layer/etcp_connections.c b/src/transport_layer/etcp_connections.c index 6fa82285..02a692a4 100644 --- a/src/transport_layer/etcp_connections.c +++ b/src/transport_layer/etcp_connections.c @@ -1074,6 +1074,18 @@ void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim) { etcp_conn_on_inflight_lim_changed(link->etcp); } +// Обнуляет last_link для всех inflight-записей ссылающихся на dead_link. +// Вызывается перед u_free(link) чтобы etcp_ack_recv не упал на use-after-free. +static void etcp_inflight_nullify_link(struct ll_queue* q, struct ETCP_LINK* dead_link) { + if (!q) return; + struct ll_entry* entry = q->head; + while (entry) { + struct INFLIGHT_PACKET* inf = (struct INFLIGHT_PACKET*)entry->data; + if (inf->last_link == dead_link) inf->last_link = NULL; + entry = entry->next; + } +} + void etcp_link_close(struct ETCP_LINK* link) { DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, ""); if (!link) return; @@ -1096,6 +1108,7 @@ void etcp_link_close(struct ETCP_LINK* link) { if (link->shaper_timer) uasync_cancel_timeout(link->etcp->instance->ua, link->shaper_timer); if (link->keepalive_timer) uasync_cancel_timeout(link->etcp->instance->ua, link->keepalive_timer); etcp_conn_on_inflight_lim_changed(link->etcp); + u_free(link->bbr); u_free(link); return; } @@ -1136,6 +1149,8 @@ void etcp_link_close(struct ETCP_LINK* link) { link->etcp->log_name, link->local_link_id, sockaddr_storage_to_str(&link->remote_addr).str, link->total_decrypted, (unsigned long long)link->acked_bytes, link->inflight_bytes, link->inflight_packets); etcp_fire_link_status_cbk(link, link->link_state, link->link_status); + etcp_inflight_nullify_link(link->etcp->input_wait_ack, link); + etcp_inflight_nullify_link(link->etcp->input_send_q, link); u_free(link->bbr); u_free(link); }