From d1f1fde691a0de8beaf4143216c660aa05ef1d4d Mon Sep 17 00:00:00 2001 From: Dave Cramer Date: Fri, 22 May 2026 07:02:25 -0700 Subject: [PATCH] Fix double-free / heap corruption during SQLDisconnect cleanup (#189) CC_cleanup() was freeing statement and descriptor objects via SC_Destructor(), but the ODBC Driver Manager still held handles to them. When the DM later called SQLFreeStmt(SQL_DROP) via SQLFreeHandle(), the driver dereferenced freed memory, causing the double-free detected by Application Verifier with Page Heap enabled. Fix by detaching statements/descriptors in CC_cleanup() without freeing them. The DM will free them later through the normal SQLFreeHandle path. Also allow SQLFreeStmt(SQL_DROP) to proceed when hdbc is NULL (the expected state after disconnect), and remove misleading error-return logic in SC_Destructor that reported failure after already freeing. --- connection.c | 19 ++++++++++++++----- descriptor.c | 15 +++++++++------ odbcapi.c | 13 +++++++++---- statement.c | 18 +++++++----------- 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/connection.c b/connection.c index ae751c0f..6eb4df67 100644 --- a/connection.c +++ b/connection.c @@ -681,28 +681,37 @@ CC_cleanup(ConnectionClass *self, BOOL keepCommunication) MYLOG(0, "after PQfinish\n"); - /* Free all the stmts on this connection */ + /* Detach all the stmts on this connection */ for (i = 0; i < self->num_stmts; i++) { stmt = self->stmts[i]; if (stmt) { + QResultClass *res; + stmt->hdbc = NULL; /* prevent any more dbase interactions */ - SC_Destructor(stmt); + /* + * NULL out the conn pointer in results so they don't + * try to use the dead connection. The statement itself + * is NOT freed here — it will be freed later when the + * Driver Manager calls SQLFreeStmt(SQL_DROP). + */ + for (res = SC_get_Result(stmt); res; res = QR_nextr(res)) + res->conn = NULL; + if (stmt->parsed) + stmt->parsed->conn = NULL; self->stmts[i] = NULL; } } - /* Free all the descs on this connection */ + /* Detach all the descs on this connection */ for (i = 0; i < self->num_descs; i++) { desc = self->descs[i]; if (desc) { DC_get_conn(desc) = NULL; /* prevent any more dbase interactions */ - DC_Destructor(desc); - free(desc); self->descs[i] = NULL; } } diff --git a/descriptor.c b/descriptor.c index 17e2557a..c8ad4d19 100644 --- a/descriptor.c +++ b/descriptor.c @@ -457,14 +457,17 @@ PGAPI_FreeDesc(SQLHDESC DescriptorHandle) DC_Destructor(desc); if (!embedded) { - int i; - - for (i = 0; i < conn->num_descs; i++) + if (conn) { - if (conn->descs[i] == desc) + int i; + + for (i = 0; i < conn->num_descs; i++) { - conn->descs[i] = NULL; - break; + if (conn->descs[i] == desc) + { + conn->descs[i] = NULL; + break; + } } } free(desc); diff --git a/odbcapi.c b/odbcapi.c index 494b08dc..1e8a0261 100644 --- a/odbcapi.c +++ b/odbcapi.c @@ -399,12 +399,17 @@ SQLFreeStmt(HSTMT StatementHandle, if (Option == SQL_DROP) { conn = stmt->hdbc; - if (!conn || (conn->status != CONN_CONNECTED && conn->status != CONN_EXECUTING)) - return SQL_INVALID_HANDLE; if (conn) + { + if (conn->status != CONN_CONNECTED && conn->status != CONN_EXECUTING) + return SQL_INVALID_HANDLE; ENTER_CONN_CS(conn); - if (!conn || (conn->status != CONN_CONNECTED && conn->status != CONN_EXECUTING)) - return SQL_INVALID_HANDLE; + if (conn->status != CONN_CONNECTED && conn->status != CONN_EXECUTING) + { + LEAVE_CONN_CS(conn); + return SQL_INVALID_HANDLE; + } + } } else ENTER_STMT_CS(stmt); diff --git a/statement.c b/statement.c index db1d028d..659d3da9 100644 --- a/statement.c +++ b/statement.c @@ -491,7 +491,6 @@ SC_Constructor(ConnectionClass *conn) char SC_Destructor(StatementClass *self) { - char cRet = TRUE; CSTR func = "SC_Destructor"; QResultClass *res = SC_get_Result(self); @@ -518,12 +517,6 @@ SC_Destructor(StatementClass *self) SC_initialize_stmts(self, TRUE); - if(self->hdbc && !self->hdbc->pqconn) - { - SC_set_error(self, STMT_COMMUNICATION_ERROR, "connection error.", func); - cRet = FALSE; - } - /* Free the parsed table information */ SC_initialize_cols_info(self, FALSE, TRUE); @@ -551,7 +544,7 @@ SC_Destructor(StatementClass *self) MYLOG(0, "leaving\n"); - return cRet; + return TRUE; } void @@ -895,10 +888,13 @@ SC_recycle_statement(StatementClass *self) return FALSE; } - if (SC_get_conn(self)->unnamed_prepared_stmt == self) - SC_get_conn(self)->unnamed_prepared_stmt = NULL; - conn = SC_get_conn(self); + if (!conn) + return TRUE; + + if (conn->unnamed_prepared_stmt == self) + conn->unnamed_prepared_stmt = NULL; + switch (self->status) { case STMT_ALLOCATED: