sql: detach the stored socket in on_close/on_connect_error

Closed
RO
2mo ago
+182-0

What

PostgresSQLConnection and JSMySQLConnection kept the raw us_socket_t* in self.socket after usockets dispatched on_close / on_connect_error. usockets frees closed sockets at us_internal_loop_post later in the same tick, so any subsequent read through self.socket (the connectionTimeout EventLoopTimer, ref() / unref() / close() on the native handle, update_has_pending_activity) dereferenced freed memory.

ASan signature from the reproduction:

AddressSanitizer: heap-use-after-free
READ of size 1 at 0x...05d2 (s->flags.is_closed, offset +18 of a freed 104-byte us_socket_t)
  us_socket_is_closed                      packages/bun-usockets/src/socket.c:143
  PostgresSQLConnection::update_has_pending_activity / ref_and_close
freed by:      us_internal_free_closed_sockets  (end of tick)
allocated by:  us_create_poll ← us_socket_group_connect

Fix

Detach the stored handle at the start of SocketHandler::on_close and SocketHandler::on_connect_error for both drivers, matching what the Valkey client already does in src/runtime/valkey_jsc/js_valkey.rs. A detached socket reports is_closed() == true without dereferencing anything, and write() / close() on it are no-ops.

Test

test/js/sql/sql-connection-socket-uaf.test.ts captures the native connection handle by shadowing the query handle's .run(connection, query), lets the mock server drop the socket, yields past us_internal_free_closed_sockets, then calls .ref() (Postgres) / .close() (MySQL) on the native handle. Under ASan this deterministically reads the freed is_closed flag without the fix and exits cleanly with it.

Existing coverage that exercises the modified callbacks still passes:

  • test/js/sql/sql-connect-error-reporting.test.ts
  • test/js/sql/sql-close-pending-connection.test.ts
  • test/js/sql/sql-mysql-clean-reentry.test.ts
  • test/js/sql/postgres-tls-ctx-leak.test.ts

Related: #32573 fixes a separate re-entrant TLS close crash and also detaches in Postgres on_close; this PR additionally covers on_connect_error for both drivers and adds a deterministic ASan regression.