From 841b3042cfa4f05edfc6939d6285aa4ef36ebf5d Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 19 Sep 2026 21:17:33 +0300 Subject: [PATCH] =?UTF-8?q?etcp:=20=D1=84=D0=B8=D0=BA=D1=81=20double-free?= =?UTF-8?q?=20=D0=B1=D1=83=D1=84=D0=B5=D1=80=D0=BE=D0=B2=20=D0=BF=D0=B0?= =?UTF-8?q?=D0=BA=D0=BE=D0=B2=D1=89=D0=B8=D0=BA=D0=B0=20=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B2=D1=82=D0=BE=D1=80=D0=BD=D1=8B=D1=85=20free=20?= =?UTF-8?q?=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20=D0=BD=D0=B5=D1=83=D0=B4=D0=B0?= =?UTF-8?q?=D1=87=D0=BD=D0=BE=D0=B3=D0=BE=20queue=5Fdata=5Fput?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pn_send_to_etcp/pn_unpacker_cb: занулять pn->data/pn->recvpart ДО queue_data_put (ре-энтрантный close/reinit в синхронной цепочке освобождал буфер повторно) - etcp_output_try_assembly: гард state==2/reinit_pending + убрать re-put освобождённого entry - etcp_conn_input: гард ре-энтрантного reinit/close в цикле секций, локальный etcp вместо pkt->link - убраны повторные free после queue_data_put (он сам освобождает entry+dgram) в 6 местах - диагностика: obj=%p в DOUBLE FREE, логи free dgram в pn_deinit/ack_recv --- lib/memory_pool.c | 4 ++-- src/call/call.c | 6 ++++-- src/chat/merkle_sync.c | 3 ++- src/transport_layer/dummynet.c | 2 +- src/transport_layer/etcp.c | 26 +++++++++++++++----------- src/transport_layer/pkt_normalizer.c | 21 ++++++++++++++------- src/tun_if.c | 3 +-- 7 files changed, 39 insertions(+), 26 deletions(-) diff --git a/lib/memory_pool.c b/lib/memory_pool.c index daf12341..75d87a79 100644 --- a/lib/memory_pool.c +++ b/lib/memory_pool.c @@ -39,9 +39,9 @@ static void pool_check_and_clear_tags(struct memory_pool* pool, void* obj, const while (_halt) {} } if (*counter == 0) { - DEBUG_ERROR(DEBUG_CATEGORY_SYS, "pool_free DOUBLE FREE pool=%p name=%s sz=%zu allocs=%zu reuse=%zu alloc=%s free=%s", + DEBUG_ERROR(DEBUG_CATEGORY_SYS, "pool_free DOUBLE FREE pool=%p name=%s sz=%zu allocs=%zu reuse=%zu obj=%p alloc=%s free=%s", pool, pool->name ? pool->name : "?", pool->object_size, pool->allocations, pool->reuse_count, - *loc ? *loc : "(null)", location); + obj, *loc ? *loc : "(null)", location); volatile int _halt = 1; while (_halt) {} } diff --git a/src/call/call.c b/src/call/call.c index 36a3c740..af63f6fb 100644 --- a/src/call/call.c +++ b/src/call/call.c @@ -158,9 +158,11 @@ static struct call_session* call_session_new(struct call_ctx* ctx, uint64_t call s->ctx = ctx; s->tx_q = queue_new(ctx->inst->ua, 0, 0, 0, "call_txq"); if (!s->tx_q) { queue_entry_free(qe); return NULL; } + // При провале queue_data_put_with_index сам освобождает qe (вместе с s), поэтому + // сохраняем tx_q заранее: queue_entry_free(qe) повторно дал бы double-free. + struct ll_queue* tx_q = s->tx_q; if (queue_data_put_with_index(ctx->sessions, qe) != 0) { - queue_free(s->tx_q); - queue_entry_free(qe); + queue_free(tx_q); return NULL; } return s; diff --git a/src/chat/merkle_sync.c b/src/chat/merkle_sync.c index 1bd50a73..f4f1105f 100644 --- a/src/chat/merkle_sync.c +++ b/src/chat/merkle_sync.c @@ -342,7 +342,8 @@ static int ms_outq_enqueue(struct ms_session* s, const uint8_t* payload, size_t e->dgram = buf; e->len = (uint16_t)(1 + plen); int was_empty = (queue_entry_count(s->out_q) == 0); - if (queue_data_put(s->out_q, e) != 0) { u_free(buf); queue_entry_free(e); return -1; } + // При провале queue_data_put сам освобождает entry+dgram, повторный free дал бы double-free. + if (queue_data_put(s->out_q, e) != 0) return -1; if (was_empty && conn->send_input_q) queue_waiter_wait(conn->send_input_q, &s->waiter, ms_outq_drain_cb, s); return 0; diff --git a/src/transport_layer/dummynet.c b/src/transport_layer/dummynet.c index c9e6c92c..534a4215 100644 --- a/src/transport_layer/dummynet.c +++ b/src/transport_layer/dummynet.c @@ -304,10 +304,10 @@ static void dummynet_delay_callback(void* user_arg) { /* Добавляем в очередь */ uint32_t id = (uint32_t)(uintptr_t)entry; + // При провале queue_data_put сам освобождает entry, повторный free дал бы double-free. if (queue_data_put(dir->queue, entry) != 0) { dir->stats.dropped++; DEBUG_WARN(DEBUG_CATEGORY_DUMMYNET, "Dir %d: queue_data_put failed", dir_idx); - queue_entry_free(entry); return; } diff --git a/src/transport_layer/etcp.c b/src/transport_layer/etcp.c index 5482399a..d9dc915d 100644 --- a/src/transport_layer/etcp.c +++ b/src/transport_layer/etcp.c @@ -786,10 +786,9 @@ int etcp_int_send(struct ETCP_CONN* etcp, const void* data, uint16_t len) { DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] created PACKET %p with data %p (len=%zu)", etcp->log_name, pkt, packet_data, len); // Add to input queue - input_queue_cb will process it + // При провале queue_data_put сам освобождает entry+dgram, повторный free дал бы double-free. if (queue_data_put(etcp->input_queue, (struct ll_entry*)pkt) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to add to input queue", etcp->log_name); - queue_dgram_free(&pkt->ll); - queue_entry_free(&pkt->ll); return -1; } @@ -923,10 +922,9 @@ static void input_queue_cb(struct ll_queue* q, void* arg) { int len=p->ll.len;// сохраним len // Add to send queue + // При провале queue_data_put_with_index сам освобождает entry+dgram, повторный free дал бы double-free. if (queue_data_put_with_index(etcp->input_send_q, &p->ll) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to add packet seq=%u to input_send_q", etcp->log_name, p->seq); - queue_dgram_free(&p->ll); - queue_entry_free(&p->ll); DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] EXIT (queue put failed)", etcp->log_name); return; } @@ -1397,16 +1395,18 @@ void etcp_output_try_assembly(struct ETCP_CONN* etcp) { DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] moving packet id=%u to output_queue (qlen=%d)", etcp->log_name, next_expected_id, etcp->output_queue->count); uint16_t pkt_len = rx_pkt->ll.len; - if (queue_data_put(etcp->output_queue, (struct ll_entry*)rx_pkt) == 0) { - delivered_bytes += pkt_len; - delivered_count++; - } else { + // На провале queue_data_put сам освобождает rx_pkt — re-put дал бы double-free. + if (queue_data_put(etcp->output_queue, (struct ll_entry*)rx_pkt) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to add packet id=%u to output_queue", etcp->log_name, next_expected_id); - // Put it back in recv_q if we can't add to output_queue - queue_data_put_with_index(etcp->recv_q, (struct ll_entry*)rx_pkt); break; } + // pn_unpacker_cb (вызывается из put синхронно) мог ре-энтрантно сделать reinit/close: + // при reinit last_delivered_id уже сброшен в 0 и recv_q очищен, при close state==2. + if (etcp->state == 2 || etcp->reinit_pending) break; + + delivered_bytes += pkt_len; + delivered_count++; // Update state for next iteration etcp->last_delivered_id = next_expected_id; @@ -1506,6 +1506,7 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] TX removed packet seq=%u from wait_ack, unacked_bytes now %u total acked=%u", etcp->log_name, seq, etcp->unacked_bytes, etcp->ack_packets_count); if (acked_pkt->ll.dgram) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] TX-ACK free dgram=%p seq=%u", etcp->log_name, acked_pkt->ll.dgram, seq); memory_pool_free(etcp->instance->data_pool, acked_pkt->ll.dgram); } memory_pool_free(etcp->inflight_pool, acked_pkt); @@ -1542,6 +1543,8 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) { DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX pkt dlen=%d", etcp->log_name, len); while (len >= 1) { + // Ре-энтрантный reinit/close (из pn_unpacker_cb/etcp_int_recv) — дальше разбирать пакет нельзя. + if (etcp->state == 2 || etcp->reinit_pending) break; uint8_t type = data[0]; // Process sections as per protocol.txt @@ -1812,7 +1815,8 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) { } - if (memory_pool_is_freed(pkt->link->etcp->instance->pkt_pool, pkt)) { + // Используем локальный etcp (валиден до phase 2 close): pkt->link мог быть освобождён ре-энтрантным close. + if (memory_pool_is_freed(etcp->instance->pkt_pool, pkt)) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt); volatile int _halt = 1; while (_halt) {} } diff --git a/src/transport_layer/pkt_normalizer.c b/src/transport_layer/pkt_normalizer.c index e83ef5ee..6c9232e7 100644 --- a/src/transport_layer/pkt_normalizer.c +++ b/src/transport_layer/pkt_normalizer.c @@ -116,6 +116,7 @@ void pn_deinit(struct PKTNORM* pn) { } if (pn->data) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "pn_deinit: free pn->data=%p ptr=%d", pn->data, pn->data_ptr); memory_pool_free(pn->etcp->instance->data_pool, pn->data); } if (pn->recvpart) { @@ -244,14 +245,17 @@ static void pn_send_to_etcp(struct PKTNORM* pn) { frag->ll.len = pn->data_ptr; frag->ll.dgram_pool = pn->etcp->instance->data_pool; frag->ll.memlen = pn->etcp->instance->data_pool->object_size; - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "pn->etcp: size=%d memlen=%d frag_size=%d", frag->ll.len, frag->ll.memlen, pn->frag_size); - if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP, "NORM->ETCP", pn->data, frag->ll.len); - queue_data_put(pn->etcp->input_queue, (struct ll_entry*)frag); - // Сбросить структуру (dgram передан во фрагмент, не освобождаем) + // Зануляем pn->data ДО queue_data_put: последняя синхронно гонит input_queue_cb → send_q → wait_ack, + // в которой может ре-энтрантно сработать close → pn_deinit → free(pn->data). + // Иначе pn_deinit освободит буфер, уже переданный во INFLIGHT-запись (double free). pn->data = NULL; pn->data_ptr = 0; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "pn->etcp: size=%d memlen=%d frag_size=%d", frag->ll.len, frag->ll.memlen, pn->frag_size); + if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP, "NORM->ETCP", frag->ll.dgram, frag->ll.len); + + queue_data_put(pn->etcp->input_queue, (struct ll_entry*)frag); } // Internal: Renew sndpart buffer @@ -404,10 +408,13 @@ static void pn_unpacker_cb(struct ll_queue* q, void* arg) { uint32_t recv_len = pn->recvpart->len; DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "unpacked dgram (size=%d)", recv_len); if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP, "NORM->", pn->recvpart->dgram, recv_len); - queue_data_put(pn->output, pn->recvpart); + // Зануляем recvpart ДО queue_data_put: etcp_int_recv может ре-энтрантно вызвать + // reinit → pn_unpacker_reset_state → free(pn->recvpart), что даст double-free. + struct ll_entry* out = pn->recvpart; + pn->recvpart = NULL; + queue_data_put(pn->output, out); pn->out_total_pkts++; pn->out_total_bytes += recv_len; - pn->recvpart = NULL; } } diff --git a/src/tun_if.c b/src/tun_if.c index e8864726..f8d0b0f0 100644 --- a/src/tun_if.c +++ b/src/tun_if.c @@ -72,10 +72,9 @@ static void tun_read_callback(int fd, void* user_arg) tun->packets_read++; // Add to output queue (TUN → routing) + // При провале queue_data_put сам освобождает entry+dgram, повторный free дал бы double-free. if (queue_data_put(tun->output_queue, pkt) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to add packet to output queue"); - u_free(packet_data); - queue_entry_free(pkt); tun->read_errors++; return; }