stcp: write-error закрывает conn отложенно (фикс UAF keepalive/etcp_tcp_send)
Синхронный send-путь (stcp_link_send → queue_data_put → tx_queue_cb → flush_pending → try_send) при ошибке сокета вызывал stcp_conn_do_close → on_close → tcp_link_close_cb → etcp_link_close внутри стека вызывающего, освобождая ETCP_LINK под keepalive_timer_cb/etcp_tcp_send (use-after-free, SIGSEGV в keepalive_timer_cb:210). Теперь write-error планирует отложенный close (uasync_call_soon) с отменой handle в stcp_conn_do_close; первый err сохраняется; flush_pending не работает вне STCP_STATE_DATA. Мёртвый hook on_write_error удалён.
This commit is contained in:
1 parent
8aaac45020
commit
f6e6e2c73f
4 files changed
+27
-7
No files matched your search
@@ -148,6 +148,26 @@ int stcp_try_send(struct stcp_conn *c, uint8_t *data, size_t len) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Write-error больше не закрывает conn синхронно: do_close → on_close →
|
||||
// tcp_link_close_cb → etcp_link_close освобождает ETCP_LINK, поэтому при
|
||||
// синхронном send-пути (stcp_link_send → flush → try_send) каскад уходил бы
|
||||
// в стек вызывающего (keepalive_timer_cb/etcp_tcp_send) — use-after-free.
|
||||
static void stcp_close_deferred(void *arg) {
|
||||
struct stcp_conn *c = (struct stcp_conn *)arg;
|
||||
if (!c) return;
|
||||
c->close_call_soon = NULL;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "stcp write-error close deferred: err=%d state=%d sock=%d",
|
||||
c->close_err, (int)c->state, (int)c->sock);
|
||||
stcp_conn_do_close(c, c->close_err);
|
||||
}
|
||||
|
||||
static void stcp_schedule_close(struct stcp_conn *c, int err) {
|
||||
if (!c) return;
|
||||
if (c->close_call_soon) return; // уже запланирован — первый err остаётся
|
||||
c->close_err = err;
|
||||
c->close_call_soon = uasync_call_soon(c->ua, c, stcp_close_deferred);
|
||||
}
|
||||
|
||||
void stcp_write_cb(socket_t sock, void *arg) {
|
||||
struct stcp_conn *c = (struct stcp_conn *)arg;
|
||||
if (!c->send_buf) { uasync_set_socket_write(c->ua, c->socket_id, 0); return; }
|
||||
@@ -156,7 +176,7 @@ void stcp_write_cb(socket_t sock, void *arg) {
|
||||
int err = socket_get_error();
|
||||
if (err == ERR_AGAIN || err == ERR_WOULDBLOCK) return;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_write_cb send failed err=%d", err);
|
||||
if (c->on_write_error) c->on_write_error(c, err);
|
||||
stcp_schedule_close(c, err);
|
||||
return;
|
||||
}
|
||||
c->send_offset += (size_t)sent;
|
||||
@@ -169,7 +189,7 @@ void stcp_write_cb(socket_t sock, void *arg) {
|
||||
}
|
||||
|
||||
void stcp_flush_pending(struct stcp_conn *c) {
|
||||
if (!c) return;
|
||||
if (!c || c->state != STCP_STATE_DATA) return; // conn закрыт/не DATA — слать нечего
|
||||
while (!c->send_buf && c->pending_head) {
|
||||
struct pending_entry *pe = c->pending_head;
|
||||
c->pending_head = pe->next;
|
||||
@@ -180,14 +200,14 @@ void stcp_flush_pending(struct stcp_conn *c) {
|
||||
if (!enc) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_flush_pending malloc enc failed len=%zu", pe->len); if (pe->data) u_free(pe->data); u_free(pe); continue; }
|
||||
if (stcp_frame_encrypt(c, pe->data, pe->len, enc, &enc_len)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_flush_pending encrypt failed"); u_free(enc); if (pe->data) u_free(pe->data); u_free(pe);
|
||||
if (c->on_write_error) c->on_write_error(c, ECANCELED);
|
||||
stcp_schedule_close(c, ECANCELED);
|
||||
return;
|
||||
}
|
||||
if (pe->data) u_free(pe->data);
|
||||
u_free(pe);
|
||||
|
||||
int r = stcp_try_send(c, enc, enc_len);
|
||||
if (r < 0) { u_free(enc); if (c->on_write_error) c->on_write_error(c, ECANCELED); return; }
|
||||
if (r < 0) { u_free(enc); stcp_schedule_close(c, ECANCELED); return; }
|
||||
if (r > 0) return;
|
||||
}
|
||||
}
|
||||
@@ -321,6 +341,7 @@ void stcp_conn_do_close(struct stcp_conn *c, int err) {
|
||||
stcp_server_remove_conn(c);
|
||||
|
||||
if (c->hs_timer) { uasync_cancel_timeout(c->ua, c->hs_timer); c->hs_timer = NULL; }
|
||||
if (c->close_call_soon) { uasync_call_soon_cancel(c->ua, c->close_call_soon); c->close_call_soon = NULL; }
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_conn close is_server=%d prev_state=%d err=%d sock=%d", c->is_server, prev, err, (int)c->sock);
|
||||
|
||||
|
||||
@@ -124,7 +124,6 @@ struct stcp_conn {
|
||||
|
||||
struct pending_entry *pending_head;
|
||||
struct pending_entry *pending_tail;
|
||||
void (*on_write_error)(struct stcp_conn *c, int err);
|
||||
|
||||
void (*on_ready)(struct stcp_conn *conn, void *arg);
|
||||
void *ready_arg;
|
||||
@@ -134,6 +133,8 @@ struct stcp_conn {
|
||||
|
||||
void *hs_timer; // handshake timeout handle, NULL when not active
|
||||
void *free_on_close; // если не NULL — uasync_call_soon для u_free(arg) при закрытии
|
||||
void *close_call_soon; // handle отложенного do_close при write-error; NULL = не запланирован
|
||||
int close_err; // сохранённый err для отложенного close
|
||||
|
||||
// recv state machine (stcp_recv_try)
|
||||
size_t recv_need; // expected next chunk size (0 = disabled)
|
||||
|
||||
@@ -274,7 +274,6 @@ struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uin
|
||||
c->ua = ua; c->state = STCP_STATE_INIT; c->is_server = 0; c->my_keys = *keys;
|
||||
c->on_ready = ready_cb; c->ready_arg = arg;
|
||||
c->on_close = close_cb; c->close_arg = close_arg;
|
||||
c->on_write_error = stcp_conn_do_close;
|
||||
c->tx_cb = stcp_tx_queue_cb;
|
||||
c->got_initial_pkt = got_initial_pkt;
|
||||
c->etcp_conn = etcp_conn;
|
||||
|
||||
@@ -299,7 +299,6 @@ static void server_accept_cb(socket_t listen_sock, void *arg) {
|
||||
c->state = STCP_STATE_HS_SERVER_WAIT;
|
||||
c->is_server = 1;
|
||||
c->allocated = 1;
|
||||
c->on_write_error = stcp_conn_do_close;
|
||||
c->tx_cb = stcp_tx_queue_cb;
|
||||
c->my_keys = srv->my_keys;
|
||||
memcpy(c->my_ed25519_pubkey, srv->my_ed25519_pubkey, SC_PUBKEY_SIZE);
|
||||
|
||||
Reference in new issue
Block a user