Browse Source

etcp: фикс double-free буферов паковщика и повторных free после неудачного queue_data_put

- 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
proxy
evgeny 2 weeks ago
parent
commit
841b3042cf
  1. 4
      lib/memory_pool.c
  2. 6
      src/call/call.c
  3. 3
      src/chat/merkle_sync.c
  4. 2
      src/transport_layer/dummynet.c
  5. 26
      src/transport_layer/etcp.c
  6. 19
      src/transport_layer/pkt_normalizer.c
  7. 3
      src/tun_if.c

4
lib/memory_pool.c

@ -39,9 +39,9 @@ static void pool_check_and_clear_tags(struct memory_pool* pool, void* obj, const
while (_halt) {} while (_halt) {}
} }
if (*counter == 0) { 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, 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; volatile int _halt = 1;
while (_halt) {} while (_halt) {}
} }

6
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->ctx = ctx;
s->tx_q = queue_new(ctx->inst->ua, 0, 0, 0, "call_txq"); s->tx_q = queue_new(ctx->inst->ua, 0, 0, 0, "call_txq");
if (!s->tx_q) { queue_entry_free(qe); return NULL; } 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) { if (queue_data_put_with_index(ctx->sessions, qe) != 0) {
queue_free(s->tx_q); queue_free(tx_q);
queue_entry_free(qe);
return NULL; return NULL;
} }
return s; return s;

3
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); e->dgram = buf; e->len = (uint16_t)(1 + plen);
int was_empty = (queue_entry_count(s->out_q) == 0); 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) if (was_empty && conn->send_input_q)
queue_waiter_wait(conn->send_input_q, &s->waiter, ms_outq_drain_cb, s); queue_waiter_wait(conn->send_input_q, &s->waiter, ms_outq_drain_cb, s);
return 0; return 0;

2
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; uint32_t id = (uint32_t)(uintptr_t)entry;
// При провале queue_data_put сам освобождает entry, повторный free дал бы double-free.
if (queue_data_put(dir->queue, entry) != 0) { if (queue_data_put(dir->queue, entry) != 0) {
dir->stats.dropped++; dir->stats.dropped++;
DEBUG_WARN(DEBUG_CATEGORY_DUMMYNET, "Dir %d: queue_data_put failed", dir_idx); DEBUG_WARN(DEBUG_CATEGORY_DUMMYNET, "Dir %d: queue_data_put failed", dir_idx);
queue_entry_free(entry);
return; return;
} }

26
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); 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 // 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) { 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); 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; return -1;
} }
@ -923,10 +922,9 @@ static void input_queue_cb(struct ll_queue* q, void* arg) {
int len=p->ll.len;// сохраним len int len=p->ll.len;// сохраним len
// Add to send queue // 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) { 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); 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); DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] EXIT (queue put failed)", etcp->log_name);
return; 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, 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); next_expected_id, etcp->output_queue->count);
uint16_t pkt_len = rx_pkt->ll.len; uint16_t pkt_len = rx_pkt->ll.len;
if (queue_data_put(etcp->output_queue, (struct ll_entry*)rx_pkt) == 0) { // На провале queue_data_put сам освобождает rx_pkt — re-put дал бы double-free.
delivered_bytes += pkt_len; if (queue_data_put(etcp->output_queue, (struct ll_entry*)rx_pkt) != 0) {
delivered_count++;
} else {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to add packet id=%u to output_queue", etcp->log_name, DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to add packet id=%u to output_queue", etcp->log_name,
next_expected_id); 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; 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 // Update state for next iteration
etcp->last_delivered_id = next_expected_id; 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); 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) { 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->instance->data_pool, acked_pkt->ll.dgram);
} }
memory_pool_free(etcp->inflight_pool, acked_pkt); 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); DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX pkt dlen=%d", etcp->log_name, len);
while (len >= 1) { while (len >= 1) {
// Ре-энтрантный reinit/close (из pn_unpacker_cb/etcp_int_recv) — дальше разбирать пакет нельзя.
if (etcp->state == 2 || etcp->reinit_pending) break;
uint8_t type = data[0]; uint8_t type = data[0];
// Process sections as per protocol.txt // 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); DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
volatile int _halt = 1; while (_halt) {} volatile int _halt = 1; while (_halt) {}
} }

19
src/transport_layer/pkt_normalizer.c

@ -116,6 +116,7 @@ void pn_deinit(struct PKTNORM* pn) {
} }
if (pn->data) { 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); memory_pool_free(pn->etcp->instance->data_pool, pn->data);
} }
if (pn->recvpart) { if (pn->recvpart) {
@ -245,13 +246,16 @@ static void pn_send_to_etcp(struct PKTNORM* pn) {
frag->ll.dgram_pool = pn->etcp->instance->data_pool; frag->ll.dgram_pool = pn->etcp->instance->data_pool;
frag->ll.memlen = pn->etcp->instance->data_pool->object_size; frag->ll.memlen = pn->etcp->instance->data_pool->object_size;
// Зануляем 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); 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); 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); queue_data_put(pn->etcp->input_queue, (struct ll_entry*)frag);
// Сбросить структуру (dgram передан во фрагмент, не освобождаем)
pn->data = NULL;
pn->data_ptr = 0;
} }
// Internal: Renew sndpart buffer // 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; uint32_t recv_len = pn->recvpart->len;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "unpacked dgram (size=%d)", recv_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); 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_pkts++;
pn->out_total_bytes += recv_len; pn->out_total_bytes += recv_len;
pn->recvpart = NULL;
} }
} }

3
src/tun_if.c

@ -72,10 +72,9 @@ static void tun_read_callback(int fd, void* user_arg)
tun->packets_read++; tun->packets_read++;
// Add to output queue (TUN → routing) // Add to output queue (TUN → routing)
// При провале queue_data_put сам освобождает entry+dgram, повторный free дал бы double-free.
if (queue_data_put(tun->output_queue, pkt) != 0) { if (queue_data_put(tun->output_queue, pkt) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to add packet to output queue"); DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to add packet to output queue");
u_free(packet_data);
queue_entry_free(pkt);
tun->read_errors++; tun->read_errors++;
return; return;
} }

Loading…
Cancel
Save